| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478 |
- 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;
- }
- }
- }
|