UI update 02
This commit is contained in:
@@ -12,6 +12,7 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
|
||||
private readonly Dictionary<int, int> currentExpByHeroId = new Dictionary<int, int>();
|
||||
private readonly Dictionary<int, int> unlockedTierByHeroId = new Dictionary<int, int>();
|
||||
private readonly Dictionary<int, bool> levelLockByHeroId = new Dictionary<int, bool>();
|
||||
private readonly Dictionary<int, bool> autoBreakthroughEnabledByHeroId = new Dictionary<int, bool>();
|
||||
private readonly Dictionary<int, int> deployCountsByHeroId = new Dictionary<int, int>();
|
||||
private readonly Dictionary<int, int> finishCountsByHeroId = new Dictionary<int, int>();
|
||||
@@ -153,6 +154,13 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
return autoBreakthroughEnabledByHeroId.TryGetValue(heroId, out value) && value;
|
||||
}
|
||||
|
||||
public bool IsLevelLockEnabled(int heroId)
|
||||
{
|
||||
InitializeIfNeeded();
|
||||
bool value;
|
||||
return levelLockByHeroId.TryGetValue(heroId, out value) && value;
|
||||
}
|
||||
|
||||
public void SetCurrentExp(AllyHero_SO hero, int value)
|
||||
{
|
||||
if (hero == null || hero.ally_heroID <= 0)
|
||||
@@ -164,6 +172,11 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
int safeValue = Mathf.Max(0, value);
|
||||
currentExpByHeroId[hero.ally_heroID] = safeValue;
|
||||
hero.ally_currentEXP = safeValue;
|
||||
int unlockedTier = GetUnlockedTierIndex(hero.ally_heroID);
|
||||
bool levelLock = ResolveLevelLockState(hero, safeValue, unlockedTier);
|
||||
levelLockByHeroId[hero.ally_heroID] = levelLock;
|
||||
hero.level_lock = levelLock;
|
||||
SyncLegacySelectedSlotExpKeys(hero.ally_heroID, safeValue);
|
||||
MarkDirty(hero);
|
||||
SaveNow();
|
||||
if (OnHeroGrowthChanged != null)
|
||||
@@ -183,7 +196,11 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
int safeValue = Mathf.Clamp(value, 0, 3);
|
||||
unlockedTierByHeroId[hero.ally_heroID] = safeValue;
|
||||
hero.ally_growthUnlockedTierIndex = safeValue;
|
||||
hero.ally_currentEXP = GetCurrentExp(hero.ally_heroID);
|
||||
int currentExp = GetCurrentExp(hero.ally_heroID);
|
||||
hero.ally_currentEXP = currentExp;
|
||||
bool levelLock = ResolveLevelLockState(hero, currentExp, safeValue);
|
||||
levelLockByHeroId[hero.ally_heroID] = levelLock;
|
||||
hero.level_lock = levelLock;
|
||||
MarkDirty(hero);
|
||||
SaveNow();
|
||||
if (OnHeroGrowthChanged != null)
|
||||
@@ -292,6 +309,7 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
}
|
||||
|
||||
SyncAllMirrorFlags();
|
||||
SyncAllLegacySelectedSlotExpKeys();
|
||||
AllyHeroDeployLedgerStorage.TrySave(BuildPayload());
|
||||
}
|
||||
|
||||
@@ -300,6 +318,7 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
InitializeIfNeeded();
|
||||
currentExpByHeroId.Clear();
|
||||
unlockedTierByHeroId.Clear();
|
||||
levelLockByHeroId.Clear();
|
||||
autoBreakthroughEnabledByHeroId.Clear();
|
||||
ResetGrowthStateToDefaults();
|
||||
ClearAllPendingDebtKeys();
|
||||
@@ -311,6 +330,7 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
{
|
||||
currentExpByHeroId.Clear();
|
||||
unlockedTierByHeroId.Clear();
|
||||
levelLockByHeroId.Clear();
|
||||
autoBreakthroughEnabledByHeroId.Clear();
|
||||
deployCountsByHeroId.Clear();
|
||||
finishCountsByHeroId.Clear();
|
||||
@@ -341,6 +361,7 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
|
||||
currentExpByHeroId[entry.heroId] = currentExp;
|
||||
unlockedTierByHeroId[entry.heroId] = unlockedTier;
|
||||
levelLockByHeroId[entry.heroId] = entry.levelLock;
|
||||
autoBreakthroughEnabledByHeroId[entry.heroId] = entry.autoBreakthroughEnabled;
|
||||
deployCountsByHeroId[entry.heroId] = Mathf.Max(0, entry.deployCount);
|
||||
finishCountsByHeroId[entry.heroId] = Mathf.Max(0, entry.finishCount);
|
||||
@@ -356,8 +377,9 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
{
|
||||
currentExpByHeroId.Clear();
|
||||
unlockedTierByHeroId.Clear();
|
||||
levelLockByHeroId.Clear();
|
||||
autoBreakthroughEnabledByHeroId.Clear();
|
||||
AllyHero_SO[] heroes = Resources.LoadAll<AllyHero_SO>(string.Empty);
|
||||
AllyHero_SO[] heroes = RuntimeResourcesCache.LoadAllAllyHeroes();
|
||||
for (int i = 0; i < heroes.Length; i++)
|
||||
{
|
||||
AllyHero_SO hero = heroes[i];
|
||||
@@ -368,6 +390,7 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
|
||||
currentExpByHeroId[hero.ally_heroID] = 0;
|
||||
unlockedTierByHeroId[hero.ally_heroID] = 0;
|
||||
levelLockByHeroId[hero.ally_heroID] = false;
|
||||
autoBreakthroughEnabledByHeroId[hero.ally_heroID] = false;
|
||||
|
||||
if (hero.ally_currentEXP != 0)
|
||||
@@ -387,12 +410,18 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
hero.ally_autoBreakthroughEnabled = false;
|
||||
MarkDirty(hero);
|
||||
}
|
||||
|
||||
if (hero.level_lock)
|
||||
{
|
||||
hero.level_lock = false;
|
||||
MarkDirty(hero);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SyncAllMirrorFlags()
|
||||
{
|
||||
AllyHero_SO[] heroes = Resources.LoadAll<AllyHero_SO>(string.Empty);
|
||||
AllyHero_SO[] heroes = RuntimeResourcesCache.LoadAllAllyHeroes();
|
||||
for (int i = 0; i < heroes.Length; i++)
|
||||
{
|
||||
AllyHero_SO hero = heroes[i];
|
||||
@@ -421,6 +450,12 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
unlockedTierByHeroId[hero.ally_heroID] = unlockedTier;
|
||||
}
|
||||
|
||||
bool levelLock;
|
||||
if (!levelLockByHeroId.TryGetValue(hero.ally_heroID, out levelLock))
|
||||
{
|
||||
levelLock = false;
|
||||
}
|
||||
|
||||
bool autoBreakthroughEnabled;
|
||||
if (!autoBreakthroughEnabledByHeroId.TryGetValue(hero.ally_heroID, out autoBreakthroughEnabled))
|
||||
{
|
||||
@@ -431,6 +466,8 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
NormalizeGrowthState(hero, ref currentExp, ref unlockedTier);
|
||||
currentExpByHeroId[hero.ally_heroID] = currentExp;
|
||||
unlockedTierByHeroId[hero.ally_heroID] = unlockedTier;
|
||||
levelLock = ResolveLevelLockState(hero, currentExp, unlockedTier);
|
||||
levelLockByHeroId[hero.ally_heroID] = levelLock;
|
||||
|
||||
int finishCount;
|
||||
if (!finishCountsByHeroId.TryGetValue(hero.ally_heroID, out finishCount))
|
||||
@@ -469,6 +506,12 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (hero.level_lock != levelLock)
|
||||
{
|
||||
hero.level_lock = levelLock;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (hero.ally_battleDeployCount != count)
|
||||
{
|
||||
hero.ally_battleDeployCount = count;
|
||||
@@ -503,7 +546,7 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
private static Dictionary<int, AllyHero_SO> BuildHeroLookup()
|
||||
{
|
||||
Dictionary<int, AllyHero_SO> heroById = new Dictionary<int, AllyHero_SO>();
|
||||
AllyHero_SO[] heroes = Resources.LoadAll<AllyHero_SO>(string.Empty);
|
||||
AllyHero_SO[] heroes = RuntimeResourcesCache.LoadAllAllyHeroes();
|
||||
for (int i = 0; i < heroes.Length; i++)
|
||||
{
|
||||
AllyHero_SO hero = heroes[i];
|
||||
@@ -564,9 +607,41 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
unlockedTier = Mathf.Min(unlockedTier, reachableTier);
|
||||
}
|
||||
|
||||
private static bool ResolveLevelLockState(AllyHero_SO hero, int currentExp, int unlockedTier)
|
||||
{
|
||||
if (hero == null || hero.levelStats == null || hero.levelStats.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
List<AllyHero_SO.AllyLevelInfo> levels = new List<AllyHero_SO.AllyLevelInfo>();
|
||||
for (int i = 0; i < hero.levelStats.Count; i++)
|
||||
{
|
||||
if (hero.levelStats[i] != null)
|
||||
{
|
||||
levels.Add(hero.levelStats[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (levels.Count < 2)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
levels.Sort((left, right) => left.requiredEXP.CompareTo(right.requiredEXP));
|
||||
int clampedTier = Mathf.Clamp(unlockedTier, 0, levels.Count - 1);
|
||||
if (clampedTier >= levels.Count - 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int currentCap = Mathf.Max(levels[clampedTier].requiredEXP, levels[clampedTier + 1].requiredEXP);
|
||||
return Mathf.Max(0, currentExp) >= currentCap;
|
||||
}
|
||||
|
||||
private static void ClearAllPendingDebtKeys()
|
||||
{
|
||||
AllyHero_SO[] heroes = Resources.LoadAll<AllyHero_SO>(string.Empty);
|
||||
AllyHero_SO[] heroes = RuntimeResourcesCache.LoadAllAllyHeroes();
|
||||
for (int i = 0; i < heroes.Length; i++)
|
||||
{
|
||||
AllyHero_SO hero = heroes[i];
|
||||
@@ -581,6 +656,63 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
|
||||
private static void SyncLegacySelectedSlotExpKeys(int heroId, int expValue)
|
||||
{
|
||||
if (heroId <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int safeExp = Mathf.Max(0, expValue);
|
||||
bool changed = false;
|
||||
for (int slot = 1; slot <= 5; slot++)
|
||||
{
|
||||
string heroKey = $"selected_heroSlot0{slot}_heroID";
|
||||
if (PlayerPrefs.GetInt(heroKey, 0) != heroId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string expKey = $"selected_heroSlot0{slot}_exp";
|
||||
if (PlayerPrefs.GetInt(expKey, int.MinValue) == safeExp)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
PlayerPrefs.SetInt(expKey, safeExp);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
}
|
||||
|
||||
private void SyncAllLegacySelectedSlotExpKeys()
|
||||
{
|
||||
bool changed = false;
|
||||
for (int slot = 1; slot <= 5; slot++)
|
||||
{
|
||||
string heroKey = $"selected_heroSlot0{slot}_heroID";
|
||||
int heroId = PlayerPrefs.GetInt(heroKey, 0);
|
||||
string expKey = $"selected_heroSlot0{slot}_exp";
|
||||
int expValue = heroId > 0 ? GetCurrentExp(heroId) : 0;
|
||||
if (PlayerPrefs.GetInt(expKey, int.MinValue) == expValue)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
PlayerPrefs.SetInt(expKey, expValue);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
PlayerPrefs.Save();
|
||||
}
|
||||
}
|
||||
|
||||
private AllyHeroDeployLedgerPayload BuildPayload()
|
||||
{
|
||||
AllyHeroDeployLedgerPayload payload = AllyHeroDeployLedgerStorage.CreateDefaultPayload();
|
||||
@@ -595,6 +727,11 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
allHeroIds.Add(pair.Key);
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<int, bool> pair in levelLockByHeroId)
|
||||
{
|
||||
allHeroIds.Add(pair.Key);
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<int, bool> pair in autoBreakthroughEnabledByHeroId)
|
||||
{
|
||||
allHeroIds.Add(pair.Key);
|
||||
@@ -627,6 +764,7 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
|
||||
heroId = heroId,
|
||||
currentExp = Mathf.Max(0, GetCurrentExp(heroId)),
|
||||
unlockedTierIndex = Mathf.Clamp(GetUnlockedTierIndex(heroId), 0, 3),
|
||||
levelLock = IsLevelLockEnabled(heroId),
|
||||
autoBreakthroughEnabled = IsAutoBreakthroughEnabled(heroId),
|
||||
deployCount = Mathf.Max(0, GetDeployCount(heroId)),
|
||||
finishCount = Mathf.Max(0, GetFinishCount(heroId)),
|
||||
|
||||
@@ -7,6 +7,7 @@ public class AllyHeroDeployEntry
|
||||
public int heroId;
|
||||
public int currentExp;
|
||||
public int unlockedTierIndex;
|
||||
public bool levelLock;
|
||||
public bool autoBreakthroughEnabled;
|
||||
public int deployCount;
|
||||
public int finishCount;
|
||||
|
||||
@@ -14,6 +14,21 @@ public static class DlcContentAccess
|
||||
return owningDlc == null || DlcOwnershipService.IsDlcOwned(owningDlc);
|
||||
}
|
||||
|
||||
public static bool IsHeroAccessible(AllyHero_SO hero)
|
||||
{
|
||||
if (hero == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(hero.sourceDlcId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return DlcOwnershipService.IsDlcOwned(hero.sourceDlcId);
|
||||
}
|
||||
|
||||
public static bool IsSkinAccessible(HeroSkinResolvedData skin)
|
||||
{
|
||||
if (skin == null)
|
||||
|
||||
@@ -24,6 +24,7 @@ public class DlcManifestEntry
|
||||
public string[] songLabels;
|
||||
public string[] songContentLabels;
|
||||
public string[] heroSkinLabels;
|
||||
public string[] heroLabels;
|
||||
|
||||
public string GetSafeDlcKey()
|
||||
{
|
||||
@@ -50,6 +51,11 @@ public class DlcManifestEntry
|
||||
return GetResolvedLabels(heroSkinLabels, "dlc:" + GetSafeDlcKey() + ":heroskins");
|
||||
}
|
||||
|
||||
public string[] GetHeroLabels()
|
||||
{
|
||||
return GetResolvedLabels(heroLabels, "dlc:" + GetSafeDlcKey() + ":heroes");
|
||||
}
|
||||
|
||||
public string[] GetDependencyKeys()
|
||||
{
|
||||
List<string> result = new List<string>();
|
||||
@@ -58,6 +64,7 @@ public class DlcManifestEntry
|
||||
AppendUnique(result, GetSongLabels());
|
||||
AppendUnique(result, GetSongContentLabels());
|
||||
AppendUnique(result, GetHeroSkinLabels());
|
||||
AppendUnique(result, GetHeroLabels());
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ public static class DlcManifestService
|
||||
|
||||
public static List<DlcManifestRuntimeEntry> LoadInstalledEntries()
|
||||
{
|
||||
DlcPackageArchiveService.PrepareInstalledPackages();
|
||||
|
||||
List<DlcManifestRuntimeEntry> result = new List<DlcManifestRuntimeEntry>();
|
||||
HashSet<string> visitedFiles = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
string[] roots = GetManifestSearchRoots();
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
public static class DlcPackageArchiveService
|
||||
{
|
||||
[Serializable]
|
||||
private sealed class InstallState
|
||||
{
|
||||
public string sourcePath;
|
||||
public long sourceFileLength;
|
||||
public long sourceWriteTicksUtc;
|
||||
}
|
||||
|
||||
private const string RuntimeFolderName = "DLC";
|
||||
private const string PackageFolderName = "packages";
|
||||
private const string InstalledFolderName = "installed_packages";
|
||||
private const string PackageExtension = ".bsnkdlc";
|
||||
private const string StateFileName = ".bsnkdlc.installstate.json";
|
||||
|
||||
private static readonly HashSet<string> PreparedPackagesThisSession =
|
||||
new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void ResetRuntimeState()
|
||||
{
|
||||
PreparedPackagesThisSession.Clear();
|
||||
}
|
||||
|
||||
public static void PrepareInstalledPackages()
|
||||
{
|
||||
string[] searchRoots = GetPackageSearchRoots();
|
||||
HashSet<string> visited = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
for (int i = 0; i < searchRoots.Length; i++)
|
||||
{
|
||||
string root = searchRoots[i];
|
||||
if (string.IsNullOrWhiteSpace(root) || !Directory.Exists(root))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string[] packageFiles;
|
||||
try
|
||||
{
|
||||
packageFiles = Directory.GetFiles(root, "*" + PackageExtension, SearchOption.AllDirectories);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning("[DLC] Failed to enumerate package files in '" + root + "': " + ex.Message);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int packageIndex = 0; packageIndex < packageFiles.Length; packageIndex++)
|
||||
{
|
||||
string packagePath = packageFiles[packageIndex];
|
||||
if (string.IsNullOrWhiteSpace(packagePath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string fullPackagePath;
|
||||
try
|
||||
{
|
||||
fullPackagePath = Path.GetFullPath(packagePath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!visited.Add(fullPackagePath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
TryInstallPackage(fullPackagePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string[] GetPackageSearchRoots()
|
||||
{
|
||||
List<string> result = new List<string>();
|
||||
AppendIfValid(result, Path.Combine(Application.streamingAssetsPath, RuntimeFolderName, PackageFolderName));
|
||||
AppendIfValid(result, Path.Combine(Application.streamingAssetsPath, RuntimeFolderName));
|
||||
AppendIfValid(result, Path.Combine(Application.persistentDataPath, RuntimeFolderName, PackageFolderName));
|
||||
AppendIfValid(result, Path.Combine(Application.persistentDataPath, RuntimeFolderName));
|
||||
|
||||
string playerRoot = GetPlayerRootDirectory();
|
||||
AppendIfValid(result, Path.Combine(playerRoot, RuntimeFolderName, PackageFolderName));
|
||||
AppendIfValid(result, Path.Combine(playerRoot, RuntimeFolderName));
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
private static void TryInstallPackage(string packagePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(packagePath) || !File.Exists(packagePath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string installDirectory = GetInstallDirectory(packagePath);
|
||||
if (string.IsNullOrWhiteSpace(installDirectory))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool needsInstall = NeedsInstall(packagePath, installDirectory);
|
||||
if (!needsInstall && PreparedPackagesThisSession.Contains(packagePath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!needsInstall)
|
||||
{
|
||||
PreparedPackagesThisSession.Add(packagePath);
|
||||
return;
|
||||
}
|
||||
|
||||
string installRoot = Path.GetDirectoryName(installDirectory) ?? string.Empty;
|
||||
string stagingDirectory = installDirectory + ".staging";
|
||||
if (!string.IsNullOrWhiteSpace(installRoot))
|
||||
{
|
||||
Directory.CreateDirectory(installRoot);
|
||||
}
|
||||
|
||||
if (Directory.Exists(stagingDirectory))
|
||||
{
|
||||
Directory.Delete(stagingDirectory, true);
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(stagingDirectory);
|
||||
ZipFile.ExtractToDirectory(packagePath, stagingDirectory);
|
||||
WriteInstallState(packagePath, stagingDirectory);
|
||||
|
||||
if (Directory.Exists(installDirectory))
|
||||
{
|
||||
Directory.Delete(installDirectory, true);
|
||||
}
|
||||
|
||||
Directory.Move(stagingDirectory, installDirectory);
|
||||
PreparedPackagesThisSession.Add(packagePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning("[DLC] Failed to install package '" + packagePath + "': " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool NeedsInstall(string packagePath, string installDirectory)
|
||||
{
|
||||
if (!Directory.Exists(installDirectory))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
InstallState state = ReadInstallState(installDirectory);
|
||||
if (state == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
FileInfo info;
|
||||
try
|
||||
{
|
||||
info = new FileInfo(packagePath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return !string.Equals(state.sourcePath ?? string.Empty, packagePath, StringComparison.OrdinalIgnoreCase)
|
||||
|| state.sourceFileLength != info.Length
|
||||
|| state.sourceWriteTicksUtc != info.LastWriteTimeUtc.Ticks;
|
||||
}
|
||||
|
||||
private static string GetInstallDirectory(string packagePath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(packagePath))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(packagePath) ?? "package";
|
||||
string safeName = SanitizeFileName(fileNameWithoutExtension);
|
||||
string hash = ComputeShortHash(packagePath);
|
||||
string installRoot = Path.Combine(Application.persistentDataPath, RuntimeFolderName, InstalledFolderName);
|
||||
return Path.Combine(installRoot, safeName + "_" + hash);
|
||||
}
|
||||
|
||||
private static void WriteInstallState(string packagePath, string directory)
|
||||
{
|
||||
FileInfo info = new FileInfo(packagePath);
|
||||
InstallState state = new InstallState
|
||||
{
|
||||
sourcePath = packagePath,
|
||||
sourceFileLength = info.Exists ? info.Length : 0L,
|
||||
sourceWriteTicksUtc = info.Exists ? info.LastWriteTimeUtc.Ticks : 0L
|
||||
};
|
||||
|
||||
string json = JsonUtility.ToJson(state, true);
|
||||
File.WriteAllText(Path.Combine(directory, StateFileName), json, Encoding.UTF8);
|
||||
}
|
||||
|
||||
private static InstallState ReadInstallState(string directory)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(directory))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
string statePath = Path.Combine(directory, StateFileName);
|
||||
if (!File.Exists(statePath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string json = File.ReadAllText(statePath, Encoding.UTF8);
|
||||
return JsonUtility.FromJson<InstallState>(json);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetPlayerRootDirectory()
|
||||
{
|
||||
try
|
||||
{
|
||||
string dataPath = Application.dataPath;
|
||||
if (string.IsNullOrWhiteSpace(dataPath))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
DirectoryInfo parent = Directory.GetParent(dataPath);
|
||||
return parent != null ? parent.FullName : string.Empty;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AppendIfValid(List<string> result, string path)
|
||||
{
|
||||
if (result == null || string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string fullPath = Path.GetFullPath(path);
|
||||
if (!result.Contains(fullPath))
|
||||
{
|
||||
result.Add(fullPath);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private static string ComputeShortHash(string value)
|
||||
{
|
||||
byte[] bytes = Encoding.UTF8.GetBytes(value ?? string.Empty);
|
||||
using (SHA1 sha1 = SHA1.Create())
|
||||
{
|
||||
byte[] hash = sha1.ComputeHash(bytes);
|
||||
StringBuilder builder = new StringBuilder(8);
|
||||
for (int i = 0; i < 4 && i < hash.Length; i++)
|
||||
{
|
||||
builder.Append(hash[i].ToString("x2"));
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private static string SanitizeFileName(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return "package";
|
||||
}
|
||||
|
||||
char[] invalidChars = Path.GetInvalidFileNameChars();
|
||||
StringBuilder builder = new StringBuilder(value.Length);
|
||||
for (int i = 0; i < value.Length; i++)
|
||||
{
|
||||
char current = value[i];
|
||||
builder.Append(Array.IndexOf(invalidChars, current) >= 0 ? '_' : current);
|
||||
}
|
||||
|
||||
return builder.ToString().Trim();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0bf72431ab28c4640b1b960e8ca98168
|
||||
@@ -19,6 +19,11 @@ public static class DlcRemoteContentService
|
||||
{
|
||||
private void Start()
|
||||
{
|
||||
if (DlcRemoteManifestSyncService.ShouldDeferInitialRefresh())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (DlcRemoteContentService.autoRefreshOnStart)
|
||||
{
|
||||
DlcRemoteContentService.RefreshInstalledDlc();
|
||||
@@ -144,42 +149,56 @@ public static class DlcRemoteContentService
|
||||
|
||||
state.retainedCatalogHandles.Add(catalogHandle);
|
||||
|
||||
if (manifest.entry.autoDownloadDependencies)
|
||||
{
|
||||
string[] dependencyKeys = manifest.entry.GetDependencyKeys();
|
||||
for (int i = 0; i < dependencyKeys.Length; i++)
|
||||
{
|
||||
string dependencyKey = dependencyKeys[i];
|
||||
if (string.IsNullOrWhiteSpace(dependencyKey))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
AsyncOperationHandle downloadHandle = Addressables.DownloadDependenciesAsync(dependencyKey, false);
|
||||
yield return downloadHandle;
|
||||
|
||||
if (downloadHandle.Status == AsyncOperationStatus.Succeeded)
|
||||
{
|
||||
state.retainedAssetHandles.Add(downloadHandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
localSuccess = false;
|
||||
Debug.LogWarning("[DLC] Failed to download dependencies for key '" + dependencyKey + "'.");
|
||||
Addressables.Release(downloadHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<dlcData> loadedDlcs = new List<dlcData>();
|
||||
List<SongData> loadedSongs = new List<SongData>();
|
||||
List<SongDlcContentSO> loadedSongContents = new List<SongDlcContentSO>();
|
||||
List<HeroSkinSO> loadedHeroSkins = new List<HeroSkinSO>();
|
||||
List<AllyHero_SO> loadedHeroes = new List<AllyHero_SO>();
|
||||
|
||||
// Load the lightweight DLC metadata first so we can decide ownership
|
||||
// before pulling (or even downloading) any heavy content.
|
||||
yield return LoadAssetsByLabels(manifest.entry.GetDlcDataLabels(), loadedDlcs, state.retainedAssetHandles, result => localSuccess &= result);
|
||||
yield return LoadAssetsByLabels(manifest.entry.GetSongLabels(), loadedSongs, state.retainedAssetHandles, result => localSuccess &= result);
|
||||
yield return LoadAssetsByLabels(manifest.entry.GetSongContentLabels(), loadedSongContents, state.retainedAssetHandles, result => localSuccess &= result);
|
||||
yield return LoadAssetsByLabels(manifest.entry.GetHeroSkinLabels(), loadedHeroSkins, state.retainedAssetHandles, result => localSuccess &= result);
|
||||
|
||||
bool owned = IsManifestEntryOwned(manifest, loadedDlcs);
|
||||
|
||||
if (owned)
|
||||
{
|
||||
if (manifest.entry.autoDownloadDependencies)
|
||||
{
|
||||
string[] dependencyKeys = manifest.entry.GetDependencyKeys();
|
||||
for (int i = 0; i < dependencyKeys.Length; i++)
|
||||
{
|
||||
string dependencyKey = dependencyKeys[i];
|
||||
if (string.IsNullOrWhiteSpace(dependencyKey))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
AsyncOperationHandle downloadHandle = Addressables.DownloadDependenciesAsync(dependencyKey, false);
|
||||
yield return downloadHandle;
|
||||
|
||||
if (downloadHandle.Status == AsyncOperationStatus.Succeeded)
|
||||
{
|
||||
state.retainedAssetHandles.Add(downloadHandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
localSuccess = false;
|
||||
Debug.LogWarning("[DLC] Failed to download dependencies for key '" + dependencyKey + "'.");
|
||||
Addressables.Release(downloadHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield return LoadAssetsByLabels(manifest.entry.GetSongLabels(), loadedSongs, state.retainedAssetHandles, result => localSuccess &= result);
|
||||
yield return LoadAssetsByLabels(manifest.entry.GetSongContentLabels(), loadedSongContents, state.retainedAssetHandles, result => localSuccess &= result);
|
||||
yield return LoadAssetsByLabels(manifest.entry.GetHeroSkinLabels(), loadedHeroSkins, state.retainedAssetHandles, result => localSuccess &= result);
|
||||
yield return LoadAssetsByLabels(manifest.entry.GetHeroLabels(), loadedHeroes, state.retainedAssetHandles, result => localSuccess &= result);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("[DLC] Skipping content load for unowned DLC '" + manifest.entry.GetSafeDlcKey() + "'. Only metadata is registered.");
|
||||
}
|
||||
|
||||
DlcRuntimePackage package = new DlcRuntimePackage
|
||||
{
|
||||
@@ -190,13 +209,54 @@ public static class DlcRemoteContentService
|
||||
dlcs = loadedDlcs.ToArray(),
|
||||
songs = loadedSongs.ToArray(),
|
||||
songContents = loadedSongContents.ToArray(),
|
||||
heroSkins = loadedHeroSkins.ToArray()
|
||||
heroSkins = loadedHeroSkins.ToArray(),
|
||||
heroes = loadedHeroes.ToArray()
|
||||
};
|
||||
|
||||
state.packages.Add(package);
|
||||
reportResult?.Invoke(localSuccess);
|
||||
}
|
||||
|
||||
private static bool IsManifestEntryOwned(DlcManifestRuntimeEntry manifest, List<dlcData> loadedDlcs)
|
||||
{
|
||||
// If any loaded dlcData asset reports owned, the package is owned. A DLC that
|
||||
// does not enforce entitlement (builtInContent / requiresOwnership == false)
|
||||
// is always owned via DlcOwnershipService.
|
||||
if (loadedDlcs != null)
|
||||
{
|
||||
bool anyEnforced = false;
|
||||
for (int i = 0; i < loadedDlcs.Count; i++)
|
||||
{
|
||||
dlcData dlc = loadedDlcs[i];
|
||||
if (dlc == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
anyEnforced = true;
|
||||
if (DlcOwnershipService.IsDlcOwned(dlc))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (anyEnforced)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// No dlcData asset was found for this manifest entry. Fall back to the manifest
|
||||
// key so a remote/local ownership flag can still gate the content.
|
||||
string key = manifest != null && manifest.entry != null ? manifest.entry.GetSafeDlcKey() : null;
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return DlcOwnershipService.IsDlcOwned(key);
|
||||
}
|
||||
|
||||
private static IEnumerator LoadAssetsByLabels<T>(
|
||||
string[] labels,
|
||||
List<T> output,
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using GameServer.Client;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
public static class DlcRemoteManifestSyncService
|
||||
{
|
||||
[Serializable]
|
||||
private sealed class RemoteManifestEnvelope
|
||||
{
|
||||
public bool success;
|
||||
public string message;
|
||||
public RemoteManifestItem[] dlcs;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private sealed class RemoteManifestItem
|
||||
{
|
||||
public string dlc_key;
|
||||
public string display_name;
|
||||
public string version;
|
||||
public string manifest_file_name;
|
||||
public string manifest_json;
|
||||
public string uploaded_at;
|
||||
public string updated_at;
|
||||
}
|
||||
|
||||
private sealed class DlcRemoteManifestSyncRunner : MonoBehaviour
|
||||
{
|
||||
private void Start()
|
||||
{
|
||||
if (autoSyncOnStart)
|
||||
{
|
||||
SyncPublishedDlcs();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const string DefaultServerUrl = "http://47.112.187.172:8080";
|
||||
private const string RemoteManifestFilePrefix = "remote_";
|
||||
private const string ManifestApiPath = "/api/dlcs/manifests";
|
||||
|
||||
private static DlcRemoteManifestSyncRunner runner;
|
||||
private static bool autoSyncOnStart = true;
|
||||
private static bool syncRequestedWhileBusy;
|
||||
private static bool initialSyncPending = true;
|
||||
|
||||
public static bool IsSyncing { get; private set; }
|
||||
public static event Action<bool> SyncCompleted;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
||||
private static void EnsureRunner()
|
||||
{
|
||||
if (runner != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameObject go = new GameObject(nameof(DlcRemoteManifestSyncService));
|
||||
UnityEngine.Object.DontDestroyOnLoad(go);
|
||||
runner = go.AddComponent<DlcRemoteManifestSyncRunner>();
|
||||
}
|
||||
|
||||
public static void SetAutoSyncOnStart(bool enabled)
|
||||
{
|
||||
autoSyncOnStart = enabled;
|
||||
if (!enabled)
|
||||
{
|
||||
initialSyncPending = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool ShouldDeferInitialRefresh()
|
||||
{
|
||||
return autoSyncOnStart && initialSyncPending && !OnlineModeSettings.IsLocalOnlyMode;
|
||||
}
|
||||
|
||||
public static void SyncPublishedDlcs()
|
||||
{
|
||||
EnsureRunner();
|
||||
if (runner == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsSyncing)
|
||||
{
|
||||
syncRequestedWhileBusy = true;
|
||||
return;
|
||||
}
|
||||
|
||||
runner.StartCoroutine(SyncPublishedDlcsRoutine());
|
||||
}
|
||||
|
||||
private static IEnumerator SyncPublishedDlcsRoutine()
|
||||
{
|
||||
if (IsSyncing)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (OnlineModeSettings.IsLocalOnlyMode)
|
||||
{
|
||||
initialSyncPending = false;
|
||||
DlcRemoteContentService.RefreshInstalledDlc();
|
||||
SyncCompleted?.Invoke(false);
|
||||
yield break;
|
||||
}
|
||||
|
||||
string url = BuildManifestApiUrl();
|
||||
if (string.IsNullOrWhiteSpace(url))
|
||||
{
|
||||
initialSyncPending = false;
|
||||
DlcRemoteContentService.RefreshInstalledDlc();
|
||||
SyncCompleted?.Invoke(false);
|
||||
yield break;
|
||||
}
|
||||
|
||||
IsSyncing = true;
|
||||
syncRequestedWhileBusy = false;
|
||||
bool success = false;
|
||||
|
||||
using (UnityWebRequest request = UnityWebRequest.Get(url))
|
||||
{
|
||||
request.timeout = 10;
|
||||
yield return request.SendWebRequest();
|
||||
|
||||
if (request.result == UnityWebRequest.Result.Success)
|
||||
{
|
||||
string responseText = request.downloadHandler != null ? request.downloadHandler.text : string.Empty;
|
||||
success = TryApplyRemoteManifestPayload(responseText);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[DLC] Remote manifest sync failed: " + request.error);
|
||||
}
|
||||
}
|
||||
|
||||
initialSyncPending = false;
|
||||
|
||||
if (success)
|
||||
{
|
||||
DlcRemoteContentService.RefreshInstalledDlc();
|
||||
}
|
||||
else
|
||||
{
|
||||
DlcRemoteContentService.RefreshInstalledDlc();
|
||||
}
|
||||
|
||||
IsSyncing = false;
|
||||
SyncCompleted?.Invoke(success);
|
||||
|
||||
if (syncRequestedWhileBusy)
|
||||
{
|
||||
SyncPublishedDlcs();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryApplyRemoteManifestPayload(string json)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RemoteManifestEnvelope envelope;
|
||||
try
|
||||
{
|
||||
envelope = JsonUtility.FromJson<RemoteManifestEnvelope>(json);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning("[DLC] Failed to parse remote manifest payload: " + ex.Message);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (envelope == null || !envelope.success)
|
||||
{
|
||||
Debug.LogWarning("[DLC] Remote manifest payload reported failure.");
|
||||
return false;
|
||||
}
|
||||
|
||||
string manifestRoot = Path.Combine(Application.persistentDataPath, "DLC", "manifests");
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(manifestRoot);
|
||||
DeleteExistingRemoteManifestFiles(manifestRoot);
|
||||
|
||||
RemoteManifestItem[] items = envelope.dlcs ?? Array.Empty<RemoteManifestItem>();
|
||||
for (int i = 0; i < items.Length; i++)
|
||||
{
|
||||
RemoteManifestItem item = items[i];
|
||||
if (item == null || string.IsNullOrWhiteSpace(item.manifest_json))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string safeKey = SanitizeFileName(item.dlc_key);
|
||||
if (string.IsNullOrWhiteSpace(safeKey))
|
||||
{
|
||||
safeKey = "package_" + i;
|
||||
}
|
||||
|
||||
string fileName = RemoteManifestFilePrefix + safeKey + ".json";
|
||||
string path = Path.Combine(manifestRoot, fileName);
|
||||
File.WriteAllText(path, item.manifest_json);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning("[DLC] Failed to write remote manifests: " + ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void DeleteExistingRemoteManifestFiles(string manifestRoot)
|
||||
{
|
||||
string[] files = Directory.GetFiles(manifestRoot, RemoteManifestFilePrefix + "*.json", SearchOption.TopDirectoryOnly);
|
||||
for (int i = 0; i < files.Length; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(files[i]);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning("[DLC] Failed to delete old remote manifest '" + files[i] + "': " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildManifestApiUrl()
|
||||
{
|
||||
string baseUrl = DefaultServerUrl;
|
||||
NetworkManager manager = NetworkManager.Instance;
|
||||
if (manager != null && !string.IsNullOrWhiteSpace(manager.ServerUrl))
|
||||
{
|
||||
baseUrl = manager.ServerUrl.Trim();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(baseUrl))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return baseUrl.TrimEnd('/') + ManifestApiPath;
|
||||
}
|
||||
|
||||
private static string SanitizeFileName(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
char[] invalidChars = Path.GetInvalidFileNameChars();
|
||||
string result = value.Trim();
|
||||
for (int i = 0; i < invalidChars.Length; i++)
|
||||
{
|
||||
result = result.Replace(invalidChars[i], '_');
|
||||
}
|
||||
|
||||
return result.Replace(' ', '_');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 08474a42ea631b04b982f1e7e67fd96a
|
||||
@@ -12,6 +12,7 @@ public sealed class DlcRuntimePackage
|
||||
public SongData[] songs = Array.Empty<SongData>();
|
||||
public SongDlcContentSO[] songContents = Array.Empty<SongDlcContentSO>();
|
||||
public HeroSkinSO[] heroSkins = Array.Empty<HeroSkinSO>();
|
||||
public AllyHero_SO[] heroes = Array.Empty<AllyHero_SO>();
|
||||
}
|
||||
|
||||
public static class DlcRuntimeRegistry
|
||||
@@ -25,11 +26,14 @@ public static class DlcRuntimeRegistry
|
||||
new Dictionary<string, SongDlcContentSO>(StringComparer.OrdinalIgnoreCase);
|
||||
private static readonly Dictionary<string, HeroSkinSO> HeroSkinsById =
|
||||
new Dictionary<string, HeroSkinSO>(StringComparer.OrdinalIgnoreCase);
|
||||
private static readonly Dictionary<int, AllyHero_SO> HeroesById =
|
||||
new Dictionary<int, AllyHero_SO>();
|
||||
|
||||
private static dlcData[] cachedDlcs = Array.Empty<dlcData>();
|
||||
private static SongData[] cachedSongs = Array.Empty<SongData>();
|
||||
private static SongDlcContentSO[] cachedSongContents = Array.Empty<SongDlcContentSO>();
|
||||
private static HeroSkinSO[] cachedHeroSkins = Array.Empty<HeroSkinSO>();
|
||||
private static AllyHero_SO[] cachedHeroes = Array.Empty<AllyHero_SO>();
|
||||
|
||||
public static event Action RuntimeContentChanged;
|
||||
|
||||
@@ -69,6 +73,11 @@ public static class DlcRuntimeRegistry
|
||||
return cachedHeroSkins;
|
||||
}
|
||||
|
||||
public static AllyHero_SO[] GetAllHeroes()
|
||||
{
|
||||
return cachedHeroes;
|
||||
}
|
||||
|
||||
public static void ReplaceAll(IList<DlcRuntimePackage> packages)
|
||||
{
|
||||
ClearSilently();
|
||||
@@ -97,10 +106,12 @@ public static class DlcRuntimeRegistry
|
||||
SongsById.Clear();
|
||||
SongContentsById.Clear();
|
||||
HeroSkinsById.Clear();
|
||||
HeroesById.Clear();
|
||||
cachedDlcs = Array.Empty<dlcData>();
|
||||
cachedSongs = Array.Empty<SongData>();
|
||||
cachedSongContents = Array.Empty<SongDlcContentSO>();
|
||||
cachedHeroSkins = Array.Empty<HeroSkinSO>();
|
||||
cachedHeroes = Array.Empty<AllyHero_SO>();
|
||||
}
|
||||
|
||||
private static void RegisterPackageInternal(DlcRuntimePackage package)
|
||||
@@ -185,6 +196,20 @@ public static class DlcRuntimeRegistry
|
||||
HeroSkinsById[skinId] = skin;
|
||||
}
|
||||
}
|
||||
|
||||
if (package.heroes != null)
|
||||
{
|
||||
for (int i = 0; i < package.heroes.Length; i++)
|
||||
{
|
||||
AllyHero_SO hero = package.heroes[i];
|
||||
if (hero == null || hero.ally_heroID <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
HeroesById[hero.ally_heroID] = hero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void RebuildSnapshots()
|
||||
@@ -200,5 +225,8 @@ public static class DlcRuntimeRegistry
|
||||
|
||||
cachedHeroSkins = new HeroSkinSO[HeroSkinsById.Count];
|
||||
HeroSkinsById.Values.CopyTo(cachedHeroSkins, 0);
|
||||
|
||||
cachedHeroes = new AllyHero_SO[HeroesById.Count];
|
||||
HeroesById.Values.CopyTo(cachedHeroes, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public static class RuntimeResourcesCache
|
||||
|
||||
public static AllyHero_SO[] LoadAllAllyHeroes()
|
||||
{
|
||||
return LoadAll<AllyHero_SO>(string.Empty);
|
||||
return MergeArrays(LoadAll<AllyHero_SO>(string.Empty), DlcRuntimeRegistry.GetAllHeroes());
|
||||
}
|
||||
|
||||
public static storeItemSO[] LoadAllStoreItems()
|
||||
|
||||
Reference in New Issue
Block a user