Merge pull request 'feature/farming_bugfixes_and_magic_word' (#30) from feature/farming_bugfixes_and_magic_word into develop
Reviewed-on: #30
This commit was merged in pull request #30.
This commit is contained in:
@@ -8,7 +8,6 @@ namespace Babushka.scripts.CSharp.Common.Animation;
|
||||
public partial class VesnaAnimations : Node
|
||||
{
|
||||
[Export] private AnimatedSprite2D _sprite;
|
||||
[Export] private CpuParticles2D _wateringParticles;
|
||||
|
||||
private bool anyActionPressed;
|
||||
private string _toolString;
|
||||
@@ -144,7 +143,6 @@ public partial class VesnaAnimations : Node
|
||||
_sprite.Animation = "diagonal wateringcan";
|
||||
_sprite.Play();
|
||||
InputService.Instance.InputEnabled = false;
|
||||
_wateringParticles.Emitting = true;
|
||||
Task.Run(DelayedInputHandlerReset);
|
||||
}
|
||||
}
|
||||
@@ -152,7 +150,6 @@ public partial class VesnaAnimations : Node
|
||||
private async Task DelayedInputHandlerReset()
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
_wateringParticles.Emitting = false;
|
||||
InputService.Instance.InputEnabled = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,12 @@ namespace Babushka.scripts.CSharp.Common.CharacterControls;
|
||||
|
||||
public partial class InteractionArea2D : Node2D
|
||||
{
|
||||
|
||||
[Export] private Area2D _area;
|
||||
[Export] private Label _label;
|
||||
[Export] private bool _active = true;
|
||||
[Export] private bool _useOutline = true;
|
||||
[Export] private ShaderMaterial _outlineMaterial;
|
||||
[Export] private CanvasItem? _spriteToOutline; // keep to not break old usages. TODO: remove later
|
||||
[Export] private CanvasItem[] _spritesToOutline = [];
|
||||
[Export] private bool _showLabel = true;
|
||||
[Export] private int _id = -1; // TODO: remove
|
||||
@@ -38,21 +38,7 @@ public partial class InteractionArea2D : Node2D
|
||||
{
|
||||
if (_useOutline)
|
||||
{
|
||||
try
|
||||
{
|
||||
// support old implementations of the script. If the sprite to outline is set, add it to the array
|
||||
if (_spriteToOutline != null)
|
||||
{
|
||||
Array.Resize(ref _spritesToOutline, _spritesToOutline.Length + 1);
|
||||
_spritesToOutline[^1] = _spriteToOutline;
|
||||
}
|
||||
|
||||
_backupMaterials = _spritesToOutline.Select(s => s.Material).ToArray();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
GD.PrintErr($"No sprite to outline found on: {GetParent().Name}" + exception.Message);
|
||||
}
|
||||
_backupMaterials = _spritesToOutline.Select(s => s.Material).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,9 +62,6 @@ public partial class InteractionArea2D : Node2D
|
||||
|
||||
public void OnPlayerExited(Node2D player)
|
||||
{
|
||||
if (!_active)
|
||||
return;
|
||||
|
||||
_label.Hide();
|
||||
|
||||
if (!_useOutline)
|
||||
@@ -129,7 +112,6 @@ public partial class InteractionArea2D : Node2D
|
||||
|
||||
public void SetSpriteActiveState(bool success, int id) // TODO: remove
|
||||
{
|
||||
GD.PrintErr("SetSpriteActiveState is being called.");
|
||||
if (!_active)
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System;
|
||||
using Babushka.scripts.CSharp.Low_Code.Variables;
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
|
||||
@@ -6,19 +8,17 @@ namespace Babushka.scripts.CSharp.Common.Farming;
|
||||
[GlobalClass]
|
||||
public partial class FarmingControls2D : Node2D
|
||||
{
|
||||
[Export] private PackedScene _fieldPrefab;
|
||||
[Export] private VariableResource _sceneKeyProvider;
|
||||
[Export] private Node2D _movingPlayer;
|
||||
[Export] private Camera2D _camera;
|
||||
[Export] private CpuParticles2D _wateringParticles;
|
||||
[Export] private float _wateringCanParticlesVerticalOffset = 50f;
|
||||
|
||||
public FieldService2D FieldService;
|
||||
[Export] private float _wateringCanParticlesVerticalOffset = 50f;
|
||||
[Export] private Vector2I _fieldOffsetVector = new Vector2I(735, 651);
|
||||
[Export] private Node2D _fieldParent;
|
||||
|
||||
private int _toolId = -1;
|
||||
private bool _wateringCanFilled = false;
|
||||
|
||||
|
||||
[Signal] public delegate void WateringFieldEventHandler();
|
||||
|
||||
#region Tools
|
||||
|
||||
@@ -49,59 +49,19 @@ public partial class FarmingControls2D : Node2D
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public override void _Input(InputEvent @event)
|
||||
{
|
||||
if (@event.IsActionPressed("click")
|
||||
&& _toolId == WateringCanState.WATERING_CAN_ID
|
||||
&& WateringCanState.GetFillState() > 0)
|
||||
{
|
||||
Vector2I adjustedPosition = GetAdjustedMousePosition();
|
||||
WaterTheField(adjustedPosition);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Vector2I GetAdjustedMousePosition()
|
||||
{
|
||||
Vector2 mousePosition = _camera.GetGlobalMousePosition();
|
||||
Vector2I mousePositionInteger = (Vector2I) mousePosition;
|
||||
Vector2I adjustedPosition = AdjustValue(mousePositionInteger, new Vector2I(735, 651));
|
||||
Vector2I adjustedPosition = AdjustValue(mousePositionInteger, _fieldOffsetVector);
|
||||
return adjustedPosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by the allowed farming area collision area 2d.
|
||||
/// </summary>
|
||||
/// <param name="node"></param>
|
||||
/// <param name="inputEvent"></param>
|
||||
/// <param name="shapeIndex"></param>
|
||||
public void InputEventPressedOn(Node node, InputEvent inputEvent, int shapeIndex)
|
||||
private Vector2I AdjustValue(Vector2I input, Vector2I step)
|
||||
{
|
||||
if (!inputEvent.IsPressed())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!inputEvent.IsActionPressed("click"))
|
||||
return;
|
||||
|
||||
if (inputEvent is InputEventMouseButton inputEventMouseButton)
|
||||
{
|
||||
if (!inputEventMouseButton.Pressed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_toolId == 0)
|
||||
{
|
||||
Vector2I adjustedPosition = GetAdjustedMousePosition();
|
||||
MakeField(adjustedPosition);
|
||||
}
|
||||
return input.Snapped(step);
|
||||
}
|
||||
|
||||
#region WATERING
|
||||
@@ -112,57 +72,7 @@ public partial class FarmingControls2D : Node2D
|
||||
WateringCanState.Fill();
|
||||
}
|
||||
}
|
||||
|
||||
private void WaterTheField(Vector2I fieldPosition)
|
||||
{
|
||||
FieldBehaviour2D field = FieldService.Get(fieldPosition);
|
||||
if (field == null || field.FieldState == FieldState.Watered)
|
||||
return;
|
||||
|
||||
field.Water();
|
||||
_wateringParticles.GlobalPosition = new Vector2(field.GlobalPosition.X, field.GlobalPosition.Y + _wateringCanParticlesVerticalOffset);
|
||||
WateringCanState.Water();
|
||||
EmitSignal(SignalName.WateringField);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region FIELD CREATION
|
||||
private void MakeField(Vector2I fieldPosition)
|
||||
{
|
||||
if(FieldService == null || _fieldPrefab == null)
|
||||
return;
|
||||
|
||||
// only instantiate a field if there isn't one already.
|
||||
if(FieldService.Get(fieldPosition) == null)
|
||||
{
|
||||
Node fieldInstance = _fieldPrefab.Instantiate();
|
||||
if (fieldInstance is Node2D field2d)
|
||||
{
|
||||
// add dictionary entry for the field
|
||||
Array<Node> fields = field2d.FindChildren("*", nameof(FieldBehaviour2D));
|
||||
if (fields.Count > 0)
|
||||
FieldService.TryAddEntry(fieldPosition, fields[0] as FieldBehaviour2D);
|
||||
|
||||
// reposition and reparent the instance
|
||||
field2d.Position = new Vector2(fieldPosition.X, fieldPosition.Y);;
|
||||
FieldService.AddChild(fieldInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int AdjustValue(float value)
|
||||
{
|
||||
float adjustedValue = value / 500;
|
||||
adjustedValue = Mathf.RoundToInt(adjustedValue);
|
||||
adjustedValue *= 500;
|
||||
return (int)adjustedValue;
|
||||
}
|
||||
|
||||
private Vector2I AdjustValue(Vector2I input, Vector2I step)
|
||||
{
|
||||
return input.Snapped(step);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using Babushka.scripts.CSharp.Common.CharacterControls;
|
||||
using Godot;
|
||||
|
||||
namespace Babushka.scripts.CSharp.Common.Farming;
|
||||
|
||||
/// <summary>
|
||||
/// Enables a preset field in the scene sothat it can be used for farming.
|
||||
/// </summary>
|
||||
public partial class FieldActivator : Node
|
||||
{
|
||||
[Export] private FieldBehaviour2D _field;
|
||||
[Export] private InteractionArea2D _activatorArea;
|
||||
|
||||
private bool _used = false;
|
||||
private bool _rakeInHand;
|
||||
|
||||
[Signal] public delegate void FieldCreatedEventHandler();
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
ToggleInteractionArea();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activates the fieldbehaviour node and sets it to the tilled state.
|
||||
/// </summary>
|
||||
public void ActivateField()
|
||||
{
|
||||
if (!_used && _rakeInHand)
|
||||
{
|
||||
_field.Visible = true;
|
||||
_field.UpdateFieldState(FieldState.Tilled);
|
||||
EmitSignal(SignalName.FieldCreated, _field);
|
||||
_used = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reacts to changes in the inventory.
|
||||
/// If setup correctly, the field activator interactable should only trigger when using the rake.
|
||||
/// </summary>
|
||||
/// <param name="activated"></param>
|
||||
public void RakeActivated(bool activated)
|
||||
{
|
||||
_rakeInHand = activated;
|
||||
ToggleInteractionArea();
|
||||
}
|
||||
|
||||
private void ToggleInteractionArea()
|
||||
{
|
||||
_activatorArea.IsActive = !_used && _rakeInHand;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://dlbjjgbs0n4b0
|
||||
@@ -1,31 +1,88 @@
|
||||
using System;
|
||||
using Babushka.scripts.CSharp.Common.CharacterControls;
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Defines the behaviour of the field, i.e. interactions, states and effects.
|
||||
/// </summary>
|
||||
[GlobalClass]
|
||||
public partial class FieldBehaviour2D : Sprite2D
|
||||
{
|
||||
[ExportGroup("Persistence")]
|
||||
[Export] public string SaveId = "";
|
||||
[Export] private VariableNode _fieldIndex;
|
||||
[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;
|
||||
[Export] public FieldState FieldState = FieldState.Tilled;
|
||||
|
||||
[ExportGroup("Field Interactions")]
|
||||
[Export] public InteractionArea2D PlantingInteraction;
|
||||
[Export] public InteractionArea2D FieldInteractionArea;
|
||||
|
||||
[ExportGroup("Configuration")]
|
||||
[Export] public Node2D PlantingPlaceholder;
|
||||
[Export] public ItemRepository ItemRepository;
|
||||
[Export] private CpuParticles2D _wateringParticles;
|
||||
[Export] private EventResource _wateringEvent;
|
||||
|
||||
private bool _seedsActive;
|
||||
private bool _wateringCanActive;
|
||||
|
||||
public Vector2 FieldPosition;
|
||||
private bool _canPlant;
|
||||
private bool _canWater;
|
||||
|
||||
private PlantBehaviour2D? _currentPlant;
|
||||
|
||||
[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 || FieldState == FieldState.Planted) && _wateringCanActive;
|
||||
FieldInteractionArea.IsActive = _canPlant || _canWater;
|
||||
}
|
||||
|
||||
public void ActivatedSeedInInventory(bool activated)
|
||||
{
|
||||
_seedsActive = activated;
|
||||
UpdateInteractionArea();
|
||||
}
|
||||
|
||||
public void ActivateWateringCanInInventory(bool activated)
|
||||
{
|
||||
_wateringCanActive = activated;
|
||||
UpdateInteractionArea();
|
||||
}
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
LoadFromSaveData();
|
||||
if(PlantingPlaceholder.GetChildCount() > 0)
|
||||
_currentPlant = PlantingPlaceholder.GetChild<PlantBehaviour2D>(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];
|
||||
base._Ready();
|
||||
}
|
||||
|
||||
@@ -56,22 +113,36 @@ public partial class FieldBehaviour2D : Sprite2D
|
||||
FieldState = FieldState.NotFound;
|
||||
break;
|
||||
}
|
||||
UpdateInteractionArea();
|
||||
UpdateSaveData();
|
||||
}
|
||||
|
||||
|
||||
public void Water()
|
||||
{
|
||||
UpdateFieldState(FieldState.Watered);
|
||||
if (WateringCanState.GetFillState() > 0)
|
||||
{
|
||||
UpdateFieldState(FieldState.Watered);
|
||||
_wateringParticles.Emitting = true;
|
||||
WateringCanState.Water();
|
||||
_wateringEvent.Raise();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the player enters the field's interaction area and presses <E>.
|
||||
/// Called when the player enters the field's interaction area and presses <E> or clicks.
|
||||
/// </summary>
|
||||
public void Farm()
|
||||
{
|
||||
if (TryPlant())
|
||||
if (_canPlant && TryPlant())
|
||||
{
|
||||
EmitSignal(SignalName.Planted);
|
||||
UpdateFieldState(FieldState.Planted);
|
||||
}
|
||||
|
||||
if (_canWater)
|
||||
{
|
||||
UpdateFieldState(FieldState.Planted);
|
||||
Water();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,30 +151,114 @@ public partial class FieldBehaviour2D : Sprite2D
|
||||
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 prefabPath = ItemRepository.TryGetPrefabPath(item.blueprint);
|
||||
string plantPrefabPath = ItemRepository.TryGetPrefabPath(item.blueprint);
|
||||
|
||||
if (prefabPath != null)
|
||||
if (!string.IsNullOrEmpty(plantPrefabPath))
|
||||
{
|
||||
PackedScene prefab = ResourceLoader.Load<PackedScene>(prefabPath, nameof(PackedScene));
|
||||
Node2D plant2d = prefab.Instantiate<Node2D>();
|
||||
PlantingPlaceholder.AddChild(plant2d);
|
||||
plant2d.GlobalPosition = PlantingPlaceholder.GlobalPosition;
|
||||
PlantBehaviour2D? plantBehaviour = plant2d as PlantBehaviour2D;
|
||||
|
||||
if (plantBehaviour != null)
|
||||
{
|
||||
plantBehaviour.Field = this;
|
||||
}
|
||||
|
||||
PlantPrefab(plantPrefabPath);
|
||||
InventoryManager.Instance.playerInventory.RemoveItem(currentSlotIndex);
|
||||
success = true;
|
||||
}
|
||||
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
}
|
||||
private void PlantPrefab(string prefabPath)
|
||||
{
|
||||
PackedScene prefab = ResourceLoader.Load<PackedScene>(prefabPath, nameof(PackedScene));
|
||||
Node2D plant2d = prefab.Instantiate<Node2D>();
|
||||
PlantingPlaceholder.AddChild(plant2d);
|
||||
plant2d.GlobalPosition = PlantingPlaceholder.GlobalPosition;
|
||||
_currentPlant = plant2d as PlantBehaviour2D;
|
||||
|
||||
if (_currentPlant != null)
|
||||
{
|
||||
_currentPlant.Field = this;
|
||||
}
|
||||
}
|
||||
|
||||
#region SAVE AND LOAD
|
||||
|
||||
public void UpdateSaveData()
|
||||
{
|
||||
var saveData = new SaveData();
|
||||
|
||||
saveData.SceneName = _sceneKeyProvider.Payload.AsString();
|
||||
saveData.Id = SaveId + _fieldIndex.Payload.AsString();
|
||||
var payloadData = new Dictionary<string, Variant>
|
||||
{
|
||||
{ "field_state", (int)FieldState }
|
||||
};
|
||||
|
||||
if (_currentPlant != null)
|
||||
{
|
||||
payloadData.Add(
|
||||
"plant_data", new Dictionary<string, Variant>()
|
||||
{
|
||||
{ "prefab_path", _currentPlant.PrefabPath },
|
||||
{ "plant_state", (int)_currentPlant.State },
|
||||
{ "plant_days_growing", _currentPlant.DaysGrowing }
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
saveData.JsonPayload = Json.Stringify(payloadData, indent: "\t");
|
||||
|
||||
SavegameService.AppendSave(saveData);
|
||||
}
|
||||
|
||||
public void LoadFromSaveData()
|
||||
{
|
||||
var sceneName = _sceneKeyProvider.Payload.AsString();
|
||||
var id = SaveId + _fieldIndex.Payload.AsString();
|
||||
string jsonPayload = SavegameService.GetSaveData(sceneName, id);
|
||||
Dictionary<string, Variant> save = Json.ParseString(jsonPayload).AsGodotDictionary<string, Variant>();
|
||||
|
||||
if (save.Count > 0)
|
||||
{
|
||||
if (save.TryGetValue("field_state", out Variant fieldStateVar))
|
||||
{
|
||||
int fieldStateInt = fieldStateVar.AsInt32();
|
||||
FieldState = (FieldState) fieldStateInt;
|
||||
|
||||
if (fieldStateInt != 0)
|
||||
{
|
||||
Visible = true;
|
||||
UpdateFieldState(FieldState);
|
||||
}
|
||||
}
|
||||
|
||||
if (save.TryGetValue("plant_data", out Variant plantDataVar))
|
||||
{
|
||||
Dictionary<string, Variant> plantDataDict = plantDataVar.AsGodotDictionary<string, Variant>();
|
||||
|
||||
if (plantDataDict.TryGetValue("prefab_path", out Variant prefabPathVar))
|
||||
{
|
||||
PlantPrefab(prefabPathVar.AsString());
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (plantDataDict.TryGetValue("plant_state", out Variant plantStateVar) && _currentPlant != null)
|
||||
{
|
||||
_currentPlant.State = (PlantState) plantStateVar.AsInt32();
|
||||
_currentPlant.GrowPlant();
|
||||
}
|
||||
|
||||
if (plantDataDict.TryGetValue("plant_days_growing", out Variant plantDaysGrowingVar) && _currentPlant != null)
|
||||
{
|
||||
_currentPlant.DaysGrowing = plantDaysGrowingVar.AsInt32();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
using System.Collections.Generic;
|
||||
using Godot;
|
||||
|
||||
namespace Babushka.scripts.CSharp.Common.Farming;
|
||||
|
||||
public partial class FieldService : Node
|
||||
{
|
||||
private Dictionary<string, FieldsInScene>? _outerDict = null!;
|
||||
public static FieldService Instance { get; private set; } = null!;
|
||||
|
||||
public override void _EnterTree()
|
||||
{
|
||||
Instance = this;
|
||||
_outerDict = new Dictionary<string, FieldsInScene>();
|
||||
}
|
||||
|
||||
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<int, FieldBehaviour2D?> fields;
|
||||
|
||||
public FieldsInScene()
|
||||
{
|
||||
fields = new Dictionary<int, FieldBehaviour2D?>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://slo0uydmmvnu
|
||||
@@ -1,17 +1,15 @@
|
||||
using System;
|
||||
using Babushka.scripts.CSharp.Common.Animation;
|
||||
using Babushka.scripts.CSharp.Common.Inventory;
|
||||
using Godot;
|
||||
|
||||
namespace Babushka.scripts.CSharp.Common.Farming;
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determines the behaviour of a plant in Babushka.
|
||||
/// </summary>
|
||||
public partial class PlantBehaviour2D : Node2D
|
||||
{
|
||||
[Export] private string _prefabPath;
|
||||
[Export] private Sprite2D[] _seeds;
|
||||
[Export] private Sprite2D[] _smallPlants;
|
||||
[Export] private Sprite2D[] _bigPlants;
|
||||
@@ -23,8 +21,19 @@ public partial class PlantBehaviour2D : Node2D
|
||||
[Export] private bool _magicWordNeeded = true;
|
||||
|
||||
private string _magicWordDialogicEventName = "MagicWord";
|
||||
private Sprite2D _currentPlantSprite = null;
|
||||
private Sprite2D? _currentPlantSprite = null;
|
||||
private bool _magicWordSaid = false;
|
||||
private bool _calledOnReady = false;
|
||||
|
||||
public PlantState State
|
||||
{
|
||||
get => _state;
|
||||
set => _state = value;
|
||||
}
|
||||
|
||||
public int DaysGrowing { get; set; }
|
||||
|
||||
public string PrefabPath => _prefabPath;
|
||||
|
||||
/// <summary>
|
||||
/// public accessor for the field reference
|
||||
@@ -39,20 +48,19 @@ public partial class PlantBehaviour2D : Node2D
|
||||
{
|
||||
if (_state == PlantState.None)
|
||||
{
|
||||
GetTree().CallGroup("PlantGrowing", VesnaAnimations.MethodName.PlayFarmingAnimation);
|
||||
_state = PlantState.Planted;
|
||||
_currentPlantSprite = GetRandomSprite(_seeds);
|
||||
_currentPlantSprite.Visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_calledOnReady = true;
|
||||
GrowPlant();
|
||||
}
|
||||
}
|
||||
|
||||
public void Grow()
|
||||
{
|
||||
GetTree().CallGroup("PlantGrowing", VesnaAnimations.MethodName.PlayFarmingAnimation);
|
||||
GrowPlant();
|
||||
}
|
||||
|
||||
@@ -62,8 +70,11 @@ public partial class PlantBehaviour2D : Node2D
|
||||
/// </summary>
|
||||
public void GrowPlant()
|
||||
{
|
||||
if (_field.FieldState != FieldState.Watered || _magicWordSaid != _magicWordNeeded)
|
||||
return;
|
||||
if (!_calledOnReady)
|
||||
{
|
||||
if (_field.FieldState != FieldState.Watered || _magicWordSaid != _magicWordNeeded)
|
||||
return;
|
||||
}
|
||||
|
||||
switch (_state)
|
||||
{
|
||||
@@ -107,6 +118,7 @@ public partial class PlantBehaviour2D : Node2D
|
||||
|
||||
_field.UpdateFieldState(FieldState.Tilled);
|
||||
_magicWordSaid = false;
|
||||
_calledOnReady = false;
|
||||
}
|
||||
|
||||
private Sprite2D GetRandomSprite(Sprite2D[] sprites)
|
||||
|
||||
@@ -9,7 +9,6 @@ namespace Babushka.scripts.CSharp.Common.Farming;
|
||||
public partial class VesnaBehaviour2D : Node
|
||||
{
|
||||
[ExportGroup("Farming")]
|
||||
[Export] private FieldService2D _fieldParent;
|
||||
[Export] private FarmingControls2D _farmingControls;
|
||||
[Export] private PlayerMovement _player2d;
|
||||
[Export] private VesnaAnimations _vesnaAnimations;
|
||||
@@ -26,7 +25,6 @@ public partial class VesnaBehaviour2D : Node
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
_farmingControls.FieldService = _fieldParent;
|
||||
_inventoryManager = InventoryManager.Instance;
|
||||
_inventoryInstance = _inventoryManager.playerInventory;
|
||||
_inventoryManager.SlotIndexChanged += HandleInventorySelectedSlotIndexChanged;
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
using Godot;
|
||||
|
||||
namespace Babushka.scripts.CSharp.Common.Inventory;
|
||||
|
||||
public partial class InventoryListener : Node
|
||||
{
|
||||
[Export] private ItemResource[] _itemResourcesToListenFor;
|
||||
|
||||
[Signal] public delegate void ItemInstanceActivatedEventHandler(bool activated);
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
InventoryManager.Instance.playerInventory.InventoryContentsChanged += HandleNewItemInInventory;
|
||||
InventoryManager.Instance.SlotIndexChanged += HandleNewItemInInventory;
|
||||
}
|
||||
|
||||
public override void _ExitTree()
|
||||
{
|
||||
InventoryManager.Instance.playerInventory.InventoryContentsChanged -= HandleNewItemInInventory;
|
||||
InventoryManager.Instance.SlotIndexChanged -= HandleNewItemInInventory;
|
||||
}
|
||||
|
||||
private void HandleNewItemInInventory(int newIndex)
|
||||
{
|
||||
HandleNewItemInInventory();
|
||||
}
|
||||
|
||||
private void HandleNewItemInInventory()
|
||||
{
|
||||
int currentSlotIndex = InventoryManager.Instance.CurrentSelectedSlotIndex;
|
||||
ItemInstance? instance = InventoryManager.Instance.playerInventory.Slots[currentSlotIndex].itemInstance;
|
||||
if (instance != null)
|
||||
{
|
||||
ItemResource? item = instance.blueprint;
|
||||
|
||||
foreach (var res in _itemResourcesToListenFor)
|
||||
{
|
||||
if (item == res)
|
||||
{
|
||||
EmitSignal(SignalName.ItemInstanceActivated, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EmitSignal(SignalName.ItemInstanceActivated, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://3t0af586fimq
|
||||
@@ -1,13 +0,0 @@
|
||||
using Babushka.scripts.CSharp.Common.Animation;
|
||||
using Godot;
|
||||
|
||||
namespace Babushka.scripts.CSharp.Common.Items;
|
||||
|
||||
public partial class NonInventoryPickup : Node2D
|
||||
{
|
||||
public void PlayPickupAnimation()
|
||||
{
|
||||
// todo: replace with EventBus implementation as soon as this is possible
|
||||
GetTree().CallGroup("Pickup", VesnaAnimations.MethodName.PlayPickUpAnimation);
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
uid://dkk1vjijvgrd7
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace Babushka.scripts.CSharp.Common.Savegame;
|
||||
|
||||
/// <summary>
|
||||
/// Central structure for SaveData entries.
|
||||
/// SceneName and ID are later combined and used as keys for the save system.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class SaveData
|
||||
{
|
||||
public string SceneName;
|
||||
public string Id;
|
||||
public string JsonPayload;
|
||||
|
||||
public string ToString()
|
||||
{
|
||||
return SceneName + " " + Id + " " + JsonPayload;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://bkftl5mj33eah
|
||||
@@ -0,0 +1,19 @@
|
||||
using Godot;
|
||||
|
||||
namespace Babushka.scripts.CSharp.Common.Savegame;
|
||||
|
||||
/// <summary>
|
||||
/// This class manages the loading of the savegame throughout the gameplay flow.
|
||||
/// </summary>
|
||||
public partial class SaveGameManager : Node
|
||||
{
|
||||
public static SaveGameManager? Instance { get; private set; } = null!;
|
||||
|
||||
public override void _EnterTree()
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
SavegameService.Load();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://c1srnefvhigef
|
||||
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
using FileAccess = Godot.FileAccess;
|
||||
|
||||
namespace Babushka.scripts.CSharp.Common.Savegame;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public static class SavegameService
|
||||
{
|
||||
public static readonly string SavePath = "user://babushka_savegame.json";
|
||||
|
||||
public static Dictionary<string, string> SaveDatas = new ();
|
||||
|
||||
public static bool _loaded = false;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds or overwrites an entry in the SaveData dictionary.
|
||||
/// </summary>
|
||||
/// <param name="saveData"></param>
|
||||
public static void AppendSave(SaveData saveData)
|
||||
{
|
||||
string key = string.Concat(saveData.SceneName, "_", saveData.Id);
|
||||
|
||||
if (SaveDatas.TryGetValue(key, out var value))
|
||||
{
|
||||
SaveDatas[key] = saveData.JsonPayload;
|
||||
}
|
||||
else
|
||||
{
|
||||
SaveDatas.Add(key, saveData.JsonPayload);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks the SaveData dictionary for an entry and returns the jsondata as a string for it.
|
||||
/// Requires the scenename and object ID for lookup.
|
||||
/// </summary>
|
||||
/// <param name="sceneName"></param>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
public static string GetSaveData(string sceneName, string id)
|
||||
{
|
||||
string saveData = "";
|
||||
|
||||
string key = string.Concat(sceneName, "_", id);
|
||||
|
||||
if (!_loaded)
|
||||
{
|
||||
GD.Print("SavegameService: SaveFile not loaded.");
|
||||
return saveData;
|
||||
}
|
||||
|
||||
if (SaveDatas.ContainsKey(key))
|
||||
{
|
||||
saveData = SaveDatas[key];
|
||||
}
|
||||
|
||||
return saveData;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Writes the contents of the current SaveData dictionary to disk as a json file.
|
||||
/// </summary>
|
||||
public static void Save()
|
||||
{
|
||||
string json = Json.Stringify(SaveDatas, indent: "\t");
|
||||
using var file = FileAccess.Open(SavePath, FileAccess.ModeFlags.Write);
|
||||
file.StoreString(json);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the current savegame file from disk and parses it into the SaveData dictionary.
|
||||
/// </summary>
|
||||
public static void Load()
|
||||
{
|
||||
try
|
||||
{
|
||||
string saveDataJson = FileAccess.GetFileAsString(SavePath);
|
||||
SaveDatas = Json.ParseString(saveDataJson).AsGodotDictionary<string, string>();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
GD.PrintRich(e.Message);
|
||||
return;
|
||||
}
|
||||
|
||||
_loaded = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://rm23q50boqe5
|
||||
@@ -1,3 +1,4 @@
|
||||
using Babushka.scripts.CSharp.Common.Savegame;
|
||||
using Babushka.scripts.CSharp.Common.SceneManagement;
|
||||
using Godot;
|
||||
|
||||
@@ -16,6 +17,7 @@ public partial class SceneTransition : Node
|
||||
|
||||
public void LoadSceneAtIndex(int index)
|
||||
{
|
||||
SavegameService.Save();
|
||||
string sceneName = _sceneNamesToLoad[index];
|
||||
SceneTransitionThreaded.Instance.ChangeSceneToFileThreaded(sceneName);
|
||||
UnloadAfterDelay();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Godot;
|
||||
namespace Babushka.scripts.CSharp.Common.Util;
|
||||
|
||||
@@ -23,4 +24,54 @@ public static class NodeExtension
|
||||
}
|
||||
throw new Exception($"Parent of type {typeof(T)} not found for node {self.Name}");
|
||||
}
|
||||
|
||||
//acts like Unity's GetComponent<T> / GetComponentInChildren<T>
|
||||
// only works with Godot's built-in types.
|
||||
public static T GetChildByType<T>(this Node node, bool recursive = true)
|
||||
where T : Node
|
||||
{
|
||||
int childCount = node.GetChildCount();
|
||||
|
||||
for (int i = 0; i < childCount; i++)
|
||||
{
|
||||
Node child = node.GetChild(i);
|
||||
if (child is T childT)
|
||||
return childT;
|
||||
|
||||
if (recursive && child.GetChildCount() > 0)
|
||||
{
|
||||
T recursiveResult = child.GetChildByType<T>(true);
|
||||
if (recursiveResult != null)
|
||||
return recursiveResult;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Another reimplementation of Unity's GetComponent<T>.
|
||||
/// Verified to work with all types, also derived ones, but only when used from within a scene and at runtime.
|
||||
/// </summary>
|
||||
/// <param name="node"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public static T GetComponent<T>(Node node)
|
||||
{
|
||||
if (node is T)
|
||||
{
|
||||
return (T)(object)node;
|
||||
}
|
||||
|
||||
foreach (Node child in node.GetChildren())
|
||||
{
|
||||
if (child is T)
|
||||
{
|
||||
return (T)(object)child;
|
||||
}
|
||||
}
|
||||
|
||||
return (T)(object)null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
using Godot;
|
||||
|
||||
namespace Babushka.scripts.CSharp.Low_Code.Variables;
|
||||
|
||||
/// <summary>
|
||||
/// A Node type that carries a Variant payload.
|
||||
/// </summary>
|
||||
public partial class VariableNode : Node
|
||||
{
|
||||
[Export] public Variant Payload { get; set; }
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://j2mhvb45egej
|
||||
@@ -0,0 +1,14 @@
|
||||
using Godot;
|
||||
|
||||
namespace Babushka.scripts.CSharp.Low_Code.Variables;
|
||||
|
||||
public partial class VariableSetter : Node
|
||||
{
|
||||
[Export] private VariableResource _variableResource;
|
||||
[Export] private Variant _payloadToSet;
|
||||
|
||||
public void Set()
|
||||
{
|
||||
_variableResource.Payload = _payloadToSet;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://dfpyjxivcuidr
|
||||
@@ -0,0 +1,69 @@
|
||||
extends Node
|
||||
|
||||
@export_node_path("Node2D")
|
||||
var target_node_path = NodePath()
|
||||
|
||||
@export var flip_bend_direction = false
|
||||
@export var joint_one_bone_index = -1
|
||||
@export var joint_two_bone_index = -1
|
||||
|
||||
var _angle_a = 0.0
|
||||
var _angle_b = 0.0
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
_update_two_bone_ik_angles()
|
||||
|
||||
|
||||
func _update_two_bone_ik_angles():
|
||||
assert(joint_one_bone_index != -1)
|
||||
assert(joint_two_bone_index != -1)
|
||||
|
||||
if target_node_path.is_empty():
|
||||
return
|
||||
|
||||
var target = get_node(target_node_path) as Node2D
|
||||
var bone_a = get_parent().get_bone(joint_one_bone_index)
|
||||
var bone_b = get_parent().get_bone(joint_two_bone_index)
|
||||
|
||||
var bone_a_len = bone_a.get_length()
|
||||
var bone_b_len = bone_b.get_length()
|
||||
|
||||
var sin_angle2 = 0.0
|
||||
var cos_angle2 = 1.0
|
||||
|
||||
_angle_b = 0.0
|
||||
|
||||
var cos_angle2_denom = 2.0 * bone_a_len * bone_b_len
|
||||
if not is_zero_approx(cos_angle2_denom):
|
||||
var target_len_sqr = _distance_squared_between(bone_a, target)
|
||||
var bone_a_len_sqr = bone_a_len * bone_a_len
|
||||
var bone_b_len_sqr = bone_b_len * bone_b_len
|
||||
|
||||
cos_angle2 = (target_len_sqr - bone_a_len_sqr - bone_b_len_sqr) / cos_angle2_denom
|
||||
cos_angle2 = clamp(cos_angle2, -1.0, 1.0);
|
||||
|
||||
_angle_b = acos(cos_angle2)
|
||||
if flip_bend_direction:
|
||||
_angle_b = -_angle_b
|
||||
|
||||
sin_angle2 = sin(_angle_b)
|
||||
|
||||
var tri_adjacent = bone_a_len + bone_b_len * cos_angle2
|
||||
var tri_opposite = bone_b_len * sin_angle2
|
||||
|
||||
var xform_inv = bone_a.get_parent().global_transform.affine_inverse()
|
||||
var target_pos = xform_inv * target.global_position - bone_a.position
|
||||
|
||||
var tan_y = target_pos.y * tri_adjacent - target_pos.x * tri_opposite
|
||||
var tan_x = target_pos.x * tri_adjacent + target_pos.y * tri_opposite
|
||||
_angle_a = atan2(tan_y, tan_x)
|
||||
|
||||
var bone_a_angle = bone_a.get_bone_angle()
|
||||
var bone_b_angle = bone_b.get_bone_angle()
|
||||
bone_a.rotation = _angle_a - bone_a_angle
|
||||
bone_b.rotation = _angle_b - angle_difference(bone_a_angle, bone_b_angle)
|
||||
|
||||
|
||||
func _distance_squared_between(node_a: Node2D, node_b: Node2D) -> float:
|
||||
return node_a.global_position.distance_squared_to(node_b.global_position)
|
||||
@@ -0,0 +1 @@
|
||||
uid://p5wxvjx24brs
|
||||
Reference in New Issue
Block a user