using System.Collections.Generic; using System; using UnityEngine; namespace Ichni.Menu { public class DifficultySelectionContainer : MonoBehaviour { public List buttons; public DifficultySelectionButton selectedButton; /// /// 在当前歌曲中解析一个可实际游玩的难度。 /// preferredListIndex 是 ,即难度在当前歌曲 /// List 中的位置;它不能替代 ,后者是游戏记录的稳定 ID。 /// 规则:只考虑已配置 Button 且 isAvailable 为 true 的难度,优先选择与目标位置距离最小者; /// 距离相同则选择位置较低者。因此目标难度不存在时,玩家会落到最接近、且偏低的可用难度。 /// private bool TryResolveAvailableDifficultyIndex( IReadOnlyList difficulties, int preferredListIndex, out int resolvedListIndex) { resolvedListIndex = -1; if (difficulties == null || buttons == null) { return false; } int candidateCount = Mathf.Min(difficulties.Count, buttons.Count); long bestDistance = long.MaxValue; for (int index = 0; index < candidateCount; index++) { DifficultyData difficulty = difficulties[index]; if (difficulty == null || !difficulty.isAvailable || buttons[index] == null) { continue; } long distance = Math.Abs((long)index - preferredListIndex); if (distance < bestDistance || (distance == bestDistance && index < resolvedListIndex)) { bestDistance = distance; resolvedListIndex = index; } } return resolvedListIndex >= 0; } /// /// 清除当前的难度选择,并同时关闭标准的进入游戏按钮。 /// SongSelectionTab 的快速进入入口也会单独检查 selectedDifficulty,避免绕过此 UI 状态。 /// private void ClearDifficultySelection() { selectedButton?.Deselect(); selectedButton = null; SongSelectionUIPage songSelectionUIPage = MenuManager.instance.songSelectionUIPage; songSelectionUIPage.selectedDifficulty = null; songSelectionUIPage.playSongUI.SetEnterGameAvailable(false); } /// /// 根据当前歌曲刷新难度按钮,并将章节内存缓存解析为一个实际可玩的难度。 /// 该方法用于首次进入、从游戏返回以及拖动切歌,所以所有自动难度回退都必须经过此处。 /// public void SetUp(List difficulties) { int difficultyCount = difficulties?.Count ?? 0; int buttonCount = buttons?.Count ?? 0; ClearDifficultySelection(); if (difficultyCount > buttonCount) { Debug.LogError($"Song '{MenuManager.instance.songSelectionUIPage.selectedSong?.songName}' has {difficultyCount} difficulties but only {buttonCount} difficulty buttons. " + "Only difficulties with configured buttons can be selected."); } for (int i = 0; i < buttonCount; i++) { DifficultySelectionButton button = buttons[i]; if (button == null) { Debug.LogError($"Difficulty button at index {i} is missing."); continue; } bool hasDifficulty = i < difficultyCount && difficulties[i] != null; button.gameObject.SetActive(hasDifficulty); if (hasDifficulty) { // SetUp 会进一步按 isAvailable 隐藏不可用难度。 button.SetUp(difficulties[i]); } } if (difficultyCount == 0) { Debug.LogWarning($"Song '{MenuManager.instance.songSelectionUIPage.selectedSong?.songName}' has no configured difficulties."); return; } if (!MenuInformationRecorder.instance.TryGetRecordOfThisChapter(out SongSelectionRecord songSelectionRecord)) { Debug.LogWarning("Cannot select a difficulty because the current chapter has no valid song selection record."); return; } if (!TryResolveAvailableDifficultyIndex(difficulties, songSelectionRecord.difficultyListIndex, out int resolvedListIndex)) { Debug.LogWarning($"Song '{MenuManager.instance.songSelectionUIPage.selectedSong?.songName}' has no available difficulty to select."); return; } // Select 会把实际选中的难度写回章节缓存;之后继续切歌时,以这次真实选择作为新的偏好。 buttons[resolvedListIndex].Select(); // 难度可用不等于内容已解锁。保持按钮状态与统一授权服务一致, // 而 PlaySongUI / 快速点击仍会在真正进入前再次检查,形成双层防御。 UnlockSaveModule unlockSaveModule = GameSaveManager.instance?.UnlockSaveModule; bool canEnterSong = unlockSaveModule != null && unlockSaveModule.CanEnterSong( ChapterSelectionManager.instance?.currentChapter, MenuManager.instance.songSelectionUIPage.selectedSong); MenuManager.instance.songSelectionUIPage.playSongUI.SetEnterGameAvailable(canEnterSong); } } }