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 && x.HasDoors(DoorDirections.North | DoorDirections.South)); private IEnumerable OffshootRooms => Rooms.Where(x => x.Type is RoomType.Regular && x.HasDoors(DoorDirections.East | DoorDirections.West)); private IEnumerable BossRooms => Rooms.Where(x => x.Type is RoomType.Boss); 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); var randomRoomsList = RegularRooms.Shuffle(DungeonLength).ToList(); var randomOffshootRoomsList = OffshootRooms.Shuffle(MaxBranchLength).ToList(); var currentPos = spawnedBeginRoom.RandomBottomExit(); _connections.Add(new RoomConnection(origin, currentPos + new Vector2I(0, 1))); Vector2I nextPos; var offshoots = new List() { RoomType.Treasure, RoomType.Secret, RoomType.Shop, RoomType.Key, RoomType.Regular }; var shuffledOffshoots = offshoots.Shuffle().ToList(); int currentOffshoot = 0; for (int i = 0; i < DungeonLength; i++) { GD.Print($"Dungeon room {i}"); var spawnedRoom = TrySpawnRoom(randomRoomsList, currentPos); if (spawnedRoom is null) { continue; } // // nextPos = currentPos + new Vector2I(0, 1); // // var roomToSpawn = randomRoomsList[randRoomIndex % randomRoomsList.Count]; // // var spawnedRoom = TrySpawnRoom(roomToSpawn, nextPos); // if (spawnedRoom is null) // { // // Failed, try another room // DungeonLength += 1; // randRoomIndex++; // continue; // } // // // 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); // // GD.Print($"Spawning room at {nextPos}"); // if (!SpawnRoom(roomToSpawn, nextPos, out var spawnedRoom)) // { // GD.PrintErr($"Could not spawn room {roomToSpawn} at {nextPos}"); // //DungeonLength += 1; // continue; // } nextPos = spawnedRoom.RandomBottomExit(); GD.Print($"Next pos is now: {nextPos}"); // nextPos is now the end of the room at the current exit _connections.Add(new RoomConnection(nextPos, nextPos + new Vector2I(0, 1))); currentPos = nextPos; // Spawn offshoot here var offshootTypeToSpawn = shuffledOffshoots[currentOffshoot % shuffledOffshoots.Count()]; if (offshootTypeToSpawn is RoomType.Regular) { currentOffshoot++; continue; } int roomsInOffshot = GD.RandRange(0, MaxBranchLength); var leftPosition = spawnedRoom.GridPosition; // Roll whether to go left or right, if direction is full go the other, if both are full do not spawn for (int j = 0; j < roomsInOffshot; j++) { var shuffledOffshootRoomsList = randomOffshootRoomsList.Shuffle().ToList(); var directions = new List() { Vector2I.Left, Vector2I.Right }.Shuffle(); foreach (var shuffledOffshoot in shuffledOffshootRoomsList) { var offshootCoord = leftPosition - new Vector2I(shuffledOffshoot.Size.X, 0); GD.Print("Spawning side room"); var spawned = SpawnRoom(shuffledOffshoot, offshootCoord, out var spawnedOffshoot); if (!spawned) { GD.Print($"Could not place room {shuffledOffshoot} at {offshootCoord}"); // Try next in list continue; } _connections.Add(new RoomConnection(leftPosition, offshootCoord + new Vector2I(shuffledOffshoot.Size.X - 1, 0))); leftPosition = offshootCoord; // Stop because we spawned the room we needed to currentOffshoot++; break; } } // Offshoot over // Spawn final room var finalRoomToSpawn = Rooms.Where(x => x.Type == offshootTypeToSpawn).PickRandom(); var finalRoomCoord = leftPosition - new Vector2I(finalRoomToSpawn.Size.X, 0); SpawnRoom(finalRoomToSpawn, finalRoomCoord, out var spawnedFinalRoom); _connections.Add(new RoomConnection(leftPosition, finalRoomCoord + new Vector2I(finalRoomToSpawn.Size.X - 1, 0))); leftPosition = finalRoomCoord; // Done with last offshoot room } nextPos = currentPos + new Vector2I(0, 1); GD.Print($"Final nextpos is {nextPos}"); var bossRoom = BossRooms.PickRandom(); if (SpawnRoom(bossRoom, nextPos, out var spawnedBossRoom)) { 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 {bossRoom} at {nextPos}"); } 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 RogueliteRoom TrySpawnRoom(List roomsList, Vector2I originPos) { var nextPos = originPos + new Vector2I(0, 1); var rooms = roomsList.Shuffle().ToList(); for (int i = 0; i < rooms.Count(); i++) { var roomToSpawn = rooms[i % rooms.Count()]; var spawnedRoom = TrySpawnRoom(roomToSpawn, nextPos); if (spawnedRoom is null) { GD.PrintErr($"Could not spawn room {roomToSpawn} at {nextPos}"); continue; } return spawnedRoom; } GD.PrintErr($"Could not spawn room at all at {nextPos}"); return null; } private RogueliteRoom TrySpawnRoom(RogueliteRoomResource roomToSpawn, Vector2I nextPos) { var offsets = roomToSpawn.GetTopRoomOffsets(nextPos).Shuffle(); foreach (var offset in offsets) { if (!SpawnRoom(roomToSpawn, offset, out var spawnedRoom)) { GD.PrintErr($"Could not spawn room {roomToSpawn} at {nextPos}"); //DungeonLength += 1; } else { return spawnedRoom; } } 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, out RogueliteRoom spawnedRoom) { if (!CanPlaceRoom(gridPos, room.Size)) { spawnedRoom = null; GD.Print($"{gridPos} size {room.Size} is occupied"); 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.ToString().Replace(" ", "_"); SpawnedRooms.Add(spawnedScene); GD.Print($"Spawned room: {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; } } } }