Bootstrap fight system

- Fight World data structure
- Generating basic fight world
- Opening correct fight room
- Block paths in fight rooms
- Transition between rooms
This commit is contained in:
jonathan
2025-09-12 13:20:27 +02:00
parent 759933c1cd
commit fd0e631b1f
34 changed files with 2456 additions and 11 deletions
@@ -0,0 +1,21 @@
using Godot;
namespace Babushka.scripts.CSharp.Common.Fight;
public partial class FightSceneSetup : Node
{
[Export] private Label debugLabel;
public override void _Ready()
{
var room = FightWorld.Instance.currentRoom!;
debugLabel.Text = $"Room Debug:\n{room.paths.Count} paths out of this room\n{room.enemyGroups.Count} enemy groups:\n";
foreach (var enemyGroup in room.enemyGroups)
{
debugLabel.Text += $" {enemyGroup.enemies.Count} enemies:\n";
foreach (var enemy in enemyGroup.enemies)
{
debugLabel.Text += $" {enemy.type}\n";
}
}
}
}
@@ -0,0 +1 @@
uid://dbu8afaiohpdh
@@ -0,0 +1,41 @@
using System;
using System.Diagnostics;
using Babushka.scripts.CSharp.Common.SceneManagement;
using Godot;
namespace Babushka.scripts.CSharp.Common.Fight;
public partial class FightSceneSwitcher : Node
{
[Export] private Node sceneRoot;
[Export] private string fightRoomScenePath;
[Export] private string fightingGroupScene;
private void LoadNext()
{
var nextRoom = FightWorld.Instance.currentRoom;
Debug.Assert(nextRoom != null, "nextRoom!=null");
var nextEnemyGroup = FightWorld.Instance.inFightWith;
SceneTransitionThreaded.Instance.ChangeSceneToFile(nextEnemyGroup != null
? fightingGroupScene
: fightRoomScenePath);
UnloadAfterDelay();
}
private async void UnloadAfterDelay()
{
await ToSignal(GetTree().CreateTimer(1.0f), "timeout"); // 1.0f seconds
sceneRoot.QueueFree();
}
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");
FightWorld.Instance.currentRoom = nextRoom;
LoadNext();
}
}
@@ -0,0 +1 @@
uid://cql8mt5jsmcdl
+159
View File
@@ -0,0 +1,159 @@
using System.Collections.Generic;
using Godot;
namespace Babushka.scripts.CSharp.Common.Fight;
public partial class FightWorld : Node
{
public class World
{
public required List<Room> rooms;
}
public class Room
{
public required Dictionary<int, Room> paths;
public required List<EnemyGroup> enemyGroups;
}
public class EnemyGroup
{
public required List<Enemy> enemies;
}
public class Enemy
{
public enum Type
{
Blob,
BigBlob,
Mavka,
YourMom
}
public required Type type;
public required int? health = null; // null => initialize to full health on spawn
}
#region AutoLoad ( Contains _EnterTree() )
public static FightWorld Instance { get; private set; } = null!;
public override void _EnterTree()
{
Instance = this;
MyEnterTree();
}
#endregion
public World? world = null;
public Room? currentRoom = null;
public EnemyGroup? inFightWith = null;
public void MyEnterTree()
{
Generate();
currentRoom = world!.rooms[0];
}
public void Generate()
{
world = new Generator().GenerateWorld();
}
private class Generator
{
public World GenerateWorld()
{
var world = new World
{
rooms = GenerateRooms()
};
return world;
}
private List<Room> GenerateRooms()
{
var rooms = new List<Room>();
var roomCount = 2;
for (var i = 0; i < roomCount; i++)
{
rooms.Add(GenerateDisconnectedRoom());
}
// Connect rooms linearly
for (var i = 0; i < rooms.Count - 1; i++)
{
rooms[i].paths[0] = rooms[i + 1];
rooms[i + 1].paths[1] = rooms[i];
}
return rooms;
}
private Room GenerateDisconnectedRoom()
{
var room = new Room
{
paths = new Dictionary<int, Room>(),
enemyGroups = GenerateEnemyGroups()
};
return room;
}
private List<EnemyGroup> GenerateEnemyGroups()
{
var enemyGroups = new List<EnemyGroup>();
var enemyGroupCount = GD.RandRange(1, 3);
for (var i = 0; i < enemyGroupCount; i++)
{
enemyGroups.Add(GenerateSingleEnemyGroup());
}
return enemyGroups;
}
private EnemyGroup GenerateSingleEnemyGroup()
{
var enemyGroup = new EnemyGroup
{
enemies = []
};
var enemyCount = GD.RandRange(1, 3);
for (var i = 0; i < enemyCount; i++)
{
enemyGroup.enemies.Add(GenerateSingleEnemy());
}
return enemyGroup;
}
private Enemy GenerateSingleEnemy()
{
var typeRoll = GD.RandRange(0, 99);
var type = typeRoll switch
{
< 50 => Enemy.Type.Blob,
< 75 => Enemy.Type.BigBlob,
< 90 => Enemy.Type.Mavka,
_ => Enemy.Type.YourMom
};
var enemy = new Enemy
{
type = type,
health = null
};
return enemy;
}
}
}
@@ -0,0 +1 @@
uid://dqe1i2qmpttwf
@@ -0,0 +1,19 @@
using Godot;
using System;
using Babushka.scripts.CSharp.Common.Fight;
public partial class NextRoomTrigger : Area2D
{
[Export] private int pathIndex;
public override void _EnterTree()
{
BodyEntered += _OnBodyEnter;
}
private void _OnBodyEnter(Node2D other)
{
var fss = GetNode<FightSceneSwitcher>("%FightSceneSwitcher");
fss.SwitchRoom(pathIndex);
}
}
@@ -0,0 +1 @@
uid://bryibv73x5iwr
+49
View File
@@ -0,0 +1,49 @@
using System.Diagnostics;
using Godot;
namespace Babushka.scripts.CSharp.Common.Fight;
public partial class PathSetup : Node
{
[Export] private int pathId;
[ExportCategory("Variants")] [Export] private CanvasItem closedVariant;
[Export] private CanvasItem nextRoomVariant;
public override void _Ready()
{
SetupPathVariant();
}
private void SetupPathVariant()
{
Debug.Assert(FightWorld.Instance.currentRoom != null);
if (FightWorld.Instance.currentRoom.paths.TryGetValue(pathId, out var nextRoom))
{
ShowOnlyVariant(nextRoomVariant);
}
else
{
ShowOnlyVariant(closedVariant);
}
}
private void ShowOnlyVariant(CanvasItem variantToShow)
{
HideVariant(closedVariant);
HideVariant(nextRoomVariant);
ShowVariant(variantToShow);
}
private void ShowVariant(CanvasItem variant)
{
variant.Visible = true;
variant.ProcessMode = ProcessModeEnum.Always;
}
private void HideVariant(CanvasItem variant)
{
variant.Visible = false;
variant.ProcessMode = ProcessModeEnum.Disabled;
}
}
@@ -0,0 +1 @@
uid://dpkx2gbg7b5xh