1724 lines
55 KiB
C#
1724 lines
55 KiB
C#
|
||
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();
|
||
BindLedgerEvents();
|
||
RefreshView();
|
||
}
|
||
|
||
private void OnDisable()
|
||
{
|
||
UnbindLedgerEvents();
|
||
}
|
||
|
||
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();
|
||
}
|
||
|
||
private void BindLedgerEvents()
|
||
{
|
||
PlayerEconomyLedger.EnsureInstance().OnCoinsChanged -= HandleExternalResourceChanged;
|
||
PlayerEconomyLedger.EnsureInstance().OnCoinsChanged += HandleExternalResourceChanged;
|
||
PlayerEconomyLedger.EnsureInstance().OnMaterialChanged -= HandleExternalResourceChanged;
|
||
PlayerEconomyLedger.EnsureInstance().OnMaterialChanged += HandleExternalResourceChanged;
|
||
|
||
ExpBottleLedger.EnsureInstance().OnBottleCountChanged -= HandleBottleLedgerChanged;
|
||
ExpBottleLedger.EnsureInstance().OnBottleCountChanged += HandleBottleLedgerChanged;
|
||
ExpBottleLedger.EnsureInstance().OnLedgerReloaded -= HandleExternalLedgerReloaded;
|
||
ExpBottleLedger.EnsureInstance().OnLedgerReloaded += HandleExternalLedgerReloaded;
|
||
|
||
DushMaterialLedger.EnsureInstance().OnMaterialCountChanged -= HandleMaterialLedgerChanged;
|
||
DushMaterialLedger.EnsureInstance().OnMaterialCountChanged += HandleMaterialLedgerChanged;
|
||
|
||
AllyHeroDeployLedger.EnsureInstance().OnHeroGrowthChanged -= HandleHeroGrowthLedgerChanged;
|
||
AllyHeroDeployLedger.EnsureInstance().OnHeroGrowthChanged += HandleHeroGrowthLedgerChanged;
|
||
}
|
||
|
||
private void UnbindLedgerEvents()
|
||
{
|
||
if (PlayerEconomyLedger.Instance != null)
|
||
{
|
||
PlayerEconomyLedger.Instance.OnCoinsChanged -= HandleExternalResourceChanged;
|
||
PlayerEconomyLedger.Instance.OnMaterialChanged -= HandleExternalResourceChanged;
|
||
}
|
||
|
||
if (ExpBottleLedger.Instance != null)
|
||
{
|
||
ExpBottleLedger.Instance.OnBottleCountChanged -= HandleBottleLedgerChanged;
|
||
ExpBottleLedger.Instance.OnLedgerReloaded -= HandleExternalLedgerReloaded;
|
||
}
|
||
|
||
if (DushMaterialLedger.Instance != null)
|
||
{
|
||
DushMaterialLedger.Instance.OnMaterialCountChanged -= HandleMaterialLedgerChanged;
|
||
}
|
||
|
||
if (AllyHeroDeployLedger.Instance != null)
|
||
{
|
||
AllyHeroDeployLedger.Instance.OnHeroGrowthChanged -= HandleHeroGrowthLedgerChanged;
|
||
}
|
||
}
|
||
|
||
private void HandleExternalResourceChanged(int _)
|
||
{
|
||
RefreshViewIfActive();
|
||
}
|
||
|
||
private void HandleBottleLedgerChanged(ExpBottleKind _, int __)
|
||
{
|
||
RefreshViewIfActive();
|
||
}
|
||
|
||
private void HandleExternalLedgerReloaded()
|
||
{
|
||
RefreshViewIfActive();
|
||
}
|
||
|
||
private void HandleMaterialLedgerChanged(DushMaterialKind _, int __)
|
||
{
|
||
RefreshViewIfActive();
|
||
}
|
||
|
||
private void HandleHeroGrowthLedgerChanged(int heroId)
|
||
{
|
||
if (currentHero == null || heroId <= 0 || currentHero.ally_heroID != heroId)
|
||
{
|
||
return;
|
||
}
|
||
|
||
RefreshViewIfActive();
|
||
}
|
||
|
||
private void RefreshViewIfActive()
|
||
{
|
||
if (!isActiveAndEnabled)
|
||
{
|
||
return;
|
||
}
|
||
|
||
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;
|
||
}
|
||
}
|