feat: Round 50 narrative systems improvements

IQuestManager+QuestManager: add FillQuestsInState/FillFilterQuests buffer overloads (no-alloc hot path); remove R49 duplicate implementations.

QuestGiver: cache current quest result (_cachedQuest/_cachedState/_cacheDirty) to avoid per-frame foreach in InteractPrompt; invalidate on OnEnable and Interact_Internal state changes.

IDialogueService+DialogueManager: add StartDialogue(..., Action onComplete) overload; callback fires once on ForceEnd (covers both normal end and interrupt); supports chained callbacks via += accumulation.

DialogueVariantPreviewWindow: add 'Copy CSV' button in matrix section; exports all 2^N flag combinations with winner column; handles N>10 guard and CSV-safe escaping.

WorldStateRegistry: add TryGetCategory(id, out category) reverse lookup for debug tools.

NpcSOEditor: new CustomEditor for NpcSO showing live nameKey localization preview in Inspector (green label or warning box if Key not found).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-05-25 00:24:20 +08:00
parent 3c3ea1ead6
commit 9c1e70fdeb
8 changed files with 283 additions and 37 deletions

View File

@@ -32,14 +32,26 @@ namespace BaseGames.Quest
// ── InteractableNPC 覆盖 ──────────────────────────────────────────────
// 缓存上次查找结果,避免 InteractPrompt get每帧调用重复遍历 _offeredQuests。
// 当状态可能变更时OnEnable、Interact_Internal 后)标记为脏。
private QuestSO _cachedQuest;
private QuestStateEnum _cachedState;
private bool _cacheDirty = true;
protected override void OnEnable()
{
base.OnEnable();
_cacheDirty = true;
}
public override string InteractPrompt
{
get
{
var qm = SL.GetOrDefault<IQuestManager>();
var quest = GetCurrentOrCompletedQuest(qm);
var quest = GetCachedQuest(qm);
if (quest == null || qm == null) return base.InteractPrompt;
return qm.GetState(quest.questId) switch
return _cachedState switch
{
QuestStateEnum.Available => "接受任务",
QuestStateEnum.Active => qm.IsReadyToComplete(quest.questId) ? "提交任务" : "进行中…",
@@ -53,32 +65,30 @@ namespace BaseGames.Quest
protected override void Interact_Internal(Transform player)
{
var qm = SL.GetOrDefault<IQuestManager>();
var quest = GetCurrentOrCompletedQuest(qm);
var quest = GetCachedQuest(qm);
if (quest == null || qm == null) return;
var state = qm.GetState(quest.questId);
if (state == QuestStateEnum.Available)
if (_cachedState == QuestStateEnum.Available)
{
qm.AcceptQuest(quest.questId);
_cacheDirty = true; // 状态已变更,下次访问重新查询
}
else if (state == QuestStateEnum.Active && qm.IsReadyToComplete(quest.questId))
else if (_cachedState == QuestStateEnum.Active && qm.IsReadyToComplete(quest.questId))
{
// 直接从 player 获取 PlayerStats避免对 PlayerController 的程序集依赖
var stats = player.GetComponentInParent<PlayerStats>();
qm.CompleteQuest(quest.questId, stats);
_cacheDirty = true; // 状态已变更,下次访问重新查询
}
}
protected override DialogueSequenceSO GetCurrentDialogue()
{
var qm = SL.GetOrDefault<IQuestManager>();
var quest = GetCurrentOrCompletedQuest(qm);
var quest = GetCachedQuest(qm);
if (quest == null || qm == null) return base.GetCurrentDialogue();
var state = qm.GetState(quest.questId);
return state switch
return _cachedState switch
{
QuestStateEnum.Available => _availableDialogue,
QuestStateEnum.Active => qm.IsReadyToComplete(quest.questId)
@@ -92,24 +102,36 @@ namespace BaseGames.Quest
// ── 私有辅助 ─────────────────────────────────────────────────────────
/// <summary>
/// 返回当前处于 AvailableActive 状态的第一个任务;
/// 若全部已完成,返回最后一个已完成任务(用于显示 completedDialogue
/// 返回缓存的当前任务(处于 Available/Active/Paused 的第一个,或最后一个已完成任务)。
/// 若缓存不脏,直接返回上次结果,避免每帧遍历 _offeredQuests
/// 调用 Interact_Internal 后将 _cacheDirty 置 true确保下次交互状态是最新的。
/// </summary>
private QuestSO GetCurrentOrCompletedQuest(IQuestManager qm = null)
private QuestSO GetCachedQuest(IQuestManager qm = null)
{
if (_offeredQuests == null) return null;
if (!_cacheDirty && _cachedQuest != null) return _cachedQuest;
qm ??= SL.GetOrDefault<IQuestManager>();
if (qm == null) return null;
if (_offeredQuests == null || qm == null) { _cacheDirty = false; return null; }
QuestSO lastCompleted = null;
foreach (var q in _offeredQuests)
{
if (q == null) continue;
var s = qm.GetState(q.questId);
if (s == QuestStateEnum.Available || s == QuestStateEnum.Active || s == QuestStateEnum.Paused) return q;
if (s == QuestStateEnum.Available || s == QuestStateEnum.Active || s == QuestStateEnum.Paused)
{
_cachedQuest = q;
_cachedState = s;
_cacheDirty = false;
return _cachedQuest;
}
if (s == QuestStateEnum.Completed) lastCompleted = q;
}
return lastCompleted;
_cachedQuest = lastCompleted;
_cachedState = lastCompleted != null ? QuestStateEnum.Completed : QuestStateEnum.Unavailable;
_cacheDirty = false;
return _cachedQuest;
}
}
}