using System; using System.Collections; using System.Collections.Generic; using DG.Tweening; using UnityEngine; using UnityEngine.UI; #if UNITY_EDITOR using UnityEditor; #endif public class UI_Idols : MonoBehaviour { [Header("quit")] public Button quitButton; [Header("prefabs")] public GameObject idolCardPrefab; public Transform idolCardParent; [Header("sprites")] public Sprite[] idolLevelIcons; public Sprite[] idolBtm; [Header("colors")] public Color[] idolSliderColors; public Color statIncreaseColor = new Color32(0x7C, 0xFF, 0x6B, 0xFF); public Color statDecreaseColor = new Color32(0xFF, 0x5A, 0x5A, 0xFF); [Header("paths")] public string idolSOpath_runtime; public string idolSOpath_editor; [Header("scan")] public bool includeLockedHeroes = false; [Header("middleParts")] public Image idolHDimage; public Text idolBackNameText; public idolMaterialController imc; [Header("rightPanel")] public GameObject rightPanel; public Text idol_chenghaoText; public Text idol_nameText; public Slider idol_expBar; public Text idol_expText; public Image levelIcon; public Image btmIdolProfile; public Text finishGameTimes; public Text mvpGetTimes; public Text joinTeamTimes; [Header("special Skills")] public GameObject ssPrefab; public Transform ssParent; public int maxSSDisplay = 3; [Header("down panels")] public idolLevels il; public idolVoices iv; public idolDocument ido; public idolSkillsHub ish; public idolSkins isk; public idolEquipments ieqpmt; public idolUpgrade iu; [Header("left toggles")] public Toggle idolLevelsToggle; public Toggle idolVoicesToggle; public Toggle idolDetailPassageToggle; public Toggle idolSkillsToggle; public Toggle idolSkinsToggle; public Toggle idolWeaponToggle; [Header("objects")] public GameObject levelsObj; public GameObject voicesObj; public GameObject detailPassageObj; public GameObject skillsObj; public GameObject skinsObj; public GameObject weaponObj; private readonly List spawnedCards = new List(); private readonly List spawnedSpecialSkillEntries = new List(); private AllyHero_SO currentSelectedHero; private bool suppressToggleCallbacks; private int pendingGrowthAnimationHeroId = -1; private Tween idolExpBarTween; private Coroutine startupSkillValidationRoutine; private void Start() { BindQuitButton(); InitializeSectionToggles(); RebuildCards(); BeginStartupSkillValidation(); } private void OnEnable() { BindQuitButton(); btmandtopController.GlobalOverlayPanelVisibilityChanged += HandleOverlayPanelsVisibilityChanged; AllyHeroDeployLedger.EnsureInstance().InitializeIfNeeded(); AllyHeroDeployLedger.EnsureInstance().OnHeroGrowthChanged += HandleHeroGrowthChanged; StoreOwnershipLedger.EnsureInstance().OnOwnershipChanged += HandleOwnershipChanged; InitializeSectionToggles(); RebuildCards(); HandleOverlayPanelsVisibilityChanged(btmandtopController.CurrentOverlayPanelsVisible); } private void OnDisable() { UnbindQuitButton(); btmandtopController.GlobalOverlayPanelVisibilityChanged -= HandleOverlayPanelsVisibilityChanged; if (AllyHeroDeployLedger.Instance != null) { AllyHeroDeployLedger.Instance.OnHeroGrowthChanged -= HandleHeroGrowthChanged; } if (StoreOwnershipLedger.Instance != null) { StoreOwnershipLedger.Instance.OnOwnershipChanged -= HandleOwnershipChanged; } if (idolExpBarTween != null && idolExpBarTween.IsActive()) { idolExpBarTween.Kill(); idolExpBarTween = null; } if (startupSkillValidationRoutine != null) { StopCoroutine(startupSkillValidationRoutine); startupSkillValidationRoutine = null; } UnbindSectionToggles(); } private void BindQuitButton() { if (quitButton == null) { return; } quitButton.onClick.RemoveListener(HandleQuitClicked); quitButton.onClick.AddListener(HandleQuitClicked); } private void UnbindQuitButton() { if (quitButton == null) { return; } quitButton.onClick.RemoveListener(HandleQuitClicked); } private void HandleQuitClicked() { gameObject.SetActive(false); } private void BeginStartupSkillValidation() { if (!isActiveAndEnabled) { return; } if (startupSkillValidationRoutine != null) { StopCoroutine(startupSkillValidationRoutine); } startupSkillValidationRoutine = StartCoroutine(ValidateSkillsOnStartupRoutine()); } private IEnumerator ValidateSkillsOnStartupRoutine() { yield return null; bool anyChanged = false; yield return AllyHeroSkillSelectionValidator.ValidateAllHeroesAsync(2, changed => anyChanged = changed); startupSkillValidationRoutine = null; if (!anyChanged) { yield break; } RebuildCards(false); } private void InitializeSectionToggles() { BindSectionToggles(); ApplySectionSelection(idolLevelsToggle != null ? idolLevelsToggle : GetFirstAvailableToggle()); } private void BindSectionToggles() { UnbindSectionToggles(); BindSectionToggle(idolLevelsToggle); BindSectionToggle(idolVoicesToggle); BindSectionToggle(idolDetailPassageToggle); BindSectionToggle(idolSkillsToggle); BindSectionToggle(idolSkinsToggle); BindSectionToggle(idolWeaponToggle); } private void UnbindSectionToggles() { UnbindSectionToggle(idolLevelsToggle); UnbindSectionToggle(idolVoicesToggle); UnbindSectionToggle(idolDetailPassageToggle); UnbindSectionToggle(idolSkillsToggle); UnbindSectionToggle(idolSkinsToggle); UnbindSectionToggle(idolWeaponToggle); } private void BindSectionToggle(Toggle toggle) { if (toggle == null) { return; } if (toggle == idolLevelsToggle) toggle.onValueChanged.AddListener(OnLevelsToggleChanged); if (toggle == idolVoicesToggle) toggle.onValueChanged.AddListener(OnVoicesToggleChanged); if (toggle == idolDetailPassageToggle) toggle.onValueChanged.AddListener(OnDetailPassageToggleChanged); if (toggle == idolSkillsToggle) toggle.onValueChanged.AddListener(OnSkillsToggleChanged); if (toggle == idolSkinsToggle) toggle.onValueChanged.AddListener(OnSkinsToggleChanged); if (toggle == idolWeaponToggle) toggle.onValueChanged.AddListener(OnWeaponToggleChanged); } private void UnbindSectionToggle(Toggle toggle) { if (toggle == null) { return; } if (toggle == idolLevelsToggle) toggle.onValueChanged.RemoveListener(OnLevelsToggleChanged); if (toggle == idolVoicesToggle) toggle.onValueChanged.RemoveListener(OnVoicesToggleChanged); if (toggle == idolDetailPassageToggle) toggle.onValueChanged.RemoveListener(OnDetailPassageToggleChanged); if (toggle == idolSkillsToggle) toggle.onValueChanged.RemoveListener(OnSkillsToggleChanged); if (toggle == idolSkinsToggle) toggle.onValueChanged.RemoveListener(OnSkinsToggleChanged); if (toggle == idolWeaponToggle) toggle.onValueChanged.RemoveListener(OnWeaponToggleChanged); } private void OnLevelsToggleChanged(bool isOn) => HandleSectionToggleChanged(idolLevelsToggle, isOn); private void OnVoicesToggleChanged(bool isOn) => HandleSectionToggleChanged(idolVoicesToggle, isOn); private void OnDetailPassageToggleChanged(bool isOn) => HandleSectionToggleChanged(idolDetailPassageToggle, isOn); private void OnSkillsToggleChanged(bool isOn) => HandleSectionToggleChanged(idolSkillsToggle, isOn); private void OnSkinsToggleChanged(bool isOn) => HandleSectionToggleChanged(idolSkinsToggle, isOn); private void OnWeaponToggleChanged(bool isOn) => HandleSectionToggleChanged(idolWeaponToggle, isOn); private void HandleSectionToggleChanged(Toggle source, bool isOn) { if (suppressToggleCallbacks || source == null) { return; } if (isOn) { ApplySectionSelection(source); return; } if (!AnySectionToggleOn()) { ApplySectionSelection(idolLevelsToggle != null ? idolLevelsToggle : GetFirstAvailableToggle()); } else { RefreshSectionObjects(); } } private void ApplySectionSelection(Toggle activeToggle) { Toggle resolved = activeToggle != null ? activeToggle : (idolLevelsToggle != null ? idolLevelsToggle : GetFirstAvailableToggle()); suppressToggleCallbacks = true; SetToggleState(idolLevelsToggle, resolved == idolLevelsToggle); SetToggleState(idolVoicesToggle, resolved == idolVoicesToggle); SetToggleState(idolDetailPassageToggle, resolved == idolDetailPassageToggle); SetToggleState(idolSkillsToggle, resolved == idolSkillsToggle); SetToggleState(idolSkinsToggle, resolved == idolSkinsToggle); SetToggleState(idolWeaponToggle, resolved == idolWeaponToggle); suppressToggleCallbacks = false; RefreshSectionObjects(); } private void RefreshSectionObjects() { SetObjectState(levelsObj, idolLevelsToggle != null && idolLevelsToggle.isOn); SetObjectState(voicesObj, idolVoicesToggle != null && idolVoicesToggle.isOn); SetObjectState(detailPassageObj, idolDetailPassageToggle != null && idolDetailPassageToggle.isOn); SetObjectState(skillsObj, idolSkillsToggle != null && idolSkillsToggle.isOn); SetObjectState(skinsObj, idolSkinsToggle != null && idolSkinsToggle.isOn); SetObjectState(weaponObj, idolWeaponToggle != null && idolWeaponToggle.isOn); } private bool AnySectionToggleOn() { return (idolLevelsToggle != null && idolLevelsToggle.isOn) || (idolVoicesToggle != null && idolVoicesToggle.isOn) || (idolDetailPassageToggle != null && idolDetailPassageToggle.isOn) || (idolSkillsToggle != null && idolSkillsToggle.isOn) || (idolSkinsToggle != null && idolSkinsToggle.isOn) || (idolWeaponToggle != null && idolWeaponToggle.isOn); } private Toggle GetFirstAvailableToggle() { if (idolLevelsToggle != null) return idolLevelsToggle; if (idolVoicesToggle != null) return idolVoicesToggle; if (idolDetailPassageToggle != null) return idolDetailPassageToggle; if (idolSkillsToggle != null) return idolSkillsToggle; if (idolSkinsToggle != null) return idolSkinsToggle; if (idolWeaponToggle != null) return idolWeaponToggle; return null; } private static void SetToggleState(Toggle toggle, bool isOn) { if (toggle == null) { return; } toggle.SetIsOnWithoutNotify(isOn); } private static void SetObjectState(GameObject target, bool active) { if (target != null && target.activeSelf != active) { target.SetActive(active); } } [ContextMenu("Rebuild Idols")] public void RebuildCards() { RebuildCards(true); } private void RebuildCards(bool animateSelectedHero) { if (idolCardPrefab == null || idolCardParent == null) { return; } AllyHeroDeployLedger.EnsureInstance().InitializeIfNeeded(); ClearCards(); ClearSpecialSkillEntries(); List heroes = LoadHeroAssets(); if (heroes.Count == 0) { ApplyHeroDetails(null, animateSelectedHero); return; } heroes.Sort((left, right) => left.ally_heroID.CompareTo(right.ally_heroID)); AllyHero_SO firstDisplayedHero = null; AllyHero_SO preferredHero = currentSelectedHero; bool preferredHeroDisplayed = false; for (int i = 0; i < heroes.Count; i++) { AllyHero_SO hero = heroes[i]; if (hero == null) { continue; } if (!includeLockedHeroes && !hero.isUnlocked) { continue; } IdolLevelSnapshot snapshot = BuildLevelSnapshot(hero); GameObject instance = Instantiate(idolCardPrefab, idolCardParent); spawnedCards.Add(instance); idolCardPrefab card = instance.GetComponent(); if (card == null) { continue; } if (firstDisplayedHero == null) { firstDisplayedHero = hero; } if (preferredHero != null && preferredHero.ally_heroID == hero.ally_heroID) { preferredHeroDisplayed = true; } AllyHero_SO capturedHero = hero; card.Setup( hero.ally_hero_squareProfile, hero.ally_heroDesignation, hero.ally_heroName, snapshot.sliderValue, snapshot.cardXpText, snapshot.levelIcon, snapshot.bottomSprite, snapshot.sliderColor, () => ApplyHeroDetails(capturedHero, true)); } ApplyHeroDetails(preferredHeroDisplayed ? preferredHero : firstDisplayedHero, animateSelectedHero); } private List LoadHeroAssets() { List heroes = new List(); #if UNITY_EDITOR if (!Application.isPlaying && !string.IsNullOrWhiteSpace(idolSOpath_editor)) { string[] searchFolders = { idolSOpath_editor }; string[] guids = AssetDatabase.FindAssets("t:AllyHero_SO", searchFolders); for (int i = 0; i < guids.Length; i++) { string assetPath = AssetDatabase.GUIDToAssetPath(guids[i]); AllyHero_SO hero = AssetDatabase.LoadAssetAtPath(assetPath); if (hero != null) { heroes.Add(hero); } } return heroes; } #endif AllyHero_SO[] loadedHeroes = RuntimeResourcesCache.LoadAllAllyHeroes(); for (int i = 0; i < loadedHeroes.Length; i++) { if (loadedHeroes[i] != null) { heroes.Add(loadedHeroes[i]); } } return heroes; } private void ApplyHeroDetails(AllyHero_SO hero) { ApplyHeroDetails(hero, true); } private void ApplyHeroDetails(AllyHero_SO hero, bool animatePortrait) { currentSelectedHero = hero; ClearSpecialSkillEntries(); if (iu != null) { iu.SetHero(hero); } if (ish != null) { ish.SetHero(hero); } if (ieqpmt != null) { ieqpmt.SetHero(hero); } if (isk != null) { isk.SetHero(hero); } if (rightPanel != null) { rightPanel.SetActive(hero != null); } if (hero == null) { SetText(idolBackNameText, string.Empty); SetText(idol_chenghaoText, string.Empty); SetText(idol_nameText, string.Empty); SetText(idol_expText, string.Empty); SetIdolDocumentText(string.Empty); SetText(finishGameTimes, "0000000"); SetText(mvpGetTimes, "0000000"); SetText(joinTeamTimes, "00000000"); ApplyHeroImage(null, animatePortrait); SetImageSprite(levelIcon, null); SetImageSpritePreserveAlpha(btmIdolProfile, null); SetSliderVisual(idol_expBar, 0f, Color.white); ApplyLevelDetails(null); ApplyBehaviourRadar(null); return; } IdolLevelSnapshot snapshot = BuildLevelSnapshot(hero); ApplyHeroImage(hero.ally_hero_HD_image, animatePortrait); SetText(idolBackNameText, hero.ally_heroName); SetText(idol_chenghaoText, hero.ally_heroDesignation); SetText(idol_nameText, hero.ally_heroName); SetIdolDocumentText(hero.ally_heroDescription); bool animateExpBar = pendingGrowthAnimationHeroId > 0 && hero.ally_heroID == pendingGrowthAnimationHeroId; SetSliderVisual(idol_expBar, snapshot.sliderValue, snapshot.sliderColor, animateExpBar); if (animateExpBar) { pendingGrowthAnimationHeroId = -1; } SetText(idol_expText, snapshot.detailXpText); SetImageSprite(levelIcon, snapshot.levelIcon); SetImageSpritePreserveAlpha(btmIdolProfile, hero.ally_hero_squareProfile); SetText(finishGameTimes, FormatSevenDigitCount(hero.ally_finishCount)); SetText(mvpGetTimes, FormatSevenDigitCount(hero.ally_mvpCount)); SetText(joinTeamTimes, FormatJoinDate(hero.ally_joinDateUtcTicks)); ApplyLevelDetails(GetEffectiveLevelInfoFromLedger(hero)); ApplyBehaviourRadar(hero); PopulateSpecialSkills(hero); } private void SetIdolDocumentText(string value) { if (ido != null && ido.idol_documentText != null) { ido.idol_documentText.text = value ?? string.Empty; } } private void ApplyHeroImage(Sprite sprite) { ApplyHeroImage(sprite, true); } private void ApplyHeroImage(Sprite sprite, bool animate) { if (imc != null) { if (animate) { imc.TransitionToSprite(sprite); } else { imc.SetSpriteImmediate(sprite); } return; } SetImageSprite(idolHDimage, sprite); } private IdolLevelSnapshot BuildLevelSnapshot(AllyHero_SO hero) { IdolLevelSnapshot snapshot = new IdolLevelSnapshot { sliderValue = 0f, cardXpText = "UNKNOWN", detailXpText = "UNKNOWN", levelIcon = GetLevelIconByIndex(4), bottomSprite = GetBottomSpriteByIndex(4), sliderColor = GetSliderColorByIndex(4) }; if (hero == null) { return snapshot; } List levels = new List(); if (hero.levelStats != null) { for (int i = 0; i < hero.levelStats.Count; i++) { if (hero.levelStats[i] != null) { levels.Add(hero.levelStats[i]); } } } if (levels.Count == 0) { return snapshot; } levels.Sort((left, right) => left.requiredEXP.CompareTo(right.requiredEXP)); AllyHeroDeployLedger ledger = AllyHeroDeployLedger.EnsureInstance(); int currentExp = hero.ally_heroID > 0 ? ledger.GetCurrentExp(hero.ally_heroID) : Mathf.Max(0, hero.ally_currentEXP); int displayIndex = ResolveUnlockedLevelIndexFromLedger(hero, levels, ledger); AllyHero_SO.AllyLevelInfo currentLevel = levels[displayIndex]; int tierIndex = ResolveTierIndex(currentLevel, displayIndex, levels.Count); snapshot.levelIcon = GetLevelIconByIndex(tierIndex); snapshot.bottomSprite = GetBottomSpriteByIndex(tierIndex); snapshot.sliderColor = GetSliderColorByIndex(tierIndex); if (displayIndex >= levels.Count - 1) { snapshot.sliderValue = 1f; snapshot.cardXpText = "MAX"; snapshot.detailXpText = "MAX"; return snapshot; } int floorExp = currentLevel.requiredEXP; int nextLevelIndex = Mathf.Clamp(displayIndex + 1, 0, levels.Count - 1); int nextExp = levels[nextLevelIndex].requiredEXP; int range = Mathf.Max(1, nextExp - floorExp); int gained = Mathf.Clamp(currentExp - floorExp, 0, range); snapshot.sliderValue = Mathf.Clamp01(gained / (float)range); int neededExp = nextExp - currentExp; if (neededExp > 0) { snapshot.cardXpText = "NEED " + neededExp + " EXP"; snapshot.detailXpText = gained + "/" + range; } else { snapshot.cardXpText = "READY"; snapshot.detailXpText = range + "/" + range; } return snapshot; } private void PopulateSpecialSkills(AllyHero_SO hero) { if (hero == null || ssPrefab == null || ssParent == null || maxSSDisplay <= 0 || hero.skillGroups == null) { return; } int shown = 0; for (int i = 0; i < hero.skillGroups.Length; i++) { SkillGroup group = hero.skillGroups[i]; if (group == null || !group.isSpecialSkill) { continue; } GameObject instance = Instantiate(ssPrefab, ssParent); spawnedSpecialSkillEntries.Add(instance); ssPrefab display = instance.GetComponent(); if (display != null) { display.Setup(group.groupIcon, group.groupName); } shown++; if (shown >= maxSSDisplay) { break; } } } private int ResolveTierIndex(AllyHero_SO.AllyLevelInfo levelInfo, int levelIndex, int levelCount) { if (levelInfo != null && !string.IsNullOrWhiteSpace(levelInfo.levelName)) { string normalized = levelInfo.levelName.Trim().ToUpperInvariant(); if (normalized.Contains("S")) return 3; if (normalized.Contains("A")) return 2; if (normalized.Contains("B")) return 1; if (normalized.Contains("C")) return 0; } if (levelCount <= 1) { return 0; } float normalizedIndex = levelIndex / (float)Mathf.Max(1, levelCount - 1); if (normalizedIndex >= 0.99f) return 3; if (normalizedIndex >= 0.66f) return 2; if (normalizedIndex >= 0.33f) return 1; return 0; } private Sprite GetLevelIconByIndex(int index) { if (idolLevelIcons == null || idolLevelIcons.Length == 0) { return null; } return idolLevelIcons[Mathf.Clamp(index, 0, idolLevelIcons.Length - 1)]; } private Sprite GetBottomSpriteByIndex(int index) { if (idolBtm == null || idolBtm.Length == 0) { return null; } return idolBtm[Mathf.Clamp(index, 0, idolBtm.Length - 1)]; } private Color GetSliderColorByIndex(int index) { if (idolSliderColors == null || idolSliderColors.Length == 0) { return Color.white; } return idolSliderColors[Mathf.Clamp(index, 0, idolSliderColors.Length - 1)]; } private void ClearCards() { for (int i = spawnedCards.Count - 1; i >= 0; i--) { if (spawnedCards[i] == null) { continue; } #if UNITY_EDITOR if (!Application.isPlaying) { DestroyImmediate(spawnedCards[i]); } else #endif { Destroy(spawnedCards[i]); } } spawnedCards.Clear(); if (idolCardParent == null) { return; } for (int i = idolCardParent.childCount - 1; i >= 0; i--) { Transform child = idolCardParent.GetChild(i); if (child == null || child.GetComponent() == null) { continue; } #if UNITY_EDITOR if (!Application.isPlaying) { DestroyImmediate(child.gameObject); } else #endif { Destroy(child.gameObject); } } } private void ClearSpecialSkillEntries() { for (int i = spawnedSpecialSkillEntries.Count - 1; i >= 0; i--) { if (spawnedSpecialSkillEntries[i] == null) { continue; } #if UNITY_EDITOR if (!Application.isPlaying) { DestroyImmediate(spawnedSpecialSkillEntries[i]); } else #endif { Destroy(spawnedSpecialSkillEntries[i]); } } spawnedSpecialSkillEntries.Clear(); if (ssParent == null) { return; } for (int i = ssParent.childCount - 1; i >= 0; i--) { Transform child = ssParent.GetChild(i); if (child == null || child.GetComponent() == null) { continue; } #if UNITY_EDITOR if (!Application.isPlaying) { DestroyImmediate(child.gameObject); } else #endif { Destroy(child.gameObject); } } } private static void SetText(Text target, string value) { if (target != null) { target.text = value ?? string.Empty; } } private static void SetImageSprite(Image target, Sprite sprite) { if (target == null) { return; } target.sprite = sprite; target.color = sprite != null ? new Color(target.color.r, target.color.g, target.color.b, 1f) : new Color(target.color.r, target.color.g, target.color.b, 0f); } private static void SetImageSpritePreserveAlpha(Image target, Sprite sprite) { if (target == null) { return; } target.sprite = sprite; } private void SetSliderVisual(Slider slider, float normalizedValue, Color fillColor, bool animate = false) { if (slider == null) { return; } float targetValue = Mathf.Clamp01(normalizedValue); if (slider.fillRect == null) { slider.normalizedValue = targetValue; return; } Image fillImage = slider.fillRect.GetComponent(); if (fillImage != null) { fillImage.color = fillColor; } if (animate) { if (idolExpBarTween != null && idolExpBarTween.IsActive()) { idolExpBarTween.Kill(); } float fromValue = slider.normalizedValue; slider.normalizedValue = fromValue; idolExpBarTween = DOTween.To(() => slider.normalizedValue, value => slider.normalizedValue = value, targetValue, 0.25f) .SetEase(Ease.OutCubic) .SetUpdate(true); return; } slider.normalizedValue = targetValue; } private void ApplyLevelDetails(AllyHero_SO.AllyLevelInfo levelInfo) { if (il == null) { return; } if (levelInfo == null) { SetText(il.hp, "0"); SetText(il.attack, "0"); SetText(il.mana, "0"); SetText(il.damageResistant, "0%"); SetText(il.scoreEfficency, "0%"); SetText(il.slotAmount, "0"); SetText(il.miss_mr, "0"); SetText(il.good_mr, "0"); SetText(il.great_mr, "0"); SetText(il.perfect_mr, "0"); SetText(il.good_dm, "0%"); SetText(il.great_dm, "0%"); SetText(il.perfect_dm, "0%"); SetText(il.miss_hl, "0"); return; } AllyHero_SO.AllyLevelInfo baseLevelInfo = currentSelectedHero != null ? GetBaseLevelInfoFromLedger(currentSelectedHero) : levelInfo; SetText(il.hp, FormatGrowthInt(baseLevelInfo != null ? baseLevelInfo.maxHP : levelInfo.maxHP, levelInfo.maxHP)); SetText(il.attack, FormatGrowthInt(baseLevelInfo != null ? baseLevelInfo.attack : levelInfo.attack, levelInfo.attack)); SetText(il.mana, FormatGrowthInt(baseLevelInfo != null ? baseLevelInfo.maxMana : levelInfo.maxMana, levelInfo.maxMana)); SetText(il.damageResistant, FormatGrowthPercent(baseLevelInfo != null ? baseLevelInfo.damageResistance : levelInfo.damageResistance, levelInfo.damageResistance)); SetText(il.scoreEfficency, FormatGrowthPercent(baseLevelInfo != null ? baseLevelInfo.scoreEfficiency : levelInfo.scoreEfficiency, levelInfo.scoreEfficiency)); int baseSlotAmount = baseLevelInfo != null ? baseLevelInfo.skill_slot_limited : levelInfo.skill_slot_limited; int effectiveSlotAmount = currentSelectedHero != null ? currentSelectedHero.GetEffectiveSkillSlotLimit() : levelInfo.skill_slot_limited; SetText(il.slotAmount, FormatGrowthInt(baseSlotAmount, effectiveSlotAmount)); SetText(il.miss_mr, levelInfo.manaGainOnMiss.ToString()); SetText(il.good_mr, levelInfo.manaGainGood.ToString()); SetText(il.great_mr, levelInfo.manaGainGreat.ToString()); SetText(il.perfect_mr, levelInfo.manaGainPerfect.ToString()); SetText(il.good_dm, FormatPercent(levelInfo.damageMultiplierGood)); SetText(il.great_dm, FormatPercent(levelInfo.damageMultiplierGreat)); SetText(il.perfect_dm, FormatPercent(levelInfo.damageMultiplierPerfect)); SetText(il.miss_hl, FormatNumber(levelInfo.missHpLossBase)); } private AllyHero_SO.AllyLevelInfo GetEffectiveLevelInfoFromLedger(AllyHero_SO hero) { AllyHero_SO.AllyLevelInfo baseLevelInfo = GetBaseLevelInfoFromLedger(hero); return hero != null ? hero.GetEffectiveLevelInfoWithEquipment(baseLevelInfo) : null; } private AllyHero_SO.AllyLevelInfo GetBaseLevelInfoFromLedger(AllyHero_SO hero) { if (hero == null || hero.levelStats == null || hero.levelStats.Count == 0) { return null; } List levels = new List(); for (int i = 0; i < hero.levelStats.Count; i++) { if (hero.levelStats[i] != null) { levels.Add(hero.levelStats[i]); } } if (levels.Count == 0) { return null; } levels.Sort((left, right) => left.requiredEXP.CompareTo(right.requiredEXP)); int index = ResolveUnlockedLevelIndexFromLedger(hero, levels, AllyHeroDeployLedger.EnsureInstance()); if (index < 0 || index >= levels.Count) { return null; } return levels[index]; } private int ResolveUnlockedLevelIndexFromLedger(AllyHero_SO hero, List levels, AllyHeroDeployLedger ledger) { if (levels == null || levels.Count == 0) { return 0; } if (hero == null) { return 0; } int unlockedIndex = hero.ally_heroID > 0 ? ledger.GetUnlockedTierIndex(hero.ally_heroID) : hero.ally_growthUnlockedTierIndex; return Mathf.Clamp(unlockedIndex, 0, levels.Count - 1); } public void RefreshCurrentHeroDetails() { ApplyHeroDetails(currentSelectedHero, false); } private void ApplyBehaviourRadar(AllyHero_SO hero) { if (ido == null || ido.idol_rader == null) { return; } idolRadarController radarController = ido.idol_rader.GetComponent(); if (radarController == null) { radarController = ido.idol_rader.GetComponentInChildren(true); } if (radarController != null) { radarController.ApplyHero(hero); } } private void HandleOverlayPanelsVisibilityChanged(bool visible) { if (ido == null || ido.idol_rader == null) { return; } ido.idol_rader.SetActive(!visible); if (!visible) { ApplyBehaviourRadar(currentSelectedHero); } } private void HandleHeroGrowthChanged(int heroId) { if (heroId <= 0) { return; } if (currentSelectedHero != null && currentSelectedHero.ally_heroID == heroId) { pendingGrowthAnimationHeroId = heroId; } RebuildCards(false); } private void HandleOwnershipChanged(int _) { RebuildCards(false); } private static string FormatSevenDigitCount(int value) { if (value <= 0) { return "0000000"; } if (value >= 9999999) { return "9999999"; } return value.ToString("D7"); } private static string FormatJoinDate(long utcTicks) { if (utcTicks <= 0L) { return "--------"; } try { DateTime date = new DateTime(utcTicks, DateTimeKind.Utc); if (date.Year <= 1 && date.Month <= 1 && date.Day <= 1) { return "--------"; } return date.ToString("yyyyMMdd"); } catch { return "--------"; } } private static string FormatPercent(float value) { return Mathf.RoundToInt(value * 100f) + "%"; } private string FormatGrowthInt(int baseValue, int effectiveValue) { int growth = effectiveValue - baseValue; if (growth == 0) { return baseValue.ToString(); } string sign = growth > 0 ? "+" : "-"; string color = "#" + ColorUtility.ToHtmlStringRGB(growth > 0 ? statIncreaseColor : statDecreaseColor); return $"{baseValue} {sign} {Mathf.Abs(growth)}"; } private string FormatGrowthPercent(float baseValue, float effectiveValue) { float growth = effectiveValue - baseValue; string baseText = FormatPercent(baseValue); if (Mathf.Approximately(growth, 0f)) { return baseText; } string sign = growth > 0f ? "+" : "-"; string color = "#" + ColorUtility.ToHtmlStringRGB(growth > 0f ? statIncreaseColor : statDecreaseColor); return $"{baseText} {sign} {FormatPercent(Mathf.Abs(growth))}"; } private static string FormatNumber(float value) { if (Mathf.Approximately(value, Mathf.Round(value))) { return Mathf.RoundToInt(value).ToString(); } return value.ToString("0.##"); } private struct IdolLevelSnapshot { public float sliderValue; public string cardXpText; public string detailXpText; public Sprite levelIcon; public Sprite bottomSprite; public Color sliderColor; } }