794 lines
23 KiB
C#
794 lines
23 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
#endif
|
|
|
|
public sealed class AllyHeroDeployLedger : MonoBehaviour
|
|
{
|
|
public static AllyHeroDeployLedger Instance { get; private set; }
|
|
|
|
public event System.Action<int> OnHeroGrowthChanged;
|
|
|
|
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>();
|
|
private readonly Dictionary<int, int> mvpCountsByHeroId = new Dictionary<int, int>();
|
|
private readonly Dictionary<int, long> joinDateUtcTicksByHeroId = new Dictionary<int, long>();
|
|
private bool initialized;
|
|
private bool loadedFromSave;
|
|
private bool hasAnyRecoverableLocalState;
|
|
|
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
|
|
private static void Bootstrap()
|
|
{
|
|
EnsureInstance();
|
|
}
|
|
|
|
public static AllyHeroDeployLedger EnsureInstance()
|
|
{
|
|
if (Instance != null)
|
|
{
|
|
Instance.ResetToDefaultsIfSaveMissing();
|
|
return Instance;
|
|
}
|
|
|
|
GameObject host = new GameObject("__runtime_ally_deploy_bridge");
|
|
host.hideFlags = HideFlags.HideInHierarchy | HideFlags.HideInInspector;
|
|
DontDestroyOnLoad(host);
|
|
Instance = host.AddComponent<AllyHeroDeployLedger>();
|
|
return Instance;
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
if (Instance != null && Instance != this)
|
|
{
|
|
Destroy(gameObject);
|
|
return;
|
|
}
|
|
|
|
Instance = this;
|
|
DontDestroyOnLoad(gameObject);
|
|
InitializeIfNeeded();
|
|
ResetToDefaultsIfSaveMissing();
|
|
}
|
|
|
|
private void OnApplicationPause(bool pauseStatus)
|
|
{
|
|
if (pauseStatus)
|
|
{
|
|
SaveNow();
|
|
}
|
|
}
|
|
|
|
private void OnApplicationQuit()
|
|
{
|
|
SaveNow();
|
|
}
|
|
|
|
public void InitializeIfNeeded()
|
|
{
|
|
if (initialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
AllyHeroDeployLedgerPayload payload;
|
|
loadedFromSave = AllyHeroDeployLedgerStorage.TryLoad(out payload);
|
|
hasAnyRecoverableLocalState = loadedFromSave || AllyHeroDeployLedgerStorage.HasAnyRecoverableState();
|
|
RebuildFromPayload(payload);
|
|
initialized = true;
|
|
if (!loadedFromSave && !hasAnyRecoverableLocalState)
|
|
{
|
|
ResetGrowthStateToDefaults();
|
|
}
|
|
SyncAllMirrorFlags();
|
|
if (loadedFromSave || !hasAnyRecoverableLocalState)
|
|
{
|
|
SaveNow();
|
|
}
|
|
}
|
|
|
|
public void ResetToDefaultsIfSaveMissing()
|
|
{
|
|
if (!initialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (hasAnyRecoverableLocalState || AllyHeroDeployLedgerStorage.HasExistingSaveFile())
|
|
{
|
|
return;
|
|
}
|
|
|
|
ResetGrowthStateToDefaults();
|
|
SyncAllMirrorFlags();
|
|
SaveNow();
|
|
}
|
|
|
|
public int GetDeployCount(int heroId)
|
|
{
|
|
InitializeIfNeeded();
|
|
int count;
|
|
return deployCountsByHeroId.TryGetValue(heroId, out count) ? count : 0;
|
|
}
|
|
|
|
public int GetFinishCount(int heroId)
|
|
{
|
|
InitializeIfNeeded();
|
|
int count;
|
|
return finishCountsByHeroId.TryGetValue(heroId, out count) ? count : 0;
|
|
}
|
|
|
|
public int GetMvpCount(int heroId)
|
|
{
|
|
InitializeIfNeeded();
|
|
int count;
|
|
return mvpCountsByHeroId.TryGetValue(heroId, out count) ? count : 0;
|
|
}
|
|
|
|
public long GetJoinDateUtcTicks(int heroId)
|
|
{
|
|
InitializeIfNeeded();
|
|
long ticks;
|
|
return joinDateUtcTicksByHeroId.TryGetValue(heroId, out ticks) ? ticks : 0L;
|
|
}
|
|
|
|
public int GetCurrentExp(int heroId)
|
|
{
|
|
InitializeIfNeeded();
|
|
int count;
|
|
return currentExpByHeroId.TryGetValue(heroId, out count) ? Mathf.Max(0, count) : 0;
|
|
}
|
|
|
|
public int GetUnlockedTierIndex(int heroId)
|
|
{
|
|
InitializeIfNeeded();
|
|
int value;
|
|
return unlockedTierByHeroId.TryGetValue(heroId, out value) ? Mathf.Clamp(value, 0, 3) : 0;
|
|
}
|
|
|
|
public bool IsAutoBreakthroughEnabled(int heroId)
|
|
{
|
|
InitializeIfNeeded();
|
|
bool value;
|
|
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)
|
|
{
|
|
return;
|
|
}
|
|
|
|
InitializeIfNeeded();
|
|
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)
|
|
{
|
|
OnHeroGrowthChanged(hero.ally_heroID);
|
|
}
|
|
}
|
|
|
|
public void SetUnlockedTierIndex(AllyHero_SO hero, int value)
|
|
{
|
|
if (hero == null || hero.ally_heroID <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
InitializeIfNeeded();
|
|
int safeValue = Mathf.Clamp(value, 0, 3);
|
|
unlockedTierByHeroId[hero.ally_heroID] = safeValue;
|
|
hero.ally_growthUnlockedTierIndex = safeValue;
|
|
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)
|
|
{
|
|
OnHeroGrowthChanged(hero.ally_heroID);
|
|
}
|
|
}
|
|
|
|
public void SetAutoBreakthroughEnabled(AllyHero_SO hero, bool value)
|
|
{
|
|
if (hero == null || hero.ally_heroID <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
InitializeIfNeeded();
|
|
autoBreakthroughEnabledByHeroId[hero.ally_heroID] = value;
|
|
hero.ally_autoBreakthroughEnabled = value;
|
|
MarkDirty(hero);
|
|
SaveNow();
|
|
if (OnHeroGrowthChanged != null)
|
|
{
|
|
OnHeroGrowthChanged(hero.ally_heroID);
|
|
}
|
|
}
|
|
|
|
public void IncrementDeployCount(AllyHero_SO hero, int amount = 1)
|
|
{
|
|
if (hero == null || hero.ally_heroID <= 0 || amount <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
InitializeIfNeeded();
|
|
int current = GetDeployCount(hero.ally_heroID);
|
|
long next = (long)current + amount;
|
|
deployCountsByHeroId[hero.ally_heroID] = next > int.MaxValue ? int.MaxValue : (int)next;
|
|
hero.ally_battleDeployCount = deployCountsByHeroId[hero.ally_heroID];
|
|
MarkDirty(hero);
|
|
SaveNow();
|
|
}
|
|
|
|
public void IncrementFinishCount(AllyHero_SO hero, int amount = 1)
|
|
{
|
|
if (hero == null || hero.ally_heroID <= 0 || amount <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
InitializeIfNeeded();
|
|
int current = GetFinishCount(hero.ally_heroID);
|
|
long next = (long)current + amount;
|
|
finishCountsByHeroId[hero.ally_heroID] = next > int.MaxValue ? int.MaxValue : (int)next;
|
|
hero.ally_finishCount = finishCountsByHeroId[hero.ally_heroID];
|
|
MarkDirty(hero);
|
|
SaveNow();
|
|
}
|
|
|
|
public void IncrementMvpCount(AllyHero_SO hero, int amount = 1)
|
|
{
|
|
if (hero == null || hero.ally_heroID <= 0 || amount <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
InitializeIfNeeded();
|
|
int current = GetMvpCount(hero.ally_heroID);
|
|
long next = (long)current + amount;
|
|
mvpCountsByHeroId[hero.ally_heroID] = next > int.MaxValue ? int.MaxValue : (int)next;
|
|
hero.ally_mvpCount = mvpCountsByHeroId[hero.ally_heroID];
|
|
MarkDirty(hero);
|
|
SaveNow();
|
|
}
|
|
|
|
public void SetJoinDateIfMissing(AllyHero_SO hero, System.DateTime? utcTime = null)
|
|
{
|
|
if (hero == null || hero.ally_heroID <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
InitializeIfNeeded();
|
|
if (GetJoinDateUtcTicks(hero.ally_heroID) > 0L)
|
|
{
|
|
hero.ally_joinDateUtcTicks = GetJoinDateUtcTicks(hero.ally_heroID);
|
|
return;
|
|
}
|
|
|
|
long ticks = (utcTime ?? System.DateTime.UtcNow).Ticks;
|
|
if (ticks <= 0L)
|
|
{
|
|
return;
|
|
}
|
|
|
|
joinDateUtcTicksByHeroId[hero.ally_heroID] = ticks;
|
|
hero.ally_joinDateUtcTicks = ticks;
|
|
MarkDirty(hero);
|
|
SaveNow();
|
|
}
|
|
|
|
public void SaveNow()
|
|
{
|
|
if (!initialized)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SyncAllMirrorFlags();
|
|
SyncAllLegacySelectedSlotExpKeys();
|
|
AllyHeroDeployLedgerStorage.TrySave(BuildPayload());
|
|
}
|
|
|
|
public void ResetGrowthProgressOnly()
|
|
{
|
|
InitializeIfNeeded();
|
|
currentExpByHeroId.Clear();
|
|
unlockedTierByHeroId.Clear();
|
|
levelLockByHeroId.Clear();
|
|
autoBreakthroughEnabledByHeroId.Clear();
|
|
ResetGrowthStateToDefaults();
|
|
ClearAllPendingDebtKeys();
|
|
SyncAllMirrorFlags();
|
|
SaveNow();
|
|
}
|
|
|
|
private void RebuildFromPayload(AllyHeroDeployLedgerPayload payload)
|
|
{
|
|
currentExpByHeroId.Clear();
|
|
unlockedTierByHeroId.Clear();
|
|
levelLockByHeroId.Clear();
|
|
autoBreakthroughEnabledByHeroId.Clear();
|
|
deployCountsByHeroId.Clear();
|
|
finishCountsByHeroId.Clear();
|
|
mvpCountsByHeroId.Clear();
|
|
joinDateUtcTicksByHeroId.Clear();
|
|
if (payload == null || payload.entries == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Dictionary<int, AllyHero_SO> heroById = BuildHeroLookup();
|
|
|
|
for (int i = 0; i < payload.entries.Count; i++)
|
|
{
|
|
AllyHeroDeployEntry entry = payload.entries[i];
|
|
if (entry == null || entry.heroId <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
int currentExp = Mathf.Max(0, entry.currentExp);
|
|
int unlockedTier = Mathf.Clamp(entry.unlockedTierIndex, 0, 3);
|
|
AllyHero_SO hero;
|
|
if (heroById.TryGetValue(entry.heroId, out hero))
|
|
{
|
|
NormalizeGrowthState(hero, ref currentExp, ref unlockedTier);
|
|
}
|
|
|
|
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);
|
|
mvpCountsByHeroId[entry.heroId] = Mathf.Max(0, entry.mvpCount);
|
|
if (entry.joinDateUtcTicks > 0L)
|
|
{
|
|
joinDateUtcTicksByHeroId[entry.heroId] = entry.joinDateUtcTicks;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ResetGrowthStateToDefaults()
|
|
{
|
|
currentExpByHeroId.Clear();
|
|
unlockedTierByHeroId.Clear();
|
|
levelLockByHeroId.Clear();
|
|
autoBreakthroughEnabledByHeroId.Clear();
|
|
AllyHero_SO[] heroes = RuntimeResourcesCache.LoadAllAllyHeroes();
|
|
for (int i = 0; i < heroes.Length; i++)
|
|
{
|
|
AllyHero_SO hero = heroes[i];
|
|
if (hero == null || hero.ally_heroID <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
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)
|
|
{
|
|
hero.ally_currentEXP = 0;
|
|
MarkDirty(hero);
|
|
}
|
|
|
|
if (hero.ally_growthUnlockedTierIndex != 0)
|
|
{
|
|
hero.ally_growthUnlockedTierIndex = 0;
|
|
MarkDirty(hero);
|
|
}
|
|
|
|
if (hero.ally_autoBreakthroughEnabled)
|
|
{
|
|
hero.ally_autoBreakthroughEnabled = false;
|
|
MarkDirty(hero);
|
|
}
|
|
|
|
if (hero.level_lock)
|
|
{
|
|
hero.level_lock = false;
|
|
MarkDirty(hero);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SyncAllMirrorFlags()
|
|
{
|
|
AllyHero_SO[] heroes = RuntimeResourcesCache.LoadAllAllyHeroes();
|
|
for (int i = 0; i < heroes.Length; i++)
|
|
{
|
|
AllyHero_SO hero = heroes[i];
|
|
if (hero == null || hero.ally_heroID <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
int count;
|
|
if (!deployCountsByHeroId.TryGetValue(hero.ally_heroID, out count))
|
|
{
|
|
count = 0;
|
|
}
|
|
|
|
int currentExp;
|
|
if (!currentExpByHeroId.TryGetValue(hero.ally_heroID, out currentExp))
|
|
{
|
|
currentExp = 0;
|
|
currentExpByHeroId[hero.ally_heroID] = currentExp;
|
|
}
|
|
|
|
int unlockedTier;
|
|
if (!unlockedTierByHeroId.TryGetValue(hero.ally_heroID, out unlockedTier))
|
|
{
|
|
unlockedTier = 0;
|
|
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))
|
|
{
|
|
autoBreakthroughEnabled = false;
|
|
autoBreakthroughEnabledByHeroId[hero.ally_heroID] = autoBreakthroughEnabled;
|
|
}
|
|
|
|
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))
|
|
{
|
|
finishCount = 0;
|
|
}
|
|
|
|
int mvpCount;
|
|
if (!mvpCountsByHeroId.TryGetValue(hero.ally_heroID, out mvpCount))
|
|
{
|
|
mvpCount = 0;
|
|
}
|
|
|
|
long joinTicks;
|
|
if (!joinDateUtcTicksByHeroId.TryGetValue(hero.ally_heroID, out joinTicks))
|
|
{
|
|
joinTicks = 0L;
|
|
}
|
|
|
|
bool changed = false;
|
|
if (hero.ally_currentEXP != currentExp)
|
|
{
|
|
hero.ally_currentEXP = currentExp;
|
|
changed = true;
|
|
}
|
|
|
|
if (hero.ally_growthUnlockedTierIndex != unlockedTier)
|
|
{
|
|
hero.ally_growthUnlockedTierIndex = unlockedTier;
|
|
changed = true;
|
|
}
|
|
|
|
if (hero.ally_autoBreakthroughEnabled != autoBreakthroughEnabled)
|
|
{
|
|
hero.ally_autoBreakthroughEnabled = autoBreakthroughEnabled;
|
|
changed = true;
|
|
}
|
|
|
|
if (hero.level_lock != levelLock)
|
|
{
|
|
hero.level_lock = levelLock;
|
|
changed = true;
|
|
}
|
|
|
|
if (hero.ally_battleDeployCount != count)
|
|
{
|
|
hero.ally_battleDeployCount = count;
|
|
changed = true;
|
|
}
|
|
|
|
if (hero.ally_finishCount != finishCount)
|
|
{
|
|
hero.ally_finishCount = finishCount;
|
|
changed = true;
|
|
}
|
|
|
|
if (hero.ally_mvpCount != mvpCount)
|
|
{
|
|
hero.ally_mvpCount = mvpCount;
|
|
changed = true;
|
|
}
|
|
|
|
if (hero.ally_joinDateUtcTicks != joinTicks)
|
|
{
|
|
hero.ally_joinDateUtcTicks = joinTicks;
|
|
changed = true;
|
|
}
|
|
|
|
if (changed)
|
|
{
|
|
MarkDirty(hero);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static Dictionary<int, AllyHero_SO> BuildHeroLookup()
|
|
{
|
|
Dictionary<int, AllyHero_SO> heroById = new Dictionary<int, AllyHero_SO>();
|
|
AllyHero_SO[] heroes = RuntimeResourcesCache.LoadAllAllyHeroes();
|
|
for (int i = 0; i < heroes.Length; i++)
|
|
{
|
|
AllyHero_SO hero = heroes[i];
|
|
if (hero == null || hero.ally_heroID <= 0 || heroById.ContainsKey(hero.ally_heroID))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
heroById.Add(hero.ally_heroID, hero);
|
|
}
|
|
|
|
return heroById;
|
|
}
|
|
|
|
private static void NormalizeGrowthState(AllyHero_SO hero, ref int currentExp, ref int unlockedTier)
|
|
{
|
|
currentExp = Mathf.Max(0, currentExp);
|
|
unlockedTier = Mathf.Clamp(unlockedTier, 0, 3);
|
|
|
|
if (hero == null || hero.levelStats == null || hero.levelStats.Count == 0)
|
|
{
|
|
if (currentExp <= 0)
|
|
{
|
|
unlockedTier = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
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 == 0)
|
|
{
|
|
if (currentExp <= 0)
|
|
{
|
|
unlockedTier = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
levels.Sort((left, right) => left.requiredEXP.CompareTo(right.requiredEXP));
|
|
|
|
int reachableTier = 0;
|
|
for (int i = 0; i < levels.Count; i++)
|
|
{
|
|
if (currentExp >= Mathf.Max(0, levels[i].requiredEXP))
|
|
{
|
|
reachableTier = i;
|
|
}
|
|
}
|
|
|
|
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 = RuntimeResourcesCache.LoadAllAllyHeroes();
|
|
for (int i = 0; i < heroes.Length; i++)
|
|
{
|
|
AllyHero_SO hero = heroes[i];
|
|
if (hero == null || hero.ally_heroID <= 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
PlayerPrefs.DeleteKey("idol_upgrade_pending_debt_" + hero.ally_heroID);
|
|
}
|
|
|
|
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();
|
|
HashSet<int> allHeroIds = new HashSet<int>();
|
|
foreach (KeyValuePair<int, int> pair in currentExpByHeroId)
|
|
{
|
|
allHeroIds.Add(pair.Key);
|
|
}
|
|
|
|
foreach (KeyValuePair<int, int> pair in unlockedTierByHeroId)
|
|
{
|
|
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);
|
|
}
|
|
|
|
foreach (KeyValuePair<int, int> pair in deployCountsByHeroId)
|
|
{
|
|
allHeroIds.Add(pair.Key);
|
|
}
|
|
|
|
foreach (KeyValuePair<int, int> pair in finishCountsByHeroId)
|
|
{
|
|
allHeroIds.Add(pair.Key);
|
|
}
|
|
|
|
foreach (KeyValuePair<int, int> pair in mvpCountsByHeroId)
|
|
{
|
|
allHeroIds.Add(pair.Key);
|
|
}
|
|
|
|
foreach (KeyValuePair<int, long> pair in joinDateUtcTicksByHeroId)
|
|
{
|
|
allHeroIds.Add(pair.Key);
|
|
}
|
|
|
|
foreach (int heroId in allHeroIds)
|
|
{
|
|
payload.entries.Add(new AllyHeroDeployEntry
|
|
{
|
|
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)),
|
|
mvpCount = Mathf.Max(0, GetMvpCount(heroId)),
|
|
joinDateUtcTicks = GetJoinDateUtcTicks(heroId)
|
|
});
|
|
}
|
|
|
|
return payload;
|
|
}
|
|
|
|
private static void MarkDirty(AllyHero_SO hero)
|
|
{
|
|
#if UNITY_EDITOR
|
|
if (!Application.isPlaying && hero != null)
|
|
{
|
|
EditorUtility.SetDirty(hero);
|
|
}
|
|
#endif
|
|
}
|
|
}
|