Files
aibis-dream/Assets/Scripts/Dialog System/OptionExploreTracker.cs
T

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(GameLoopEnum.GameStart, Clear);
EnumEventSystem.Global.Register(GameLoopEnum.GameQuit, 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(GameLoopEnum.GameStart, Clear);
EnumEventSystem.Global.UnRegister(GameLoopEnum.GameQuit, 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);
}
}
}