Compare commits

..

2 Commits

Author SHA1 Message Date
jonathan 45d4b26b6a ♻️ Implemented FightersFormation to keep track of the fight entered state of the fighters 2025-10-07 18:08:53 +02:00
jonathan 719b1fa800 ♻️Code cleanup 2025-10-07 15:50:33 +02:00
17 changed files with 238 additions and 161 deletions
+3 -4
View File
@@ -2,8 +2,7 @@
[ext_resource type="Script" uid="uid://cql8mt5jsmcdl" path="res://scripts/CSharp/Common/Fight/FightSceneSwitcher.cs" id="1_5dt1r"]
[node name="FightSceneSwitcher" type="Node" node_paths=PackedStringArray("sceneRoot")]
[node name="FightSceneSwitcher" type="Node"]
script = ExtResource("1_5dt1r")
sceneRoot = NodePath("")
fightRoomScenePath = "res://scenes/Babushka_scene_fight_world_room.tscn"
fightHappeningScene = "res://scenes/Babushka_scene_fight_happening.tscn"
_fightRoomScenePath = "res://scenes/Babushka_scene_fight_world_room.tscn"
_fightHappeningScene = "res://scenes/Babushka_scene_fight_happening.tscn"
+2 -2
View File
@@ -59,8 +59,8 @@ visible = false
script = ExtResource("10_qqd8u")
_fightSceneSwitcher = NodePath("FightSceneSwitcher")
[node name="FightSceneSwitcher" parent="SwitchSceneOnFightEnd" node_paths=PackedStringArray("sceneRoot") instance=ExtResource("2_phrlx")]
sceneRoot = NodePath("../..")
[node name="FightSceneSwitcher" parent="SwitchSceneOnFightEnd" node_paths=PackedStringArray("_sceneRoot") instance=ExtResource("2_phrlx")]
_sceneRoot = NodePath("../..")
[node name="ActionSelect" type="CanvasLayer" parent="." node_paths=PackedStringArray("_attackActionButton", "_summonActionButton", "_talkActionButton", "_fleeActionButton")]
visible = false
+2 -1
View File
@@ -2148,8 +2148,9 @@ position = Vector2(1560, 422)
[node name="Spawn4" type="Node2D" parent="YSorted/EnemyGroupSpawns"]
position = Vector2(-1127, 671)
[node name="FightSceneSwitcher" parent="." instance=ExtResource("40_elhbh")]
[node name="FightSceneSwitcher" parent="." node_paths=PackedStringArray("_sceneRoot") instance=ExtResource("40_elhbh")]
unique_name_in_owner = true
_sceneRoot = NodePath("..")
[node name="FightSceneSetup" type="Node" parent="." node_paths=PackedStringArray("_enemyGroupSpawns", "_fightSceneSwitcher")]
script = ExtResource("40_cvg1r")
@@ -52,7 +52,7 @@ public class AllyAttackAction : FighterAction
public override async Task AnimateAction(AllFightersVisual allFightersVisual)
{
var currentFighter = HappeningData.fighterStack.Current;
var currentFighter = HappeningData.fighterTurn.Current;
var targetFighter = targetSelect.GetTarget();
var currentFighterVisual = allFightersVisual.GetVisualForFighter(currentFighter);
@@ -23,7 +23,7 @@ public class BlobAttackAction : FighterAction
public override async Task AnimateAction(AllFightersVisual allFightersVisual)
{
var currentFighter = HappeningData.fighterStack.Current;
var currentFighter = HappeningData.fighterTurn.Current;
var targetFighter = FightWorld.Instance.allyFighters.vesnaFighter;
var currentFighterVisual = allFightersVisual.GetVisualForFighter(currentFighter);
@@ -1,10 +1,11 @@
using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
using Babushka.scripts.CSharp.Common.Fight;
using Babushka.scripts.CSharp.Common.Fight.ActionDetails;
using Babushka.scripts.CSharp.Common.Util;
using Godot;
namespace Babushka.scripts.CSharp.Common.Fight;
public partial class AllFightersVisual : Node
{
@@ -57,7 +58,7 @@ public partial class AllFightersVisual : Node
if (from == FightHappening.FightState.ActionAnim)
{
_fighterVisuals.Values.ForEach(fv=>fv.UpdateHealthBar());
_fighterVisuals.Values.ForEach(fv => fv.UpdateHealthBar());
}
}
@@ -112,10 +113,14 @@ public partial class AllFightersVisual : Node
private void ShowTargetSelect(TargetSelectActionDetail targetDetail)
{
if (targetDetail.selectEnemy)
_fighterVisuals.Where(kv => kv.Key.isEnemy).ForEach(kv => kv.Value.SetTargetSelectionActive(true));
_fighterVisuals
.Where(kv => kv.Key.IsInFormation(HappeningData.enemyFighterFormation))
.ForEach(kv => kv.Value.SetTargetSelectionActive(true));
if (targetDetail.selectAlly)
_fighterVisuals.Where(kv => !kv.Key.isEnemy).ForEach(kv => kv.Value.SetTargetSelectionActive(true));
_fighterVisuals
.Where(kv => kv.Key.IsInFormation(HappeningData.allyFighterFormation))
.ForEach(kv => kv.Value.SetTargetSelectionActive(true));
}
private void HideTargetSelect()
@@ -8,7 +8,6 @@ public class AllyFighters
{
type = FightWorld.Fighter.Type.Vesna,
maxHealth = 20,
isEnemy = false,
availableActions =
[
new AllyAttackAction()
@@ -18,7 +17,6 @@ public class AllyFighters
{
type = FightWorld.Fighter.Type.Chuha,
maxHealth = 15,
isEnemy = false,
availableActions =
[
new FighterAction.Skip()
+36 -17
View File
@@ -61,7 +61,7 @@ public partial class FightHappening : Node
private static FightWorld.FightHappeningData HappeningData =>
FightWorld.Instance.fightHappeningData ?? throw new NoFightHappeningException();
private static FightWorld.Fighter CurrentFighter => HappeningData.fighterStack.Current;
private static FightWorld.Fighter CurrentFighter => HappeningData.fighterTurn.Current;
#endregion
@@ -88,7 +88,7 @@ public partial class FightHappening : Node
}
#endregion
public override void _Ready()
{
SetupInstance();
@@ -110,7 +110,7 @@ public partial class FightHappening : Node
action.Reset();
ChangeState(FightState.ActionCheckDetails);
}
public void DetailFilled()
{
RequireState(FightState.InputActionDetail);
@@ -204,7 +204,7 @@ public partial class FightHappening : Node
{
ChangeState(FightState.FightersEnter);
}
else if (CurrentFighter.isEnemy)
else if (CurrentFighter.IsInFormation(HappeningData.enemyFighterFormation))
{
ChangeState(FightState.EnemyActionSelect);
}
@@ -219,7 +219,7 @@ public partial class FightHappening : Node
break;
case FightState.ActionCheckDetails:
RequireNotNull(HappeningData.actionStaging);
if (ActionAbort())
ChangeState(FightState.InputActionSelect);
else if (ActionNeededDetail())
@@ -249,10 +249,15 @@ public partial class FightHappening : Node
_ = AdvanceToStateWhenDone(FightState.StateCheck, actionTime);
}
break;
case FightState.EnemyWin:
// TODO: remove and find proper solution
ReviveVesna();
break;
default: break;
}
}
#endregion
#region Game Logic
@@ -262,19 +267,23 @@ public partial class FightHappening : Node
// ally
var enteringAllyFighters = new List<FightWorld.Fighter>();
var allyFighters = FightWorld.Instance.allyFighters;
if (!allyFighters.vesnaFighter.entered)
if (!allyFighters.vesnaFighter.IsInFormation(HappeningData.allyFighterFormation))
{
enteringAllyFighters.Add(allyFighters.vesnaFighter);
}
// enemy
const int totalEnemySpace = 3;
var enemySpaceLeft = totalEnemySpace - HappeningData.enemyGroup.GetEnteredAmount();
var enemySpaceLeft = HappeningData.enemyFighterFormation.GetEmptySlotCount();
return new FightersEnterStaging
{
enteringAllyFighters = enteringAllyFighters,
enteringEnemyFighters = HappeningData.enemyGroup.GetUptoUnenteredFighters(enemySpaceLeft).ToList()
enteringEnemyFighters = HappeningData.enemyGroup.fighters
.WhereIsAlive()
.WhereIsNotInFormation(HappeningData.enemyFighterFormation)
.Take(enemySpaceLeft)
.ToList()
};
}
@@ -283,21 +292,25 @@ public partial class FightHappening : Node
Debug.Assert(HappeningData.fightersEnterStaging != null);
foreach (var fighter in HappeningData.fightersEnterStaging.enteringAllyFighters)
{
fighter.entered = true;
HappeningData.fighterStack.AddAsLast(fighter);
var emptySlotIndex = HappeningData.allyFighterFormation.GetFirstEmptySlot();
if (emptySlotIndex < 0) throw new Exception("No empty slot for ally fighter to enter");
HappeningData.allyFighterFormation.SetFighterAtPosition(emptySlotIndex, fighter);
HappeningData.fighterTurn.AddAsLast(fighter);
}
foreach (var fighter in HappeningData.fightersEnterStaging.enteringEnemyFighters)
{
fighter.entered = true;
HappeningData.fighterStack.AddAsLast(fighter);
var emptySlotIndex = HappeningData.enemyFighterFormation.GetFirstEmptySlot();
if (emptySlotIndex < 0) throw new Exception("No empty slot for enemy fighter to enter");
HappeningData.enemyFighterFormation.SetFighterAtPosition(emptySlotIndex, fighter);
HappeningData.fighterTurn.AddAsLast(fighter);
}
}
private void ExecuteNextFighter()
{
HappeningData.fighterStack.Next();
CurrentFighter.actionPointsLeft = CurrentFighter.maxActionPoints;
HappeningData.fighterTurn.Next();
CurrentFighter.actionPointsLeft = FightWorld.Fighter.MaxActionPoints;
}
private void ExecuteAction()
@@ -325,6 +338,14 @@ public partial class FightHappening : Node
return HappeningData.actionStaging.NextDetail();
}
// TODO: remove
private void ReviveVesna()
{
var vesnaFighter = FightWorld.Instance.allyFighters.vesnaFighter;
vesnaFighter.health = vesnaFighter.maxHealth;
GD.Print("Vesna has been revived. This is for the current prototype only");
}
#endregion // Game Logic
#region Utility
@@ -337,7 +358,7 @@ public partial class FightHappening : Node
throw new Exception(
$"Can not call this Method while in state {HappeningData.fightState}. Only available in {string.Join(" ,", states)}");
}
private void RequireNotNull(Object? o)
{
if (o != null)
@@ -362,6 +383,4 @@ public partial class FightHappening : Node
}
#endregion
}
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Babushka.scripts.CSharp.Common.SceneManagement;
using Godot;
@@ -7,9 +8,9 @@ namespace Babushka.scripts.CSharp.Common.Fight;
public partial class FightSceneSwitcher : Node
{
[Export] private Node sceneRoot;
[Export] private string fightRoomScenePath;
[Export] private string fightHappeningScene;
[Export] private Node _sceneRoot = null!;
[Export] private string _fightRoomScenePath = null!;
[Export] private string _fightHappeningScene = null!;
private void LoadNext()
{
@@ -17,12 +18,12 @@ public partial class FightSceneSwitcher : Node
Debug.Assert(nextRoom != null, "nextRoom!=null");
var nextFightHappening = FightWorld.Instance.fightHappeningData;
SceneTransitionThreaded.Instance.ChangeSceneToFile(nextFightHappening != null
? fightHappeningScene
: fightRoomScenePath);
UnloadAfterDelay();
? _fightHappeningScene
: _fightRoomScenePath);
_ = UnloadAfterDelay();
}
private async void UnloadAfterDelay()
private async Task UnloadAfterDelay()
{
await ToSignal(GetTree().CreateTimer(1.0f), "timeout"); // 1.0f seconds
//sceneRoot.QueueFree();
@@ -31,7 +32,7 @@ public partial class FightSceneSwitcher : Node
public void SwitchRoom(int pathIndex)
{
Debug.Assert(FightWorld.Instance.currentRoom != null, "FightWorld.Instance.currentRoom!=null");
if (!FightWorld.Instance.currentRoom.paths.TryGetValue(pathIndex, out var nextRoom))
throw new Exception("Trying to go down a non-existent path");
+11 -14
View File
@@ -6,22 +6,14 @@ namespace Babushka.scripts.CSharp.Common.Fight;
public static class FightUtils
{
public static int GetEnteredAmount(this FightWorld.FighterGroup self)
public static IEnumerable<FightWorld.Fighter> WhereIsAlive(this IEnumerable<FightWorld.Fighter> self)
{
return self.enemies.Count(e => e.IsAlive() && e.entered);
return self.Where(e => e.IsAlive());
}
public static IEnumerable<FightWorld.Fighter> GetUptoUnenteredFighters(
this FightWorld.FighterGroup self,
int maxFighters)
public static IEnumerable<FightWorld.Fighter> WhereIsNotInFormation(this IEnumerable<FightWorld.Fighter> self, FighterFormation formation)
{
if (maxFighters <= self.enemies.Count)
return self.enemies
.Where(e => !e.entered && e.IsAlive());
return self.enemies
.Where(e => !e.entered && e.IsAlive())
.Take(maxFighters);
return self.Where(e => !e.IsInFormation(formation));
}
public static bool IsAlive(this FightWorld.Fighter self)
@@ -43,9 +35,14 @@ public static class FightUtils
{
self.health = self.GetHealth() + addHealth;
}
public static bool IsInFormation(this FightWorld.Fighter self, FighterFormation formation)
{
return formation.ContainsFighter(self);
}
public static bool AreAllDead(this FightWorld.FighterGroup self)
{
return self.enemies.All(e => e.IsDead());
return self.fighters.All(e => e.IsDead());
}
}
+8 -9
View File
@@ -20,14 +20,16 @@ public partial class FightWorld : Node
public class FighterGroup
{
public required List<Fighter> enemies;
public required List<Fighter> fighters;
}
public class FightHappeningData
{
public FightHappening.FightState fightState = FightHappening.FightState.None;
public FighterStack fighterStack = new();
public required FighterGroup enemyGroup;
public FightHappening.FightState fightState = FightHappening.FightState.None;
public readonly FighterTurn fighterTurn = new();
public readonly FighterFormation allyFighterFormation = new();
public readonly FighterFormation enemyFighterFormation = new();
public FightHappening.FightersEnterStaging? fightersEnterStaging;
public FighterAction? actionStaging;
}
@@ -46,11 +48,9 @@ public partial class FightWorld : Node
public required Type type;
public required int maxHealth;
public required bool isEnemy;
public required List<FighterAction> availableActions;
public int maxActionPoints = 1;
public const int MaxActionPoints = 1;
public int? health = null; // null => initialize to full health on spawn
public bool entered = false;
public int actionPointsLeft;
public FighterAction AutoSelectAction()
@@ -147,14 +147,14 @@ public partial class FightWorld : Node
{
var enemyGroup = new FighterGroup
{
enemies = []
fighters = []
};
var enemyCount = GD.RandRange(1, 3);
for (var i = 0; i < enemyCount; i++)
{
enemyGroup.enemies.Add(GenerateSingleEnemy());
enemyGroup.fighters.Add(GenerateSingleEnemy());
}
return enemyGroup;
@@ -177,7 +177,6 @@ public partial class FightWorld : Node
{
type = type,
health = null,
isEnemy = true,
maxHealth = 12,
availableActions =
[
@@ -0,0 +1,56 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace Babushka.scripts.CSharp.Common.Fight;
public class FighterFormation
{
private readonly List<FightWorld.Fighter?> _fighters;
public FighterFormation(int slots = 3)
{
_fighters = [];
for (var i = 0; i < slots; i++)
{
_fighters.Add(null);
}
}
public FightWorld.Fighter? GetFighterAtPosition(int position)
{
Debug.Assert(position >= 0, "position>=0");
Debug.Assert(position < _fighters.Count, "position does not exist");
return _fighters[position];
}
public void SetFighterAtPosition(int position, FightWorld.Fighter value)
{
Debug.Assert(position >= 0, "position>=0");
Debug.Assert(position < _fighters.Count, "position does not exist");
_fighters[position] = value;
}
public bool ContainsFighter(FightWorld.Fighter fighter)
{
return _fighters.Contains(fighter);
}
public int GetFirstEmptySlot()
{
for (var i = 0; i < _fighters.Count; i++)
{
if (_fighters[i] == null)
return i;
}
return -1;
}
public int GetEmptySlotCount()
{
return _fighters.Count(fighter => fighter == null);
}
}
@@ -1,95 +0,0 @@
using System.Collections.Generic;
using Godot.NativeInterop;
namespace Babushka.scripts.CSharp.Common.Fight;
public class FighterStack
{
private class Node
{
public Node next;
public FightWorld.Fighter fighter;
}
private Node? currentNode;
public FightWorld.Fighter Current => currentNode.fighter;
public void Next()
{
currentNode = currentNode.next;
}
public FightWorld.Fighter PeekNext()
{
return currentNode.next.fighter;
}
public void AddAsLast(FightWorld.Fighter value)
{
// if first node
if (currentNode == null)
{
currentNode = new Node { fighter = value };
currentNode.next = currentNode;
return;
}
var newNode = new Node { fighter = value, next = currentNode };
var node = currentNode;
while (node.next != currentNode)
{
node = node.next;
}
node.next = newNode;
}
public void AddAsNext(FightWorld.Fighter value)
{
// if first node
if (currentNode == null)
{
AddAsLast(value);
return;
}
var newNode = new Node { fighter = value, next = currentNode.next };
currentNode.next = newNode;
}
public bool Remove(FightWorld.Fighter value)
{
if (currentNode == null) return false;
// if only one node
if (currentNode.next == currentNode)
{
if (currentNode.fighter == value)
{
currentNode = null;
return true;
}
return false;
}
var node = currentNode;
do
{
// next is the fighter to remove
if (node.next.fighter == value)
{
// if removing current, keep current
// it will be implicitly deleted on the next Next() call
node.next = node.next.next;
return true;
}
node = node.next;
} while (node != currentNode);
return false;
}
}
@@ -0,0 +1,97 @@
using System;
using System.Diagnostics;
namespace Babushka.scripts.CSharp.Common.Fight;
public class FighterTurn
{
private class Node
{
public required Node next;
public required FightWorld.Fighter fighter;
}
private Node? _currentNode;
public FightWorld.Fighter Current => _currentNode?.fighter ?? throw new InvalidOperationException("No current fighter");
public void Next()
{
Debug.Assert(_currentNode != null, "currentNode!=null");
_currentNode = _currentNode.next;
}
public FightWorld.Fighter PeekNext()
{
Debug.Assert(_currentNode != null, "currentNode!=null");
return _currentNode.next.fighter;
}
public void AddAsLast(FightWorld.Fighter value)
{
// if first node
if (_currentNode == null)
{
_currentNode = new Node { fighter = value, next = null! };
_currentNode.next = _currentNode;
return;
}
var newNode = new Node { fighter = value, next = _currentNode };
var node = _currentNode;
while (node.next != _currentNode)
{
node = node.next;
}
node.next = newNode;
}
public void AddAsNext(FightWorld.Fighter value)
{
// if first node
if (_currentNode == null)
{
AddAsLast(value);
return;
}
var newNode = new Node { fighter = value, next = _currentNode.next };
_currentNode.next = newNode;
}
public bool Remove(FightWorld.Fighter value)
{
if (_currentNode == null) return false;
// if only one node
if (_currentNode.next == _currentNode)
{
if (_currentNode.fighter == value)
{
_currentNode = null;
return true;
}
return false;
}
var node = _currentNode;
do
{
// next is the fighter to remove
if (node.next.fighter == value)
{
// if removing current, keep current
// it will be implicitly deleted by loss of reference on the next Next() call
node.next = node.next.next;
return true;
}
node = node.next;
} while (node != _currentNode);
return false;
}
}
+1 -1
View File
@@ -38,7 +38,7 @@ public partial class FighterVisual : Node2D
/// </summary>
private void UpdateMirrorState()
{
_visualParent.Scale = new Vector2(_boundFighter.isEnemy ? -1 : 1, 1);
_visualParent.Scale = new Vector2(_boundFighter.IsInFormation(HappeningData.enemyFighterFormation) ? -1 : 1, 1);
}
public void UpdateHealthBar()
@@ -8,7 +8,7 @@ public partial class ActionSelectUiSetup : CanvasLayer
// shortcuts
private FightWorld.FightHappeningData HappeningData =>
FightWorld.Instance.fightHappeningData ?? throw new NoFightHappeningException();
private FightWorld.Fighter CurrentFighter => HappeningData.fighterStack.Current;
private FightWorld.Fighter CurrentFighter => HappeningData.fighterTurn.Current;
// references
[Export] private Button _attackActionButton = null!;