using System; using System.Collections.Generic; using System.Linq; using AibisDream.Framework; namespace AibisDream { /// /// 选项已探索状态追踪(内存态,由 OptionView 私有持有)。 /// public class OptionExploreTracker { private sealed class OptionRecord { public bool explored; public string[] deps = Array.Empty(); } private static readonly string[] EmptyDeps = Array.Empty(); private readonly Dictionary _records = new(); private readonly Dictionary> _varToOptions = new(); private string _currentExecutingLineId; public void Bind() { EnumEventSystem.Global.Register(StorageEvent.VariableSet, OnVariableSet); EnumEventSystem.Global.Register(DialogEventEnum.OptionSelected, OnOptionSelected); EnumEventSystem.Global.Register(DialogEventEnum.OptionShow, OnOptionShow); EnumEventSystem.Global.Register(GameLifecycleEvent.SessionStarted, Clear); EnumEventSystem.Global.Register(GameLifecycleEvent.SessionEnded, Clear); EnumEventSystem.Global.Register(EventEnum.NextYarn, Clear); } public void Unbind() { EnumEventSystem.Global.UnRegister(StorageEvent.VariableSet, OnVariableSet); EnumEventSystem.Global.UnRegister(DialogEventEnum.OptionSelected, OnOptionSelected); EnumEventSystem.Global.UnRegister(DialogEventEnum.OptionShow, OnOptionShow); EnumEventSystem.Global.UnRegister(GameLifecycleEvent.SessionStarted, Clear); EnumEventSystem.Global.UnRegister(GameLifecycleEvent.SessionEnded, Clear); EnumEventSystem.Global.UnRegister(EventEnum.NextYarn, Clear); } public (bool isTrackable, bool isExplored) Evaluate( string lineId, IReadOnlyList metadata, string currentNodeName, IReadOnlyList nodeTags) { if (string.IsNullOrEmpty(lineId)) { return (false, false); } ParseMetadata(metadata, out var forceTrack, out var forceNoTrack, out var deps); var isTrackable = ResolveTrackable(forceTrack, forceNoTrack, deps, nodeTags); if (!isTrackable) { return (false, false); } if (!_records.TryGetValue(lineId, out var rec)) { rec = new OptionRecord { explored = false, deps = deps }; _records[lineId] = rec; RegisterVarIndex(lineId, deps); } else { UpdateDeps(lineId, rec, deps); } return (true, rec.explored); } private void OnOptionSelected(LineInfo info) { if (!info.isOption || string.IsNullOrEmpty(info.lineId)) { return; } if (_records.TryGetValue(info.lineId, out var rec)) { rec.explored = true; _currentExecutingLineId = info.lineId; } } private void OnVariableSet(VariableItem item) { if (item.variableName == null || !_varToOptions.TryGetValue(item.variableName, out var lineIds)) { return; } foreach (var lineId in lineIds) { if (lineId == _currentExecutingLineId) { continue; } if (_records.TryGetValue(lineId, out var rec)) { rec.explored = false; } } } private void OnOptionShow() { _currentExecutingLineId = null; } private void Clear() { _records.Clear(); _varToOptions.Clear(); _currentExecutingLineId = null; } private static void ParseMetadata( IReadOnlyList metadata, out bool forceTrack, out bool forceNoTrack, out string[] deps) { forceTrack = false; forceNoTrack = false; deps = EmptyDeps; if (metadata == null) { return; } for (var i = 0; i < metadata.Count; i++) { var tag = metadata[i]; if (string.IsNullOrEmpty(tag)) { continue; } if (tag == "track") { forceTrack = true; } else if (tag == "notrack") { forceNoTrack = true; } else if (tag.StartsWith("deps:", StringComparison.Ordinal)) { deps = tag.Substring(5).Split(',') .Select(v => v.Trim()) .Where(v => v.Length > 0) .Select(NormalizeVariableName) .ToArray(); } } } private static string NormalizeVariableName(string name) { return name.StartsWith("$") ? name : $"${name}"; } private static bool ResolveTrackable( bool forceTrack, bool forceNoTrack, string[] deps, IReadOnlyList nodeTags) { if (forceNoTrack) { return false; } if (forceTrack || deps.Length > 0) { return true; } if (nodeTags == null) { return false; } for (var i = 0; i < nodeTags.Count; i++) { if (nodeTags[i] == "hub" || nodeTags[i] == "detour") { return true; } } return false; } private void RegisterVarIndex(string lineId, string[] deps) { foreach (var dep in deps) { if (!_varToOptions.TryGetValue(dep, out var lineIds)) { lineIds = new HashSet(); _varToOptions[dep] = lineIds; } lineIds.Add(lineId); } } private void UpdateDeps(string lineId, OptionRecord rec, string[] deps) { if (ReferenceEquals(rec.deps, deps) || (rec.deps.Length == deps.Length && rec.deps.SequenceEqual(deps))) { return; } foreach (var oldDep in rec.deps) { if (_varToOptions.TryGetValue(oldDep, out var oldSet)) { oldSet.Remove(lineId); } } rec.deps = deps; RegisterVarIndex(lineId, deps); } } }