using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class JudgeManager : MonoBehaviour { [Header("settlement go")] public GameObject settlement_go; public settlementController sc; public NoteSpawner ns; public static JudgeManager Instance { get; private set; } // total/remaining notes tracking for end-of-song detection private int totalNotes = 0; private int remainingNotes = 0; // ===== ԭ������ ===== private Dictionary> judgeQueues = new Dictionary>(); private Dictionary startJudgedNotes = new Dictionary(); private Dictionary releasedNotes = new Dictionary(); private Dictionary noteEndTimes = new Dictionary(); private Dictionary middlePassedCounts = new Dictionary(); // ===== ������������ȫ�ֻ���״̬ ===== private class HoldJudgeState { public bool startResolved; // Start �Ƿ��Ѿ��ж������ɹ� or ʧ�ܣ� public bool endResolved; // End �Ƿ��Ѿ��ж��� public bool skillTriggered; // �����Ƿ��Ѵ��� } private Dictionary holdStates = new Dictionary(); private HoldJudgeState GetHoldState(string noteID) { if (!holdStates.ContainsKey(noteID)) holdStates[noteID] = new HoldJudgeState(); return holdStates[noteID]; } // Public event and hook invoked when the entire beatmap's last note has been judged. // The method is intentionally empty so you can override or call it via the singleton. public event Action AllNotesJudged; /// /// Public empty hook called when all notes have been judged. Implement your end-of-song logic here /// or subscribe to the AllNotesJudged event. This method is intentionally left empty. /// public void OnAllNotesJudged() { if (settlement_go == null) { Debug.LogError("结算页面不存在!"); return; } // Activate settlement UI so components run their lifecycle, then call update next frame try { settlement_go.SetActive(true); } catch (Exception ex) { Debug.LogWarning($"[JudgeManager] Failed to activate settlement_go: {ex}"); } // Try to resolve settlementController reference if missing if (sc == null && settlement_go != null) { sc = settlement_go.GetComponent() ?? settlement_go.GetComponentInChildren(true); } // Invoke the settlement update on next frame to ensure UI initialization finished StartCoroutine(InvokeSettlementUpdateNextFrame()); } private IEnumerator InvokeSettlementUpdateNextFrame() { yield return null; // wait one frame to allow Awake/Start/OnEnable if (sc != null) { try { sc.startSettlement_uiUpdate(); } catch (Exception ex) { Debug.LogWarning($"[JudgeManager] Exception calling startSettlement_uiUpdate: {ex}"); } } else { Debug.LogWarning("[JudgeManager] settlementController (sc) not found on settlement_go; cannot call startSettlement_uiUpdate()."); } } /// /// Helper to invoke the AllNotesJudged event and call the public hook. /// Call this when you detect the last note was judged. /// public void TriggerAllNotesJudged() { try { AllNotesJudged?.Invoke(); } catch (Exception ex) { Debug.LogWarning($"[JudgeManager] Exception while invoking AllNotesJudged event: {ex}"); } try { OnAllNotesJudged(); } catch (Exception ex) { Debug.LogWarning($"[JudgeManager] Exception in OnAllNotesJudged hook: {ex}"); } } private void Awake() { if (Instance == null) Instance = this; else Destroy(gameObject); if (settlement_go != null) settlement_go.SetActive(false); } /// /// Set the total number of notes in the current beatmap. Remaining notes will be initialized /// to this value. Call before playback/spawning starts. /// public void SetTotalNotes(int total) { totalNotes = Mathf.Max(0, total); remainingNotes = totalNotes; Debug.Log($"[JudgeManager] SetTotalNotes: total={totalNotes}"); } /// /// Notify that one note has been finally judged (hit or miss). When remaining reaches zero, /// TriggerAllNotesJudged will be invoked. /// public void NotifyNoteJudged() { if (remainingNotes <= 0) return; remainingNotes = Mathf.Max(0, remainingNotes - 1); Debug.Log($"[JudgeManager] NotifyNoteJudged: remaining={remainingNotes}"); if (remainingNotes == 0) { Debug.Log("[JudgeManager] All notes judged -> Triggering AllNotesJudged"); TriggerAllNotesJudged(); } } // ===== �������ж�����խ�� ===== public bool TryResolveStart(string noteID) { var s = GetHoldState(noteID); if (s.startResolved) { Debug.LogWarning($"[JudgeManager] TryResolveStart: already resolved start for {noteID}"); return false; } s.startResolved = true; Debug.Log($"[JudgeManager] TryResolveStart: start resolved for {noteID} at time={Time.time:F3}"); return true; } public bool TryResolveEnd(string noteID) { var s = GetHoldState(noteID); if (s.endResolved) { Debug.LogWarning($"[JudgeManager] TryResolveEnd: already resolved end for {noteID}"); return false; } s.endResolved = true; Debug.Log($"[JudgeManager] TryResolveEnd: end resolved for {noteID} at time={Time.time:F3}"); return true; } public bool TryTriggerSkill(string noteID) { var s = GetHoldState(noteID); if (s.skillTriggered) { Debug.LogWarning($"[JudgeManager] TryTriggerSkill: already triggered for {noteID}"); return false; } s.skillTriggered = true; Debug.Log($"[JudgeManager] TryTriggerSkill: skill triggered for {noteID} at time={Time.time:F3}"); return true; } public void ClearHoldState(string noteID) { if (holdStates.ContainsKey(noteID)) { holdStates.Remove(noteID); Debug.Log($"[JudgeManager] ClearHoldState: cleared state for {noteID}"); } } // ===== ԭ�нӿڣ����ֲ��䣩 ===== public void RegisterScheduledEndTime(string noteID, float endTime) { noteEndTimes[noteID] = endTime; Debug.Log($"[JudgeManager] RegisterScheduledEndTime: {noteID} -> {endTime:F3}"); } public float GetScheduledEndTime(string noteID) { return noteEndTimes.ContainsKey(noteID) ? noteEndTimes[noteID] : 0f; } public void RegisterStartJudged(string noteID, bool state) { startJudgedNotes[noteID] = state; Debug.Log($"[JudgeManager] RegisterStartJudged: {noteID} = {state} at time={Time.time:F3}"); } public bool IsStartJudged(string noteID) { return startJudgedNotes.ContainsKey(noteID) && startJudgedNotes[noteID]; } public void RegisterNoteReleased(string noteID, bool state) { releasedNotes[noteID] = state; Debug.Log($"[JudgeManager] RegisterNoteReleased: {noteID} = {state}"); } public bool HasNoteReleased(string noteID) { return releasedNotes.ContainsKey(noteID) && releasedNotes[noteID]; } public void RegisterNote(KeyCode key, Note note) { if (!judgeQueues.ContainsKey(key)) judgeQueues[key] = new Queue(); if (!judgeQueues[key].Contains(note)) judgeQueues[key].Enqueue(note); Debug.Log($"[JudgeManager] RegisterNote: key={key} note={note.name} time={Time.time:F3} queueSize={judgeQueues[key].Count}"); } public void UnregisterNote(KeyCode key, Note note) { if (!judgeQueues.ContainsKey(key)) return; Queue newQueue = new Queue(); while (judgeQueues[key].Count > 0) { Note n = judgeQueues[key].Dequeue(); if (n != note) newQueue.Enqueue(n); else if (!n.IsJudged()) n.JudgeMiss(); } judgeQueues[key] = newQueue; Debug.Log($"[JudgeManager] UnregisterNote: key={key} removed {note.name}, newQueueSize={judgeQueues[key].Count}"); } public void JudgeEarliestNote(KeyCode key) { if (judgeQueues.ContainsKey(key) && judgeQueues[key].Count > 0) { Note note = judgeQueues[key].Dequeue(); if (note != null && !note.IsJudged()) { Debug.Log($"[JudgeManager] JudgeEarliestNote: judging {note.name} for key={key} at time={Time.time:F3}"); note.Judge(); } } } public void RegisterMiddlePassed(string noteID) { if (!middlePassedCounts.ContainsKey(noteID)) middlePassedCounts[noteID] = 0; middlePassedCounts[noteID]++; Debug.Log($"[JudgeManager] Middle passed for {noteID}, total={middlePassedCounts[noteID]}"); } public int GetMiddlePassedCount(string noteID) { return middlePassedCounts.ContainsKey(noteID) ? middlePassedCounts[noteID] : 0; } public void ClearNoteRecord(string noteID) { startJudgedNotes.Remove(noteID); releasedNotes.Remove(noteID); noteEndTimes.Remove(noteID); middlePassedCounts.Remove(noteID); ClearHoldState(noteID); Debug.Log($"[JudgeManager] ClearNoteRecord: cleared records for {noteID}"); } }