Files
bansonic_beta_main/Assets/idolsDisplay/levels/idolUpgrade.cs
T

1634 lines
52 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using Bansonic;
using UnityEngine;
using UnityEngine.UI;
public class idolUpgrade : MonoBehaviour
{
[Serializable]
private sealed class PendingDebtMaterialEntry
{
public int materialKind;
public int amount;
}
[Serializable]
private sealed class PendingDebtState
{
public int pendingCoins;
public List<PendingDebtMaterialEntry> materials = new List<PendingDebtMaterialEntry>();
}
private sealed class AggregatedMaterialRequirement
{
public growthMaterialSO material;
public DushMaterialKind materialKind;
public expBottlesSO bottle;
public ExpBottleKind bottleKind;
public int requiredAmount;
public bool isUniversalOption;
public bool isBottleRequirement;
}
[Header("Config")]
public upgradeConfig config;
[Header("Runtime")]
public AllyHero_SO currentHero;
[Header("objects")]
public GameObject materialPrefab;
public Transform materialContainer;
[Header("the Button")]
public Button yesdoButton;
public Text yesdoButtonText;
public Text infoButton;
public Text upgradeText;
private readonly List<materialPrefab> spawnedMaterials = new List<materialPrefab>();
private readonly Dictionary<materialPrefab, expBottlesSO> spawnedBottleBindings = new Dictionary<materialPrefab, expBottlesSO>();
private expBottlesSO selectedBottle;
private DushMaterialKind? selectedBreakthroughMaterialKind;
private bool isShowingBreakthrough;
private string lastOperationSuccessMessage;
private storeItemSO[] cachedStoreItems;
private static readonly Color UpgradeWarningColor = new Color32(120, 36, 36, 255);
private static readonly Color UpgradeMaxColor = new Color32(205, 170, 64, 255);
private void Awake()
{
BindButtons();
}
private void OnEnable()
{
BindButtons();
RefreshView();
}
public void SetHero(AllyHero_SO hero)
{
bool heroChanged = currentHero == null || hero == null || currentHero.ally_heroID != hero.ally_heroID;
currentHero = hero;
if (heroChanged)
{
selectedBottle = null;
selectedBreakthroughMaterialKind = null;
}
RefreshView();
}
public HeroGrowthTier GetCurrentTier()
{
return AllyHeroGrowthService.GetUnlockedTier(currentHero);
}
public upgradeConfig.TierRule GetCurrentRule()
{
if (config == null || currentHero == null)
{
return null;
}
HeroGrowthTier currentTier = GetCurrentTier();
if (currentTier >= HeroGrowthTier.S)
{
return null;
}
return config.GetRule(currentTier);
}
public List<expBottlesSO> GetCurrentLinkedBottles()
{
upgradeConfig.TierRule rule = GetCurrentRule();
return rule != null ? rule.linkedExpBottles : null;
}
public List<upgradeConfig.GrowthMaterialCost> GetCurrentBreakthroughCosts()
{
upgradeConfig.TierRule rule = GetCurrentRule();
return rule != null ? rule.breakthroughCosts : null;
}
public int GetCurrentBreakthroughCoinCost()
{
upgradeConfig.TierRule rule = GetCurrentRule();
return rule != null ? Mathf.Max(0, rule.requiredCoins) : 0;
}
public upgradeConfig.SpecialBottleRule GetSpecialBottleRule(expBottlesSO bottle)
{
return config != null ? config.GetSpecialBottleRule(bottle) : null;
}
public void RefreshView()
{
ClearMaterialItems();
SetUpgradeTextVisible(false, string.Empty, UpgradeWarningColor);
if (currentHero == null || config == null)
{
SetInfoText(string.Empty);
SetConfirmInteractable(false);
SetConfirmButtonText("不可用");
return;
}
if (GetCurrentTier() == HeroGrowthTier.S)
{
selectedBottle = null;
selectedBreakthroughMaterialKind = null;
isShowingBreakthrough = false;
SetInfoText("已达最高阶");
SetConfirmInteractable(false);
SetConfirmButtonText("已满级");
SetUpgradeTextVisible(true, "满级角色已不可培养", UpgradeMaxColor);
return;
}
isShowingBreakthrough = ShouldShowBreakthrough();
if (isShowingBreakthrough)
{
List<AggregatedMaterialRequirement> requirements = BuildEffectiveBreakthroughRequirements();
bool requiresFullSubmission = HasPendingDebtRequirements();
bool bundledSelection = ShouldUseBundledBreakthroughSelection(requirements, requiresFullSubmission);
BuildBreakthroughMaterialList();
string prefix = requiresFullSubmission ? "<color=#FF4D4D>须全部提交,</color>" : string.Empty;
if (bundledSelection && selectedBreakthroughMaterialKind.HasValue && selectedBreakthroughMaterialKind.Value != DushMaterialKind.Material78024)
{
prefix += "<color=#FF4D4D>所选两项材料为一组,</color>";
}
SetInfoText(string.Format("{0}突破需消耗 {1} 金币", prefix, GetEffectiveBreakthroughCoinCost()));
SetConfirmInteractable(true);
SetConfirmButtonText("突破");
return;
}
if (!BuildExpBottleList())
{
selectedBottle = null;
SetInfoText(string.Empty);
SetConfirmInteractable(false);
SetConfirmButtonText("不可用");
SetUpgradeTextVisible(true, "未拥有相关材料", UpgradeWarningColor);
return;
}
UpdateTrainingInfoAndSelectionState();
}
private void BindButtons()
{
if (yesdoButton != null)
{
yesdoButton.onClick.RemoveListener(HandleYesDoClicked);
yesdoButton.onClick.AddListener(HandleYesDoClicked);
}
}
private void HandleYesDoClicked()
{
if (currentHero == null)
{
gNotice.warning.display("未选择偶像");
return;
}
if (isShowingBreakthrough)
{
string failureReason;
if (!TryBreakthroughCurrentHero(out failureReason))
{
gNotice.warning.display(failureReason);
RefreshView();
return;
}
gNotice.recommendation.display("突破成功");
RefreshView();
return;
}
if (selectedBottle == null)
{
gNotice.warning.display("请选择经验瓶");
return;
}
string bottleName = GetDisplayName(selectedBottle);
string failure;
int consumedCount;
int grantedExp;
string successMessage;
if (!TryUseSelectedBottle(selectedBottle, out failure, out consumedCount, out grantedExp, out successMessage))
{
gNotice.warning.display(failure);
RefreshView();
return;
}
gNotice.recommendation.display(!string.IsNullOrWhiteSpace(successMessage)
? successMessage
: string.Format("给{0}增加了{1}经验", currentHero.ally_heroName, grantedExp));
RefreshView();
SetInfoText(bottleName);
}
private bool BuildExpBottleList()
{
List<expBottlesSO> validBottles = GetDisplayableBottles();
if (validBottles.Count == 0)
{
return false;
}
if (selectedBottle == null || !validBottles.Contains(selectedBottle))
{
selectedBottle = validBottles[0];
}
for (int i = 0; i < validBottles.Count; i++)
{
expBottlesSO bottle = validBottles[i];
materialPrefab item = CreateMaterialItem();
if (item == null)
{
continue;
}
item.Bind(
bottle.expBottleSprite,
GetDisplayName(bottle),
Mathf.Max(0, ExpBottleLedger.EnsureInstance().GetCount(bottle.bottleKind)).ToString(),
bottle.itemRarity,
bottle == selectedBottle,
true,
() => SelectBottle(bottle));
spawnedBottleBindings[item] = bottle;
}
return true;
}
private List<expBottlesSO> GetDisplayableBottles()
{
List<expBottlesSO> result = new List<expBottlesSO>();
List<expBottlesSO> source = GetCurrentLinkedBottles() ?? new List<expBottlesSO>();
for (int i = 0; i < source.Count; i++)
{
expBottlesSO bottle = source[i];
if (bottle == null || ExpBottleLedger.EnsureInstance().GetCount(bottle.bottleKind) <= 0)
{
continue;
}
result.Add(bottle);
}
return result;
}
private void BuildBreakthroughMaterialList()
{
List<AggregatedMaterialRequirement> requirements = BuildEffectiveBreakthroughRequirements();
bool requiresFullSubmission = HasPendingDebtRequirements();
bool bundledSelection = ShouldUseBundledBreakthroughSelection(requirements, requiresFullSubmission);
if (requirements.Count == 0)
{
selectedBreakthroughMaterialKind = null;
return;
}
bool selectionValid = false;
for (int i = 0; i < requirements.Count; i++)
{
if (selectedBreakthroughMaterialKind.HasValue && requirements[i].materialKind == selectedBreakthroughMaterialKind.Value)
{
selectionValid = true;
break;
}
}
if (!selectionValid)
{
selectedBreakthroughMaterialKind = GetDefaultBreakthroughSelection(requirements, bundledSelection);
}
for (int i = 0; i < requirements.Count; i++)
{
AggregatedMaterialRequirement requirement = requirements[i];
int owned = requirement.isBottleRequirement
? ExpBottleLedger.EnsureInstance().GetCount(requirement.bottleKind)
: DushMaterialLedger.EnsureInstance().GetCount(requirement.materialKind);
bool enough = owned >= requirement.requiredAmount;
string ownedText = enough ? owned.ToString() : string.Format("<color=#FF4D4D>{0}</color>", owned);
string amountText = string.Format("{0}/{1}", ownedText, requirement.requiredAmount);
materialPrefab item = CreateMaterialItem();
if (item == null)
{
continue;
}
item.Bind(
requirement.isBottleRequirement
? (requirement.bottle != null ? requirement.bottle.expBottleSprite : null)
: (requirement.material != null ? requirement.material.growthMaterialSprite : null),
requirement.isBottleRequirement
? GetDisplayName(requirement.bottle, requirement.bottleKind)
: GetDisplayName(requirement.material, requirement.materialKind),
amountText,
requirement.isBottleRequirement
? (requirement.bottle != null ? requirement.bottle.itemRarity : ItemRarity.None)
: (requirement.material != null ? requirement.material.itemRarity : ItemRarity.None),
ShouldHighlightBreakthroughRequirement(requirement, requiresFullSubmission, bundledSelection),
!requiresFullSubmission,
() => SelectBreakthroughMaterial(requirement.materialKind));
}
}
private void UpdateTrainingInfoAndSelectionState()
{
if (selectedBottle == null)
{
SetInfoText("请选择经验瓶");
SetConfirmInteractable(false);
SetConfirmButtonText("选择");
return;
}
SetInfoText(GetDisplayName(selectedBottle));
SetConfirmInteractable(true);
SetConfirmButtonText("使用");
}
private void SelectBottle(expBottlesSO bottle)
{
selectedBottle = bottle;
for (int i = 0; i < spawnedMaterials.Count; i++)
{
materialPrefab item = spawnedMaterials[i];
if (item == null)
{
continue;
}
expBottlesSO boundBottle;
bool isSelected = bottle != null
&& spawnedBottleBindings.TryGetValue(item, out boundBottle)
&& boundBottle == bottle;
item.SetSelected(isSelected);
}
UpdateTrainingInfoAndSelectionState();
}
private void SelectBreakthroughMaterial(DushMaterialKind materialKind)
{
selectedBreakthroughMaterialKind = materialKind;
RefreshView();
}
private bool TryUseSelectedBottle(expBottlesSO bottle, out string failureReason, out int consumedCount, out int grantedExp, out string successMessage)
{
consumedCount = 0;
grantedExp = 0;
failureReason = null;
successMessage = null;
lastOperationSuccessMessage = null;
if (bottle == null)
{
failureReason = "未选择经验瓶";
return false;
}
if (bottle.bottleType == expBottlesSO.BottleType.RainAll)
{
bool success = TryUseRainAllBottle(bottle, out failureReason, out consumedCount, out grantedExp);
successMessage = lastOperationSuccessMessage;
return success;
}
upgradeConfig.SpecialBottleRule specialRule = GetSpecialBottleRule(bottle);
if (specialRule != null)
{
bool success = TryUseSpecialBottleRule(bottle, specialRule, out failureReason, out consumedCount, out grantedExp);
successMessage = lastOperationSuccessMessage;
return success;
}
bool normalSuccess = TryUseNormalBottle(bottle, out failureReason, out consumedCount, out grantedExp);
successMessage = lastOperationSuccessMessage;
return normalSuccess;
}
private bool TryUseNormalBottle(expBottlesSO bottle, out string failureReason, out int consumedCount, out int grantedExp)
{
consumedCount = 0;
grantedExp = 0;
failureReason = null;
if (bottle == null || currentHero == null)
{
failureReason = "经验瓶配置缺失";
return false;
}
HeroGrowthTier currentTier = GetCurrentTier();
if (currentTier >= HeroGrowthTier.S)
{
failureReason = "当前偶像已达最高阶";
return false;
}
if (!CanBottleServeTier(bottle.serviceLevel, currentTier))
{
failureReason = "该经验瓶不可用于当前偶像等级";
return false;
}
if (!ExpBottleLedger.EnsureInstance().HasEnough(bottle.bottleKind, 1))
{
failureReason = "经验瓶库存不足";
return false;
}
int expToGrant = ResolveBottleGrantExp(bottle, currentHero);
if (expToGrant <= 0)
{
failureReason = "当前偶像无法从该经验瓶获得经验";
return false;
}
grantedExp = ApplyDirectExpToHero(currentHero, expToGrant);
if (grantedExp <= 0)
{
failureReason = "当前偶像无法从该经验瓶获得经验";
return false;
}
if (!ExpBottleLedger.EnsureInstance().TryConsume(bottle.bottleKind, 1))
{
failureReason = "经验瓶库存不足";
return false;
}
consumedCount = 1;
lastOperationSuccessMessage = string.Format("给{0}增加了{1}经验", currentHero.ally_heroName, grantedExp);
return true;
}
private bool TryUseRainAllBottle(expBottlesSO bottle, out string failureReason, out int consumedCount, out int grantedExp)
{
consumedCount = 0;
grantedExp = 0;
failureReason = null;
if (bottle == null || currentHero == null)
{
failureReason = "经验瓶配置缺失";
return false;
}
if (!ExpBottleLedger.EnsureInstance().HasEnough(bottle.bottleKind, 1))
{
failureReason = "经验瓶库存不足";
return false;
}
int targetCount = Mathf.Max(1, bottle.rainAllTargetCount);
int expPerHero = Mathf.Max(0, bottle.rainAllGrantedExpPerHero);
if (expPerHero <= 0)
{
failureReason = "雨露均沾经验瓶配置不完整";
return false;
}
List<AllyHero_SO> targets = BuildRainAllTargets(targetCount);
if (targets.Count == 0)
{
failureReason = "没有可被该经验瓶作用的偶像";
return false;
}
int totalGrantedExp = 0;
List<string> recipientSummaries = new List<string>();
List<string> secondaryHeroNames = new List<string>();
List<int> secondaryGrantedValues = new List<int>();
for (int i = 0; i < targets.Count; i++)
{
int grantedToHero = ApplyDirectExpToHero(targets[i], expPerHero);
totalGrantedExp += grantedToHero;
if (grantedToHero > 0)
{
recipientSummaries.Add(string.Format("{0}+{1}", targets[i].ally_heroName, grantedToHero));
if (i > 0)
{
secondaryHeroNames.Add(targets[i].ally_heroName);
secondaryGrantedValues.Add(grantedToHero);
}
}
}
if (totalGrantedExp <= 0)
{
failureReason = "没有可被该经验瓶作用的偶像";
return false;
}
if (!ExpBottleLedger.EnsureInstance().TryConsume(bottle.bottleKind, 1))
{
failureReason = "经验瓶库存不足";
return false;
}
consumedCount = 1;
grantedExp = totalGrantedExp;
lastOperationSuccessMessage = BuildRainAllSuccessMessage(recipientSummaries, secondaryHeroNames, secondaryGrantedValues);
return true;
}
private List<AllyHero_SO> BuildRainAllTargets(int targetCount)
{
List<AllyHero_SO> result = new List<AllyHero_SO>();
if (currentHero == null || !CanReceiveTrainingExp(currentHero))
{
return result;
}
result.Add(currentHero);
if (targetCount <= 1)
{
return result;
}
AllyHero_SO[] heroes = RuntimeResourcesCache.LoadAllAllyHeroes();
List<AllyHero_SO> candidates = new List<AllyHero_SO>();
for (int i = 0; i < heroes.Length; i++)
{
AllyHero_SO hero = heroes[i];
if (hero == null || hero.ally_heroID <= 0 || hero.ally_heroID == currentHero.ally_heroID || !hero.isUnlocked)
{
continue;
}
if (!CanReceiveTrainingExp(hero))
{
continue;
}
candidates.Add(hero);
}
candidates.Sort((left, right) => GetRemainingProgressExp(left).CompareTo(GetRemainingProgressExp(right)));
for (int i = 0; i < candidates.Count && result.Count < targetCount; i++)
{
result.Add(candidates[i]);
}
return result;
}
private bool CanReceiveTrainingExp(AllyHero_SO hero)
{
if (hero == null || hero.ally_heroID <= 0)
{
return false;
}
HeroGrowthTier tier = AllyHeroGrowthService.GetUnlockedTier(hero);
if (tier >= HeroGrowthTier.S)
{
return false;
}
List<AllyHero_SO.AllyLevelInfo> levels = GetSortedLevels(hero);
if (levels.Count < 2)
{
return false;
}
int currentExp = AllyHeroDeployLedger.EnsureInstance().GetCurrentExp(hero.ally_heroID);
int currentCap = GetTierCap(levels, (int)tier);
if (currentExp < currentCap)
{
return true;
}
return AllyHeroDeployLedger.EnsureInstance().IsAutoBreakthroughEnabled(hero.ally_heroID);
}
private int GetRemainingProgressExp(AllyHero_SO hero)
{
if (hero == null)
{
return int.MaxValue;
}
HeroGrowthTier tier = AllyHeroGrowthService.GetUnlockedTier(hero);
List<AllyHero_SO.AllyLevelInfo> levels = GetSortedLevels(hero);
if (levels.Count < 2 || tier >= HeroGrowthTier.S)
{
return int.MaxValue;
}
int currentExp = AllyHeroDeployLedger.EnsureInstance().GetCurrentExp(hero.ally_heroID);
int currentCap = GetTierCap(levels, (int)tier);
if (currentExp >= currentCap && AllyHeroDeployLedger.EnsureInstance().IsAutoBreakthroughEnabled(hero.ally_heroID))
{
return 0;
}
return Mathf.Max(0, currentCap - currentExp);
}
private int ResolveBottleGrantExp(expBottlesSO bottle, AllyHero_SO hero)
{
if (bottle == null || hero == null)
{
return 0;
}
List<AllyHero_SO.AllyLevelInfo> levels = GetSortedLevels(hero);
if (levels.Count == 0)
{
return 0;
}
int tierIndex = Mathf.Clamp(AllyHeroDeployLedger.EnsureInstance().GetUnlockedTierIndex(hero.ally_heroID), 0, levels.Count - 1);
int currentExp = AllyHeroDeployLedger.EnsureInstance().GetCurrentExp(hero.ally_heroID);
int currentCap = GetTierCap(levels, tierIndex);
switch (bottle.expApplyMode)
{
case expBottlesSO.ExpApplyMode.FillCurrentLevel:
return Mathf.Max(0, currentCap - currentExp);
case expBottlesSO.ExpApplyMode.FillAllLevels:
return Mathf.Max(0, levels[levels.Count - 1].requiredEXP - currentExp);
default:
return Mathf.Max(0, bottle.grantedExp);
}
}
private int ApplyDirectExpToHero(AllyHero_SO hero, int expAmount)
{
if (hero == null || hero.ally_heroID <= 0 || expAmount <= 0)
{
return 0;
}
List<AllyHero_SO.AllyLevelInfo> levels = GetSortedLevels(hero);
if (levels.Count == 0)
{
return 0;
}
int currentExp = AllyHeroDeployLedger.EnsureInstance().GetCurrentExp(hero.ally_heroID);
int tierIndex = Mathf.Clamp(AllyHeroDeployLedger.EnsureInstance().GetUnlockedTierIndex(hero.ally_heroID), 0, levels.Count - 1);
bool autoBreakthrough = AllyHeroDeployLedger.EnsureInstance().IsAutoBreakthroughEnabled(hero.ally_heroID);
int remaining = expAmount;
int granted = 0;
while (remaining > 0)
{
if (tierIndex >= (int)HeroGrowthTier.S)
{
break;
}
int currentCap = GetTierCap(levels, tierIndex);
if (!autoBreakthrough)
{
if (currentExp >= currentCap)
{
break;
}
int add = Mathf.Min(remaining, Mathf.Max(0, currentCap - currentExp));
currentExp += add;
granted += add;
break;
}
if (currentExp >= currentCap)
{
tierIndex = Mathf.Clamp(tierIndex + 1, 0, (int)HeroGrowthTier.S);
continue;
}
int delta = Mathf.Min(remaining, Mathf.Max(0, currentCap - currentExp));
currentExp += delta;
granted += delta;
remaining -= delta;
if (currentExp >= currentCap)
{
tierIndex = Mathf.Clamp(tierIndex + 1, 0, (int)HeroGrowthTier.S);
}
}
AllyHeroDeployLedger.EnsureInstance().SetUnlockedTierIndex(hero, tierIndex);
AllyHeroDeployLedger.EnsureInstance().SetCurrentExp(hero, currentExp);
return granted;
}
private bool TryUseSpecialBottleRule(expBottlesSO bottle, upgradeConfig.SpecialBottleRule rule, out string failureReason, out int consumedCount, out int grantedExp)
{
consumedCount = 0;
grantedExp = 0;
failureReason = null;
if (bottle == null || rule == null)
{
failureReason = "经验瓶配置缺失";
return false;
}
if (currentHero == null)
{
failureReason = "未选择偶像";
return false;
}
HeroGrowthTier currentTier = GetCurrentTier();
if (currentTier >= HeroGrowthTier.S)
{
failureReason = "当前偶像已达最高阶";
return false;
}
if (!rule.allowAnyCurrentTier && (currentTier < rule.minimumCurrentTier || currentTier > rule.maximumCurrentTier))
{
failureReason = "当前偶像无法使用该经验瓶";
return false;
}
if (!ExpBottleLedger.EnsureInstance().HasEnough(bottle.bottleKind, 1))
{
failureReason = "经验瓶库存不足";
return false;
}
List<AllyHero_SO.AllyLevelInfo> sortedLevels = GetSortedLevels(currentHero);
if (sortedLevels.Count < 2)
{
failureReason = "偶像等级数据不完整";
return false;
}
int previousExp = AllyHeroDeployLedger.EnsureInstance().GetCurrentExp(currentHero.ally_heroID);
int currentTierIndex = Mathf.Clamp((int)currentTier, 0, sortedLevels.Count - 1);
switch (rule.effect)
{
case upgradeConfig.SpecialBottleEffect.FillToNextTierCap:
{
int currentCap = GetTierCap(sortedLevels, currentTierIndex);
if (previousExp >= currentCap && !AllyHeroDeployLedger.EnsureInstance().IsAutoBreakthroughEnabled(currentHero.ally_heroID))
{
failureReason = "当前等级已满,请先突破";
return false;
}
int needed = Mathf.Max(0, currentCap - previousExp);
grantedExp = needed > 0 ? ApplyDirectExpToHero(currentHero, needed) : 0;
if (grantedExp <= 0)
{
failureReason = "当前偶像无法从该经验瓶获得经验";
return false;
}
if (!ExpBottleLedger.EnsureInstance().TryConsume(bottle.bottleKind, 1))
{
failureReason = "经验瓶库存不足";
return false;
}
consumedCount = 1;
lastOperationSuccessMessage = string.Format("给{0}增加了{1}经验", currentHero.ally_heroName, grantedExp);
return true;
}
case upgradeConfig.SpecialBottleEffect.ReachTargetTierMaxWithoutSkippedBreakthroughPayment:
{
HeroGrowthTier targetTier = rule.targetTier;
int targetTierIndex = Mathf.Clamp((int)targetTier, 0, (int)HeroGrowthTier.A);
if (currentTierIndex > targetTierIndex)
{
failureReason = "当前偶像已超过该经验瓶作用范围";
return false;
}
int targetCap = GetTierCap(sortedLevels, targetTierIndex);
if (currentTierIndex == targetTierIndex && previousExp >= targetCap)
{
failureReason = "当前偶像已达到该培养上限";
return false;
}
PendingDebtState debtState = LoadPendingDebt(currentHero.ally_heroID);
for (int tier = currentTierIndex; tier < targetTierIndex; tier++)
{
upgradeConfig.TierRule tierRule = config.GetRule((HeroGrowthTier)tier);
if (tierRule == null)
{
continue;
}
debtState.pendingCoins += Mathf.Max(0, tierRule.requiredCoins);
if (tierRule.breakthroughCosts != null)
{
for (int i = 0; i < tierRule.breakthroughCosts.Count; i++)
{
upgradeConfig.GrowthMaterialCost cost = tierRule.breakthroughCosts[i];
if (cost == null || cost.material == null || cost.amount <= 0)
{
continue;
}
AddDebtMaterial(debtState, cost.material.materialKind, cost.amount);
}
}
}
if (!ExpBottleLedger.EnsureInstance().TryConsume(bottle.bottleKind, 1))
{
failureReason = "经验瓶库存不足";
return false;
}
SavePendingDebt(currentHero.ally_heroID, debtState);
AllyHeroDeployLedger.EnsureInstance().SetUnlockedTierIndex(currentHero, targetTierIndex);
AllyHeroDeployLedger.EnsureInstance().SetCurrentExp(currentHero, targetCap);
consumedCount = 1;
grantedExp = Mathf.Max(0, targetCap - previousExp);
lastOperationSuccessMessage = string.Format("已将{0}提升至{1}阶满经验", currentHero.ally_heroName, targetTier.ToString());
return true;
}
default:
failureReason = "未支持的特殊经验瓶效果";
return false;
}
}
private bool TryBreakthroughCurrentHero(out string failureReason)
{
failureReason = null;
if (currentHero == null)
{
failureReason = "未选择偶像";
return false;
}
if (!ShouldShowBreakthrough())
{
failureReason = "当前等级经验未满,无法突破";
return false;
}
if (selectedBreakthroughMaterialKind == DushMaterialKind.Material78024)
{
return TryUseUniversalBreakthroughMaterial(out failureReason);
}
upgradeConfig.TierRule rule = GetCurrentRule();
if (rule == null || !rule.canBreakthrough)
{
failureReason = "当前偶像无法突破";
return false;
}
int requiredCoins = GetEffectiveBreakthroughCoinCost();
if (!PlayerEconomyLedger.EnsureInstance().HasEnoughCoins(requiredCoins))
{
failureReason = "金币不足";
return false;
}
List<AggregatedMaterialRequirement> requirements = BuildEffectiveBreakthroughRequirements(false);
for (int i = 0; i < requirements.Count; i++)
{
AggregatedMaterialRequirement requirement = requirements[i];
bool enough = requirement.isBottleRequirement
? ExpBottleLedger.EnsureInstance().GetCount(requirement.bottleKind) >= requirement.requiredAmount
: DushMaterialLedger.EnsureInstance().GetCount(requirement.materialKind) >= requirement.requiredAmount;
if (!enough)
{
failureReason = "突破材料不足";
return false;
}
}
if (!PlayerEconomyLedger.EnsureInstance().TrySpendCoins(requiredCoins))
{
failureReason = "金币不足";
return false;
}
for (int i = 0; i < requirements.Count; i++)
{
AggregatedMaterialRequirement requirement = requirements[i];
if (requirement.isBottleRequirement)
{
ExpBottleLedger.EnsureInstance().TryConsume(requirement.bottleKind, requirement.requiredAmount);
}
else
{
DushMaterialLedger.EnsureInstance().TryConsume(requirement.materialKind, requirement.requiredAmount);
}
}
int nextTierIndex = Mathf.Clamp((int)GetCurrentTier() + 1, 0, (int)HeroGrowthTier.S);
AllyHeroDeployLedger.EnsureInstance().SetUnlockedTierIndex(currentHero, nextTierIndex);
AllyHeroDeployLedger.EnsureInstance().SetCurrentExp(currentHero, AllyHeroDeployLedger.EnsureInstance().GetCurrentExp(currentHero.ally_heroID));
ClearPendingDebt(currentHero.ally_heroID);
return true;
}
private bool TryUseUniversalBreakthroughMaterial(out string failureReason)
{
failureReason = null;
if (currentHero == null)
{
failureReason = "未选择偶像";
return false;
}
growthMaterialSO universal = FindGrowthMaterialDefinition(DushMaterialKind.Material78024);
if (universal == null)
{
failureReason = "未找到通用突破材料定义";
return false;
}
if (DushMaterialLedger.EnsureInstance().GetCount(DushMaterialKind.Material78024) <= 0 || !DushMaterialLedger.EnsureInstance().TryConsume(DushMaterialKind.Material78024, 1))
{
failureReason = "通用突破材料不足";
return false;
}
int nextTierIndex = Mathf.Clamp((int)GetCurrentTier() + 1, 0, (int)HeroGrowthTier.S);
AllyHeroDeployLedger.EnsureInstance().SetAutoBreakthroughEnabled(currentHero, true);
AllyHeroDeployLedger.EnsureInstance().SetUnlockedTierIndex(currentHero, nextTierIndex);
AllyHeroDeployLedger.EnsureInstance().SetCurrentExp(currentHero, AllyHeroDeployLedger.EnsureInstance().GetCurrentExp(currentHero.ally_heroID));
ClearPendingDebt(currentHero.ally_heroID);
return true;
}
private bool ShouldShowBreakthrough()
{
if (currentHero == null || GetCurrentTier() >= HeroGrowthTier.S)
{
return false;
}
if (AllyHeroDeployLedger.EnsureInstance().IsAutoBreakthroughEnabled(currentHero.ally_heroID))
{
return false;
}
List<AllyHero_SO.AllyLevelInfo> sortedLevels = GetSortedLevels(currentHero);
if (sortedLevels.Count < 2)
{
return false;
}
int currentExp = AllyHeroDeployLedger.EnsureInstance().GetCurrentExp(currentHero.ally_heroID);
int currentCap = GetTierCap(sortedLevels, (int)GetCurrentTier());
return currentExp >= currentCap;
}
private int GetEffectiveBreakthroughCoinCost()
{
int coinCost = GetCurrentBreakthroughCoinCost();
if (currentHero == null)
{
return coinCost;
}
PendingDebtState debtState = LoadPendingDebt(currentHero.ally_heroID);
return Mathf.Max(0, coinCost + debtState.pendingCoins);
}
private DushMaterialKind? GetDefaultBreakthroughSelection(List<AggregatedMaterialRequirement> requirements, bool bundledSelection)
{
if (requirements == null || requirements.Count == 0)
{
return null;
}
if (!bundledSelection)
{
return requirements[0].materialKind;
}
for (int i = 0; i < requirements.Count; i++)
{
if (!requirements[i].isUniversalOption)
{
return requirements[i].materialKind;
}
}
return requirements[0].materialKind;
}
private bool ShouldUseBundledBreakthroughSelection(List<AggregatedMaterialRequirement> requirements, bool requiresFullSubmission)
{
if (requiresFullSubmission || currentHero == null || GetCurrentTier() != HeroGrowthTier.A || requirements == null || requirements.Count == 0)
{
return false;
}
bool hasBottleRequirement = false;
bool hasNormalMaterialRequirement = false;
bool hasUniversalOption = false;
for (int i = 0; i < requirements.Count; i++)
{
AggregatedMaterialRequirement requirement = requirements[i];
if (requirement == null)
{
continue;
}
if (requirement.isUniversalOption)
{
hasUniversalOption = true;
continue;
}
if (requirement.isBottleRequirement)
{
hasBottleRequirement = true;
}
else
{
hasNormalMaterialRequirement = true;
}
}
return hasBottleRequirement && hasNormalMaterialRequirement && hasUniversalOption;
}
private bool ShouldHighlightBreakthroughRequirement(AggregatedMaterialRequirement requirement, bool requiresFullSubmission, bool bundledSelection)
{
if (requirement == null)
{
return false;
}
if (requiresFullSubmission)
{
return true;
}
if (!selectedBreakthroughMaterialKind.HasValue)
{
return false;
}
if (!bundledSelection)
{
return requirement.materialKind == selectedBreakthroughMaterialKind.Value;
}
if (selectedBreakthroughMaterialKind.Value == DushMaterialKind.Material78024)
{
return requirement.materialKind == DushMaterialKind.Material78024;
}
return !requirement.isUniversalOption;
}
private List<AggregatedMaterialRequirement> BuildEffectiveBreakthroughRequirements()
{
return BuildEffectiveBreakthroughRequirements(!HasPendingDebtRequirements());
}
private List<AggregatedMaterialRequirement> BuildEffectiveBreakthroughRequirements(bool includeUniversalOption)
{
Dictionary<DushMaterialKind, AggregatedMaterialRequirement> byKind = new Dictionary<DushMaterialKind, AggregatedMaterialRequirement>();
List<DushMaterialKind> orderedKinds = new List<DushMaterialKind>();
List<upgradeConfig.GrowthMaterialCost> directCosts = GetCurrentBreakthroughCosts();
if (directCosts != null)
{
for (int i = 0; i < directCosts.Count; i++)
{
upgradeConfig.GrowthMaterialCost cost = directCosts[i];
if (cost == null || cost.material == null || cost.amount <= 0)
{
continue;
}
AppendRequirement(byKind, orderedKinds, cost.material.materialKind, cost.material, cost.amount, false);
}
}
upgradeConfig.TierRule currentRule = GetCurrentRule();
if (currentRule != null && currentRule.breakthroughBottleCosts != null)
{
for (int i = 0; i < currentRule.breakthroughBottleCosts.Count; i++)
{
upgradeConfig.BreakthroughBottleCost bottleCost = currentRule.breakthroughBottleCosts[i];
if (bottleCost == null || bottleCost.bottle == null || bottleCost.amount <= 0)
{
continue;
}
AppendBottleRequirement(byKind, orderedKinds, bottleCost.bottle.bottleKind, bottleCost.bottle, bottleCost.amount);
}
}
if (currentHero != null)
{
PendingDebtState debtState = LoadPendingDebt(currentHero.ally_heroID);
if (debtState.materials != null)
{
for (int i = 0; i < debtState.materials.Count; i++)
{
PendingDebtMaterialEntry debt = debtState.materials[i];
if (debt == null || debt.amount <= 0)
{
continue;
}
AppendRequirement(byKind, orderedKinds, (DushMaterialKind)debt.materialKind, FindGrowthMaterialDefinition((DushMaterialKind)debt.materialKind), debt.amount, false);
}
}
}
if (includeUniversalOption)
{
growthMaterialSO universal = FindGrowthMaterialDefinition(DushMaterialKind.Material78024);
if (universal != null)
{
AppendRequirement(byKind, orderedKinds, DushMaterialKind.Material78024, universal, 1, true);
}
}
List<AggregatedMaterialRequirement> orderedRequirements = new List<AggregatedMaterialRequirement>(orderedKinds.Count);
for (int i = 0; i < orderedKinds.Count; i++)
{
AggregatedMaterialRequirement requirement;
if (byKind.TryGetValue(orderedKinds[i], out requirement) && requirement != null)
{
orderedRequirements.Add(requirement);
}
}
return orderedRequirements;
}
private materialPrefab CreateMaterialItem()
{
if (materialPrefab == null || materialContainer == null)
{
return null;
}
GameObject instance = Instantiate(materialPrefab, materialContainer);
materialPrefab item = instance.GetComponent<materialPrefab>();
if (item != null)
{
spawnedMaterials.Add(item);
}
return item;
}
private void ClearMaterialItems()
{
for (int i = materialContainer != null ? materialContainer.childCount - 1 : -1; i >= 0; i--)
{
Destroy(materialContainer.GetChild(i).gameObject);
}
spawnedMaterials.Clear();
spawnedBottleBindings.Clear();
}
private string GetDisplayName(expBottlesSO bottle)
{
return GetDisplayName(bottle, bottle != null ? bottle.bottleKind : default);
}
private string GetDisplayName(expBottlesSO bottle, ExpBottleKind fallbackKind)
{
if (bottle != null)
{
storeItemSO storeItem = FindStoreItemForBottle(bottle);
if (storeItem != null && !string.IsNullOrWhiteSpace(storeItem.itemName))
{
return storeItem.itemName.Trim();
}
if (!string.IsNullOrWhiteSpace(bottle.expBottleName))
{
return bottle.expBottleName.Trim();
}
}
return fallbackKind.ToString().Trim();
}
private string GetDisplayName(growthMaterialSO material, DushMaterialKind fallbackKind)
{
if (material != null)
{
storeItemSO storeItem = FindStoreItemForGrowthMaterial(material);
if (storeItem != null && !string.IsNullOrWhiteSpace(storeItem.itemName))
{
return storeItem.itemName.Trim();
}
if (!string.IsNullOrWhiteSpace(material.growthMaterialName))
{
return material.growthMaterialName.Trim();
}
}
return fallbackKind.ToString().Trim();
}
private storeItemSO FindStoreItemForBottle(expBottlesSO bottle)
{
if (bottle == null)
{
return null;
}
storeItemSO[] storeItems = GetStoreItems();
for (int i = 0; i < storeItems.Length; i++)
{
storeItemSO storeItem = storeItems[i];
if (storeItem != null && storeItem.associatedExpBottle == bottle)
{
return storeItem;
}
}
return null;
}
private storeItemSO FindStoreItemForGrowthMaterial(growthMaterialSO material)
{
if (material == null)
{
return null;
}
storeItemSO[] storeItems = GetStoreItems();
for (int i = 0; i < storeItems.Length; i++)
{
storeItemSO storeItem = storeItems[i];
if (storeItem != null && storeItem.associatedGrowthMaterial == material)
{
return storeItem;
}
}
return null;
}
private storeItemSO[] GetStoreItems()
{
if (cachedStoreItems == null)
{
cachedStoreItems = RuntimeResourcesCache.LoadAllStoreItems() ?? Array.Empty<storeItemSO>();
}
return cachedStoreItems;
}
private void SetInfoText(string content)
{
if (infoButton != null)
{
infoButton.text = content ?? string.Empty;
}
}
private void SetConfirmButtonText(string content)
{
Text target = yesdoButtonText;
if (target == null && yesdoButton != null)
{
target = yesdoButton.GetComponentInChildren<Text>(true);
}
if (target != null)
{
target.text = content ?? string.Empty;
}
}
private void SetConfirmInteractable(bool interactable)
{
if (yesdoButton != null)
{
yesdoButton.interactable = interactable;
}
}
private void SetUpgradeTextVisible(bool visible, string content, Color color)
{
if (upgradeText == null)
{
return;
}
upgradeText.gameObject.SetActive(visible);
if (!visible)
{
return;
}
upgradeText.text = content ?? string.Empty;
upgradeText.color = color;
}
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 int GetTierCap(List<AllyHero_SO.AllyLevelInfo> sortedLevels, int tierIndex)
{
if (sortedLevels == null || sortedLevels.Count == 0)
{
return 0;
}
int clampedIndex = Mathf.Clamp(tierIndex, 0, sortedLevels.Count - 1);
if (clampedIndex >= sortedLevels.Count - 1)
{
return Mathf.Max(0, sortedLevels[clampedIndex].requiredEXP);
}
return Mathf.Max(sortedLevels[clampedIndex].requiredEXP, sortedLevels[clampedIndex + 1].requiredEXP);
}
private static bool CanBottleServeTier(expBottlesSO.ServiceLevel serviceLevel, HeroGrowthTier tier)
{
return serviceLevel == expBottlesSO.ServiceLevel.Any || (int)serviceLevel - 1 == (int)tier;
}
private static void AppendRequirement(Dictionary<DushMaterialKind, AggregatedMaterialRequirement> byKind, List<DushMaterialKind> orderedKinds, DushMaterialKind kind, growthMaterialSO material, int amount, bool isUniversalOption)
{
if (amount <= 0)
{
return;
}
AggregatedMaterialRequirement existing;
if (!byKind.TryGetValue(kind, out existing))
{
existing = new AggregatedMaterialRequirement
{
material = material,
materialKind = kind,
requiredAmount = 0,
isUniversalOption = isUniversalOption
};
byKind[kind] = existing;
if (orderedKinds != null)
{
orderedKinds.Add(kind);
}
}
else if (existing.material == null && material != null)
{
existing.material = material;
}
existing.requiredAmount = Mathf.Max(existing.requiredAmount, amount);
existing.isUniversalOption |= isUniversalOption;
}
private static void AppendBottleRequirement(Dictionary<DushMaterialKind, AggregatedMaterialRequirement> byKind, List<DushMaterialKind> orderedKinds, ExpBottleKind kind, expBottlesSO bottle, int amount)
{
if (amount <= 0)
{
return;
}
DushMaterialKind surrogateKind = (DushMaterialKind)(100000 + (int)kind);
AggregatedMaterialRequirement existing;
if (!byKind.TryGetValue(surrogateKind, out existing))
{
existing = new AggregatedMaterialRequirement
{
bottle = bottle,
bottleKind = kind,
requiredAmount = 0,
isUniversalOption = false,
isBottleRequirement = true
};
byKind[surrogateKind] = existing;
if (orderedKinds != null)
{
orderedKinds.Add(surrogateKind);
}
}
else if (existing.bottle == null && bottle != null)
{
existing.bottle = bottle;
}
existing.requiredAmount = Mathf.Max(existing.requiredAmount, amount);
}
private bool HasPendingDebtRequirements()
{
if (currentHero == null)
{
return false;
}
PendingDebtState debtState = LoadPendingDebt(currentHero.ally_heroID);
if (debtState == null)
{
return false;
}
if (debtState.pendingCoins > 0)
{
return true;
}
if (debtState.materials == null)
{
return false;
}
for (int i = 0; i < debtState.materials.Count; i++)
{
PendingDebtMaterialEntry entry = debtState.materials[i];
if (entry != null && entry.amount > 0)
{
return true;
}
}
return false;
}
private static void AddDebtMaterial(PendingDebtState debtState, DushMaterialKind kind, int amount)
{
if (debtState == null || amount <= 0)
{
return;
}
if (debtState.materials == null)
{
debtState.materials = new List<PendingDebtMaterialEntry>();
}
for (int i = 0; i < debtState.materials.Count; i++)
{
PendingDebtMaterialEntry entry = debtState.materials[i];
if (entry != null && entry.materialKind == (int)kind)
{
entry.amount += amount;
return;
}
}
debtState.materials.Add(new PendingDebtMaterialEntry
{
materialKind = (int)kind,
amount = amount
});
}
private string BuildRainAllSuccessMessage(List<string> recipientSummaries, List<string> secondaryHeroNames, List<int> secondaryGrantedValues)
{
if (recipientSummaries == null || recipientSummaries.Count == 0)
{
return string.Format("给{0}增加了经验", currentHero != null ? currentHero.ally_heroName : "当前偶像");
}
if (recipientSummaries.Count == 1)
{
return string.Format("雨露均沾生效:{0}", recipientSummaries[0]);
}
string primary = recipientSummaries[0];
if (secondaryHeroNames != null && secondaryHeroNames.Count > 0 && secondaryGrantedValues != null && secondaryGrantedValues.Count == secondaryHeroNames.Count)
{
bool sameGrant = true;
int granted = secondaryGrantedValues[0];
for (int i = 1; i < secondaryGrantedValues.Count; i++)
{
if (secondaryGrantedValues[i] != granted)
{
sameGrant = false;
break;
}
}
if (sameGrant)
{
return string.Format("雨露均沾生效:{0};同时为{1}提供{2}经验", primary, string.Join("", secondaryHeroNames.ToArray()), granted);
}
}
List<string> others = recipientSummaries.GetRange(1, recipientSummaries.Count - 1);
return string.Format("雨露均沾生效:{0};同时为{1}提供经验", primary, string.Join("", others.ToArray()));
}
private static string GetPendingDebtKey(int heroId)
{
return "idol_upgrade_pending_debt_" + heroId;
}
private static PendingDebtState LoadPendingDebt(int heroId)
{
if (heroId <= 0)
{
return new PendingDebtState();
}
string raw = PlayerPrefs.GetString(GetPendingDebtKey(heroId), string.Empty);
if (string.IsNullOrWhiteSpace(raw))
{
return new PendingDebtState();
}
try
{
PendingDebtState state = JsonUtility.FromJson<PendingDebtState>(raw);
return state ?? new PendingDebtState();
}
catch
{
return new PendingDebtState();
}
}
private static void SavePendingDebt(int heroId, PendingDebtState state)
{
if (heroId <= 0)
{
return;
}
if (state == null || (state.pendingCoins <= 0 && (state.materials == null || state.materials.Count == 0)))
{
ClearPendingDebt(heroId);
return;
}
PlayerPrefs.SetString(GetPendingDebtKey(heroId), JsonUtility.ToJson(state));
PlayerPrefs.Save();
}
private static void ClearPendingDebt(int heroId)
{
if (heroId <= 0)
{
return;
}
PlayerPrefs.DeleteKey(GetPendingDebtKey(heroId));
PlayerPrefs.Save();
}
private static growthMaterialSO FindGrowthMaterialDefinition(DushMaterialKind materialKind)
{
growthMaterialSO[] definitions = Resources.FindObjectsOfTypeAll<growthMaterialSO>();
for (int i = 0; i < definitions.Length; i++)
{
if (definitions[i] != null && definitions[i].materialKind == materialKind)
{
return definitions[i];
}
}
return null;
}
}