Ver Fonte

Dungeon Generation Basics, the branches are alright, start and endpoint defined clearly

Jan há 2 semanas atrás
pai
commit
8078e47588

+ 4 - 1
src/main/java/me/lethunderhawk/dungeon/DungeonModule.java

@@ -3,6 +3,7 @@ package me.lethunderhawk.dungeon;
 import me.lethunderhawk.bazaarflux.service.Services;
 import me.lethunderhawk.bazaarflux.util.interfaces.BazaarFluxModule;
 import me.lethunderhawk.dungeon.command.DungeonCommand;
+import me.lethunderhawk.dungeon.manager.DungeonManager;
 import me.lethunderhawk.main.Main;
 
 public class DungeonModule extends BazaarFluxModule {
@@ -16,6 +17,7 @@ public class DungeonModule extends BazaarFluxModule {
     public void onEnable() {
 
         Main mainPlugin = Services.get(Main.class);
+        Services.register(DungeonManager.class, new DungeonManager(mainPlugin));
 
         mainPlugin.getCommand("dungeon").setExecutor(new DungeonCommand());
         mainPlugin.getCommand("dungeon").setTabCompleter(new DungeonCommand());
@@ -23,6 +25,7 @@ public class DungeonModule extends BazaarFluxModule {
 
     @Override
     public void onDisable() {
-
+        Services.get(DungeonManager.class).deleteAllDungeons();
+        Services.unregister(DungeonManager.class);
     }
 }

+ 57 - 13
src/main/java/me/lethunderhawk/dungeon/command/DungeonCommand.java

@@ -5,30 +5,74 @@ import me.lethunderhawk.bazaarflux.util.command.CommandNode;
 import me.lethunderhawk.bazaarflux.util.command.CustomCommand;
 import me.lethunderhawk.dungeon.DungeonModule;
 import me.lethunderhawk.dungeon.generation.DungeonWorld;
+import me.lethunderhawk.dungeon.manager.DungeonManager;
+import me.lethunderhawk.main.Main;
+import org.bukkit.Bukkit;
 import org.bukkit.command.CommandSender;
 import org.bukkit.entity.Player;
 
 public class DungeonCommand extends CustomCommand {
+
     public DungeonCommand() {
-        super(new CommandNode("dungeon", "Main Dungeon command", null), Services.get(DungeonModule.class));
+        super(
+                new CommandNode("dungeon", "Main Dungeon command", null),
+                Services.get(DungeonModule.class)
+        );
     }
 
     @Override
     public void createCommands() {
-        registerCommand("test", "creates a test instance of a dungeon and teleports you there", this::testDungeon);
-        registerCommand("back", "go back to the world you were previously in", this::backFromDungeon);
+        registerCommand(
+                "test",
+                "creates a test instance of a dungeon and teleports you there",
+                this::testDungeon
+        );
+        registerCommand(
+                "cleanup",
+                "Clears all dungeon worlds",
+                this::clearDungeons
+        );
+
+        registerCommand(
+                "back",
+                "go back to the world you were previously in",
+                this::backFromDungeon
+        );
     }
-    private void backFromDungeon(CommandSender sender, String[] strings) {
-        if(!(sender instanceof Player pl)) return;
 
-        DungeonWorld world = new DungeonWorld();
-        world.createWorld();
-        world.sendPlayerToDungeon(pl);
+    private void clearDungeons(CommandSender sender, String[] strings) {
+        Services.get(DungeonManager.class).deleteAllDungeons();
     }
-    private void testDungeon(CommandSender sender, String[] strings) {
-        if(!(sender instanceof Player pl)) return;
-        DungeonWorld world = new DungeonWorld();
-        world.createWorld();
-        world.sendPlayerToDungeon(pl);
+
+    private void testDungeon(CommandSender sender, String[] args) {
+        if (!(sender instanceof Player player)) return;
+        backFromDungeon(sender, args);
+        DungeonWorld dungeon = Services.get(DungeonManager.class).createDungeon();
+        dungeon.sendPlayerToDungeon(player);
+    }
+
+    private void backFromDungeon(CommandSender sender, String[] args) {
+        if (!(sender instanceof Player player)) return;
+
+        DungeonManager manager = Services.get(DungeonManager.class);
+        DungeonWorld dungeon = manager.getDungeonByWorld(player.getWorld());
+
+        if (dungeon == null) {
+            player.sendMessage("§cYou are not in a dungeon.");
+            return;
+        }
+
+        dungeon.returnPlayer(player);
+
+        // Optional: auto-delete dungeon if empty
+        Bukkit.getScheduler().runTaskLater(
+                Services.get(Main.class),
+                () -> {
+                    if (player.getWorld().getPlayers().isEmpty()) {
+                        manager.deleteDungeon(dungeon.getUUID());
+                    }
+                },
+                1L
+        );
     }
 }

+ 132 - 0
src/main/java/me/lethunderhawk/dungeon/generation/DungeonGenerator.java

@@ -0,0 +1,132 @@
+package me.lethunderhawk.dungeon.generation;
+
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.World;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+public class DungeonGenerator {
+
+    private final World world;
+    private final Random random = new Random();
+
+    /* =========================
+       CONFIGURATION
+       ========================= */
+
+    private final int roomSize;        // square size (blocks)
+    private final int roomHeight;
+    private final int roomCount;
+    private final int spacing;
+    private final Location DUNGEON_ORIGIN;
+    private final int baseY = 99;
+
+    private static final int START_X = 0;
+    private static final int START_Z = 0;
+
+    public DungeonGenerator(
+            World world,
+            int roomSize,
+            int roomHeight,
+            int roomCount,
+            int spacing
+    ) {
+        this.world = world;
+        this.roomSize = roomSize;
+        this.roomHeight = roomHeight;
+        this.roomCount = roomCount;
+        this.spacing = spacing;
+        this.DUNGEON_ORIGIN = new Location(world, 0,99,0);
+    }
+
+    /* =========================
+       ENTRY POINT
+       ========================= */
+
+    public void generate() {
+        List<Location> roomCenters = new ArrayList<>();
+
+        // First room is ALWAYS at 0 / 100 / 0
+        Location start = new Location(world, START_X, baseY, START_Z);
+        roomCenters.add(start);
+        generateRoom(start);
+
+        // Generate remaining rooms relative to start
+        for (int i = 1; i < roomCount; i++) {
+            int x = START_X + (i * spacing);
+            int z = START_Z + (random.nextBoolean() ? spacing : -spacing);
+
+            Location center = new Location(world, x, baseY, z);
+            roomCenters.add(center);
+            generateRoom(center);
+        }
+
+        generatePaths(roomCenters);
+    }
+
+    /* =========================
+       ROOM GENERATION
+       ========================= */
+
+    private void generateRoom(Location center) {
+        int half = roomSize / 2;
+
+        for (int x = -half; x <= half; x++) {
+            for (int z = -half; z <= half; z++) {
+                for (int y = 0; y < roomHeight; y++) {
+
+                    Location loc = center.clone().add(x, y, z);
+
+                    if (y == 0 || y == roomHeight - 1) {
+                        loc.getBlock().setType(Material.STONE);
+                    }
+                    else if (
+                            x == -half || x == half ||
+                                    z == -half || z == half
+                    ) {
+                        loc.getBlock().setType(Material.STONE);
+                    }
+                    else {
+                        loc.getBlock().setType(Material.AIR);
+                    }
+                }
+            }
+        }
+    }
+
+    /* =========================
+       PATH GENERATION
+       ========================= */
+
+    private void generatePaths(List<Location> rooms) {
+        for (int i = 0; i < rooms.size() - 1; i++) {
+            carvePath(rooms.get(i), rooms.get(i + 1));
+        }
+    }
+
+    private void carvePath(Location from, Location to) {
+        int y = baseY;
+
+        int x1 = from.getBlockX();
+        int z1 = from.getBlockZ();
+        int x2 = to.getBlockX();
+        int z2 = to.getBlockZ();
+
+        for (int x = Math.min(x1, x2); x <= Math.max(x1, x2); x++) {
+            placePathBlock(x, y, z1);
+        }
+
+        for (int z = Math.min(z1, z2); z <= Math.max(z1, z2); z++) {
+            placePathBlock(x2, y, z);
+        }
+    }
+
+    private void placePathBlock(int x, int y, int z) {
+        world.getBlockAt(x, y, z).setType(Material.STONE_BRICKS);
+        world.getBlockAt(x, y + 1, z).setType(Material.AIR);
+        world.getBlockAt(x, y + 2, z).setType(Material.AIR);
+    }
+}

+ 478 - 0
src/main/java/me/lethunderhawk/dungeon/generation/DungeonGridGenerator.java

@@ -0,0 +1,478 @@
+package me.lethunderhawk.dungeon.generation;
+
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.World;
+
+import java.util.*;
+
+public class DungeonGridGenerator {
+
+    private final World world;
+
+    private final int gridWidth;
+    private final int gridHeight;
+    private final int roomSize;
+    private final int roomHeight;
+    private final int baseY = 99;
+
+    private DungeonRoom[][] grid;
+    private final Random random = new Random();
+
+    // Track main path and branches separately
+    private final List<DungeonRoom> mainPathRooms = new ArrayList<>();
+    private final Set<DungeonRoom> branchRooms = new HashSet<>();
+
+    // Probability that a branch spawns from a room initially
+    private static final double INITIAL_BRANCH_CHANCE = 0.4;
+
+    public DungeonGridGenerator(World world, int gridWidth, int gridHeight, int roomSize, int roomHeight) {
+        this.world = world;
+        this.gridWidth = gridWidth;
+        this.gridHeight = gridHeight;
+        this.roomSize = roomSize;
+        this.roomHeight = roomHeight;
+    }
+
+    public void generate() {
+        initGrid();
+        generateRoomTypes();
+        carveRooms();
+        carveDoors();
+        carvePaths();
+    }
+
+    /* =========================
+       INITIALIZE GRID
+       ========================= */
+
+    private void initGrid() {
+        grid = new DungeonRoom[gridWidth][gridHeight];
+        for (int x = 0; x < gridWidth; x++)
+            for (int z = 0; z < gridHeight; z++)
+                grid[x][z] = new DungeonRoom(x, z, DungeonRoomType.REGULAR);
+        mainPathRooms.clear();
+        branchRooms.clear();
+    }
+
+    /* =========================
+       GENERATE ROOM TYPES + PATHS
+       ========================= */
+
+    private void generateRoomTypes() {
+
+        // 1) RESET
+        for (int x = 0; x < gridWidth; x++)
+            for (int z = 0; z < gridHeight; z++)
+                grid[x][z].type = DungeonRoomType.REGULAR;
+
+        mainPathRooms.clear();
+        branchRooms.clear();
+
+        DungeonRoom start = grid[0][0];
+        DungeonRoom end = grid[gridWidth - 1][gridHeight - 1];
+
+        // 2) ERZEUGE EINEN SPANNBAUM ÜBER DAS GANZE GRID
+        generateSpanningTree(start);
+
+        // 3) FINDE DEN EINDEUTIGEN WEG VON START → END
+        List<DungeonRoom> mainPath = findPathInTree(start, end);
+        enforceSingleEntranceForEnd(end, mainPath);
+        if (mainPath.isEmpty())
+            throw new IllegalStateException("Tree path failed");
+
+        mainPathRooms.addAll(mainPath);
+
+        // 4) ROOM TYPES
+        mainPath.get(0).type = DungeonRoomType.START;
+        mainPath.get(mainPath.size() - 1).type = DungeonRoomType.BLOOD;
+
+        if (mainPath.size() > 2) {
+            int fairyIndex = 1 + random.nextInt(mainPath.size() - 2);
+            mainPath.get(fairyIndex).type = DungeonRoomType.FAIRY;
+        }
+    }
+    private void enforceSingleEntranceForEnd(DungeonRoom end, List<DungeonRoom> mainPath) {
+
+        // The room that is actually BEFORE the end on the main path
+        DungeonRoom realParent = mainPath.get(mainPath.size() - 2);
+
+        // For every neighbor of the end room:
+        for (DungeonRoom n : getNeighbors(end)) {
+
+            // If this neighbor is NOT the real main-path predecessor,
+            // cut the connection.
+            if (n != realParent && areConnected(end, n)) {
+                disconnectRooms(end, n);
+            }
+        }
+    }
+    private void disconnectRooms(DungeonRoom a, DungeonRoom b) {
+        int dx = b.gridX - a.gridX;
+        int dz = b.gridZ - a.gridZ;
+
+        if (dx == 1) {
+            a.doors[Direction.EAST.ordinal()] = false;
+            b.doors[Direction.WEST.ordinal()] = false;
+        }
+        else if (dx == -1) {
+            a.doors[Direction.WEST.ordinal()] = false;
+            b.doors[Direction.EAST.ordinal()] = false;
+        }
+        else if (dz == 1) {
+            a.doors[Direction.SOUTH.ordinal()] = false;
+            b.doors[Direction.NORTH.ordinal()] = false;
+        }
+        else if (dz == -1) {
+            a.doors[Direction.NORTH.ordinal()] = false;
+            b.doors[Direction.SOUTH.ordinal()] = false;
+        }
+    }
+    private void generateSpanningTree(DungeonRoom start) {
+        Set<DungeonRoom> visited = new HashSet<>();
+        Deque<DungeonRoom> stack = new ArrayDeque<>();
+
+        stack.push(start);
+        visited.add(start);
+
+        while (!stack.isEmpty()) {
+            DungeonRoom current = stack.peek();
+            List<DungeonRoom> neighbors = getNeighbors(current);
+            Collections.shuffle(neighbors, random);
+
+            DungeonRoom next = null;
+            for (DungeonRoom n : neighbors) {
+                if (!visited.contains(n)) {
+                    next = n;
+                    break;
+                }
+            }
+
+            if (next == null) {
+                stack.pop();
+            } else {
+                visited.add(next);
+                connectRooms(current, next);
+                stack.push(next);
+            }
+        }
+    }
+    private List<DungeonRoom> findPathInTree(DungeonRoom start, DungeonRoom end) {
+        Map<DungeonRoom, DungeonRoom> parent = new HashMap<>();
+        Deque<DungeonRoom> queue = new ArrayDeque<>();
+        queue.add(start);
+        parent.put(start, null);
+
+        while (!queue.isEmpty()) {
+            DungeonRoom cur = queue.poll();
+            if (cur == end) break;
+
+            for (DungeonRoom n : getNeighbors(cur)) {
+                if (!parent.containsKey(n) && areConnected(cur, n)) {
+                    parent.put(n, cur);
+                    queue.add(n);
+                }
+            }
+        }
+
+        List<DungeonRoom> path = new ArrayList<>();
+        DungeonRoom cur = end;
+
+        while (cur != null) {
+            path.add(cur);
+            cur = parent.get(cur);
+        }
+
+        Collections.reverse(path);
+        return path;
+    }
+    private boolean areConnected(DungeonRoom a, DungeonRoom b) {
+        int dx = b.gridX - a.gridX;
+        int dz = b.gridZ - a.gridZ;
+
+        if (dx == 1) return a.doors[Direction.EAST.ordinal()];
+        if (dx == -1) return a.doors[Direction.WEST.ordinal()];
+        if (dz == 1) return a.doors[Direction.SOUTH.ordinal()];
+        if (dz == -1) return a.doors[Direction.NORTH.ordinal()];
+        return false;
+    }
+
+    /* =========================
+       GENERATE MAIN PATH (biased random walk)
+       ========================= */
+
+    private List<DungeonRoom> generateMainPathRandomWalk(DungeonRoom start, DungeonRoom end) {
+        List<DungeonRoom> path = new ArrayList<>();
+        DungeonRoom current = start;
+        path.add(current);
+
+        while (current != end) {
+            List<DungeonRoom> candidates = new ArrayList<>();
+
+            for (DungeonRoom neighbor : getNeighbors(current)) {
+                int distCurrent = manhattanDistance(current, end);
+                int distNeighbor = manhattanDistance(neighbor, end);
+
+                // Only allow steps that don't increase distance to end and are not already in path
+                if (distNeighbor <= distCurrent && !path.contains(neighbor)) {
+                    candidates.add(neighbor);
+                }
+            }
+
+            if (candidates.isEmpty()) {
+                // Dead end encountered, restart generation to avoid stuck
+                return Collections.emptyList();
+            }
+
+            current = candidates.get(random.nextInt(candidates.size()));
+            path.add(current);
+        }
+
+        return path;
+    }
+
+    private int manhattanDistance(DungeonRoom a, DungeonRoom b) {
+        return Math.abs(a.gridX - b.gridX) + Math.abs(a.gridZ - b.gridZ);
+    }
+
+    /* =========================
+       CREATE BRANCHES FROM MAIN PATH
+       ========================= */
+
+    private void createBranches(DungeonRoom from, double chance) {
+        if (chance < 0.1) return; // Stop recursion
+
+        List<DungeonRoom> neighbors = getNeighbors(from);
+        Collections.shuffle(neighbors, random);
+
+        for (DungeonRoom neighbor : neighbors) {
+            if (mainPathRooms.contains(neighbor) || branchRooms.contains(neighbor))
+                continue;
+
+            if (random.nextDouble() < chance) {
+                branchRooms.add(neighbor);
+                neighbor.type = DungeonRoomType.REGULAR;
+                connectRooms(from, neighbor);
+                createBranches(neighbor, chance * 0.6);
+            }
+        }
+    }
+
+    /* =========================
+       GET GRID NEIGHBORS (N, E, S, W)
+       ========================= */
+
+    private List<DungeonRoom> getNeighbors(DungeonRoom room) {
+        List<DungeonRoom> neighbors = new ArrayList<>();
+        int x = room.gridX;
+        int z = room.gridZ;
+
+        if (x > 0) neighbors.add(grid[x - 1][z]);
+        if (x < gridWidth - 1) neighbors.add(grid[x + 1][z]);
+        if (z > 0) neighbors.add(grid[x][z - 1]);
+        if (z < gridHeight - 1) neighbors.add(grid[x][z + 1]);
+
+        return neighbors;
+    }
+
+    /* =========================
+       CONNECT ROOMS BY SETTING DOORS
+       ========================= */
+
+    private enum Direction {
+        NORTH, EAST, SOUTH, WEST
+    }
+
+    private void connectRooms(DungeonRoom a, DungeonRoom b) {
+        int dx = b.gridX - a.gridX;
+        int dz = b.gridZ - a.gridZ;
+
+        if (dx == 1) {
+            a.doors[Direction.EAST.ordinal()] = true;
+            b.doors[Direction.WEST.ordinal()] = true;
+        } else if (dx == -1) {
+            a.doors[Direction.WEST.ordinal()] = true;
+            b.doors[Direction.EAST.ordinal()] = true;
+        } else if (dz == 1) {
+            a.doors[Direction.SOUTH.ordinal()] = true;
+            b.doors[Direction.NORTH.ordinal()] = true;
+        } else if (dz == -1) {
+            a.doors[Direction.NORTH.ordinal()] = true;
+            b.doors[Direction.SOUTH.ordinal()] = true;
+        }
+    }
+
+    /* =========================
+       CARVE ROOMS
+       ========================= */
+
+    private void carveRooms() {
+        for (int x = 0; x < gridWidth; x++) {
+            for (int z = 0; z < gridHeight; z++) {
+                DungeonRoom room = grid[x][z];
+                Location center = gridToWorld(x, z);
+                carveRoom(center, room.type);
+            }
+        }
+    }
+
+    private void carveRoom(Location center, DungeonRoomType type) {
+        int half = roomSize / 2;
+
+        for (int dx = -half; dx <= half; dx++) {
+            for (int dz = -half; dz <= half; dz++) {
+                for (int y = 0; y < roomHeight; y++) {
+                    Location loc = center.clone().add(dx, y, dz);
+
+                    if (y == 0 || y == roomHeight - 1) {
+                        loc.getBlock().setType(Material.STONE);
+                        continue;
+                    }
+
+                    if (dx == -half || dx == half || dz == -half || dz == half) {
+                        loc.getBlock().setType(Material.STONE);
+                        continue;
+                    }
+
+                    loc.getBlock().setType(Material.AIR);
+                }
+            }
+        }
+
+        // Place visible marker block on top center (one block above floor)
+        Location markerLoc = center.clone().add(0, roomHeight + 3, 0);
+
+        if (type == DungeonRoomType.START) {
+            markerLoc.getBlock().setType(Material.GREEN_CONCRETE);
+        } else if (type == DungeonRoomType.BLOOD) {
+            markerLoc.getBlock().setType(Material.RED_CONCRETE);
+        }
+    }
+
+    /* =========================
+       CARVE DOORS BETWEEN CONNECTED ROOMS
+       ========================= */
+
+    private void carveDoors() {
+        for (int x = 0; x < gridWidth; x++) {
+            for (int z = 0; z < gridHeight; z++) {
+                DungeonRoom room = grid[x][z];
+
+                if (x < gridWidth - 1 && room.doors[Direction.EAST.ordinal()]) {
+                    DungeonRoom eastRoom = grid[x + 1][z];
+                    carveDoorBetween(room, eastRoom, Direction.EAST);
+                }
+
+                if (z < gridHeight - 1 && room.doors[Direction.SOUTH.ordinal()]) {
+                    DungeonRoom southRoom = grid[x][z + 1];
+                    carveDoorBetween(room, southRoom, Direction.SOUTH);
+                }
+            }
+        }
+    }
+
+    private void carveDoorBetween(DungeonRoom from, DungeonRoom to, Direction dir) {
+        int y = baseY + 1;
+
+        Location fromCenter = gridToWorld(from.gridX, from.gridZ);
+        Location toCenter = gridToWorld(to.gridX, to.gridZ);
+
+        int doorX = fromCenter.getBlockX();
+        int doorZ = fromCenter.getBlockZ();
+
+        switch (dir) {
+            case NORTH -> doorZ -= roomSize / 2;
+            case EAST -> doorX += roomSize / 2;
+            case SOUTH -> doorZ += roomSize / 2;
+            case WEST -> doorX -= roomSize / 2;
+        }
+
+        world.getBlockAt(doorX, y, doorZ).setType(Material.AIR);
+        world.getBlockAt(doorX, y + 1, doorZ).setType(Material.AIR);
+    }
+
+    /* =========================
+       CARVE PATHS BETWEEN CONNECTED ROOMS
+       ========================= */
+
+    private void carvePaths() {
+        for (int x = 0; x < gridWidth; x++) {
+            for (int z = 0; z < gridHeight; z++) {
+                DungeonRoom room = grid[x][z];
+
+                if (x < gridWidth - 1 && room.doors[Direction.EAST.ordinal()]) {
+                    DungeonRoom eastRoom = grid[x + 1][z];
+                    carvePathBetweenRooms(room, eastRoom);
+                }
+
+                if (z < gridHeight - 1 && room.doors[Direction.SOUTH.ordinal()]) {
+                    DungeonRoom southRoom = grid[x][z + 1];
+                    carvePathBetweenRooms(room, southRoom);
+                }
+            }
+        }
+    }
+
+    private void carvePathBetweenRooms(DungeonRoom from, DungeonRoom to) {
+        Location start = gridToWorld(from.gridX, from.gridZ);
+        Location end = gridToWorld(to.gridX, to.gridZ);
+
+        int y = baseY;
+
+        int x1 = start.getBlockX();
+        int z1 = start.getBlockZ();
+        int x2 = end.getBlockX();
+        int z2 = end.getBlockZ();
+
+        for (int x = Math.min(x1, x2); x <= Math.max(x1, x2); x++) {
+            placePathBlock(x, y, z1);
+            placePathBlock(x, y + roomHeight + 10, z1);
+        }
+        for (int z = Math.min(z1, z2); z <= Math.max(z1, z2); z++) {
+            placePathBlock(x2, y, z);
+            placePathBlock(x2, y + roomHeight + 10, z);
+        }
+    }
+
+    private void placePathBlock(int x, int y, int z) {
+        world.getBlockAt(x, y, z).setType(Material.STONE_BRICKS);
+        world.getBlockAt(x, y + 1, z).setType(Material.AIR);
+        world.getBlockAt(x, y + 2, z).setType(Material.AIR);
+    }
+
+    /* =========================
+       HELPERS
+       ========================= */
+
+    private Location gridToWorld(int gridX, int gridZ) {
+        int worldX = gridX * (roomSize + 2);
+        int worldZ = gridZ * (roomSize + 2);
+        return new Location(world, worldX, baseY, worldZ);
+    }
+
+    /* =========================
+       INNER ROOM CLASS + ROOM TYPES
+       ========================= */
+
+    public enum DungeonRoomType {
+        START,
+        FAIRY,
+        PUZZLE,
+        MINIBOSS,
+        TRAP,
+        BLOOD,
+        REGULAR
+    }
+
+    public static class DungeonRoom {
+        public final int gridX, gridZ;
+        public DungeonRoomType type;
+        public final boolean[] doors = new boolean[4]; // N, E, S, W
+
+        public DungeonRoom(int gridX, int gridZ, DungeonRoomType type) {
+            this.gridX = gridX;
+            this.gridZ = gridZ;
+            this.type = type;
+        }
+    }
+}

+ 11 - 0
src/main/java/me/lethunderhawk/dungeon/generation/DungeonRoomType.java

@@ -0,0 +1,11 @@
+package me.lethunderhawk.dungeon.generation;
+
+public enum DungeonRoomType {
+    START,
+    FAIRY,
+    PUZZLE,
+    MINIBOSS,
+    TRAP,
+    BLOOD,
+    REGULAR
+}

+ 9 - 3
src/main/java/me/lethunderhawk/dungeon/generation/DungeonWorld.java

@@ -59,8 +59,14 @@ public class DungeonWorld implements Listener {
     }
 
     private void startDungeonGeneration() {
-        // NOT IMPLEMENTED
-        // This is where you queue structure generation, rooms, corridors, etc.
+        DungeonGridGenerator gen = new DungeonGridGenerator(
+                world,
+                5,     // grid width (e.g. 4 to 6)
+                5,     // grid height
+                13,     // room size (odd recommended)
+                13      // room height
+        );
+        gen.generate();
     }
 
     /* =========================
@@ -113,7 +119,7 @@ public class DungeonWorld implements Listener {
             Player player = Bukkit.getPlayer(playerUUID);
             if (player == null) continue;
 
-            Bukkit.getScheduler().runTask(plugin, () -> returnPlayer(player));
+            returnPlayer(player);
         }
     }
 

+ 10 - 0
src/main/java/me/lethunderhawk/dungeon/manager/DungeonManager.java

@@ -82,6 +82,16 @@ public class DungeonManager {
         if (world == null) return false;
         return activeDungeons.containsKey(UUID.fromString(world.getName()));
     }
+    public DungeonWorld getDungeonByWorld(World world) {
+        if (world == null) return null;
+
+        try {
+            UUID uuid = UUID.fromString(world.getName());
+            return activeDungeons.get(uuid);
+        } catch (IllegalArgumentException ignored) {
+            return null;
+        }
+    }
 
     /* =========================
        FILE DELETION