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 { /// /// 可发布/完成任务的 NPC(架构 22_QuestChallengeModule §6)。 /// 继承 InteractableNPC,根据任务状态切换对话版本,在交互时处理任务接收/完成。 /// 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(); 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(); 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(); qm.CompleteQuest(quest.questId, stats); _cacheDirty = true; // 状态已变更,下次访问重新查询 } } protected override DialogueSequenceSO GetCurrentDialogue() { var qm = SL.GetOrDefault(); 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(), }; } // ── 私有辅助 ───────────────────────────────────────────────────────── /// /// 返回缓存的当前任务(处于 Available/Active/Paused 的第一个,或最后一个已完成任务)。 /// 若缓存不脏,直接返回上次结果,避免每帧遍历 _offeredQuests。 /// 调用 Interact_Internal 后将 _cacheDirty 置 true,确保下次交互状态是最新的。 /// private QuestSO GetCachedQuest(IQuestManager qm = null) { if (!_cacheDirty && _cachedQuest != null) return _cachedQuest; qm ??= SL.GetOrDefault(); 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; } } }