From b6fd6292e3665f2644b47aa2795c74f2ea7b9bc5 Mon Sep 17 00:00:00 2001 From: cblech Date: Thu, 10 Jul 2025 03:38:48 +0200 Subject: [PATCH] Basic fighting system --- art/ui/UI/AttackButton.png | Bin 0 -> 11584 bytes art/ui/UI/AttackButton.png.import | 34 + art/ui/UI/MagicButton.png | Bin 0 -> 12915 bytes art/ui/UI/MagicButton.png.import | 34 + art/ui/UI/attack_select_wheel.png | Bin 0 -> 8866 bytes art/ui/UI/attack_select_wheel.png.import | 34 + prefabs/fight/fight_base_scene.tscn | 49 + prefabs/fight/fight_manager_autoload.tscn | 8 + .../fight/fighters/enemy_blob_fighter.tscn | 89 + prefabs/fight/fighters/vesna_fighter.tscn | 84 + project.godot | 4 +- scenes/Babushka_scene_forest_fight_1_2d.tscn | 2139 +++++++++++++++++ .../CSharp/Common/Camera/CameraController.cs | 28 +- scripts/CSharp/Common/Fight/FightInstance.cs | 280 +++ .../CSharp/Common/Fight/FightInstance.cs.uid | 1 + scripts/CSharp/Common/Fight/FightManager.cs | 28 + .../CSharp/Common/Fight/FightManager.cs.uid | 1 + scripts/CSharp/Common/Fight/FightParty.cs | 6 + scripts/CSharp/Common/Fight/FightParty.cs.uid | 1 + scripts/CSharp/Common/Fight/FightStarter.cs | 19 + .../CSharp/Common/Fight/FightStarter.cs.uid | 1 + .../CSharp/Common/Fight/FightStateManager.cs | 73 + .../Common/Fight/FightStateManager.cs.uid | 1 + scripts/CSharp/Common/Fight/Fighter.cs | 131 + scripts/CSharp/Common/Fight/Fighter.cs.uid | 1 + scripts/CSharp/Common/Util/LinqExtras.cs | 27 + 26 files changed, 3065 insertions(+), 8 deletions(-) create mode 100644 art/ui/UI/AttackButton.png create mode 100644 art/ui/UI/AttackButton.png.import create mode 100644 art/ui/UI/MagicButton.png create mode 100644 art/ui/UI/MagicButton.png.import create mode 100644 art/ui/UI/attack_select_wheel.png create mode 100644 art/ui/UI/attack_select_wheel.png.import create mode 100644 prefabs/fight/fight_base_scene.tscn create mode 100644 prefabs/fight/fight_manager_autoload.tscn create mode 100644 prefabs/fight/fighters/enemy_blob_fighter.tscn create mode 100644 prefabs/fight/fighters/vesna_fighter.tscn create mode 100644 scenes/Babushka_scene_forest_fight_1_2d.tscn create mode 100644 scripts/CSharp/Common/Fight/FightInstance.cs create mode 100644 scripts/CSharp/Common/Fight/FightInstance.cs.uid create mode 100644 scripts/CSharp/Common/Fight/FightManager.cs create mode 100644 scripts/CSharp/Common/Fight/FightManager.cs.uid create mode 100644 scripts/CSharp/Common/Fight/FightParty.cs create mode 100644 scripts/CSharp/Common/Fight/FightParty.cs.uid create mode 100644 scripts/CSharp/Common/Fight/FightStarter.cs create mode 100644 scripts/CSharp/Common/Fight/FightStarter.cs.uid create mode 100644 scripts/CSharp/Common/Fight/FightStateManager.cs create mode 100644 scripts/CSharp/Common/Fight/FightStateManager.cs.uid create mode 100644 scripts/CSharp/Common/Fight/Fighter.cs create mode 100644 scripts/CSharp/Common/Fight/Fighter.cs.uid create mode 100644 scripts/CSharp/Common/Util/LinqExtras.cs diff --git a/art/ui/UI/AttackButton.png b/art/ui/UI/AttackButton.png new file mode 100644 index 0000000000000000000000000000000000000000..ebbddc58deaf25fdb159ffbfc15db933e8c97cca GIT binary patch literal 11584 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 literal 0 HcmV?d00001 diff --git a/art/ui/UI/AttackButton.png.import b/art/ui/UI/AttackButton.png.import new file mode 100644 index 0000000..7370887 --- /dev/null +++ b/art/ui/UI/AttackButton.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bn56p0ytuo060" +path="res://.godot/imported/AttackButton.png-ab1949863046f66b014201d64778c962.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://art/ui/UI/AttackButton.png" +dest_files=["res://.godot/imported/AttackButton.png-ab1949863046f66b014201d64778c962.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/art/ui/UI/MagicButton.png b/art/ui/UI/MagicButton.png new file mode 100644 index 0000000000000000000000000000000000000000..d1e06c8612c9b08133eee0f47815d61a852cda66 GIT binary patch literal 12915 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 _friendlyFighters = new(); + private List _enemyFighters = new(); + + private FightAttack? _stagedAttack = null; + + public override void _Ready() + { + //_fightStateManager.CurrentFightState = FightStateManager.FightState.FightStartAnim; + _fightStateManager.ExitingTransition += from => + { + switch (from) + { + case FightStateManager.FightState.None: + CaptureCamera(); + Show(); + EmitSignalFightStarted(); + break; + case FightStateManager.FightState.Input: + HideAttackButtons(); + break; + case FightStateManager.FightState.InputTargetSelect: + HideTargetButtons(); + break; + case FightStateManager.FightState.FriendAttackAnim: + _stagedAttack = null; + break; + case FightStateManager.FightState.PlayerWinAnim: + case FightStateManager.FightState.EnemyWinAnim: + _fightEndText.Text = ""; + break; + } + }; + + _fightStateManager.EnteringTransition += to => + { + switch (to) + { + case FightStateManager.FightState.None: + EmitSignalFightEnded(); + CleanUp(); + Hide(); + ReleaseCamera(); + break; + case FightStateManager.FightState.Input: + if (CheckWin()) + { + break; + } + ShowAttackButtons(); + break; + case FightStateManager.FightState.InputTargetSelect: + ShowTargetButtons(); + break; + case FightStateManager.FightState.FriendAttackAnim: + ExecuteAttack(); + GetTree().CreateTimer(1).Timeout += () => _fightStateManager.CurrentFightState = FightStateManager.FightState.Enemy; + break; + case FightStateManager.FightState.Enemy: + if (CheckWin()) + { + break; + } + DecideEnemyAttack(); + _fightStateManager.CurrentFightState = FightStateManager.FightState.EnemyAttackAnim; + break; + case FightStateManager.FightState.EnemyAttackAnim: + ExecuteAttack(); + GetTree().CreateTimer(1).Timeout += () => _fightStateManager.CurrentFightState = FightStateManager.FightState.Input; + break; + case FightStateManager.FightState.PlayerWinAnim: + _fightEndText.Text = "You Win!"; + GetTree().CreateTimer(3).Timeout += () => _fightStateManager.CurrentFightState = FightStateManager.FightState.None; + break; + case FightStateManager.FightState.EnemyWinAnim: + _fightEndText.Text = "You Died :("; + GetTree().CreateTimer(3).Timeout += () => _fightStateManager.CurrentFightState = FightStateManager.FightState.None; + break; + } + }; + } + private void CleanUp() + { + _enemyFighters.ForEach(f => f.QueueFree()); + _friendlyFighters.ForEach(f => f.QueueFree()); + _enemyFighters = new(); + _friendlyFighters = new(); + } + private void DecideEnemyAttack() + { + var aliveEnemyFighters = _enemyFighters.Where(f => !f.IsDead()).ToList(); + var aliveFriendlyFighters = _friendlyFighters.Where(f => !f.IsDead()).ToList(); + + if (aliveEnemyFighters.Count <= 0) + throw new InvalidOperationException("No enemy fighters available for attack."); + + if (aliveFriendlyFighters.Count <= 0) + throw new InvalidOperationException("No friendly fighters available to target."); + + var fighter = aliveEnemyFighters.Random(); + var target = aliveFriendlyFighters.Random(); + + _stagedAttack = new FightAttack + { + attacker = fighter!, + needsSelectedTarget = true, + damage = fighter!.attackStrength, + target = target! + }; + } + + private void ExecuteAttack() + { + if (_stagedAttack == null) + throw new InvalidOperationException("No staged attack to execute."); + + if (!_stagedAttack.needsSelectedTarget) + throw new NotImplementedException("Non-targeted attacks are not implemented yet."); + + if (_stagedAttack.needsSelectedTarget && _stagedAttack.target == null) + throw new InvalidOperationException("No target selected for the staged attack."); + + _stagedAttack.target!.Health -= _stagedAttack.damage; + + _stagedAttack.attacker.AttackAnimation(_stagedAttack); + + UpdateHealthVisual(); + } + + private void UpdateHealthVisual() + { + _friendlyFighters + .Concat(_enemyFighters) + .ForEach(f => f.UpdateHealthVisual()); + } + + private void ReleaseCamera() + { + CameraController.Instance.fightToShow = null; + } + + private void CaptureCamera() + { + CameraController.Instance.fightToShow = this; + } + + public void Start(FightParty fightParty, PackedScene?[] enemies) + { + if (_fightStateManager.IsRunning()) + { + GD.PushWarning("Can not start a running fight"); + return; + } + + if (fightParty.vesna) + { + InstantiateFighter(_friendlyFightSpots[1], FightManager.Instance.fightingVesnaScene); + } + + for (var i = 0; i < Math.Min(_enemyFightSpots.Length, enemies.Length); i++) + { + var enemy = enemies[i]; + if (enemy == null) + continue; + + InstantiateFighter(_enemyFightSpots[i], enemy, true); + } + + _fightStateManager.ToStartAnim(); + } + + private void InstantiateFighter(Node2D parent, PackedScene fighterScene, bool isEnemy = false) + { + var fighter = fighterScene.Instantiate(); + fighter.fightInstance = this; + parent.AddChild(fighter); + + if (isEnemy) + { + _enemyFighters.Add(fighter); + } + else + { + _friendlyFighters.Add(fighter); + } + } + + public void SelectAttack(Fighter fighter) + { + _stagedAttack = new FightAttack + { + attacker = fighter, + damage = fighter.attackStrength, + needsSelectedTarget = true + }; + + if (_stagedAttack.needsSelectedTarget) + { + _fightStateManager.CurrentFightState = FightStateManager.FightState.InputTargetSelect; + } + else + { + _fightStateManager.CurrentFightState = FightStateManager.FightState.FriendAttackAnim; + } + } + + private void HideAttackButtons() + { + _friendlyFighters.ForEach(f => f.HideAttackButton()); + } + + private void ShowAttackButtons() + { + _friendlyFighters.ForEach(f => f.ShowAttackButton()); + } + + private void HideTargetButtons() + { + _enemyFighters.ForEach(f => f.HideTargetButtons()); + } + + private void ShowTargetButtons() + { + _enemyFighters.ForEach(f => f.ShowTargetButtons()); + } + + public void SelectTargetAndAttack(Fighter fighter) + { + if (_stagedAttack == null) + throw new InvalidOperationException("No staged attack to select target for."); + + _stagedAttack.target = fighter; + + _fightStateManager.CurrentFightState = FightStateManager.FightState.FriendAttackAnim; + } + + public bool CheckWin() + { + if (_enemyFighters.All(f => f.IsDead())) + { + _fightStateManager.CurrentFightState = FightStateManager.FightState.PlayerWinAnim; + return true; + } + if (_friendlyFighters.All(f => f.IsDead())) + { + _fightStateManager.CurrentFightState = FightStateManager.FightState.EnemyWinAnim; + return true; + } + return false; + } +} +public class FightAttack +{ + public int damage; + public bool needsSelectedTarget; + public Fighter? target; + public Fighter attacker; +} diff --git a/scripts/CSharp/Common/Fight/FightInstance.cs.uid b/scripts/CSharp/Common/Fight/FightInstance.cs.uid new file mode 100644 index 0000000..c16905c --- /dev/null +++ b/scripts/CSharp/Common/Fight/FightInstance.cs.uid @@ -0,0 +1 @@ +uid://c76mhhqyk4lgh diff --git a/scripts/CSharp/Common/Fight/FightManager.cs b/scripts/CSharp/Common/Fight/FightManager.cs new file mode 100644 index 0000000..ef0652c --- /dev/null +++ b/scripts/CSharp/Common/Fight/FightManager.cs @@ -0,0 +1,28 @@ +using Godot; +namespace Babushka.scripts.CSharp.Common.Fight; + +public partial class FightManager : Node +{ + #region AutoLoad ( Contains _EnterTree() ) + + public static FightManager Instance { get; private set; } = null!; + + public override void _EnterTree() + { + Instance = this; + } + + #endregion + + [Export] + public PackedScene fightingVesnaScene; + + public FightParty fightParty = new(); + + public void StartFight(PackedScene[] enemies, FightInstance instance) + { + GD.Print("Starting Fight"); + instance.Start(fightParty, enemies); + } + +} diff --git a/scripts/CSharp/Common/Fight/FightManager.cs.uid b/scripts/CSharp/Common/Fight/FightManager.cs.uid new file mode 100644 index 0000000..ead98f6 --- /dev/null +++ b/scripts/CSharp/Common/Fight/FightManager.cs.uid @@ -0,0 +1 @@ +uid://j5ge24rk25wm diff --git a/scripts/CSharp/Common/Fight/FightParty.cs b/scripts/CSharp/Common/Fight/FightParty.cs new file mode 100644 index 0000000..c38c8c4 --- /dev/null +++ b/scripts/CSharp/Common/Fight/FightParty.cs @@ -0,0 +1,6 @@ +namespace Babushka.scripts.CSharp.Common.Fight; + +public class FightParty +{ + public bool vesna = true; +} diff --git a/scripts/CSharp/Common/Fight/FightParty.cs.uid b/scripts/CSharp/Common/Fight/FightParty.cs.uid new file mode 100644 index 0000000..6af5aa3 --- /dev/null +++ b/scripts/CSharp/Common/Fight/FightParty.cs.uid @@ -0,0 +1 @@ +uid://cvhgnboybc4cm diff --git a/scripts/CSharp/Common/Fight/FightStarter.cs b/scripts/CSharp/Common/Fight/FightStarter.cs new file mode 100644 index 0000000..0fd6ae8 --- /dev/null +++ b/scripts/CSharp/Common/Fight/FightStarter.cs @@ -0,0 +1,19 @@ +using Godot; +namespace Babushka.scripts.CSharp.Common.Fight; + +public partial class FightStarter : Node +{ + [Export(PropertyHint.ArrayType)] private PackedScene[] enemies; + [Export] private FightInstance _fightInstance; + [Export] private bool _once = true; + private bool hasBeenStarted = false; + + public void Start(Node2D _) + { + if (_once && hasBeenStarted) + return; + + hasBeenStarted = true; + FightManager.Instance.StartFight(enemies, _fightInstance); + } +} diff --git a/scripts/CSharp/Common/Fight/FightStarter.cs.uid b/scripts/CSharp/Common/Fight/FightStarter.cs.uid new file mode 100644 index 0000000..58860f7 --- /dev/null +++ b/scripts/CSharp/Common/Fight/FightStarter.cs.uid @@ -0,0 +1 @@ +uid://di0xxwfw43m0i diff --git a/scripts/CSharp/Common/Fight/FightStateManager.cs b/scripts/CSharp/Common/Fight/FightStateManager.cs new file mode 100644 index 0000000..9d86e2c --- /dev/null +++ b/scripts/CSharp/Common/Fight/FightStateManager.cs @@ -0,0 +1,73 @@ +using Godot; +namespace Babushka.scripts.CSharp.Common.Fight; + +public partial class FightStateManager : Node +{ + [Signal] + public delegate void ExitingTransitionEventHandler(FightState exitingState); + + [Signal] + public delegate void EnteringTransitionEventHandler(FightState enteringState); + + public enum FightState + { + None, + FightStartAnim, + Input, + InputTargetSelect, + FriendAttackAnim, + Enemy, + EnemyAttackAnim, + PlayerWinAnim, + EnemyWinAnim + } + + private FightState _fightStateBacking = FightState.None; + + public FightState CurrentFightState + { + set => Transition(_fightStateBacking, value); + get => _fightStateBacking; + } + + private void Transition(FightState from, FightState to) + { + if(from == to) + return; + + GD.Print($"Transitioning from {from} to {to}"); + ExitTransition(from); + _fightStateBacking = to; + EnterTransition(to); + } + + private void ExitTransition(FightState from) + { + EmitSignalExitingTransition(from); + } + + private void EnterTransition(FightState to) + { + EmitSignalEnteringTransition(to); + switch (to) + { + case FightState.FightStartAnim: + EnterFightStartAnim(); + break; + } + } + private void EnterFightStartAnim() + { + GetTree().CreateTimer(1).Timeout += () => CurrentFightState = FightState.Input; + } + + public void ToStartAnim() + { + CurrentFightState = FightState.FightStartAnim; + } + + public bool IsRunning() + { + return CurrentFightState != FightState.None; + } +} diff --git a/scripts/CSharp/Common/Fight/FightStateManager.cs.uid b/scripts/CSharp/Common/Fight/FightStateManager.cs.uid new file mode 100644 index 0000000..1527d8c --- /dev/null +++ b/scripts/CSharp/Common/Fight/FightStateManager.cs.uid @@ -0,0 +1 @@ +uid://oe1uypehqvr7 diff --git a/scripts/CSharp/Common/Fight/Fighter.cs b/scripts/CSharp/Common/Fight/Fighter.cs new file mode 100644 index 0000000..af3a699 --- /dev/null +++ b/scripts/CSharp/Common/Fight/Fighter.cs @@ -0,0 +1,131 @@ +using Godot; +namespace Babushka.scripts.CSharp.Common.Fight; + +public partial class Fighter : Node2D +{ + [Export] public string name; + [Export] public int maxHealth; + [Export] public int attackStrength; + + [ExportCategory("References")] + [Export] private Node2D _attackButtons; + [Export] private Node2D _targetButtons; + [Export] private Node2D _targetMarker; + [Export] private Label _healthText; + [Export] private Node2D _visualSprite; + + + private int _health; + + + public FightInstance fightInstance; + public int Health + { + get => _health; + set + { + _health = value; + if (_health <= 0) + { + _health = 0; + Die(); + } + } + } + + private void Die() + { + _visualSprite.Scale = new Vector2(1, 0.3f); + } + + public override void _Ready() + { + Health = maxHealth; + UpdateHealthVisual(); + } + + public void Attack() + { + fightInstance.SelectAttack(this); + } + + public void HideAttackButton() + { + _attackButtons.Hide(); + } + + public void ShowAttackButton() + { + _attackButtons.Show(); + } + + public void HideTargetButtons() + { + _targetButtons.Hide(); + } + + public void ShowTargetButtons() + { + _targetButtons.Show(); + } + + public void TargetMouseEvent(Node viewport, InputEvent inputEvent, int shapeIdx) + { + if (inputEvent.IsPressed()) + ClickedTarget(); + } + + public void AttackMouseEvent(Node viewport, InputEvent inputEvent, int shapeIdx) + { + if (inputEvent.IsPressed()) + ClickedAttack(); + } + + private void ClickedAttack() + { + fightInstance.SelectAttack(this); + } + + private void ClickedTarget() + { + fightInstance.SelectTargetAndAttack(this); + } + + public void StartHoverTarget() + { + _targetMarker.Visible = true; + } + + public void EndHoverTarget() + { + _targetMarker.Visible = false; + } + + public void UpdateHealthVisual() + { + _healthText.Text = $"{Health}/{maxHealth}"; + } + + public void AttackAnimation(FightAttack attack) + { + var tween = GetTree().CreateTween(); + tween.TweenProperty(this, "global_position", attack.target.GlobalPosition, 0.15); + tween.TweenCallback(Callable.From(() => attack.target?.HitAnimation(attack))); + tween.TweenProperty(this, "position", new Vector2(0, 0), 0.7) + .SetTrans(Tween.TransitionType.Cubic).SetEase(Tween.EaseType.Out); + + } + + private void HitAnimation(FightAttack attack) + { + var tween = GetTree().CreateTween(); + tween.TweenProperty(this, "scale", new Vector2(1.4f, 0.6f), 0.15); + tween.TweenProperty(this, "scale", new Vector2(1,1), 0.4) + .SetTrans(Tween.TransitionType.Cubic).SetEase(Tween.EaseType.Out); + } + + public bool IsDead() + { + return Health <= 0; + } +} diff --git a/scripts/CSharp/Common/Fight/Fighter.cs.uid b/scripts/CSharp/Common/Fight/Fighter.cs.uid new file mode 100644 index 0000000..1a45840 --- /dev/null +++ b/scripts/CSharp/Common/Fight/Fighter.cs.uid @@ -0,0 +1 @@ +uid://by88f32fou7lh diff --git a/scripts/CSharp/Common/Util/LinqExtras.cs b/scripts/CSharp/Common/Util/LinqExtras.cs new file mode 100644 index 0000000..92a79c1 --- /dev/null +++ b/scripts/CSharp/Common/Util/LinqExtras.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data.SqlTypes; +using System.Linq; +using System.Xml.Schema; +namespace Babushka.scripts.CSharp.Common.Util; + +public static class LinqExtras +{ + public static void ForEach(this IEnumerable self, Action action) + { + foreach (var t in self) + { + action.Invoke(t); + } + } + + public static T? Random(this IEnumerable self) + { + var selfList = self.ToList(); + if (selfList.Count == 0) return default; + if (selfList.Count == 1) return selfList[0]; + var randomIndex = new Random().Next(0, selfList.Count); + return selfList[randomIndex]; + } +}