2025-04-11 18:39:39 +02:00
|
|
|
|
using System;
|
2025-04-16 18:18:52 +02:00
|
|
|
|
using System.Collections.Generic;
|
2025-04-11 18:39:39 +02:00
|
|
|
|
using System.Linq;
|
2025-04-16 18:18:52 +02:00
|
|
|
|
using Cirno.Scripts.Components.FSM.Enemy;
|
2025-04-22 18:21:53 +02:00
|
|
|
|
using Cirno.Scripts.Enums;
|
2025-04-24 16:40:51 +02:00
|
|
|
|
using Cirno.Scripts.Interactables;
|
2025-04-11 15:53:59 +02:00
|
|
|
|
using Cirno.Scripts.Resources;
|
|
|
|
|
|
using Cirno.Scripts.Resources.Roguelite;
|
2025-04-10 19:04:06 +02:00
|
|
|
|
using Godot;
|
2025-04-11 15:53:59 +02:00
|
|
|
|
using Godot.Collections;
|
2025-04-10 19:04:06 +02:00
|
|
|
|
|
|
|
|
|
|
namespace Cirno.Scripts.Controllers;
|
|
|
|
|
|
|
2025-04-23 13:38:54 +02:00
|
|
|
|
[Tool]
|
2025-04-10 19:04:06 +02:00
|
|
|
|
public partial class RogueliteRoom : Node2D
|
|
|
|
|
|
{
|
|
|
|
|
|
[Export] public RogueliteRoomResource RoomResource { get; set; }
|
2025-04-24 16:40:51 +02:00
|
|
|
|
|
|
|
|
|
|
public RogueliteMapTheme MapTheme { get; set; }
|
2025-04-14 16:50:58 +02:00
|
|
|
|
|
2025-04-11 18:39:39 +02:00
|
|
|
|
public Vector2I GridPosition { get; set; } // Set by dungeon manager
|
2025-04-14 16:50:58 +02:00
|
|
|
|
|
2025-04-21 17:46:26 +02:00
|
|
|
|
public Vector2I BottomLeft => GridPosition + new Vector2I(0, RoomResource.Size.Y - 1);
|
|
|
|
|
|
|
2025-04-23 13:38:54 +02:00
|
|
|
|
private Vector2 BaseRoomSize => new Vector2(320, 160);
|
2025-04-23 14:27:30 +02:00
|
|
|
|
|
2025-04-22 13:50:26 +02:00
|
|
|
|
public Vector2I RandomBottomExit()
|
|
|
|
|
|
{
|
|
|
|
|
|
return BottomLeft + new Vector2I(GD.RandRange(0, RoomResource.Size.X - 1), 0);
|
|
|
|
|
|
}
|
2025-04-23 14:27:30 +02:00
|
|
|
|
|
2025-04-22 18:21:53 +02:00
|
|
|
|
public Vector2I RandomExit(Direction direction)
|
|
|
|
|
|
{
|
|
|
|
|
|
return direction switch
|
|
|
|
|
|
{
|
|
|
|
|
|
Direction.Up => GridPosition + new Vector2I(GD.RandRange(0, RoomResource.Size.X - 1), 0),
|
|
|
|
|
|
Direction.Down => BottomLeft + new Vector2I(GD.RandRange(0, RoomResource.Size.X - 1), 0),
|
|
|
|
|
|
Direction.Left => GridPosition + new Vector2I(0, GD.RandRange(0, RoomResource.Size.Y - 1)),
|
|
|
|
|
|
Direction.Right => GridPosition + new Vector2I(RoomResource.Size.X - 1, 0) +
|
|
|
|
|
|
new Vector2I(0, GD.RandRange(0, RoomResource.Size.Y - 1)),
|
|
|
|
|
|
_ => throw new ArgumentOutOfRangeException(nameof(direction), direction, null)
|
|
|
|
|
|
};
|
2025-04-23 14:27:30 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-16 18:18:52 +02:00
|
|
|
|
private static readonly Godot.Collections.Dictionary<string, Vector2I> DirectionMap = new()
|
2025-04-11 18:39:39 +02:00
|
|
|
|
{
|
|
|
|
|
|
{ "North", new Vector2I(0, -1) },
|
|
|
|
|
|
{ "South", new Vector2I(0, 1) },
|
2025-04-14 16:50:58 +02:00
|
|
|
|
{ "East", new Vector2I(1, 0) },
|
|
|
|
|
|
{ "West", new Vector2I(-1, 0) },
|
2025-04-11 18:39:39 +02:00
|
|
|
|
};
|
2025-04-11 15:53:59 +02:00
|
|
|
|
|
2025-04-16 18:18:52 +02:00
|
|
|
|
private List<Door> _doors = [];
|
|
|
|
|
|
private List<RoomConnection> _connections = [];
|
|
|
|
|
|
private List<EnemyFSMProxy> _enemies = [];
|
|
|
|
|
|
|
2025-04-14 16:50:58 +02:00
|
|
|
|
private Array<EnemyResource> SpawnableEnemies => RoomResource.SpawnableEnemies;
|
2025-04-11 15:53:59 +02:00
|
|
|
|
|
2025-04-14 16:50:58 +02:00
|
|
|
|
public RogueliteRoom Spawn()
|
2025-04-11 18:39:39 +02:00
|
|
|
|
{
|
|
|
|
|
|
SpawnEnemies();
|
2025-04-14 16:50:58 +02:00
|
|
|
|
//HandleDoors(connectionChecker);
|
2025-04-16 15:11:29 +02:00
|
|
|
|
AddDebugLabel();
|
2025-04-11 18:39:39 +02:00
|
|
|
|
return this;
|
|
|
|
|
|
}
|
2025-04-14 16:50:58 +02:00
|
|
|
|
|
2025-04-16 15:11:29 +02:00
|
|
|
|
private void AddDebugLabel()
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int i = 0; i < RoomResource.Size.X; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int j = 0; j < RoomResource.Size.Y; j++)
|
|
|
|
|
|
{
|
|
|
|
|
|
var label = new Label();
|
|
|
|
|
|
label.Text = $"{GridPosition + new Vector2I(i, j)}";
|
|
|
|
|
|
label.ZIndex = 11;
|
|
|
|
|
|
label.Position = (new Vector2(i, j) * new Vector2(320, 160)) + new Vector2(160, 80);
|
|
|
|
|
|
this.AddChild(label);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-23 13:38:54 +02:00
|
|
|
|
private List<DoorMarker> GenerateDoors()
|
2025-04-11 18:39:39 +02:00
|
|
|
|
{
|
2025-04-23 13:38:54 +02:00
|
|
|
|
List<DoorMarker> doors = [];
|
2025-04-23 14:27:30 +02:00
|
|
|
|
|
2025-04-23 13:38:54 +02:00
|
|
|
|
var doorsContainer = new Node2D();
|
|
|
|
|
|
this.AddChild(doorsContainer);
|
|
|
|
|
|
doorsContainer.Name = "Doors";
|
2025-04-23 14:27:30 +02:00
|
|
|
|
|
2025-04-23 13:38:54 +02:00
|
|
|
|
// North
|
|
|
|
|
|
for (int i = 0; i < RoomResource.Size.X; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (RoomResource.HasDoors(DoorDirections.North))
|
|
|
|
|
|
{
|
|
|
|
|
|
doors.Add(MakeDoorMarker(DoorDirections.North, i));
|
|
|
|
|
|
}
|
2025-04-23 14:27:30 +02:00
|
|
|
|
|
2025-04-23 13:38:54 +02:00
|
|
|
|
if (RoomResource.HasDoors(DoorDirections.South))
|
|
|
|
|
|
{
|
|
|
|
|
|
doors.Add(MakeDoorMarker(DoorDirections.South, i));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-04-11 18:39:39 +02:00
|
|
|
|
|
2025-04-23 13:38:54 +02:00
|
|
|
|
for (int j = 0; j < RoomResource.Size.Y; j++)
|
2025-04-11 18:39:39 +02:00
|
|
|
|
{
|
2025-04-23 13:38:54 +02:00
|
|
|
|
doors.Add(MakeDoorMarker(DoorDirections.East, j));
|
|
|
|
|
|
doors.Add(MakeDoorMarker(DoorDirections.West, j));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var door in doors)
|
|
|
|
|
|
{
|
|
|
|
|
|
doorsContainer.AddChild(door);
|
|
|
|
|
|
}
|
2025-04-14 16:50:58 +02:00
|
|
|
|
|
2025-04-23 13:38:54 +02:00
|
|
|
|
return doors;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private DoorMarker MakeDoorMarker(DoorDirections direction, int wallIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
var doorMarker = new DoorMarker();
|
|
|
|
|
|
doorMarker.Direction = direction;
|
|
|
|
|
|
doorMarker.WallIndex = wallIndex;
|
2025-04-23 14:27:30 +02:00
|
|
|
|
|
2025-04-23 13:38:54 +02:00
|
|
|
|
doorMarker.Position = GetDoorPosition(direction, wallIndex);
|
|
|
|
|
|
|
|
|
|
|
|
return doorMarker;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void HandleDoors(Func<Vector2I, Vector2I, RoomConnection> connectionChecker)
|
|
|
|
|
|
{
|
|
|
|
|
|
var doors = GenerateDoors();
|
2025-04-23 14:27:30 +02:00
|
|
|
|
|
2025-04-23 13:38:54 +02:00
|
|
|
|
foreach (DoorMarker marker in doors)
|
|
|
|
|
|
{
|
2025-04-11 18:39:39 +02:00
|
|
|
|
var baseDir = marker.GetWorldDirection();
|
|
|
|
|
|
|
|
|
|
|
|
// WallIndex determines the offset *along* the edge of the room
|
|
|
|
|
|
Vector2I offset = marker.Direction switch
|
|
|
|
|
|
{
|
2025-04-23 13:38:54 +02:00
|
|
|
|
DoorDirections.North => new Vector2I(marker.WallIndex, 0),
|
|
|
|
|
|
DoorDirections.South => new Vector2I(marker.WallIndex, RoomResource.Size.Y - 1),
|
|
|
|
|
|
DoorDirections.East => new Vector2I(RoomResource.Size.X - 1, marker.WallIndex),
|
|
|
|
|
|
DoorDirections.West => new Vector2I(0, marker.WallIndex),
|
2025-04-11 18:39:39 +02:00
|
|
|
|
_ => Vector2I.Zero
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Combine GridPosition + offset to locate where this door aligns
|
|
|
|
|
|
Vector2I doorEdge = GridPosition + offset;
|
|
|
|
|
|
Vector2I neighborPos = doorEdge + baseDir;
|
|
|
|
|
|
|
2025-04-16 15:11:29 +02:00
|
|
|
|
var connection = connectionChecker.Invoke(doorEdge, neighborPos);
|
|
|
|
|
|
|
|
|
|
|
|
var connected = connection is not null;
|
|
|
|
|
|
|
2025-04-23 14:27:30 +02:00
|
|
|
|
|
|
|
|
|
|
var door = this.CreateChildOf<Door>(marker, marker.Direction switch
|
|
|
|
|
|
{
|
2025-04-24 16:40:51 +02:00
|
|
|
|
DoorDirections.North => MapTheme.HorizontalDoorPrefab,
|
|
|
|
|
|
DoorDirections.South => MapTheme.HorizontalDoorPrefab,
|
|
|
|
|
|
DoorDirections.East => MapTheme.VerticalDoorPrefab,
|
|
|
|
|
|
DoorDirections.West => MapTheme.VerticalDoorPrefab,
|
2025-04-23 14:27:30 +02:00
|
|
|
|
_ => throw new ArgumentOutOfRangeException()
|
|
|
|
|
|
}, marker.GlobalPosition);
|
|
|
|
|
|
|
2025-04-24 16:40:51 +02:00
|
|
|
|
door.Close();
|
|
|
|
|
|
|
|
|
|
|
|
// door.State = DoorState.Closed;
|
2025-04-14 16:50:58 +02:00
|
|
|
|
if (connected)
|
|
|
|
|
|
{
|
2025-04-16 18:18:52 +02:00
|
|
|
|
_doors.Add(door);
|
2025-04-17 16:51:20 +02:00
|
|
|
|
|
|
|
|
|
|
if (doorEdge == connection.From)
|
2025-04-17 13:04:56 +02:00
|
|
|
|
{
|
|
|
|
|
|
connection.FromDoor = door;
|
2025-04-24 16:40:51 +02:00
|
|
|
|
|
|
|
|
|
|
// Spawn lock if locked
|
|
|
|
|
|
if (connection.IsLocked)
|
|
|
|
|
|
{
|
|
|
|
|
|
var doorLock = door.CreateChild<RogueliteDoorLock>(MapTheme.DoorLockPrefab);
|
|
|
|
|
|
|
|
|
|
|
|
doorLock.Connection = connection;
|
|
|
|
|
|
|
|
|
|
|
|
doorLock.ZIndex += 2;
|
|
|
|
|
|
|
|
|
|
|
|
//doorLock.Targets.Add(door);
|
|
|
|
|
|
//doorLock.Targets.Add(connection.ToDoor);
|
|
|
|
|
|
doorLock.ActivationType = ActivationType.Open;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-17 13:04:56 +02:00
|
|
|
|
}
|
2025-04-17 16:51:20 +02:00
|
|
|
|
else if (doorEdge == connection.To)
|
2025-04-17 13:04:56 +02:00
|
|
|
|
{
|
|
|
|
|
|
connection.ToDoor = door;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-04-23 14:27:30 +02:00
|
|
|
|
GD.Print(
|
|
|
|
|
|
$"Door {door} connection was full: {connection.From} {connection.FromDoor} to {connection.To} {connection.ToDoor}");
|
2025-04-17 13:04:56 +02:00
|
|
|
|
}
|
2025-04-16 18:18:52 +02:00
|
|
|
|
|
2025-04-23 14:27:30 +02:00
|
|
|
|
_connections.Add(connection);
|
2025-04-24 16:40:51 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-04-14 16:50:58 +02:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2025-04-23 14:27:30 +02:00
|
|
|
|
// var wall = this.CreateChild<Node2D>(WallPrefab, marker.GlobalPosition);
|
|
|
|
|
|
|
2025-04-14 16:50:58 +02:00
|
|
|
|
}
|
2025-04-11 18:39:39 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void SpawnEnemies()
|
2025-04-11 15:53:59 +02:00
|
|
|
|
{
|
2025-04-11 18:39:39 +02:00
|
|
|
|
if (SpawnableEnemies is null || !SpawnableEnemies.Any()) return;
|
2025-04-14 16:50:58 +02:00
|
|
|
|
|
2025-04-11 15:53:59 +02:00
|
|
|
|
var enemySpawners = this.GetNode("EnemySpawners").GetChildren().Cast<Marker2D>();
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var spawner in enemySpawners)
|
|
|
|
|
|
{
|
|
|
|
|
|
var index = GD.RandRange(0, SpawnableEnemies.Count - 1);
|
2025-04-14 16:50:58 +02:00
|
|
|
|
|
2025-04-11 15:53:59 +02:00
|
|
|
|
var e = SpawnableEnemies[index];
|
2025-04-14 16:50:58 +02:00
|
|
|
|
|
2025-04-11 15:53:59 +02:00
|
|
|
|
var enemyScene = GD.Load<PackedScene>(e.PrefabPath);
|
2025-04-16 18:18:52 +02:00
|
|
|
|
var spawnedEnemy = spawner.CreateChild<EnemyFSMProxy>(enemyScene);
|
2025-04-23 14:27:30 +02:00
|
|
|
|
|
2025-04-16 18:18:52 +02:00
|
|
|
|
_enemies.Add(spawnedEnemy);
|
2025-04-23 14:27:30 +02:00
|
|
|
|
|
2025-04-16 18:18:52 +02:00
|
|
|
|
spawnedEnemy.Death += SpawnedEnemyOnDeath;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void SpawnedEnemyOnDeath(EnemyFSMProxy enemy)
|
|
|
|
|
|
{
|
|
|
|
|
|
enemy.Death -= SpawnedEnemyOnDeath;
|
|
|
|
|
|
_enemies.Remove(enemy);
|
|
|
|
|
|
|
|
|
|
|
|
if (_enemies.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
OpenDoors();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void OpenDoors()
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var connection in _connections)
|
|
|
|
|
|
{
|
2025-04-24 16:40:51 +02:00
|
|
|
|
if(!connection.IsLocked)
|
|
|
|
|
|
{
|
|
|
|
|
|
connection.FromDoor?.Open();
|
|
|
|
|
|
}
|
|
|
|
|
|
if(!connection.IsLocked)
|
|
|
|
|
|
{
|
|
|
|
|
|
connection.ToDoor?.Open();
|
|
|
|
|
|
}
|
2025-04-11 15:53:59 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-04-16 18:18:52 +02:00
|
|
|
|
|
|
|
|
|
|
public void CloseDoors()
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var connection in _connections)
|
|
|
|
|
|
{
|
2025-04-24 16:40:51 +02:00
|
|
|
|
if(!connection.IsLocked)
|
|
|
|
|
|
{
|
|
|
|
|
|
connection.FromDoor?.Close();
|
|
|
|
|
|
}
|
|
|
|
|
|
if(!connection.IsLocked)
|
|
|
|
|
|
{
|
|
|
|
|
|
connection.ToDoor?.Close();
|
|
|
|
|
|
}
|
2025-04-16 18:18:52 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private void OnRoomEntered(Area2D area)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (area is not InteractionController player) return;
|
2025-04-23 14:27:30 +02:00
|
|
|
|
|
2025-04-16 18:18:52 +02:00
|
|
|
|
if (_enemies.Count <= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
OpenDoors();
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
CloseDoors();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-04-23 14:27:30 +02:00
|
|
|
|
|
2025-04-16 18:18:52 +02:00
|
|
|
|
private void OnRoomExited(Area2D area)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (area is not InteractionController player) return;
|
|
|
|
|
|
}
|
2025-04-23 14:27:30 +02:00
|
|
|
|
|
2025-04-21 17:46:26 +02:00
|
|
|
|
public override string ToString()
|
|
|
|
|
|
{
|
|
|
|
|
|
return $"{GridPosition} {RoomResource}";
|
|
|
|
|
|
}
|
2025-04-23 13:38:54 +02:00
|
|
|
|
|
|
|
|
|
|
public Vector2 GetDoorPosition(DoorDirections direction, int wallIndex)
|
|
|
|
|
|
{
|
|
|
|
|
|
return direction switch
|
|
|
|
|
|
{
|
|
|
|
|
|
DoorDirections.North => new Vector2((BaseRoomSize.X / 2) + (BaseRoomSize.X * wallIndex), 32),
|
|
|
|
|
|
DoorDirections.South => new Vector2((BaseRoomSize.X / 2) + (BaseRoomSize.X * wallIndex),
|
2025-04-23 14:27:30 +02:00
|
|
|
|
((BaseRoomSize.Y) * RoomResource.Size.Y) + 2),
|
|
|
|
|
|
DoorDirections.East => new Vector2((BaseRoomSize.X * RoomResource.Size.X) - 12,
|
|
|
|
|
|
(BaseRoomSize.Y / 2) + (BaseRoomSize.Y * wallIndex) - 8),
|
2025-04-23 13:38:54 +02:00
|
|
|
|
DoorDirections.West => new Vector2(12, (BaseRoomSize.Y / 2) + (BaseRoomSize.Y * wallIndex) - 8),
|
|
|
|
|
|
_ => Vector2.Zero
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2025-04-23 14:27:30 +02:00
|
|
|
|
|
2025-04-23 13:38:54 +02:00
|
|
|
|
// [ExportToolButton("Arrange Doors")] public Callable ArrangeDoorsButton => Callable.From(ArrangeDoors);
|
2025-04-23 14:27:30 +02:00
|
|
|
|
|
2025-04-23 13:38:54 +02:00
|
|
|
|
public void ArrangeDoors()
|
|
|
|
|
|
{
|
|
|
|
|
|
var doorNode = this.GetNode("Doors");
|
2025-04-23 14:27:30 +02:00
|
|
|
|
|
2025-04-23 13:38:54 +02:00
|
|
|
|
var doors = doorNode.GetChildren();
|
2025-04-23 14:27:30 +02:00
|
|
|
|
|
2025-04-23 13:38:54 +02:00
|
|
|
|
foreach (var node in doors)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (node is DoorMarker doorMarker)
|
|
|
|
|
|
{
|
|
|
|
|
|
GD.Print($"{doorMarker.Name} {doorMarker.Direction} {doorMarker.WallIndex}");
|
|
|
|
|
|
|
|
|
|
|
|
var baseGridSize = new Vector2(320, 160);
|
|
|
|
|
|
|
|
|
|
|
|
Vector2 doorPosition = doorMarker.Direction switch
|
|
|
|
|
|
{
|
2025-04-23 14:27:30 +02:00
|
|
|
|
DoorDirections.North => new Vector2((baseGridSize.X / 2) + (baseGridSize.X * doorMarker.WallIndex),
|
|
|
|
|
|
32),
|
2025-04-23 13:38:54 +02:00
|
|
|
|
DoorDirections.South => new Vector2((baseGridSize.X / 2) + (baseGridSize.X * doorMarker.WallIndex),
|
2025-04-23 14:27:30 +02:00
|
|
|
|
((baseGridSize.Y) * RoomResource.Size.Y) + 2),
|
2025-04-23 13:38:54 +02:00
|
|
|
|
DoorDirections.East => doorMarker.Position,
|
|
|
|
|
|
DoorDirections.West => doorMarker.Position,
|
|
|
|
|
|
_ => doorMarker.Position
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
doorMarker.Position = doorPosition;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
GD.Print($"Node was something else: {node}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-04-10 19:04:06 +02:00
|
|
|
|
}
|