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 int Seed = -1; [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() { Vector2I origin = Vector2I.Zero; var starterRoom = Rooms.Where(r => r.Type == RoomType.Starter).PickRandom(); SpawnRoom(starterRoom, origin); _mainPath.Add(origin); var randomRoomsList = RegularRooms.Shuffle(DungeonLength).ToList(); var currentPos = origin; 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]; var offset = 0; // Place it at a random X position if (roomToSpawn.Size.X > 1) { offset -= GD.RandRange(0, roomToSpawn.Size.X - 1); } var posWithOffset = new Vector2I(offset, nextPos.Y); if (!SpawnRoom(roomToSpawn, posWithOffset)) { GD.PrintErr("Could not spawn room"); break; }; _mainPath.Add(posWithOffset); if (roomToSpawn.Size.Y > 1) { nextPos += new Vector2I(0, roomToSpawn.Size.Y - 1); } // Pick a random exit to continue var exit = Math.Min(0, GD.RandRange(0, roomToSpawn.Size.X - 1)); nextPos += new Vector2I(0, exit); _connections.Add(new RoomConnection(currentPos, nextPos)); //+ 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)) { _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 roomEntry in _roomGrid) { var room = roomEntry.Value; room.HandleDoors(pos => { var neighborPos = room.GridPosition + pos; return _roomGrid.ContainsKey(neighborPos); }); } EmitSignalMapCreated(); } private void GenerateDungeon() { Vector2I origin = Vector2I.Zero; var starterRoom = Rooms.Where(r => r.Type == RoomType.Starter).PickRandom(); SpawnRoom(starterRoom, origin); _mainPath.Add(origin); var currentPos = origin; // 🔐 Reserve boss room position var bossRoom = BossRooms.FirstOrDefault(); Vector2I? bossTargetPos = FindValidBossRoomLocation(currentPos); if (bossRoom == null || bossTargetPos == null) { GD.PrintErr("Failed to place boss room!"); return; } // Generate path to boss room while (currentPos != bossTargetPos.Value) { var nextPos = GetNextPosition(currentPos, bossTargetPos.Value); if (!SpawnRoom(Rooms.Where(x => x.Type == RoomType.Regular).PickRandom(), nextPos)) break; _mainPath.Add(nextPos); _connections.Add(new RoomConnection(currentPos, nextPos)); currentPos = nextPos; } // Place the boss room if (SpawnRoom(bossRoom, bossTargetPos.Value)) { _mainPath.Add(bossTargetPos.Value); _connections.Add(new RoomConnection(currentPos, bossTargetPos.Value)); } } // private void SpawnRoomsBinaryTree() // { // var directions = new List // { // new Vector2I(0, -1), // North // new Vector2I(0, 1), // South // new Vector2I(1, 0), // East // new Vector2I(-1, 0), // West // }; // // //var origin = Vector2I.Zero; // var tileSize = new Vector2(16, 16); // var gridRoomSizeInTiles = new Vector2(20, 10); // // var starterRooms = StarterRooms.ToList(); // var regularRooms = RegularRooms.ToList(); // var bossRooms = BossRooms.ToList(); // var starterRoom = starterRooms[GD.RandRange(0, starterRooms.Count - 1)]; // // var spawnedStartRoom = SpawnRoom(starterRoom, SpawnOrigin); // // Vector2I currentPos = SpawnOrigin; // // //MarkRoom(SpawnOrigin, starterRoom.Size, starterRoom); // // for (int i = 0; i < DungeonLength - 2; i++) // { // var nextPos = GetNextPosition(currentPos); // if (nextPos == currentPos) break; // // var room = regularRooms[GD.RandRange(0, regularRooms.Count - 1)]; // if (!SpawnRoom(room, nextPos)) break; // Skip on overlap // // _mainPath.Add(nextPos); // _connections.Add(new RoomConnection(currentPos, nextPos)); // currentPos = nextPos; // } // // // Place Boss Room // // var bossRoom = bossRooms[GD.RandRange(0, bossRooms.Count - 1)]; // var bossPos = GetNextPosition(currentPos); // if (!SpawnRoom(bossRoom, bossPos)) break; // Skip on overlap // SpawnRoom(bossRoom, bossPos); // _mainPath.Add(bossPos); // _connections.Add(new RoomConnection(currentPos, bossPos)); // // // Place Shop and Secret Rooms // //PlaceSpecialRoom(RoomType.Shop); // //PlaceSpecialRoom(RoomType.Secret); // // // Optionally, add branches // for (int i = 0; i < MaxBranches; i++) // { // var branchStart = _mainPath[GD.RandRange(0, _mainPath.Count -1)]; // var branchLength = GD.RandRange(1, MaxBranchLength); // //_random.Next(1, MaxBranchLength + 1); // var branchPos = branchStart; // // for (int j = 0; j < branchLength; j++) // { // var nextPos = GetNextPosition(branchPos); // if (_roomGrid.ContainsKey(nextPos)) break; // // var room = regularRooms[GD.RandRange(0,regularRooms.Count -1)]; // // SpawnRoom(room, nextPos); // _connections.Add(new RoomConnection(branchPos, nextPos)); // branchPos = nextPos; // } // } // // // Spawn doors and walls // foreach (var roomEntry in _roomGrid) // { // var room = roomEntry.Value; // room.HandleDoors(pos => // { // var neighborPos = room.GridPosition + pos; // return _roomGrid.ContainsKey(neighborPos); // }); // } // } private Vector2I? FindValidBossRoomLocation(Vector2I from, int maxSteps = 10) { var directions = new List { Vector2I.Right, Vector2I.Left, Vector2I.Up, Vector2I.Down }; for (int step = 0; step < maxSteps; step++) { directions = directions.OrderBy(_ => GD.Randi()).ToList(); foreach (var dir in directions) { var pos = from + dir; var bossRoom = Rooms.FirstOrDefault(r => r.Type == RoomType.Boss); if (bossRoom != null && CanPlaceRoom(pos, bossRoom.Size)) return pos; from += dir; } } return null; } 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) { if (!CanPlaceRoom(gridPos, room.Size)) return false; var position = gridPos * TileSize * RoomSizeInTiles; var roomScene = GD.Load(room.ScenePath); var spawnedScene = this.CreateChild(roomScene, position); spawnedScene.GridPosition = gridPos; SpawnedRooms.Add(spawnedScene); // for reference //SpawnRoom(room, origin + (room.Size * new Vector2(i, j) * tileSize)); spawnedScene.Spawn(); MarkRoom(gridPos, room.Size, 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; } } } } public class RoomConnection { public Vector2I From; public Vector2I To; public bool IsLocked; public bool IsSecret; public RoomConnection(Vector2I from, Vector2I to, bool isLocked = false, bool isSecret = false) { From = from; To = to; IsLocked = isLocked; IsSecret = isSecret; } }