using System; using System.Collections.Generic; using System.Linq; using Babushka.scripts.CSharp.Common.Camera; using Babushka.scripts.CSharp.Common.Util; using Godot; namespace Babushka.scripts.CSharp.Common.Fight; public partial class FightInstance : Node2D //TODO: remake { [Export(PropertyHint.ArrayType)] private Node2D[] _friendlyFightSpots; [Export(PropertyHint.ArrayType)] private Node2D[] _enemyFightSpots; [Export] public Node2D camPositionNode; [Export] private FightStateManager _fightStateManager; [Export] private Label _fightEndText; [Signal] public delegate void FightStartedEventHandler(); [Signal] public delegate void FightEndedEventHandler(); private List _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 (CheckWinAndSetState()) break; if (CheckFriendlyActionsLeftAndSetState()) break; ShowAttackButtons(); break; case FightStateManager.FightState.InputTargetSelect: ShowTargetButtons(); break; case FightStateManager.FightState.FriendAttackAnim: ExecuteAttack(); GetTree().CreateTimer(1).Timeout += () => _fightStateManager.CurrentFightState = FightStateManager.FightState.Input; break; case FightStateManager.FightState.Enemy: if (CheckWinAndSetState()) break; if (CheckEnemyActionsLeftAndSetState()) break; DecideEnemyAttack(); _fightStateManager.CurrentFightState = FightStateManager.FightState.EnemyAttackAnim; break; case FightStateManager.FightState.EnemyAttackAnim: ExecuteAttack(); GetTree().CreateTimer(1).Timeout += () => _fightStateManager.CurrentFightState = FightStateManager.FightState.Enemy; 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; case FightStateManager.FightState.ChangeSideToEnemy: ResetEnemyActions(); _fightStateManager.CurrentFightState = FightStateManager.FightState.Enemy; break; case FightStateManager.FightState.ChangeSideToFriendly: ResetFriendlyActions(); _fightStateManager.CurrentFightState = FightStateManager.FightState.Input; break; case FightStateManager.FightState.Heal: Heal(); GetTree().CreateTimer(1).Timeout += () => _fightStateManager.CurrentFightState = FightStateManager.FightState.Input; break; } }; } private void Heal() { // TODO: make heal staging system _friendlyFighters.Where(f => !f.IsDead()).ForEach(f => { f.Health += 50; f.HealAnimation(); f.DecrementActions(); }); UpdateHealthVisual(); } private void ResetEnemyActions() { _enemyFighters.ForEach(f => f.ResetActions()); } private void ResetFriendlyActions() { _friendlyFighters.ForEach(f => f.ResetActions()); } /** * * true if the state was changed * */ private bool CheckFriendlyActionsLeftAndSetState() { var hasActionsLeft = _friendlyFighters.Where(f => !f.IsDead()).Any(f => f.HasActionsLeft()); if (hasActionsLeft) { return false; } // else _fightStateManager.CurrentFightState = FightStateManager.FightState.ChangeSideToEnemy; return true; } /** * * true if the state was changed * */ private bool CheckEnemyActionsLeftAndSetState() { var hasActionsLeft = _enemyFighters.Where(f => !f.IsDead()).Any(f => f.HasActionsLeft()); if (hasActionsLeft) { return false; } // else _fightStateManager.CurrentFightState = FightStateManager.FightState.ChangeSideToFriendly; return true; } private void CleanUp() { _enemyFighters.ForEach(f => f.QueueFree()); _friendlyFighters.ForEach(f => f.QueueFree()); _enemyFighters = new(); _friendlyFighters = new(); } private void DecideEnemyAttack() { var availableEnemyFighters = _enemyFighters .Where(f => !f.IsDead()) .Where(f=>f.HasActionsLeft()) .ToList(); var aliveFriendlyFighters = _friendlyFighters .Where(f => !f.IsDead()) .ToList(); if (availableEnemyFighters.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 = availableEnemyFighters.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.DecrementActions(); _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.Where(f => !f.IsDead()).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 void SelectHeal(Fighter fighter) { _fightStateManager.CurrentFightState = FightStateManager.FightState.Heal; } public bool CheckWinAndSetState() { 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; }