Files
bansonic_beta_main/Assets/idolsDisplay/UI_Idols.cs
T
2026-07-20 22:19:25 +08:00

1167 lines
36 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using Bansonic;
using DG.Tweening;
using UnityEngine;
using UnityEngine.UI;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class UI_Idols : MonoBehaviour
{
private const string MemorySystemComingSoonMessage = "\"记忆\"系统即将上线,敬请期待!";
[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<GameObject> spawnedCards = new List<GameObject>();
private readonly List<GameObject> spawnedSpecialSkillEntries = new List<GameObject>();
private AllyHero_SO currentSelectedHero;
private bool suppressToggleCallbacks;
private int pendingGrowthAnimationHeroId = -1;
private Tween idolExpBarTween;
private Coroutine startupSkillValidationRoutine;
private bool hasStarted;
private void Start()
{
hasStarted = true;
BindQuitButton();
InitializeSectionToggles();
RebuildCards();
BeginStartupSkillValidation();
}
private void OnEnable()
{
BindQuitButton();
btmandtopController.GlobalOverlayPanelVisibilityChanged += HandleOverlayPanelsVisibilityChanged;
AllyHeroDeployLedger.EnsureInstance().InitializeIfNeeded();
AllyHeroDeployLedger.EnsureInstance().OnHeroGrowthChanged += HandleHeroGrowthChanged;
StoreOwnershipLedger.EnsureInstance().OnOwnershipChanged += HandleOwnershipChanged;
InitializeSectionToggles();
if (hasStarted)
{
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()
{
UI_PanelExitUtility.PlayExitThen(gameObject, () =>
{
if (gameObject != null)
{
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)
{
if (source == idolWeaponToggle)
{
ShowMemorySystemComingSoon();
ApplySectionSelection(GetFirstAvailableUnlockedToggle());
return;
}
ApplySectionSelection(source);
return;
}
if (!AnySectionToggleOn())
{
ApplySectionSelection(GetFirstAvailableUnlockedToggle());
}
else
{
RefreshSectionObjects();
}
}
private void ApplySectionSelection(Toggle activeToggle)
{
Toggle resolved = activeToggle != null && activeToggle != idolWeaponToggle
? activeToggle
: GetFirstAvailableUnlockedToggle();
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);
PlayActiveSectionRefresh();
}
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 Toggle GetFirstAvailableUnlockedToggle()
{
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;
return null;
}
private static void ShowMemorySystemComingSoon()
{
gNotice.error.display(MemorySystemComingSoonMessage);
}
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<AllyHero_SO> 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;
int visibleCardCount = 0;
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 = GetOrCreateCard(visibleCardCount);
UI_OrderedEntryAnimator.PlaySingle(instance, visibleCardCount, 0.18f, 0.015f, -18f, 0.965f, true);
visibleCardCount++;
idolCardPrefab card = instance.GetComponent<idolCardPrefab>();
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<AllyHero_SO> LoadHeroAssets()
{
List<AllyHero_SO> heroes = new List<AllyHero_SO>();
#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<AllyHero_SO>(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);
UI_OrderedEntryAnimator.PlayRefresh(rightPanel, 0.16f, -8f, 0.99f, true);
}
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<AllyHero_SO.AllyLevelInfo> levels = new List<AllyHero_SO.AllyLevelInfo>();
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 = GetOrCreateSpecialSkillEntry(shown);
UI_OrderedEntryAnimator.PlaySingle(instance, shown, 0.16f, 0.025f, -10f, 0.97f, true);
ssPrefab display = instance.GetComponent<ssPrefab>();
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--)
{
GameObject card = spawnedCards[i];
if (card == null)
{
continue;
}
if (card.activeSelf)
{
card.SetActive(false);
}
}
}
private void ClearSpecialSkillEntries()
{
for (int i = spawnedSpecialSkillEntries.Count - 1; i >= 0; i--)
{
GameObject entry = spawnedSpecialSkillEntries[i];
if (entry == null)
{
continue;
}
if (entry.activeSelf)
{
entry.SetActive(false);
}
}
}
private GameObject GetOrCreateCard(int index)
{
while (spawnedCards.Count <= index)
{
GameObject instance = Instantiate(idolCardPrefab, idolCardParent);
spawnedCards.Add(instance);
}
GameObject card = spawnedCards[index];
if (card != null)
{
card.transform.SetParent(idolCardParent, false);
card.transform.SetSiblingIndex(index);
if (!card.activeSelf)
{
card.SetActive(true);
}
}
return card;
}
private GameObject GetOrCreateSpecialSkillEntry(int index)
{
while (spawnedSpecialSkillEntries.Count <= index)
{
GameObject instance = Instantiate(ssPrefab, ssParent);
spawnedSpecialSkillEntries.Add(instance);
}
GameObject entry = spawnedSpecialSkillEntries[index];
if (entry != null)
{
entry.transform.SetParent(ssParent, false);
entry.transform.SetSiblingIndex(index);
if (!entry.activeSelf)
{
entry.SetActive(true);
}
}
return entry;
}
private void PlayActiveSectionRefresh()
{
GameObject active = null;
if (levelsObj != null && levelsObj.activeSelf) active = levelsObj;
else if (voicesObj != null && voicesObj.activeSelf) active = voicesObj;
else if (detailPassageObj != null && detailPassageObj.activeSelf) active = detailPassageObj;
else if (skillsObj != null && skillsObj.activeSelf) active = skillsObj;
else if (skinsObj != null && skinsObj.activeSelf) active = skinsObj;
else if (weaponObj != null && weaponObj.activeSelf) active = weaponObj;
UI_OrderedEntryAnimator.PlayRefresh(active, 0.16f, -8f, 0.99f, true);
}
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<Image>();
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 ? AllyHeroLevelRuntimeResolver.GetEffectiveSkillSlotLimit(currentSelectedHero) : 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)
{
return AllyHeroLevelRuntimeResolver.GetEffectiveLevelInfo(hero);
}
private AllyHero_SO.AllyLevelInfo GetBaseLevelInfoFromLedger(AllyHero_SO hero)
{
return AllyHeroLevelRuntimeResolver.GetBaseLevelInfo(hero);
}
private int ResolveUnlockedLevelIndexFromLedger(AllyHero_SO hero, List<AllyHero_SO.AllyLevelInfo> 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()
{
if (currentSelectedHero == null)
{
ApplyHeroDetails(null, false);
return;
}
IdolLevelSnapshot snapshot = BuildLevelSnapshot(currentSelectedHero);
SetSliderVisual(idol_expBar, snapshot.sliderValue, snapshot.sliderColor, false);
SetText(idol_expText, snapshot.detailXpText);
SetImageSprite(levelIcon, snapshot.levelIcon);
ApplyLevelDetails(GetEffectiveLevelInfoFromLedger(currentSelectedHero));
if (ieqpmt != null)
{
ieqpmt.RefreshCurrentHeroEquipmentState();
}
}
private void ApplyBehaviourRadar(AllyHero_SO hero)
{
if (ido == null || ido.idol_rader == null)
{
return;
}
idolRadarController radarController = ido.idol_rader.GetComponent<idolRadarController>();
if (radarController == null)
{
radarController = ido.idol_rader.GetComponentInChildren<idolRadarController>(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} <color={color}>{sign} {Mathf.Abs(growth)}</color>";
}
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} <color={color}>{sign} {FormatPercent(Mathf.Abs(growth))}</color>";
}
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;
}
}