Created
May 25, 2024 20:58
-
-
Save dogboydog/dcc20253b7dec6f1cb75672394a7875b to your computer and use it in GitHub Desktop.
Custom variable storage for YarnSpinner-Godot . Variables that start with `$_` are saved to a separate dictionary, this is intended for variables that don't need to be saved to disk.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Globalization; | |
using Godot; | |
using YarnSpinnerGodot; | |
/// <summary> | |
/// Interface to YarnSpinner that allows getting and setting of save data from | |
/// dialogue. Also handles temporary variables that aren't committed to save data | |
/// in a separate dictionary | |
/// </summary> | |
public partial class ExampleYarnVariableStorage : VariableStorageBehaviour { | |
private const string TEMP_VAR_PREFIX = "_"; | |
public override void _Ready() { | |
Initialize(); | |
} | |
/// <summary> | |
/// Stores values with keys prefixed by tempPrefix that are | |
/// not saved to save data but can be used by yarn scripts | |
/// </summary> | |
private Dictionary<string, string> _tempVariableStorage = new(); | |
/// <summary> | |
/// Try to get the value of a save data variable. Used by YarnSpinner. | |
/// </summary> | |
/// <param name="variableName">The name of the variable from YarnSpinner</param> | |
/// <param name="result">the value from savedata if any</param> | |
/// <typeparam name="T"></typeparam> | |
/// <returns>true if the variable was found</returns> | |
public override bool TryGetValue<T>(string variableName, out T result) { | |
variableName = normalizeVariableName(variableName); | |
var varType = typeof(T); | |
string value = null; | |
if (variableName.StartsWith(TEMP_VAR_PREFIX)) { | |
Log.Info( | |
$"Reading variable {variableName} ({varType}) from temporary variable storage."); | |
if (!_tempVariableStorage.ContainsKey(variableName)) { | |
Log.Info($"No temporary variable named {variableName} "); | |
result = default; | |
return false; | |
} | |
value = _tempVariableStorage[variableName]; | |
} | |
else { | |
var save = GameState.LoadedSave; | |
Log.Info($"Reading variable {variableName} ({varType}) from savedata."); | |
if (save == null || !save.ContainsKey(variableName)) { | |
Log.Info($"No variable named {variableName} "); | |
result = default; | |
return false; | |
} | |
value = save[variableName]; | |
} | |
Log.Info($"Found variable {variableName}={value}"); | |
if (typeof(T) == typeof(IConvertible)) { | |
result = (T) (object) value; | |
return true; | |
} | |
if (typeof(T) == typeof(bool) && bool.TryParse(value, out var boolResult)) { | |
result = (T) (object) boolResult; | |
return true; | |
} | |
if (typeof(T) == typeof(float) && float.TryParse(value, out var floatResult)) { | |
result = (T) (object) floatResult; | |
return true; | |
} | |
if (typeof(T) == typeof(string)) { | |
// string type default | |
result = (T) (object) value; | |
return true; | |
} | |
result = default; | |
return false; | |
} | |
/// <summary> | |
/// Set a string type variable in the save data | |
/// Exposed for YarnSpinner | |
/// </summary> | |
/// <param name="variableName">the name of the savedata variable to set</param> | |
/// <param name="stringValue">string value for the variable</param> | |
public override void SetValue(string variableName, string stringValue) { | |
SetValue(variableName, stringValue, "string"); | |
} | |
/// <summary> | |
/// Set a nboolean type variable in the save data | |
/// Exposed for YarnSpinner | |
/// </summary> | |
/// <param name="variableName">the name of the savedata variable to set</param> | |
/// <param name="boolValue">bool value for the variable</param> | |
public override void SetValue(string variableName, bool boolValue) { | |
SetValue(variableName, boolValue.ToString(CultureInfo.InvariantCulture), "bool"); | |
} | |
/// <summary> | |
/// Set a float type variable in the save data | |
/// Exposed for YarnSpinner | |
/// </summary> | |
/// <param name="variableName">the name of the savedata variable to set</param> | |
/// <param name="floatValue">float value for the variable</param> | |
public override void SetValue(string variableName, float floatValue) { | |
SetValue(variableName, floatValue.ToString(CultureInfo.InvariantCulture), | |
"float"); | |
} | |
/// <summary> | |
/// Common setter for values | |
/// </summary> | |
/// <param name="variableName">the name of the variable to set </param> | |
/// <param name="variableValue">the value to set the variable to</param> | |
/// <param name="typeName">variable type for logging</param> | |
private void SetValue(string variableName, string variableValue, string typeName) { | |
variableName = normalizeVariableName(variableName); | |
if (variableName.StartsWith(TEMP_VAR_PREFIX)) { | |
Log.Info( | |
$"Setting _tempVariableStorage variable {variableName}({typeName})={variableValue}"); | |
_tempVariableStorage[variableName] = variableValue; | |
} | |
else { | |
Log.Info( | |
$"Setting {nameof(SaveData)} variable {variableName}({typeName})={variableValue}"); | |
if (GameState.LoadedSave == null) { | |
_queuedValues[variableName] = variableValue; | |
} | |
else { | |
GameState.LoadedSave[variableName] = variableValue; | |
} | |
} | |
} | |
private Dictionary<string, string> _queuedValues = new(); | |
/// <summary> | |
/// YarnSpinner upgrade uses SetValue to set default values. | |
/// If this happens before the GameManager is started, we queue the default values to be | |
/// set when GameManager is ready. | |
/// </summary> | |
/// <returns></returns> | |
private async void Initialize() { | |
await Wait.Until(() => GameState.LoadedSave != null); | |
if (!IsInstanceValid(this)) { | |
return; | |
} | |
foreach (var entry in _queuedValues) { | |
// only set the default if the savedata doesn't already have a value | |
if (!Contains(entry.Key)) { | |
SetValue(entry.Key, entry.Value, "queued"); | |
} | |
} | |
_queuedValues.Clear(); | |
} | |
/// <summary> | |
/// Handler for yarnspinner clear() event. | |
/// It's supposed to clear all variables but we don't want to expose that | |
/// to yarn scripts. We have to implement it since it's part of the interface | |
/// </summary> | |
public override void Clear() { | |
Log.Info("Not clearing save data from yarn. "); | |
} | |
/// <summary> | |
/// Returns whether a variable exists in YarnSpinner | |
/// </summary> | |
/// <param name="variableName"></param> | |
/// <returns></returns> | |
public override bool Contains(string variableName) { | |
variableName = normalizeVariableName(variableName); | |
if (variableName.StartsWith(TEMP_VAR_PREFIX)) { | |
return _tempVariableStorage.ContainsKey(variableName); | |
} | |
if (GameState.LoadedSave == null) { | |
return false; | |
} | |
return GameState.LoadedSave.ContainsKey(variableName); | |
} | |
public override void SetAllVariables(Dictionary<string, float> floats, | |
Dictionary<string, string> strings, Dictionary<string, bool> bools, | |
bool clear = true) { | |
foreach (var floatVar in floats.Keys) { | |
SetValue(floatVar, floats[floatVar]); | |
} | |
foreach (var stringVar in strings.Keys) { | |
SetValue(stringVar, floats[stringVar]); | |
} | |
foreach (var boolVar in bools.Keys) { | |
SetValue(boolVar, bools[boolVar]); | |
} | |
} | |
/// <summary> | |
/// This was introduced by a YarnSpinner upgrade. we don't use it | |
/// </summary> | |
/// <returns></returns> | |
public override (Dictionary<string, float>, Dictionary<string, string>, | |
Dictionary<string, bool>) GetAllVariables() { | |
var floats = new Dictionary<string, float>(); | |
var strings = new Dictionary<string, string>(); | |
var bools = new Dictionary<string, bool>(); | |
if (GameState.LoadedSave != null) { | |
foreach (var varNameNoDollarSign in GameState.LoadedSave.GetKeys()) { | |
var varName = $"${varNameNoDollarSign}"; | |
if (TryGetValue<bool>(varName, out var boolValue)) { | |
bools[varName] = boolValue; | |
} | |
else if (TryGetValue<float>(varName, out var floatValue)) { | |
floats[varName] = floatValue; | |
} | |
else { | |
if (TryGetValue<string>(varName, out var stringValue)) { | |
strings[varName] = stringValue; | |
} | |
else { | |
// shouldn't happen... | |
Log.Err( | |
$"Expected to find a string value from the variable {varName}," + | |
$" but {nameof(TryGetValue)} for this variable failed..."); | |
} | |
} | |
} | |
} | |
return (floats, strings, bools); | |
} | |
/// <summary> | |
/// Remove the leading $ in a YS variable name as | |
/// we don't include that in our save data keys | |
/// </summary> | |
/// <param name="varName">the original name of the variable</param> | |
/// <returns></returns> | |
private string normalizeVariableName(string varName) { | |
return varName.StartsWith("$") ? varName[1..] : varName; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment