Created
October 4, 2025 05:06
-
-
Save ChrisPritchard/88e285e700ea1bedcd4a6bd73ba0cedb to your computer and use it in GitHub Desktop.
An example config loader node script in Godot (C#) making use of my yamlrecords project
This file contains hidden or 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
| [Tool] | |
| public partial class ConfigLoader : Node | |
| { | |
| public static GameConfig Config { get; private set; } | |
| private string config_path; | |
| [Export(PropertyHint.File, hintString: "*.yml")] | |
| public string ConfigPath | |
| { | |
| get => config_path; set | |
| { | |
| config_path = value; | |
| if (!string.IsNullOrEmpty(config_path)) | |
| TryLoad(value); | |
| } | |
| } | |
| private readonly List<string> errors = []; | |
| public override void _Ready() | |
| { | |
| if (Engine.IsEditorHint()) | |
| return; | |
| if (string.IsNullOrEmpty(config_path)) | |
| HardFail("the config file path must be set"); | |
| else if (!TryLoad(config_path)) | |
| HardFail("failed to load config file:\n" + string.Join('\n', errors)); | |
| } | |
| private void HardFail(string message) | |
| { | |
| GD.PrintErr(message); | |
| GetTree().Quit(1); | |
| } | |
| private bool TryLoad(string value) | |
| { | |
| errors.Clear(); | |
| try | |
| { | |
| using (var config_yml = FileAccess.Open(value, FileAccess.ModeFlags.Read)) | |
| Config = YamlRecords.Deserialize<GameConfig>(config_yml.GetAsText()); | |
| return CheckKeys(); | |
| } | |
| catch (Exception ex) | |
| { | |
| errors.Add($"config parsing error: {ex.Message}"); | |
| GD.PrintRich($"[color=red]config parsing error: {ex.Message}[color=white]"); | |
| UpdateConfigurationWarnings(); | |
| return false; | |
| } | |
| } | |
| private bool CheckKeys() | |
| { | |
| var unknown_cards = Config.StartingCards.Where(c => !Config.CardTypes.ContainsKey(c)).Distinct().ToArray(); | |
| if (unknown_cards.Length > 0) | |
| errors.Add("the following starting cards do not exist in card types: " + string.Join(", ", unknown_cards)); | |
| var unknown_flows = Config.StartingFlows.Where(c => !Config.GameFlows.ContainsKey(c)).Distinct().ToArray(); | |
| if (unknown_flows.Length > 0) | |
| errors.Add("the following starting flows do not exist in game flows: " + string.Join(", ", unknown_flows)); | |
| foreach (var flow in Config.GameFlows) | |
| { | |
| var flow_key = flow.Key; | |
| var flow_body = flow.Value; | |
| if (flow_body.States.Count == 0) | |
| { | |
| errors.Add($"flow {flow_key} has no states defined"); | |
| continue; | |
| } | |
| if (string.IsNullOrEmpty(flow_body.StartState) && flow_body.States.Count > 1) | |
| errors.Add($"flow {flow_key} does not have a start state set, but has multiple states"); | |
| foreach (var state in flow_body.States) | |
| { | |
| var state_key = state.Key; | |
| var state_body = state.Value; | |
| if (state_body.Variants.Count == 0) | |
| { | |
| errors.Add($"state {state_key} in flow {flow_key} has no variants defined"); | |
| continue; | |
| } | |
| if (string.IsNullOrEmpty(state_body.DefaultVariant) && state_body.Variants.Count > 1) | |
| errors.Add($"state {state_key} in flow {flow_key} does not have a default variant set, but has multiple variants"); | |
| // todo, validate all actions | |
| } | |
| } | |
| if (errors.Count == 0) | |
| return true; | |
| foreach (var error in errors) | |
| GD.PrintRich($"[color=red]config parsing error: {error}[color=white]"); | |
| UpdateConfigurationWarnings(); | |
| return false; | |
| } | |
| public override string[] _GetConfigurationWarnings() => string.IsNullOrEmpty(config_path) ? ["the config file path must be set"] : [.. errors]; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment