Skip to content

Instantly share code, notes, and snippets.

@dogboydog
Created May 25, 2024 20:58
Show Gist options
  • Save dogboydog/dcc20253b7dec6f1cb75672394a7875b to your computer and use it in GitHub Desktop.
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.
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