using UnityEngine; using UnityEngine.UI; using System.Collections.Generic; using UnityEngine.Serialization; using UnityEngine.SceneManagement; using System.Collections; using UnityEngine.Audio; using UnityEditor; public class settlementController : MonoBehaviour { [Header("Inspector")] [SerializeField] private BeatmapManager bmm; [SerializeField] private ScoreManager sm; public GameManager gm; private SongData thisSong_so; private int maxScore_sum = 2000000; [Header("1000000")] public int _1000000 = 1000000; [Header("song image")] [SerializeField] private Image thisSong_backPic; [Header("Inspector")] public Button exit_toSelectSongs; public Button replay_thisGame; public Button display_rankList; public Button share_toSocialMedia; [Header("Text And Progress")] public Text songName_Text; [Tooltip("Documentation text normalized.")] public Text thisLevel_currentPercentage_Text; public Text finalScore_Text; public Text pmScoreSum_Text; public Text idolScoreSum_Text; public Text accuracy_Text; public Image thisLevel_progressBar_Image; [Header("Accuracy Weights")] public float perfect_weight = 1f; public float great_weight = 0.6666667f; public float good_weight = 0.333333f; public float miss_weight = 0; [Header("Inspector")] public Text reward_playerEXP_Text; public Text reward_money_Text; public Text reward_idolEXP_bottle_Text; [Header("Inspector")] public loadSettlementTeamPrefab settlementTeamLoader; public Image mvp_hero_hd_image; [Header("Inspector")] [SerializeField] private long moneyToGive_thisLevel; // Documentation text normalized. [Header("Inspector")] public Text perfectHitCount_Text; public Text perfectHitPercent_Text; public Image perfect_barFill_Image; public Text greatHitCount_Text; public Text greatHitPercent_Text; public Image great_barFill_Image; public Text goodHitCount_Text; public Text goodHitPercent_Text; public Image good_barFill_Image; public Text missHitCount_Text; public Text missHitPercent_Text; public Image miss_barFill_Image; public Text gameNote_rate; [Header("Timing Statistics")] public Text earlyHitCount_Text; public Text lateHitCount_Text; public Text avgOffset_Text; [Header("Inspector")] [Tooltip("Settlement background music clip used during the settlement flow.")] public AudioSource settlementAudioSource; [Tooltip("Original gameplay music source (usually the chart song). ")] public AudioSource oriGameMusicSource; [Tooltip("Documentation text normalized.")] public AudioMixer gameMusicMixer; [Tooltip("Documentation text normalized.")] public string lowpassParamName = "inGameMusic_lowpass"; [Tooltip("Duration of lowpass transition in seconds.")] public float lowpassFadeDuration = 2f; [Tooltip("Settlement CanvasGroup used for fade-in.")] public CanvasGroup settlementCanvasGroup; [Header("Inspector")] [Tooltip("Settlement control manager object.")] public GameObject cdm; private Coroutine musicTransitionCoroutine; private Coroutine canvasFadeCoroutine; private bool settlementUiInitialized = false; private void Awake() { // Documentation text normalized. if (replay_thisGame != null) { replay_thisGame.onClick.AddListener(OnReplayButtonClicked); } if (exit_toSelectSongs != null) { exit_toSelectSongs.onClick.AddListener(OnExitButtonClicked); } // Documentation text normalized. if (cdm != null) { cdm.SetActive(false); if (JudgeManager.IsDebugEnabled) Debug.Log("[SettlementController] CDM object disabled at startup"); } else { Debug.LogWarning("[SettlementController] CDM object is not assigned"); } } private void Start() { // Documentation text normalized. if (gameMusicMixer != null && !string.IsNullOrEmpty(lowpassParamName)) { try { gameMusicMixer.SetFloat(lowpassParamName, 22000f); } catch { } } } private void OnDestroy() { if (replay_thisGame != null) { replay_thisGame.onClick.RemoveListener(OnReplayButtonClicked); } if (exit_toSelectSongs != null) { exit_toSelectSongs.onClick.RemoveListener(OnExitButtonClicked); } // Documentation text normalized. if (musicTransitionCoroutine != null) { StopCoroutine(musicTransitionCoroutine); musicTransitionCoroutine = null; } // Documentation text normalized. if (canvasFadeCoroutine != null) { StopCoroutine(canvasFadeCoroutine); canvasFadeCoroutine = null; } } // Start is called once before the first execution of Update after the MonoBehaviour is created public void getThisSong_info() { if (bmm != null) { thisSong_so = bmm.assignedSongData; } } public void startSettlement_uiUpdate() { if (settlementUiInitialized) { Debug.Log("[SettlementController] startSettlement_uiUpdate ignored: settlement already initialized."); return; } settlementUiInitialized = true; // Documentation text normalized. InitializeSettlementCanvas(); // Documentation text normalized. if (cdm != null) { cdm.SetActive(true); Debug.Log("[SettlementController] CDM object enabled at settlement start"); } getThisSong_info(); // --- Statistics: Record total play time at settlement --- if (gm != null) gm.RecordTotalPlayTime(); else { var activeGM = FindAnyObjectByType(); if (activeGM != null) activeGM.RecordTotalPlayTime(); } // Documentation text normalized. PrepareSettlementMusic(); if(sm != null) { songName_Text.text = bmm.parsedTitle; thisSong_backPic.sprite = bmm.assignedSongData.fullscreen_songPicture; finalScore_Text.text = (sm.allSum_pmScore + sm.allSum_idolScore).ToString(); pmScoreSum_Text.text = sm.allSum_pmScore.ToString(); idolScoreSum_Text.text = sm.allSum_idolScore.ToString(); thisLevel_currentPercentage_Text.text = ((float)(sm.allSum_pmScore + sm.allSum_idolScore) / _1000000 * 100).ToString("F2") + "%"; thisLevel_progressBar_Image.fillAmount = (float)(sm.allSum_pmScore + sm.allSum_idolScore) / _1000000; int noteCountSum = sm.countPerfect + sm.countGreat + sm.countGood + sm.countMiss; perfectHitCount_Text.text = sm.countPerfect.ToString(); greatHitCount_Text.text = sm.countGreat.ToString(); goodHitCount_Text.text = sm.countGood.ToString(); missHitCount_Text.text = sm.countMiss.ToString(); perfect_barFill_Image.fillAmount = (float)sm.countPerfect / noteCountSum; great_barFill_Image.fillAmount = (float)sm.countGreat / noteCountSum; good_barFill_Image.fillAmount = (float)sm.countGood / noteCountSum; miss_barFill_Image.fillAmount = (float)sm.countMiss / noteCountSum; gameNote_rate.text = "宸插畬鎴? " + noteCountSum.ToString() + "/" + bmm.parsedNoteAmount.ToString(); perfectHitPercent_Text.text = ((float)sm.countPerfect / noteCountSum * 100).ToString("F1") + "%"; greatHitPercent_Text.text = ((float)sm.countGreat / noteCountSum * 100).ToString("F1") + "%"; goodHitPercent_Text.text = ((float)sm.countGood / noteCountSum * 100).ToString("F1") + "%"; missHitPercent_Text.text = ((float)sm.countMiss / noteCountSum * 100).ToString("F1") + "%"; accuracy_Text.text = ((float)(sm.countPerfect * perfect_weight + sm.countGreat * great_weight + sm.countGood * good_weight + sm.countMiss * miss_weight) / noteCountSum * 100).ToString("F3") + "%"; // Timing Statistics if (earlyHitCount_Text != null) earlyHitCount_Text.text = sm.countEarly.ToString(); if (lateHitCount_Text != null) lateHitCount_Text.text = sm.countLate.ToString(); if (avgOffset_Text != null) { float avg = sm.offsetCount > 0 ? sm.totalOffsetMs / sm.offsetCount : 0f; avgOffset_Text.text = avg.ToString("F2") + " ms"; } } else Debug.LogError("score manager is null"); // --- Achievement System: Load and display achievements --- if (InGamePerformanceManager.Instance != null) { // Calculate total cure, damage and mana from teamUIController float totalCure = 0; float totalDamage = 0; float totalMana = 0; if (teamUIController.Instance != null) { if (teamUIController.Instance.totalHealProvided != null) { foreach (float heal in teamUIController.Instance.totalHealProvided) { totalCure += heal; } } if (teamUIController.Instance.totalDamageDealt != null) { foreach (float dmg in teamUIController.Instance.totalDamageDealt) { totalDamage += dmg; } } if (teamUIController.Instance.totalManaRestored != null) { foreach (float mana in teamUIController.Instance.totalManaRestored) { totalMana += mana; } } Debug.Log($"[SettlementController] Stats Aggregated - Cure: {totalCure}, Damage: {totalDamage}, Mana: {totalMana}"); } // Update achievement categories InGamePerformanceManager.Instance.UpdateTotalCure(totalCure); InGamePerformanceManager.Instance.UpdateTotalDamage(totalDamage); InGamePerformanceManager.Instance.UpdateTotalManaRestored(totalMana); if (sm != null) InGamePerformanceManager.Instance.UpdateTotalScore(sm.allSum_pmScore + sm.allSum_idolScore); // Load and display achievement prefabs InGamePerformanceManager.Instance.LoadArchievePrefab(); } // Persist run results into the SongData SO for this difficulty // (update personal / idol / total records and per-entry progress if improved) getThisSong_info(); // ensure thisSong_so set if (thisSong_so != null && bmm != null) { int diff = bmm.assignedDifficulty; if (diff >= 0) { int pm = sm != null ? sm.allSum_pmScore : 0; int idol = sm != null ? sm.allSum_idolScore : 0; thisSong_so.ApplySettlementResult(diff, pm, idol, maxScore_sum); } } // Populate settlement team cards if a loader is assigned if (settlementTeamLoader == null) { // try to auto-locate loader in scene if not assigned in inspector settlementTeamLoader = FindAnyObjectByType(); } if (settlementTeamLoader != null) { // ensure loader has key references if (settlementTeamLoader.sm == null) settlementTeamLoader.sm = sm ?? ScoreManager.Instance; if (settlementTeamLoader.tuic == null) settlementTeamLoader.tuic = teamUIController.Instance; if (settlementTeamLoader.gm == null) settlementTeamLoader.gm = gm ?? FindAnyObjectByType(); try { settlementTeamLoader.PopulateSettlementCards(); } catch (System.Exception ex) { Debug.LogWarning("Failed to PopulateSettlementCards: " + ex); } } // Documentation text normalized. // Documentation text normalized. SetMvpHeroImageFromTopScorer(); StartMusicTransition(); // Documentation text normalized. StartCanvasFadeIn(); } /// /// Documentation text normalized. /// private void InitializeSettlementCanvas() { if (settlementCanvasGroup != null) { settlementCanvasGroup.alpha = 0f; settlementCanvasGroup.interactable = false; settlementCanvasGroup.blocksRaycasts = false; Debug.Log("[SettlementController] Settlement canvas initialized to transparent state"); } else { Debug.LogWarning("[SettlementController] settlementCanvasGroup is not assigned, cannot initialize"); } } // Documentation text normalized. private static AllyHero_SO[] _cachedAllyHeroSOs; /// /// Documentation text normalized. /// private void PrepareSettlementMusic() { if (thisSong_so == null) { Debug.LogWarning("[SettlementController] thisSong_so is null, cannot prepare settlement music"); return; } if (settlementAudioSource == null) { Debug.LogWarning("[SettlementController] settlementAudioSource is not assigned, cannot prepare settlement music"); return; } dlcData[] allDlcs = Resources.LoadAll(""); dlcData foundDlc = null; for (int i = 0; i < allDlcs.Length; i++) { dlcData dlc = allDlcs[i]; if (dlc == null || dlc.songList == null) continue; if (!dlc.songList.Contains(thisSong_so)) continue; foundDlc = dlc; break; } if (foundDlc == null) { Debug.LogWarning($"[SettlementController] No DLC found containing song '{thisSong_so.songName}'"); return; } if (foundDlc.settlementMusic == null) { Debug.LogWarning($"[SettlementController] DLC '{foundDlc.dlcName}' does not have settlement music assigned"); return; } bool clipChanged = settlementAudioSource.clip != foundDlc.settlementMusic; if (clipChanged) { settlementAudioSource.Stop(); settlementAudioSource.clip = foundDlc.settlementMusic; settlementAudioSource.time = 0f; } settlementAudioSource.loop = true; settlementAudioSource.playOnAwake = false; settlementAudioSource.volume = 1f; settlementAudioSource.mute = false; Debug.Log($"[SettlementController] Prepared settlement music from DLC '{foundDlc.dlcName}': {foundDlc.settlementMusic.name}"); } /// /// Documentation text normalized. /// private void StartMusicTransition() { if (musicTransitionCoroutine != null) { StopCoroutine(musicTransitionCoroutine); } musicTransitionCoroutine = StartCoroutine(MusicTransitionRoutine()); } /// /// Documentation text normalized. /// Documentation text normalized. /// Documentation text normalized. /// private IEnumerator MusicTransitionRoutine() { float elapsed = 0f; float transitionDuration = Mathf.Max(0.01f, lowpassFadeDuration); float startVolume = (oriGameMusicSource != null) ? oriGameMusicSource.volume : 1f; bool settlementMusicStarted = false; float settlementStartVolume = 0f; // Start settlement music immediately to avoid delayed playback at settlement start. if (settlementAudioSource != null && settlementAudioSource.clip != null) { try { settlementAudioSource.mute = false; if (!settlementAudioSource.isPlaying) { settlementAudioSource.volume = 0f; settlementAudioSource.Play(); settlementStartVolume = 0f; } else { settlementStartVolume = Mathf.Clamp01(settlementAudioSource.volume); } settlementMusicStarted = true; Debug.Log($"[SettlementController] Settlement music active: {settlementAudioSource.clip.name}"); } catch (System.Exception ex) { Debug.LogWarning($"[SettlementController] Failed to start settlement music immediately: {ex.Message}"); } } if (oriGameMusicSource != null) { Debug.Log($"[SettlementController] Starting game-music fade out over {transitionDuration}s"); while (elapsed < transitionDuration) { elapsed += Time.unscaledDeltaTime; float t = Mathf.Clamp01(elapsed / transitionDuration); oriGameMusicSource.volume = Mathf.Lerp(startVolume, 0f, t); if (settlementMusicStarted && settlementAudioSource != null) settlementAudioSource.volume = Mathf.Lerp(settlementStartVolume, 1f, t); yield return null; } oriGameMusicSource.volume = 0f; oriGameMusicSource.Pause(); Debug.Log("[SettlementController] Game music fade complete, game music paused."); } else { Debug.LogWarning("[SettlementController] oriGameMusicSource not assigned, skipping game music fade"); if (settlementMusicStarted && settlementAudioSource != null) settlementAudioSource.volume = 1f; } if (!settlementMusicStarted && settlementAudioSource != null && settlementAudioSource.clip != null) { try { settlementAudioSource.mute = false; settlementAudioSource.volume = 1f; settlementAudioSource.Play(); Debug.Log($"[SettlementController] Settlement music started: {settlementAudioSource.clip.name}"); } catch (System.Exception ex) { Debug.LogWarning($"[SettlementController] Failed to start settlement music: {ex.Message}"); } } musicTransitionCoroutine = null; } /// /// Documentation text normalized. /// private void StartCanvasFadeIn() { if (canvasFadeCoroutine != null) { StopCoroutine(canvasFadeCoroutine); } canvasFadeCoroutine = StartCoroutine(CanvasFadeInRoutine()); } /// /// Documentation text normalized. /// private IEnumerator CanvasFadeInRoutine() { if (settlementCanvasGroup == null) { Debug.LogWarning("[SettlementController] settlementCanvasGroup is not assigned, cannot fade in"); yield break; } float fadeDuration = 0.25f; float elapsed = 0f; float startAlpha = settlementCanvasGroup.alpha; float targetAlpha = 1f; Debug.Log($"[SettlementController] Starting CanvasGroup fade-in from {startAlpha} to {targetAlpha} over {fadeDuration}s"); while (elapsed < fadeDuration) { elapsed += Time.unscaledDeltaTime; float t = Mathf.Clamp01(elapsed / fadeDuration); settlementCanvasGroup.alpha = Mathf.Lerp(startAlpha, targetAlpha, t); yield return null; } // Documentation text normalized. settlementCanvasGroup.alpha = targetAlpha; settlementCanvasGroup.interactable = true; settlementCanvasGroup.blocksRaycasts = true; Debug.Log("[SettlementController] CanvasGroup fade-in complete"); canvasFadeCoroutine = null; } /// /// Documentation text normalized. /// private void OnReplayButtonClicked() { Debug.Log("[SettlementController] Replay button clicked - restarting current game"); // Documentation text normalized. if (bmm == null) { Debug.LogError("[SettlementController] BeatmapManager is null, cannot replay"); return; } if (bmm.assignedSongData == null) { Debug.LogError("[SettlementController] AssignedSongData is null, cannot replay"); return; } // Documentation text normalized. SongData songToReplay = bmm.assignedSongData; int difficultyToReplay = bmm.assignedDifficulty; if (difficultyToReplay < 0) { Debug.LogError("[SettlementController] AssignedDifficulty is invalid, cannot replay"); return; } Debug.Log($"[SettlementController] Reloading song: {songToReplay.songName}, difficulty: {difficultyToReplay}"); // Documentation text normalized. if (settlementAudioSource != null && settlementAudioSource.isPlaying) { settlementAudioSource.Stop(); } // Documentation text normalized. BeatmapManager.SetPendingSong(songToReplay, difficultyToReplay); // Documentation text normalized. StartCoroutine(LoadSceneAsync(SceneManager.GetActiveScene().name)); } private IEnumerator LoadSceneAsync(string sceneName) { AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName); while (!asyncLoad.isDone) { yield return null; } } /// /// Documentation text normalized. /// private void OnExitButtonClicked() { Debug.Log("[SettlementController] Exit button clicked - returning to song selection"); // Documentation text normalized. if (settlementAudioSource != null && settlementAudioSource.isPlaying) { settlementAudioSource.Stop(); } // Documentation text normalized. BeatmapManager.pendingSongData = null; BeatmapManager.pendingDifficulty = -1; // Ensure time scale restored try { Time.timeScale = 1f; } catch {} // If GameManager and its blackMaskImage available, start coroutine on gm to fade to black then load if (gm != null && gm.blackMaskImage != null) { gm.StartCoroutine(FadeToBlackAndLoadOnGM("selectYourSongFirst", 0.25f)); } else { StartCoroutine(LoadSceneAsync("selectYourSongFirst")); } } // Coroutine that will be started on the GameManager instance so that the gm's MonoBehaviour runs it private IEnumerator FadeToBlackAndLoadOnGM(string sceneName, float duration) { if (gm == null || gm.blackMaskImage == null) { AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName); while (!asyncLoad.isDone) yield return null; yield break; } var blackMask = gm.blackMaskImage; // ensure image active if (!blackMask.gameObject.activeSelf) blackMask.gameObject.SetActive(true); float startA = blackMask.color.a; float elapsed = 0f; // enable raycast target while fading to block input try { blackMask.raycastTarget = true; } catch {} while (elapsed < duration) { elapsed += Time.unscaledDeltaTime; float frac = Mathf.Clamp01(elapsed / Mathf.Max(0.0001f, duration)); Color c = blackMask.color; c.a = Mathf.Lerp(startA, 1f, frac); blackMask.color = c; yield return null; } // ensure fully opaque Color fc = blackMask.color; fc.a = 1f; blackMask.color = fc; // load target scene AsyncOperation asyncOp = SceneManager.LoadSceneAsync(sceneName); while (!asyncOp.isDone) { yield return null; } } /// /// Documentation text normalized. /// Documentation text normalized. /// private void SetMvpHeroImageFromTopScorer() { if (mvp_hero_hd_image == null) { Debug.LogWarning("[SettlementController] mvp_hero_hd_image is not assigned"); return; } int topIndex = -1; int[] scores = new int[5]; if (sm != null) { scores[0] = sm.red_pmScore_sum; scores[1] = sm.green_pmScore_sum; scores[2] = sm.yellow_pmScore_sum; scores[3] = sm.purple_pmScore_sum; scores[4] = sm.blue_pmScore_sum; } else { // fallback: read AllyCombatant.currentScore from scene for (int i = 0; i < 5; i++) { var go = GameObject.Find($"ally_0{ i+1 }"); if (go != null) { var ac = go.GetComponent(); scores[i] = ac != null ? ac.currentScore : 0; } else scores[i] = 0; } } int max = -1; for (int i = 0; i < scores.Length; i++) { if (scores[i] > max) { max = scores[i]; topIndex = i; } } if (topIndex < 0 || max <= 0) { Debug.Log("[SettlementController] No top scorer found or scores are all zero"); // hide image mvp_hero_hd_image.sprite = null; mvp_hero_hd_image.color = new Color(mvp_hero_hd_image.color.r, mvp_hero_hd_image.color.g, mvp_hero_hd_image.color.b, 0f); return; } // Try to resolve AllyHero_SO for the winning slot AllyHero_SO heroSO = null; // try SkillBuilder cache if (SkillBuilder.Instance != null) { try { heroSO = SkillBuilder.Instance.GetAllyHeroSOBySlot(topIndex); } catch { heroSO = null; } } // fallback: use teamUIController slot ids to find hero id -> load SO if (heroSO == null && teamUIController.Instance != null && teamUIController.Instance.allySlotIds != null) { int allyId = -1; if (topIndex >= 0 && topIndex < teamUIController.Instance.allySlotIds.Count) allyId = teamUIController.Instance.allySlotIds[topIndex]; if (allyId > 0) { if (_cachedAllyHeroSOs == null || _cachedAllyHeroSOs.Length == 0) { _cachedAllyHeroSOs = Resources.LoadAll(""); } foreach (var a in _cachedAllyHeroSOs) { if (a != null && a.ally_heroID == allyId) { heroSO = a; break; } } } } // final fallback: try direct Resources lookup by scanning all and picking first non-null for slot if (heroSO == null) { if (_cachedAllyHeroSOs == null || _cachedAllyHeroSOs.Length == 0) { _cachedAllyHeroSOs = Resources.LoadAll(""); } if (_cachedAllyHeroSOs != null && _cachedAllyHeroSOs.Length > 0) { // attempt to match by name or just pick first heroSO = _cachedAllyHeroSOs[0] as AllyHero_SO; } } if (heroSO != null && heroSO.ally_hero_HD_image != null) { mvp_hero_hd_image.sprite = heroSO.ally_hero_HD_image; mvp_hero_hd_image.color = new Color(mvp_hero_hd_image.color.r, mvp_hero_hd_image.color.g, mvp_hero_hd_image.color.b, 1f); Debug.Log($"[SettlementController] MVP assigned from slot {topIndex+1} with score {max}: {heroSO.ally_heroName}"); } else { mvp_hero_hd_image.sprite = null; mvp_hero_hd_image.color = new Color(mvp_hero_hd_image.color.r, mvp_hero_hd_image.color.g, mvp_hero_hd_image.color.b, 0f); Debug.LogWarning("[SettlementController] MVP SO or HD image not found"); } } }