208 lines
8.4 KiB
C#
208 lines
8.4 KiB
C#
using System.Collections.Generic;
|
||
using System.Globalization;
|
||
using UnityEngine;
|
||
using Yarn.Unity;
|
||
|
||
namespace Ichni.Story.Dialogue
|
||
{
|
||
/// <summary>
|
||
/// 自定义 Yarn 变量存储。直接读写 <see cref="StorySaveModule.variables"/>(全局剧情变量),
|
||
/// 让 Yarn 对话中的 <c><<set $var to ...>></c> 与故事树解锁条件共享同一份数据来源,
|
||
/// 并借助存档模块的 ES3 序列化持久化。
|
||
/// <para>变量名沿用 Yarn 约定(带 <c>$</c> 前缀);解锁条件中的变量名也应保持一致。</para>
|
||
/// </summary>
|
||
public class StoryVariableStorage : VariableStorageBehaviour
|
||
{
|
||
// GameSaveManager 尚未就绪(编辑器早期 / 场景未初始化)时的兜底容器
|
||
private readonly StoryVariablesSave _fallback = new StoryVariablesSave();
|
||
|
||
/// <summary>当前生效的变量容器:优先取存档模块,缺失时用本地兜底容器。</summary>
|
||
private StoryVariablesSave Variables
|
||
{
|
||
get
|
||
{
|
||
if (GameSaveManager.instance != null && GameSaveManager.instance.StorySaveModule != null)
|
||
return GameSaveManager.instance.StorySaveModule.variables;
|
||
return _fallback;
|
||
}
|
||
}
|
||
|
||
// ── IVariableStorage 实现 ────────────────────────────────────────────────
|
||
|
||
public override bool TryGetValue<T>(string variableName, out T result)
|
||
{
|
||
StoryVariablesSave vars = Variables;
|
||
|
||
if (vars.floatVariables.TryGetValue(variableName, out float f))
|
||
return TryConvert(f, out result);
|
||
|
||
if (vars.boolVariables.TryGetValue(variableName, out bool b))
|
||
return TryConvert(b, out result);
|
||
|
||
if (vars.stringVariables.TryGetValue(variableName, out string s))
|
||
return TryConvert(s, out result);
|
||
|
||
result = default;
|
||
return false;
|
||
}
|
||
|
||
public override void SetValue(string variableName, string stringValue)
|
||
{
|
||
RemoveStaleEntries(variableName, keepFloat: false, keepString: true, keepBool: false);
|
||
Variables.stringVariables[variableName] = stringValue;
|
||
NotifyVariableChanged(variableName, stringValue);
|
||
}
|
||
|
||
public override void SetValue(string variableName, float floatValue)
|
||
{
|
||
RemoveStaleEntries(variableName, keepFloat: true, keepString: false, keepBool: false);
|
||
Variables.floatVariables[variableName] = floatValue;
|
||
NotifyVariableChanged(variableName, floatValue);
|
||
}
|
||
|
||
public override void SetValue(string variableName, bool boolValue)
|
||
{
|
||
RemoveStaleEntries(variableName, keepFloat: false, keepString: false, keepBool: true);
|
||
Variables.boolVariables[variableName] = boolValue;
|
||
NotifyVariableChanged(variableName, boolValue);
|
||
}
|
||
|
||
public override bool Contains(string variableName)
|
||
{
|
||
StoryVariablesSave vars = Variables;
|
||
return vars.floatVariables.ContainsKey(variableName)
|
||
|| vars.boolVariables.ContainsKey(variableName)
|
||
|| vars.stringVariables.ContainsKey(variableName);
|
||
}
|
||
|
||
public override void Clear()
|
||
{
|
||
StoryVariablesSave vars = Variables;
|
||
vars.floatVariables.Clear();
|
||
vars.stringVariables.Clear();
|
||
vars.boolVariables.Clear();
|
||
}
|
||
|
||
public override void SetAllVariables(
|
||
Dictionary<string, float> floats,
|
||
Dictionary<string, string> strings,
|
||
Dictionary<string, bool> bools,
|
||
bool clear = true)
|
||
{
|
||
StoryVariablesSave vars = Variables;
|
||
|
||
if (clear)
|
||
{
|
||
vars.floatVariables.Clear();
|
||
vars.stringVariables.Clear();
|
||
vars.boolVariables.Clear();
|
||
}
|
||
|
||
if (floats != null)
|
||
foreach (KeyValuePair<string, float> kv in floats) vars.floatVariables[kv.Key] = kv.Value;
|
||
if (strings != null)
|
||
foreach (KeyValuePair<string, string> kv in strings) vars.stringVariables[kv.Key] = kv.Value;
|
||
if (bools != null)
|
||
foreach (KeyValuePair<string, bool> kv in bools) vars.boolVariables[kv.Key] = kv.Value;
|
||
}
|
||
|
||
public override (Dictionary<string, float> FloatVariables,
|
||
Dictionary<string, string> StringVariables,
|
||
Dictionary<string, bool> BoolVariables) GetAllVariables()
|
||
{
|
||
StoryVariablesSave vars = Variables;
|
||
return (
|
||
new Dictionary<string, float>(vars.floatVariables),
|
||
new Dictionary<string, string>(vars.stringVariables),
|
||
new Dictionary<string, bool>(vars.boolVariables)
|
||
);
|
||
}
|
||
|
||
// ── 供故事树解锁条件复用的便捷读取 ────────────────────────────────────────
|
||
|
||
/// <summary>读取变量的整数近似值(float 四舍五入、bool 取 0/1);不存在返回 0。</summary>
|
||
public int GetIntVariable(string variableName)
|
||
{
|
||
StoryVariablesSave vars = Variables;
|
||
if (vars.floatVariables.TryGetValue(variableName, out float f))
|
||
return Mathf.RoundToInt(f);
|
||
if (vars.boolVariables.TryGetValue(variableName, out bool b))
|
||
return b ? 1 : 0;
|
||
return 0;
|
||
}
|
||
|
||
/// <summary>该变量是否存在于任一类型的存储中。</summary>
|
||
public bool HasVariable(string variableName) => Contains(variableName);
|
||
|
||
// ── 内部 ────────────────────────────────────────────────────────────────
|
||
|
||
// 当变量被重新赋值为其它类型时,从旧类型字典移除,避免同名变量残留多份
|
||
private void RemoveStaleEntries(string name, bool keepFloat, bool keepString, bool keepBool)
|
||
{
|
||
StoryVariablesSave vars = Variables;
|
||
if (!keepFloat) vars.floatVariables.Remove(name);
|
||
if (!keepString) vars.stringVariables.Remove(name);
|
||
if (!keepBool) vars.boolVariables.Remove(name);
|
||
}
|
||
|
||
// 将存储中的原始值转换为 Yarn 请求的类型(float / bool / string / int 之间互转)
|
||
private static bool TryConvert<T>(object value, out T result)
|
||
{
|
||
if (value is T typed)
|
||
{
|
||
result = typed;
|
||
return true;
|
||
}
|
||
|
||
try
|
||
{
|
||
if (typeof(T) == typeof(float) || typeof(T) == typeof(double) || typeof(T) == typeof(int))
|
||
{
|
||
float f = value switch
|
||
{
|
||
float fv => fv,
|
||
bool bv => bv ? 1f : 0f,
|
||
string sv => float.TryParse(sv, NumberStyles.Any, CultureInfo.InvariantCulture, out float p) ? p : 0f,
|
||
_ => 0f
|
||
};
|
||
result = (T)System.Convert.ChangeType(f, typeof(T), CultureInfo.InvariantCulture);
|
||
return true;
|
||
}
|
||
|
||
if (typeof(T) == typeof(bool))
|
||
{
|
||
bool b = value switch
|
||
{
|
||
bool bv => bv,
|
||
float fv => fv != 0f,
|
||
string sv => bool.TryParse(sv, out bool p) && p,
|
||
_ => false
|
||
};
|
||
result = (T)(object)b;
|
||
return true;
|
||
}
|
||
|
||
if (typeof(T) == typeof(string))
|
||
{
|
||
string s = value switch
|
||
{
|
||
string sv => sv,
|
||
float fv => fv.ToString(CultureInfo.InvariantCulture),
|
||
bool bv => bv.ToString(),
|
||
_ => value != null ? value.ToString() : string.Empty
|
||
};
|
||
result = (T)(object)s;
|
||
return true;
|
||
}
|
||
}
|
||
catch
|
||
{
|
||
// 转换失败时落到下方失败分支
|
||
}
|
||
|
||
result = default;
|
||
return false;
|
||
}
|
||
}
|
||
}
|