feat: Round 51 narrative systems improvements
- SaveData: update QuestState.Status comment to include Paused state - QuestManager: add inline comment on AcceptQuest duplicate-accept guard - QuestManager: wrap reward.Apply() in try-catch so exceptions don't corrupt already-committed Completed state - QuestManager.UnlockBranches: support new conditionFlagEntries (invert/ NOT logic) with graceful fallback to legacy conditionFlags - QuestGiver: cache IQuestManager field in OnEnable; subscribe to OnQuestStateChanged for automatic cache invalidation instead of manual _cacheDirty = true after each Interact; remove per-call SL.GetOrDefault - QuestGiver: replace hardcoded Chinese prompt strings with LocalizationManager.Get(key, 'UI') + inline fallback via GetPrompt() - QuestSO: add BranchFlagEntry struct (flagId + invert) for NOT-logic branch conditions; add conditionFlagEntries to QuestBranch with HideInInspector on legacy conditionFlags for backward compat - QuestModule: add static TTL cache (5 s) for FindAll<QuestSO>() in PopulateDependencyGraph to avoid re-scanning disk on every foldout open - NpcSOEditor: add 'jump to localization file' button that pings and selects the UI table JSON in the Project window Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -141,7 +141,7 @@ namespace BaseGames.Core.Save
|
|||||||
/// OnLoad 按此字段显式选择解析路径,杜绝依赖 Count > 0 的隐式推断。
|
/// OnLoad 按此字段显式选择解析路径,杜绝依赖 Count > 0 的隐式推断。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int DataVersion = 2;
|
public int DataVersion = 2;
|
||||||
public string Status; // "NotStarted"|"Active"|"Completed"|"Failed"
|
public string Status; // "Unavailable"|"Available"|"Active"|"Paused"|"Completed"|"Failed"
|
||||||
public int ObjectiveIndex;
|
public int ObjectiveIndex;
|
||||||
/// <summary>新格式(Round 24+,DataVersion=2):objectiveId → progressCount,重排目标顺序后存档不会错位。</summary>
|
/// <summary>新格式(Round 24+,DataVersion=2):objectiveId → progressCount,重排目标顺序后存档不会错位。</summary>
|
||||||
public Dictionary<string, int> ObjectiveProgress = new();
|
public Dictionary<string, int> ObjectiveProgress = new();
|
||||||
|
|||||||
@@ -50,6 +50,16 @@ namespace BaseGames.Editor.Dialogue
|
|||||||
$"▸ nameKey 解析预览:{resolved}",
|
$"▸ nameKey 解析预览:{resolved}",
|
||||||
s_previewStyle);
|
s_previewStyle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── 跳转到本地化文件 ────────────────────────────────────────────
|
||||||
|
EditorGUILayout.Space(4);
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
GUILayout.FlexibleSpace();
|
||||||
|
if (GUILayout.Button("跳转到本地化文件(UI 表)", GUILayout.Width(200)))
|
||||||
|
{
|
||||||
|
PingLocalizationFile("UI");
|
||||||
|
}
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -69,5 +79,32 @@ namespace BaseGames.Editor.Dialogue
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 在 Project 窗口中 Ping 指定表名对应的本地化 JSON 文件(Resources/Localization/…/{tableName}.json)。
|
||||||
|
/// 遍历所有语言目录,以第一个找到的文件为准。
|
||||||
|
/// </summary>
|
||||||
|
private static void PingLocalizationFile(string tableName)
|
||||||
|
{
|
||||||
|
string[] guids = AssetDatabase.FindAssets(
|
||||||
|
$"t:TextAsset {tableName}",
|
||||||
|
new[] { "Assets/Resources/Localization" });
|
||||||
|
|
||||||
|
foreach (var guid in guids)
|
||||||
|
{
|
||||||
|
string path = AssetDatabase.GUIDToAssetPath(guid);
|
||||||
|
// 文件名(不含扩展名)必须完全匹配 tableName
|
||||||
|
if (!path.EndsWith($"/{tableName}.json", System.StringComparison.OrdinalIgnoreCase)) continue;
|
||||||
|
|
||||||
|
var asset = AssetDatabase.LoadAssetAtPath<TextAsset>(path);
|
||||||
|
if (asset == null) continue;
|
||||||
|
|
||||||
|
EditorGUIUtility.PingObject(asset);
|
||||||
|
Selection.activeObject = asset;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.LogWarning($"[NpcSOEditor] 未找到本地化表文件:Resources/Localization/…/{tableName}.json");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ namespace BaseGames.Editor.Modules
|
|||||||
// playModeStateChanged 订阅的字段引用,便于在重建 ActionBar 时退订旧订阅,避免内存泄漏
|
// playModeStateChanged 订阅的字段引用,便于在重建 ActionBar 时退订旧订阅,避免内存泄漏
|
||||||
private System.Action<UnityEditor.PlayModeStateChange> _playModeHandler;
|
private System.Action<UnityEditor.PlayModeStateChange> _playModeHandler;
|
||||||
|
|
||||||
|
// 依赖关系图中 FindAll<QuestSO>() 的静态缓存,同一编辑器会话内复用,避免重复扫描磁盘
|
||||||
|
private static QuestSO[] s_allQuestCache;
|
||||||
|
private static double s_allQuestCacheTime;
|
||||||
|
private const double k_AllQuestCacheTtl = 5.0; // 秒;超时后下次打开 foldout 时刷新
|
||||||
|
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
{
|
{
|
||||||
_listPane = new SoListPane<QuestSO>(
|
_listPane = new SoListPane<QuestSO>(
|
||||||
@@ -421,7 +426,14 @@ namespace BaseGames.Editor.Modules
|
|||||||
|
|
||||||
private static void PopulateDependencyGraph(VisualElement container, QuestSO s)
|
private static void PopulateDependencyGraph(VisualElement container, QuestSO s)
|
||||||
{
|
{
|
||||||
var allQuests = AssetOperations.FindAll<QuestSO>();
|
// 静态 TTL 缓存:5 秒内复用上次 FindAll 结果,避免每次展开 foldout 都扫描全量资产
|
||||||
|
if (s_allQuestCache == null ||
|
||||||
|
EditorApplication.timeSinceStartup - s_allQuestCacheTime > k_AllQuestCacheTtl)
|
||||||
|
{
|
||||||
|
s_allQuestCache = AssetOperations.FindAll<QuestSO>();
|
||||||
|
s_allQuestCacheTime = EditorApplication.timeSinceStartup;
|
||||||
|
}
|
||||||
|
var allQuests = s_allQuestCache;
|
||||||
|
|
||||||
// ── 前置任务(上游)────────────────────────────────────────────────
|
// ── 前置任务(上游)────────────────────────────────────────────────
|
||||||
bool hasPrereqs = s.prerequisiteQuests != null && s.prerequisiteQuests.Length > 0;
|
bool hasPrereqs = s.prerequisiteQuests != null && s.prerequisiteQuests.Length > 0;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using BaseGames.Dialogue;
|
|||||||
using BaseGames.Player;
|
using BaseGames.Player;
|
||||||
using QuestStateEnum = BaseGames.Core.Events.QuestState;
|
using QuestStateEnum = BaseGames.Core.Events.QuestState;
|
||||||
using SL = BaseGames.Core.ServiceLocator;
|
using SL = BaseGames.Core.ServiceLocator;
|
||||||
|
using L10n = BaseGames.Localization.LocalizationManager;
|
||||||
|
|
||||||
namespace BaseGames.Quest
|
namespace BaseGames.Quest
|
||||||
{
|
{
|
||||||
@@ -38,25 +39,68 @@ namespace BaseGames.Quest
|
|||||||
private QuestStateEnum _cachedState;
|
private QuestStateEnum _cachedState;
|
||||||
private bool _cacheDirty = true;
|
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";
|
||||||
|
|
||||||
|
// 缓存 IQuestManager + IQuestEventSource 引用,避免每次访问 InteractPrompt 调用 SL.GetOrDefault
|
||||||
|
private IQuestManager _questManager;
|
||||||
|
private IQuestEventSource _questEvents;
|
||||||
|
|
||||||
protected override void OnEnable()
|
protected override void OnEnable()
|
||||||
{
|
{
|
||||||
base.OnEnable();
|
base.OnEnable();
|
||||||
_cacheDirty = true;
|
_cacheDirty = true;
|
||||||
|
_questManager = SL.GetOrDefault<IQuestManager>();
|
||||||
|
_questEvents = _questManager as IQuestEventSource;
|
||||||
|
if (_questEvents != null)
|
||||||
|
_questEvents.OnQuestStateChanged += HandleQuestStateChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDisable()
|
||||||
|
{
|
||||||
|
base.OnDisable();
|
||||||
|
if (_questEvents != null)
|
||||||
|
{
|
||||||
|
_questEvents.OnQuestStateChanged -= HandleQuestStateChanged;
|
||||||
|
_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; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 本地化提示词辅助:如 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
|
public override string InteractPrompt
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var qm = SL.GetOrDefault<IQuestManager>();
|
var quest = GetCachedQuest();
|
||||||
var quest = GetCachedQuest(qm);
|
if (quest == null || _questManager == null) return base.InteractPrompt;
|
||||||
if (quest == null || qm == null) return base.InteractPrompt;
|
|
||||||
return _cachedState switch
|
return _cachedState switch
|
||||||
{
|
{
|
||||||
QuestStateEnum.Available => "接受任务",
|
QuestStateEnum.Available => GetPrompt(K_Accept, "接受任务"),
|
||||||
QuestStateEnum.Active => qm.IsReadyToComplete(quest.questId) ? "提交任务" : "进行中…",
|
QuestStateEnum.Active => _questManager.IsReadyToComplete(quest.questId)
|
||||||
QuestStateEnum.Paused => "暂停中…",
|
? GetPrompt(K_Submit, "提交任务")
|
||||||
QuestStateEnum.Completed => "对话",
|
: GetPrompt(K_InProgress, "进行中…"),
|
||||||
|
QuestStateEnum.Paused => GetPrompt(K_Paused, "暂停中…"),
|
||||||
|
QuestStateEnum.Completed => GetPrompt(K_Talk, "对话"),
|
||||||
_ => base.InteractPrompt,
|
_ => base.InteractPrompt,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -64,34 +108,32 @@ namespace BaseGames.Quest
|
|||||||
|
|
||||||
protected override void Interact_Internal(Transform player)
|
protected override void Interact_Internal(Transform player)
|
||||||
{
|
{
|
||||||
var qm = SL.GetOrDefault<IQuestManager>();
|
var quest = GetCachedQuest();
|
||||||
var quest = GetCachedQuest(qm);
|
if (quest == null || _questManager == null) return;
|
||||||
if (quest == null || qm == null) return;
|
|
||||||
|
|
||||||
if (_cachedState == QuestStateEnum.Available)
|
if (_cachedState == QuestStateEnum.Available)
|
||||||
{
|
{
|
||||||
qm.AcceptQuest(quest.questId);
|
_questManager.AcceptQuest(quest.questId);
|
||||||
_cacheDirty = true; // 状态已变更,下次访问重新查询
|
// OnQuestStateChanged 事件会自动触发 HandleQuestStateChanged → _cacheDirty = true
|
||||||
}
|
}
|
||||||
else if (_cachedState == QuestStateEnum.Active && qm.IsReadyToComplete(quest.questId))
|
else if (_cachedState == QuestStateEnum.Active && _questManager.IsReadyToComplete(quest.questId))
|
||||||
{
|
{
|
||||||
// 直接从 player 获取 PlayerStats,避免对 PlayerController 的程序集依赖
|
// 直接从 player 获取 PlayerStats,避免对 PlayerController 的程序集依赖
|
||||||
var stats = player.GetComponentInParent<PlayerStats>();
|
var stats = player.GetComponentInParent<PlayerStats>();
|
||||||
qm.CompleteQuest(quest.questId, stats);
|
_questManager.CompleteQuest(quest.questId, stats);
|
||||||
_cacheDirty = true; // 状态已变更,下次访问重新查询
|
// OnQuestStateChanged 事件会自动触发 HandleQuestStateChanged → _cacheDirty = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override DialogueSequenceSO GetCurrentDialogue()
|
protected override DialogueSequenceSO GetCurrentDialogue()
|
||||||
{
|
{
|
||||||
var qm = SL.GetOrDefault<IQuestManager>();
|
var quest = GetCachedQuest();
|
||||||
var quest = GetCachedQuest(qm);
|
if (quest == null || _questManager == null) return base.GetCurrentDialogue();
|
||||||
if (quest == null || qm == null) return base.GetCurrentDialogue();
|
|
||||||
|
|
||||||
return _cachedState switch
|
return _cachedState switch
|
||||||
{
|
{
|
||||||
QuestStateEnum.Available => _availableDialogue,
|
QuestStateEnum.Available => _availableDialogue,
|
||||||
QuestStateEnum.Active => qm.IsReadyToComplete(quest.questId)
|
QuestStateEnum.Active => _questManager.IsReadyToComplete(quest.questId)
|
||||||
? _readyDialogue : _activeDialogue,
|
? _readyDialogue : _activeDialogue,
|
||||||
QuestStateEnum.Paused => _activeDialogue, // 暂停中显示"催促"对话,不触发任何状态推进
|
QuestStateEnum.Paused => _activeDialogue, // 暂停中显示"催促"对话,不触发任何状态推进
|
||||||
QuestStateEnum.Completed => _completedDialogue,
|
QuestStateEnum.Completed => _completedDialogue,
|
||||||
@@ -104,13 +146,14 @@ namespace BaseGames.Quest
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 返回缓存的当前任务(处于 Available/Active/Paused 的第一个,或最后一个已完成任务)。
|
/// 返回缓存的当前任务(处于 Available/Active/Paused 的第一个,或最后一个已完成任务)。
|
||||||
/// 若缓存不脏,直接返回上次结果,避免每帧遍历 _offeredQuests。
|
/// 若缓存不脏,直接返回上次结果,避免每帧遍历 _offeredQuests。
|
||||||
/// 调用 Interact_Internal 后将 _cacheDirty 置 true,确保下次交互状态是最新的。
|
/// 任务状态事件(HandleQuestStateChanged)或 OnEnable 会自动将 _cacheDirty 置 true,
|
||||||
|
/// 确保下次访问状态是最新的。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private QuestSO GetCachedQuest(IQuestManager qm = null)
|
private QuestSO GetCachedQuest()
|
||||||
{
|
{
|
||||||
if (!_cacheDirty && _cachedQuest != null) return _cachedQuest;
|
if (!_cacheDirty && _cachedQuest != null) return _cachedQuest;
|
||||||
|
|
||||||
qm ??= SL.GetOrDefault<IQuestManager>();
|
var qm = _questManager;
|
||||||
if (_offeredQuests == null || qm == null) { _cacheDirty = false; return null; }
|
if (_offeredQuests == null || qm == null) { _cacheDirty = false; return null; }
|
||||||
|
|
||||||
QuestSO lastCompleted = null;
|
QuestSO lastCompleted = null;
|
||||||
|
|||||||
@@ -244,6 +244,7 @@ namespace BaseGames.Quest
|
|||||||
public void AcceptQuest(string questId)
|
public void AcceptQuest(string questId)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(questId)) return;
|
if (string.IsNullOrEmpty(questId)) return;
|
||||||
|
// CanAccept 内部已通过 GetState() != Available 检查,防止重复接取 Active/Completed 任务产生重复事件
|
||||||
if (!CanAccept(questId)) return;
|
if (!CanAccept(questId)) return;
|
||||||
var oldState = GetState(questId);
|
var oldState = GetState(questId);
|
||||||
_questStates[questId] = QuestStateEnum.Active;
|
_questStates[questId] = QuestStateEnum.Active;
|
||||||
@@ -361,7 +362,13 @@ namespace BaseGames.Quest
|
|||||||
Chan_QuestCompleted?.Raise(questId);
|
Chan_QuestCompleted?.Raise(questId);
|
||||||
OnQuestCompleted?.Invoke(questId);
|
OnQuestCompleted?.Invoke(questId);
|
||||||
|
|
||||||
quest.reward?.Apply(rewardTarget);
|
// 奖励发放:用 try-catch 包裹,防止 Apply 异常导致好感度/对话解锁等后续逻辑中断
|
||||||
|
try { quest.reward?.Apply(rewardTarget); }
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogError(
|
||||||
|
$"[QuestManager] 任务 '{questId}' 奖励发放时抛出异常(任务状态已提交为 Completed):{ex.Message}\n{ex.StackTrace}");
|
||||||
|
}
|
||||||
ApplyAffinity(quest);
|
ApplyAffinity(quest);
|
||||||
UnlockDialogueKey(quest);
|
UnlockDialogueKey(quest);
|
||||||
UnlockBranches(questId, quest);
|
UnlockBranches(questId, quest);
|
||||||
@@ -425,38 +432,62 @@ namespace BaseGames.Quest
|
|||||||
if (!conditionMet) continue;
|
if (!conditionMet) continue;
|
||||||
|
|
||||||
// 世界状态标志条件(And/Or 由 conditionFlagsLogic 决定)
|
// 世界状态标志条件(And/Or 由 conditionFlagsLogic 决定)
|
||||||
|
// 优先用新版 conditionFlagEntries(支持 invert/NOT 取反),若为空则回退到旧版 conditionFlags
|
||||||
// saveService 未注入时降级:跳过标志检查,仅由 conditionQuest 决定分支
|
// saveService 未注入时降级:跳过标志检查,仅由 conditionQuest 决定分支
|
||||||
if (branch.conditionFlags != null && branch.conditionFlags.Length > 0
|
bool hasFlagEntries = branch.conditionFlagEntries != null && branch.conditionFlagEntries.Length > 0;
|
||||||
&& saveService != null)
|
bool hasLegacyFlags = branch.conditionFlags != null && branch.conditionFlags.Length > 0;
|
||||||
|
bool hasFlagConds = hasFlagEntries || hasLegacyFlags;
|
||||||
|
|
||||||
|
if (hasFlagConds && saveService != null)
|
||||||
{
|
{
|
||||||
if (branch.conditionFlagsLogic == BaseGames.Core.WorldStateFlagLogic.Or)
|
if (branch.conditionFlagsLogic == BaseGames.Core.WorldStateFlagLogic.Or)
|
||||||
{
|
{
|
||||||
conditionMet = false;
|
conditionMet = false;
|
||||||
foreach (var flag in branch.conditionFlags)
|
if (hasFlagEntries)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(flag) && saveService.GetFlag(flag))
|
foreach (var entry in branch.conditionFlagEntries)
|
||||||
{
|
{
|
||||||
conditionMet = true;
|
if (string.IsNullOrEmpty(entry.flagId)) continue;
|
||||||
break;
|
bool raw = saveService.GetFlag(entry.flagId);
|
||||||
|
if (entry.invert ? !raw : raw) { conditionMet = true; break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var flag in branch.conditionFlags)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(flag) && saveService.GetFlag(flag))
|
||||||
|
{ conditionMet = true; break; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// AND(默认):全部标志均须满足
|
// AND(默认):全部标志均须满足(支持 invert 取反)
|
||||||
foreach (var flag in branch.conditionFlags)
|
if (hasFlagEntries)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(flag)) continue;
|
foreach (var entry in branch.conditionFlagEntries)
|
||||||
if (!saveService.GetFlag(flag)) { conditionMet = false; break; }
|
{
|
||||||
|
if (string.IsNullOrEmpty(entry.flagId)) continue;
|
||||||
|
bool raw = saveService.GetFlag(entry.flagId);
|
||||||
|
if (entry.invert ? raw : !raw) { conditionMet = false; break; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var flag in branch.conditionFlags)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(flag)) continue;
|
||||||
|
if (!saveService.GetFlag(flag)) { conditionMet = false; break; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||||
else if (branch.conditionFlags != null && branch.conditionFlags.Length > 0
|
else if (hasFlagConds && saveService == null)
|
||||||
&& saveService == null)
|
|
||||||
{
|
{
|
||||||
Debug.LogWarning(
|
Debug.LogWarning(
|
||||||
$"[QuestManager] 任务 '{questId}' 分支配置了 conditionFlags,但 ISaveService 未注册," +
|
$"[QuestManager] 任务 '{questId}' 分支配置了标志条件,但 ISaveService 未注册," +
|
||||||
"标志条件已跳过(降级为仅 conditionQuest 判断)。");
|
"标志条件已跳过(降级为仅 conditionQuest 判断)。");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -310,12 +310,14 @@ namespace BaseGames.Quest
|
|||||||
[Tooltip("条件任务:该任务已 Completed 时走本分支(留空 = 默认分支,总是满足)。")]
|
[Tooltip("条件任务:该任务已 Completed 时走本分支(留空 = 默认分支,总是满足)。")]
|
||||||
public QuestSO conditionQuest;
|
public QuestSO conditionQuest;
|
||||||
[Tooltip("世界状态标志条件判断逻辑:\n" +
|
[Tooltip("世界状态标志条件判断逻辑:\n" +
|
||||||
" And(默认)= 全部 conditionFlags 均满足才走本分支\n" +
|
" And(默认)= 全部 conditionFlagEntries 均满足才走本分支\n" +
|
||||||
" Or = 任意一个 conditionFlag 满足即可走本分支")]
|
" Or = 任意一个 conditionFlagEntry 满足即可走本分支")]
|
||||||
public BaseGames.Core.WorldStateFlagLogic conditionFlagsLogic = BaseGames.Core.WorldStateFlagLogic.And;
|
public BaseGames.Core.WorldStateFlagLogic conditionFlagsLogic = BaseGames.Core.WorldStateFlagLogic.And;
|
||||||
[Tooltip("世界状态标志条件(可选)。按 conditionFlagsLogic 逻辑与 conditionQuest 共同决定分支是否激活。\n" +
|
[Tooltip("世界状态标志条件(支持 invert 取反)。按 conditionFlagsLogic 逻辑与 conditionQuest 共同决定分支是否激活。\n" +
|
||||||
"标志由 ISaveService.GetFlag(flagId) 查询,通常由 SetFlagAction 或其他系统写入。")]
|
"优先使用此字段;若为空则自动回退到旧版 conditionFlags 以保证兼容性。")]
|
||||||
[WorldStateFlag]
|
public BranchFlagEntry[] conditionFlagEntries;
|
||||||
|
[Tooltip("(旧版兼容字段,已被 conditionFlagEntries 取代。如 conditionFlagEntries 不为空则本字段被忽略。)")]
|
||||||
|
[HideInInspector]
|
||||||
public string[] conditionFlags;
|
public string[] conditionFlags;
|
||||||
[Tooltip("本分支解锁的后续任务。满足所有条件后,此任务将被设为 Available。")]
|
[Tooltip("本分支解锁的后续任务。满足所有条件后,此任务将被设为 Available。")]
|
||||||
public QuestSO nextQuest;
|
public QuestSO nextQuest;
|
||||||
@@ -327,6 +329,19 @@ namespace BaseGames.Quest
|
|||||||
public string npcDialogueKey;
|
public string npcDialogueKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 任务分支中单个世界状态标志条件,支持取反(NOT)逻辑。
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public struct BranchFlagEntry
|
||||||
|
{
|
||||||
|
[Tooltip("世界状态标志 ID(由 ISaveService.GetFlag 查询)。")]
|
||||||
|
[BaseGames.Core.WorldStateFlag]
|
||||||
|
public string flagId;
|
||||||
|
[Tooltip("若勾选,则该标志为 false 时才满足条件(NOT 取反逻辑)。")]
|
||||||
|
public bool invert;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>任务分类,供日志 UI 分区和 DataHub 过滤使用。</summary>
|
/// <summary>任务分类,供日志 UI 分区和 DataHub 过滤使用。</summary>
|
||||||
public enum QuestCategory
|
public enum QuestCategory
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user