养成基本完毕,新UI,修bug,装备初步,

This commit is contained in:
FloatGaming
2026-03-23 22:37:14 +08:00
parent 49e45ac464
commit dd205b6cb4
2349 changed files with 261722 additions and 283 deletions
+3
View File
@@ -32,6 +32,9 @@ public class SkillGroup
[Tooltip("This skill group requires the hero to reach at least this level (1-4) to be selectable.")]
public int thisSkill_levelLimit = 1;
[Tooltip("Marks this skill group as the hero's exclusive special skill.")]
public bool isSpecialSkill = false;
// Backwards-compatible accessor that previously returned a per-skill description.
// Now returns the single group-level text so callers can still obtain a description string.
public string GetSkillDescription(int index)
+144 -35
View File
@@ -1,9 +1,24 @@
using System;
using UnityEngine;
using System.Collections.Generic;
[CreateAssetMenu(fileName = "NewAllyHero", menuName = "SO_Data/AllyHero")]
public class AllyHero_SO : ScriptableObject
{
public static readonly string[] BehaviourAxisNames =
{
"题海战术",
"诛锄异己",
"逸乐共谋",
"完美至上",
"涵养心境",
"众志成城",
"过激行为",
"二次形变",
"自有主见",
"迷茫或凡想"
};
[Header("Inspector")]
public string ally_heroName;
public string ally_heroDesignation;
@@ -45,16 +60,12 @@ public class AllyHero_SO : ScriptableObject
[Tooltip("Documentation text normalized.")]
public int attack = 0;
[Tooltip("Documentation text normalized.")]
public int maxHP = 100;
[Tooltip("Documentation text normalized.")]
public float damageResistance = 0f;
[Tooltip("Documentation text normalized.")]
public int maxMana = 100;
[Tooltip("Documentation text normalized.")]
public float scoreEfficiency = 1f;
@@ -82,98 +93,101 @@ public class AllyHero_SO : ScriptableObject
public float damageMultiplierGreat = 0.75f;
[Tooltip("Documentation text normalized.")]
public float damageMultiplierPerfect = 1f;
[Tooltip("Documentation text normalized.")]
public float missHpLossBase = 10f;
}
[System.Serializable]
public class AllyBehaviourAxis
{
public string axisName;
[Range(0, 100)] public int value;
}
[Header("Inspector")]
public int ally_currentEXP;
public int ally_growthUnlockedTierIndex;
public bool ally_autoBreakthroughEnabled;
public int ally_battleDeployCount;
public int ally_finishCount;
public int ally_mvpCount;
public long ally_joinDateUtcTicks;
[Header("Behaviour Radar")]
public List<AllyBehaviourAxis> behaviourAxes = new List<AllyBehaviourAxis>();
[Header("Skills")]
public SkillDefinition[] availableSkills;
[Tooltip("Indexes into availableSkills for preselected skills; empty means none selected")]
public int[] selectedSkillIndices = new int[0];
[Tooltip("Primary selected skill index into availableSkills (-1 = none)")]
public int primarySkillIndex = -1;
// Documentation text normalized.
[Tooltip("Skill groups defined on this hero. Each SkillGroup has a numeric skillGroupID which can be equipped into slots to allow activation of the entire group via that ID.")]
public SkillGroup[] skillGroups = new SkillGroup[0];
[Tooltip("Equipped skill group IDs for this ally. Each int references a skillGroupID defined in skillGroups. Use 0 to indicate empty.")]
public int[] equippedSkillGroupIDs = new int[0];
// Documentation text normalized.
public SkillDefinition GetPrimarySkill()
{
if (availableSkills == null || primarySkillIndex < 0 || primarySkillIndex >= availableSkills.Length) return null;
return availableSkills[primarySkillIndex];
}
// Adjusted: primary group is no longer stored as an index; prefer the first equipped group ID, otherwise fall back to the first non-null defined group.
public SkillGroup GetPrimarySkillGroup()
{
if (skillGroups == null || skillGroups.Length == 0) return null;
// If equipped group IDs exist, prefer the first valid referenced group
if (equippedSkillGroupIDs != null && equippedSkillGroupIDs.Length > 0)
{
foreach (var gid in equippedSkillGroupIDs)
foreach (int gid in equippedSkillGroupIDs)
{
if (gid == 0) continue;
var g = GetSkillGroupByID(gid);
if (g != null) return g;
SkillGroup group = GetSkillGroupByID(gid);
if (group != null) return group;
}
}
// Fallback: return first non-null skill group defined on the SO
foreach (var g in skillGroups)
foreach (SkillGroup group in skillGroups)
{
if (g != null) return g;
if (group != null) return group;
}
return null;
}
// New helper: find a SkillGroup by its numeric skillGroupID
public SkillGroup GetSkillGroupByID(int groupID)
{
if (skillGroups == null || skillGroups.Length == 0) return null;
foreach (var g in skillGroups)
foreach (SkillGroup group in skillGroups)
{
if (g != null && g.skillGroupID == groupID) return g;
if (group != null && group.skillGroupID == groupID) return group;
}
return null;
}
// Return the AllyLevelInfo that corresponds to the current ally_currentEXP.
// Picks the highest level whose requiredEXP <= ally_currentEXP. If none match, returns levelStats[0] if present, otherwise null.
public AllyLevelInfo GetEffectiveLevelForCurrentEXP()
{
if (levelStats == null || levelStats.Count == 0) return null;
AllyLevelInfo best = null;
int currentExp = ally_currentEXP;
foreach (var lvl in levelStats)
List<AllyLevelInfo> sorted = new List<AllyLevelInfo>();
for (int i = 0; i < levelStats.Count; i++)
{
if (lvl == null) continue;
if (best == null)
if (levelStats[i] != null)
{
if (currentExp >= lvl.requiredEXP) best = lvl;
}
else
{
if (currentExp >= lvl.requiredEXP && lvl.requiredEXP >= best.requiredEXP) best = lvl;
sorted.Add(levelStats[i]);
}
}
if (best == null) return levelStats[0];
return best;
if (sorted.Count == 0)
{
return null;
}
sorted.Sort((left, right) => left.requiredEXP.CompareTo(right.requiredEXP));
int unlockedTierIndex = Mathf.Clamp(ally_growthUnlockedTierIndex, 0, sorted.Count - 1);
return sorted[unlockedTierIndex];
}
[System.Serializable]
@@ -206,6 +220,56 @@ public class AllyHero_SO : ScriptableObject
equippedSkillGroupIDs = payload.equippedSkillGroupIDs ?? new int[0];
}
public void ClearEquippedSkills()
{
equippedSkillGroupIDs = Array.Empty<int>();
PlayerPrefs.DeleteKey(GetEquippedSkillsPrefsKey());
PlayerPrefs.Save();
#if UNITY_EDITOR
if (!Application.isPlaying)
{
UnityEditor.EditorUtility.SetDirty(this);
}
#endif
}
public static void ClearAllEquippedSkills()
{
HashSet<int> clearedHeroIds = new HashSet<int>();
#if UNITY_EDITOR
string[] guids = UnityEditor.AssetDatabase.FindAssets("t:AllyHero_SO", new[] { "Assets/Resources/so/ally" });
for (int i = 0; i < guids.Length; i++)
{
string path = UnityEditor.AssetDatabase.GUIDToAssetPath(guids[i]);
AllyHero_SO hero = UnityEditor.AssetDatabase.LoadAssetAtPath<AllyHero_SO>(path);
if (hero == null)
{
continue;
}
hero.ClearEquippedSkills();
clearedHeroIds.Add(hero.ally_heroID);
}
UnityEditor.AssetDatabase.SaveAssets();
#endif
AllyHero_SO[] runtimeHeroes = Resources.LoadAll<AllyHero_SO>("so/ally");
for (int i = 0; i < runtimeHeroes.Length; i++)
{
AllyHero_SO hero = runtimeHeroes[i];
if (hero == null || clearedHeroIds.Contains(hero.ally_heroID))
{
continue;
}
hero.ClearEquippedSkills();
}
PlayerPrefs.Save();
}
public void SetUnlocked(bool value)
{
if (isUnlocked == value) return;
@@ -218,6 +282,40 @@ public class AllyHero_SO : ScriptableObject
#endif
}
private void OnValidate()
{
EnsureBehaviourAxes();
}
public void EnsureBehaviourAxes()
{
if (behaviourAxes == null)
{
behaviourAxes = new List<AllyBehaviourAxis>();
}
while (behaviourAxes.Count < BehaviourAxisNames.Length)
{
behaviourAxes.Add(new AllyBehaviourAxis());
}
if (behaviourAxes.Count > BehaviourAxisNames.Length)
{
behaviourAxes.RemoveRange(BehaviourAxisNames.Length, behaviourAxes.Count - BehaviourAxisNames.Length);
}
for (int i = 0; i < BehaviourAxisNames.Length; i++)
{
if (behaviourAxes[i] == null)
{
behaviourAxes[i] = new AllyBehaviourAxis();
}
behaviourAxes[i].axisName = BehaviourAxisNames[i];
behaviourAxes[i].value = Mathf.Clamp(behaviourAxes[i].value, 0, 100);
}
}
public void IncrementBattleDeployCount(int amount = 1)
{
if (amount <= 0) return;
@@ -261,6 +359,17 @@ public class AllyHero_SO : ScriptableObject
{
UnityEditor.EditorUtility.SetDirty(this);
}
#endif
}
public void SetJoinDateIfMissing(System.DateTime? utcTime = null)
{
AllyHeroDeployLedger.EnsureInstance().SetJoinDateIfMissing(this, utcTime);
#if UNITY_EDITOR
if (!Application.isPlaying)
{
UnityEditor.EditorUtility.SetDirty(this);
}
#endif
}
}
+22
View File
@@ -0,0 +1,22 @@
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "hero_growth_rules", menuName = "SO_Data/Hero Growth Rules")]
public class HeroGrowthRulesSO : ScriptableObject
{
[System.Serializable]
public class MaterialRequirement
{
public DushMaterialKind materialKind;
public int amount;
}
[System.Serializable]
public class BreakthroughRule
{
public HeroGrowthTier fromTier;
public List<MaterialRequirement> requirements = new List<MaterialRequirement>();
}
public List<BreakthroughRule> breakthroughRules = new List<BreakthroughRule>();
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4fa52f0f554aec04bb14d2490947c1eb
+7
View File
@@ -0,0 +1,7 @@
public enum HeroGrowthTier
{
C = 0,
B = 1,
A = 2,
S = 3
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ed30b59f63db88b44a107823ed483d2c
@@ -11,7 +11,9 @@ using Steamworks;
public class btmandtopController : MonoBehaviour, ICancelHandler
{
public static event System.Action<bool> GlobalSettingsVisibilityChanged;
public static event System.Action<bool> GlobalOverlayPanelVisibilityChanged;
public static bool CurrentSettingsVisible { get; private set; }
public static bool CurrentOverlayPanelsVisible { get; private set; }
public event System.Action<bool> SettingsVisibilityChanged;
@@ -113,6 +115,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
private bool musicPicVisible = true;
private bool navSceneLoading = false;
private bool lastSettingsVisibilityState;
private bool lastOverlayPanelsVisibilityState;
private readonly Dictionary<string, int> guideIndexByScene = new Dictionary<string, int>();
private string currentGuideScene = string.Empty;
@@ -145,7 +148,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
}
instantiateSettings = () => ToggleSettingsPrefab();
instantiateUserInfo = () => ToggleUserInfoPrefab();
instantiateStore = () => ShowStorePrefab();
instantiateStore = () => { };
instantiateShowLevel = () => { gNotice.error.display("功能即将下线,禁止访问"); };
// instantiateShowLevel = () => ShowLevelPrefab();
instantiateEmail = () => ShowEmailPrefab();
@@ -266,6 +269,8 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
}
lastSettingsVisibilityState = IsSettingsPanelVisible;
lastOverlayPanelsVisibilityState = AreOverlayPanelsVisible();
CurrentOverlayPanelsVisible = lastOverlayPanelsVisibilityState;
}
@@ -285,16 +290,28 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
{
BroadcastSettingsVisibility(false);
}
if (CurrentOverlayPanelsVisible)
{
lastOverlayPanelsVisibilityState = false;
CurrentOverlayPanelsVisible = false;
GlobalOverlayPanelVisibilityChanged?.Invoke(false);
}
return;
}
bool visible = IsSettingsPanelVisible;
if (visible == lastSettingsVisibilityState)
if (visible != lastSettingsVisibilityState)
{
return;
BroadcastSettingsVisibility(visible);
}
BroadcastSettingsVisibility(visible);
bool overlayVisible = AreOverlayPanelsVisible();
if (overlayVisible != lastOverlayPanelsVisibilityState)
{
lastOverlayPanelsVisibilityState = overlayVisible;
CurrentOverlayPanelsVisible = overlayVisible;
GlobalOverlayPanelVisibilityChanged?.Invoke(overlayVisible);
}
}
@@ -660,6 +677,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
settingsInstance.SetActive(true);
EnsureSettingsLastSibling();
BroadcastSettingsVisibility(true);
BroadcastOverlayPanelsVisibility();
return;
}
@@ -675,6 +693,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
EnsureSettingsLastSibling();
}
BroadcastSettingsVisibility(settingsInstance.activeSelf);
BroadcastOverlayPanelsVisibility();
}
public bool IsSettingsPanelVisible
@@ -692,6 +711,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
TryAssignCanvasCamera(userInfoInstance);
PlacePanelBelowSettings(userInfoInstance);
userInfoInstance.SetActive(true);
BroadcastOverlayPanelsVisibility();
return;
}
@@ -701,6 +721,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
{
PlacePanelBelowSettings(userInfoInstance);
}
BroadcastOverlayPanelsVisibility();
}
private void BroadcastSettingsVisibility(bool visible)
@@ -724,6 +745,23 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
return string.Equals(sceneName, targetScene, System.StringComparison.OrdinalIgnoreCase);
}
private bool AreOverlayPanelsVisible()
{
return (settingsInstance != null && settingsInstance.activeInHierarchy)
|| (storeInstance != null && storeInstance.activeInHierarchy)
|| (showLevelInstance != null && showLevelInstance.activeInHierarchy)
|| (emailInstance != null && emailInstance.activeInHierarchy)
|| (noticeInstance != null && noticeInstance.activeInHierarchy);
}
private void BroadcastOverlayPanelsVisibility()
{
bool visible = AreOverlayPanelsVisible();
lastOverlayPanelsVisibilityState = visible;
CurrentOverlayPanelsVisible = visible;
GlobalOverlayPanelVisibilityChanged?.Invoke(visible);
}
private void ShowLevelPrefab()
{
bool opened = ShowPrefab(showLevel_prefab, ref showLevelInstance);
@@ -782,10 +820,12 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
instance.SetActive(shouldOpen);
if (!shouldOpen)
{
BroadcastOverlayPanelsVisibility();
return false;
}
PlacePanelBelowSettings(instance);
BroadcastOverlayPanelsVisibility();
return true;
}
@@ -834,6 +874,7 @@ public class btmandtopController : MonoBehaviour, ICancelHandler
{
if (instance == null || instance == keep) return;
instance.SetActive(false);
BroadcastOverlayPanelsVisibility();
}
private GameObject FindExistingInstance(GameObject prefab)
@@ -8,9 +8,15 @@ 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> 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;
@@ -24,6 +30,7 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
{
if (Instance != null)
{
Instance.ResetToDefaultsIfSaveMissing();
return Instance;
}
@@ -45,6 +52,7 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
Instance = this;
DontDestroyOnLoad(gameObject);
InitializeIfNeeded();
ResetToDefaultsIfSaveMissing();
}
private void OnApplicationPause(bool pauseStatus)
@@ -71,6 +79,27 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
loadedFromSave = AllyHeroDeployLedgerStorage.TryLoad(out payload);
RebuildFromPayload(payload);
initialized = true;
if (!loadedFromSave)
{
ResetGrowthStateToDefaults();
}
SyncAllMirrorFlags();
SaveNow();
}
public void ResetToDefaultsIfSaveMissing()
{
if (!initialized)
{
return;
}
if (AllyHeroDeployLedgerStorage.HasExistingSaveFile())
{
return;
}
ResetGrowthStateToDefaults();
SyncAllMirrorFlags();
SaveNow();
}
@@ -96,6 +125,91 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
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 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;
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;
hero.ally_currentEXP = GetCurrentExp(hero.ally_heroID);
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)
@@ -144,6 +258,32 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
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)
@@ -155,16 +295,34 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
AllyHeroDeployLedgerStorage.TrySave(BuildPayload());
}
public void ResetGrowthProgressOnly()
{
InitializeIfNeeded();
currentExpByHeroId.Clear();
unlockedTierByHeroId.Clear();
autoBreakthroughEnabledByHeroId.Clear();
ResetGrowthStateToDefaults();
ClearAllPendingDebtKeys();
SyncAllMirrorFlags();
SaveNow();
}
private void RebuildFromPayload(AllyHeroDeployLedgerPayload payload)
{
currentExpByHeroId.Clear();
unlockedTierByHeroId.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];
@@ -173,9 +331,62 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
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;
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();
autoBreakthroughEnabledByHeroId.Clear();
AllyHero_SO[] heroes = Resources.LoadAll<AllyHero_SO>(string.Empty);
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;
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);
}
}
}
@@ -196,6 +407,31 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
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 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;
int finishCount;
if (!finishCountsByHeroId.TryGetValue(hero.ally_heroID, out finishCount))
{
@@ -208,7 +444,31 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
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.ally_battleDeployCount != count)
{
hero.ally_battleDeployCount = count;
@@ -227,6 +487,12 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
changed = true;
}
if (hero.ally_joinDateUtcTicks != joinTicks)
{
hero.ally_joinDateUtcTicks = joinTicks;
changed = true;
}
if (changed)
{
MarkDirty(hero);
@@ -234,10 +500,106 @@ 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);
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 void ClearAllPendingDebtKeys()
{
AllyHero_SO[] heroes = Resources.LoadAll<AllyHero_SO>(string.Empty);
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 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 autoBreakthroughEnabledByHeroId)
{
allHeroIds.Add(pair.Key);
}
foreach (KeyValuePair<int, int> pair in deployCountsByHeroId)
{
allHeroIds.Add(pair.Key);
@@ -253,14 +615,23 @@ public sealed class AllyHeroDeployLedger : MonoBehaviour
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),
autoBreakthroughEnabled = IsAutoBreakthroughEnabled(heroId),
deployCount = Mathf.Max(0, GetDeployCount(heroId)),
finishCount = Mathf.Max(0, GetFinishCount(heroId)),
mvpCount = Mathf.Max(0, GetMvpCount(heroId))
mvpCount = Mathf.Max(0, GetMvpCount(heroId)),
joinDateUtcTicks = GetJoinDateUtcTicks(heroId)
});
}
@@ -5,9 +5,13 @@ using System.Collections.Generic;
public class AllyHeroDeployEntry
{
public int heroId;
public int currentExp;
public int unlockedTierIndex;
public bool autoBreakthroughEnabled;
public int deployCount;
public int finishCount;
public int mvpCount;
public long joinDateUtcTicks;
}
[Serializable]
@@ -17,6 +17,11 @@ public static class AllyHeroDeployLedgerStorage
private static string BackupFilePath => Path.Combine(VaultDirectoryPath, BackupFileName);
private static string TempFilePath => Path.Combine(VaultDirectoryPath, TempFileName);
public static bool HasExistingSaveFile()
{
return File.Exists(MainFilePath) || File.Exists(BackupFilePath);
}
public static bool TryLoad(out AllyHeroDeployLedgerPayload payload)
{
payload = CreateDefaultPayload();
@@ -0,0 +1,444 @@
using System.Collections.Generic;
using UnityEngine;
public static class AllyHeroGrowthService
{
private const string RuntimeRulesPath = "so/system/hero_growth_rules";
public static bool TryUseBottleOnHero(expBottlesSO bottleDefinition, AllyHero_SO hero, int bottleCount, out string failureMessage, out int consumedBottleCount, out int totalGrantedExp)
{
failureMessage = null;
consumedBottleCount = 0;
totalGrantedExp = 0;
if (bottleDefinition == null)
{
failureMessage = "经验瓶定义缺失";
return false;
}
if (hero == null)
{
failureMessage = "目标偶像不存在";
return false;
}
if (bottleDefinition.bottleType != expBottlesSO.BottleType.Normal)
{
failureMessage = "该经验瓶不是单体经验瓶";
return false;
}
if (bottleCount <= 0)
{
failureMessage = "经验瓶数量无效";
return false;
}
HeroGrowthTier unlockedTier = GetUnlockedTier(hero);
if (unlockedTier >= HeroGrowthTier.S)
{
failureMessage = "S级偶像不可继续培养";
return false;
}
if (!CanBottleServeTier(bottleDefinition.serviceLevel, unlockedTier))
{
failureMessage = "该经验瓶不可用于当前偶像等级";
return false;
}
List<AllyHero_SO.AllyLevelInfo> sortedLevels = GetSortedLevels(hero);
if (sortedLevels.Count < 2)
{
failureMessage = "偶像等级数据不完整";
return false;
}
int tierIndex = Mathf.Clamp((int)unlockedTier, 0, sortedLevels.Count - 1);
int currentExp = AllyHeroDeployLedger.EnsureInstance().GetCurrentExp(hero.ally_heroID);
int currentCap = GetCurrentTierCap(sortedLevels, tierIndex);
if (currentExp >= currentCap)
{
failureMessage = "当前等级已满,请先突破";
return false;
}
if (!ExpBottleLedger.EnsureInstance().HasEnough(bottleDefinition.bottleKind, bottleCount))
{
failureMessage = "经验瓶库存不足";
return false;
}
int nextExp = currentExp;
for (int i = 0; i < bottleCount; i++)
{
if (nextExp >= currentCap)
{
break;
}
int appliedExp = ResolveBottleExpGain(bottleDefinition, nextExp, currentCap);
if (appliedExp <= 0)
{
break;
}
int before = nextExp;
nextExp = Mathf.Min(currentCap, nextExp + appliedExp);
consumedBottleCount++;
totalGrantedExp += Mathf.Max(0, nextExp - before);
}
if (consumedBottleCount <= 0)
{
failureMessage = "当前经验瓶对该偶像无效";
return false;
}
if (!ExpBottleLedger.EnsureInstance().TryConsume(bottleDefinition.bottleKind, consumedBottleCount))
{
consumedBottleCount = 0;
totalGrantedExp = 0;
failureMessage = "经验瓶库存不足";
return false;
}
AllyHeroDeployLedger.EnsureInstance().SetCurrentExp(hero, nextExp);
return true;
}
public static bool TryUseRainAllBottle(expBottlesSO bottleDefinition, IList<AllyHero_SO> heroes, int bottleCount, out string failureMessage, out int consumedBottleCount, out int totalGrantedExp)
{
failureMessage = null;
consumedBottleCount = 0;
totalGrantedExp = 0;
if (bottleDefinition == null)
{
failureMessage = "经验瓶定义缺失";
return false;
}
if (bottleDefinition.bottleType != expBottlesSO.BottleType.RainAll)
{
failureMessage = "该经验瓶不是雨露均沾经验瓶";
return false;
}
if (heroes == null || heroes.Count == 0)
{
failureMessage = "没有可作用的偶像";
return false;
}
if (bottleCount <= 0)
{
failureMessage = "经验瓶数量无效";
return false;
}
if (!ExpBottleLedger.EnsureInstance().HasEnough(bottleDefinition.bottleKind, bottleCount))
{
failureMessage = "经验瓶库存不足";
return false;
}
int targetCount = Mathf.Max(0, bottleDefinition.rainAllTargetCount);
int expPerHero = Mathf.Max(0, bottleDefinition.rainAllGrantedExpPerHero);
if (targetCount <= 0 || expPerHero <= 0)
{
failureMessage = "雨露均沾经验瓶定义不完整";
return false;
}
int remainingUses = bottleCount;
while (remainingUses > 0)
{
List<AllyHero_SO> eligible = BuildEligibleRainAllTargets(heroes);
if (eligible.Count == 0)
{
break;
}
ShuffleInPlace(eligible);
int applyCount = Mathf.Min(targetCount, eligible.Count);
int grantedThisUse = 0;
for (int i = 0; i < applyCount; i++)
{
AllyHero_SO hero = eligible[i];
List<AllyHero_SO.AllyLevelInfo> sortedLevels = GetSortedLevels(hero);
if (sortedLevels.Count < 2)
{
continue;
}
int tierIndex = AllyHeroDeployLedger.EnsureInstance().GetUnlockedTierIndex(hero.ally_heroID);
int currentExp = AllyHeroDeployLedger.EnsureInstance().GetCurrentExp(hero.ally_heroID);
int currentCap = GetCurrentTierCap(sortedLevels, tierIndex);
if (currentExp >= currentCap)
{
continue;
}
int nextExp = Mathf.Min(currentCap, currentExp + expPerHero);
grantedThisUse += Mathf.Max(0, nextExp - currentExp);
AllyHeroDeployLedger.EnsureInstance().SetCurrentExp(hero, nextExp);
}
consumedBottleCount++;
totalGrantedExp += grantedThisUse;
remainingUses--;
}
if (consumedBottleCount <= 0)
{
failureMessage = "没有可被该经验瓶作用的偶像";
return false;
}
if (!ExpBottleLedger.EnsureInstance().TryConsume(bottleDefinition.bottleKind, consumedBottleCount))
{
consumedBottleCount = 0;
totalGrantedExp = 0;
failureMessage = "经验瓶库存不足";
return false;
}
return true;
}
public static bool TryBreakthrough(AllyHero_SO hero, out string failureMessage)
{
failureMessage = null;
if (hero == null)
{
failureMessage = "目标偶像不存在";
return false;
}
HeroGrowthTier unlockedTier = GetUnlockedTier(hero);
if (unlockedTier >= HeroGrowthTier.S)
{
failureMessage = "当前偶像已到最高培养等级";
return false;
}
List<AllyHero_SO.AllyLevelInfo> sortedLevels = GetSortedLevels(hero);
if (sortedLevels.Count < 2)
{
failureMessage = "偶像等级数据不完整";
return false;
}
int tierIndex = Mathf.Clamp((int)unlockedTier, 0, sortedLevels.Count - 1);
int currentExp = AllyHeroDeployLedger.EnsureInstance().GetCurrentExp(hero.ally_heroID);
int currentCap = GetCurrentTierCap(sortedLevels, tierIndex);
if (currentExp < currentCap)
{
failureMessage = "当前等级经验未满,无法突破";
return false;
}
HeroGrowthRulesSO rules = LoadRules();
if (rules == null)
{
failureMessage = "未配置成长规则";
return false;
}
HeroGrowthRulesSO.BreakthroughRule rule = FindBreakthroughRule(rules, unlockedTier);
if (rule == null || rule.requirements == null || rule.requirements.Count == 0)
{
failureMessage = "未配置突破材料";
return false;
}
for (int i = 0; i < rule.requirements.Count; i++)
{
HeroGrowthRulesSO.MaterialRequirement requirement = rule.requirements[i];
if (requirement == null || requirement.amount <= 0)
{
continue;
}
if (DushMaterialLedger.EnsureInstance().GetCount(requirement.materialKind) < requirement.amount)
{
failureMessage = "突破材料不足";
return false;
}
}
bool anyPositiveRequirement = false;
for (int i = 0; i < rule.requirements.Count; i++)
{
HeroGrowthRulesSO.MaterialRequirement requirement = rule.requirements[i];
if (requirement == null || requirement.amount <= 0)
{
continue;
}
anyPositiveRequirement = true;
if (!DushMaterialLedger.EnsureInstance().TryConsume(requirement.materialKind, requirement.amount))
{
failureMessage = "突破材料不足";
return false;
}
}
if (!anyPositiveRequirement)
{
failureMessage = "未配置突破材料";
return false;
}
AllyHeroDeployLedger.EnsureInstance().SetUnlockedTierIndex(hero, tierIndex + 1);
return true;
}
public static HeroGrowthTier GetUnlockedTier(AllyHero_SO hero)
{
if (hero == null)
{
return HeroGrowthTier.C;
}
return (HeroGrowthTier)Mathf.Clamp(AllyHeroDeployLedger.EnsureInstance().GetUnlockedTierIndex(hero.ally_heroID), 0, 3);
}
private static HeroGrowthRulesSO LoadRules()
{
return Resources.Load<HeroGrowthRulesSO>(RuntimeRulesPath);
}
private static HeroGrowthRulesSO.BreakthroughRule FindBreakthroughRule(HeroGrowthRulesSO rules, HeroGrowthTier tier)
{
if (rules == null || rules.breakthroughRules == null)
{
return null;
}
for (int i = 0; i < rules.breakthroughRules.Count; i++)
{
HeroGrowthRulesSO.BreakthroughRule rule = rules.breakthroughRules[i];
if (rule != null && rule.fromTier == tier)
{
return rule;
}
}
return null;
}
private static bool CanBottleServeTier(expBottlesSO.ServiceLevel serviceLevel, HeroGrowthTier tier)
{
switch (serviceLevel)
{
case expBottlesSO.ServiceLevel.Any:
return true;
case expBottlesSO.ServiceLevel.C:
return tier == HeroGrowthTier.C;
case expBottlesSO.ServiceLevel.B:
return tier == HeroGrowthTier.B;
case expBottlesSO.ServiceLevel.A:
return tier == HeroGrowthTier.A;
case expBottlesSO.ServiceLevel.S:
return tier == HeroGrowthTier.S;
default:
return false;
}
}
private static int ResolveBottleExpGain(expBottlesSO bottleDefinition, int currentExp, int currentCap)
{
switch (bottleDefinition.expApplyMode)
{
case expBottlesSO.ExpApplyMode.FillCurrentLevel:
return Mathf.Max(0, currentCap - currentExp);
case expBottlesSO.ExpApplyMode.FillAllLevels:
return Mathf.Max(0, currentCap - currentExp);
default:
return Mathf.Max(0, bottleDefinition.grantedExp);
}
}
private static int GetCurrentTierCap(List<AllyHero_SO.AllyLevelInfo> sortedLevels, int tierIndex)
{
int nextIndex = Mathf.Clamp(tierIndex + 1, 0, sortedLevels.Count - 1);
if (nextIndex == tierIndex)
{
return sortedLevels[tierIndex].requiredEXP;
}
return Mathf.Max(sortedLevels[tierIndex].requiredEXP, sortedLevels[nextIndex].requiredEXP);
}
private static List<AllyHero_SO.AllyLevelInfo> GetSortedLevels(AllyHero_SO hero)
{
List<AllyHero_SO.AllyLevelInfo> sorted = new List<AllyHero_SO.AllyLevelInfo>();
if (hero == null || hero.levelStats == null)
{
return sorted;
}
for (int i = 0; i < hero.levelStats.Count; i++)
{
if (hero.levelStats[i] != null)
{
sorted.Add(hero.levelStats[i]);
}
}
sorted.Sort((left, right) => left.requiredEXP.CompareTo(right.requiredEXP));
return sorted;
}
private static List<AllyHero_SO> BuildEligibleRainAllTargets(IList<AllyHero_SO> heroes)
{
List<AllyHero_SO> result = new List<AllyHero_SO>();
for (int i = 0; i < heroes.Count; i++)
{
AllyHero_SO hero = heroes[i];
if (hero == null)
{
continue;
}
HeroGrowthTier tier = GetUnlockedTier(hero);
if (tier >= HeroGrowthTier.S)
{
continue;
}
List<AllyHero_SO.AllyLevelInfo> sortedLevels = GetSortedLevels(hero);
if (sortedLevels.Count < 2)
{
continue;
}
int currentExp = AllyHeroDeployLedger.EnsureInstance().GetCurrentExp(hero.ally_heroID);
int currentCap = GetCurrentTierCap(sortedLevels, (int)tier);
if (currentExp >= currentCap)
{
continue;
}
result.Add(hero);
}
return result;
}
private static void ShuffleInPlace<T>(IList<T> list)
{
for (int i = list.Count - 1; i > 0; i--)
{
int swapIndex = Random.Range(0, i + 1);
T temp = list[i];
list[i] = list[swapIndex];
list[swapIndex] = temp;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8da2914f05632cc46b1a6e4185597d99
@@ -70,6 +70,7 @@ public sealed class DushMaterialLedger : MonoBehaviour
loadedFromSave = DushMaterialLedgerStorage.TryLoad(out payload);
RebuildFromPayload(payload);
initialized = true;
SyncMirrorCounts();
SaveNow();
}
@@ -162,6 +163,19 @@ public sealed class DushMaterialLedger : MonoBehaviour
return true;
}
public void ResetAllToZero()
{
InitializeIfNeeded();
for (int i = 0; i < DushMaterialCatalog.All.Count; i++)
{
countsByKey[DushMaterialCatalog.All[i].Key] = 0;
}
SaveNow();
NotifyAllCountsChanged();
}
public void SaveNow()
{
if (!initialized)
@@ -169,10 +183,26 @@ public sealed class DushMaterialLedger : MonoBehaviour
InitializeIfNeeded();
}
SyncMirrorCounts();
DushMaterialLedgerStorage.TrySave(BuildPayload());
SyncToPlayerData();
}
private void SyncMirrorCounts()
{
growthMaterialSO[] definitions = Resources.FindObjectsOfTypeAll<growthMaterialSO>();
for (int i = 0; i < definitions.Length; i++)
{
growthMaterialSO definition = definitions[i];
if (definition == null)
{
continue;
}
definition.runtimeOwnedCount = GetCount(definition.materialKind);
}
}
private int GetCountByKey(string key)
{
InitializeIfNeeded();
@@ -77,6 +77,7 @@ public sealed class ExpBottleLedger : MonoBehaviour
loadedFromSave = ExpBottleLedgerStorage.TryLoad(out payload);
RebuildFromPayload(payload);
initialized = true;
SyncMirrorCounts();
SaveNow();
if (OnLedgerReloaded != null)
{
@@ -289,10 +290,26 @@ public sealed class ExpBottleLedger : MonoBehaviour
public void SaveNow()
{
InitializeIfNeededForSave();
SyncMirrorCounts();
ExpBottleLedgerStorage.TrySave(BuildPayload());
SyncToPlayerData();
}
private void SyncMirrorCounts()
{
expBottlesSO[] definitions = Resources.FindObjectsOfTypeAll<expBottlesSO>();
for (int i = 0; i < definitions.Length; i++)
{
expBottlesSO definition = definitions[i];
if (definition == null)
{
continue;
}
definition.runtimeOwnedCount = GetCount(definition.bottleKind);
}
}
private void InitializeIfNeededForSave()
{
if (!initialized)
@@ -3,6 +3,13 @@ using UnityEngine;
public static class StoreExpBottlePurchaseService
{
private enum ConsumableGrantType
{
None,
ExpBottle,
GrowthMaterial
}
public static bool TryPurchase(Player_SO playerData, storeItemSO itemSO, int packageCount, out string failureMessage, out int grantedCount)
{
failureMessage = string.Empty;
@@ -127,10 +134,12 @@ public static class StoreExpBottlePurchaseService
switch (itemSO.itemType)
{
case storeItemSO.ItemType.consumable:
ConsumableGrantType grantType;
ExpBottleKind bottleKind;
if (!StoreExpBottleGrantResolver.TryResolve(itemSO, out bottleKind))
DushMaterialKind materialKind;
if (!TryResolveConsumableGrant(itemSO, out grantType, out bottleKind, out materialKind))
{
failureMessage = "当前仅支持经验瓶发放";
failureMessage = "当前未配置发放逻辑";
return false;
}
return true;
@@ -158,16 +167,31 @@ public static class StoreExpBottlePurchaseService
switch (itemSO.itemType)
{
case storeItemSO.ItemType.consumable:
ConsumableGrantType grantType;
ExpBottleKind bottleKind;
if (!StoreExpBottleGrantResolver.TryResolve(itemSO, out bottleKind))
DushMaterialKind materialKind;
if (!TryResolveConsumableGrant(itemSO, out grantType, out bottleKind, out materialKind))
{
failureMessage = "当前仅支持经验瓶发放";
failureMessage = "当前未配置发放逻辑";
return false;
}
ExpBottleLedger.EnsureInstance().AttachPlayerData(playerData);
ExpBottleLedger.EnsureInstance().Add(bottleKind, grantedCount);
return true;
if (grantType == ConsumableGrantType.ExpBottle)
{
ExpBottleLedger.EnsureInstance().AttachPlayerData(playerData);
ExpBottleLedger.EnsureInstance().Add(bottleKind, grantedCount);
return true;
}
if (grantType == ConsumableGrantType.GrowthMaterial)
{
DushMaterialLedger.EnsureInstance().AttachPlayerData(playerData);
DushMaterialLedger.EnsureInstance().Add(materialKind, grantedCount);
return true;
}
failureMessage = "当前未配置发放逻辑";
return false;
case storeItemSO.ItemType.character:
case storeItemSO.ItemType.song:
@@ -197,6 +221,25 @@ public static class StoreExpBottlePurchaseService
return;
}
ConsumableGrantType grantType;
ExpBottleKind bottleKind;
DushMaterialKind materialKind;
if (!TryResolveConsumableGrant(itemSO, out grantType, out bottleKind, out materialKind))
{
Debug.Log(builder.ToString());
return;
}
if (grantType == ConsumableGrantType.GrowthMaterial)
{
builder.Append(" | 突破材料库存=");
builder.Append(DushMaterialCatalog.GetDisplayName(materialKind));
builder.Append(":");
builder.Append(DushMaterialLedger.EnsureInstance().GetCount(materialKind));
Debug.Log(builder.ToString());
return;
}
var snapshot = ExpBottleLedger.EnsureInstance().GetSnapshot();
builder.Append(" | 经验瓶库存:");
@@ -223,4 +266,38 @@ public static class StoreExpBottlePurchaseService
Debug.Log(builder.ToString());
}
private static bool TryResolveConsumableGrant(storeItemSO itemSO, out ConsumableGrantType grantType, out ExpBottleKind bottleKind, out DushMaterialKind materialKind)
{
grantType = ConsumableGrantType.None;
bottleKind = default(ExpBottleKind);
materialKind = default(DushMaterialKind);
if (itemSO == null)
{
return false;
}
if (itemSO.associatedExpBottle != null)
{
grantType = ConsumableGrantType.ExpBottle;
bottleKind = itemSO.associatedExpBottle.bottleKind;
return true;
}
if (itemSO.associatedGrowthMaterial != null)
{
grantType = ConsumableGrantType.GrowthMaterial;
materialKind = itemSO.associatedGrowthMaterial.materialKind;
return true;
}
if (StoreExpBottleGrantResolver.TryResolve(itemSO, out bottleKind))
{
grantType = ConsumableGrantType.ExpBottle;
return true;
}
return false;
}
}
@@ -501,6 +501,7 @@ public sealed class StoreOwnershipLedger : MonoBehaviour
if (entry.owned && itemSO.associatedAllyHero != null)
{
itemSO.associatedAllyHero.isUnlocked = true;
itemSO.associatedAllyHero.SetJoinDateIfMissing();
MarkDirty(itemSO.associatedAllyHero);
}
break;
+12
View File
@@ -187,6 +187,18 @@ public class userSettings : MonoBehaviour
yield return null;
}
AllyHeroDeployLedger.EnsureInstance().ResetGrowthProgressOnly();
yield return null;
ExpBottleLedger.EnsureInstance().ResetAllToZero();
yield return null;
DushMaterialLedger.EnsureInstance().ResetAllToZero();
yield return null;
AllyHero_SO.ClearAllEquippedSkills();
yield return null;
#if UNITY_EDITOR
AssetDatabase.SaveAssets();
#endif