diff --git a/src/main/java/org/dungeon/game/DungeonCreator.java b/src/main/java/org/dungeon/game/DungeonCreator.java index eeaa40b4..70ef0278 100644 --- a/src/main/java/org/dungeon/game/DungeonCreator.java +++ b/src/main/java/org/dungeon/game/DungeonCreator.java @@ -18,6 +18,8 @@ package org.dungeon.game; import org.dungeon.game.LocationPreset.Type; +import org.dungeon.logging.DungeonLogger; +import org.dungeon.util.Percentage; import org.jetbrains.annotations.NotNull; @@ -29,7 +31,14 @@ */ class DungeonCreator implements Serializable { - private static final MinimumBoundingRectangle minimumBoundingRectangle = new MinimumBoundingRectangle(1, 1); + private static final Percentage HORIZONTAL_EXPANSION_PROBABILITY = Percentage.fromString("50%"); + + /** + * It is of uttermost importance that we respect this rectangle. + * + * Currently a 5x1 So that we can make dungeons like this: R=R=R (where R is a room and = is a corridor). + */ + private static final MinimumBoundingRectangle minimumBoundingRectangle = new MinimumBoundingRectangle(5, 1); private final DungeonDistributor distributor; public DungeonCreator(DungeonDistributor distributor) { @@ -46,29 +55,106 @@ private static LocationPreset getRandomEntrancePreset() { return Random.select(entrancePresets); } + private static LocationPreset getRandomStairwayPreset() { + LocationPresetStore locationPresetStore = GameData.getLocationPresetStore(); + List entrancePresets = locationPresetStore.getLocationPresetsByType(Type.DUNGEON_STAIRWAY); + return Random.select(entrancePresets); + } + + private static LocationPreset getRandomRoomPreset() { + LocationPresetStore locationPresetStore = GameData.getLocationPresetStore(); + List entrancePresets = locationPresetStore.getLocationPresetsByType(Type.DUNGEON_ROOM); + return Random.select(entrancePresets); + } + + private static LocationPreset getRandomCorridorPreset() { + LocationPresetStore locationPresetStore = GameData.getLocationPresetStore(); + List entrancePresets = locationPresetStore.getLocationPresetsByType(Type.DUNGEON_CORRIDOR); + return Random.select(entrancePresets); + } + /** * Creates a dungeon placing the entrance at the specified point. The world should not have a location at the * specified point. */ public void createDungeon(@NotNull World world, @NotNull Point entrance) { - createEntrance(world, entrance); - Location dungeonRoom = new Location(getRandomRoomPreset(), world); - world.addLocation(dungeonRoom, new Point(entrance, Direction.DOWN)); + Point mainRoomPoint = createEntrance(world, entrance); + Location mainRoomLocation = createMainRoom(world, mainRoomPoint); + finishDungeon(world, mainRoomPoint, mainRoomLocation); } - private void createEntrance(@NotNull World world, @NotNull Point entrance) { + /** + * Creates the dungeon's entrance and the necessary stairways. + * + * Returns the point where the main dungeon room should be. + */ + private Point createEntrance(@NotNull World world, @NotNull Point entrance) { + // The entrance. if (world.alreadyHasLocationAt(entrance)) { throw new IllegalStateException("world has location at the specified entrance."); } Location dungeonEntrance = new Location(getRandomEntrancePreset(), world); world.addLocation(dungeonEntrance, entrance); distributor.registerDungeonEntrance(entrance); + // The stairway. + Point stairwayPoint = new Point(entrance, Direction.DOWN); + // Note that all DUNGEON_STAIRWAY presets are blocked towards North, East, South, and West. + world.addLocation(new Location(getRandomStairwayPreset(), world), stairwayPoint); + return new Point(stairwayPoint, Direction.DOWN); } - private LocationPreset getRandomRoomPreset() { - LocationPresetStore locationPresetStore = GameData.getLocationPresetStore(); - List entrancePresets = locationPresetStore.getLocationPresetsByType(Type.DUNGEON_ROOM); - return Random.select(entrancePresets); + @NotNull + private Location createMainRoom(@NotNull World world, Point mainRoomPoint) { + // Note that all DUNGEON_ROOM presets are open on all directions. It is up to the code to properly block them. + Location dungeonRoom = new Location(getRandomRoomPreset(), world); + dungeonRoom.getBlockedEntrances().block(Direction.NORTH); + dungeonRoom.getBlockedEntrances().block(Direction.DOWN); + dungeonRoom.getBlockedEntrances().block(Direction.SOUTH); + world.addLocation(dungeonRoom, mainRoomPoint); // The main room. All dungeons have one. + return dungeonRoom; + } + + /** + * Finishes the dungeon by randomly deciding to expand it to the east and west. + * + * If this method does not make a corridor to east or west, it blocks that entrance in the main room to prevent + * glitches. + */ + private void finishDungeon(@NotNull World world, Point mainRoomPoint, Location mainRoomLocation) { + // UPDATING THIS LOGIC MAY REQUIRE YOU TO UPDATE THE minimumBoundingRectangle variable. + if (Random.roll(HORIZONTAL_EXPANSION_PROBABILITY)) { + expandTowards(world, mainRoomPoint, Direction.EAST); + } else { + mainRoomLocation.getBlockedEntrances().block(Direction.EAST); + } + if (Random.roll(HORIZONTAL_EXPANSION_PROBABILITY)) { + expandTowards(world, mainRoomPoint, Direction.WEST); + } else { + mainRoomLocation.getBlockedEntrances().block(Direction.WEST); + } + } + + private void expandTowards(@NotNull World world, @NotNull Point origin, Direction direction) { + Point corridorPoint = new Point(origin, direction); + if (world.alreadyHasLocationAt(corridorPoint)) { + DungeonLogger.warning("Found an existing location when attempting to expand a Dungeon at " + corridorPoint + "."); + } + // Note that all DUNGEON_CORRIDOR presets have blocked UP and DOWN. It is up to the code to properly block the rest. + Location corridorLocation = new Location(getRandomCorridorPreset(), world); + corridorLocation.getBlockedEntrances().block(Direction.NORTH); + corridorLocation.getBlockedEntrances().block(Direction.SOUTH); + world.addLocation(corridorLocation, corridorPoint); + Point roomPoint = new Point(corridorPoint, direction); + if (world.alreadyHasLocationAt(roomPoint)) { + DungeonLogger.warning("Found an existing location when attempting to expand a Dungeon at " + roomPoint + "."); + } + Location roomLocation = new Location(getRandomRoomPreset(), world); + roomLocation.getBlockedEntrances().block(Direction.UP); + roomLocation.getBlockedEntrances().block(Direction.NORTH); + roomLocation.getBlockedEntrances().block(Direction.DOWN); + roomLocation.getBlockedEntrances().block(Direction.SOUTH); + roomLocation.getBlockedEntrances().block(direction); + world.addLocation(roomLocation, roomPoint); } } diff --git a/src/main/java/org/dungeon/game/DungeonDistributor.java b/src/main/java/org/dungeon/game/DungeonDistributor.java index 65396126..2e696267 100644 --- a/src/main/java/org/dungeon/game/DungeonDistributor.java +++ b/src/main/java/org/dungeon/game/DungeonDistributor.java @@ -29,7 +29,7 @@ /** * A class that is responsible for distributing dungeons. */ -public class DungeonDistributor implements Serializable { +class DungeonDistributor implements Serializable { private static final Percentage dungeonProbability = Percentage.fromString("2%"); private static final Set entrances = new HashSet(); diff --git a/src/main/java/org/dungeon/game/LocationPreset.java b/src/main/java/org/dungeon/game/LocationPreset.java index 9016f89f..e488ea53 100644 --- a/src/main/java/org/dungeon/game/LocationPreset.java +++ b/src/main/java/org/dungeon/game/LocationPreset.java @@ -123,6 +123,6 @@ public void setBlobSize(int blobSize) { this.blobSize = blobSize; } - enum Type {RIVER, BRIDGE, DUNGEON_ENTRANCE, DUNGEON_ROOM, LAND} + enum Type {RIVER, BRIDGE, DUNGEON_ENTRANCE, DUNGEON_STAIRWAY, DUNGEON_ROOM, DUNGEON_CORRIDOR, LAND} } diff --git a/src/main/resources/locations.json b/src/main/resources/locations.json index 666c0895..3837b694 100644 --- a/src/main/resources/locations.json +++ b/src/main/resources/locations.json @@ -166,6 +166,28 @@ } ] }, + { + "id": "DUNGEON_STAIRWAY", + "type": "DUNGEON_STAIRWAY", + "name": { + "singular": "Dungeon Stairway" + }, + "color": [ + 192, + 192, + 192 + ], + "symbol": "S", + "info": "A set of stone steps.", + "blobSize": 0, + "lightPermittivity": 0.2, + "blockedEntrances": [ + "N", + "E", + "S", + "W" + ] + }, { "id": "DUNGEON_ROOM", "type": "DUNGEON_ROOM", @@ -182,11 +204,48 @@ "blobSize": 0, "lightPermittivity": 0.0, "blockedEntrances": [ - "N", - "E", - "D", - "S", - "W" + ], + "spawners": [ + { + "id": "FROG", + "population": 2, + "delay": 2 + }, + { + "id": "HUGE_SPIDER", + "population": 1, + "delay": 4 + } + ], + "items": [ + { + "id": "TORCH", + "probability": 1.0 + }, + { + "id": "TOME_OF_PERCEIVE", + "probability": 0.05 + } + ] + }, + { + "id": "DUNGEON_CORRIDOR", + "type": "DUNGEON_CORRIDOR", + "name": { + "singular": "Dungeon Corridor" + }, + "color": [ + 192, + 192, + 192 + ], + "symbol": "=", + "info": "A dark corridor with stone walls.", + "blobSize": 0, + "lightPermittivity": 0.0, + "blockedEntrances": [ + "U", + "D" ], "spawners": [ {