226 lines
11 KiB
C#
226 lines
11 KiB
C#
using UnityEngine;
|
||
using BaseGames.Core;
|
||
using BaseGames.Dialogue;
|
||
using BaseGames.Player;
|
||
using QuestStateEnum = BaseGames.Core.Events.QuestState;
|
||
using SL = BaseGames.Core.ServiceLocator;
|
||
using L10n = BaseGames.Localization.LocalizationManager;
|
||
|
||
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;
|
||
|
||
[Header("交互选项")]
|
||
[Tooltip("勾选后,任务进行中(Active 且未完成)时交互提示变为\"放弃任务\",交互即触发 AbandonQuest。\n" +
|
||
"适合允许玩家主动放弃的支线任务;主线任务建议保持取消勾选。")]
|
||
[SerializeField] private bool _allowAbandon;
|
||
|
||
// ── InteractableNPC 覆盖 ──────────────────────────────────────────────
|
||
|
||
// 缓存上次查找结果,避免 InteractPrompt get(每帧调用)重复遍历 _offeredQuests。
|
||
// 当状态可能变更时(OnEnable、Interact_Internal 后)标记为脏。
|
||
private QuestSO _cachedQuest;
|
||
private QuestStateEnum _cachedState;
|
||
private bool _cacheDirty = true;
|
||
|
||
// 本地化 Key 常量 — 对应 UI 本地化表中的条目;
|
||
// 如本地化表未配置该 Key,GetPrompt 会降级为内联的中文默认文本。
|
||
private const string K_Accept = "QUEST_PROMPT_ACCEPT";
|
||
private const string K_Submit = "QUEST_PROMPT_SUBMIT";
|
||
private const string K_InProgress = "QUEST_PROMPT_IN_PROGRESS";
|
||
private const string K_Paused = "QUEST_PROMPT_PAUSED";
|
||
private const string K_Talk = "QUEST_PROMPT_TALK";
|
||
private const string K_Locked = "QUEST_PROMPT_LOCKED";
|
||
private const string K_Abandon = "QUEST_PROMPT_ABANDON";
|
||
|
||
// 缓存 IQuestManager + IQuestEventSource 引用,避免每次访问 InteractPrompt 调用 SL.GetOrDefault
|
||
private IQuestManager _questManager;
|
||
private IQuestEventSource _questEvents;
|
||
|
||
protected override void OnEnable()
|
||
{
|
||
base.OnEnable();
|
||
_cacheDirty = true;
|
||
_questManager = SL.GetOrDefault<IQuestManager>();
|
||
_questEvents = _questManager as IQuestEventSource;
|
||
if (_questEvents != null)
|
||
{
|
||
_questEvents.OnQuestStateChanged += HandleQuestStateChanged;
|
||
_questEvents.OnAfterSaveLoaded += HandleAfterSaveLoaded;
|
||
}
|
||
}
|
||
|
||
protected override void OnDisable()
|
||
{
|
||
base.OnDisable();
|
||
if (_questEvents != null)
|
||
{
|
||
_questEvents.OnQuestStateChanged -= HandleQuestStateChanged;
|
||
_questEvents.OnAfterSaveLoaded -= HandleAfterSaveLoaded;
|
||
_questEvents = null;
|
||
}
|
||
_questManager = null;
|
||
}
|
||
|
||
// 任务状态变化时自动标记缓存失效(无需再手动设 _cacheDirty)
|
||
private void HandleQuestStateChanged(string questId, QuestStateEnum from, QuestStateEnum to)
|
||
{
|
||
if (_offeredQuests == null) return;
|
||
foreach (var q in _offeredQuests)
|
||
if (q != null && q.questId == questId) { _cacheDirty = true; return; }
|
||
}
|
||
|
||
// 存档加载完成后统一刷新缓存,确保 NPC 交互提示反映最新任务状态
|
||
private void HandleAfterSaveLoaded() => _cacheDirty = true;
|
||
|
||
// 本地化提示词辅助:如 Key 在表中找不到(返回值等于 Key 自身),回退到内联默认文本
|
||
private static string GetPrompt(string key, string fallback)
|
||
{
|
||
var v = L10n.Get(key, "UI");
|
||
return v != key ? v : fallback;
|
||
}
|
||
|
||
public override string InteractPrompt
|
||
{
|
||
get
|
||
{
|
||
var quest = GetCachedQuest();
|
||
if (quest == null || _questManager == null) return base.InteractPrompt;
|
||
|
||
if (_cachedState == QuestStateEnum.Available)
|
||
{
|
||
// 检查亲密度门槛等锁定条件,锁定时显示具体原因而非直接"接受任务"
|
||
var lockInfo = _questManager.GetQuestLockInfo(quest.questId);
|
||
if (lockInfo.IsLocked)
|
||
{
|
||
string fallback = lockInfo.Reason == QuestLockReason.InsufficientAffinity
|
||
? $"好感度不足({lockInfo.Param})"
|
||
: "条件未满足";
|
||
return GetPrompt(K_Locked, fallback);
|
||
}
|
||
return GetPrompt(K_Accept, "接受任务");
|
||
}
|
||
|
||
if (_cachedState == QuestStateEnum.Active)
|
||
{
|
||
if (_questManager.IsReadyToComplete(quest.questId))
|
||
return GetPrompt(K_Submit, "提交任务");
|
||
return _allowAbandon
|
||
? GetPrompt(K_Abandon, "放弃任务")
|
||
: GetPrompt(K_InProgress, "进行中…");
|
||
}
|
||
|
||
return _cachedState switch
|
||
{
|
||
QuestStateEnum.Paused => GetPrompt(K_Paused, "暂停中…"),
|
||
QuestStateEnum.Completed => GetPrompt(K_Talk, "对话"),
|
||
_ => base.InteractPrompt,
|
||
};
|
||
}
|
||
}
|
||
|
||
protected override void Interact_Internal(Transform player)
|
||
{
|
||
var quest = GetCachedQuest();
|
||
if (quest == null || _questManager == null) return;
|
||
|
||
if (_cachedState == QuestStateEnum.Available)
|
||
{
|
||
// 亲密度门槛等锁定条件未满足时静默返回(InteractPrompt 已显示原因,玩家可见)
|
||
var lockInfo = _questManager.GetQuestLockInfo(quest.questId);
|
||
if (lockInfo.IsLocked) return;
|
||
_questManager.AcceptQuest(quest.questId);
|
||
// OnQuestStateChanged 事件会自动触发 HandleQuestStateChanged → _cacheDirty = true
|
||
}
|
||
else if (_cachedState == QuestStateEnum.Active)
|
||
{
|
||
if (_questManager.IsReadyToComplete(quest.questId))
|
||
{
|
||
// 直接从 player 获取 PlayerStats,避免对 PlayerController 的程序集依赖
|
||
var stats = player.GetComponentInParent<PlayerStats>();
|
||
_questManager.CompleteQuest(quest.questId, stats);
|
||
// OnQuestStateChanged 事件会自动触发 HandleQuestStateChanged → _cacheDirty = true
|
||
}
|
||
else if (_allowAbandon)
|
||
{
|
||
_questManager.AbandonQuest(quest.questId);
|
||
// OnQuestStateChanged 事件会自动触发 HandleQuestStateChanged → _cacheDirty = true
|
||
}
|
||
}
|
||
}
|
||
|
||
protected override DialogueSequenceSO GetCurrentDialogue()
|
||
{
|
||
var quest = GetCachedQuest();
|
||
if (quest == null || _questManager == null) return base.GetCurrentDialogue();
|
||
|
||
return _cachedState switch
|
||
{
|
||
QuestStateEnum.Available => _availableDialogue,
|
||
QuestStateEnum.Active => _questManager.IsReadyToComplete(quest.questId)
|
||
? _readyDialogue : _activeDialogue,
|
||
QuestStateEnum.Paused => _activeDialogue, // 暂停中显示"催促"对话,不触发任何状态推进
|
||
QuestStateEnum.Completed => _completedDialogue,
|
||
_ => base.GetCurrentDialogue(),
|
||
};
|
||
}
|
||
|
||
// ── 私有辅助 ─────────────────────────────────────────────────────────
|
||
|
||
/// <summary>
|
||
/// 返回缓存的当前任务(处于 Available/Active/Paused 的第一个,或最后一个已完成任务)。
|
||
/// 若缓存不脏,直接返回上次结果,避免每帧遍历 _offeredQuests。
|
||
/// 任务状态事件(HandleQuestStateChanged)或 OnEnable 会自动将 _cacheDirty 置 true,
|
||
/// 确保下次访问状态是最新的。
|
||
/// </summary>
|
||
private QuestSO GetCachedQuest()
|
||
{
|
||
if (!_cacheDirty && _cachedQuest != null) return _cachedQuest;
|
||
|
||
var qm = _questManager;
|
||
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;
|
||
}
|
||
}
|
||
}
|