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 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 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 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 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 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(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 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 GetSortedLevels(AllyHero_SO hero) { List sorted = new List(); 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 BuildEligibleRainAllTargets(IList heroes) { List result = new List(); 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 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(IList 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; } } }