using System; using System.Collections.Generic; using Sirenix.OdinInspector; using UnityEngine; namespace Ichni.Story { /// /// Block 在故事树中的状态:已锁定、当前可交互、已完成。 /// public enum StoryBlockState { Locked, Current, Completed } /// /// Block 的类型,决定点击后触发的行为。 /// public enum StoryBlockType { Text, Song, Tutorial } /// /// 变量条件的比较方式。 /// public enum VariableComparison { Equal, NotEqual, Greater, GreaterOrEqual, Less, LessOrEqual } /// /// 故事树中单个 block 的完整数据定义,由 持有。 /// [InlineProperty] [Serializable] public class StoryBlockDefinition { [FoldoutGroup("$blockId", false)] [LabelText("Block ID")] public string blockId; [FoldoutGroup("$blockId")] [LabelText("Type")] public StoryBlockType blockType; [FoldoutGroup("$blockId")] [LabelText("Grid Column")] [Tooltip("故事树网格中的列:向右为正,(0,0) 位于容器左侧中部。可为负数与小数(用于微调)。")] public float gridColumn; [FoldoutGroup("$blockId")] [LabelText("Grid Row")] [Tooltip("故事树网格中的行:向下为正、向上为负,0 对应垂直中线。可为小数(用于微调)。")] public float gridRow; [FoldoutGroup("$blockId")] [LabelText("Next Block IDs")] public List nextBlockIds = new List(); [FoldoutGroup("$blockId")] [LabelText("Unlock Condition")] public UnlockCondition unlockCondition = new UnlockCondition(); // ── Text Block ────────────────────────────────────────────────────────── [FoldoutGroup("$blockId")] [ShowIf("blockType", StoryBlockType.Text)] [LabelText("Yarn Node Name")] public string yarnNodeName; [FoldoutGroup("$blockId")] [ShowIf("blockType", StoryBlockType.Text)] [LabelText("Title Key (Localization)")] public string titleKey; // ── Song Block ────────────────────────────────────────────────────────── [FoldoutGroup("$blockId")] [ShowIf("blockType", StoryBlockType.Song)] [LabelText("Song Name")] public string songName; // ── Tutorial Block ────────────────────────────────────────────────────── [FoldoutGroup("$blockId")] [ShowIf("blockType", StoryBlockType.Tutorial)] [LabelText("Tutorial Name")] public string tutorialName; /// /// 预览 / 调试用的显示标题:按类型取对应字段,缺省时回退到 blockId。 /// public string GetDisplayTitle() { string title = blockType switch { StoryBlockType.Text => titleKey, StoryBlockType.Song => songName, StoryBlockType.Tutorial => tutorialName, _ => null }; return string.IsNullOrEmpty(title) ? blockId : title; } } /// /// Block 的解锁条件。以一棵可组合的条件树()表达, /// 支持"与 / 或 / 非 + 叶子条件"的任意嵌套。根节点为空时视为无条件解锁(始终满足)。 /// [InlineProperty] [Serializable] public class UnlockCondition { [HideLabel] [SerializeReference] [InfoBox("留空表示无条件解锁(章节起始即可用)。可选择 All Of(AND) / Any Of(OR) / Not 复合节点进行任意嵌套。")] public StoryConditionNode root; /// /// 检查解锁条件是否满足。根节点为空视为满足。 /// /// 按变量名返回其整型值的委托。 /// 按 blockId 判断该 block 是否已完成的委托。 public bool IsSatisfied(Func getVariable, Func isBlockCompleted) { return root == null || root.Evaluate(getVariable, isBlockCompleted); } } }