Files

360 lines
12 KiB
C#

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;
[Header("Settlement Delay")]
[SerializeField] private float settlementEnterDelaySeconds = 1.5f;
private bool _cachedIsDebugEnabled;
private void UpdateDebugCache()
{
_cachedIsDebugEnabled = (_enableDebugLogs || GameConfig.verboseLogs);
}
[Header("Debug Settings")]
[SerializeField] private bool _enableDebugLogs = false;
public bool EnableDebugLogs => _enableDebugLogs;
public static bool IsDebugEnabled => (Instance != null && Instance._cachedIsDebugEnabled);
[Header("Global Note Settings")]
[Tooltip("Global material applied to hold notes when they are judged.")]
public Material globalJudgedMaterial;
[Tooltip("Whether to enable material switching after a hold note is judged.")]
public bool enableJudgedMaterial = false;
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 bool settlementTriggeredThisChart = false;
// Documentation text normalized.
private Dictionary<KeyCode, Queue<Note>> judgeQueues = new Dictionary<KeyCode, Queue<Note>>();
private Dictionary<string, bool> startJudgedNotes = new Dictionary<string, bool>();
private Dictionary<string, bool> releasedNotes = new Dictionary<string, bool>();
private Dictionary<string, float> noteEndTimes = new Dictionary<string, float>();
private Dictionary<string, int> middlePassedCounts = new Dictionary<string, int>();
// Documentation text normalized.
private class HoldJudgeState
{
public bool startResolved; // Documentation text normalized.
public bool endResolved; // Documentation text normalized.
public bool skillTriggered; // Documentation text normalized.
}
private Dictionary<string, HoldJudgeState> holdStates = new Dictionary<string, HoldJudgeState>();
private HoldJudgeState GetHoldState(string noteID)
{
if (!holdStates.TryGetValue(noteID, out var state))
{
state = new HoldJudgeState();
holdStates[noteID] = state;
}
return state;
}
// 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;
/// <summary>
/// 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.
/// </summary>
public void OnAllNotesJudged()
{
if (settlementTriggeredThisChart)
{
if (IsDebugEnabled) Debug.Log("[JudgeManager] OnAllNotesJudged ignored: settlement already triggered for this chart.");
return;
}
settlementTriggeredThisChart = true;
if (settlement_go == null)
{
if (IsDebugEnabled) Debug.LogError("结算页面不存在!");
return;
}
// Enter settlement after a real-time delay to leave a short idle window after chart end.
StartCoroutine(InvokeSettlementUpdateNextFrame());
}
private IEnumerator InvokeSettlementUpdateNextFrame()
{
// Keep settlement lead-in fixed at 1.5s for consistent pacing.
float delay = 1.5f;
if (delay > 0f)
{
yield return new WaitForSecondsRealtime(delay);
}
// Activate settlement UI so components run their lifecycle.
try
{
settlement_go.SetActive(true);
}
catch (Exception ex)
{
if (IsDebugEnabled) 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<settlementController>() ?? settlement_go.GetComponentInChildren<settlementController>(true);
}
// Wait one frame to allow Awake/Start/OnEnable.
yield return null;
if (sc != null)
{
try
{
sc.startSettlement_uiUpdate();
}
catch (Exception ex)
{
if (IsDebugEnabled) Debug.LogWarning($"[JudgeManager] Exception calling startSettlement_uiUpdate: {ex}");
}
}
else
{
if (IsDebugEnabled) Debug.LogWarning("[JudgeManager] settlementController (sc) not found on settlement_go; cannot call startSettlement_uiUpdate().");
}
}
/// <summary>
/// Helper to invoke the AllNotesJudged event and call the public hook.
/// Call this when you detect the last note was judged.
/// </summary>
public void TriggerAllNotesJudged()
{
try
{
AllNotesJudged?.Invoke();
}
catch (Exception ex)
{
if (IsDebugEnabled) Debug.LogWarning($"[JudgeManager] Exception while invoking AllNotesJudged event: {ex}");
}
try
{
OnAllNotesJudged();
}
catch (Exception ex)
{
if (IsDebugEnabled) Debug.LogWarning($"[JudgeManager] Exception in OnAllNotesJudged hook: {ex}");
}
}
private void Awake()
{
if (Instance == null) Instance = this;
else Destroy(gameObject);
UpdateDebugCache();
if (settlement_go != null) settlement_go.SetActive(false);
}
/// <summary>
/// Set the total number of notes in the current beatmap. Remaining notes will be initialized
/// to this value. Call before playback/spawning starts.
/// </summary>
public void SetTotalNotes(int total)
{
totalNotes = Mathf.Max(0, total);
remainingNotes = totalNotes;
settlementTriggeredThisChart = false;
if (IsDebugEnabled) Debug.Log($"[JudgeManager] SetTotalNotes: total={totalNotes}");
}
/// <summary>
/// Notify that one note has been finally judged (hit or miss). When remaining reaches zero,
/// TriggerAllNotesJudged will be invoked.
/// </summary>
public void NotifyNoteJudged()
{
if (remainingNotes <= 0) return;
remainingNotes = Mathf.Max(0, remainingNotes - 1);
if (IsDebugEnabled) Debug.Log($"[JudgeManager] NotifyNoteJudged: remaining={remainingNotes}");
if (remainingNotes == 0)
{
if (IsDebugEnabled) Debug.Log("[JudgeManager] All notes judged -> Triggering AllNotesJudged");
TriggerAllNotesJudged();
}
}
// Documentation text normalized.
public bool TryResolveStart(string noteID)
{
var s = GetHoldState(noteID);
if (s.startResolved)
{
if (IsDebugEnabled) Debug.LogWarning($"[JudgeManager] TryResolveStart: already resolved start for {noteID}");
return false;
}
s.startResolved = true;
if (IsDebugEnabled) Debug.Log($"[JudgeManager] TryResolveStart: start resolved for {noteID} at time={GameplayClock.NowSongTime:F3}");
return true;
}
public bool TryResolveEnd(string noteID)
{
var s = GetHoldState(noteID);
if (s.endResolved)
{
if (IsDebugEnabled) Debug.LogWarning($"[JudgeManager] TryResolveEnd: already resolved end for {noteID}");
return false;
}
s.endResolved = true;
if (IsDebugEnabled) Debug.Log($"[JudgeManager] TryResolveEnd: end resolved for {noteID} at time={GameplayClock.NowSongTime:F3}");
return true;
}
public bool TryTriggerSkill(string noteID)
{
var s = GetHoldState(noteID);
if (s.skillTriggered)
{
if (IsDebugEnabled) Debug.LogWarning($"[JudgeManager] TryTriggerSkill: already triggered for {noteID}");
return false;
}
s.skillTriggered = true;
if (IsDebugEnabled) Debug.Log($"[JudgeManager] TryTriggerSkill: skill triggered for {noteID} at time={GameplayClock.NowSongTime:F3}");
return true;
}
public void ClearHoldState(string noteID)
{
if (holdStates.ContainsKey(noteID))
{
holdStates.Remove(noteID);
if (IsDebugEnabled) Debug.Log($"[JudgeManager] ClearHoldState: cleared state for {noteID}");
}
}
// Documentation text normalized.
public void RegisterScheduledEndTime(string noteID, float endTime)
{
noteEndTimes[noteID] = endTime;
if (IsDebugEnabled) Debug.Log($"[JudgeManager] RegisterScheduledEndTime: {noteID} -> {endTime:F3}");
}
public float GetScheduledEndTime(string noteID)
{
return noteEndTimes.TryGetValue(noteID, out var endTime) ? endTime : 0f;
}
public void RegisterStartJudged(string noteID, bool state)
{
startJudgedNotes[noteID] = state;
if (IsDebugEnabled) Debug.Log($"[JudgeManager] RegisterStartJudged: {noteID} = {state} at time={GameplayClock.NowSongTime:F3}");
}
public bool IsStartJudged(string noteID)
{
return startJudgedNotes.TryGetValue(noteID, out var state) && state;
}
public void RegisterNoteReleased(string noteID, bool state)
{
releasedNotes[noteID] = state;
if (IsDebugEnabled) Debug.Log($"[JudgeManager] RegisterNoteReleased: {noteID} = {state}");
}
public bool HasNoteReleased(string noteID)
{
return releasedNotes.TryGetValue(noteID, out var state) && state;
}
public void RegisterNote(KeyCode key, Note note)
{
if (!judgeQueues.ContainsKey(key))
judgeQueues[key] = new Queue<Note>();
if (!judgeQueues[key].Contains(note))
judgeQueues[key].Enqueue(note);
if (IsDebugEnabled) Debug.Log($"[JudgeManager] RegisterNote: key={key} note={note.name} time={GameplayClock.NowSongTime:F3} queueSize={judgeQueues[key].Count}");
}
public void UnregisterNote(KeyCode key, Note note)
{
if (!judgeQueues.ContainsKey(key)) return;
Queue<Note> newQueue = new Queue<Note>();
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;
if (IsDebugEnabled) 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())
{
if (IsDebugEnabled) Debug.Log($"[JudgeManager] JudgeEarliestNote: judging {note.name} for key={key} at time={GameplayClock.NowSongTime:F3}");
note.Judge();
}
}
}
public void RegisterMiddlePassed(string noteID)
{
if (middlePassedCounts.TryGetValue(noteID, out int count))
{
middlePassedCounts[noteID] = count + 1;
}
else
{
middlePassedCounts[noteID] = 1;
}
if (IsDebugEnabled) Debug.Log($"[JudgeManager] Middle passed for {noteID}, total={middlePassedCounts[noteID]}");
}
// Event for syncing alpha across segments of a hold note
public event Action<string, float> OnHoldAlphaSync;
public void SyncHoldAlpha(string noteID, float alpha)
{
OnHoldAlphaSync?.Invoke(noteID, alpha);
}
public int GetMiddlePassedCount(string noteID)
{
return middlePassedCounts.TryGetValue(noteID, out var count) ? count : 0;
}
public void ClearNoteRecord(string noteID)
{
startJudgedNotes.Remove(noteID);
releasedNotes.Remove(noteID);
noteEndTimes.Remove(noteID);
middlePassedCounts.Remove(noteID);
ClearHoldState(noteID);
if (IsDebugEnabled) Debug.Log($"[JudgeManager] ClearNoteRecord: cleared records for {noteID}");
}
}