2025-07-21 05:42:20 -04:00
using System.Collections.Generic ;
2026-07-18 16:51:18 -04:00
using System ;
2025-07-21 05:42:20 -04:00
using UnityEngine ;
2025-08-11 14:04:06 -04:00
namespace Ichni.Menu
2025-07-21 05:42:20 -04:00
{
public class DifficultySelectionContainer : MonoBehaviour
{
public List < DifficultySelectionButton > buttons ;
public DifficultySelectionButton selectedButton ;
2026-07-18 11:39:55 +08:00
2026-07-18 16:51:18 -04:00
/// <summary>
/// 在当前歌曲中解析一个可实际游玩的难度。
/// <para>preferredListIndex 是 <see cref="SongSelectionRecord.difficultyListIndex"/>,即难度在当前歌曲
/// List 中的位置;它不能替代 <see cref="DifficultyData.saveDifficultyId"/>,后者是游戏记录的稳定 ID。</para>
/// <para>规则:只考虑已配置 Button 且 <c>isAvailable</c> 为 true 的难度,优先选择与目标位置距离最小者;
/// 距离相同则选择位置较低者。因此目标难度不存在时,玩家会落到最接近、且偏低的可用难度。</para>
/// </summary>
private bool TryResolveAvailableDifficultyIndex (
IReadOnlyList < DifficultyData > 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 ;
}
/// <summary>
/// 清除当前的难度选择,并同时关闭标准的进入游戏按钮。
/// SongSelectionTab 的快速进入入口也会单独检查 <c>selectedDifficulty</c>,避免绕过此 UI 状态。
/// </summary>
private void ClearDifficultySelection ( )
{
selectedButton ? . Deselect ( ) ;
selectedButton = null ;
SongSelectionUIPage songSelectionUIPage = MenuManager . instance . songSelectionUIPage ;
songSelectionUIPage . selectedDifficulty = null ;
songSelectionUIPage . playSongUI . SetEnterGameAvailable ( false ) ;
}
/// <summary>
/// 根据当前歌曲刷新难度按钮,并将章节内存缓存解析为一个实际可玩的难度。
/// 该方法用于首次进入、从游戏返回以及拖动切歌,所以所有自动难度回退都必须经过此处。
/// </summary>
2025-07-21 05:42:20 -04:00
public void SetUp ( List < DifficultyData > difficulties )
{
2026-07-18 16:51:18 -04:00
int difficultyCount = difficulties ? . Count ? ? 0 ;
int buttonCount = buttons ? . Count ? ? 0 ;
2025-07-21 05:42:20 -04:00
2026-07-18 16:51:18 -04:00
ClearDifficultySelection ( ) ;
if ( difficultyCount > buttonCount )
2025-07-21 05:42:20 -04:00
{
2026-07-18 16:51:18 -04:00
Debug . LogError ( $"Song '{MenuManager.instance.songSelectionUIPage.selectedSong?.songName}' has {difficultyCount} difficulties but only {buttonCount} difficulty buttons. " +
"Only difficulties with configured buttons can be selected." ) ;
2025-07-21 05:42:20 -04:00
}
2026-07-18 16:51:18 -04:00
for ( int i = 0 ; i < buttonCount ; i + + )
2025-07-21 05:42:20 -04:00
{
2026-07-18 16:51:18 -04:00
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 ] ) ;
}
2025-07-21 05:42:20 -04:00
}
2026-07-18 16:51:18 -04:00
if ( difficultyCount = = 0 )
{
Debug . LogWarning ( $"Song '{MenuManager.instance.songSelectionUIPage.selectedSong?.songName}' has no configured difficulties." ) ;
return ;
}
2026-07-18 11:39:55 +08:00
2026-07-18 16:51:18 -04:00
if ( ! MenuInformationRecorder . instance . TryGetRecordOfThisChapter ( out SongSelectionRecord songSelectionRecord ) )
2025-07-21 05:42:20 -04:00
{
2026-07-18 16:51:18 -04:00
Debug . LogWarning ( "Cannot select a difficulty because the current chapter has no valid song selection record." ) ;
return ;
2025-07-21 05:42:20 -04:00
}
2026-07-18 11:39:55 +08:00
2026-07-18 16:51:18 -04:00
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 ) ;
2025-07-21 05:42:20 -04:00
}
}
2026-07-18 16:51:18 -04:00
}