using UnityEngine; using UnityEngine.UI; using TMPro; using System; using System.Linq; using System.Threading.Tasks; using GameServer.Client; using DG.Tweening; using UnityEngine.Events; using UnityEngine.SceneManagement; public class SongDetailsUI : MonoBehaviour { private const string BackSceneName = "selectYourSongFirst"; private const float ProgressNumberMinX = -370f; private const float ProgressNumberMaxX = 362f; [Header("this_song_SO")] public ScriptableObject this_song_SO; [Header("ranking")] public Button rankingButton; public rankingList rkl; [Header("room")] public Button roomButton; public GameObject askPrefab; public GameObject roomDetailsPrefab; public GameObject roomRankingPrefab; public Transform roomSpawnPoint; [Header("basic UI")] public Image song_bgImage; public Text songNameText; public Text artistNameText; public Text painterNameText; public Text difficultyText; public Text difficultyDesciptionText; public Text charterNameText; public Text dlcNameText; public TextMeshProUGUI bpmText; public Text songSerialNumberText; public Text personalRecordText; // Documentation text normalized. public Text chartScoreText; public Text thisSong_progressText; public Text thisSong_lastScoreText; // will show last run total score if available public Text idolRecordText; public TextMeshProUGUI timeSpentText; public TextMeshProUGUI enterTimeText; public Text difficultyIdText; public Image progressImage; public GameObject progressNumberObject; public TextMeshProUGUI progressPercentText; public Text progressPercentText2; public Image rankLevel_img; public RankConfig rc; //public Image songImage; public int id_of_thisSong; [Header("Inspector")] public GameObject picDetail; public Image profound_image; public Text detail_informations_text; [Header("back to last scene")] [SerializeField] private Button backtoLastScene; [Header("Inspector")] public Button Button_EZ; public Button Button_HD; public Button Button_IN; public Button Button_IM; private SongData currentSong; private GameObject spawnedAskInstance; private GameObject spawnedRoomDetailsInstance; private SongData lastSyncedSong; private int lastSyncedDifficultyId = int.MinValue; private UnityAction difficultyEzAction; private UnityAction difficultyHdAction; private UnityAction difficultyInAction; private UnityAction difficultyImAction; private RectTransform progressImageRect; private RectTransform progressNumberRect; private float progressImageFullWidth = -1f; private float progressImageLeftEdgeX; private float progressImageScaleX = 1f; private bool progressUsesMaskWidthMode; [SerializeField] private float progressTweenDuration = 0.35f; [SerializeField] private Ease progressTweenEase = Ease.OutCubic; private Tween progressBarTween; private Tween progressNumberTween; private Tween progressTextTween; private float currentProgressVisual; private float currentProgressTextValue; private bool hasStarted; private SongData ResolveCurrentSong() { if (SongDataHolder.SelectedSongData != null) { currentSong = SongDataHolder.SelectedSongData; return currentSong; } if (currentSong != null) { return currentSong; } if (this_song_SO is SongData soSong) { currentSong = soSong; return currentSong; } return null; } private void OnEnable() { currentSong = ResolveCurrentSong(); CacheProgressUiGeometry(forceRefresh: false); ArenaRoomService roomService = ArenaRoomService.Instance; if (roomService != null) { roomService.OnRoomSnapshotChanged -= HandleArenaRoomSnapshotChanged; roomService.OnRoomSnapshotChanged += HandleArenaRoomSnapshotChanged; HandleArenaRoomSnapshotChanged(roomService.CurrentRoom); } if (roomButton == null) { roomButton = FindButtonByName("roomButton"); } if (rankingButton != null) { rankingButton.onClick.RemoveListener(OnRankingButtonClicked); rankingButton.onClick.AddListener(OnRankingButtonClicked); } if (roomButton != null) { roomButton.onClick.RemoveListener(OnRoomButtonClicked); roomButton.onClick.AddListener(OnRoomButtonClicked); } if (backtoLastScene != null) { backtoLastScene.onClick.RemoveListener(OnBackToLastSceneClicked); backtoLastScene.onClick.AddListener(OnBackToLastSceneClicked); } BindDifficultyButtons(); PrepareDifficultyButtons(); if (hasStarted) { ResetProgressVisualState(); SyncSongDisplay(true); } } private void Update() { SyncSongDisplay(false); } private void OnDisable() { KillProgressTweens(); ArenaRoomService roomService = ArenaRoomService.Instance; if (roomService != null) { roomService.OnRoomSnapshotChanged -= HandleArenaRoomSnapshotChanged; } if (rankingButton != null) { rankingButton.onClick.RemoveListener(OnRankingButtonClicked); } if (roomButton != null) { roomButton.onClick.RemoveListener(OnRoomButtonClicked); } if (backtoLastScene != null) { backtoLastScene.onClick.RemoveListener(OnBackToLastSceneClicked); } UnbindDifficultyButtons(); } private void HandleArenaRoomSnapshotChanged(ArenaRoomSnapshot snapshot) { if (snapshot == null || string.IsNullOrWhiteSpace(snapshot.song_id)) { return; } if (!int.TryParse(snapshot.song_id, out int songId)) { Debug.LogWarning($"[SongDetailsUI] Invalid arena room song_id: {snapshot.song_id}"); return; } SongDataLibrary library = SongDataLibrary.Instance; if (library == null) { Debug.LogWarning("[SongDetailsUI] SongDataLibrary is not initialized."); return; } SongData roomSong = library.GetSongDataByID(songId); if (roomSong == null) { Debug.LogWarning($"[SongDetailsUI] Room song not found for song_id={songId}"); return; } int roomDifficultyId = ConvertDifficultyKeyToId(snapshot.difficulty); bool sameSong = currentSong != null && currentSong.songID == roomSong.songID; bool sameDifficulty = sameSong && currentSong.thisLevel_selectedDifficultyID == roomDifficultyId; if (sameSong && sameDifficulty) { return; } ApplySongSelection(roomSong, roomDifficultyId); } private void ApplySongSelection(SongData song, int difficultyId) { if (song == null) { return; } difficultyId = Mathf.Clamp(difficultyId, 0, 3); song.thisLevel_selectedDifficultyID = difficultyId; currentSong = song; this_song_SO = song; SongDataHolder.SelectedSongData = song; id_of_thisSong = song.songID; SyncSongDisplay(true); } private void SyncSongDisplay(bool force) { SongData song = ResolveCurrentSong(); if (song == null) { if (force || lastSyncedSong != null) { SetDifficultyButtons(-1); if (difficultyText != null) difficultyText.text = string.Empty; if (difficultyDesciptionText != null) difficultyDesciptionText.text = string.Empty; if (difficultyIdText != null) difficultyIdText.text = string.Empty; if (personalRecordText != null) personalRecordText.text = "0"; if (idolRecordText != null) idolRecordText.text = "0"; if (chartScoreText != null) chartScoreText.text = "0"; if (thisSong_lastScoreText != null) thisSong_lastScoreText.text = "0"; if (rankLevel_img != null && rc != null) rankLevel_img.sprite = rc.defaultSprite; if (progressImage != null) progressImage.fillAmount = 0f; UpdateProgressUI(0f); } lastSyncedSong = null; lastSyncedDifficultyId = int.MinValue; return; } currentSong = song; this_song_SO = song; id_of_thisSong = song.songID; int selectedDifficulty = Mathf.Clamp(song.thisLevel_selectedDifficultyID, 0, 3); bool songChanged = lastSyncedSong != song; bool difficultyChanged = lastSyncedDifficultyId != selectedDifficulty; if (force || songChanged) { RefreshSongPresentation(song); } if (force || songChanged || difficultyChanged) { SetDifficultyButtons(selectedDifficulty); UpdateDifficultyUI(selectedDifficulty); } lastSyncedSong = song; lastSyncedDifficultyId = selectedDifficulty; } private void RefreshSongPresentation(SongData song) { if (song == null) { return; } if (song_bgImage != null) song_bgImage.sprite = song.GetResolvedBackgroundPic(); if (songNameText != null) songNameText.text = song.songName; if (artistNameText != null) artistNameText.text = song.artistName; if (painterNameText != null) painterNameText.text = song.painter; if (charterNameText != null) charterNameText.text = song.level_creator; if (dlcNameText != null) dlcNameText.text = song.belongsTo_whichDLC; if (songSerialNumberText != null) songSerialNumberText.text = song.songID.ToString(); if (bpmText != null) bpmText.text = LocalizationService.GetFormat("song.details.bpm", song.bpm); if (profound_image != null) profound_image.sprite = song.GetResolvedFullscreenSongPicture(); if (timeSpentText != null) timeSpentText.text = LocalizationService.GetFormat("song.details.play_time", song.time_totalPlayingTime.ToString("F0")); if (enterTimeText != null) enterTimeText.text = LocalizationService.GetFormat("song.details.play_count", song.game_enterTimes); if (detail_informations_text != null) { detail_informations_text.text = LocalizationService.GetFormat("song.details.information", song.artistName, song.songName, song.painter, song.level_creator, song.belongsTo_whichDLC); } } private int ConvertDifficultyKeyToId(string difficultyKey) { if (string.IsNullOrWhiteSpace(difficultyKey)) { return 2; } switch (difficultyKey.Trim().ToLowerInvariant()) { case "ez": return 0; case "hd": return 1; case "im": return 3; case "in": default: return 2; } } void Start() { hasStarted = true; picDetail.SetActive(false); CacheProgressUiGeometry(forceRefresh: false); BindDifficultyButtons(); PrepareDifficultyButtons(); ResetProgressVisualState(); SyncSongDisplay(true); } public void OnRankingButtonClicked() { SongData song = ResolveCurrentSong(); if (song == null) { Debug.LogWarning("[SongDetailsUI] Ranking button clicked, but no song is currently selected."); return; } if (rkl == null) { Debug.LogWarning("[SongDetailsUI] Ranking button clicked, but rankingList reference is missing."); return; } rkl.Open(song.songID.ToString(), song.songName); } public void OnBackToLastSceneClicked() { if (!Bansonic.gTransition.LoadScene(BackSceneName, LoadSceneMode.Single)) { SceneManager.LoadScene(BackSceneName, LoadSceneMode.Single); } } public async void OnRoomButtonClicked() { SongData song = ResolveCurrentSong(); if (song == null) { Debug.LogWarning("[SongDetailsUI] Room button clicked, but no song is currently selected."); return; } ArenaRoomService roomService = ArenaRoomService.Instance; if (roomService == null) { Debug.LogWarning("[SongDetailsUI] ArenaRoomService is missing."); return; } if (roomService.IsInRoom) { try { await roomService.RefreshRoomInfo(); ShowRoomDetails(roomService); } catch (Exception ex) { Debug.LogWarning($"[SongDetailsUI] Failed to refresh current room: {ex.Message}"); DestroyRoomDetailsInstance(); ShowRoomAsk(song, roomService); } return; } DestroyRoomDetailsInstance(); ShowRoomAsk(song, roomService); } public void OpenRoomDetailsForCurrentRoom() { ArenaRoomService roomService = ArenaRoomService.Instance; if (roomService == null || !roomService.IsInRoom) { return; } HandleArenaRoomSnapshotChanged(roomService.CurrentRoom); DestroyAskInstance(); ShowRoomDetails(roomService); } private void ShowRoomAsk(SongData song, ArenaRoomService roomService) { if (spawnedAskInstance != null) { Destroy(spawnedAskInstance); } GameObject askTemplate = ResolveAskTemplate(); if (askTemplate == null) { Debug.LogWarning("[SongDetailsUI] Ask prefab/template is missing."); return; } spawnedAskInstance = Instantiate(askTemplate, ResolveRoomSpawnParent(), false); PrepareSpawnedPanel(spawnedAskInstance); RegisterManagedOverlay(spawnedAskInstance, DestroyAskInstance); roomAsk ask = spawnedAskInstance.GetComponent(); if (ask == null) { Debug.LogWarning("[SongDetailsUI] Spawned ask object has no roomAsk component."); return; } ask.Bind( async () => { await roomService.CreateRoom( song.songID.ToString(), song.songName, ResolveSelectedDifficultyKey(song), null); try { await roomService.RefreshRoomInfo(); } catch (Exception ex) { Debug.LogWarning($"[SongDetailsUI] Refresh room info after create failed: {ex.Message}"); } DestroyAskInstance(); ShowRoomDetails(roomService); }, async roomCode => { await roomService.JoinRoom(roomCode, null); try { await roomService.RefreshRoomInfo(); } catch (Exception ex) { Debug.LogWarning($"[SongDetailsUI] Refresh room info after join failed: {ex.Message}"); } DestroyAskInstance(); ShowRoomDetails(roomService); }); } private void ShowRoomDetails(ArenaRoomService roomService) { if (spawnedRoomDetailsInstance != null) { roomDetails existing = spawnedRoomDetailsInstance.GetComponent(); if (existing != null) { existing.ConfigureRoomRanking(roomRankingPrefab, ResolveRoomSpawnParent()); existing.Bind(roomService); RegisterManagedOverlay(spawnedRoomDetailsInstance, DestroyRoomDetailsInstance); } return; } GameObject roomTemplate = ResolveRoomDetailsTemplate(); if (roomTemplate == null) { Debug.LogWarning("[SongDetailsUI] Room details prefab/template is missing."); return; } spawnedRoomDetailsInstance = Instantiate(roomTemplate, ResolveRoomSpawnParent(), false); PrepareSpawnedPanel(spawnedRoomDetailsInstance); RegisterManagedOverlay(spawnedRoomDetailsInstance, DestroyRoomDetailsInstance); roomDetails details = spawnedRoomDetailsInstance.GetComponent(); if (details == null) { Debug.LogWarning("[SongDetailsUI] Spawned room details object has no roomDetails component."); return; } details.ConfigureRoomRanking(roomRankingPrefab, ResolveRoomSpawnParent()); details.Bind(roomService); } private void DestroyAskInstance() { if (spawnedAskInstance != null) { UIBackStack.Unregister(spawnedAskInstance); Destroy(spawnedAskInstance); spawnedAskInstance = null; } } private void DestroyRoomDetailsInstance() { if (spawnedRoomDetailsInstance != null) { UIBackStack.Unregister(spawnedRoomDetailsInstance); Destroy(spawnedRoomDetailsInstance); spawnedRoomDetailsInstance = null; } } private GameObject ResolveAskTemplate() { if (askPrefab != null) { return askPrefab; } roomAsk sceneTemplate = FindSceneObject(); if (sceneTemplate != null) { sceneTemplate.gameObject.SetActive(false); return sceneTemplate.gameObject; } return null; } private GameObject ResolveRoomDetailsTemplate() { if (roomDetailsPrefab != null) { return roomDetailsPrefab; } roomDetails sceneTemplate = FindSceneObject(); if (sceneTemplate != null) { sceneTemplate.gameObject.SetActive(false); return sceneTemplate.gameObject; } return null; } private Transform ResolveRoomSpawnParent() { if (roomSpawnPoint != null && roomSpawnPoint.gameObject.activeInHierarchy && roomSpawnPoint.lossyScale.sqrMagnitude > 0.0001f) { return roomSpawnPoint; } Canvas canvas = null; if (roomButton != null) { canvas = roomButton.GetComponentsInParent(true).FirstOrDefault(); } if (canvas == null) { canvas = GetComponentsInParent(true).FirstOrDefault(); } if (canvas != null) { return canvas.transform; } return transform; } private void PrepareSpawnedPanel(GameObject panel) { if (panel == null) { return; } panel.SetActive(true); RectTransform rect = panel.GetComponent(); if (rect != null) { rect.localScale = Vector3.one; rect.anchoredPosition3D = Vector3.zero; } } private void RegisterManagedOverlay(GameObject panel, Action closeAction) { if (panel == null) { return; } UIBackStack.RegisterOrBump( panel, () => panel != null && panel.activeInHierarchy, () => { closeAction?.Invoke(); return true; }); } private Button FindButtonByName(string objectName) { Button[] buttons = Resources.FindObjectsOfTypeAll