feat: Round 48 narrative systems improvements
- 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>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BaseGames.Core;
|
||||
using BaseGames.Core.Save;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -22,16 +23,23 @@ namespace BaseGames.World
|
||||
/// LoadAsync → WorldStateRegistrySaver.OnLoad → 调用 LoadFromSave(data) 恢复缓存。
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "BaseGames/World/WorldStateRegistry")]
|
||||
public class WorldStateRegistry : ScriptableObject
|
||||
public class WorldStateRegistry : ScriptableObject, IWorldStateReader
|
||||
{
|
||||
// ── 统一状态字典 ─────────────────────────────────────────────────────
|
||||
private readonly Dictionary<WorldObjectCategory, HashSet<string>> _states = new();
|
||||
|
||||
/// <summary>
|
||||
/// 状态变更时广播:(类别, id)。UI / 测试代码可订阅此事件做响应式刷新。
|
||||
/// 若需批量写入多个 ID,推荐使用 <see cref="BatchMark"/> 避免同帧多次重绘。
|
||||
/// </summary>
|
||||
public event Action<WorldObjectCategory, string> OnStateChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 批次状态变更时广播:(类别, 新增标记的 ID 数组)。
|
||||
/// 由 <see cref="BatchMark"/> 触发,一次性广播所有新增 ID,避免 UI 同帧重绘 N 次。
|
||||
/// </summary>
|
||||
public event Action<WorldObjectCategory, string[]> OnBatchStateChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Editor 重新进入 Play Mode 时 ScriptableObject 保留上一次运行的状态,
|
||||
/// OnEnable 在域重载(Domain Reload)和每次 Play 开始时都会调用,确保状态干净。
|
||||
@@ -70,10 +78,39 @@ namespace BaseGames.World
|
||||
|
||||
public bool HasFlag(string key) => IsMarked(WorldObjectCategory.Flag, key);
|
||||
public void SetFlag(string key) => Mark(WorldObjectCategory.Flag, key);
|
||||
public void ClearFlag(string key)
|
||||
public void ClearFlag(string key) => Clear(WorldObjectCategory.Flag, key);
|
||||
|
||||
/// <summary>
|
||||
/// 通用清除接口:移除指定类别中 id 的标记状态(幂等)。
|
||||
/// 用于调试重置、测试、或撤销错误标记。
|
||||
/// </summary>
|
||||
public void Clear(WorldObjectCategory category, string id)
|
||||
{
|
||||
if (_states.TryGetValue(WorldObjectCategory.Flag, out var set) && set.Remove(key))
|
||||
OnStateChanged?.Invoke(WorldObjectCategory.Flag, key);
|
||||
if (_states.TryGetValue(category, out var set) && set.Remove(id))
|
||||
OnStateChanged?.Invoke(category, id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 一次性标记多个 ID(批次写入)。已标记的 ID 被幂等跳过;
|
||||
/// 全部写入后触发单次 <see cref="OnBatchStateChanged"/>,而非逐个触发 <see cref="OnStateChanged"/>,
|
||||
/// 适合 EventChain 同帧连续设置多个标志时使用以避免 UI 同帧重绘 N 次。
|
||||
/// </summary>
|
||||
/// <returns>实际新增标记的 ID 数量。</returns>
|
||||
public int BatchMark(WorldObjectCategory category, System.Collections.Generic.IEnumerable<string> ids)
|
||||
{
|
||||
if (ids == null) return 0;
|
||||
if (!_states.TryGetValue(category, out var set))
|
||||
{
|
||||
set = new HashSet<string>();
|
||||
_states[category] = set;
|
||||
}
|
||||
var added = new System.Collections.Generic.List<string>();
|
||||
foreach (var id in ids)
|
||||
if (!string.IsNullOrEmpty(id) && set.Add(id))
|
||||
added.Add(id);
|
||||
if (added.Count > 0)
|
||||
OnBatchStateChanged?.Invoke(category, added.ToArray());
|
||||
return added.Count;
|
||||
}
|
||||
|
||||
// ── Persistence ───────────────────────────────────────────────────────
|
||||
@@ -98,14 +135,18 @@ namespace BaseGames.World
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 返回指定分类中所有已标记的 ID(只读视图,非副本)。
|
||||
/// 返回指定分类中所有已标记 ID 的快照副本(数组)。
|
||||
/// 由 WorldStateRegistrySaver.OnSave 调用,将运行时状态写入 SaveData。
|
||||
/// 注意:不保证跨帧稳定性,调用方若需持久快照请自行 ToArray()。
|
||||
/// 返回数组副本而非内部集合引用,防止调用方意外修改内部状态。
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<string> GetAllIds(WorldObjectCategory category)
|
||||
{
|
||||
if (_states.TryGetValue(category, out var set))
|
||||
return set;
|
||||
if (_states.TryGetValue(category, out var set) && set.Count > 0)
|
||||
{
|
||||
var copy = new string[set.Count];
|
||||
set.CopyTo(copy);
|
||||
return copy;
|
||||
}
|
||||
return System.Array.Empty<string>();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user