237 lines
7.1 KiB
C#
237 lines
7.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using AibisDream.Framework;
|
|
|
|
namespace AibisDream
|
|
{
|
|
/// <summary>
|
|
/// 选项已探索状态追踪(内存态,由 OptionView 私有持有)。
|
|
/// </summary>
|
|
public class OptionExploreTracker
|
|
{
|
|
private sealed class OptionRecord
|
|
{
|
|
public bool explored;
|
|
public string[] deps = Array.Empty<string>();
|
|
}
|
|
|
|
private static readonly string[] EmptyDeps = Array.Empty<string>();
|
|
|
|
private readonly Dictionary<string, OptionRecord> _records = new();
|
|
private readonly Dictionary<string, HashSet<string>> _varToOptions = new();
|
|
private string _currentExecutingLineId;
|
|
|
|
public void Bind()
|
|
{
|
|
EnumEventSystem.Global.Register<StorageEvent, VariableItem>(StorageEvent.VariableSet, OnVariableSet);
|
|
EnumEventSystem.Global.Register<DialogEventEnum, LineInfo>(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, VariableItem>(StorageEvent.VariableSet, OnVariableSet);
|
|
EnumEventSystem.Global.UnRegister<DialogEventEnum, LineInfo>(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<string> metadata,
|
|
string currentNodeName,
|
|
IReadOnlyList<string> 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<string> 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<string> 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<string>();
|
|
_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);
|
|
}
|
|
}
|
|
}
|