using Ichni;
using Ichni.Menu;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace Ichni.Story.UI
{
///
/// 剧情树中的歌曲 Block 视图。
/// 它只保存 这一个稳定歌曲标识;歌曲名称、作曲者与插画
/// 均从当前章节的 读取,避免 StoryData 与选曲资料出现两份可漂移的数据。
/// 点击后切换到选曲并自动选中对应歌曲的流程将在后续阶段接入。
///
public class SongBlockView : StoryBlockView
{
[Header("Song Block")]
[Tooltip("Mask 下的 16:9 插画 Image。Sprite 由当前章节的 SongItemData.illustration 提供。")]
public Image illustrationImage;
[Tooltip("illustrationImage 对应的 RectTransform。仅修改其 anchoredPosition.y 以完成纵向裁切。")]
public RectTransform illustrationRect;
public TMP_Text songNameText;
public TMP_Text composerText;
[Tooltip("歌曲或剧情 Block 不可进入时显示的遮罩节点。")]
public GameObject lockOverlay;
private ChapterSelectionUnit _chapter;
private SongItemData _song;
private bool _canEnterSong;
public override void Initialize(StoryBlockDefinition def, Vector2 position, StoryBlockState blockState)
{
base.Initialize(def, position, blockState);
if (TryResolveSong(out _chapter, out _song))
{
illustrationImage.sprite = _song.illustration;
songNameText.text = _song.displaySongName;
composerText.text = _song.composer;
}
else
{
// 配置错误时保留稳定 ID 以便定位问题,同时锁定 Block,避免从剧情页绕过内容授权。
illustrationImage.sprite = null;
songNameText.text = def.songName;
composerText.text = string.Empty;
}
// 数据存储的是最终 local Y,而非叠加在 Prefab 默认位置上的偏移量。
// Clamp 仍保留作为运行时防线:即使旧资产或外部工具写入越界值,也不会在 Mask 边缘露出空白。
Vector2 illustrationPosition = illustrationRect.anchoredPosition;
illustrationPosition.y = Mathf.Clamp(def.illustrationLocalY, -74.25f, 74.25f);
illustrationRect.anchoredPosition = illustrationPosition;
ApplyState(blockState);
}
///
/// StoryBlock 的剧情状态和歌曲的内容解锁状态必须同时满足,才能真正点击进入歌曲。
/// 复用选曲页面的统一授权判断,防止 Story SongBlock 成为绕过入口。
///
public override void ApplyState(StoryBlockState newState)
{
base.ApplyState(newState);
bool storyAllowsInteraction = state != StoryBlockState.Locked && state != StoryBlockState.Forbidden;
_canEnterSong = _chapter != null && _song != null &&
GameSaveManager.instance != null &&
GameSaveManager.instance.UnlockSaveModule != null &&
GameSaveManager.instance.UnlockSaveModule.CanEnterSong(_chapter, _song);
bool canInteract = storyAllowsInteraction && _canEnterSong;
button.interactable = canInteract;
lockOverlay.SetActive(!canInteract);
}
///
/// 根据正在构建的剧情章节索引定位歌曲,而不是盲目信任 currentChapter。
/// 这样即使未来从其它页面直接打开 StoryData,也不会把同名歌曲解析到上一次残留的章节中。
///
private bool TryResolveSong(out ChapterSelectionUnit chapter, out SongItemData song)
{
chapter = null;
song = null;
string chapterIndex = StoryManager.instance?.treeController?.ActiveChapterIndex;
ChapterSelectionManager chapterManager = ChapterSelectionManager.instance;
if (string.IsNullOrEmpty(chapterIndex) || chapterManager == null)
{
Debug.LogWarning($"[SongBlockView] 无法解析歌曲 Block '{blockId}':当前章节或 ChapterSelectionManager 不可用。");
return false;
}
foreach (ChapterSelectionUnit candidate in chapterManager.chapters)
{
if (candidate != null && candidate.chapterIndex == chapterIndex)
{
chapter = candidate;
break;
}
}
if (chapter == null)
{
Debug.LogWarning($"[SongBlockView] 无法解析歌曲 Block '{blockId}':找不到章节 '{chapterIndex}' 的 ChapterSelectionUnit。");
return false;
}
foreach (SongItemData candidate in chapter.songs)
{
if (candidate != null && candidate.songName == definition.songName)
{
song = candidate;
return true;
}
}
Debug.LogWarning($"[SongBlockView] 无法解析歌曲 Block '{blockId}':章节 '{chapterIndex}' 中不存在歌曲 ID '{definition.songName}'。");
return false;
}
protected override void OnClicked()
{
if (!_canEnterSong)
{
return;
}
// 占位:后续接入曲目选择流程
Debug.Log($"[SongBlockView] 点击音乐块 '{blockId}' (song='{definition.songName}')。占位实现,曲目选择将在后续阶段接入。");
}
}
}