Files
zeling_v2/Assets/_Game/Scripts/Dialogue/NarrativeNPC.cs
Joywayer 6eaa83dc71 feat: Round 48 narrative systems improvements
- QuestSO: Add ValidateBranchCycles() DFS detection for branches[].nextQuest loop
- QuestSO: Mark three legacy prerequisite fields with v2.0 removal warning in Tooltip
- IQuestManager: Add QuestLockReason enum + QuestLockInfo struct (strongly-typed lock info)
- IQuestManager: Add GetQuestLockInfo() method to interface; GetQuestLockReason() now delegates to it
- IQuestEventSource: Add OnQuestStateChanged(questId, oldState, newState) unified event
- QuestManager: Implement GetQuestLockInfo(); fire OnQuestStateChanged on all state transitions
- DialogueManager: Add one-frame yield in HandleChoices before ShowChoices (skip-debounce fix)
- DialogueManager: Increment _playbackId in ForceEnd() to invalidate residual choice callbacks
- DialogueSequenceSO: Add UNITY_EDITOR debug log in TryGetActiveVariant on variant match
- WorldStateRegistry: Add OnBatchStateChanged event + BatchMark() batch-write API
- DialogueModule: List badge shows warning indicator for unconditional-shadowing variants
- DialogueModule: BuildVariantsCard shows logic mode (AND/OR) alongside flag conditions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-25 00:05:15 +08:00

90 lines
3.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using BaseGames.Core;
using BaseGames.World;
using UnityEngine;
namespace BaseGames.Dialogue
{
/// <summary>
/// 条件对话 NPC架构 14_NarrativeModule §7
/// 扩展 InteractableNPC根据世界状态标志动态选择对话版本。
/// 版本列表从高到低优先级排列;第一个满足条件的版本生效。
///
/// _worldState 可留空:留空时自动从 ServiceLocator 获取全局注册的 IWorldStateReader
/// 便于无需在每个 NPC Prefab 上手动拖入 WorldStateRegistry 资产。
/// </summary>
public class NarrativeNPC : InteractableNPC
{
[Header("台词版本集(从高到低优先级排列)")]
[Tooltip("条件对话版本列表。运行时从上到下检查,第一个满足条件的版本被播放。\n" +
"版本之间的优先级由列表顺序决定——请将最具体的条件放在最上方。")]
[SerializeField] private DialogueVersion[] _dialogueVersions;
[Tooltip("所有版本均不满足条件时的兜底对话。务必配置,否则运行时会输出 LogWarning 且 NPC 无对话。")]
[SerializeField] private DialogueSequenceSO _fallbackDialogue;
[Tooltip("世界状态 SO可选。留空时自动从 ServiceLocator 获取全局 IWorldStateReader。\n" +
"通常同场景下多个 NPC 共用同一个 WorldStateRegistry\n" +
"若全局已通过 ServiceLocator 注册,可不在此处手动指定。")]
[SerializeField] private WorldStateRegistry _worldState;
protected override DialogueSequenceSO GetCurrentDialogue()
{
IWorldStateReader reader = _worldState
?? ServiceLocator.GetOrDefault<IWorldStateReader>();
if (_dialogueVersions == null) return _fallbackDialogue;
foreach (var version in _dialogueVersions)
{
if (version != null && version.CheckConditions(reader))
return version.dialogue;
}
if (_fallbackDialogue == null)
Debug.LogWarning($"[NarrativeNPC] '{name}' 没有版本满足当前条件,且未配置兜底对话 (_fallbackDialogue)。", gameObject);
return _fallbackDialogue;
}
}
// ─────────────────────────────────────────────────────────────────────────
/// <summary>
/// 一个对话版本及其激活条件(架构 14_NarrativeModule §7
/// </summary>
[Serializable]
public class DialogueVersion
{
[Tooltip("编辑器显示名,如'森林 Boss 击败后'")]
public string versionLabel;
[Tooltip("此版本对应的对话序列 SO。条件满足时播放。留空时等同于跳过此版本。")]
public DialogueSequenceSO dialogue;
[Tooltip("全部满足才激活此版本AND 关系)")]
[WorldStateFlag]
public string[] requiredFlags;
[Tooltip("有任意一个 = 此版本不激活NOT 关系)")]
[WorldStateFlag]
public string[] blockedByFlags;
/// <summary>
/// 检查此版本的激活条件AND requiredFlags / NOT blockedByFlags
/// reader 为 null 时直接返回 false无法判断视为条件不满足
/// </summary>
public bool CheckConditions(IWorldStateReader reader)
{
if (reader == null) return false;
if (requiredFlags != null)
foreach (var f in requiredFlags)
if (!reader.HasFlag(f)) return false;
if (blockedByFlags != null)
foreach (var f in blockedByFlags)
if (reader.HasFlag(f)) return false;
return true;
}
}
}