using System.Collections.Generic; using System.Globalization; using UnityEngine; using Yarn.Unity; namespace Ichni.Story.Dialogue { /// /// 自定义 Yarn 变量存储。直接读写 (全局剧情变量), /// 让 Yarn 对话中的 <<set $var to ...>> 与故事树解锁条件共享同一份数据来源, /// 并借助存档模块的 ES3 序列化持久化。 /// 变量名沿用 Yarn 约定(带 $ 前缀);解锁条件中的变量名也应保持一致。 /// public class StoryVariableStorage : VariableStorageBehaviour { // GameSaveManager 尚未就绪(编辑器早期 / 场景未初始化)时的兜底容器 private readonly StoryVariablesSave _fallback = new StoryVariablesSave(); /// 当前生效的变量容器:优先取存档模块,缺失时用本地兜底容器。 private StoryVariablesSave Variables { get { if (GameSaveManager.instance != null && GameSaveManager.instance.StorySaveModule != null) return GameSaveManager.instance.StorySaveModule.variables; return _fallback; } } // ── IVariableStorage 实现 ──────────────────────────────────────────────── public override bool TryGetValue(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 floats, Dictionary strings, Dictionary bools, bool clear = true) { StoryVariablesSave vars = Variables; if (clear) { vars.floatVariables.Clear(); vars.stringVariables.Clear(); vars.boolVariables.Clear(); } if (floats != null) foreach (KeyValuePair kv in floats) vars.floatVariables[kv.Key] = kv.Value; if (strings != null) foreach (KeyValuePair kv in strings) vars.stringVariables[kv.Key] = kv.Value; if (bools != null) foreach (KeyValuePair kv in bools) vars.boolVariables[kv.Key] = kv.Value; } public override (Dictionary FloatVariables, Dictionary StringVariables, Dictionary BoolVariables) GetAllVariables() { StoryVariablesSave vars = Variables; return ( new Dictionary(vars.floatVariables), new Dictionary(vars.stringVariables), new Dictionary(vars.boolVariables) ); } // ── 供故事树解锁条件复用的便捷读取 ──────────────────────────────────────── /// 读取变量的整数近似值(float 四舍五入、bool 取 0/1);不存在返回 0。 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; } /// 该变量是否存在于任一类型的存储中。 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(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; } } }