From 72079044bde4fe06ec5df5948d3532cc882c028b Mon Sep 17 00:00:00 2001 From: kziolkowski Date: Tue, 18 Nov 2025 18:30:17 +0100 Subject: [PATCH 01/18] :construction: WIP implementing save and load for the inventory --- .../Common/Inventory/InventoryInstance.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/scripts/CSharp/Common/Inventory/InventoryInstance.cs b/scripts/CSharp/Common/Inventory/InventoryInstance.cs index d1c7b50..3ec2ba0 100644 --- a/scripts/CSharp/Common/Inventory/InventoryInstance.cs +++ b/scripts/CSharp/Common/Inventory/InventoryInstance.cs @@ -3,6 +3,9 @@ using System; using Godot; using System.Collections.Generic; using System.Linq; +using Babushka.scripts.CSharp.Common.Farming; +using Babushka.scripts.CSharp.Common.Savegame; +using Godot.Collections; namespace Babushka.scripts.CSharp.Common.Inventory; @@ -17,6 +20,9 @@ public partial class InventoryInstance : Node [Signal] public delegate void InventoryContentsChangedEventHandler(); + private const string SCENE_NAME = "inventory"; + private const string ID = "instace"; + /// /// The total amount of Inventoryslots in the inventory (empty and occupied). /// @@ -156,4 +162,51 @@ public partial class InventoryInstance : Node { return items.All(HasItems); } + + #region SAVE AND LOAD + + public void UpdateSaveData() + { + var saveData = new SaveData(); + + saveData.SceneName = SCENE_NAME; + saveData.Id = ID; + var payloadData = new Godot.Collections.Dictionary(); + + for (int i = 0; i < _slots.Count; i++) + { + if (!_slots[i].IsEmpty()) + { + string key = i.ToString(); + string[] value = new string[2]; + value[0] = _slots[i].itemInstance.blueprint.ResourcePath; + value[1] = _slots[i].itemInstance.amount.ToString(); + payloadData.Add(key,value); + } + } + + saveData.JsonPayload = Json.Stringify(payloadData, indent: "\t"); + + SavegameService.AppendSave(saveData); + } + + public void LoadFromSaveData() + { + var sceneName = SCENE_NAME; + var id = ID; + string jsonPayload = SavegameService.GetSaveData(sceneName, id); + Godot.Collections.Dictionary save = Json.ParseString(jsonPayload).AsGodotDictionary(); + + if (save.Count > 0) + { + for (int i = 0; i < _slots.Count; i++) + { + if (save.TryGetValue(i.ToString(), out Variant inventoryItem)) + { + // todo: we have the resource paths and amounts, but how do we get to ItemResources and ItemInstances? + } + } + } + } + #endregion } -- 2.36.3 From 2a4425b9dbe89e4a8fda8ecc4ea25920dd25e9a4 Mon Sep 17 00:00:00 2001 From: kziolkowski Date: Wed, 19 Nov 2025 15:18:33 +0100 Subject: [PATCH 02/18] :sparkles: Added Save and load support to the inventory --- .../Common/Inventory/InventoryInstance.cs | 24 +++++++++++++++---- .../Common/Inventory/InventoryManager.cs | 4 +++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/scripts/CSharp/Common/Inventory/InventoryInstance.cs b/scripts/CSharp/Common/Inventory/InventoryInstance.cs index 3ec2ba0..5945ff7 100644 --- a/scripts/CSharp/Common/Inventory/InventoryInstance.cs +++ b/scripts/CSharp/Common/Inventory/InventoryInstance.cs @@ -3,9 +3,7 @@ using System; using Godot; using System.Collections.Generic; using System.Linq; -using Babushka.scripts.CSharp.Common.Farming; using Babushka.scripts.CSharp.Common.Savegame; -using Godot.Collections; namespace Babushka.scripts.CSharp.Common.Inventory; @@ -48,6 +46,19 @@ public partial class InventoryInstance : Node } } + public override void _EnterTree() + { + LoadFromSaveData(); + InventoryContentsChanged += UpdateSaveData; + SlotAmountChanged += UpdateSaveData; + } + + public override void _ExitTree() + { + InventoryContentsChanged -= UpdateSaveData; + SlotAmountChanged -= UpdateSaveData; + } + public InventoryActionResult AddItem(ItemInstance newItem) { var result = AddItemAndStackRecursive(newItem, 0); @@ -201,9 +212,14 @@ public partial class InventoryInstance : Node { for (int i = 0; i < _slots.Count; i++) { - if (save.TryGetValue(i.ToString(), out Variant inventoryItem)) + if (save.TryGetValue(i.ToString(), out Variant inventoryItemData)) { - // todo: we have the resource paths and amounts, but how do we get to ItemResources and ItemInstances? + string[] savePayload = inventoryItemData.AsStringArray(); + ItemResource resource = ResourceLoader.Load(savePayload[0]); + int _amount = int.Parse(savePayload[1]); + + ItemInstance instance = new ItemInstance { blueprint = resource, amount = _amount }; + AddItem(instance); } } } diff --git a/scripts/CSharp/Common/Inventory/InventoryManager.cs b/scripts/CSharp/Common/Inventory/InventoryManager.cs index 86778da..36166b1 100644 --- a/scripts/CSharp/Common/Inventory/InventoryManager.cs +++ b/scripts/CSharp/Common/Inventory/InventoryManager.cs @@ -23,7 +23,7 @@ public partial class InventoryManager : Node } } - public InventoryInstance playerInventory = new InventoryInstance(); + public InventoryInstance? playerInventory; private int _currentSelectedSlotIndex = 0; @@ -34,7 +34,9 @@ public partial class InventoryManager : Node public override void _Ready() { + playerInventory = new InventoryInstance(); playerInventory.SlotAmount = 37; + AddChild(playerInventory); } public InventoryActionResult CreateItem( -- 2.36.3 From 2947011a1a5a1685ebb2665091af126661db3134 Mon Sep 17 00:00:00 2001 From: kziolkowski Date: Fri, 21 Nov 2025 17:27:46 +0100 Subject: [PATCH 03/18] working intermediate state of the save system --- scenes/Babushka_scene_farm_outside_2d.tscn | 7 +++++-- scripts/CSharp/Common/Farming/FieldBehaviour2D.cs | 3 +++ .../CSharp/Common/Inventory/InventoryInstance.cs | 3 +++ scripts/CSharp/Common/Savegame/SaveCheats.cs | 15 +++++++++++++++ scripts/CSharp/Common/Savegame/SaveCheats.cs.uid | 1 + scripts/CSharp/Common/Savegame/SavegameService.cs | 6 ++++++ 6 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 scripts/CSharp/Common/Savegame/SaveCheats.cs create mode 100644 scripts/CSharp/Common/Savegame/SaveCheats.cs.uid diff --git a/scenes/Babushka_scene_farm_outside_2d.tscn b/scenes/Babushka_scene_farm_outside_2d.tscn index fbf8c7e..fcf00fc 100644 --- a/scenes/Babushka_scene_farm_outside_2d.tscn +++ b/scenes/Babushka_scene_farm_outside_2d.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=116 format=3 uid="uid://gigb28qk8t12"] +[gd_scene load_steps=117 format=3 uid="uid://gigb28qk8t12"] [ext_resource type="PackedScene" uid="uid://c25udixd5m6l0" path="res://prefabs/characters/Player2D.tscn" id="1_7wfwe"] [ext_resource type="Texture2D" uid="uid://8sr11ex30n0m" path="res://art/mockups/Kenney_Backgrounds/Samples/uncolored_hills.png" id="2_7b2ri"] @@ -38,7 +38,6 @@ [ext_resource type="Resource" uid="uid://d1uuxp1lp4aro" path="res://resources/items/tomato_seed.tres" id="35_64mdn"] [ext_resource type="Texture2D" uid="uid://65e44yde224q" path="res://art/farm/Babushka_house_01.png" id="36_e5b7x"] [ext_resource type="Resource" uid="uid://duq7tshxv6uhp" path="res://resources/items/beet_seed.tres" id="36_fv1t2"] -[ext_resource type="Texture2D" uid="uid://cyyxqmphcrjj" path="res://art/farm/farming/farmobjekte/tomaten/tomaten_template.png" id="36_l7ekk"] [ext_resource type="AudioStream" uid="uid://cfqg50am0swb7" path="res://audio/Music/Farming_90BPM_69Bars_Loop.wav" id="37_8ey8m"] [ext_resource type="AudioStream" uid="uid://dku1rq5cocisg" path="res://audio/Music/Farming_90BPM_69Bars.wav" id="37_di1ed"] [ext_resource type="Shader" uid="uid://braevmqauoek7" path="res://shader/swaying_plant.gdshader" id="37_taxvr"] @@ -79,6 +78,7 @@ [ext_resource type="Script" uid="uid://dih1b0opgc3f7" path="res://scripts/GdScript/dialogic_start_specific.gd" id="77_l7ekk"] [ext_resource type="Resource" uid="uid://tt3d166mntmi" path="res://resources/low code/farming/var_sceneNameProvider.tres" id="77_xcwle"] [ext_resource type="PackedScene" uid="uid://b1d2e7ely6hyw" path="res://prefabs/farm/base_field.tscn" id="78_xcwle"] +[ext_resource type="Script" uid="uid://iquhbkr7pqeg" path="res://scripts/CSharp/Common/Savegame/SaveCheats.cs" id="79_065st"] [sub_resource type="ShaderMaterial" id="ShaderMaterial_wtdui"] shader = ExtResource("13_7p0hq") @@ -2509,6 +2509,9 @@ script = ExtResource("76_l7ekk") _variableResource = ExtResource("77_xcwle") _payloadToSet = "farmOutside" +[node name="SaveGameCheat" type="Node" parent="."] +script = ExtResource("79_065st") + [connection signal="FilledWateringCan" from="YSorted/Vesna" to="Audio/SFX/FillWater SFX2" method="PlayOneShot"] [connection signal="InteractedTool" from="YSorted/Well/InteractionArea" to="YSorted/Vesna" method="TryFillWateringCan"] [connection signal="SuccessfulPickUp" from="YSorted/CanGenericPickup" to="YSorted/Vesna" method="HandlePickUp"] diff --git a/scripts/CSharp/Common/Farming/FieldBehaviour2D.cs b/scripts/CSharp/Common/Farming/FieldBehaviour2D.cs index 80a1254..6a53ccb 100644 --- a/scripts/CSharp/Common/Farming/FieldBehaviour2D.cs +++ b/scripts/CSharp/Common/Farming/FieldBehaviour2D.cs @@ -216,6 +216,9 @@ public partial class FieldBehaviour2D : Sprite2D var sceneName = _sceneKeyProvider.Payload.AsString(); var id = SaveId + _fieldIndex.Payload.AsString(); string jsonPayload = SavegameService.GetSaveData(sceneName, id); + if (string.IsNullOrEmpty(jsonPayload)) + return; + Dictionary save = Json.ParseString(jsonPayload).AsGodotDictionary(); if (save.Count > 0) diff --git a/scripts/CSharp/Common/Inventory/InventoryInstance.cs b/scripts/CSharp/Common/Inventory/InventoryInstance.cs index 5945ff7..64530b6 100644 --- a/scripts/CSharp/Common/Inventory/InventoryInstance.cs +++ b/scripts/CSharp/Common/Inventory/InventoryInstance.cs @@ -206,6 +206,9 @@ public partial class InventoryInstance : Node var sceneName = SCENE_NAME; var id = ID; string jsonPayload = SavegameService.GetSaveData(sceneName, id); + if (string.IsNullOrEmpty(jsonPayload)) + return; + Godot.Collections.Dictionary save = Json.ParseString(jsonPayload).AsGodotDictionary(); if (save.Count > 0) diff --git a/scripts/CSharp/Common/Savegame/SaveCheats.cs b/scripts/CSharp/Common/Savegame/SaveCheats.cs new file mode 100644 index 0000000..0129b8c --- /dev/null +++ b/scripts/CSharp/Common/Savegame/SaveCheats.cs @@ -0,0 +1,15 @@ +using Godot; + +namespace Babushka.scripts.CSharp.Common.Savegame; + +[Tool] +public partial class SaveCheats : Node +{ + [ExportToolButton("ResetSave")] Callable _raiseAction => Callable.From(Reset); + + public void Reset() + { + SavegameService.Reset(); + } + +} \ No newline at end of file diff --git a/scripts/CSharp/Common/Savegame/SaveCheats.cs.uid b/scripts/CSharp/Common/Savegame/SaveCheats.cs.uid new file mode 100644 index 0000000..125babd --- /dev/null +++ b/scripts/CSharp/Common/Savegame/SaveCheats.cs.uid @@ -0,0 +1 @@ +uid://iquhbkr7pqeg diff --git a/scripts/CSharp/Common/Savegame/SavegameService.cs b/scripts/CSharp/Common/Savegame/SavegameService.cs index d3a3fef..546e1f6 100644 --- a/scripts/CSharp/Common/Savegame/SavegameService.cs +++ b/scripts/CSharp/Common/Savegame/SavegameService.cs @@ -94,4 +94,10 @@ public static class SavegameService _loaded = true; } + + public static void Reset() + { + SaveDatas = new (); + Save(); + } } \ No newline at end of file -- 2.36.3 From ce29711614296403b1b3f2219084188818573f73 Mon Sep 17 00:00:00 2001 From: kziolkowski Date: Fri, 21 Nov 2025 20:33:52 +0100 Subject: [PATCH 04/18] :sparkle: Added Settings classes to save and load local configurations --- project.godot | 1 + scenes/Babushka_scene_bootstrap.tscn | 7 +- .../CSharp/Common/Savegame/SettingsData.cs | 31 +++++ .../Common/Savegame/SettingsData.cs.uid | 1 + .../Common/Savegame/SettingsSaveController.cs | 108 ++++++++++++++++++ .../Savegame/SettingsSaveController.cs.uid | 1 + .../Common/Savegame/WindowSettingsSync.cs | 101 ++++++++++++++++ .../Common/Savegame/WindowSettingsSync.cs.uid | 1 + scripts/CSharp/Common/SceneTransition.cs | 1 + 9 files changed, 250 insertions(+), 2 deletions(-) create mode 100644 scripts/CSharp/Common/Savegame/SettingsData.cs create mode 100644 scripts/CSharp/Common/Savegame/SettingsData.cs.uid create mode 100644 scripts/CSharp/Common/Savegame/SettingsSaveController.cs create mode 100644 scripts/CSharp/Common/Savegame/SettingsSaveController.cs.uid create mode 100644 scripts/CSharp/Common/Savegame/WindowSettingsSync.cs create mode 100644 scripts/CSharp/Common/Savegame/WindowSettingsSync.cs.uid diff --git a/project.godot b/project.godot index 759b354..2658752 100644 --- a/project.godot +++ b/project.godot @@ -34,6 +34,7 @@ Signal_Debugger="*res://addons/SignalVisualizer/Debugger/SignalDebugger.gd" FightWorldAutoload="*res://prefabs/fight/fight_world_autoload.tscn" FieldService="*res://scripts/CSharp/Common/Farming/FieldService.cs" SaveGameManager="*res://scripts/CSharp/Common/Savegame/SaveGameManager.cs" +SettingsSaveController="*res://scripts/CSharp/Common/Savegame/SettingsSaveController.cs" [dialogic] diff --git a/scenes/Babushka_scene_bootstrap.tscn b/scenes/Babushka_scene_bootstrap.tscn index 6ae60a7..f567a1b 100644 --- a/scenes/Babushka_scene_bootstrap.tscn +++ b/scenes/Babushka_scene_bootstrap.tscn @@ -1,10 +1,13 @@ -[gd_scene load_steps=2 format=3 uid="uid://bopv10dqm1knc"] +[gd_scene load_steps=3 format=3 uid="uid://bopv10dqm1knc"] [ext_resource type="PackedScene" uid="uid://c6wnoif01ltld" path="res://scenes/Babushka_scene_startMenu.tscn" id="1_15ton"] +[ext_resource type="Script" uid="uid://bbp0dyddwdbl8" path="res://scripts/CSharp/Common/Savegame/WindowSettingsSync.cs" id="2_d3jfo"] [node name="BabushkaSceneBootstrap" type="Node2D"] [node name="BabushkaSceneStartMenu" parent="." instance=ExtResource("1_15ton")] -_sceneNamesToLoad = PackedStringArray("res://scenes/Babushka_scene_farm_outside_2d.tscn") [node name="SceneParent" type="Node" parent="."] + +[node name="WindowSettings" type="Node" parent="."] +script = ExtResource("2_d3jfo") diff --git a/scripts/CSharp/Common/Savegame/SettingsData.cs b/scripts/CSharp/Common/Savegame/SettingsData.cs new file mode 100644 index 0000000..d97dd8c --- /dev/null +++ b/scripts/CSharp/Common/Savegame/SettingsData.cs @@ -0,0 +1,31 @@ + +namespace Babushka.scripts.CSharp.Common.Savegame; + +/// +/// Data structure for device-specific settings that should be saved / loaded to disk. +/// Will not be synced across devices. +/// +public class SettingsData +{ + /// + /// To be incremented (and migrated) on modification. + /// + public const int VERSION = 1; + public int version { get; set; } = VERSION; + + public bool IsVersionValid() + { + return VERSION == version; + } + + public double runtimeSeconds { get; set; } + public int windowSizeX { get; set; } = 800; + public int windowSizeY { get; set; } = 600; + public int windowPositionX { get; set; } = 100; + public int windowPositionY { get; set; } = 100; + public bool windowBorderless { get; set; } + public float volumeMaster { get; set; } = 1.0f; + public float volumeFx { get; set; } = 1.0f; + public float volumeMusic { get; set; } = 1.0f; + +} \ No newline at end of file diff --git a/scripts/CSharp/Common/Savegame/SettingsData.cs.uid b/scripts/CSharp/Common/Savegame/SettingsData.cs.uid new file mode 100644 index 0000000..aede861 --- /dev/null +++ b/scripts/CSharp/Common/Savegame/SettingsData.cs.uid @@ -0,0 +1 @@ +uid://ol8xw1ekx0c2 diff --git a/scripts/CSharp/Common/Savegame/SettingsSaveController.cs b/scripts/CSharp/Common/Savegame/SettingsSaveController.cs new file mode 100644 index 0000000..2596ed8 --- /dev/null +++ b/scripts/CSharp/Common/Savegame/SettingsSaveController.cs @@ -0,0 +1,108 @@ +using System; +using System.Text.Json; +using Godot; + +namespace Babushka.scripts.CSharp.Common.Savegame; + +/// +/// Handles the saving and loading of local settings data. +/// +[GlobalClass] +public partial class SettingsSaveController : Node +{ + public static string SETTINGS_FILE_PATH = "user://userSettings.json"; + public static SettingsSaveController Instance; + + public SettingsData? settings = new SettingsData(); + public SettingsData settingsDefault = new SettingsData(); + + public event Action OnSettingsReloaded; + + private bool _loadedData; + private DateTime _readyTime; + + public bool LoadedData => _loadedData; + + public override void _EnterTree() + { + SETTINGS_FILE_PATH = ProjectSettings.GlobalizePath(SETTINGS_FILE_PATH); + _readyTime = DateTime.Now; + Instance = this; + LoadSettings(); + } + + public override void _ExitTree() + { + if (settings != null) + settings.runtimeSeconds += (DateTime.Now - _readyTime).TotalSeconds; + SaveSettings(); + Instance = null; + } + + /// + /// Saves Settings Data onto disk. + /// + public void SaveSettings() + { + try + { + string jsonString = JsonSerializer.Serialize( + settings, + new JsonSerializerOptions() { WriteIndented = true} + ); + + System.IO.File.WriteAllText(SETTINGS_FILE_PATH, jsonString); + } + catch (Exception e) + { + GD.PrintErr("Error Saving Settings:", e); + Console.WriteLine(e); + } + } + + /// + /// Loads Settings data from disk. + /// + public void LoadSettings() + { + _loadedData = false; + + try + { + if (!System.IO.File.Exists(SETTINGS_FILE_PATH)) + { + settings = new SettingsData(); + } + else + { + string jsonString = System.IO.File.ReadAllText(SETTINGS_FILE_PATH); + SettingsData? loadedSettings = JsonSerializer.Deserialize(jsonString); + if (loadedSettings != null && !loadedSettings.IsVersionValid()) + { + _loadedData = false; + } + else + { + settings = loadedSettings; + _loadedData = true; + } + } + } + catch (Exception e) + { + GD.PrintErr("Loading Error:", e); + Console.WriteLine(e); + } + + } + + /// + /// Resets Settings to default. + /// + public void ResetSettings() + { + settings = JsonSerializer.Deserialize(JsonSerializer.Serialize(settingsDefault)); + OnSettingsReloaded?.Invoke(); + } + +} \ No newline at end of file diff --git a/scripts/CSharp/Common/Savegame/SettingsSaveController.cs.uid b/scripts/CSharp/Common/Savegame/SettingsSaveController.cs.uid new file mode 100644 index 0000000..eae620f --- /dev/null +++ b/scripts/CSharp/Common/Savegame/SettingsSaveController.cs.uid @@ -0,0 +1 @@ +uid://cc7gnydmbcft7 diff --git a/scripts/CSharp/Common/Savegame/WindowSettingsSync.cs b/scripts/CSharp/Common/Savegame/WindowSettingsSync.cs new file mode 100644 index 0000000..df1c165 --- /dev/null +++ b/scripts/CSharp/Common/Savegame/WindowSettingsSync.cs @@ -0,0 +1,101 @@ +using Godot; + +namespace Babushka.scripts.CSharp.Common.Savegame; + +/// +/// Tracks important window settings and communicates with the to save/load them. +/// +public partial class WindowSettingsSync : Node +{ + private Window window; + + public override void _Ready() + { + window = GetWindow(); + window.SizeChanged += SaveWindowSize; + + SyncSettings(); + SettingsSaveController.Instance.OnSettingsReloaded += SyncSettings; + } + + + public override void _ExitTree() + { + SaveWindowPosition(); + SaveWindowBorderless(); + SaveWindowSize(); + SettingsSaveController.Instance.SaveSettings(); + } + + /// + /// Tries to get previous settings from settings-savefile, if available. + /// + public void SyncSettings() + { + if (!SettingsSaveController.Instance.LoadedData) + { + SaveWindowPosition(); + SaveWindowSize(); + SaveWindowBorderless(); + return; + } + + SettingsData? settingsData = SettingsSaveController.Instance.settings; + if (settingsData != null) + { + window.Position = new Vector2I(settingsData.windowPositionX, settingsData.windowPositionY); + ValidateWindowPosition(); + + window.Size = new Vector2I(settingsData.windowSizeX, settingsData.windowSizeY); + window.Borderless = settingsData.windowBorderless; + } + } + + private void ValidateWindowPosition() + { + bool validWindowPosition = false; + foreach (Rect2I displayRect in DisplayServer.GetDisplayCutouts()) + { + if (displayRect.HasPoint(window.Position)) + { + validWindowPosition = true; + break; + } + } + + if (!validWindowPosition) + { + window.MoveToCenter(); + SaveWindowPosition(); + } + } + + private void SaveWindowPosition() + { + SettingsData? settingsData = SettingsSaveController.Instance.settings; + + if (settingsData != null) + { + settingsData.windowPositionX = window.Position.X; + settingsData.windowPositionY = window.Position.Y; + } + } + + private void SaveWindowSize() + { + SettingsData? settingsData = SettingsSaveController.Instance.settings; + if (settingsData != null) + { + settingsData.windowSizeX = window.Size.X; + settingsData.windowSizeY = window.Size.Y; + } + } + + private void SaveWindowBorderless() + { + SettingsData? settingsData = SettingsSaveController.Instance.settings; + if (settingsData != null) + settingsData.windowBorderless = window.Borderless; + } + +} \ No newline at end of file diff --git a/scripts/CSharp/Common/Savegame/WindowSettingsSync.cs.uid b/scripts/CSharp/Common/Savegame/WindowSettingsSync.cs.uid new file mode 100644 index 0000000..0382595 --- /dev/null +++ b/scripts/CSharp/Common/Savegame/WindowSettingsSync.cs.uid @@ -0,0 +1 @@ +uid://bbp0dyddwdbl8 diff --git a/scripts/CSharp/Common/SceneTransition.cs b/scripts/CSharp/Common/SceneTransition.cs index 7709acb..1aa3f88 100644 --- a/scripts/CSharp/Common/SceneTransition.cs +++ b/scripts/CSharp/Common/SceneTransition.cs @@ -34,6 +34,7 @@ public partial class SceneTransition : Node public void Quit() { + SettingsSaveController.Instance.SaveSettings(); GetTree().Quit(); } -- 2.36.3 From 4ed6f4e7d9999aeaecd7653870ae0f87187630a5 Mon Sep 17 00:00:00 2001 From: kziolkowski Date: Fri, 21 Nov 2025 21:04:37 +0100 Subject: [PATCH 05/18] Reworked existing save system for user data: Made path steam-compatible, added Versioning and try/catch to save --- scripts/CSharp/Common/Savegame/SaveData.cs | 9 +++++++ .../CSharp/Common/Savegame/SaveGameManager.cs | 5 +++- .../CSharp/Common/Savegame/SavegameService.cs | 26 ++++++++++++++++--- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/scripts/CSharp/Common/Savegame/SaveData.cs b/scripts/CSharp/Common/Savegame/SaveData.cs index 1a7e3a8..a26c1d9 100644 --- a/scripts/CSharp/Common/Savegame/SaveData.cs +++ b/scripts/CSharp/Common/Savegame/SaveData.cs @@ -9,6 +9,15 @@ namespace Babushka.scripts.CSharp.Common.Savegame; [Serializable] public class SaveData { + // for future use in case the structure changes. + public const int VERSION = 1; + public int version { get; set; } = VERSION; + + public bool IsVersionValid() + { + return VERSION == version; + } + public string SceneName; public string Id; public string JsonPayload; diff --git a/scripts/CSharp/Common/Savegame/SaveGameManager.cs b/scripts/CSharp/Common/Savegame/SaveGameManager.cs index 9d53244..3d20162 100644 --- a/scripts/CSharp/Common/Savegame/SaveGameManager.cs +++ b/scripts/CSharp/Common/Savegame/SaveGameManager.cs @@ -7,12 +7,15 @@ namespace Babushka.scripts.CSharp.Common.Savegame; /// public partial class SaveGameManager : Node { + public static string USER_DATA_FILE_PATH = "user://save_data/userData.json"; + public static SaveGameManager? Instance { get; private set; } = null!; public override void _EnterTree() { Instance = this; - + USER_DATA_FILE_PATH = ProjectSettings.GlobalizePath(USER_DATA_FILE_PATH); + SavegameService.SavePath = USER_DATA_FILE_PATH; SavegameService.Load(); } diff --git a/scripts/CSharp/Common/Savegame/SavegameService.cs b/scripts/CSharp/Common/Savegame/SavegameService.cs index 546e1f6..b733da7 100644 --- a/scripts/CSharp/Common/Savegame/SavegameService.cs +++ b/scripts/CSharp/Common/Savegame/SavegameService.cs @@ -9,10 +9,11 @@ namespace Babushka.scripts.CSharp.Common.Savegame; /// Handles the saving and loading of game states. /// Holds the central SaveData object that serves as a temporary SaveFile. /// Automatically writes to disk on every scene change. +/// Uses the Godot Json Serializer, which may implicitly convert integers to floats, so be careful when extending! /// public static class SavegameService { - public static readonly string SavePath = "user://babushka_savegame.json"; + public static string SavePath = ""; public static Dictionary SaveDatas = new (); @@ -72,8 +73,20 @@ public static class SavegameService public static void Save() { string json = Json.Stringify(SaveDatas, indent: "\t"); - using var file = FileAccess.Open(SavePath, FileAccess.ModeFlags.Write); - file.StoreString(json); + + GD.Print(SavePath); + // create cloud directory + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(SavePath) ?? ""); + + try + { + using var file = FileAccess.Open(SavePath, FileAccess.ModeFlags.Write); + file.StoreString(json); + } + catch (Exception e) + { + GD.Print(e.Message); + } } /// @@ -83,8 +96,15 @@ public static class SavegameService { try { + if (!System.IO.File.Exists(SavePath)) + { + SaveDatas = new(); + return; + } + string saveDataJson = FileAccess.GetFileAsString(SavePath); SaveDatas = Json.ParseString(saveDataJson).AsGodotDictionary(); + } catch(Exception e) { -- 2.36.3 From 4bbaab1a2a45d4bf3e5474245613126267b21611 Mon Sep 17 00:00:00 2001 From: kziolkowski Date: Sun, 23 Nov 2025 20:10:42 +0100 Subject: [PATCH 06/18] Created ISaveable interface and made existing saveable classes inherit from it. --- .../CharacterControls/InteractionArea2D.cs | 1 - .../CSharp/Common/Farming/FieldBehaviour2D.cs | 2 +- .../Common/Inventory/InventoryInstance.cs | 2 +- scripts/CSharp/Common/Savegame/ISaveable.cs | 17 +++++++++++++++++ 4 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 scripts/CSharp/Common/Savegame/ISaveable.cs diff --git a/scripts/CSharp/Common/CharacterControls/InteractionArea2D.cs b/scripts/CSharp/Common/CharacterControls/InteractionArea2D.cs index 9a421cd..08aab4d 100644 --- a/scripts/CSharp/Common/CharacterControls/InteractionArea2D.cs +++ b/scripts/CSharp/Common/CharacterControls/InteractionArea2D.cs @@ -1,4 +1,3 @@ -using System; using System.Linq; using Babushka.scripts.CSharp.Common.Services; using Godot; diff --git a/scripts/CSharp/Common/Farming/FieldBehaviour2D.cs b/scripts/CSharp/Common/Farming/FieldBehaviour2D.cs index 6a53ccb..7abbb81 100644 --- a/scripts/CSharp/Common/Farming/FieldBehaviour2D.cs +++ b/scripts/CSharp/Common/Farming/FieldBehaviour2D.cs @@ -13,7 +13,7 @@ namespace Babushka.scripts.CSharp.Common.Farming; /// Defines the behaviour of the field, i.e. interactions, states and effects. /// [GlobalClass] -public partial class FieldBehaviour2D : Sprite2D +public partial class FieldBehaviour2D : Sprite2D, ISaveable { [ExportGroup("Persistence")] [Export] public string SaveId = ""; diff --git a/scripts/CSharp/Common/Inventory/InventoryInstance.cs b/scripts/CSharp/Common/Inventory/InventoryInstance.cs index 64530b6..61f45fe 100644 --- a/scripts/CSharp/Common/Inventory/InventoryInstance.cs +++ b/scripts/CSharp/Common/Inventory/InventoryInstance.cs @@ -7,7 +7,7 @@ using Babushka.scripts.CSharp.Common.Savegame; namespace Babushka.scripts.CSharp.Common.Inventory; -public partial class InventoryInstance : Node +public partial class InventoryInstance : Node, ISaveable { private List _slots = new(); public IReadOnlyList Slots => _slots; diff --git a/scripts/CSharp/Common/Savegame/ISaveable.cs b/scripts/CSharp/Common/Savegame/ISaveable.cs new file mode 100644 index 0000000..060b1b0 --- /dev/null +++ b/scripts/CSharp/Common/Savegame/ISaveable.cs @@ -0,0 +1,17 @@ +namespace Babushka.scripts.CSharp.Common.Savegame; + +/// +/// Defines the behaviour of Nodes that have fields that should save / load to disk. +/// +public interface ISaveable +{ + /// + /// Adds or updates the field data that shall be stored to disk. + /// + public void UpdateSaveData(); + + /// + /// Loads the field data from disk. + /// + public void LoadFromSaveData(); +} \ No newline at end of file -- 2.36.3 From fda2b95b38bee77293f150963c368db87c3684df Mon Sep 17 00:00:00 2001 From: kziolkowski Date: Sun, 23 Nov 2025 21:02:40 +0100 Subject: [PATCH 07/18] Savesystem rework --- .../CharacterControls/InteractionArea2D.cs | 34 ++++++++++++++++++- .../CSharp/Common/Farming/FieldBehaviour2D.cs | 13 ++----- .../Common/Inventory/InventoryInstance.cs | 13 ++----- .../CSharp/Common/Savegame/ISaveable.cs.uid | 1 + .../CSharp/Common/Savegame/SavegameService.cs | 24 +++++++++---- 5 files changed, 56 insertions(+), 29 deletions(-) create mode 100644 scripts/CSharp/Common/Savegame/ISaveable.cs.uid diff --git a/scripts/CSharp/Common/CharacterControls/InteractionArea2D.cs b/scripts/CSharp/Common/CharacterControls/InteractionArea2D.cs index 08aab4d..fc3769a 100644 --- a/scripts/CSharp/Common/CharacterControls/InteractionArea2D.cs +++ b/scripts/CSharp/Common/CharacterControls/InteractionArea2D.cs @@ -1,12 +1,20 @@ using System.Linq; +using Babushka.scripts.CSharp.Common.Savegame; using Babushka.scripts.CSharp.Common.Services; +using Babushka.scripts.CSharp.Low_Code.Variables; using Godot; +using Godot.Collections; namespace Babushka.scripts.CSharp.Common.CharacterControls; -public partial class InteractionArea2D : Node2D +public partial class InteractionArea2D : Node2D, ISaveable { + [ExportGroup("Persistence")] + [Export] public string SaveId = ""; + [Export] private VariableNode _sceneID; + [Export] public VariableResource _sceneKeyProvider; + [ExportGroup("Settings")] [Export] private Area2D _area; [Export] private Label _label; [Export] private bool _active = true; @@ -17,6 +25,7 @@ public partial class InteractionArea2D : Node2D [Export] private int _id = -1; // TODO: remove private Material[] _backupMaterials; + private int _interactionCounter; [Signal] public delegate void InteractedToolEventHandler(int id); // TODO: remove @@ -106,6 +115,7 @@ public partial class InteractionArea2D : Node2D EmitSignal(SignalName.InteractedTool, _id); EmitSignal(SignalName.Interacted); + _interactionCounter++; } } @@ -120,4 +130,26 @@ public partial class InteractionArea2D : Node2D _active = !_active; _label.Hide(); } + + #region SAVE AND LOAD + + public void UpdateSaveData() + { + var payloadData = new Dictionary + { + { "interaction counter", _interactionCounter } + }; + + SavegameService.AppendDataToSave(_sceneKeyProvider.Payload.AsString(), SaveId + _sceneID.Payload.AsString(), payloadData); + } + + public void LoadFromSaveData() + { + var sceneName = _sceneKeyProvider.Payload.AsString(); + var id = SaveId + _sceneID.Payload.AsString(); + + Dictionary save = SavegameService.GetSaveData(sceneName, id); + } + + #endregion } \ No newline at end of file diff --git a/scripts/CSharp/Common/Farming/FieldBehaviour2D.cs b/scripts/CSharp/Common/Farming/FieldBehaviour2D.cs index 7abbb81..ad422da 100644 --- a/scripts/CSharp/Common/Farming/FieldBehaviour2D.cs +++ b/scripts/CSharp/Common/Farming/FieldBehaviour2D.cs @@ -185,10 +185,6 @@ public partial class FieldBehaviour2D : Sprite2D, ISaveable public void UpdateSaveData() { - var saveData = new SaveData(); - - saveData.SceneName = _sceneKeyProvider.Payload.AsString(); - saveData.Id = SaveId + _fieldIndex.Payload.AsString(); var payloadData = new Dictionary { { "field_state", (int)FieldState } @@ -206,20 +202,15 @@ public partial class FieldBehaviour2D : Sprite2D, ISaveable ); } - saveData.JsonPayload = Json.Stringify(payloadData, indent: "\t"); - - SavegameService.AppendSave(saveData); + SavegameService.AppendDataToSave(_sceneKeyProvider.Payload.AsString(), SaveId + _fieldIndex.Payload.AsString(), payloadData); } public void LoadFromSaveData() { var sceneName = _sceneKeyProvider.Payload.AsString(); var id = SaveId + _fieldIndex.Payload.AsString(); - string jsonPayload = SavegameService.GetSaveData(sceneName, id); - if (string.IsNullOrEmpty(jsonPayload)) - return; - Dictionary save = Json.ParseString(jsonPayload).AsGodotDictionary(); + Dictionary save = SavegameService.GetSaveData(sceneName, id); if (save.Count > 0) { diff --git a/scripts/CSharp/Common/Inventory/InventoryInstance.cs b/scripts/CSharp/Common/Inventory/InventoryInstance.cs index 61f45fe..8d235da 100644 --- a/scripts/CSharp/Common/Inventory/InventoryInstance.cs +++ b/scripts/CSharp/Common/Inventory/InventoryInstance.cs @@ -178,10 +178,6 @@ public partial class InventoryInstance : Node, ISaveable public void UpdateSaveData() { - var saveData = new SaveData(); - - saveData.SceneName = SCENE_NAME; - saveData.Id = ID; var payloadData = new Godot.Collections.Dictionary(); for (int i = 0; i < _slots.Count; i++) @@ -196,20 +192,15 @@ public partial class InventoryInstance : Node, ISaveable } } - saveData.JsonPayload = Json.Stringify(payloadData, indent: "\t"); - - SavegameService.AppendSave(saveData); + SavegameService.AppendDataToSave(SCENE_NAME, ID, payloadData); } public void LoadFromSaveData() { var sceneName = SCENE_NAME; var id = ID; - string jsonPayload = SavegameService.GetSaveData(sceneName, id); - if (string.IsNullOrEmpty(jsonPayload)) - return; - Godot.Collections.Dictionary save = Json.ParseString(jsonPayload).AsGodotDictionary(); + Godot.Collections.Dictionary save = SavegameService.GetSaveData(sceneName, id); if (save.Count > 0) { diff --git a/scripts/CSharp/Common/Savegame/ISaveable.cs.uid b/scripts/CSharp/Common/Savegame/ISaveable.cs.uid new file mode 100644 index 0000000..cccea3d --- /dev/null +++ b/scripts/CSharp/Common/Savegame/ISaveable.cs.uid @@ -0,0 +1 @@ +uid://bqapxuprxuj20 diff --git a/scripts/CSharp/Common/Savegame/SavegameService.cs b/scripts/CSharp/Common/Savegame/SavegameService.cs index b733da7..c8504e5 100644 --- a/scripts/CSharp/Common/Savegame/SavegameService.cs +++ b/scripts/CSharp/Common/Savegame/SavegameService.cs @@ -18,13 +18,23 @@ public static class SavegameService public static Dictionary SaveDatas = new (); public static bool _loaded = false; - + + + public static void AppendDataToSave(string scenename, string id, Dictionary payload) + { + var saveData = new SaveData(); + + saveData.SceneName = scenename; + saveData.Id = id; + saveData.JsonPayload = Json.Stringify(payload, indent: "\t"); + AppendSave(saveData); + } /// /// Adds or overwrites an entry in the SaveData dictionary. /// /// - public static void AppendSave(SaveData saveData) + private static void AppendSave(SaveData saveData) { string key = string.Concat(saveData.SceneName, "_", saveData.Id); @@ -46,21 +56,23 @@ public static class SavegameService /// /// /// - public static string GetSaveData(string sceneName, string id) + public static Dictionary GetSaveData(string sceneName, string id) { - string saveData = ""; + Dictionary saveData = new(); string key = string.Concat(sceneName, "_", id); + string saveDataString = ""; if (!_loaded) { GD.Print("SavegameService: SaveFile not loaded."); return saveData; } - + if (SaveDatas.ContainsKey(key)) { - saveData = SaveDatas[key]; + saveDataString = SaveDatas[key]; + saveData = Json.ParseString(saveDataString).AsGodotDictionary(); } return saveData; -- 2.36.3 From bee6249c6fe1ad677a6dd5333a38fecb1e2f2f53 Mon Sep 17 00:00:00 2001 From: kziolkowski Date: Sun, 23 Nov 2025 22:25:59 +0100 Subject: [PATCH 08/18] :construction: implemented interaction area loading --- prefabs/farm/animals/duck.tscn | 6 ++- scenes/Babushka_scene_farm_outside_2d.tscn | 33 ++++++++++++++- .../CharacterControls/InteractionArea2D.cs | 42 ++++++++++++++----- 3 files changed, 66 insertions(+), 15 deletions(-) diff --git a/prefabs/farm/animals/duck.tscn b/prefabs/farm/animals/duck.tscn index d3c0ff5..b28adea 100644 --- a/prefabs/farm/animals/duck.tscn +++ b/prefabs/farm/animals/duck.tscn @@ -160,9 +160,11 @@ libraries = { &"": SubResource("AnimationLibrary_54k4r") } -[node name="InteractionArea" parent="." node_paths=PackedStringArray("_spritesToOutline") instance=ExtResource("15_uo3dh")] +[node name="InteractionArea" parent="." instance=ExtResource("15_uo3dh")] position = Vector2(18, -250) -_spritesToOutline = [NodePath("../Duck rendered")] +_sceneKeyProvider = null +_saveId = "f99549a9-c97e-45a4-b3c9-4bac23fe6fe7" +_outlineMaterial = null [node name="CollisionShape3D" parent="InteractionArea/Area2D" index="0"] shape = SubResource("CircleShape2D_uo3dh") diff --git a/scenes/Babushka_scene_farm_outside_2d.tscn b/scenes/Babushka_scene_farm_outside_2d.tscn index fcf00fc..d71fd58 100644 --- a/scenes/Babushka_scene_farm_outside_2d.tscn +++ b/scenes/Babushka_scene_farm_outside_2d.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=117 format=3 uid="uid://gigb28qk8t12"] +[gd_scene load_steps=121 format=3 uid="uid://gigb28qk8t12"] [ext_resource type="PackedScene" uid="uid://c25udixd5m6l0" path="res://prefabs/characters/Player2D.tscn" id="1_7wfwe"] [ext_resource type="Texture2D" uid="uid://8sr11ex30n0m" path="res://art/mockups/Kenney_Backgrounds/Samples/uncolored_hills.png" id="2_7b2ri"] @@ -236,6 +236,20 @@ shader_parameter/value_mult = 1.0 shader_parameter/brightness_add = 0.0 shader_parameter/contrast_mult = 1.0 +[sub_resource type="CircleShape2D" id="CircleShape2D_065st"] +resource_local_to_scene = true +radius = 200.0 + +[sub_resource type="ViewportTexture" id="ViewportTexture_w1kgo"] +viewport_path = NodePath("SubViewport") + +[sub_resource type="CircleShape2D" id="CircleShape2D_w1kgo"] +resource_local_to_scene = true +radius = 200.0 + +[sub_resource type="ViewportTexture" id="ViewportTexture_q1g8e"] +viewport_path = NodePath("SubViewport") + [sub_resource type="AudioStreamPlaylist" id="AudioStreamPlaylist_ceriq"] loop = false stream_count = 1 @@ -2243,7 +2257,6 @@ position = Vector2(-113.561, 193.035) shape = SubResource("RectangleShape2D_2vojv") [node name="ducks" type="Node2D" parent="YSorted"] -visible = false z_index = 1 y_sort_enabled = true script = ExtResource("49_uxa2m") @@ -2255,12 +2268,24 @@ y_sort_enabled = false position = Vector2(4374, 2652) _penTarget = NodePath("../../pen/penSlot1") +[node name="CollisionShape3D" parent="YSorted/ducks/Duck2/InteractionArea/Area2D" index="0"] +shape = SubResource("CircleShape2D_065st") + +[node name="Duck rendered" parent="YSorted/ducks/Duck2" index="3"] +texture = SubResource("ViewportTexture_w1kgo") + [node name="Duck3" parent="YSorted/ducks" node_paths=PackedStringArray("_penTarget") instance=ExtResource("62_i36hd")] z_index = 0 y_sort_enabled = false position = Vector2(9259, 3194) _penTarget = NodePath("../../pen/penSlot2") +[node name="CollisionShape3D" parent="YSorted/ducks/Duck3/InteractionArea/Area2D" index="0"] +shape = SubResource("CircleShape2D_w1kgo") + +[node name="Duck rendered" parent="YSorted/ducks/Duck3" index="3"] +texture = SubResource("ViewportTexture_q1g8e") + [node name="Duck4" parent="YSorted/ducks" node_paths=PackedStringArray("_penTarget") instance=ExtResource("62_i36hd")] z_index = 0 y_sort_enabled = false @@ -2544,6 +2569,10 @@ script = ExtResource("79_065st") [editable path="YSorted/SeedPickup2"] [editable path="YSorted/SeedPickup2/PickupInteractionArea"] [editable path="YSorted/Blocker/InteractionArea"] +[editable path="YSorted/ducks/Duck2"] +[editable path="YSorted/ducks/Duck2/InteractionArea"] +[editable path="YSorted/ducks/Duck3"] +[editable path="YSorted/ducks/Duck3/InteractionArea"] [editable path="YSorted/trash/trashObject2"] [editable path="YSorted/trash/trashObject3"] [editable path="YSorted/trash/trashObject4"] diff --git a/scripts/CSharp/Common/CharacterControls/InteractionArea2D.cs b/scripts/CSharp/Common/CharacterControls/InteractionArea2D.cs index fc3769a..ede7626 100644 --- a/scripts/CSharp/Common/CharacterControls/InteractionArea2D.cs +++ b/scripts/CSharp/Common/CharacterControls/InteractionArea2D.cs @@ -1,18 +1,18 @@ +using System; using System.Linq; using Babushka.scripts.CSharp.Common.Savegame; using Babushka.scripts.CSharp.Common.Services; -using Babushka.scripts.CSharp.Low_Code.Variables; using Godot; using Godot.Collections; namespace Babushka.scripts.CSharp.Common.CharacterControls; +[Tool] public partial class InteractionArea2D : Node2D, ISaveable { [ExportGroup("Persistence")] - [Export] public string SaveId = ""; - [Export] private VariableNode _sceneID; - [Export] public VariableResource _sceneKeyProvider; + [Export] public Babushka.scripts.CSharp.Low_Code.Variables.VariableResource _sceneKeyProvider; + [Export] private string _saveId; [ExportGroup("Settings")] [Export] private Area2D _area; @@ -48,9 +48,10 @@ public partial class InteractionArea2D : Node2D, ISaveable { _backupMaterials = _spritesToOutline.Select(s => s.Material).ToArray(); } + + LoadFromSaveData(); } - public void OnPlayerEntered(Node2D player) { if (!_active || !InputService.Instance.InputEnabled) @@ -113,12 +114,18 @@ public partial class InteractionArea2D : Node2D, ISaveable } } - EmitSignal(SignalName.InteractedTool, _id); - EmitSignal(SignalName.Interacted); - _interactionCounter++; + Interact(); + UpdateSaveData(); } } + private void Interact() + { + EmitSignal(SignalName.InteractedTool, _id); + EmitSignal(SignalName.Interacted); + _interactionCounter++; + } + public void SetSpriteActiveState(bool success, int id) // TODO: remove { if (!_active) @@ -137,18 +144,31 @@ public partial class InteractionArea2D : Node2D, ISaveable { var payloadData = new Dictionary { - { "interaction counter", _interactionCounter } + { "interaction_counter", _interactionCounter } }; - SavegameService.AppendDataToSave(_sceneKeyProvider.Payload.AsString(), SaveId + _sceneID.Payload.AsString(), payloadData); + SavegameService.AppendDataToSave(_sceneKeyProvider.Payload.AsString(), _saveId, payloadData); } public void LoadFromSaveData() { var sceneName = _sceneKeyProvider.Payload.AsString(); - var id = SaveId + _sceneID.Payload.AsString(); + var id = _saveId; + int counter = 0; Dictionary save = SavegameService.GetSaveData(sceneName, id); + if (save.Count > 0) + { + if (save.TryGetValue("interaction_counter", out Variant interactionCounterVar)) + { + counter = interactionCounterVar.AsInt32(); + } + } + + for (int i = 0; i < counter; i++) + { + Interact(); + } } #endregion -- 2.36.3 From c68727aebe201caa490c843f0c02b3dfda3d74eb Mon Sep 17 00:00:00 2001 From: kziolkowski Date: Sun, 23 Nov 2025 22:50:32 +0100 Subject: [PATCH 09/18] :construction: intermediate state with homework --- prefabs/farm/animals/duck.tscn | 6 +- prefabs/interactions/interaction_area_2d.tscn | 4 +- scenes/Babushka_scene_farm_outside_2d.tscn | 72 ++++++++----------- .../CharacterControls/InteractionArea2D.cs | 17 +++-- 4 files changed, 45 insertions(+), 54 deletions(-) diff --git a/prefabs/farm/animals/duck.tscn b/prefabs/farm/animals/duck.tscn index b28adea..d3c0ff5 100644 --- a/prefabs/farm/animals/duck.tscn +++ b/prefabs/farm/animals/duck.tscn @@ -160,11 +160,9 @@ libraries = { &"": SubResource("AnimationLibrary_54k4r") } -[node name="InteractionArea" parent="." instance=ExtResource("15_uo3dh")] +[node name="InteractionArea" parent="." node_paths=PackedStringArray("_spritesToOutline") instance=ExtResource("15_uo3dh")] position = Vector2(18, -250) -_sceneKeyProvider = null -_saveId = "f99549a9-c97e-45a4-b3c9-4bac23fe6fe7" -_outlineMaterial = null +_spritesToOutline = [NodePath("../Duck rendered")] [node name="CollisionShape3D" parent="InteractionArea/Area2D" index="0"] shape = SubResource("CircleShape2D_uo3dh") diff --git a/prefabs/interactions/interaction_area_2d.tscn b/prefabs/interactions/interaction_area_2d.tscn index fb04b7f..cf2871e 100644 --- a/prefabs/interactions/interaction_area_2d.tscn +++ b/prefabs/interactions/interaction_area_2d.tscn @@ -1,6 +1,7 @@ -[gd_scene load_steps=6 format=3 uid="uid://cqc72e4hq6bcd"] +[gd_scene load_steps=7 format=3 uid="uid://cqc72e4hq6bcd"] [ext_resource type="Script" uid="uid://ckp413wrub5fm" path="res://scripts/CSharp/Common/CharacterControls/InteractionArea2D.cs" id="1_5ajrf"] +[ext_resource type="Resource" uid="uid://tt3d166mntmi" path="res://resources/low code/farming/var_sceneNameProvider.tres" id="2_o1drf"] [ext_resource type="Material" uid="uid://blch5kdhkbj75" path="res://art/materials/simple_interactable_outline.tres" id="2_qoey7"] [ext_resource type="Script" uid="uid://cp2q4k62sjo6h" path="res://scripts/CSharp/Common/CharacterControls/DetectableInteractionArea.cs" id="3_2wrrq"] @@ -13,6 +14,7 @@ default_font_size = 30 [node name="InteractionArea" type="Node2D" node_paths=PackedStringArray("_area", "_label")] script = ExtResource("1_5ajrf") +_sceneKeyProvider = ExtResource("2_o1drf") _area = NodePath("Area2D") _label = NodePath("Area2D/CanvasLayer/MarginContainer/Label") _outlineMaterial = ExtResource("2_qoey7") diff --git a/scenes/Babushka_scene_farm_outside_2d.tscn b/scenes/Babushka_scene_farm_outside_2d.tscn index d71fd58..f8e461e 100644 --- a/scenes/Babushka_scene_farm_outside_2d.tscn +++ b/scenes/Babushka_scene_farm_outside_2d.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=121 format=3 uid="uid://gigb28qk8t12"] +[gd_scene load_steps=117 format=3 uid="uid://gigb28qk8t12"] [ext_resource type="PackedScene" uid="uid://c25udixd5m6l0" path="res://prefabs/characters/Player2D.tscn" id="1_7wfwe"] [ext_resource type="Texture2D" uid="uid://8sr11ex30n0m" path="res://art/mockups/Kenney_Backgrounds/Samples/uncolored_hills.png" id="2_7b2ri"] @@ -236,20 +236,6 @@ shader_parameter/value_mult = 1.0 shader_parameter/brightness_add = 0.0 shader_parameter/contrast_mult = 1.0 -[sub_resource type="CircleShape2D" id="CircleShape2D_065st"] -resource_local_to_scene = true -radius = 200.0 - -[sub_resource type="ViewportTexture" id="ViewportTexture_w1kgo"] -viewport_path = NodePath("SubViewport") - -[sub_resource type="CircleShape2D" id="CircleShape2D_w1kgo"] -resource_local_to_scene = true -radius = 200.0 - -[sub_resource type="ViewportTexture" id="ViewportTexture_q1g8e"] -viewport_path = NodePath("SubViewport") - [sub_resource type="AudioStreamPlaylist" id="AudioStreamPlaylist_ceriq"] loop = false stream_count = 1 @@ -1073,9 +1059,7 @@ collision_mask = 4 position = Vector2(145.5, -224) shape = SubResource("RectangleShape2D_0sfl7") -[node name="InteractionArea" parent="YSorted/Well" node_paths=PackedStringArray("_spritesToOutline") instance=ExtResource("27_klb81")] -_spritesToOutline = [NodePath("..")] -_id = 1 +[node name="InteractionArea" parent="YSorted/Well" instance=ExtResource("27_klb81")] [node name="CollisionShape3D" parent="YSorted/Well/InteractionArea/Area2D" index="0"] position = Vector2(146, -130) @@ -1087,6 +1071,10 @@ position = Vector2(8192, 3507) [node name="SpawnWithItem" parent="YSorted/CanGenericPickup" index="0"] _blueprint = ExtResource("28_ipqaa") +[node name="PickupInteractionArea" parent="YSorted/CanGenericPickup" index="3" node_paths=PackedStringArray("_spritesToOutline")] +_outlineMaterial = null +_spritesToOutline = [] + [node name="CollisionShape3D" parent="YSorted/CanGenericPickup/PickupInteractionArea/Area2D" index="0"] shape = SubResource("CircleShape2D_2065p") @@ -1099,6 +1087,10 @@ position = Vector2(8391, 2060) [node name="SpawnWithItem" parent="YSorted/RakeGenericPickup" index="0"] _blueprint = ExtResource("28_6b2nr") +[node name="PickupInteractionArea" parent="YSorted/RakeGenericPickup" index="3" node_paths=PackedStringArray("_spritesToOutline")] +_outlineMaterial = null +_spritesToOutline = [] + [node name="CollisionShape3D" parent="YSorted/RakeGenericPickup/PickupInteractionArea/Area2D" index="0"] shape = SubResource("CircleShape2D_tm0yg") @@ -1110,6 +1102,10 @@ _finiteSupply = 3 [node name="SpawnWithItem" parent="YSorted/SeedPickup" index="0"] _blueprint = ExtResource("35_64mdn") +[node name="PickupInteractionArea" parent="YSorted/SeedPickup" index="3" node_paths=PackedStringArray("_spritesToOutline")] +_outlineMaterial = null +_spritesToOutline = [] + [node name="CollisionShape3D" parent="YSorted/SeedPickup/PickupInteractionArea/Area2D" index="0"] shape = SubResource("CircleShape2D_tm0yg") @@ -1123,6 +1119,10 @@ _finiteSupply = 3 [node name="SpawnWithItem" parent="YSorted/SeedPickup2" index="0"] _blueprint = ExtResource("36_fv1t2") +[node name="PickupInteractionArea" parent="YSorted/SeedPickup2" index="3" node_paths=PackedStringArray("_spritesToOutline")] +_outlineMaterial = null +_spritesToOutline = [] + [node name="CollisionShape3D" parent="YSorted/SeedPickup2/PickupInteractionArea/Area2D" index="0"] shape = SubResource("CircleShape2D_tm0yg") @@ -1261,11 +1261,9 @@ collision_mask = 6 position = Vector2(-252.56, 231.32) polygon = PackedVector2Array(247.227, 43.5123, 44.7822, 43.5123, -87.2178, 45.123, -104.329, -55.2797, -154.107, -73.5347, -160.107, -380.38, -175.44, -400.783, -63.44, -512.461, 97.8934, -541.991, 261.671, -599.172, 374.782, -526.421, 502.338, -526.421, 637.893, -396.488, 598.56, -360.783, 596.338, -58.2327, 528.782, -58.2327, 501.449, 45.9283) -[node name="EnterHouseInteraction" parent="YSorted/Farm visuals/Static" node_paths=PackedStringArray("_spritesToOutline") instance=ExtResource("27_klb81")] +[node name="EnterHouseInteraction" parent="YSorted/Farm visuals/Static" instance=ExtResource("27_klb81")] position = Vector2(5834, 2354) scale = Vector2(2.425, 2.425) -_spritesToOutline = [NodePath("DoorSprite")] -_id = 0 [node name="DoorSprite" type="Sprite2D" parent="YSorted/Farm visuals/Static/EnterHouseInteraction"] position = Vector2(0.412364, -33.1959) @@ -2233,10 +2231,8 @@ collision_mask = 4 position = Vector2(-106.663, 182.891) shape = SubResource("RectangleShape2D_ycj14") -[node name="InteractionArea" parent="YSorted/Blocker" node_paths=PackedStringArray("_spritesToOutline") instance=ExtResource("27_klb81")] +[node name="InteractionArea" parent="YSorted/Blocker" instance=ExtResource("27_klb81")] position = Vector2(11234, 1850) -_spritesToOutline = [NodePath("Fence Door")] -_id = 1 [node name="CollisionShape3D" parent="YSorted/Blocker/InteractionArea/Area2D" index="0"] shape = SubResource("CircleShape2D_l7ekk") @@ -2268,24 +2264,12 @@ y_sort_enabled = false position = Vector2(4374, 2652) _penTarget = NodePath("../../pen/penSlot1") -[node name="CollisionShape3D" parent="YSorted/ducks/Duck2/InteractionArea/Area2D" index="0"] -shape = SubResource("CircleShape2D_065st") - -[node name="Duck rendered" parent="YSorted/ducks/Duck2" index="3"] -texture = SubResource("ViewportTexture_w1kgo") - [node name="Duck3" parent="YSorted/ducks" node_paths=PackedStringArray("_penTarget") instance=ExtResource("62_i36hd")] z_index = 0 y_sort_enabled = false position = Vector2(9259, 3194) _penTarget = NodePath("../../pen/penSlot2") -[node name="CollisionShape3D" parent="YSorted/ducks/Duck3/InteractionArea/Area2D" index="0"] -shape = SubResource("CircleShape2D_w1kgo") - -[node name="Duck rendered" parent="YSorted/ducks/Duck3" index="3"] -texture = SubResource("ViewportTexture_q1g8e") - [node name="Duck4" parent="YSorted/ducks" node_paths=PackedStringArray("_penTarget") instance=ExtResource("62_i36hd")] z_index = 0 y_sort_enabled = false @@ -2361,8 +2345,9 @@ position = Vector2(3183, 2369) offset = Vector2(1, -50) region_rect = Rect2(207, 1184, 149, 142) -[node name="InteractionArea" parent="YSorted/trash/trashObject2" index="0"] +[node name="InteractionArea" parent="YSorted/trash/trashObject2" index="0" node_paths=PackedStringArray("_spritesToOutline")] position = Vector2(-9, -46) +_spritesToOutline = [] [node name="trashObject3" parent="YSorted/trash" instance=ExtResource("53_ycj14")] z_index = 0 @@ -2371,8 +2356,9 @@ position = Vector2(4724, 3519) offset = Vector2(1, -50) region_rect = Rect2(400, 1053, 163, 141) -[node name="InteractionArea" parent="YSorted/trash/trashObject3" index="0"] +[node name="InteractionArea" parent="YSorted/trash/trashObject3" index="0" node_paths=PackedStringArray("_spritesToOutline")] position = Vector2(-13, -53) +_spritesToOutline = [] [node name="trashObject4" parent="YSorted/trash" instance=ExtResource("53_ycj14")] z_index = 0 @@ -2381,8 +2367,9 @@ position = Vector2(5385, 3391) offset = Vector2(1, -50) region_rect = Rect2(1048, 1092, 348, 106) -[node name="InteractionArea" parent="YSorted/trash/trashObject4" index="0"] +[node name="InteractionArea" parent="YSorted/trash/trashObject4" index="0" node_paths=PackedStringArray("_spritesToOutline")] position = Vector2(0, -59) +_spritesToOutline = [] [node name="trashObject5" parent="YSorted/trash" instance=ExtResource("53_ycj14")] z_index = 0 @@ -2418,8 +2405,9 @@ rotation = 1.77025 offset = Vector2(0, 0) region_rect = Rect2(1048, 1092, 348, 106) -[node name="InteractionArea" parent="YSorted/trash/trashObject9" index="0"] +[node name="InteractionArea" parent="YSorted/trash/trashObject9" index="0" node_paths=PackedStringArray("_spritesToOutline")] position = Vector2(22.40873, 25.05658) +_spritesToOutline = [] [node name="CanvasLayer" parent="." instance=ExtResource("32_2nee2")] @@ -2569,10 +2557,6 @@ script = ExtResource("79_065st") [editable path="YSorted/SeedPickup2"] [editable path="YSorted/SeedPickup2/PickupInteractionArea"] [editable path="YSorted/Blocker/InteractionArea"] -[editable path="YSorted/ducks/Duck2"] -[editable path="YSorted/ducks/Duck2/InteractionArea"] -[editable path="YSorted/ducks/Duck3"] -[editable path="YSorted/ducks/Duck3/InteractionArea"] [editable path="YSorted/trash/trashObject2"] [editable path="YSorted/trash/trashObject3"] [editable path="YSorted/trash/trashObject4"] diff --git a/scripts/CSharp/Common/CharacterControls/InteractionArea2D.cs b/scripts/CSharp/Common/CharacterControls/InteractionArea2D.cs index ede7626..c683a05 100644 --- a/scripts/CSharp/Common/CharacterControls/InteractionArea2D.cs +++ b/scripts/CSharp/Common/CharacterControls/InteractionArea2D.cs @@ -1,18 +1,18 @@ -using System; using System.Linq; using Babushka.scripts.CSharp.Common.Savegame; using Babushka.scripts.CSharp.Common.Services; +using Babushka.scripts.CSharp.Low_Code.Variables; using Godot; using Godot.Collections; namespace Babushka.scripts.CSharp.Common.CharacterControls; -[Tool] public partial class InteractionArea2D : Node2D, ISaveable { [ExportGroup("Persistence")] - [Export] public Babushka.scripts.CSharp.Low_Code.Variables.VariableResource _sceneKeyProvider; - [Export] private string _saveId; + [Export] public VariableResource _sceneKeyProvider; + [Export] private string _saveId = ""; // todo: find good default / generated solution + //todo: rewire broken instances in scenes [ExportGroup("Settings")] [Export] private Area2D _area; @@ -48,7 +48,14 @@ public partial class InteractionArea2D : Node2D, ISaveable { _backupMaterials = _spritesToOutline.Select(s => s.Material).ToArray(); } - + + // bad solution for interaction areas, because they are all named the same. + // option (equally bad) 1: take grandparent's name (could be null though) + // option 2 (also bad): Write Identity Provider class that uses, checks and assigns GUIDs for this purpose. + if (string.IsNullOrEmpty(_saveId)) + { + _saveId = Name; + } LoadFromSaveData(); } -- 2.36.3 From c695115a4a29ba73d51fc19ca5eb3f249001b513 Mon Sep 17 00:00:00 2001 From: kziolkowski Date: Tue, 25 Nov 2025 13:33:53 +0100 Subject: [PATCH 10/18] :feature: implemented saving and loading for ducks, :fire: removed save implementation from interactionArea --- prefabs/farm/animals/duck.tscn | 16 +++--- prefabs/interactions/interaction_area_2d.tscn | 4 +- project.godot | 4 ++ scenes/Babushka_scene_farm_outside_2d.tscn | 27 ++++++++- .../CharacterControls/InteractionArea2D.cs | 55 +----------------- .../Common/Savegame/SaveIDProviderTool.cs | 28 ++++++++++ .../Common/Savegame/SaveIDProviderTool.cs.uid | 1 + scripts/CSharp/Common/Temp/MVPDuck.cs | 56 ++++++++++++++++++- 8 files changed, 123 insertions(+), 68 deletions(-) create mode 100644 scripts/CSharp/Common/Savegame/SaveIDProviderTool.cs create mode 100644 scripts/CSharp/Common/Savegame/SaveIDProviderTool.cs.uid diff --git a/prefabs/farm/animals/duck.tscn b/prefabs/farm/animals/duck.tscn index d3c0ff5..b503a3d 100644 --- a/prefabs/farm/animals/duck.tscn +++ b/prefabs/farm/animals/duck.tscn @@ -1,7 +1,8 @@ -[gd_scene load_steps=24 format=3 uid="uid://muuxxgvx33fp"] +[gd_scene load_steps=25 format=3 uid="uid://muuxxgvx33fp"] [ext_resource type="Script" uid="uid://7m1rt7agb6rm" path="res://scripts/CSharp/Common/Temp/MVPDuck.cs" id="1_54k4r"] [ext_resource type="Texture2D" uid="uid://hvchk6t0xe7j" path="res://art/animals/Ente.png" id="1_cgxhx"] +[ext_resource type="Resource" uid="uid://tt3d166mntmi" path="res://resources/low code/farming/var_sceneNameProvider.tres" id="2_fdf3t"] [ext_resource type="AudioStream" uid="uid://qv0aubjeyi0u" path="res://audio/sfx/Animals/SFX_Duck_Quack_01.wav" id="3_kjie1"] [ext_resource type="Script" uid="uid://cfnrd5k1k0gxw" path="res://scripts/CSharp/Common/AudioPlayer2D.cs" id="3_rdn2q"] [ext_resource type="AudioStream" uid="uid://da84l8e44scwh" path="res://audio/sfx/Animals/SFX_Duck_Quack_02.wav" id="4_54k4r"] @@ -138,10 +139,11 @@ radius = 200.0 [sub_resource type="ViewportTexture" id="ViewportTexture_4830j"] viewport_path = NodePath("SubViewport") -[node name="Duck" type="Node2D" node_paths=PackedStringArray("_animationPlayer")] +[node name="Duck" type="Node2D" node_paths=PackedStringArray("_animationPlayer") groups=["Saveable"]] z_index = 1 y_sort_enabled = true script = ExtResource("1_54k4r") +_sceneKeyProvider = ExtResource("2_fdf3t") _transferDelayMs = 1000 _animationPlayer = NodePath("AnimationPlayer") @@ -160,11 +162,11 @@ libraries = { &"": SubResource("AnimationLibrary_54k4r") } -[node name="InteractionArea" parent="." node_paths=PackedStringArray("_spritesToOutline") instance=ExtResource("15_uo3dh")] +[node name="duck interaction" parent="." node_paths=PackedStringArray("_spritesToOutline") instance=ExtResource("15_uo3dh")] position = Vector2(18, -250) _spritesToOutline = [NodePath("../Duck rendered")] -[node name="CollisionShape3D" parent="InteractionArea/Area2D" index="0"] +[node name="CollisionShape3D" parent="duck interaction/Area2D" index="0"] shape = SubResource("CircleShape2D_uo3dh") [node name="Duck rendered" type="Sprite2D" parent="."] @@ -321,7 +323,7 @@ offset = Vector2(40, 40) region_enabled = true region_rect = Rect2(246, 393, 111, 111) -[connection signal="Interacted" from="InteractionArea" to="." method="TransferToTargetAfterDelay"] -[connection signal="Interacted" from="InteractionArea" to="Audio/NakNak" method="PlayOneShot"] +[connection signal="Interacted" from="duck interaction" to="." method="TransferToTargetAfterDelay"] +[connection signal="Interacted" from="duck interaction" to="Audio/NakNak" method="PlayOneShot"] -[editable path="InteractionArea"] +[editable path="duck interaction"] diff --git a/prefabs/interactions/interaction_area_2d.tscn b/prefabs/interactions/interaction_area_2d.tscn index cf2871e..fb04b7f 100644 --- a/prefabs/interactions/interaction_area_2d.tscn +++ b/prefabs/interactions/interaction_area_2d.tscn @@ -1,7 +1,6 @@ -[gd_scene load_steps=7 format=3 uid="uid://cqc72e4hq6bcd"] +[gd_scene load_steps=6 format=3 uid="uid://cqc72e4hq6bcd"] [ext_resource type="Script" uid="uid://ckp413wrub5fm" path="res://scripts/CSharp/Common/CharacterControls/InteractionArea2D.cs" id="1_5ajrf"] -[ext_resource type="Resource" uid="uid://tt3d166mntmi" path="res://resources/low code/farming/var_sceneNameProvider.tres" id="2_o1drf"] [ext_resource type="Material" uid="uid://blch5kdhkbj75" path="res://art/materials/simple_interactable_outline.tres" id="2_qoey7"] [ext_resource type="Script" uid="uid://cp2q4k62sjo6h" path="res://scripts/CSharp/Common/CharacterControls/DetectableInteractionArea.cs" id="3_2wrrq"] @@ -14,7 +13,6 @@ default_font_size = 30 [node name="InteractionArea" type="Node2D" node_paths=PackedStringArray("_area", "_label")] script = ExtResource("1_5ajrf") -_sceneKeyProvider = ExtResource("2_o1drf") _area = NodePath("Area2D") _label = NodePath("Area2D/CanvasLayer/MarginContainer/Label") _outlineMaterial = ExtResource("2_qoey7") diff --git a/project.godot b/project.godot index 2658752..7d188a7 100644 --- a/project.godot +++ b/project.godot @@ -221,6 +221,10 @@ folder_colors={ "res://shader/": "pink" } +[global_group] + +Saveable="" + [input] move_left={ diff --git a/scenes/Babushka_scene_farm_outside_2d.tscn b/scenes/Babushka_scene_farm_outside_2d.tscn index f8e461e..91f359e 100644 --- a/scenes/Babushka_scene_farm_outside_2d.tscn +++ b/scenes/Babushka_scene_farm_outside_2d.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=117 format=3 uid="uid://gigb28qk8t12"] +[gd_scene load_steps=118 format=3 uid="uid://gigb28qk8t12"] [ext_resource type="PackedScene" uid="uid://c25udixd5m6l0" path="res://prefabs/characters/Player2D.tscn" id="1_7wfwe"] [ext_resource type="Texture2D" uid="uid://8sr11ex30n0m" path="res://art/mockups/Kenney_Backgrounds/Samples/uncolored_hills.png" id="2_7b2ri"] @@ -79,6 +79,7 @@ [ext_resource type="Resource" uid="uid://tt3d166mntmi" path="res://resources/low code/farming/var_sceneNameProvider.tres" id="77_xcwle"] [ext_resource type="PackedScene" uid="uid://b1d2e7ely6hyw" path="res://prefabs/farm/base_field.tscn" id="78_xcwle"] [ext_resource type="Script" uid="uid://iquhbkr7pqeg" path="res://scripts/CSharp/Common/Savegame/SaveCheats.cs" id="79_065st"] +[ext_resource type="Script" uid="uid://ca4s0algeij1h" path="res://scripts/CSharp/Common/Savegame/SaveIDProviderTool.cs" id="80_w1kgo"] [sub_resource type="ShaderMaterial" id="ShaderMaterial_wtdui"] shader = ExtResource("13_7p0hq") @@ -1060,6 +1061,7 @@ position = Vector2(145.5, -224) shape = SubResource("RectangleShape2D_0sfl7") [node name="InteractionArea" parent="YSorted/Well" instance=ExtResource("27_klb81")] +metadata/SaveID = "b8f7b7fe-e057-4974-ba12-9134722998de" [node name="CollisionShape3D" parent="YSorted/Well/InteractionArea/Area2D" index="0"] position = Vector2(146, -130) @@ -1074,6 +1076,7 @@ _blueprint = ExtResource("28_ipqaa") [node name="PickupInteractionArea" parent="YSorted/CanGenericPickup" index="3" node_paths=PackedStringArray("_spritesToOutline")] _outlineMaterial = null _spritesToOutline = [] +metadata/SaveID = "0c006f5c-c472-4f89-908b-d8f34503ba37" [node name="CollisionShape3D" parent="YSorted/CanGenericPickup/PickupInteractionArea/Area2D" index="0"] shape = SubResource("CircleShape2D_2065p") @@ -1090,6 +1093,7 @@ _blueprint = ExtResource("28_6b2nr") [node name="PickupInteractionArea" parent="YSorted/RakeGenericPickup" index="3" node_paths=PackedStringArray("_spritesToOutline")] _outlineMaterial = null _spritesToOutline = [] +metadata/SaveID = "c148aa78-114b-4770-a040-8498483edb1d" [node name="CollisionShape3D" parent="YSorted/RakeGenericPickup/PickupInteractionArea/Area2D" index="0"] shape = SubResource("CircleShape2D_tm0yg") @@ -1105,6 +1109,7 @@ _blueprint = ExtResource("35_64mdn") [node name="PickupInteractionArea" parent="YSorted/SeedPickup" index="3" node_paths=PackedStringArray("_spritesToOutline")] _outlineMaterial = null _spritesToOutline = [] +metadata/SaveID = "ad152c51-3631-42c1-9aa4-4df896b35d8c" [node name="CollisionShape3D" parent="YSorted/SeedPickup/PickupInteractionArea/Area2D" index="0"] shape = SubResource("CircleShape2D_tm0yg") @@ -1122,6 +1127,7 @@ _blueprint = ExtResource("36_fv1t2") [node name="PickupInteractionArea" parent="YSorted/SeedPickup2" index="3" node_paths=PackedStringArray("_spritesToOutline")] _outlineMaterial = null _spritesToOutline = [] +metadata/SaveID = "09e115e7-1d21-485a-be3e-b3fff9c83e78" [node name="CollisionShape3D" parent="YSorted/SeedPickup2/PickupInteractionArea/Area2D" index="0"] shape = SubResource("CircleShape2D_tm0yg") @@ -1264,6 +1270,7 @@ polygon = PackedVector2Array(247.227, 43.5123, 44.7822, 43.5123, -87.2178, 45.12 [node name="EnterHouseInteraction" parent="YSorted/Farm visuals/Static" instance=ExtResource("27_klb81")] position = Vector2(5834, 2354) scale = Vector2(2.425, 2.425) +metadata/SaveID = "5a93071f-c1ab-4b4b-b74e-a6324d44ddf8" [node name="DoorSprite" type="Sprite2D" parent="YSorted/Farm visuals/Static/EnterHouseInteraction"] position = Vector2(0.412364, -33.1959) @@ -2231,8 +2238,11 @@ collision_mask = 4 position = Vector2(-106.663, 182.891) shape = SubResource("RectangleShape2D_ycj14") -[node name="InteractionArea" parent="YSorted/Blocker" instance=ExtResource("27_klb81")] +[node name="InteractionArea" parent="YSorted/Blocker" node_paths=PackedStringArray("_spritesToOutline") instance=ExtResource("27_klb81")] position = Vector2(11234, 1850) +_spritesToOutline = [NodePath("Fence Door")] +_id = 1 +metadata/SaveID = "6ee77256-42af-49c9-a3f2-cf167853f6fb" [node name="CollisionShape3D" parent="YSorted/Blocker/InteractionArea/Area2D" index="0"] shape = SubResource("CircleShape2D_l7ekk") @@ -2263,12 +2273,14 @@ z_index = 0 y_sort_enabled = false position = Vector2(4374, 2652) _penTarget = NodePath("../../pen/penSlot1") +metadata/SaveID = "348bd0e3-1da5-4f10-84ab-b0444e99d541" [node name="Duck3" parent="YSorted/ducks" node_paths=PackedStringArray("_penTarget") instance=ExtResource("62_i36hd")] z_index = 0 y_sort_enabled = false position = Vector2(9259, 3194) _penTarget = NodePath("../../pen/penSlot2") +metadata/SaveID = "94c8a740-2745-4162-91e7-66f36b8681e0" [node name="Duck4" parent="YSorted/ducks" node_paths=PackedStringArray("_penTarget") instance=ExtResource("62_i36hd")] z_index = 0 @@ -2277,6 +2289,7 @@ position = Vector2(13441, 3612) rotation = 3.14159 scale = Vector2(1, -1) _penTarget = NodePath("../../pen/penSlot3") +metadata/SaveID = "b3508312-eb61-4520-8349-e49e0e5328d3" [node name="Duck5" parent="YSorted/ducks" node_paths=PackedStringArray("_penTarget") instance=ExtResource("62_i36hd")] z_index = 0 @@ -2285,12 +2298,14 @@ position = Vector2(15330, 2487) rotation = 3.14159 scale = Vector2(1, -1) _penTarget = NodePath("../../pen/penSlot4") +metadata/SaveID = "b73895c2-6366-4c7e-b5e2-23f3dc9485f2" [node name="Duck6" parent="YSorted/ducks" node_paths=PackedStringArray("_penTarget") instance=ExtResource("62_i36hd")] z_index = 0 y_sort_enabled = false position = Vector2(232, 2862) _penTarget = NodePath("../../pen/penSlot5") +metadata/SaveID = "a963b9d2-862f-458b-be2c-9a54ec1bde90" [node name="Duck7" parent="YSorted/ducks" node_paths=PackedStringArray("_penTarget") instance=ExtResource("62_i36hd")] z_index = 0 @@ -2299,6 +2314,7 @@ position = Vector2(2409, 3958) rotation = 3.14159 scale = Vector2(1, -1) _penTarget = NodePath("../../pen/penSlot6") +metadata/SaveID = "748aff78-10eb-4a4e-bb6d-a8ee25d472d1" [node name="DialogicToggle" type="Node2D" parent="YSorted/ducks"] script = ExtResource("51_uxa2m") @@ -2348,6 +2364,7 @@ region_rect = Rect2(207, 1184, 149, 142) [node name="InteractionArea" parent="YSorted/trash/trashObject2" index="0" node_paths=PackedStringArray("_spritesToOutline")] position = Vector2(-9, -46) _spritesToOutline = [] +metadata/SaveID = "549bbcf4-ea57-4b8f-80b1-b13ca648559b" [node name="trashObject3" parent="YSorted/trash" instance=ExtResource("53_ycj14")] z_index = 0 @@ -2359,6 +2376,7 @@ region_rect = Rect2(400, 1053, 163, 141) [node name="InteractionArea" parent="YSorted/trash/trashObject3" index="0" node_paths=PackedStringArray("_spritesToOutline")] position = Vector2(-13, -53) _spritesToOutline = [] +metadata/SaveID = "29874314-50c1-4a21-9494-18f936d6e097" [node name="trashObject4" parent="YSorted/trash" instance=ExtResource("53_ycj14")] z_index = 0 @@ -2370,6 +2388,7 @@ region_rect = Rect2(1048, 1092, 348, 106) [node name="InteractionArea" parent="YSorted/trash/trashObject4" index="0" node_paths=PackedStringArray("_spritesToOutline")] position = Vector2(0, -59) _spritesToOutline = [] +metadata/SaveID = "7ccaa831-5526-40ed-8ca3-31ba2ad929a6" [node name="trashObject5" parent="YSorted/trash" instance=ExtResource("53_ycj14")] z_index = 0 @@ -2408,6 +2427,7 @@ region_rect = Rect2(1048, 1092, 348, 106) [node name="InteractionArea" parent="YSorted/trash/trashObject9" index="0" node_paths=PackedStringArray("_spritesToOutline")] position = Vector2(22.40873, 25.05658) _spritesToOutline = [] +metadata/SaveID = "7bf227d6-3844-41e9-a9cd-524052aced3b" [node name="CanvasLayer" parent="." instance=ExtResource("32_2nee2")] @@ -2525,6 +2545,9 @@ _payloadToSet = "farmOutside" [node name="SaveGameCheat" type="Node" parent="."] script = ExtResource("79_065st") +[node name="SaveIDProvider" type="Node" parent="."] +script = ExtResource("80_w1kgo") + [connection signal="FilledWateringCan" from="YSorted/Vesna" to="Audio/SFX/FillWater SFX2" method="PlayOneShot"] [connection signal="InteractedTool" from="YSorted/Well/InteractionArea" to="YSorted/Vesna" method="TryFillWateringCan"] [connection signal="SuccessfulPickUp" from="YSorted/CanGenericPickup" to="YSorted/Vesna" method="HandlePickUp"] diff --git a/scripts/CSharp/Common/CharacterControls/InteractionArea2D.cs b/scripts/CSharp/Common/CharacterControls/InteractionArea2D.cs index c683a05..df66172 100644 --- a/scripts/CSharp/Common/CharacterControls/InteractionArea2D.cs +++ b/scripts/CSharp/Common/CharacterControls/InteractionArea2D.cs @@ -1,19 +1,11 @@ using System.Linq; -using Babushka.scripts.CSharp.Common.Savegame; using Babushka.scripts.CSharp.Common.Services; -using Babushka.scripts.CSharp.Low_Code.Variables; using Godot; -using Godot.Collections; namespace Babushka.scripts.CSharp.Common.CharacterControls; -public partial class InteractionArea2D : Node2D, ISaveable +public partial class InteractionArea2D : Node2D { - [ExportGroup("Persistence")] - [Export] public VariableResource _sceneKeyProvider; - [Export] private string _saveId = ""; // todo: find good default / generated solution - //todo: rewire broken instances in scenes - [ExportGroup("Settings")] [Export] private Area2D _area; [Export] private Label _label; @@ -48,15 +40,6 @@ public partial class InteractionArea2D : Node2D, ISaveable { _backupMaterials = _spritesToOutline.Select(s => s.Material).ToArray(); } - - // bad solution for interaction areas, because they are all named the same. - // option (equally bad) 1: take grandparent's name (could be null though) - // option 2 (also bad): Write Identity Provider class that uses, checks and assigns GUIDs for this purpose. - if (string.IsNullOrEmpty(_saveId)) - { - _saveId = Name; - } - LoadFromSaveData(); } public void OnPlayerEntered(Node2D player) @@ -120,9 +103,7 @@ public partial class InteractionArea2D : Node2D, ISaveable sprite.Material = _backupMaterials[i]; } } - Interact(); - UpdateSaveData(); } } @@ -145,38 +126,4 @@ public partial class InteractionArea2D : Node2D, ISaveable _label.Hide(); } - #region SAVE AND LOAD - - public void UpdateSaveData() - { - var payloadData = new Dictionary - { - { "interaction_counter", _interactionCounter } - }; - - SavegameService.AppendDataToSave(_sceneKeyProvider.Payload.AsString(), _saveId, payloadData); - } - - public void LoadFromSaveData() - { - var sceneName = _sceneKeyProvider.Payload.AsString(); - var id = _saveId; - int counter = 0; - - Dictionary save = SavegameService.GetSaveData(sceneName, id); - if (save.Count > 0) - { - if (save.TryGetValue("interaction_counter", out Variant interactionCounterVar)) - { - counter = interactionCounterVar.AsInt32(); - } - } - - for (int i = 0; i < counter; i++) - { - Interact(); - } - } - - #endregion } \ No newline at end of file diff --git a/scripts/CSharp/Common/Savegame/SaveIDProviderTool.cs b/scripts/CSharp/Common/Savegame/SaveIDProviderTool.cs new file mode 100644 index 0000000..5ba38ed --- /dev/null +++ b/scripts/CSharp/Common/Savegame/SaveIDProviderTool.cs @@ -0,0 +1,28 @@ +using System; +using Godot; +using Godot.Collections; + +namespace Babushka.scripts.CSharp.Common.Savegame; + +[Tool] +public partial class SaveIDProviderTool : Node +{ + [ExportToolButton("Assign IDs")] private Callable assignIDs => Callable.From(AssignIDs); + + private void AssignIDs() + { + Array saveables = GetTree().GetNodesInGroup("Saveable"); + foreach (var node in saveables) + { + GD.Print($"Checking {node.Name}."); + GD.Print($"Node has Meta SaveID: {node.HasMeta("SaveID")} and it's: " + node.GetMeta("SaveID").AsString()); + if (!node.HasMeta("SaveID") || string.IsNullOrEmpty(node.GetMeta("SaveID").AsString())) + { + string saveID = Guid.NewGuid().ToString(); + node.SetMeta("SaveID", saveID); + GD.Print($"Setting Save ID for node {node.Name}: " + saveID); + } + + } + } +} \ No newline at end of file diff --git a/scripts/CSharp/Common/Savegame/SaveIDProviderTool.cs.uid b/scripts/CSharp/Common/Savegame/SaveIDProviderTool.cs.uid new file mode 100644 index 0000000..953d5d2 --- /dev/null +++ b/scripts/CSharp/Common/Savegame/SaveIDProviderTool.cs.uid @@ -0,0 +1 @@ +uid://ca4s0algeij1h diff --git a/scripts/CSharp/Common/Temp/MVPDuck.cs b/scripts/CSharp/Common/Temp/MVPDuck.cs index 7c7cf93..d0e8f69 100644 --- a/scripts/CSharp/Common/Temp/MVPDuck.cs +++ b/scripts/CSharp/Common/Temp/MVPDuck.cs @@ -1,13 +1,20 @@ using System.Threading.Tasks; +using Babushka.scripts.CSharp.Common.Savegame; +using Babushka.scripts.CSharp.Low_Code.Variables; using Godot; +using Godot.Collections; namespace Babushka.scripts.CSharp.Common.Temp; /// /// Temporary Duck behaviour to make sure we can use them in the first showcase /// -public partial class MVPDuck : Node2D +public partial class MVPDuck : Node2D, ISaveable { + [ExportGroup("Persistence")] + [Export] public VariableResource _sceneKeyProvider; + + [ExportGroup("Animation")] [Export] private Node2D _penTarget; [Export] private int _transferDelayMs; [Export] private AnimationPlayer _animationPlayer; @@ -17,6 +24,11 @@ public partial class MVPDuck : Node2D [Signal] public delegate void DuckCollectedEventHandler(); + public override void _Ready() + { + LoadFromSaveData(); + } + public void TransferToTargetAfterDelay() { if (!_collected) @@ -25,7 +37,6 @@ public partial class MVPDuck : Node2D PlayAnimation(); _collected = true; } - } private void PlayAnimation() @@ -40,7 +51,48 @@ public partial class MVPDuck : Node2D if(!_penTarget.Equals(null)) Position = _penTarget.GlobalPosition; EmitSignal(SignalName.DuckCollected); + UpdateSaveData(); } +#region SAVE AND LOAD + public void UpdateSaveData() + { + var payloadData = new Dictionary + { + { "globalPositionX", GlobalPosition.X }, + { "globalPositionY", GlobalPosition.Y }, + }; + + string id = GetMeta("SaveID").AsString(); + GD.Print($"Updating Save ID for object {Name}: {id}"); + SavegameService.AppendDataToSave(_sceneKeyProvider.Payload.AsString(), id, payloadData); + } + + public void LoadFromSaveData() + { + var sceneName = _sceneKeyProvider.Payload.AsString(); + string id = GetMeta("SaveID").AsString(); + GD.Print($"Loading Save ID for object {Name}: " + id); + + Dictionary save = SavegameService.GetSaveData(sceneName, id); + if (save.Count > 0) + { + float xPos = 0; + float yPos = 0; + if (save.TryGetValue("globalPositionX", out Variant xPosVar)) + { + xPos = xPosVar.AsSingle(); + } + + if (save.TryGetValue("globalPositionY", out Variant yPosVar)) + { + yPos = yPosVar.AsSingle(); + } + + GlobalPosition = new Vector2(xPos, yPos); + } + } + + #endregion } \ No newline at end of file -- 2.36.3 From b4013b1ff2c8ba21c79032cc8d418a4cb3010dd8 Mon Sep 17 00:00:00 2001 From: kziolkowski Date: Tue, 25 Nov 2025 13:34:22 +0100 Subject: [PATCH 11/18] :feature: made GetComponent nullable in NodeExtension --- scripts/CSharp/Common/Util/NodeExtension.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/CSharp/Common/Util/NodeExtension.cs b/scripts/CSharp/Common/Util/NodeExtension.cs index 52e8477..30ceb96 100644 --- a/scripts/CSharp/Common/Util/NodeExtension.cs +++ b/scripts/CSharp/Common/Util/NodeExtension.cs @@ -56,7 +56,7 @@ public static class NodeExtension /// /// /// - public static T GetComponent(Node node) + public static T? GetComponent(Node node) { if (node is T) { -- 2.36.3 From 638ebaff46a68ca0d29042b1bcdd5d6733e75cf7 Mon Sep 17 00:00:00 2001 From: kziolkowski Date: Tue, 25 Nov 2025 13:46:46 +0100 Subject: [PATCH 12/18] :memo: Added documentation --- scripts/CSharp/Common/Savegame/ISaveable.cs | 2 ++ scripts/CSharp/Common/Savegame/SaveIDProviderTool.cs | 8 +++++++- scripts/CSharp/Common/Temp/MVPDuck.cs | 7 +++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/scripts/CSharp/Common/Savegame/ISaveable.cs b/scripts/CSharp/Common/Savegame/ISaveable.cs index 060b1b0..5e9f37b 100644 --- a/scripts/CSharp/Common/Savegame/ISaveable.cs +++ b/scripts/CSharp/Common/Savegame/ISaveable.cs @@ -2,6 +2,8 @@ namespace Babushka.scripts.CSharp.Common.Savegame; /// /// Defines the behaviour of Nodes that have fields that should save / load to disk. +/// When implementing new Saveable objects, please beware: Please check if object instances need to be identified separately. +/// If so, make sure to give them a proper ID. You can use the tool for that. /// public interface ISaveable { diff --git a/scripts/CSharp/Common/Savegame/SaveIDProviderTool.cs b/scripts/CSharp/Common/Savegame/SaveIDProviderTool.cs index 5ba38ed..9b7de2e 100644 --- a/scripts/CSharp/Common/Savegame/SaveIDProviderTool.cs +++ b/scripts/CSharp/Common/Savegame/SaveIDProviderTool.cs @@ -4,9 +4,16 @@ using Godot.Collections; namespace Babushka.scripts.CSharp.Common.Savegame; +/// +/// And editor tool that lives in the scene scope and iterates over saveable nodes and assigns them unique IDs where necessary. +/// It only works if the object (prefab) in question has been added to the "Saveable"-group beforehand. +/// [Tool] public partial class SaveIDProviderTool : Node { + /// + /// Creates an inspector button that calls the AssignIDs method. + /// [ExportToolButton("Assign IDs")] private Callable assignIDs => Callable.From(AssignIDs); private void AssignIDs() @@ -15,7 +22,6 @@ public partial class SaveIDProviderTool : Node foreach (var node in saveables) { GD.Print($"Checking {node.Name}."); - GD.Print($"Node has Meta SaveID: {node.HasMeta("SaveID")} and it's: " + node.GetMeta("SaveID").AsString()); if (!node.HasMeta("SaveID") || string.IsNullOrEmpty(node.GetMeta("SaveID").AsString())) { string saveID = Guid.NewGuid().ToString(); diff --git a/scripts/CSharp/Common/Temp/MVPDuck.cs b/scripts/CSharp/Common/Temp/MVPDuck.cs index d0e8f69..eb34f46 100644 --- a/scripts/CSharp/Common/Temp/MVPDuck.cs +++ b/scripts/CSharp/Common/Temp/MVPDuck.cs @@ -12,6 +12,7 @@ namespace Babushka.scripts.CSharp.Common.Temp; public partial class MVPDuck : Node2D, ISaveable { [ExportGroup("Persistence")] + // A container for the current scene. Used to automatically add the current scene context to the save ID. [Export] public VariableResource _sceneKeyProvider; [ExportGroup("Animation")] @@ -56,6 +57,9 @@ public partial class MVPDuck : Node2D, ISaveable #region SAVE AND LOAD + /// + // Saves duck position. + /// public void UpdateSaveData() { var payloadData = new Dictionary @@ -69,6 +73,9 @@ public partial class MVPDuck : Node2D, ISaveable SavegameService.AppendDataToSave(_sceneKeyProvider.Payload.AsString(), id, payloadData); } + /// + /// Loads duck position. + /// public void LoadFromSaveData() { var sceneName = _sceneKeyProvider.Payload.AsString(); -- 2.36.3 From e9cd4ce276ed5f68956348a76c10faf7cf62a456 Mon Sep 17 00:00:00 2001 From: kziolkowski Date: Tue, 25 Nov 2025 14:39:31 +0100 Subject: [PATCH 13/18] :recycle: reworked and debugged the SaveSystem. Removed unnecessary scene reference. --- .../CSharp/Common/Farming/FieldBehaviour2D.cs | 5 ++- .../Common/Inventory/InventoryInstance.cs | 8 ++--- scripts/CSharp/Common/Savegame/SaveData.cs | 3 +- .../CSharp/Common/Savegame/SavegameService.cs | 34 +++++++++++-------- scripts/CSharp/Common/Temp/MVPDuck.cs | 9 ++--- 5 files changed, 27 insertions(+), 32 deletions(-) diff --git a/scripts/CSharp/Common/Farming/FieldBehaviour2D.cs b/scripts/CSharp/Common/Farming/FieldBehaviour2D.cs index ad422da..78e575a 100644 --- a/scripts/CSharp/Common/Farming/FieldBehaviour2D.cs +++ b/scripts/CSharp/Common/Farming/FieldBehaviour2D.cs @@ -202,15 +202,14 @@ public partial class FieldBehaviour2D : Sprite2D, ISaveable ); } - SavegameService.AppendDataToSave(_sceneKeyProvider.Payload.AsString(), SaveId + _fieldIndex.Payload.AsString(), payloadData); + SavegameService.AppendDataToSave(SaveId + _fieldIndex.Payload.AsString(), payloadData); } public void LoadFromSaveData() { - var sceneName = _sceneKeyProvider.Payload.AsString(); var id = SaveId + _fieldIndex.Payload.AsString(); - Dictionary save = SavegameService.GetSaveData(sceneName, id); + Dictionary save = SavegameService.GetSaveData(id); if (save.Count > 0) { diff --git a/scripts/CSharp/Common/Inventory/InventoryInstance.cs b/scripts/CSharp/Common/Inventory/InventoryInstance.cs index 8d235da..f6c0cea 100644 --- a/scripts/CSharp/Common/Inventory/InventoryInstance.cs +++ b/scripts/CSharp/Common/Inventory/InventoryInstance.cs @@ -17,8 +17,7 @@ public partial class InventoryInstance : Node, ISaveable [Signal] public delegate void InventoryContentsChangedEventHandler(); - - private const string SCENE_NAME = "inventory"; + private const string ID = "instace"; /// @@ -192,15 +191,14 @@ public partial class InventoryInstance : Node, ISaveable } } - SavegameService.AppendDataToSave(SCENE_NAME, ID, payloadData); + SavegameService.AppendDataToSave(ID, payloadData); } public void LoadFromSaveData() { - var sceneName = SCENE_NAME; var id = ID; - Godot.Collections.Dictionary save = SavegameService.GetSaveData(sceneName, id); + Godot.Collections.Dictionary save = SavegameService.GetSaveData(id); if (save.Count > 0) { diff --git a/scripts/CSharp/Common/Savegame/SaveData.cs b/scripts/CSharp/Common/Savegame/SaveData.cs index a26c1d9..412dc95 100644 --- a/scripts/CSharp/Common/Savegame/SaveData.cs +++ b/scripts/CSharp/Common/Savegame/SaveData.cs @@ -18,12 +18,11 @@ public class SaveData return VERSION == version; } - public string SceneName; public string Id; public string JsonPayload; public string ToString() { - return SceneName + " " + Id + " " + JsonPayload; + return Id + " " + JsonPayload; } } \ No newline at end of file diff --git a/scripts/CSharp/Common/Savegame/SavegameService.cs b/scripts/CSharp/Common/Savegame/SavegameService.cs index c8504e5..36a7e65 100644 --- a/scripts/CSharp/Common/Savegame/SavegameService.cs +++ b/scripts/CSharp/Common/Savegame/SavegameService.cs @@ -20,11 +20,9 @@ public static class SavegameService public static bool _loaded = false; - public static void AppendDataToSave(string scenename, string id, Dictionary payload) + public static void AppendDataToSave( string id, Dictionary payload) { var saveData = new SaveData(); - - saveData.SceneName = scenename; saveData.Id = id; saveData.JsonPayload = Json.Stringify(payload, indent: "\t"); AppendSave(saveData); @@ -36,15 +34,13 @@ public static class SavegameService /// private static void AppendSave(SaveData saveData) { - string key = string.Concat(saveData.SceneName, "_", saveData.Id); - - if (SaveDatas.TryGetValue(key, out var value)) + if (SaveDatas.TryGetValue(saveData.Id, out var value)) { - SaveDatas[key] = saveData.JsonPayload; + SaveDatas[saveData.Id] = saveData.JsonPayload; } else { - SaveDatas.Add(key, saveData.JsonPayload); + SaveDatas.Add(saveData.Id, saveData.JsonPayload); } } @@ -56,11 +52,10 @@ public static class SavegameService /// /// /// - public static Dictionary GetSaveData(string sceneName, string id) + public static Dictionary GetSaveData(string id) { Dictionary saveData = new(); - string key = string.Concat(sceneName, "_", id); string saveDataString = ""; if (!_loaded) @@ -69,9 +64,9 @@ public static class SavegameService return saveData; } - if (SaveDatas.ContainsKey(key)) + if (SaveDatas.ContainsKey(id)) { - saveDataString = SaveDatas[key]; + saveDataString = SaveDatas[id]; saveData = Json.ParseString(saveDataString).AsGodotDictionary(); } @@ -88,8 +83,10 @@ public static class SavegameService GD.Print(SavePath); // create cloud directory - System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(SavePath) ?? ""); + CreateSaveDirectory(); + GD.Print("Save."); + try { using var file = FileAccess.Open(SavePath, FileAccess.ModeFlags.Write); @@ -101,21 +98,28 @@ public static class SavegameService } } + private static void CreateSaveDirectory() + { + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(SavePath) ?? ""); + } + /// /// Loads the current savegame file from disk and parses it into the SaveData dictionary. /// public static void Load() { + GD.Print("Load."); try { if (!System.IO.File.Exists(SavePath)) { SaveDatas = new(); - return; + CreateSaveDirectory(); } string saveDataJson = FileAccess.GetFileAsString(SavePath); - SaveDatas = Json.ParseString(saveDataJson).AsGodotDictionary(); + if(!string.IsNullOrEmpty(saveDataJson)) + SaveDatas = Json.ParseString(saveDataJson).AsGodotDictionary(); } catch(Exception e) diff --git a/scripts/CSharp/Common/Temp/MVPDuck.cs b/scripts/CSharp/Common/Temp/MVPDuck.cs index eb34f46..4ef5fc7 100644 --- a/scripts/CSharp/Common/Temp/MVPDuck.cs +++ b/scripts/CSharp/Common/Temp/MVPDuck.cs @@ -11,10 +11,6 @@ namespace Babushka.scripts.CSharp.Common.Temp; /// public partial class MVPDuck : Node2D, ISaveable { - [ExportGroup("Persistence")] - // A container for the current scene. Used to automatically add the current scene context to the save ID. - [Export] public VariableResource _sceneKeyProvider; - [ExportGroup("Animation")] [Export] private Node2D _penTarget; [Export] private int _transferDelayMs; @@ -70,7 +66,7 @@ public partial class MVPDuck : Node2D, ISaveable string id = GetMeta("SaveID").AsString(); GD.Print($"Updating Save ID for object {Name}: {id}"); - SavegameService.AppendDataToSave(_sceneKeyProvider.Payload.AsString(), id, payloadData); + SavegameService.AppendDataToSave( id, payloadData); } /// @@ -78,11 +74,10 @@ public partial class MVPDuck : Node2D, ISaveable /// public void LoadFromSaveData() { - var sceneName = _sceneKeyProvider.Payload.AsString(); string id = GetMeta("SaveID").AsString(); GD.Print($"Loading Save ID for object {Name}: " + id); - Dictionary save = SavegameService.GetSaveData(sceneName, id); + Dictionary save = SavegameService.GetSaveData(id); if (save.Count > 0) { float xPos = 0; -- 2.36.3 From 9d818e5079762e3230f4d1dec6cf1be54376e294 Mon Sep 17 00:00:00 2001 From: kziolkowski Date: Tue, 25 Nov 2025 15:29:36 +0100 Subject: [PATCH 14/18] :recycle: refactored field code to work with the new id system --- prefabs/farm/base_field.tscn | 6 +- project.godot | 1 - scenes/Babushka_scene_farm_outside_2d.tscn | 32 +++++- scenes/Babushka_scene_outside_beets.tscn | 30 +++++- .../CSharp/Common/Farming/FieldBehaviour2D.cs | 9 +- scripts/CSharp/Common/Farming/FieldService.cs | 100 ------------------ .../CSharp/Common/Farming/FieldService.cs.uid | 1 - 7 files changed, 66 insertions(+), 113 deletions(-) delete mode 100644 scripts/CSharp/Common/Farming/FieldService.cs delete mode 100644 scripts/CSharp/Common/Farming/FieldService.cs.uid diff --git a/prefabs/farm/base_field.tscn b/prefabs/farm/base_field.tscn index f000cf4..55ee78b 100644 --- a/prefabs/farm/base_field.tscn +++ b/prefabs/farm/base_field.tscn @@ -28,7 +28,7 @@ resource_local_to_scene = true radius = 325.2599 -[node name="BaseField" type="Node2D"] +[node name="BaseField" type="Node2D" groups=["Saveable"]] script = ExtResource("1_4mg73") Payload = 0 @@ -38,13 +38,13 @@ z_index = 1 scale = Vector2(1.3499999, 1.5) texture = ExtResource("9_wx561") -[node name="FieldBehaviour" type="Sprite2D" parent="." node_paths=PackedStringArray("_fieldIndex", "_fieldSprite", "_maskSprite", "_outlineSprite", "PlantingInteraction", "FieldInteractionArea", "PlantingPlaceholder", "_wateringParticles")] +[node name="FieldBehaviour" type="Sprite2D" parent="." node_paths=PackedStringArray("_fieldIndex", "_saveIdHolder", "_fieldSprite", "_maskSprite", "_outlineSprite", "PlantingInteraction", "FieldInteractionArea", "PlantingPlaceholder", "_wateringParticles")] visible = false z_index = -1 scale = Vector2(0.9, 1) script = ExtResource("1_qa01x") -SaveId = "field" _fieldIndex = NodePath("..") +_saveIdHolder = NodePath("..") _sceneKeyProvider = ExtResource("11_cjahb") FieldState = 0 _fieldSprite = NodePath("MaskedField/FieldTexture") diff --git a/project.godot b/project.godot index 7d188a7..a88b31e 100644 --- a/project.godot +++ b/project.godot @@ -32,7 +32,6 @@ InputService="*res://scripts/CSharp/Common/Services/InputService.cs" QuestManager="*res://prefabs/quests/quest_manager_autoload.tscn" Signal_Debugger="*res://addons/SignalVisualizer/Debugger/SignalDebugger.gd" FightWorldAutoload="*res://prefabs/fight/fight_world_autoload.tscn" -FieldService="*res://scripts/CSharp/Common/Farming/FieldService.cs" SaveGameManager="*res://scripts/CSharp/Common/Savegame/SaveGameManager.cs" SettingsSaveController="*res://scripts/CSharp/Common/Savegame/SettingsSaveController.cs" diff --git a/scenes/Babushka_scene_farm_outside_2d.tscn b/scenes/Babushka_scene_farm_outside_2d.tscn index 91f359e..9a09b1e 100644 --- a/scenes/Babushka_scene_farm_outside_2d.tscn +++ b/scenes/Babushka_scene_farm_outside_2d.tscn @@ -1142,58 +1142,72 @@ position = Vector2(0, -200) [node name="BaseField" parent="YSorted/FieldParent/right" instance=ExtResource("78_xcwle")] position = Vector2(8807, 3061) +metadata/SaveID = "533e356b-386b-49c9-beb1-4484f2a5164f" [node name="BaseField2" parent="YSorted/FieldParent/right" instance=ExtResource("78_xcwle")] position = Vector2(9335, 3562) Payload = 1 +metadata/SaveID = "79084490-9e12-4153-9ae8-6162b5348c37" [node name="BaseField3" parent="YSorted/FieldParent/right" instance=ExtResource("78_xcwle")] position = Vector2(9854, 3071) Payload = 2 +metadata/SaveID = "7332aadf-2583-468b-889c-ccbad96137f8" [node name="BaseField4" parent="YSorted/FieldParent/right" instance=ExtResource("78_xcwle")] position = Vector2(10361, 3536) Payload = 3 +metadata/SaveID = "493f7b1f-3d01-4d66-b668-6014ef9b3c15" [node name="BaseField5" parent="YSorted/FieldParent/right" instance=ExtResource("78_xcwle")] position = Vector2(10948, 3025) Payload = 4 +metadata/SaveID = "ba9d21eb-7c31-4459-bd90-ae23e9a09eb1" [node name="BaseField6" parent="YSorted/FieldParent/right" instance=ExtResource("78_xcwle")] position = Vector2(11296, 3607) Payload = 5 +metadata/SaveID = "e6a3367e-afe1-4c5d-abd9-153cc365dce0" [node name="BaseField7" parent="YSorted/FieldParent/right" instance=ExtResource("78_xcwle")] position = Vector2(11869, 3026) Payload = 6 +metadata/SaveID = "08de526f-e22f-42a4-a3f9-549d2dc15b5d" [node name="BaseField8" parent="YSorted/FieldParent/right" instance=ExtResource("78_xcwle")] position = Vector2(12353, 3554) Payload = 7 +metadata/SaveID = "9fd46633-8028-48e6-9176-ffe8c7a26a2e" [node name="BaseField9" parent="YSorted/FieldParent/right" instance=ExtResource("78_xcwle")] position = Vector2(12828, 2999) Payload = 8 +metadata/SaveID = "82e139eb-47f4-4ce4-962f-f72d22f9fe45" [node name="BaseField10" parent="YSorted/FieldParent/right" instance=ExtResource("78_xcwle")] position = Vector2(13285, 3536) Payload = 9 +metadata/SaveID = "03ff923a-614a-4fc9-8865-f5290ec16169" [node name="BaseField11" parent="YSorted/FieldParent/right" instance=ExtResource("78_xcwle")] position = Vector2(13733, 2990) Payload = 10 +metadata/SaveID = "1347c031-e8c0-44f9-9d42-80084ae9bc29" [node name="BaseField12" parent="YSorted/FieldParent/right" instance=ExtResource("78_xcwle")] position = Vector2(14261, 3474) Payload = 11 +metadata/SaveID = "c0b02859-bd5c-4780-8ca7-80b4234ee1d7" [node name="BaseField13" parent="YSorted/FieldParent/right" instance=ExtResource("78_xcwle")] position = Vector2(14753, 2982) Payload = 12 +metadata/SaveID = "bee82b2a-a437-4d3a-b003-dac55dfd24aa" [node name="BaseField14" parent="YSorted/FieldParent/right" instance=ExtResource("78_xcwle")] position = Vector2(15201, 3519) Payload = 13 +metadata/SaveID = "4750a191-6902-46d4-9b2e-964c1aa0a29c" [node name="left" type="Node2D" parent="YSorted/FieldParent"] position = Vector2(-8661, -143) @@ -1201,50 +1215,62 @@ position = Vector2(-8661, -143) [node name="BaseField" parent="YSorted/FieldParent/left" instance=ExtResource("78_xcwle")] position = Vector2(8807, 3061) Payload = 14 +metadata/SaveID = "b44ef2e7-898a-40d0-975b-ba75d7df4601" [node name="BaseField2" parent="YSorted/FieldParent/left" instance=ExtResource("78_xcwle")] position = Vector2(9227, 3562) Payload = 15 +metadata/SaveID = "5705ba02-f444-4f2b-b527-b95578132b87" [node name="BaseField3" parent="YSorted/FieldParent/left" instance=ExtResource("78_xcwle")] position = Vector2(9756, 3111) Payload = 16 +metadata/SaveID = "bca9073b-0b2f-4371-a645-a37aa04f4e23" [node name="BaseField4" parent="YSorted/FieldParent/left" instance=ExtResource("78_xcwle")] position = Vector2(10322, 3536) Payload = 17 +metadata/SaveID = "f636a1ef-016d-4361-9b6e-1d59262a8c04" [node name="BaseField5" parent="YSorted/FieldParent/left" instance=ExtResource("78_xcwle")] position = Vector2(10810, 3055) Payload = 18 +metadata/SaveID = "d074a30d-3f52-4788-879a-4ea8c5818c32" [node name="BaseField6" parent="YSorted/FieldParent/left" instance=ExtResource("78_xcwle")] position = Vector2(11266, 3607) Payload = 19 +metadata/SaveID = "8296a15c-be4a-4127-b0f7-8a330dd2a843" [node name="BaseField7" parent="YSorted/FieldParent/left" instance=ExtResource("78_xcwle")] position = Vector2(11741, 3026) Payload = 20 +metadata/SaveID = "02468d9f-0120-42ff-806d-0aaa351f055c" [node name="BaseField8" parent="YSorted/FieldParent/left" instance=ExtResource("78_xcwle")] position = Vector2(12255, 3593) Payload = 21 +metadata/SaveID = "803f7df5-23a8-424a-8389-08371ea80101" [node name="BaseField9" parent="YSorted/FieldParent/left" instance=ExtResource("78_xcwle")] position = Vector2(12690, 3019) Payload = 22 +metadata/SaveID = "88efb617-5e57-4b16-8ceb-c45c56463690" [node name="BaseField10" parent="YSorted/FieldParent/left" instance=ExtResource("78_xcwle")] position = Vector2(13216, 3556) Payload = 23 +metadata/SaveID = "ca6c1e3d-2c4e-4977-a89e-55d265f65faf" [node name="BaseField11" parent="YSorted/FieldParent/left" instance=ExtResource("78_xcwle")] position = Vector2(13684, 3000) Payload = 24 +metadata/SaveID = "262fbec5-597d-432e-8dd0-50d53fd51b59" [node name="BaseField12" parent="YSorted/FieldParent/left" instance=ExtResource("78_xcwle")] position = Vector2(14143, 3523) Payload = 25 +metadata/SaveID = "05389ea3-d19b-42fc-8bf0-b0755437ec10" [node name="Farm visuals" type="Node2D" parent="YSorted"] position = Vector2(-60, 122) @@ -2542,10 +2568,12 @@ script = ExtResource("76_l7ekk") _variableResource = ExtResource("77_xcwle") _payloadToSet = "farmOutside" -[node name="SaveGameCheat" type="Node" parent="."] +[node name="SaveSystem" type="Node" parent="."] + +[node name="SaveGameCheat" type="Node" parent="SaveSystem"] script = ExtResource("79_065st") -[node name="SaveIDProvider" type="Node" parent="."] +[node name="SaveIDProvider" type="Node" parent="SaveSystem"] script = ExtResource("80_w1kgo") [connection signal="FilledWateringCan" from="YSorted/Vesna" to="Audio/SFX/FillWater SFX2" method="PlayOneShot"] diff --git a/scenes/Babushka_scene_outside_beets.tscn b/scenes/Babushka_scene_outside_beets.tscn index a259778..cb7f0da 100644 --- a/scenes/Babushka_scene_outside_beets.tscn +++ b/scenes/Babushka_scene_outside_beets.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=96 format=3 uid="uid://b3ibx4resa1f3"] +[gd_scene load_steps=98 format=3 uid="uid://b3ibx4resa1f3"] [ext_resource type="Script" uid="uid://cssdu8viimwm6" path="res://scripts/CSharp/Common/SceneTransition.cs" id="1_6krrk"] [ext_resource type="Script" uid="uid://bqomwxclsbhd3" path="res://scripts/CSharp/Common/Camera/CameraController.cs" id="2_4ktoi"] @@ -52,7 +52,9 @@ [ext_resource type="AudioStream" uid="uid://bxh5m04vdo0sr" path="res://audio/sfx/Farming/SFX_Harke_04_Solo.wav" id="57_euap5"] [ext_resource type="Resource" uid="uid://tt3d166mntmi" path="res://resources/low code/farming/var_sceneNameProvider.tres" id="57_hpgl7"] [ext_resource type="Script" uid="uid://cfnrd5k1k0gxw" path="res://scripts/CSharp/Common/AudioPlayer2D.cs" id="58_m3hs4"] +[ext_resource type="Script" uid="uid://iquhbkr7pqeg" path="res://scripts/CSharp/Common/Savegame/SaveCheats.cs" id="58_qavgq"] [ext_resource type="AudioStream" uid="uid://dapsknn486aee" path="res://audio/sfx/Farming/SFX_WateringPlants_01.wav" id="59_km2vg"] +[ext_resource type="Script" uid="uid://ca4s0algeij1h" path="res://scripts/CSharp/Common/Savegame/SaveIDProviderTool.cs" id="59_njxly"] [ext_resource type="AudioStream" uid="uid://dnyne8wov50so" path="res://audio/sfx/Farming/SFX_WateringPlants_02.wav" id="60_qi2gu"] [ext_resource type="AudioStream" uid="uid://fsiypqhql67w" path="res://audio/sfx/Farming/SFX_GettingWater_01.wav" id="61_wy1mx"] [ext_resource type="AudioStream" uid="uid://foyw26hq1qp5" path="res://audio/sfx/Farming/SFX_GettingWater_02.wav" id="62_kmjnt"] @@ -1749,9 +1751,11 @@ scale = Vector2(1, 0.993819) [node name="BaseField" parent="YSorted/Farm visuals/FieldParent" instance=ExtResource("40_efblm")] position = Vector2(651, 2630.26) scale = Vector2(1, 1.00622) +metadata/SaveID = "b3b47e69-6115-4405-8da6-508b783823d2" [node name="FieldBehaviour" parent="YSorted/Farm visuals/FieldParent/BaseField" index="1"] visible = true +metadata/SaveID = "7a8210bf-479d-4b4c-9758-98d23e59d5d7" [node name="Beet2" parent="YSorted/Farm visuals/FieldParent/BaseField/FieldBehaviour/PlantPlaceholder" index="0" node_paths=PackedStringArray("_field") instance=ExtResource("41_vyqmy")] _state = 2 @@ -1767,9 +1771,11 @@ shape = SubResource("CircleShape2D_qavgq") position = Vector2(1226, 3098.15) scale = Vector2(1, 1.00622) Payload = 1 +metadata/SaveID = "f536efd3-3da8-4ef5-a520-570220e6c19f" [node name="FieldBehaviour" parent="YSorted/Farm visuals/FieldParent/BaseField2" index="1"] visible = true +metadata/SaveID = "a1d8a0a2-c51c-4410-83c3-0edb31cbf2de" [node name="Beet2" parent="YSorted/Farm visuals/FieldParent/BaseField2/FieldBehaviour/PlantPlaceholder" index="0" node_paths=PackedStringArray("_field") instance=ExtResource("41_vyqmy")] _state = 2 @@ -1785,9 +1791,11 @@ shape = SubResource("CircleShape2D_njxly") position = Vector2(1782, 2606.11) scale = Vector2(1, 1.00622) Payload = 2 +metadata/SaveID = "14ce64d5-8a4d-43b9-a8ff-3a57725c4dc8" [node name="FieldBehaviour" parent="YSorted/Farm visuals/FieldParent/BaseField3" index="1"] visible = true +metadata/SaveID = "79a65532-cb2b-4268-8eb0-8c41a3935cbb" [node name="Beet2" parent="YSorted/Farm visuals/FieldParent/BaseField3/FieldBehaviour/PlantPlaceholder" index="0" node_paths=PackedStringArray("_field") instance=ExtResource("41_vyqmy")] _state = 2 @@ -1803,10 +1811,12 @@ shape = SubResource("CircleShape2D_54ty3") position = Vector2(2559, 2624.22) scale = Vector2(1, 1.00622) Payload = 3 +metadata/SaveID = "6872ce5d-6f42-47d7-97e2-a7214ff3b08c" [node name="FieldBehaviour" parent="YSorted/Farm visuals/FieldParent/BaseField4" index="1"] visible = true FieldState = 3 +metadata/SaveID = "13fa64a3-01dc-4fd8-822e-0839c0da3163" [node name="Beet2" parent="YSorted/Farm visuals/FieldParent/BaseField4/FieldBehaviour/PlantPlaceholder" index="0" node_paths=PackedStringArray("_field") instance=ExtResource("41_vyqmy")] _state = 2 @@ -1822,10 +1832,12 @@ shape = SubResource("CircleShape2D_gbxtf") position = Vector2(3305, 2624.22) scale = Vector2(1, 1.00622) Payload = 4 +metadata/SaveID = "f163c38d-9ee0-4844-9def-479833febb4e" [node name="FieldBehaviour" parent="YSorted/Farm visuals/FieldParent/BaseField5" index="1"] visible = true FieldState = 3 +metadata/SaveID = "414ecdb9-f3c4-4fb9-81bd-6ca575784f78" [node name="Beet2" parent="YSorted/Farm visuals/FieldParent/BaseField5/FieldBehaviour/PlantPlaceholder" index="0" node_paths=PackedStringArray("_field") instance=ExtResource("41_vyqmy")] _state = 2 @@ -1841,10 +1853,12 @@ shape = SubResource("CircleShape2D_6krrk") position = Vector2(4033, 2618.18) scale = Vector2(1, 1.00622) Payload = 5 +metadata/SaveID = "da691381-ee82-4f86-abf3-dda2c9f19337" [node name="FieldBehaviour" parent="YSorted/Farm visuals/FieldParent/BaseField6" index="1"] visible = true FieldState = 3 +metadata/SaveID = "b235febb-b231-4f8c-94e9-1f9d9f115b08" [node name="Beet2" parent="YSorted/Farm visuals/FieldParent/BaseField6/FieldBehaviour/PlantPlaceholder" index="0" node_paths=PackedStringArray("_field") instance=ExtResource("41_vyqmy")] _state = 1 @@ -1860,10 +1874,12 @@ shape = SubResource("CircleShape2D_4ktoi") position = Vector2(4755, 2630.26) scale = Vector2(1, 1.00622) Payload = 6 +metadata/SaveID = "8116fa8b-b164-4d39-a1ed-6cd476d18f94" [node name="FieldBehaviour" parent="YSorted/Farm visuals/FieldParent/BaseField7" index="1"] visible = true FieldState = 3 +metadata/SaveID = "9ccdba91-812a-4c13-9ee0-12472e174fc9" [node name="Beet2" parent="YSorted/Farm visuals/FieldParent/BaseField7/FieldBehaviour/PlantPlaceholder" index="0" node_paths=PackedStringArray("_field") instance=ExtResource("41_vyqmy")] _state = 2 @@ -1879,10 +1895,12 @@ shape = SubResource("CircleShape2D_aaup4") position = Vector2(4418, 3226.95) scale = Vector2(1, 1.00622) Payload = 7 +metadata/SaveID = "5298f423-4c3b-45b5-94fb-d4aef36bac21" [node name="FieldBehaviour" parent="YSorted/Farm visuals/FieldParent/BaseField8" index="1"] visible = true FieldState = 3 +metadata/SaveID = "da44e54a-0b76-4888-ad8b-782a9d146fa3" [node name="Beet2" parent="YSorted/Farm visuals/FieldParent/BaseField8/FieldBehaviour/PlantPlaceholder" index="0" node_paths=PackedStringArray("_field") instance=ExtResource("41_vyqmy")] _state = 2 @@ -1898,9 +1916,11 @@ shape = SubResource("CircleShape2D_v10dc") position = Vector2(5317, 3208.83) scale = Vector2(1, 1.00622) Payload = 8 +metadata/SaveID = "0de8dc13-5851-4471-be35-309cd6687ebc" [node name="FieldBehaviour" parent="YSorted/Farm visuals/FieldParent/BaseField9" index="1"] visible = true +metadata/SaveID = "9919b294-04a1-4a16-9783-38c8aa0e291c" [node name="FieldActivator" parent="YSorted/Farm visuals/FieldParent/BaseField9" index="7"] visible = false @@ -2031,6 +2051,14 @@ script = ExtResource("56_34r5t") _variableResource = ExtResource("57_hpgl7") _payloadToSet = "beetRootScene" +[node name="SaveSystem" type="Node" parent="."] + +[node name="SaveGameCheat" type="Node" parent="SaveSystem"] +script = ExtResource("58_qavgq") + +[node name="SaveIDProvider" type="Node" parent="SaveSystem"] +script = ExtResource("59_njxly") + [connection signal="FilledWateringCan" from="YSorted/Vesna" to="Audio/SFX/FillWater SFX2" method="PlayOneShot"] [connection signal="InteractedTool" from="YSorted/BrĂ¼nnen/InteractionArea" to="YSorted/Vesna" method="TryFillWateringCan"] [connection signal="InteractedTool" from="YSorted/Blocker/BackToFarm" to="." method="LoadSceneAtIndex"] diff --git a/scripts/CSharp/Common/Farming/FieldBehaviour2D.cs b/scripts/CSharp/Common/Farming/FieldBehaviour2D.cs index 78e575a..52b02a5 100644 --- a/scripts/CSharp/Common/Farming/FieldBehaviour2D.cs +++ b/scripts/CSharp/Common/Farming/FieldBehaviour2D.cs @@ -16,8 +16,8 @@ namespace Babushka.scripts.CSharp.Common.Farming; public partial class FieldBehaviour2D : Sprite2D, ISaveable { [ExportGroup("Persistence")] - [Export] public string SaveId = ""; [Export] private VariableNode _fieldIndex; + [Export] private Node _saveIdHolder; [Export] public VariableResource _sceneKeyProvider; [Export] public FieldState FieldState = FieldState.Tilled; @@ -78,8 +78,6 @@ public partial class FieldBehaviour2D : Sprite2D, ISaveable _currentPlant = PlantingPlaceholder.GetChild(0); UpdateFieldState(FieldState); - FieldService.Instance.TryAddEntry(_sceneKeyProvider.Payload.AsString(),_fieldIndex.Payload.AsInt32(), this); - int randomIndex = new Random().Next(0, _maskTexture.Length); _maskSprite.Texture = _maskTexture[randomIndex]; _outlineSprite.Texture = _maskOutlineTextures[randomIndex]; @@ -202,12 +200,13 @@ public partial class FieldBehaviour2D : Sprite2D, ISaveable ); } - SavegameService.AppendDataToSave(SaveId + _fieldIndex.Payload.AsString(), payloadData); + string id = _saveIdHolder.GetMeta("SaveID").AsString(); + SavegameService.AppendDataToSave(id, payloadData); } public void LoadFromSaveData() { - var id = SaveId + _fieldIndex.Payload.AsString(); + string id = _saveIdHolder.GetMeta("SaveID").AsString(); Dictionary save = SavegameService.GetSaveData(id); diff --git a/scripts/CSharp/Common/Farming/FieldService.cs b/scripts/CSharp/Common/Farming/FieldService.cs deleted file mode 100644 index 197e7c9..0000000 --- a/scripts/CSharp/Common/Farming/FieldService.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System.Collections.Generic; -using Godot; - -namespace Babushka.scripts.CSharp.Common.Farming; - -public partial class FieldService : Node -{ - private Dictionary? _outerDict = null!; - public static FieldService Instance { get; private set; } = null!; - - public override void _EnterTree() - { - Instance = this; - _outerDict = new Dictionary(); - } - - public override void _ExitTree() - { - Instance = null; - _outerDict = null; - } - - - //Create - public bool TryAddEntry(string sceneName, int fieldIndex, FieldBehaviour2D field) - { - if (_outerDict != null ) - { - FieldsInScene innerDict; - bool outerDictEntryExists = _outerDict.TryGetValue(sceneName, out innerDict); - - if (!outerDictEntryExists) - { - innerDict = new FieldsInScene(); - _outerDict.Add(sceneName, innerDict); - } - - if (!innerDict.fields.ContainsKey(fieldIndex)) - { - innerDict.fields.Add(fieldIndex, field); - return true; - } - } - return false; - } - - // Read - public FieldBehaviour2D? TryGet(string key, int fieldIndex) - { - if (_outerDict != null && _outerDict.TryGetValue(key, out FieldsInScene? field)) - { - if (field.fields.TryGetValue(fieldIndex, out FieldBehaviour2D? fieldInstance)) - { - return fieldInstance; - } - } - return null; - } - - - //Update - public void UpdateEntry(string key, int fieldIndex, FieldBehaviour2D state) - { - if (_outerDict != null && _outerDict.TryGetValue(key, out FieldsInScene? field)) - { - if (field.fields.ContainsKey(fieldIndex)) - { - field.fields[fieldIndex] = state; - } - else - { - TryAddEntry(key, fieldIndex, state); - } - } - } - - //Delete - public void RemoveEntry(string key, int fieldIndex) - { - if (_outerDict != null && _outerDict.TryGetValue(key, out FieldsInScene? field)) - { - if (field.fields.ContainsKey(fieldIndex)) - { - field.fields.Remove(fieldIndex); - } - } - } -} - -internal class FieldsInScene -{ - public Dictionary fields; - - public FieldsInScene() - { - fields = new Dictionary(); - } -} - - diff --git a/scripts/CSharp/Common/Farming/FieldService.cs.uid b/scripts/CSharp/Common/Farming/FieldService.cs.uid deleted file mode 100644 index 42d3b36..0000000 --- a/scripts/CSharp/Common/Farming/FieldService.cs.uid +++ /dev/null @@ -1 +0,0 @@ -uid://slo0uydmmvnu -- 2.36.3 From 88f3c90eecb0dfeb86d39081c5cba1c269f0e746 Mon Sep 17 00:00:00 2001 From: kziolkowski Date: Tue, 25 Nov 2025 16:13:17 +0100 Subject: [PATCH 15/18] :recycle: inventory works --- scripts/CSharp/Common/Inventory/InventoryInstance.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/CSharp/Common/Inventory/InventoryInstance.cs b/scripts/CSharp/Common/Inventory/InventoryInstance.cs index f6c0cea..498b297 100644 --- a/scripts/CSharp/Common/Inventory/InventoryInstance.cs +++ b/scripts/CSharp/Common/Inventory/InventoryInstance.cs @@ -18,7 +18,7 @@ public partial class InventoryInstance : Node, ISaveable [Signal] public delegate void InventoryContentsChangedEventHandler(); - private const string ID = "instace"; + private const string ID = "inventoryInstance"; /// /// The total amount of Inventoryslots in the inventory (empty and occupied). -- 2.36.3 From 70383fc16ead8a9006172e87f56f2235979e7496 Mon Sep 17 00:00:00 2001 From: kziolkowski Date: Tue, 25 Nov 2025 17:00:50 +0100 Subject: [PATCH 16/18] :sparkles: Implemented load check for unique inventory items --- .../generic_item_on_ground_2d.tscn | 2 +- resources/items/rake.tres | 1 + resources/items/wateringcan.tres | 1 + .../Common/Inventory/InventoryInstance.cs | 2 +- .../CSharp/Common/Inventory/ItemOnGround2D.cs | 75 +++++++++++++++---- .../CSharp/Common/Inventory/ItemResource.cs | 3 + 6 files changed, 69 insertions(+), 15 deletions(-) diff --git a/prefabs/interactions/generic_item_on_ground_2d.tscn b/prefabs/interactions/generic_item_on_ground_2d.tscn index 17b25e2..fedd814 100644 --- a/prefabs/interactions/generic_item_on_ground_2d.tscn +++ b/prefabs/interactions/generic_item_on_ground_2d.tscn @@ -30,7 +30,7 @@ stream_1/stream = ExtResource("7_edjam") stream_2/stream = ExtResource("8_kflfw") stream_3/stream = ExtResource("9_dltn0") -[node name="GenericItemOnGround" type="Node2D"] +[node name="GenericItemOnGround" type="Node2D" groups=["Saveable"]] z_index = 1 y_sort_enabled = true script = ExtResource("1_tlhp6") diff --git a/resources/items/rake.tres b/resources/items/rake.tres index 4df7eb0..d6959db 100644 --- a/resources/items/rake.tres +++ b/resources/items/rake.tres @@ -13,4 +13,5 @@ name = "Hoe" color = Color(0.751421, 0.329615, 0.570911, 1) icon = SubResource("AtlasTexture_i5wdx") maxStack = 1 +isUnique = true metadata/_custom_type_script = "uid://cbskymrxs6ksu" diff --git a/resources/items/wateringcan.tres b/resources/items/wateringcan.tres index d92cf6d..4159edf 100644 --- a/resources/items/wateringcan.tres +++ b/resources/items/wateringcan.tres @@ -13,4 +13,5 @@ name = "Can" color = Color(0.336269, 0.489145, 0.825324, 1) icon = SubResource("AtlasTexture_tqw18") maxStack = 1 +isUnique = true metadata/_custom_type_script = "uid://cbskymrxs6ksu" diff --git a/scripts/CSharp/Common/Inventory/InventoryInstance.cs b/scripts/CSharp/Common/Inventory/InventoryInstance.cs index 498b297..a05f778 100644 --- a/scripts/CSharp/Common/Inventory/InventoryInstance.cs +++ b/scripts/CSharp/Common/Inventory/InventoryInstance.cs @@ -18,7 +18,7 @@ public partial class InventoryInstance : Node, ISaveable [Signal] public delegate void InventoryContentsChangedEventHandler(); - private const string ID = "inventoryInstance"; + public static string ID = "inventoryInstance"; /// /// The total amount of Inventoryslots in the inventory (empty and occupied). diff --git a/scripts/CSharp/Common/Inventory/ItemOnGround2D.cs b/scripts/CSharp/Common/Inventory/ItemOnGround2D.cs index b313dae..2bbed06 100644 --- a/scripts/CSharp/Common/Inventory/ItemOnGround2D.cs +++ b/scripts/CSharp/Common/Inventory/ItemOnGround2D.cs @@ -1,8 +1,10 @@ +using Babushka.scripts.CSharp.Common.Savegame; using Godot; +using Godot.Collections; namespace Babushka.scripts.CSharp.Common.Inventory; -public partial class ItemOnGround2D : Node +public partial class ItemOnGround2D : Node, ISaveable { private ItemInstance _itemInstance; @@ -30,6 +32,7 @@ public partial class ItemOnGround2D : Node public override void _Ready() { + LoadFromSaveData(); UpdateVisuals(); _pickupErrorLabel.Text = ""; } @@ -43,24 +46,34 @@ public partial class ItemOnGround2D : Node EmitSignal(SignalName.SuccessfulPickUp); if (result == InventoryActionResult.Success) { - if (!_infiniteSupply) - { - pickUpCounter++; - if (pickUpCounter >= _finiteSupply) - { - QueueFree(); - } - } + Pickup(); } else { - _pickupErrorLabel.Text = "Inventory Full"; - var tween = GetTree().CreateTween(); - tween.TweenInterval(2); - tween.TweenCallback(Callable.From(() => _pickupErrorLabel.Text = "")); + FailToPickup(); + } + } + + private void Pickup() + { + if (!_infiniteSupply) + { + pickUpCounter++; + if (pickUpCounter >= _finiteSupply) + { + QueueFree(); + } } } + private void FailToPickup() + { + _pickupErrorLabel.Text = "Inventory Full"; + var tween = GetTree().CreateTween(); + tween.TweenInterval(2); + tween.TweenCallback(Callable.From(() => _pickupErrorLabel.Text = "")); + } + public void UpdateVisuals() { if (!IsActive) @@ -76,4 +89,40 @@ public partial class ItemOnGround2D : Node _itemLabel.Text = ""; } } + + public void UpdateSaveData() + { + // do nothing? + } + + public void LoadFromSaveData() + { + if (_infiniteSupply) + return; + + ItemResource itemResource = itemInstance.blueprint; + Dictionary savegameData = SavegameService.GetSaveData(InventoryInstance.ID); + if (savegameData.Count > 0) + { + foreach (var kvp in savegameData) + { + // if it's a unique item, then it can only exist once in the world (either as a pickup OR in the inventory) + if (itemInstance.blueprint.isUnique) + { + //comparing resource path to identify the item + string[] valuePair = kvp.Value.AsStringArray(); + if (valuePair[0] == itemResource.ResourcePath) + { + int amountInInventory = int.Parse(valuePair[1]); + // comparing amount to see if it's all in the inventory now. + if (amountInInventory > 0) + { + Pickup(); + } + } + } + + } + } + } } diff --git a/scripts/CSharp/Common/Inventory/ItemResource.cs b/scripts/CSharp/Common/Inventory/ItemResource.cs index fd1b427..01d6ae2 100644 --- a/scripts/CSharp/Common/Inventory/ItemResource.cs +++ b/scripts/CSharp/Common/Inventory/ItemResource.cs @@ -17,6 +17,9 @@ public partial class ItemResource : Resource [Export] public int maxStack; + [Export] + public bool isUnique; + public ItemResource() { name = ""; -- 2.36.3 From f37f7c7ceb8a1aa59e6b52bfea9b27190ddb42e4 Mon Sep 17 00:00:00 2001 From: kziolkowski Date: Tue, 25 Nov 2025 17:14:30 +0100 Subject: [PATCH 17/18] :sparkles: added saving and loading support to collectable inventory items --- .../generic_item_on_ground_2d.tscn | 1 + scenes/Babushka_scene_farm_outside_2d.tscn | 13 ++++---- .../CSharp/Common/Inventory/ItemOnGround2D.cs | 32 +++++++++++++++++-- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/prefabs/interactions/generic_item_on_ground_2d.tscn b/prefabs/interactions/generic_item_on_ground_2d.tscn index fedd814..2d93d9a 100644 --- a/prefabs/interactions/generic_item_on_ground_2d.tscn +++ b/prefabs/interactions/generic_item_on_ground_2d.tscn @@ -34,6 +34,7 @@ stream_3/stream = ExtResource("9_dltn0") z_index = 1 y_sort_enabled = true script = ExtResource("1_tlhp6") +metadata/SaveID = "" [node name="SpawnWithItem" type="Node" parent="."] script = ExtResource("3_xu8me") diff --git a/scenes/Babushka_scene_farm_outside_2d.tscn b/scenes/Babushka_scene_farm_outside_2d.tscn index 9a09b1e..aa1002c 100644 --- a/scenes/Babushka_scene_farm_outside_2d.tscn +++ b/scenes/Babushka_scene_farm_outside_2d.tscn @@ -1069,6 +1069,7 @@ shape = SubResource("CircleShape2D_p6n74") [node name="CanGenericPickup" parent="YSorted" instance=ExtResource("25_hukxv")] position = Vector2(8192, 3507) +metadata/SaveID = "5a823507-8107-40ce-8b32-6d0f81a3c44e" [node name="SpawnWithItem" parent="YSorted/CanGenericPickup" index="0"] _blueprint = ExtResource("28_ipqaa") @@ -1086,6 +1087,7 @@ offset = Vector2(0, -50) [node name="RakeGenericPickup" parent="YSorted" instance=ExtResource("25_hukxv")] position = Vector2(8391, 2060) +metadata/SaveID = "e13886b1-2131-4072-bc06-7d8abb19357b" [node name="SpawnWithItem" parent="YSorted/RakeGenericPickup" index="0"] _blueprint = ExtResource("28_6b2nr") @@ -1100,15 +1102,13 @@ shape = SubResource("CircleShape2D_tm0yg") [node name="SeedPickup" parent="YSorted" instance=ExtResource("25_hukxv")] position = Vector2(9927, 2257) -_infiniteSupply = true _finiteSupply = 3 +metadata/SaveID = "e1bbe13f-0622-42b8-97f3-87a8af369dc0" [node name="SpawnWithItem" parent="YSorted/SeedPickup" index="0"] _blueprint = ExtResource("35_64mdn") -[node name="PickupInteractionArea" parent="YSorted/SeedPickup" index="3" node_paths=PackedStringArray("_spritesToOutline")] -_outlineMaterial = null -_spritesToOutline = [] +[node name="PickupInteractionArea" parent="YSorted/SeedPickup" index="3"] metadata/SaveID = "ad152c51-3631-42c1-9aa4-4df896b35d8c" [node name="CollisionShape3D" parent="YSorted/SeedPickup/PickupInteractionArea/Area2D" index="0"] @@ -1120,13 +1120,12 @@ scale = Vector2(1, 1) [node name="SeedPickup2" parent="YSorted" instance=ExtResource("25_hukxv")] position = Vector2(10705, 2257) _finiteSupply = 3 +metadata/SaveID = "77972c50-63a7-461a-bc7d-6fa46333bc5c" [node name="SpawnWithItem" parent="YSorted/SeedPickup2" index="0"] _blueprint = ExtResource("36_fv1t2") -[node name="PickupInteractionArea" parent="YSorted/SeedPickup2" index="3" node_paths=PackedStringArray("_spritesToOutline")] -_outlineMaterial = null -_spritesToOutline = [] +[node name="PickupInteractionArea" parent="YSorted/SeedPickup2" index="3"] metadata/SaveID = "09e115e7-1d21-485a-be3e-b3fff9c83e78" [node name="CollisionShape3D" parent="YSorted/SeedPickup2/PickupInteractionArea/Area2D" index="0"] diff --git a/scripts/CSharp/Common/Inventory/ItemOnGround2D.cs b/scripts/CSharp/Common/Inventory/ItemOnGround2D.cs index 2bbed06..1a2cc1b 100644 --- a/scripts/CSharp/Common/Inventory/ItemOnGround2D.cs +++ b/scripts/CSharp/Common/Inventory/ItemOnGround2D.cs @@ -63,6 +63,8 @@ public partial class ItemOnGround2D : Node, ISaveable { QueueFree(); } + + UpdateSaveData(); } } @@ -92,7 +94,14 @@ public partial class ItemOnGround2D : Node, ISaveable public void UpdateSaveData() { - // do nothing? + var payloadData = new Dictionary + { + {"pickupCounter", pickUpCounter} + }; + + string id = GetMeta("SaveID").AsString(); + GD.Print($"Updating Save ID for object {Name}: {id}"); + SavegameService.AppendDataToSave( id, payloadData); } public void LoadFromSaveData() @@ -100,6 +109,26 @@ public partial class ItemOnGround2D : Node, ISaveable if (_infiniteSupply) return; + // standard check: how many times has this item been collected? + string id = GetMeta("SaveID").AsString(); + GD.Print($"Loading Save ID for object {Name}: " + id); + + Dictionary save = SavegameService.GetSaveData(id); + if (save.Count > 0) + { + if(save.TryGetValue("pickupCounter", out Variant countVar)) + { + int count = countVar.AsInt32(); + GD.Print($"Item count for {Name}: {count}"); + for (int i = 0; i < count; i++) + { + Pickup(); + GD.Print($"Picking up {Name}: {i}"); + } + } + } + + //separate check for unique items: If already in inventory, delete this instance. ItemResource itemResource = itemInstance.blueprint; Dictionary savegameData = SavegameService.GetSaveData(InventoryInstance.ID); if (savegameData.Count > 0) @@ -121,7 +150,6 @@ public partial class ItemOnGround2D : Node, ISaveable } } } - } } } -- 2.36.3 From 75e4bdd7fd932cafc87846bd6b032cf85d851660 Mon Sep 17 00:00:00 2001 From: kziolkowski Date: Tue, 25 Nov 2025 17:20:58 +0100 Subject: [PATCH 18/18] :recycle: removed Print statements --- scripts/CSharp/Common/Inventory/InventoryUi.cs | 1 - scripts/CSharp/Common/Inventory/ItemOnGround2D.cs | 4 ---- scripts/CSharp/Common/Quest/QuestTrigger.cs | 2 -- scripts/CSharp/Common/Savegame/SavegameService.cs | 5 ----- scripts/CSharp/Common/Temp/MVPDuck.cs | 2 -- 5 files changed, 14 deletions(-) diff --git a/scripts/CSharp/Common/Inventory/InventoryUi.cs b/scripts/CSharp/Common/Inventory/InventoryUi.cs index f38e8e2..690d1f5 100644 --- a/scripts/CSharp/Common/Inventory/InventoryUi.cs +++ b/scripts/CSharp/Common/Inventory/InventoryUi.cs @@ -18,7 +18,6 @@ public partial class InventoryUi : Control public override void _Ready() { - GD.Print("Ready inventory ui"); _playerInventory = InventoryManager.Instance.playerInventory; //PopulateSlots(); SubscribeSlots(); diff --git a/scripts/CSharp/Common/Inventory/ItemOnGround2D.cs b/scripts/CSharp/Common/Inventory/ItemOnGround2D.cs index 1a2cc1b..1dc21a1 100644 --- a/scripts/CSharp/Common/Inventory/ItemOnGround2D.cs +++ b/scripts/CSharp/Common/Inventory/ItemOnGround2D.cs @@ -100,7 +100,6 @@ public partial class ItemOnGround2D : Node, ISaveable }; string id = GetMeta("SaveID").AsString(); - GD.Print($"Updating Save ID for object {Name}: {id}"); SavegameService.AppendDataToSave( id, payloadData); } @@ -111,7 +110,6 @@ public partial class ItemOnGround2D : Node, ISaveable // standard check: how many times has this item been collected? string id = GetMeta("SaveID").AsString(); - GD.Print($"Loading Save ID for object {Name}: " + id); Dictionary save = SavegameService.GetSaveData(id); if (save.Count > 0) @@ -119,11 +117,9 @@ public partial class ItemOnGround2D : Node, ISaveable if(save.TryGetValue("pickupCounter", out Variant countVar)) { int count = countVar.AsInt32(); - GD.Print($"Item count for {Name}: {count}"); for (int i = 0; i < count; i++) { Pickup(); - GD.Print($"Picking up {Name}: {i}"); } } } diff --git a/scripts/CSharp/Common/Quest/QuestTrigger.cs b/scripts/CSharp/Common/Quest/QuestTrigger.cs index a828d8f..b208e2d 100644 --- a/scripts/CSharp/Common/Quest/QuestTrigger.cs +++ b/scripts/CSharp/Common/Quest/QuestTrigger.cs @@ -12,8 +12,6 @@ public partial class QuestTrigger : Node public void Trigger() { - GD.Print("trigger"); - if (questResource == null) throw new Exception("QuestResource is not set on QuestTrigger node."); diff --git a/scripts/CSharp/Common/Savegame/SavegameService.cs b/scripts/CSharp/Common/Savegame/SavegameService.cs index 36a7e65..8def82e 100644 --- a/scripts/CSharp/Common/Savegame/SavegameService.cs +++ b/scripts/CSharp/Common/Savegame/SavegameService.cs @@ -60,7 +60,6 @@ public static class SavegameService if (!_loaded) { - GD.Print("SavegameService: SaveFile not loaded."); return saveData; } @@ -80,12 +79,9 @@ public static class SavegameService public static void Save() { string json = Json.Stringify(SaveDatas, indent: "\t"); - - GD.Print(SavePath); // create cloud directory CreateSaveDirectory(); - GD.Print("Save."); try { @@ -108,7 +104,6 @@ public static class SavegameService /// public static void Load() { - GD.Print("Load."); try { if (!System.IO.File.Exists(SavePath)) diff --git a/scripts/CSharp/Common/Temp/MVPDuck.cs b/scripts/CSharp/Common/Temp/MVPDuck.cs index 4ef5fc7..1cd019d 100644 --- a/scripts/CSharp/Common/Temp/MVPDuck.cs +++ b/scripts/CSharp/Common/Temp/MVPDuck.cs @@ -65,7 +65,6 @@ public partial class MVPDuck : Node2D, ISaveable }; string id = GetMeta("SaveID").AsString(); - GD.Print($"Updating Save ID for object {Name}: {id}"); SavegameService.AppendDataToSave( id, payloadData); } @@ -75,7 +74,6 @@ public partial class MVPDuck : Node2D, ISaveable public void LoadFromSaveData() { string id = GetMeta("SaveID").AsString(); - GD.Print($"Loading Save ID for object {Name}: " + id); Dictionary save = SavegameService.GetSaveData(id); if (save.Count > 0) -- 2.36.3