1167 lines
36 KiB
C#
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;
|
|
}
|
|
}
|