Files
zeling_v2/Assets/_Game/Scripts/Quest/QuestGiver.cs
Joywayer 9c1e70fdeb 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>
2026-05-25 00:24:20 +08:00

138 lines
6.6 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 UnityEngine;
using BaseGames.Core;
using BaseGames.Dialogue;
using BaseGames.Player;
using QuestStateEnum = BaseGames.Core.Events.QuestState;
using SL = BaseGames.Core.ServiceLocator;
namespace BaseGames.Quest
{
/// <summary>
/// 可发布/完成任务的 NPC架构 22_QuestChallengeModule §6
/// 继承 InteractableNPC根据任务状态切换对话版本在交互时处理任务接收/完成。
/// </summary>
public class QuestGiver : InteractableNPC
{
[Header("任务")]
[Tooltip("该 NPC 可提供的所有任务,按优先级从高到低排列。\n" +
"交互时从列表头部找到第一个 Available 或 Active 状态的任务作为当前任务;\n" +
"若全部已完成,显示最后一个已完成任务的 completedDialogue。")]
[SerializeField] private QuestSO[] _offeredQuests;
[Header("对话版本(根据任务状态切换)")]
[Tooltip("任务尚未接取QuestState.Available时播放。通常是 NPC 发布任务、介绍背景的对话。")]
[SerializeField] private DialogueSequenceSO _availableDialogue;
[Tooltip("任务已接取、目标尚未全部完成QuestState.Active时播放。通常是 NPC 催促或加油打气的对话。")]
[SerializeField] private DialogueSequenceSO _activeDialogue;
[Tooltip("全部非可选目标已达成、任务可以交付时播放IsReadyToComplete = true。\n" +
"通常是 NPC 感谢、确认收取物品的对话,播放后执行 CompleteQuest 逻辑。")]
[SerializeField] private DialogueSequenceSO _readyDialogue;
[Tooltip("任务已完成QuestState.Completed后再次交互时播放。通常是 NPC 闲聊或后续剧情的对话。")]
[SerializeField] private DialogueSequenceSO _completedDialogue;
// ── 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 = GetCachedQuest(qm);
if (quest == null || qm == null) return base.InteractPrompt;
return _cachedState switch
{
QuestStateEnum.Available => "接受任务",
QuestStateEnum.Active => qm.IsReadyToComplete(quest.questId) ? "提交任务" : "进行中…",
QuestStateEnum.Paused => "暂停中…",
QuestStateEnum.Completed => "对话",
_ => base.InteractPrompt,
};
}
}
protected override void Interact_Internal(Transform player)
{
var qm = SL.GetOrDefault<IQuestManager>();
var quest = GetCachedQuest(qm);
if (quest == null || qm == null) return;
if (_cachedState == QuestStateEnum.Available)
{
qm.AcceptQuest(quest.questId);
_cacheDirty = true; // 状态已变更,下次访问重新查询
}
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 = GetCachedQuest(qm);
if (quest == null || qm == null) return base.GetCurrentDialogue();
return _cachedState switch
{
QuestStateEnum.Available => _availableDialogue,
QuestStateEnum.Active => qm.IsReadyToComplete(quest.questId)
? _readyDialogue : _activeDialogue,
QuestStateEnum.Paused => _activeDialogue, // 暂停中显示"催促"对话,不触发任何状态推进
QuestStateEnum.Completed => _completedDialogue,
_ => base.GetCurrentDialogue(),
};
}
// ── 私有辅助 ─────────────────────────────────────────────────────────
/// <summary>
/// 返回缓存的当前任务(处于 Available/Active/Paused 的第一个,或最后一个已完成任务)。
/// 若缓存不脏,直接返回上次结果,避免每帧遍历 _offeredQuests。
/// 调用 Interact_Internal 后将 _cacheDirty 置 true确保下次交互状态是最新的。
/// </summary>
private QuestSO GetCachedQuest(IQuestManager qm = null)
{
if (!_cacheDirty && _cachedQuest != null) return _cachedQuest;
qm ??= SL.GetOrDefault<IQuestManager>();
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)
{
_cachedQuest = q;
_cachedState = s;
_cacheDirty = false;
return _cachedQuest;
}
if (s == QuestStateEnum.Completed) lastCompleted = q;
}
_cachedQuest = lastCompleted;
_cachedState = lastCompleted != null ? QuestStateEnum.Completed : QuestStateEnum.Unavailable;
_cacheDirty = false;
return _cachedQuest;
}
}
}