445 lines
14 KiB
C#
445 lines
14 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|