using System; using Babushka.scripts.CSharp.Common.CharacterControls; using Babushka.scripts.CSharp.Common.DayAndNight; using Babushka.scripts.CSharp.Common.Inventory; using Babushka.scripts.CSharp.Common.Savegame; using Babushka.scripts.CSharp.Low_Code.Events; using Babushka.scripts.CSharp.Low_Code.Variables; using Godot; using Godot.Collections; namespace Babushka.scripts.CSharp.Common.Farming; /// /// Defines the behaviour of the field, i.e. interactions, states and effects. /// [GlobalClass] public partial class FieldBehaviour2D : Sprite2D, ISaveable { [ExportGroup("Persistence")] [Export] private VariableNode _fieldIndex; [Export] private Node _saveIdHolder; [Export] public VariableResource _sceneKeyProvider; [Export] public FieldState FieldState = FieldState.Tilled; [ExportGroup("Field Visuals")] [Export] private Sprite2D _fieldSprite; [Export] private Sprite2D _maskSprite; [Export] private Sprite2D _outlineSprite; [Export] private Texture2D[] _maskOutlineTextures; [Export] private Texture2D[] _maskTexture; [Export] private Texture2D Tilled; [Export] private Texture2D Watered; [ExportGroup("Field Interactions")] [Export] public InteractionArea2D PlantingInteraction; [ExportGroup("Configuration")] [Export] public Node2D PlantingPlaceholder; [Export] public ItemRepository ItemRepository; [Export] private CpuParticles2D _wateringParticles; [Export] private EventResource _wateringEvent; private bool _seedsActive; private bool _wateringCanActive; private bool _canPlant; private bool _canWater; private int _currentDay; public bool IsPlanted; private PlantBehaviour2D? _currentPlant; private const string DAY_COUNTER_SAVE_ID = "12c6da2e-fc71-4281-a04a-dfd3c7943975"; [Signal] public delegate void PlantedEventHandler(); private void UpdateInteractionArea() { // fieldstate == tilled / watered && samen im Inventar _canPlant = (FieldState == FieldState.Tilled || FieldState == FieldState.Watered) && _seedsActive; // fieldstate == tilled && watering can ausgewählt _canWater = (FieldState == FieldState.Tilled || IsPlanted) && _wateringCanActive && WateringCanState.GetFillState() > 0; PlantingInteraction.IsActive = _canPlant || _canWater; } public void ActivatedSeedInInventory(bool activated) { _seedsActive = activated; UpdateInteractionArea(); } public void ActivateWateringCanInInventory(bool activated) { _wateringCanActive = activated; UpdateInteractionArea(); } public override void _EnterTree() { LoadFromSaveData(); } public override void _Ready() { if(PlantingPlaceholder.GetChildCount() > 0) _currentPlant = PlantingPlaceholder.GetChild(0); UpdateFieldState(FieldState); int randomIndex = new Random().Next(0, _maskTexture.Length); _maskSprite.Texture = _maskTexture[randomIndex]; _outlineSprite.Texture = _maskOutlineTextures[randomIndex]; base._Ready(); } public void UpdateFieldState(FieldState state, bool updateSaveAfter = true) { switch (state) { case FieldState.Empty: FieldState = FieldState.Empty; PlantingInteraction.IsActive = false; break; case FieldState.Tilled: FieldState = FieldState.Tilled; _fieldSprite.Texture = Tilled; if(!IsPlanted) PlantingInteraction.IsActive = true; break; case FieldState.Watered: FieldState = FieldState.Watered; _fieldSprite.Texture = Watered; if(!IsPlanted) PlantingInteraction.IsActive = true; break; default: FieldState = FieldState.NotFound; break; } UpdateInteractionArea(); if(updateSaveAfter) UpdateSaveData(); } public void Water() { if (WateringCanState.GetFillState() > 0 && FieldState != FieldState.Watered) { UpdateFieldState(FieldState.Watered); _wateringParticles.Emitting = true; WateringCanState.Water(); _wateringEvent.Raise(); if (_currentPlant != null) { _currentPlant.DaysWatered++; UpdateSaveData(); } } } /// /// Called when the player enters the field's interaction area and presses or clicks. /// public void Farm() { if (_canPlant && TryPlant()) { EmitSignal(SignalName.Planted); UpdateSaveData(); } if (_canWater) { Water(); } } public void ChangePlantedState() { IsPlanted = true; if(FieldState == FieldState.Tilled) _fieldSprite.Texture = Tilled; if(FieldState == FieldState.Watered) _fieldSprite.Texture = Watered; PlantingInteraction.IsActive = false; } private bool TryPlant() { bool success = false; int currentSlotIndex = InventoryManager.Instance.CurrentSelectedSlotIndex; ItemInstance? item = InventoryManager.Instance.playerInventory.Slots[currentSlotIndex].itemInstance; if (item == null || PlantingPlaceholder.GetChildCount() > 0 || item.amount == 0) return success; string plantPrefabPath = ItemRepository.TryGetPrefabPath(item.blueprint); if (!string.IsNullOrEmpty(plantPrefabPath)) { PlantPrefab(plantPrefabPath); InventoryManager.Instance.playerInventory.RemoveItem(currentSlotIndex); success = true; } return success; } private void PlantPrefab(string prefabPath) { InstantiatePlant(prefabPath); if (_currentPlant != null) { ChangePlantedState(); _currentPlant.DayPlanted = _currentDay; } } private void InstantiatePlant(string prefabPath) { PackedScene prefab = ResourceLoader.Load(prefabPath, nameof(PackedScene)); Node2D plant2d = prefab.Instantiate(); PlantingPlaceholder.AddChild(plant2d); plant2d.GlobalPosition = PlantingPlaceholder.GlobalPosition; _currentPlant = plant2d as PlantBehaviour2D; if (_currentPlant != null) { _currentPlant.Field = this; } } public void HarvestPlant() { _currentPlant = null; UpdateFieldState(FieldState.Empty, true); IsPlanted = false; } #region SAVE AND LOAD /// /// Update save data as prep for scene transition (when data is saved and loaded from disk). /// public void UpdateSaveData() { var payloadData = new Dictionary { { "field_state", (int)FieldState }, { "day_count_on_last_exit", _currentDay}, }; if (IsPlanted) { payloadData.Add( "plant_data", new Dictionary() { { "prefab_path", _currentPlant.PrefabPath }, { "plant_start_day", _currentPlant.DayPlanted }, { "plant_watered_days", _currentPlant.DaysWatered } } ); } string id = _saveIdHolder.GetMeta("SaveID").AsString(); SavegameService.AppendDataToSave(id, payloadData); } /// /// Loads on scene enter. /// public void LoadFromSaveData() { // Get field and plant data string id = _saveIdHolder.GetMeta("SaveID").AsString(); Dictionary save = SavegameService.GetSaveData(id); // if we already have a plant, don't instantiate another one int plantCount = PlantingPlaceholder.GetChildCount(); if (save.Count > 0) { // get plant first because it's also relevant for the field state if (save.TryGetValue("plant_data", out Variant plantDataVar)) { IsPlanted = true; Dictionary plantDataDict = plantDataVar.AsGodotDictionary(); if (plantDataDict.TryGetValue("prefab_path", out Variant prefabPathVar)) { if(plantCount == 0) InstantiatePlant(prefabPathVar.AsString()); else _currentPlant = PlantingPlaceholder.GetChild(0) as PlantBehaviour2D; } else { return; } if (plantDataDict.TryGetValue("plant_start_day", out Variant plantStartDay) && _currentPlant != null) { _currentPlant.DayPlanted = plantStartDay.AsInt32(); } if (plantDataDict.TryGetValue("plant_watered_days", out Variant plantDaysWatered) && _currentPlant != null) { _currentPlant.DaysWatered = plantDaysWatered.AsInt32(); } } // Get current day count: Load only. Saving the day count is handled on the day and night prefab. Dictionary dayCountSave = SavegameService.GetSaveData(DAY_COUNTER_SAVE_ID); if (dayCountSave.Count > 0) { if (dayCountSave.TryGetValue("payload", out Variant dayCountVar)) { _currentDay = dayCountVar.AsInt32(); if (_currentPlant != null) { _currentPlant.CurrentDayInCalendar = _currentDay; } } } // get field state if (save.TryGetValue("field_state", out Variant fieldStateVar)) { int fieldStateInt = fieldStateVar.AsInt32(); // if the field has been unlocked, make it visible. if (fieldStateInt != 0) { Visible = true; if (save.TryGetValue("day_count_on_last_exit", out Variant lastDayCountVar)) { int lastDayCount = lastDayCountVar.AsInt32(); // if day is today, then just use the provided field state as is. if (CalendarController.Instance != null && _currentDay != lastDayCount) { // if the field was watered the day before, set it to tilled if (fieldStateInt == 3) { fieldStateInt = 1; } } } FieldState = (FieldState) fieldStateInt; UpdateFieldState(FieldState, false); } } } } #endregion }