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>
This commit is contained in:
2026-05-25 00:05:15 +08:00
parent 446fd5dcd0
commit 6eaa83dc71
72 changed files with 7080 additions and 373 deletions

View File

@@ -0,0 +1,61 @@
using BaseGames.Core.Events;
using UnityEngine;
namespace BaseGames.Quest
{
/// <summary>
/// 任务事件频道注册表 SO架构 22_QuestChallengeModule §4.1)。
/// 将 QuestManager 的 10+ 个分散事件频道字段集中到一个可复用的 ScriptableObject 中,
/// 便于多场景共享同一套频道配置,同时减少 QuestManager Inspector 的视觉复杂度。
///
/// 使用方式:
/// 1. 创建一个 QuestEventChannelRegistry 资产菜单BaseGames/Quest/EventChannelRegistry
/// 2. 在资产中将现有各 EventChannelSO 拖入对应字段。
/// 3. 将资产引用填入 QuestManager 的 "事件频道注册表" 字段。
/// 4. QuestManager 的独立频道字段将自动隐藏(通过注册表覆盖)。
/// </summary>
[CreateAssetMenu(menuName = "BaseGames/Quest/EventChannelRegistry", fileName = "QuestEventChannelRegistry")]
public class QuestEventChannelRegistry : ScriptableObject
{
[Header("输入频道QuestManager 监听)")]
[Tooltip("EVT_EnemyDiedpayload = enemyIdstring。敌人死亡时由战斗系统广播驱动击败类目标进度。")]
public StringEventChannelSO onEnemyDied;
[Tooltip("EVT_CollectiblePickuppayload = itemIdstring。拾取物品时广播驱动收集类目标进度。")]
public StringEventChannelSO onCollectiblePickup;
[Tooltip("EVT_SceneLoadedpayload = sceneNamestring。场景切换完成时广播驱动到达类目标进度。")]
public StringEventChannelSO onSceneLoaded;
[Tooltip("EVT_NpcDialogueCompletedpayload = npcIdstring。DialogueManager 播完一段对话后广播,驱动对话类目标进度。")]
public StringEventChannelSO onNpcDialogueCompleted;
[Tooltip("EVT_SkillUsedpayload = AbilityType.ToString()string。玩家使用技能时广播驱动技能使用类目标进度。")]
public StringEventChannelSO onSkillUsed;
[Tooltip("EVT_AreaReachedpayload = markerTagstring。TriggerZone 在玩家进入时广播,驱动区域到达类目标进度。")]
public StringEventChannelSO onAreaReached;
[Header("广播频道QuestManager 广播)")]
[Tooltip("EVT_QuestStartedpayload = questId。AcceptQuest 成功后广播。")]
public StringEventChannelSO onQuestStarted;
[Tooltip("EVT_QuestCompletedpayload = questId。CompleteQuest 成功后广播。")]
public StringEventChannelSO onQuestCompleted;
[Tooltip("EVT_QuestFailedpayload = questId。失败条件触发后广播。")]
public StringEventChannelSO onQuestFailed;
[Tooltip("EVT_QuestAbandonedpayload = questId。玩家放弃任务时广播。")]
public StringEventChannelSO onQuestAbandoned;
[Tooltip("EVT_QuestPausedpayload = questId。PauseQuest 成功后广播。")]
public StringEventChannelSO onQuestPaused;
[Tooltip("EVT_QuestResumedpayload = questId。ResumeQuest 成功后广播。")]
public StringEventChannelSO onQuestResumed;
[Tooltip("EVT_QuestReadyToCompletepayload = questId。目标全部达成时广播一次去重。")]
public StringEventChannelSO onQuestReadyToComplete;
[Tooltip("EVT_QuestObjectiveUpdated目标进度变化时广播强类型 QuestObjectiveEvent。")]
public QuestObjectiveEventChannelSO onObjectiveUpdated;
[Tooltip("EVT_QuestObjectiveBatchUpdated同帧内多目标聚合后广播一次避免 HUD 同帧多次重绘)。")]
public QuestObjectiveBatchEventChannelSO onObjectiveBatchUpdated;
[Tooltip("EVT_NpcAffinityChangedNPC 好感度变化(强类型 NpcAffinityEvent。")]
public NpcAffinityEventChannelSO onNpcAffinityChanged;
[Tooltip("EVT_DialogueKeyUnlockedpayload = unlockDialogueKey供 NPC 台词系统监听。")]
public StringEventChannelSO onDialogueKeyUnlocked;
[Tooltip("EVT_DialogueChoiceSelected玩家选择对话选项时广播payload = \"sequenceId/choiceIndex\")。\n" +
"供 QA 埋点、成就系统、或数据分析监听,以还原玩家的对话选择路径。")]
public StringEventChannelSO onDialogueChoiceSelected;
}
}