using System; using System.Collections.Generic; using System.Linq; using Cirno.Scripts.Enums; using Cirno.Scripts.Resources.Roguelite; using Cirno.Scripts.Utils; using Godot; using Godot.Collections; namespace Cirno.Scripts.Controllers; public partial class RogueliteRoomManager : Node2D { [Export] public Array Rooms { get; set; } [Export] public Node2D SceneContainer { get; set; } //private Godot.Collections.Dictionary _grid = new(); private Godot.Collections.Dictionary _roomGrid = new(); public Godot.Collections.Dictionary RoomGrid => _roomGrid; public List SpawnedRooms { get; private set; } = []; [Export] public Vector2I SpawnOrigin { get; private set; } = Vector2I.Zero; [Export] public int DungeonLength { get; set; } = 10; [Export] public int MaxBranches { get; set; } = 3; [Export] public int MaxBranchLength { get; set; } = 3; [Export] public int DungeonWidth = 10; [Export] public int DungeonHeight = 10; [Export] public int MaxRooms = 12; [Export] public ulong Seed = 0; [Export] public Vector2I TileSize { get; set; } = new Vector2I(16, 16); [Export] public Vector2I RoomSizeInTiles { get; set; } = new Vector2I(20, 10); [Signal] public delegate void MapCreatedEventHandler(); public override void _Ready() { // Spawn first room } public void InitSpawning() { GenerateStraightLineDungeon(); } private void SpawnRoomsGrid() { //var firstRoom = Rooms.FirstOrDefault(); var origin = Vector2.Zero; var tileSize = new Vector2(16, 16); for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; j++) { var roomIndex = GD.RandRange(0, Rooms.Count - 1); var room = Rooms[roomIndex]; //SpawnRoom(room, origin + (room.Size * new Vector2(i, j) * tileSize)); } } //CallDeferred(MethodName.RebakeNavigationDeferred); } private IEnumerable StarterRooms => Rooms.Where(x => x.Type is RoomType.Starter); private IEnumerable RegularRooms => Rooms.Where(x => x.Type is RoomType.Regular); private IEnumerable BossRooms => Rooms.Where(x => x.Type is RoomType.Boss); private List _mainPath = new(); private List _connections = new(); public List Connections => _connections; private void GenerateStraightLineDungeon() { if (Seed > 0) { GD.Seed(Seed); } Vector2I origin = Vector2I.Zero; var starterRoom = Rooms.Where(r => r.Type == RoomType.Starter).PickRandom(); SpawnRoom(starterRoom, origin, out var spawnedBeginRoom); _mainPath.Add(origin); var randomRoomsList = RegularRooms.Shuffle(DungeonLength).ToList(); var currentPos = origin; _connections.Add(new RoomConnection(origin, currentPos + new Vector2I(0, 1))); Vector2I nextPos; for (int i = 0; i < DungeonLength; i++) { nextPos = currentPos + new Vector2I(0, 1); //var roomToSpawn = Rooms.Where(x => x.Type == RoomType.Regular).PickRandom(); var roomToSpawn = randomRoomsList[i]; // First pick a random door on the from room //var doorOffset = Math.Min(0, GD.RandRange(0, currentRoom.Size.X - 1)); // We're already in the new room position, we do not care about previous anymore var offset = 0; // Place it at a random X position if (roomToSpawn.Size.X > 1) { offset -= GD.RandRange(0, roomToSpawn.Size.X - 1); } nextPos += new Vector2I(offset, 0); //var posWithOffset = nextPos + new Vector2I(offset, 0); if (!SpawnRoom(roomToSpawn, nextPos, out var spawnedRoom)) { GD.PrintErr("Could not spawn room"); break; } _mainPath.Add(nextPos); // Place cursor at bottom of room if (roomToSpawn.Size.Y > 1) { nextPos += new Vector2I(0, roomToSpawn.Size.Y - 1); } // Pick a random exit to continue var exit = GD.RandRange(0, roomToSpawn.Size.X - 1); if (exit < 0) exit = 0; nextPos += new Vector2I(exit, 0); // nextPos is now the end of the room at the current exit _connections.Add(new RoomConnection(nextPos, nextPos + new Vector2I(0, 1))); //+ new Vector2I(0, roomToSpawn.Size.Y -1) // Reset X offset //nextPos = new Vector2I(0, nextPos.Y); currentPos = nextPos; } nextPos = currentPos + new Vector2I(0, 1); var bossRoom = BossRooms.PickRandom(); if (SpawnRoom(bossRoom, nextPos, out var spawnedBossRoom)) { _mainPath.Add(nextPos); if (bossRoom.Size.Y > 1) { nextPos += new Vector2I(0, bossRoom.Size.Y - 1); } _connections.Add(new RoomConnection(currentPos, nextPos)); } else { GD.PrintErr("Could not spawn boss room"); } foreach (var room in SpawnedRooms) { room.HandleDoors((doorEdge, pos) => { //var neighborPos = room.GridPosition + pos; return _connections.FirstOrDefault(x => (x.From == doorEdge && x.To == pos) || (x.From == pos && x.To == doorEdge)); //return _roomGrid.ContainsKey(neighborPos); }); } EmitSignalMapCreated(); //CallDeferred(MethodName.OpenStartDoorsDeferred, spawnedBeginRoom); } private Vector2I GetNextPosition(Vector2I current, Vector2I target) { var possibleDirs = new List(); if (target.X > current.X) possibleDirs.Add(Vector2I.Right); else if (target.X < current.X) possibleDirs.Add(Vector2I.Left); if (target.Y > current.Y) possibleDirs.Add(Vector2I.Down); else if (target.Y < current.Y) possibleDirs.Add(Vector2I.Up); // Add random noise to allow slightly winding paths possibleDirs.AddRange(new[] { Vector2I.Right, Vector2I.Left, Vector2I.Up, Vector2I.Down }); possibleDirs = possibleDirs.Distinct().OrderBy(_ => GD.Randi()).ToList(); foreach (var dir in possibleDirs) { var nextPos = current + dir; if (!_roomGrid.ContainsKey(nextPos)) return nextPos; } return current; // No valid next position found } private void RebakeNavigationDeferred() { GameManager.Instance.RebakeNavigation(); } private bool SpawnRoom(RogueliteRoomResource room, Vector2I gridPos, out RogueliteRoom spawnedRoom) { if (!CanPlaceRoom(gridPos, room.Size)) { spawnedRoom = null; return false; } var position = gridPos * TileSize * RoomSizeInTiles; var roomScene = GD.Load(room.ScenePath); var spawnedScene = this.CreateChild(roomScene, position); spawnedScene.GridPosition = gridPos; spawnedScene.Name = room.RoomName; SpawnedRooms.Add(spawnedScene); // for reference //SpawnRoom(room, origin + (room.Size * new Vector2(i, j) * tileSize)); spawnedScene.Spawn(); MarkRoom(gridPos, room.Size, spawnedScene); spawnedRoom = spawnedScene; return true; } // private bool CheckConnection(Vector2I pos) // { // return _grid.ContainsKey(pos); // <- this is your dungeon grid dictionary // } // private bool CanPlaceRoom(Vector2I origin, Vector2I size) { for (int x = 0; x < size.X; x++) { for (int y = 0; y < size.Y; y++) { var pos = origin + new Vector2I(x, y); if (_roomGrid.ContainsKey(pos)) return false; } } return true; } private void MarkRoom(Vector2I origin, Vector2I size, RogueliteRoom room) { for (int x = 0; x < size.X; x++) { for (int y = 0; y < size.Y; y++) { var pos = origin + new Vector2I(x, y); _roomGrid[pos] = room; } } } }