From fef8380a575f7cd5bceea3d22eabc3b10548720c Mon Sep 17 00:00:00 2001 From: jonathan Date: Thu, 11 Dec 2025 17:13:28 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8When=20beetroot=20is=20in=20inventory?= =?UTF-8?q?=20the=20player=20can=20heal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prefabs/fight/fight_world_autoload.tscn | 4 +- scenes/Babushka_scene_fight_happening.tscn | 2 +- .../Common/Fight/Actions/AllyAttackAction.cs | 4 +- .../Common/Fight/Actions/BlobAttackAction.cs | 4 +- .../Common/Fight/Actions/EatBeetrootAction.cs | 46 +++++++++++++ .../Fight/Actions/EatBeetrootAction.cs.uid | 1 + scripts/CSharp/Common/Fight/AllyFighters.cs | 3 +- scripts/CSharp/Common/Fight/FightHappening.cs | 12 ++-- scripts/CSharp/Common/Fight/FightUtils.cs | 21 +++--- scripts/CSharp/Common/Fight/FightWorld.cs | 13 +++- scripts/CSharp/Common/Fight/FighterAction.cs | 12 +--- .../FighterDamageIndicatorFlyingNumber.cs | 4 +- .../Fight/FighterDamageIndicatorVisual.cs | 4 +- scripts/CSharp/Common/Fight/FighterVisual.cs | 33 ++++++---- .../Common/Inventory/InventoryInstance.cs | 66 ++++++++++++++----- .../CSharp/Common/Inventory/ItemInstance.cs | 2 +- 16 files changed, 161 insertions(+), 70 deletions(-) create mode 100644 scripts/CSharp/Common/Fight/Actions/EatBeetrootAction.cs create mode 100644 scripts/CSharp/Common/Fight/Actions/EatBeetrootAction.cs.uid diff --git a/prefabs/fight/fight_world_autoload.tscn b/prefabs/fight/fight_world_autoload.tscn index 1dbbe33..e6d7a57 100644 --- a/prefabs/fight/fight_world_autoload.tscn +++ b/prefabs/fight/fight_world_autoload.tscn @@ -1,8 +1,10 @@ -[gd_scene load_steps=3 format=3 uid="uid://n5cj71bxxjkk"] +[gd_scene load_steps=4 format=3 uid="uid://n5cj71bxxjkk"] [ext_resource type="Script" uid="uid://dqe1i2qmpttwf" path="res://scripts/CSharp/Common/Fight/FightWorld.cs" id="1_tnyce"] [ext_resource type="Resource" uid="uid://duq7tshxv6uhp" path="res://resources/items/beet_seed.tres" id="2_lxs0o"] +[ext_resource type="Resource" uid="uid://0mnsr4anoaiq" path="res://resources/items/beet.tres" id="3_008v8"] [node name="FightWorldAutoload" type="Node2D"] script = ExtResource("1_tnyce") _itemToDropByEnemyGroup = ExtResource("2_lxs0o") +itemBeetrootToEatForHealth = ExtResource("3_008v8") diff --git a/scenes/Babushka_scene_fight_happening.tscn b/scenes/Babushka_scene_fight_happening.tscn index 943f465..dbd8132 100644 --- a/scenes/Babushka_scene_fight_happening.tscn +++ b/scenes/Babushka_scene_fight_happening.tscn @@ -146,7 +146,7 @@ theme_override_constants/margin_bottom = 10 [node name="Talk Button" type="Button" parent="ActionSelect/BottomPanel/VBoxContainer/MarginContainer/HBoxContainer/MarginContainer3"] layout_mode = 2 theme_override_font_sizes/font_size = 41 -text = "Talk" +text = "Heal" [node name="MarginContainer4" type="MarginContainer" parent="ActionSelect/BottomPanel/VBoxContainer/MarginContainer/HBoxContainer"] layout_mode = 2 diff --git a/scripts/CSharp/Common/Fight/Actions/AllyAttackAction.cs b/scripts/CSharp/Common/Fight/Actions/AllyAttackAction.cs index e67a7f2..34e0264 100644 --- a/scripts/CSharp/Common/Fight/Actions/AllyAttackAction.cs +++ b/scripts/CSharp/Common/Fight/Actions/AllyAttackAction.cs @@ -47,7 +47,7 @@ public class AllyAttackAction : FighterAction public override void ExecuteAction() { var totalDamage = minigameDetail.damageHits!.Sum(dh => dh); - targetSelect.GetTarget().AddHealth(-totalDamage); + targetSelect.GetTarget().ChangeHealth(-totalDamage); } public override async Task AnimateAction(AllFightersVisual allFightersVisual) @@ -63,7 +63,7 @@ public class AllyAttackAction : FighterAction foreach (var hit in minigameDetail.damageHits!) { - targetFighterVisual.SpawnDamageIndicatorNumber(hit); + targetFighterVisual.SpawnDamageIndicatorNumber($"-{hit}"); } await currentFighterVisual.AnimatePosToBase(); diff --git a/scripts/CSharp/Common/Fight/Actions/BlobAttackAction.cs b/scripts/CSharp/Common/Fight/Actions/BlobAttackAction.cs index f0e7f90..dbae1b1 100644 --- a/scripts/CSharp/Common/Fight/Actions/BlobAttackAction.cs +++ b/scripts/CSharp/Common/Fight/Actions/BlobAttackAction.cs @@ -18,7 +18,7 @@ public class BlobAttackAction(int damage = 3) : FighterAction public override void ExecuteAction() { - FightWorld.Instance.allyFighters.vesnaFighter.AddHealth(-damage); + FightWorld.Instance.allyFighters.vesnaFighter.ChangeHealth(-damage); } public override async Task AnimateAction(AllFightersVisual allFightersVisual) @@ -31,7 +31,7 @@ public class BlobAttackAction(int damage = 3) : FighterAction await currentFighterVisual.AnimatePosToTarget(targetFighterVisual); _ = targetFighterVisual.AnimateHit(); - targetFighterVisual.SpawnDamageIndicatorNumber(damage); + targetFighterVisual.SpawnDamageIndicatorNumber($"-{damage}"); await currentFighterVisual.AnimatePosToBase(); } } \ No newline at end of file diff --git a/scripts/CSharp/Common/Fight/Actions/EatBeetrootAction.cs b/scripts/CSharp/Common/Fight/Actions/EatBeetrootAction.cs new file mode 100644 index 0000000..ddb87a0 --- /dev/null +++ b/scripts/CSharp/Common/Fight/Actions/EatBeetrootAction.cs @@ -0,0 +1,46 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Babushka.scripts.CSharp.Common.Inventory; +using Babushka.scripts.CSharp.Common.Util; + +namespace Babushka.scripts.CSharp.Common.Fight.Actions; + +public class EatBeetrootAction : FighterAction +{ + public override Variant> GetAnimationEnd() => 1; + public override bool NextDetail() => false; + + private const int HealAmount = 20; + + public override bool ShouldAbort() + { + Debug.Assert(FightWorld.Instance.itemBeetrootToEatForHealth != null, + "Item to eat for health has not been set in the FightWorld autoload"); + return !InventoryManager.Instance.playerInventory!.HasItems(new ItemInstance + { blueprint = FightWorld.Instance.itemBeetrootToEatForHealth }); + } + + public override async Task AnimateAction(AllFightersVisual allFightersVisual) + { + var fighter = HappeningData.fighterTurn.Current; + var fighterVisual = allFightersVisual.GetVisualForFighter(fighter); + fighterVisual.SpawnDamageIndicatorNumber($"+{HealAmount}"); + await fighterVisual.AnimateHeal(); + } + + public override void ExecuteAction() + { + var fighter = HappeningData.fighterTurn.Current; + + var result = InventoryManager.Instance.playerInventory!.TryRemoveAllItems( + new ItemInstance { blueprint = FightWorld.Instance.itemBeetrootToEatForHealth! }); + + if (result != InventoryActionResult.Success) + throw new Exception("No Beetroot in inventory. This case should have been handled earlier"); + + fighter.ChangeHealth(HealAmount); + } + + public override AllyActionButton BindToActionButton() => AllyActionButton.Talk; // Temporarily bound to talk button +} \ No newline at end of file diff --git a/scripts/CSharp/Common/Fight/Actions/EatBeetrootAction.cs.uid b/scripts/CSharp/Common/Fight/Actions/EatBeetrootAction.cs.uid new file mode 100644 index 0000000..88e4809 --- /dev/null +++ b/scripts/CSharp/Common/Fight/Actions/EatBeetrootAction.cs.uid @@ -0,0 +1 @@ +uid://b2463q1waqvdu diff --git a/scripts/CSharp/Common/Fight/AllyFighters.cs b/scripts/CSharp/Common/Fight/AllyFighters.cs index 2881ee7..f10fcff 100644 --- a/scripts/CSharp/Common/Fight/AllyFighters.cs +++ b/scripts/CSharp/Common/Fight/AllyFighters.cs @@ -10,7 +10,8 @@ public class AllyFighters maxHealth = 60, availableActions = [ - new AllyAttackAction() + new AllyAttackAction(), + new EatBeetrootAction() ] }; public FightWorld.Fighter chuhaFighter = new() diff --git a/scripts/CSharp/Common/Fight/FightHappening.cs b/scripts/CSharp/Common/Fight/FightHappening.cs index 328710c..aadb417 100644 --- a/scripts/CSharp/Common/Fight/FightHappening.cs +++ b/scripts/CSharp/Common/Fight/FightHappening.cs @@ -222,9 +222,9 @@ public partial class FightHappening : Node case FightState.ActionCheckDetails: RequireNotNull(HappeningData.actionStaging); - if (ActionAbort()) + if (ShouldActionAbort()) ChangeState(FightState.InputActionSelect); - else if (ActionNeededDetail()) + else if (DoesActionNeededDetail()) ChangeState(FightState.InputActionDetail); else ChangeState(FightState.ActionExecute); @@ -328,13 +328,13 @@ public partial class FightHappening : Node return HappeningData.actionStaging.GetAnimationEnd(); } - private bool ActionAbort() + private bool ShouldActionAbort() { Debug.Assert(HappeningData.actionStaging != null); - return HappeningData.actionStaging.MarkedForAbort(); + return HappeningData.actionStaging.ShouldAbort(); } - private bool ActionNeededDetail() + private bool DoesActionNeededDetail() { Debug.Assert(HappeningData.actionStaging != null); return HappeningData.actionStaging.NextDetail(); @@ -344,7 +344,7 @@ public partial class FightHappening : Node private void ReviveVesna() { var vesnaFighter = FightWorld.Instance.allyFighters.vesnaFighter; - vesnaFighter.health = vesnaFighter.maxHealth; + vesnaFighter.Health = vesnaFighter.maxHealth; GD.Print("Vesna has been revived. This is for the current prototype only"); } diff --git a/scripts/CSharp/Common/Fight/FightUtils.cs b/scripts/CSharp/Common/Fight/FightUtils.cs index 52d33af..978f197 100644 --- a/scripts/CSharp/Common/Fight/FightUtils.cs +++ b/scripts/CSharp/Common/Fight/FightUtils.cs @@ -10,15 +10,16 @@ public static class FightUtils { return self.Where(e => e.IsAlive()); } - - public static IEnumerable WhereIsNotInFormation(this IEnumerable self, FighterFormation formation) + + public static IEnumerable WhereIsNotInFormation(this IEnumerable self, + FighterFormation formation) { return self.Where(e => !e.IsInFormation(formation)); } public static bool IsAlive(this FightWorld.Fighter self) { - return self.GetHealth() > 0; + return self.Health > 0; } public static bool IsDead(this FightWorld.Fighter self) @@ -26,16 +27,16 @@ public static class FightUtils return !self.IsAlive(); } - public static int GetHealth(this FightWorld.Fighter self) + /// + /// Changes the health of a fighter + /// + /// The fighter itself + /// The amount of health to add. Make negative to remove health + public static void ChangeHealth(this FightWorld.Fighter self, int amount) { - return Math.Max(self.health ?? self.maxHealth, 0); + self.Health += amount; } - public static void AddHealth(this FightWorld.Fighter self, int addHealth) - { - self.health = self.GetHealth() + addHealth; - } - public static bool IsInFormation(this FightWorld.Fighter self, FighterFormation formation) { return formation.ContainsFighter(self); diff --git a/scripts/CSharp/Common/Fight/FightWorld.cs b/scripts/CSharp/Common/Fight/FightWorld.cs index 96245bc..fc3e19d 100644 --- a/scripts/CSharp/Common/Fight/FightWorld.cs +++ b/scripts/CSharp/Common/Fight/FightWorld.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Babushka.scripts.CSharp.Common.Fight.Actions; using Babushka.scripts.CSharp.Common.Inventory; @@ -59,9 +60,17 @@ public partial class FightWorld : Node public required int maxHealth; public required List availableActions; public const int MaxActionPoints = 1; - public int? health = null; // null => initialize to full health on spawn public int actionPointsLeft; + private int? _healthBacking = null; + + public int Health + { + get => _healthBacking ?? maxHealth; + set => _healthBacking = Math.Clamp(value, 0, maxHealth); + } + + public FighterAction AutoSelectAction() { return availableActions.Random() ?? new FighterAction.Skip(); @@ -86,6 +95,7 @@ public partial class FightWorld : Node // settings [Export] private ItemResource? _itemToDropByEnemyGroup; + [Export] public ItemResource? itemBeetrootToEatForHealth; public void ResetFightWorld() { @@ -200,7 +210,6 @@ public partial class FightWorld : Node var enemy = new Fighter { type = type, - health = null, maxHealth = GD.RandRange(8, 20), availableActions = [ diff --git a/scripts/CSharp/Common/Fight/FighterAction.cs b/scripts/CSharp/Common/Fight/FighterAction.cs index 789120d..e3ea88b 100644 --- a/scripts/CSharp/Common/Fight/FighterAction.cs +++ b/scripts/CSharp/Common/Fight/FighterAction.cs @@ -30,8 +30,6 @@ public abstract class FighterAction public abstract bool DetailComplete(); } - private bool _abort = false; - #region Shortcuts protected static FightWorld.FightHappeningData HappeningData => @@ -65,15 +63,7 @@ public abstract class FighterAction { } - public void MarkAbort() - { - _abort = true; - } - - public bool MarkedForAbort() - { - return _abort; - } + public virtual bool ShouldAbort() => false; /// /// Returns the FighterActionDetail, that is currently handled. diff --git a/scripts/CSharp/Common/Fight/FighterDamageIndicatorFlyingNumber.cs b/scripts/CSharp/Common/Fight/FighterDamageIndicatorFlyingNumber.cs index e833d67..cb9cd25 100644 --- a/scripts/CSharp/Common/Fight/FighterDamageIndicatorFlyingNumber.cs +++ b/scripts/CSharp/Common/Fight/FighterDamageIndicatorFlyingNumber.cs @@ -7,9 +7,9 @@ public partial class FighterDamageIndicatorFlyingNumber : Node2D { [Export] private Label _label; - public void Initialize(int number) + public void Initialize(string text) { - _label.Text = number.ToString(); + _label.Text = text; var tween = CreateTween(); var xMovement = GD.RandRange(-150, 150); diff --git a/scripts/CSharp/Common/Fight/FighterDamageIndicatorVisual.cs b/scripts/CSharp/Common/Fight/FighterDamageIndicatorVisual.cs index 4e79ac4..7509dae 100644 --- a/scripts/CSharp/Common/Fight/FighterDamageIndicatorVisual.cs +++ b/scripts/CSharp/Common/Fight/FighterDamageIndicatorVisual.cs @@ -7,10 +7,10 @@ public partial class FighterDamageIndicatorVisual : Node2D [Export] private PackedScene _flyingNumberPrefab; - public void SpawnFlyingNumber(int number) + public void SpawnFlyingNumber(string text) { var flyingNumberInstance = _flyingNumberPrefab.Instantiate(); AddChild(flyingNumberInstance); - flyingNumberInstance.Initialize(number); + flyingNumberInstance.Initialize(text); } } \ No newline at end of file diff --git a/scripts/CSharp/Common/Fight/FighterVisual.cs b/scripts/CSharp/Common/Fight/FighterVisual.cs index 9e3b7ab..73b5621 100644 --- a/scripts/CSharp/Common/Fight/FighterVisual.cs +++ b/scripts/CSharp/Common/Fight/FighterVisual.cs @@ -42,7 +42,12 @@ public partial class FighterVisual : Node2D _boundFighter.IsInFormation(HappeningData.enemyFighterFormation) ? -1 : 1, _boundFighter.IsDead() ? .3f : 1); - healthBarVisual.UpdateHealth(_boundFighter.GetHealth(), _boundFighter.maxHealth); + UpdateHealthBarVisuals(); + } + + private void UpdateHealthBarVisuals() + { + healthBarVisual.UpdateHealth(_boundFighter.Health, _boundFighter.maxHealth); } public void SetTargetSelectionActive(bool value) @@ -78,24 +83,26 @@ public partial class FighterVisual : Node2D public async Task AnimateHit() { + UpdateHealthBarVisuals(); var tween = GetTree().CreateTween(); tween.TweenProperty(_squashParent, "scale", new Vector2(1.4f, 0.6f), 0.15); tween.TweenProperty(_squashParent, "scale", new Vector2(1, 1), 0.4) .SetTrans(Tween.TransitionType.Cubic).SetEase(Tween.EaseType.Out); await ToSignal(tween, "finished"); } - - // Keep for reference for new Heal animation - //public void HealAnimation() - //{ - // EmitSignalHealed(); - // var tween = GetTree().CreateTween(); - // tween.TweenProperty(this, "scale", new Vector2(0.6f, 1.4f), 0.15); - // tween.TweenProperty(this, "scale", new Vector2(1, 1), 0.4) - // .SetTrans(Tween.TransitionType.Cubic).SetEase(Tween.EaseType.Out); - //} - public void SpawnDamageIndicatorNumber(int number) + + public async Task AnimateHeal() + { + UpdateHealthBarVisuals(); + var tween = GetTree().CreateTween(); + tween.TweenProperty(_squashParent, "scale", new Vector2(0.6f, 1.4f), 0.15); + tween.TweenProperty(_squashParent, "scale", new Vector2(1, 1), 0.4) + .SetTrans(Tween.TransitionType.Cubic).SetEase(Tween.EaseType.Out); + await ToSignal(tween, "finished"); + } + + public void SpawnDamageIndicatorNumber(string text) { - _fighterDamageIndicatorVisual.SpawnFlyingNumber(number); + _fighterDamageIndicatorVisual.SpawnFlyingNumber(text); } } \ No newline at end of file diff --git a/scripts/CSharp/Common/Inventory/InventoryInstance.cs b/scripts/CSharp/Common/Inventory/InventoryInstance.cs index 55fb944..431ec45 100644 --- a/scripts/CSharp/Common/Inventory/InventoryInstance.cs +++ b/scripts/CSharp/Common/Inventory/InventoryInstance.cs @@ -17,7 +17,7 @@ public partial class InventoryInstance : Node, ISaveable [Signal] public delegate void InventoryContentsChangedEventHandler(); - + public static string ID = "inventoryInstance"; /// @@ -52,7 +52,7 @@ public partial class InventoryInstance : Node, ISaveable SlotAmountChanged += UpdateSaveData; SavegameService.OnSaveGameReset += SaveGameReset; } - + public override void _ExitTree() { InventoryContentsChanged -= UpdateSaveData; @@ -101,7 +101,8 @@ public partial class InventoryInstance : Node, ISaveable return InventoryActionResult.DestinationFull; } - var itemInstance = _slots[slotIndex].itemInstance ?? new ItemInstance { blueprint = newItem.blueprint, amount = 0 }; + var itemInstance = _slots[slotIndex].itemInstance ?? + new ItemInstance { blueprint = newItem.blueprint, amount = 0 }; var maxStack = itemInstance!.blueprint.maxStack; var freeOnStack = maxStack - itemInstance.amount; var moveAmount = Math.Min(freeOnStack, newItem.amount); @@ -130,12 +131,12 @@ public partial class InventoryInstance : Node, ISaveable itemInstance = _slots[inventorySlot].itemInstance; if (itemInstance == null) return InventoryActionResult.SourceDoesNotExist; - + itemInstance.amount -= 1; - - if(itemInstance.amount == 0) + + if (itemInstance.amount == 0) _slots[inventorySlot].itemInstance = null; - + EmitSignal(SignalName.InventoryContentsChanged); return InventoryActionResult.Success; } @@ -145,6 +146,38 @@ public partial class InventoryInstance : Node, ISaveable return RemoveItem(inventorySlot, out _); } + public InventoryActionResult TryRemoveAllItems(ItemInstance items) + { + var hasItemsCount = TotalItemsOfBlueprint(items.blueprint); + if (hasItemsCount < items.amount) + return InventoryActionResult.SourceDoesNotExist; + + var amountToRemove = items.amount; + foreach (var s in _slots) + { + if (s.IsEmpty() || s.itemInstance!.blueprint != items.blueprint) + continue; + + var slotItem = s.itemInstance!; + if (slotItem.amount <= amountToRemove) + { + amountToRemove -= slotItem.amount; + s.itemInstance = null; + } + else + { + slotItem.amount -= amountToRemove; + amountToRemove = 0; + } + + if (amountToRemove == 0) + break; + } + + EmitSignal(SignalName.InventoryContentsChanged); + return InventoryActionResult.Success; + } + public InventoryActionResult AddItemToSlot(ItemInstance itemInstance, int destinationSlot) { if (destinationSlot < 0 || destinationSlot >= _slots.Count) @@ -174,8 +207,8 @@ public partial class InventoryInstance : Node, ISaveable { return items.All(HasItems); } - - #region SAVE AND LOAD + + #region SAVE AND LOAD public void UpdateSaveData() { @@ -189,17 +222,17 @@ public partial class InventoryInstance : Node, ISaveable string[] value = new string[2]; value[0] = _slots[i].itemInstance.blueprint.ResourcePath; value[1] = _slots[i].itemInstance.amount.ToString(); - payloadData.Add(key,value); + payloadData.Add(key, value); } } - + SavegameService.AppendDataToSave(ID, payloadData); } public void LoadFromSaveData() { var id = ID; - + Godot.Collections.Dictionary save = SavegameService.GetSaveData(id); if (save.Count > 0) @@ -210,15 +243,15 @@ public partial class InventoryInstance : Node, ISaveable { string[] savePayload = inventoryItemData.AsStringArray(); ItemResource resource = ResourceLoader.Load(savePayload[0]); - int _amount = int.Parse(savePayload[1]); - + int _amount = int.Parse(savePayload[1]); + ItemInstance instance = new ItemInstance { blueprint = resource, amount = _amount }; AddItem(instance); } } } } - + /// /// Called when a new save is created. /// Needs to do a runtime check because the InventoryInstance is already in existence at the beginning of the first scene. @@ -230,5 +263,6 @@ public partial class InventoryInstance : Node, ISaveable slot.itemInstance = null; } } + #endregion -} +} \ No newline at end of file diff --git a/scripts/CSharp/Common/Inventory/ItemInstance.cs b/scripts/CSharp/Common/Inventory/ItemInstance.cs index f541e97..f0dd765 100644 --- a/scripts/CSharp/Common/Inventory/ItemInstance.cs +++ b/scripts/CSharp/Common/Inventory/ItemInstance.cs @@ -6,7 +6,7 @@ namespace Babushka.scripts.CSharp.Common.Inventory; [GlobalClass] public partial class ItemInstance: Resource { - [Export] public ItemResource blueprint; + [Export] public required ItemResource blueprint; [Export] public int amount = 1; public ItemInstance Clone()