- 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>
173 lines
8.4 KiB
C#
173 lines
8.4 KiB
C#
using System;
|
||
using BaseGames.Core.Events;
|
||
using QuestStateEnum = BaseGames.Core.Events.QuestState;
|
||
|
||
namespace BaseGames.Quest
|
||
{
|
||
// =========================================================================
|
||
// QuestLockReason / QuestLockInfo ── 任务锁定原因(强类型 API)
|
||
// =========================================================================
|
||
|
||
/// <summary>任务无法接取的原因枚举。<see cref="None"/> 表示无锁定(可接取)。</summary>
|
||
public enum QuestLockReason
|
||
{
|
||
/// <summary>无锁定,任务当前可以接取。</summary>
|
||
None,
|
||
/// <summary>任务已在进行中(Active)。</summary>
|
||
AlreadyActive,
|
||
/// <summary>任务已完成(Completed)。</summary>
|
||
AlreadyCompleted,
|
||
/// <summary>任务已失败(Failed)。</summary>
|
||
Failed,
|
||
/// <summary>任务已暂停(Paused)。</summary>
|
||
Paused,
|
||
/// <summary>任务 ID 未找到或资产未加载。</summary>
|
||
NotFound,
|
||
/// <summary>好感度或存档数据尚未初始化。</summary>
|
||
DataNotLoaded,
|
||
/// <summary>NPC 好感度不足。<see cref="QuestLockInfo.Param"/> 格式:"{actual}/{min}"。</summary>
|
||
InsufficientAffinity,
|
||
/// <summary>前置任务未完成。<see cref="QuestLockInfo.Param"/> 为该前置任务的 questId。</summary>
|
||
RequiresQuest,
|
||
/// <summary>世界状态标志条件未满足。</summary>
|
||
FlagConditionNotMet,
|
||
}
|
||
|
||
/// <summary>
|
||
/// 任务锁定信息(强类型版本)。
|
||
/// 相比字符串 Key,可在编译期检查原因类型,UI 层无需手动解析冒号分隔的参数。
|
||
/// 通过 <see cref="ToLocalizationKey"/> 可转换为与旧版 <c>GetQuestLockReason</c> 兼容的 Key 格式。
|
||
/// </summary>
|
||
public struct QuestLockInfo
|
||
{
|
||
/// <summary>锁定原因枚举值。<see cref="QuestLockReason.None"/> 表示无锁定(可接取)。</summary>
|
||
public QuestLockReason Reason;
|
||
|
||
/// <summary>
|
||
/// 附带参数(可选):<br/>
|
||
/// - <see cref="QuestLockReason.RequiresQuest"/>:前置任务 questId<br/>
|
||
/// - <see cref="QuestLockReason.InsufficientAffinity"/>:格式 "{actual}/{min}"
|
||
/// </summary>
|
||
public string Param;
|
||
|
||
/// <summary>任务当前是否处于锁定状态(不可接取)。</summary>
|
||
public bool IsLocked => Reason != QuestLockReason.None;
|
||
|
||
/// <summary>
|
||
/// 转换为本地化 Key 格式,与旧版 <see cref="IQuestManager.GetQuestLockReason"/> 完全兼容。
|
||
/// 格式:<c>"Quest.LockReason.{Reason}"</c>;有参数时为 <c>"Quest.LockReason.{Reason}:{Param}"</c>。
|
||
/// </summary>
|
||
public string ToLocalizationKey() =>
|
||
Reason == QuestLockReason.None
|
||
? string.Empty
|
||
: string.IsNullOrEmpty(Param)
|
||
? $"Quest.LockReason.{Reason}"
|
||
: $"Quest.LockReason.{Reason}:{Param}";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 任务管理器的公开契约。ServiceLocator.Get<IQuestManager>() 获取实例,
|
||
/// 避免外部代码直接依赖 QuestManager 具体类型。
|
||
/// </summary>
|
||
public interface IQuestManager
|
||
{
|
||
/// <summary>接取任务(幂等)。</summary>
|
||
void AcceptQuest(string questId);
|
||
|
||
/// <summary>
|
||
/// 主动放弃进行中的任务(Active → Available/Unavailable),清除目标进度。
|
||
/// 非 Active 状态的任务调用此方法无效。
|
||
/// </summary>
|
||
void AbandonQuest(string questId);
|
||
|
||
/// <summary>完成任务并发放奖励。rewardTarget 接收奖励(如玩家)。</summary>
|
||
void CompleteQuest(string questId, IRewardTarget rewardTarget);
|
||
|
||
/// <summary>
|
||
/// 暂停进行中的任务(Active → Paused)。暂停期间目标不推进,失败条件不判定。
|
||
/// 非 Active 状态的任务调用此方法无效。
|
||
/// </summary>
|
||
void PauseQuest(string questId);
|
||
|
||
/// <summary>
|
||
/// 恢复已暂停的任务(Paused → Active)。
|
||
/// 非 Paused 状态的任务调用此方法无效。
|
||
/// </summary>
|
||
void ResumeQuest(string questId);
|
||
|
||
/// <summary>返回当前任务状态。未知 questId 返回 Unavailable。</summary>
|
||
QuestStateEnum GetState(string questId);
|
||
|
||
/// <summary>判断任务是否满足完成条件。</summary>
|
||
bool IsReadyToComplete(string questId);
|
||
|
||
/// <summary>返回指定 NPC 的当前好感度数值(未记录时返回 0)。</summary>
|
||
int GetNpcAffinity(string npcId);
|
||
|
||
/// <summary>
|
||
/// 返回任务无法被接取的原因(本地化 Key 格式)。
|
||
/// 若任务当前可以接取,返回空字符串。
|
||
/// Key 格式:<c>"Quest.LockReason.{Reason}"</c>;带动态参数时以冒号分隔,如
|
||
/// <c>"Quest.LockReason.RequiresQuest:Quest_FindMushroom"</c>。
|
||
/// <para>推荐新代码使用 <see cref="GetQuestLockInfo"/> 获取强类型结果,无需手动解析字符串。</para>
|
||
/// </summary>
|
||
string GetQuestLockReason(string questId);
|
||
|
||
/// <summary>
|
||
/// 返回任务无法被接取的强类型锁定信息。
|
||
/// 相比 <see cref="GetQuestLockReason"/>,可在编译期检查原因枚举,UI 层无需解析字符串。
|
||
/// 若任务当前可以接取,返回 <see cref="QuestLockInfo.Reason"/> 为 <see cref="QuestLockReason.None"/> 的实例。
|
||
/// </summary>
|
||
QuestLockInfo GetQuestLockInfo(string questId);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 任务事件订阅接口。
|
||
/// 外部系统(成就、地图标记、HUD、埋点)通过此接口订阅任务生命周期事件,
|
||
/// 无需直接持有 StringEventChannelSO,保持与 QuestManager 具体实现的解耦。
|
||
/// 获取方式:<c>ServiceLocator.Get<IQuestManager>() as IQuestEventSource</c>
|
||
/// </summary>
|
||
public interface IQuestEventSource
|
||
{
|
||
/// <summary>任务成功接取时触发。参数 = questId。</summary>
|
||
event Action<string> OnQuestStarted;
|
||
/// <summary>任务完成时触发。参数 = questId。</summary>
|
||
event Action<string> OnQuestCompleted;
|
||
/// <summary>任务失败时触发。参数 = questId。</summary>
|
||
event Action<string> OnQuestFailed;
|
||
/// <summary>任务被主动放弃时触发。参数 = questId。</summary>
|
||
event Action<string> OnQuestAbandoned;
|
||
/// <summary>任务暂停时触发(Active → Paused)。参数 = questId。供埋点/分析系统使用。</summary>
|
||
event Action<string> OnQuestPaused;
|
||
/// <summary>任务从暂停恢复时触发(Paused → Active)。参数 = questId。供埋点/分析系统使用。</summary>
|
||
event Action<string> OnQuestResumed;
|
||
/// <summary>目标全部达成、可回去交任务时触发(去重,同任务只触发一次)。参数 = questId。</summary>
|
||
event Action<string> OnQuestReadyToComplete;
|
||
/// <summary>
|
||
/// 任务状态发生任意转换时触发(涵盖所有状态变更,含旧状态和新状态)。
|
||
/// 供状态机审计面板、通用 UI 绑定(无需分别订阅六个离散事件)使用。
|
||
/// 参数:(questId, oldState, newState)。
|
||
/// </summary>
|
||
event Action<string, QuestStateEnum, QuestStateEnum> OnQuestStateChanged;
|
||
}
|
||
|
||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||
/// <summary>
|
||
/// 任务调试接口(仅编辑器 / 开发构建可用)。
|
||
/// 通过 <c>(IQuestManager as IQuestDebugger)?.ResetQuest(id)</c> 使用,
|
||
/// 正式发布构建中此接口不存在,调用方无需任何 #if 守卫。
|
||
/// </summary>
|
||
public interface IQuestDebugger
|
||
{
|
||
/// <summary>
|
||
/// 将任务重置为 Available(前置满足)或 Unavailable(前置未满足),并清除目标进度。
|
||
/// 不广播 QuestStarted / QuestCompleted 等运行时事件,仅用于开发/调试。
|
||
/// </summary>
|
||
/// <param name="questId">要重置的任务 ID。</param>
|
||
/// <param name="rollbackAffinity">若为 true(默认),同步回滚此任务对应 NPC 的好感度增量,
|
||
/// 防止调试期间重复完成导致好感度叠加。</param>
|
||
void ResetQuest(string questId, bool rollbackAffinity = true);
|
||
}
|
||
#endif
|
||
}
|