From 3bdc4113c0a659f4c2d28e13cae336a5355bd894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Sat, 18 Jul 2026 22:13:49 +0200 Subject: [PATCH] added 20 obstacle types, adjustable track length and forked obstacle generation --- .../types/playerRace/PlayerRace.java | 5 +- .../playerRace/PlayerRaceChunkgenerator.java | 62 +++++++++------- .../types/playerRace/PlayerRaceFactory.java | 10 ++- .../playerRace/obstacles/BambooThicket.java | 34 +++++++++ .../playerRace/obstacles/BasaltPillars.java | 47 +++++++++++++ .../playerRace/obstacles/BerryPatch.java | 33 +++++++++ .../playerRace/obstacles/BoulderTrench.java | 44 ++++++++++++ .../playerRace/obstacles/ChorusGrove.java | 70 +++++++++++++++++++ .../playerRace/obstacles/CobwebGrove.java | 49 +++++++++++++ .../playerRace/obstacles/DripstoneSpikes.java | 51 ++++++++++++++ .../playerRace/obstacles/GlassPaneWeave.java | 40 +++++++++++ .../playerRace/obstacles/HayHurdles.java | 39 +++++++++++ .../playerRace/obstacles/HoneyField.java | 38 ++++++++++ .../types/playerRace/obstacles/IceRink.java | 57 +++++++++++++++ .../playerRace/obstacles/LadderWall.java | 37 ++++++++++ .../playerRace/obstacles/LeafHedges.java | 39 +++++++++++ .../types/playerRace/obstacles/MudPools.java | 44 ++++++++++++ .../types/playerRace/obstacles/Mudpit.java | 6 +- .../playerRace/obstacles/MushroomRoofs.java | 41 +++++++++++ .../types/playerRace/obstacles/Obstacle.java | 52 +++++++++++++- .../playerRace/obstacles/PowderSnowField.java | 46 ++++++++++++ .../types/playerRace/obstacles/SandDunes.java | 44 ++++++++++++ .../playerRace/obstacles/SlimePatches.java | 34 +++++++++ .../playerRace/obstacles/WallSlalom.java | 42 +++++++++++ .../playerRace/obstacles/WaterChannel.java | 41 +++++++++++ 25 files changed, 970 insertions(+), 35 deletions(-) create mode 100644 src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/BambooThicket.java create mode 100644 src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/BasaltPillars.java create mode 100644 src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/BerryPatch.java create mode 100644 src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/BoulderTrench.java create mode 100644 src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/ChorusGrove.java create mode 100644 src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/CobwebGrove.java create mode 100644 src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/DripstoneSpikes.java create mode 100644 src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/GlassPaneWeave.java create mode 100644 src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/HayHurdles.java create mode 100644 src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/HoneyField.java create mode 100644 src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/IceRink.java create mode 100644 src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/LadderWall.java create mode 100644 src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/LeafHedges.java create mode 100644 src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/MudPools.java create mode 100644 src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/MushroomRoofs.java create mode 100644 src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/PowderSnowField.java create mode 100644 src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/SandDunes.java create mode 100644 src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/SlimePatches.java create mode 100644 src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/WallSlalom.java create mode 100644 src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/WaterChannel.java diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/PlayerRace.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/PlayerRace.java index ea21829..73bc8e7 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/PlayerRace.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/PlayerRace.java @@ -4,16 +4,15 @@ import eu.mhsl.minenet.minigames.instance.Dimension; import eu.mhsl.minenet.minigames.instance.game.stateless.StatelessGame; import eu.mhsl.minenet.minigames.score.LastWinsScore; import net.minestom.server.coordinate.Pos; -import net.minestom.server.instance.batch.AbsoluteBlockBatch; import net.minestom.server.instance.block.Block; import org.jetbrains.annotations.NotNull; import java.util.concurrent.CompletableFuture; public class PlayerRace extends StatelessGame { - public PlayerRace() { + public PlayerRace(int obstacleCount) { super(Dimension.THE_END.key, "PlayerRace", new LastWinsScore()); - this.setGenerator(new PlayerRaceChunkgenerator(64)); + this.setGenerator(new PlayerRaceChunkgenerator(64, obstacleCount)); } @Override diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/PlayerRaceChunkgenerator.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/PlayerRaceChunkgenerator.java index f3251d7..d8b5704 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/PlayerRaceChunkgenerator.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/PlayerRaceChunkgenerator.java @@ -6,44 +6,56 @@ import eu.mhsl.minenet.minigames.world.BlockPallet; import eu.mhsl.minenet.minigames.world.generator.featureEnriched.ValeGenerator; import eu.mhsl.minenet.minigames.world.generator.terrain.BaseGenerator; import net.minestom.server.coordinate.Point; +import net.minestom.server.coordinate.Vec; import net.minestom.server.instance.block.Block; - -import java.util.function.BiConsumer; +import net.minestom.server.instance.generator.GenerationUnit; public class PlayerRaceChunkgenerator extends BaseGenerator { - public PlayerRaceChunkgenerator(int height) { + private static final int START_LENGTH = 16; + private static final int GAP_LENGTH = 3; + private static final int PERIOD = Obstacle.LENGTH + GAP_LENGTH; + + public PlayerRaceChunkgenerator(int height, int obstacleCount) { ValeGenerator vale = new ValeGenerator(); vale.setXShiftMultiplier(integer -> 0.5d); vale.setHeightNoiseMultiplier(integer -> 2); this.addMixIn(vale); + int finishStart = START_LENGTH + obstacleCount * PERIOD; + int finishEnd = finishStart + 16; + this.addMixIn(unit -> { - if ( - unit.absoluteStart().chunkX() != 0 || - unit.absoluteStart().chunkZ() < 0 || - unit.absoluteStart().chunkZ() > 16 + Point chunkStart = unit.absoluteStart(); + if( + chunkStart.chunkX() != 0 || + chunkStart.blockZ() + 16 <= 0 || + chunkStart.blockZ() >= finishEnd ) return; - Point start = unit.absoluteStart().withY(height); + GeneratorUtils.foreachXZ(unit, point -> { + Block ground = groundBlockAt(point.blockZ(), finishStart, finishEnd); + if(ground != null) unit.modifier().setBlock(point.withY(height), ground); + }); - if(unit.absoluteStart().chunkZ() % 2 == 1) { - Obstacle.getRandomObstacle().generate(unit, start); - return; + for(int i = 0; i < obstacleCount; i++) { + int obstacleZ = START_LENGTH + i * PERIOD; + if(obstacleZ < chunkStart.blockZ() || obstacleZ >= chunkStart.blockZ() + 16) continue; + + GenerationUnit fork = unit.fork( + new Vec(chunkStart.blockX(), height - 8, obstacleZ), + new Vec(chunkStart.blockX() + 16, height + 16, obstacleZ + Obstacle.LENGTH) + ); + Obstacle.getRandomObstacle().generate(fork, new Vec(chunkStart.blockX(), height, obstacleZ)); } - - BiConsumer setBlock = (Point point, Block block) -> unit.modifier().setBlock(point.withY(height), block); - - if(unit.absoluteStart().chunkZ() == 0) { - GeneratorUtils.foreachXZ(unit, point -> setBlock.accept(point, Block.AMETHYST_BLOCK)); - return; - } - - if(unit.absoluteStart().chunkZ() == 16) { - GeneratorUtils.foreachXZ(unit, point -> setBlock.accept(point, Block.DIAMOND_BLOCK)); - return; - } - - GeneratorUtils.foreachXZ(unit, point -> setBlock.accept(point, BlockPallet.GROUND.rnd())); }); } + + private static Block groundBlockAt(int z, int finishStart, int finishEnd) { + if(z < 0 || z >= finishEnd) return null; + if(z < START_LENGTH) return Block.AMETHYST_BLOCK; + if(z >= finishStart) return Block.DIAMOND_BLOCK; + + int relative = (z - START_LENGTH) % PERIOD; + return relative >= Obstacle.LENGTH ? BlockPallet.GROUND.rnd() : null; + } } diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/PlayerRaceFactory.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/PlayerRaceFactory.java index 8f82310..838dfaa 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/PlayerRaceFactory.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/PlayerRaceFactory.java @@ -1,8 +1,10 @@ package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace; import eu.mhsl.minenet.minigames.instance.game.Game; +import eu.mhsl.minenet.minigames.instance.game.stateless.config.ConfigManager; import eu.mhsl.minenet.minigames.instance.game.stateless.config.GameFactory; import eu.mhsl.minenet.minigames.instance.game.stateless.config.Option; +import eu.mhsl.minenet.minigames.instance.game.stateless.config.common.NumericOption; import eu.mhsl.minenet.minigames.instance.room.Room; import eu.mhsl.minenet.minigames.message.component.TranslatedComponent; import net.minestom.server.item.Material; @@ -15,9 +17,15 @@ public class PlayerRaceFactory implements GameFactory { return TranslatedComponent.byId("game_PlayerRace#name"); } + @Override + public ConfigManager configuration() { + return new ConfigManager() + .addOption(new NumericOption("length", Material.RAIL, TranslatedComponent.byId("optionCommon#length"), 8, 12, 16, 20)); + } + @Override public Game manufacture(Room parent, Map> configuration) throws Exception { - return new PlayerRace().setParent(parent); + return new PlayerRace(configuration.get("length").getAsInt()).setParent(parent); } @Override diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/BambooThicket.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/BambooThicket.java new file mode 100644 index 0000000..e7dbaca --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/BambooThicket.java @@ -0,0 +1,34 @@ +package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace.obstacles; + +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.generator.GenerationUnit; + +import java.util.Random; + +/** + * Dichter Bambushain: Die dünnen Halme haben kleine Hitboxen — + * wer sich geschickt durchschlängelt, verliert kaum Tempo. + */ +class BambooThicket extends Obstacle { + @Override + public void generate(GenerationUnit unit, Point start) { + Random rnd = seededRandom(start); + + forEachColumn(start, point -> { + unit.modifier().setBlock(point.withY(start.blockY()), rnd.nextInt(3) == 0 ? Block.PODZOL : Block.GRASS_BLOCK); + + int z = relZ(point, start); + if(z < 2 || z > 13 || rnd.nextInt(100) >= 45) return; + + int height = 3 + rnd.nextInt(3); + for(int y = 1; y <= height; y++) { + String leaves = y == height ? "large" : y == height - 1 ? "small" : "none"; + unit.modifier().setBlock( + point.withY(start.blockY() + y), + Block.BAMBOO.withProperty("age", "1").withProperty("leaves", leaves) + ); + } + }); + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/BasaltPillars.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/BasaltPillars.java new file mode 100644 index 0000000..fdf6208 --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/BasaltPillars.java @@ -0,0 +1,47 @@ +package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace.obstacles; + +import de.articdive.jnoise.JNoise; +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.generator.GenerationUnit; + +import java.util.Random; + +/** + * Basaltdeltas-Säulenfeld: Hohe Basaltsäulen mit Seelenlaternen bilden + * einen düsteren Hindernisparcours zum Durchkurven. + */ +class BasaltPillars extends Obstacle { + private final JNoise ground = JNoise.newBuilder() + .fastSimplex() + .setFrequency(0.13) + .build(); + + @Override + public void generate(GenerationUnit unit, Point start) { + Random rnd = seededRandom(start); + + forEachColumn(start, point -> { + double noise = this.ground.getNoise(point.blockX(), point.blockZ()); + Block floor = noise > 0.4 ? Block.SOUL_SOIL : noise < -0.4 ? Block.BASALT : Block.BLACKSTONE; + unit.modifier().setBlock(point.withY(start.blockY()), floor); + }); + + for(int i = 0; i < 16; i++) { + int x = rnd.nextInt(16); + int z = 2 + rnd.nextInt(12); + int height = 2 + rnd.nextInt(3); + boolean thick = rnd.nextInt(3) == 0 && x < 15; + Block pillar = rnd.nextInt(3) == 0 ? Block.POLISHED_BASALT : Block.BASALT; + for(int y = 1; y <= height; y++) { + unit.modifier().setBlock(start.add(x, y, z), pillar); + if(thick) { + unit.modifier().setBlock(start.add(x + 1, y, z), pillar); + } + } + if(rnd.nextInt(3) == 0) { + unit.modifier().setBlock(start.add(x, height + 1, z), Block.SOUL_LANTERN); + } + } + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/BerryPatch.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/BerryPatch.java new file mode 100644 index 0000000..b40fcb0 --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/BerryPatch.java @@ -0,0 +1,33 @@ +package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace.obstacles; + +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.generator.GenerationUnit; + +import java.util.Random; + +/** + * Süßbeerenfeld: Die Büsche bremsen stark. Ein geschwungener Trampelpfad + * führt hindurch, ist aber in der Mitte selbst zugewuchert — + * ganz ohne Gestrüpp kommt hier niemand durch. + */ +class BerryPatch extends Obstacle { + @Override + public void generate(GenerationUnit unit, Point start) { + Random rnd = seededRandom(start); + + forEachColumn(start, point -> { + int z = relZ(point, start); + int pathX = 7 + (int) Math.round(5 * Math.sin(z * Math.PI / 7.5)); + boolean onPath = relX(point, start) == pathX || relX(point, start) == pathX + 1; + boolean overgrown = z == 8; + + unit.modifier().setBlock(point.withY(start.blockY()), onPath ? Block.DIRT_PATH : Block.GRASS_BLOCK); + + boolean bush = onPath ? overgrown : z >= 1 && z <= 14 && rnd.nextInt(100) < 45; + if(bush) { + unit.modifier().setBlock(point.withY(start.blockY() + 1), Block.SWEET_BERRY_BUSH.withProperty("age", "3")); + } + }); + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/BoulderTrench.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/BoulderTrench.java new file mode 100644 index 0000000..b4045a2 --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/BoulderTrench.java @@ -0,0 +1,44 @@ +package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace.obstacles; + +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.generator.GenerationUnit; + +import java.util.Random; + +/** + * Ausgetrocknetes Flussbett: Ein zwei Blöcke tiefer Graben quer zur Strecke, + * auf dessen Grund bemooste Findlinge liegen. Rein springen, durchkämpfen, rausklettern. + */ +class BoulderTrench extends Obstacle { + private static final int[] DEPTHS = {0, 0, 0, 0, 0, 1, 2, 2, 2, 2, 1, 0, 0, 0, 0, 0}; + + @Override + public void generate(GenerationUnit unit, Point start) { + Random rnd = seededRandom(start); + + forEachColumn(start, point -> { + int depth = DEPTHS[relZ(point, start)]; + for(int y = start.blockY() - 2; y < start.blockY() - depth; y++) { + unit.modifier().setBlock(point.withY(y), Block.STONE); + } + Block floor = depth == 0 + ? Block.GRASS_BLOCK + : rnd.nextBoolean() ? Block.COARSE_DIRT : Block.GRAVEL; + unit.modifier().setBlock(point.withY(start.blockY() - depth), floor); + }); + + for(int i = 0; i < 10; i++) { + int x = rnd.nextInt(16); + int z = 6 + rnd.nextInt(4); + unit.modifier().setBlock(start.add(x, -1, z), rnd.nextBoolean() ? Block.MOSSY_COBBLESTONE : Block.COBBLESTONE); + if(rnd.nextInt(3) == 0) { + unit.modifier().setBlock(start.add(x, 0, z), Block.MOSSY_COBBLESTONE); + } + } + + for(int i = 0; i < 4; i++) { + unit.modifier().setBlock(start.add(rnd.nextInt(16), -1, 6 + rnd.nextInt(4)), Block.COBWEB); + } + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/ChorusGrove.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/ChorusGrove.java new file mode 100644 index 0000000..e924c53 --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/ChorusGrove.java @@ -0,0 +1,70 @@ +package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace.obstacles; + +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.generator.GenerationUnit; + +import java.util.Random; + +/** + * End-Ruine: Zwei Endstein-Ziegelmauern (nicht überspringbar) mit versetzten, + * schmalen Durchgängen, dazwischen ein dichtes Chorus-Dickicht und + * Purpur-Trümmer. Passt zur End-Dimension des Spiels. + */ +class ChorusGrove extends Obstacle { + @Override + public void generate(GenerationUnit unit, Point start) { + Random rnd = seededRandom(start); + + forEachColumn(start, point -> { + Block floor = rnd.nextInt(14) == 0 ? Block.PURPUR_BLOCK : Block.END_STONE; + unit.modifier().setBlock(point.withY(start.blockY()), floor); + }); + + int[] rows = {5, 10}; + for(int i = 0; i < rows.length; i++) { + int gapStart = i % 2 == 0 ? 1 + rnd.nextInt(4) : 10 + rnd.nextInt(4); + + boolean[] hasWall = new boolean[16]; + for(int x = 0; x < 16; x++) { + hasWall[x] = x < gapStart || x >= gapStart + 2; + } + + for(int x = 0; x < 16; x++) { + if(!hasWall[x]) continue; + boolean westConnected = x > 0 && hasWall[x - 1]; + boolean eastConnected = x < 15 && hasWall[x + 1]; + Block wall = Block.END_STONE_BRICK_WALL + .withProperty("west", westConnected ? "low" : "none") + .withProperty("east", eastConnected ? "low" : "none") + .withProperty("up", String.valueOf(!westConnected || !eastConnected || x % 4 == 0)); + unit.modifier().setBlock(start.add(x, 1, rows[i]), wall); + + if(rnd.nextInt(6) == 0) { + unit.modifier().setBlock(start.add(x, 2, rows[i]), Block.END_ROD); + } + } + } + + for(int i = 0; i < 15; i++) { + int x = rnd.nextInt(16); + int z = 2 + rnd.nextInt(12); + if(z == 5 || z == 10) continue; + int height = 1 + rnd.nextInt(3); + for(int y = 1; y <= height; y++) { + unit.modifier().setBlock(start.add(x, y, z), Block.CHORUS_PLANT); + } + unit.modifier().setBlock(start.add(x, height + 1, z), Block.CHORUS_FLOWER.withProperty("age", "5")); + } + + for(int i = 0; i < 5; i++) { + int x = rnd.nextInt(16); + int z = 2 + rnd.nextInt(12); + if(z == 5 || z == 10) continue; + unit.modifier().setBlock(start.add(x, 1, z), Block.PURPUR_BLOCK); + if(rnd.nextBoolean()) { + unit.modifier().setBlock(start.add(x, 2, z), Block.PURPUR_BLOCK); + } + } + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/CobwebGrove.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/CobwebGrove.java new file mode 100644 index 0000000..39f77e9 --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/CobwebGrove.java @@ -0,0 +1,49 @@ +package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace.obstacles; + +import de.articdive.jnoise.JNoise; +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.generator.GenerationUnit; + +import java.util.Random; + +/** + * Verlassener Fichtenhain: Spinnweben zwischen alten Baumstümpfen + * bremsen jeden, der nicht sauber um sie herumläuft. + */ +class CobwebGrove extends Obstacle { + private final JNoise soil = JNoise.newBuilder() + .fastSimplex() + .setFrequency(0.12) + .build(); + + @Override + public void generate(GenerationUnit unit, Point start) { + Random rnd = seededRandom(start); + + forEachColumn(start, point -> { + Block floor = this.soil.getNoise(point.blockX(), point.blockZ()) > 0 ? Block.PODZOL : Block.COARSE_DIRT; + unit.modifier().setBlock(point.withY(start.blockY()), floor); + + int z = relZ(point, start); + if(z < 2 || z > 13) return; + + int roll = rnd.nextInt(100); + if(roll < 28) { + unit.modifier().setBlock(point.withY(start.blockY() + 1), Block.COBWEB); + if(roll < 8) unit.modifier().setBlock(point.withY(start.blockY() + 2), Block.COBWEB); + } else if(roll < 36) { + unit.modifier().setBlock(point.withY(start.blockY() + 1), Block.FERN); + } + }); + + for(int i = 0; i < 4; i++) { + int x = rnd.nextInt(16); + int z = 3 + rnd.nextInt(10); + int height = 1 + rnd.nextInt(2); + for(int y = 1; y <= height; y++) { + unit.modifier().setBlock(start.add(x, y, z), Block.SPRUCE_LOG); + } + } + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/DripstoneSpikes.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/DripstoneSpikes.java new file mode 100644 index 0000000..86978da --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/DripstoneSpikes.java @@ -0,0 +1,51 @@ +package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace.obstacles; + +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.generator.GenerationUnit; + +import java.util.Random; + +/** + * Tropfsteinkamm: Drei Zackenreihen aus Dripstone-Sockeln mit Stalagmiten + * obendrauf sind unpassierbar — nur die versetzten Lücken führen hindurch. + * Dazwischen ragen weitere Stalagmiten aus dem Boden. + */ +class DripstoneSpikes extends Obstacle { + @Override + public void generate(GenerationUnit unit, Point start) { + Random rnd = seededRandom(start); + + forEachColumn(start, point -> + unit.modifier().setBlock(point.withY(start.blockY()), rnd.nextInt(6) == 0 ? Block.STONE : Block.DRIPSTONE_BLOCK)); + + int[] rows = {4, 8, 12}; + for(int i = 0; i < rows.length; i++) { + int gapStart = i % 2 == 0 ? 2 + rnd.nextInt(4) : 9 + rnd.nextInt(4); + for(int x = 0; x < 16; x++) { + if(x >= gapStart && x < gapStart + 2) continue; + unit.modifier().setBlock(start.add(x, 1, rows[i]), Block.DRIPSTONE_BLOCK); + this.placeSpike(unit, start.add(0, 1, 0), x, rows[i], 1 + rnd.nextInt(2)); + } + } + + for(int i = 0; i < 12; i++) { + int x = rnd.nextInt(16); + int z = 2 + rnd.nextInt(12); + if(z == 4 || z == 8 || z == 12) continue; + this.placeSpike(unit, start, x, z, 1 + rnd.nextInt(3)); + } + } + + private void placeSpike(GenerationUnit unit, Point start, int x, int z, int height) { + for(int y = 1; y <= height; y++) { + String thickness = y == height ? "tip" : y == height - 1 ? "frustum" : "base"; + unit.modifier().setBlock( + start.add(x, y, z), + Block.POINTED_DRIPSTONE + .withProperty("thickness", thickness) + .withProperty("vertical_direction", "up") + ); + } + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/GlassPaneWeave.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/GlassPaneWeave.java new file mode 100644 index 0000000..3ac8ea2 --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/GlassPaneWeave.java @@ -0,0 +1,40 @@ +package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace.obstacles; + +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.generator.GenerationUnit; + +import java.util.Random; + +/** + * Gläsernes Labyrinth auf Quarzboden: Fast unsichtbare Glasscheiben-Wände + * mit versetzten Lücken — wer rast, läuft dagegen. + */ +class GlassPaneWeave extends Obstacle { + @Override + public void generate(GenerationUnit unit, Point start) { + Random rnd = seededRandom(start); + + forEachColumn(start, point -> + unit.modifier().setBlock(point.withY(start.blockY()), rnd.nextInt(5) == 0 ? Block.CALCITE : Block.QUARTZ_BLOCK)); + + int[] rows = {4, 8, 12}; + for(int i = 0; i < rows.length; i++) { + int gapStart = i % 2 == 0 ? 2 + rnd.nextInt(3) : 11 + rnd.nextInt(3); + + boolean[] hasPane = new boolean[16]; + for(int x = 0; x < 16; x++) { + hasPane[x] = x < gapStart || x >= gapStart + 2; + } + + for(int x = 0; x < 16; x++) { + if(!hasPane[x]) continue; + Block pane = (rnd.nextInt(6) == 0 ? Block.GRAY_STAINED_GLASS_PANE : Block.WHITE_STAINED_GLASS_PANE) + .withProperty("west", String.valueOf(x > 0 && hasPane[x - 1])) + .withProperty("east", String.valueOf(x < 15 && hasPane[x + 1])); + unit.modifier().setBlock(start.add(x, 1, rows[i]), pane); + unit.modifier().setBlock(start.add(x, 2, rows[i]), pane); + } + } + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/HayHurdles.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/HayHurdles.java new file mode 100644 index 0000000..c748853 --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/HayHurdles.java @@ -0,0 +1,39 @@ +package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace.obstacles; + +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.generator.GenerationUnit; + +import java.util.Random; + +/** + * Bauernhof-Hürdenlauf: Vier Reihen Strohballen — die flachen müssen ohne + * Lücke übersprungen werden, die hohen zwingen durch eine schmale Gasse. + */ +class HayHurdles extends Obstacle { + @Override + public void generate(GenerationUnit unit, Point start) { + Random rnd = seededRandom(start); + + forEachColumn(start, point -> { + unit.modifier().setBlock(point.withY(start.blockY()), Block.GRASS_BLOCK); + if(relZ(point, start) % 3 != 0 && rnd.nextInt(8) == 0) { + unit.modifier().setBlock(point.withY(start.blockY() + 1), Block.SHORT_GRASS); + } + }); + + int[] rows = {3, 6, 9, 12}; + for(int i = 0; i < rows.length; i++) { + boolean tall = i % 2 == 1; + int gapStart = rnd.nextInt(14); + for(int x = 0; x < 16; x++) { + if(tall && (x == gapStart || x == gapStart + 1)) continue; + Block block = rnd.nextInt(12) == 0 ? Block.CARVED_PUMPKIN : Block.HAY_BLOCK; + unit.modifier().setBlock(start.add(x, 1, rows[i]), block); + if(tall) { + unit.modifier().setBlock(start.add(x, 2, rows[i]), Block.HAY_BLOCK); + } + } + } + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/HoneyField.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/HoneyField.java new file mode 100644 index 0000000..2833dbb --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/HoneyField.java @@ -0,0 +1,38 @@ +package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace.obstacles; + +import de.articdive.jnoise.JNoise; +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.generator.GenerationUnit; + +import java.util.Random; + +/** + * Honigwaben-Feld: klebrige Honigflecken im Boden bremsen, zwei Honigwände + * mit versetzten Durchlässen zwingen in eine Zickzack-Linie. + */ +class HoneyField extends Obstacle { + private final JNoise sticky = JNoise.newBuilder() + .fastSimplex() + .setFrequency(0.2) + .build(); + + @Override + public void generate(GenerationUnit unit, Point start) { + Random rnd = seededRandom(start); + + forEachColumn(start, point -> { + boolean stickyFloor = this.sticky.getNoise(point.blockX(), point.blockZ()) > 0.35; + unit.modifier().setBlock(point.withY(start.blockY()), stickyFloor ? Block.HONEY_BLOCK : Block.HONEYCOMB_BLOCK); + }); + + for(int wallZ : new int[]{5, 10}) { + int gapStart = wallZ == 5 ? 1 + rnd.nextInt(5) : 8 + rnd.nextInt(5); + for(int x = 0; x < 16; x++) { + if(x >= gapStart && x < gapStart + 2) continue; + unit.modifier().setBlock(start.add(x, 1, wallZ), Block.HONEY_BLOCK); + unit.modifier().setBlock(start.add(x, 2, wallZ), Block.HONEY_BLOCK); + } + } + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/IceRink.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/IceRink.java new file mode 100644 index 0000000..719bf13 --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/IceRink.java @@ -0,0 +1,57 @@ +package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace.obstacles; + +import de.articdive.jnoise.JNoise; +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.generator.GenerationUnit; + +import java.util.Random; + +/** + * Spiegelglatte Eisfläche: Blue-Ice-Streifen beschleunigen unkontrolliert, + * zwei Eiswände mit versetzten Durchlässen müssen rutschend getroffen werden. + */ +class IceRink extends Obstacle { + private final JNoise streaks = JNoise.newBuilder() + .fastSimplex() + .setFrequency(0.15) + .build(); + + @Override + public void generate(GenerationUnit unit, Point start) { + Random rnd = seededRandom(start); + + forEachColumn(start, point -> { + double noise = this.streaks.getNoise(point.blockX(), point.blockZ()); + Block floor = noise > 0.45 ? Block.BLUE_ICE : noise < -0.55 ? Block.ICE : Block.PACKED_ICE; + unit.modifier().setBlock(point.withY(start.blockY()), floor); + + if(relZ(point, start) >= 2 && relZ(point, start) <= 13 && rnd.nextInt(14) == 0) { + unit.modifier().setBlock( + point.withY(start.blockY() + 1), + Block.SNOW.withProperty("layers", String.valueOf(1 + rnd.nextInt(2))) + ); + } + }); + + for(int wallZ : new int[]{5, 10}) { + int gapStart = wallZ == 5 ? 1 + rnd.nextInt(4) : 9 + rnd.nextInt(4); + for(int x = 0; x < 16; x++) { + if(x >= gapStart && x < gapStart + 3) continue; + unit.modifier().setBlock(start.add(x, 1, wallZ), Block.PACKED_ICE); + unit.modifier().setBlock(start.add(x, 2, wallZ), Block.ICE); + if(rnd.nextInt(4) == 0) { + unit.modifier().setBlock(start.add(x, 3, wallZ), Block.SNOW.withProperty("layers", "1")); + } + } + } + + for(int i = 0; i < 5; i++) { + int x = rnd.nextInt(16); + int z = 2 + rnd.nextInt(12); + if(z == 5 || z == 10) continue; + unit.modifier().setBlock(start.add(x, 1, z), Block.PACKED_ICE); + unit.modifier().setBlock(start.add(x, 2, z), Block.SNOW.withProperty("layers", "1")); + } + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/LadderWall.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/LadderWall.java new file mode 100644 index 0000000..4e2e9ab --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/LadderWall.java @@ -0,0 +1,37 @@ +package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace.obstacles; + +import eu.mhsl.minenet.minigames.world.BlockPallet; +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.generator.GenerationUnit; + +import java.util.Random; + +/** + * Alte Festungsmauer quer über die Strecke: Drei Blöcke hoch, zu hoch zum + * Springen — auf beiden Seiten hängen Leitern zum Drüberklettern. + */ +class LadderWall extends Obstacle { + @Override + public void generate(GenerationUnit unit, Point start) { + Random rnd = seededRandom(start); + + forEachColumn(start, point -> + unit.modifier().setBlock(point.withY(start.blockY()), BlockPallet.STONE.rnd())); + + for(int x = 0; x < 16; x++) { + for(int y = 1; y <= 3; y++) { + for(int z = 7; z <= 8; z++) { + Block block = rnd.nextInt(3) == 0 ? Block.MOSSY_STONE_BRICKS : Block.STONE_BRICKS; + unit.modifier().setBlock(start.add(x, y, z), block); + } + unit.modifier().setBlock(start.add(x, y, 6), Block.LADDER.withProperty("facing", "north")); + unit.modifier().setBlock(start.add(x, y, 9), Block.LADDER.withProperty("facing", "south")); + } + + if(x % 5 == 2) { + unit.modifier().setBlock(start.add(x, 4, 7), Block.LANTERN); + } + } + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/LeafHedges.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/LeafHedges.java new file mode 100644 index 0000000..478de31 --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/LeafHedges.java @@ -0,0 +1,39 @@ +package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace.obstacles; + +import eu.mhsl.minenet.minigames.world.BlockPallet; +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.generator.GenerationUnit; + +import java.util.Random; + +/** + * Heckengarten: Zwei Blöcke hohe Azaleen-Hecken mit versetzten Durchgängen, + * dazwischen blühen Blumen — hübsch, aber im Weg. + */ +class LeafHedges extends Obstacle { + @Override + public void generate(GenerationUnit unit, Point start) { + Random rnd = seededRandom(start); + + forEachColumn(start, point -> { + unit.modifier().setBlock(point.withY(start.blockY()), Block.GRASS_BLOCK); + int z = relZ(point, start); + if(z % 4 != 0 && z >= 1 && z <= 14 && rnd.nextInt(10) == 0) { + unit.modifier().setBlock(point.withY(start.blockY() + 1), BlockPallet.FLOWER.rnd()); + } + }); + + int[] rows = {4, 8, 12}; + for(int i = 0; i < rows.length; i++) { + int gapStart = i % 2 == 0 ? 11 + rnd.nextInt(3) : 1 + rnd.nextInt(3); + for(int x = 0; x < 16; x++) { + if(x >= gapStart && x < gapStart + 2) continue; + Block leaves = (rnd.nextInt(10) < 3 ? Block.FLOWERING_AZALEA_LEAVES : Block.AZALEA_LEAVES) + .withProperty("persistent", "true"); + unit.modifier().setBlock(start.add(x, 1, rows[i]), leaves); + unit.modifier().setBlock(start.add(x, 2, rows[i]), leaves); + } + } + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/MudPools.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/MudPools.java new file mode 100644 index 0000000..e7c1abd --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/MudPools.java @@ -0,0 +1,44 @@ +package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace.obstacles; + +import de.articdive.jnoise.JNoise; +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.generator.GenerationUnit; + +import java.util.Random; + +/** + * Mangroven-Schlammbecken: Die Becken liegen einen Block tief — man fällt in + * den zähen Schlamm und muss sich wieder herauskämpfen, während + * Mangrovenwurzeln die trockenen Umwege versperren. + */ +class MudPools extends Obstacle { + private final JNoise pools = JNoise.newBuilder() + .fastSimplex() + .setFrequency(0.11) + .build(); + + @Override + public void generate(GenerationUnit unit, Point start) { + Random rnd = seededRandom(start); + + forEachColumn(start, point -> { + int z = relZ(point, start); + double noise = this.pools.getNoise(point.blockX(), point.blockZ()); + boolean pool = z >= 1 && z <= 14 && noise > 0.05; + + if(pool) { + unit.modifier().setBlock(point.withY(start.blockY() - 1), Block.MUD); + return; + } + + Block floor = noise > -0.3 ? Block.MUDDY_MANGROVE_ROOTS : Block.GRASS_BLOCK; + unit.modifier().setBlock(point.withY(start.blockY() - 1), Block.MUD); + unit.modifier().setBlock(point.withY(start.blockY()), floor); + + if(z >= 2 && z <= 13 && rnd.nextInt(6) == 0) { + unit.modifier().setBlock(point.withY(start.blockY() + 1), Block.MANGROVE_ROOTS); + } + }); + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/Mudpit.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/Mudpit.java index 763a01f..5492106 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/Mudpit.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/Mudpit.java @@ -1,8 +1,6 @@ package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace.obstacles; import de.articdive.jnoise.JNoise; -import eu.mhsl.minenet.minigames.util.GeneratorUtils; -import net.minestom.server.coordinate.CoordConversion; import net.minestom.server.coordinate.Point; import net.minestom.server.instance.block.Block; import net.minestom.server.instance.generator.GenerationUnit; @@ -15,8 +13,8 @@ class Mudpit extends Obstacle { @Override public void generate(GenerationUnit unit, Point start) { - GeneratorUtils.foreachXZ(unit, point -> { - double relative = CoordConversion.globalToSectionRelative(point.blockZ()); + forEachColumn(start, point -> { + double relative = relZ(point, start); double modifier = -1.5 * Math.cos(relative * Math.PI/7.5) + 1.5; double heightAtBlock = start.blockY() + this.curves.getNoise(point.blockX(), point.blockZ()) * modifier; unit.modifier().setBlock(point.withY(heightAtBlock), Block.SOUL_SAND); diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/MushroomRoofs.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/MushroomRoofs.java new file mode 100644 index 0000000..26bc95c --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/MushroomRoofs.java @@ -0,0 +1,41 @@ +package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace.obstacles; + +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.generator.GenerationUnit; + +import java.util.Random; + +/** + * Riesenpilz-Wäldchen auf Myzel: Die breiten Kappen hängen so tief, dass man + * darunter nur kriechend durchkommt — oder außen eng um die Stiele kurvt. + */ +class MushroomRoofs extends Obstacle { + @Override + public void generate(GenerationUnit unit, Point start) { + Random rnd = seededRandom(start); + + forEachColumn(start, point -> { + unit.modifier().setBlock(point.withY(start.blockY()), Block.MYCELIUM); + int z = relZ(point, start); + if(z >= 1 && z <= 14 && rnd.nextInt(20) == 0) { + unit.modifier().setBlock(point.withY(start.blockY() + 1), rnd.nextBoolean() ? Block.RED_MUSHROOM : Block.BROWN_MUSHROOM); + } + }); + + for(int stemZ : new int[]{4, 8, 12}) { + int x = 4 + rnd.nextInt(8); + Block cap = rnd.nextBoolean() ? Block.RED_MUSHROOM_BLOCK : Block.BROWN_MUSHROOM_BLOCK; + + unit.modifier().setBlock(start.add(x, 1, stemZ), Block.MUSHROOM_STEM); + + for(int capX = x - 3; capX <= x + 3; capX++) { + for(int capZ = stemZ - 1; capZ <= stemZ + 1; capZ++) { + if(capX < 0 || capX > 15 || capZ < 0 || capZ > 15) continue; + if(Math.abs(capX - x) == 3 && Math.abs(capZ - stemZ) == 1) continue; + unit.modifier().setBlock(start.add(capX, 2, capZ), cap); + } + } + } + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/Obstacle.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/Obstacle.java index e95f618..9068a10 100644 --- a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/Obstacle.java +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/Obstacle.java @@ -1,18 +1,66 @@ package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace.obstacles; +import eu.mhsl.minenet.minigames.util.GeneratorUtils; import net.minestom.server.coordinate.Point; import net.minestom.server.instance.generator.GenerationUnit; import java.util.List; +import java.util.Random; import java.util.concurrent.ThreadLocalRandom; +import java.util.function.Consumer; public abstract class Obstacle { - @SuppressWarnings("StaticInitializerReferencesSubClass") - private static final List obstacles = List.of(new Mudpit()); + public static final int LENGTH = 16; + @SuppressWarnings("StaticInitializerReferencesSubClass") + private static final List obstacles = List.of( + new Mudpit(), + new IceRink(), + new HoneyField(), + new CobwebGrove(), + new BerryPatch(), + new WaterChannel(), + new HayHurdles(), + new SlimePatches(), + new MudPools(), + new PowderSnowField(), + new DripstoneSpikes(), + new ChorusGrove(), + new BasaltPillars(), + new WallSlalom(), + new LeafHedges(), + new MushroomRoofs(), + new SandDunes(), + new LadderWall(), + new GlassPaneWeave(), + new BoulderTrench(), + new BambooThicket() + ); + + /** + * Generiert das Hindernis ab {@code start} (16 breit, {@link #LENGTH} lang). + * {@code unit} kann ein Fork sein, der über Chunk-Grenzen reicht — daher nie + * über die Unit-Bounds iterieren, sondern über {@link #forEachColumn(Point, Consumer)}. + */ public abstract void generate(GenerationUnit unit, Point start); public static Obstacle getRandomObstacle() { return obstacles.get(ThreadLocalRandom.current().nextInt(obstacles.size())); } + + protected static void forEachColumn(Point start, Consumer callback) { + GeneratorUtils.foreachXZ(start, start.add(15, 0, LENGTH - 1), callback); + } + + protected static int relX(Point point, Point start) { + return point.blockX() - start.blockX(); + } + + protected static int relZ(Point point, Point start) { + return point.blockZ() - start.blockZ(); + } + + protected static Random seededRandom(Point start) { + return new Random(start.blockX() * 341873128712L + start.blockZ() * 132897987541L); + } } diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/PowderSnowField.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/PowderSnowField.java new file mode 100644 index 0000000..d566f35 --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/PowderSnowField.java @@ -0,0 +1,46 @@ +package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace.obstacles; + +import de.articdive.jnoise.JNoise; +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.generator.GenerationUnit; + +import java.util.Random; + +/** + * Verschneites Feld voller Pulverschnee-Verwehungen: Sie liegen auf dem Boden + * statt darunter — man watet stark gebremst hindurch, bleibt aber nie stecken + * (aus einer Grube käme man ohne Lederstiefel nicht mehr heraus). + * Die geschwungene Packeis-Spur ist schnell, aber rutschig — und selbst dort + * lauern vereinzelte Verwehungen. + */ +class PowderSnowField extends Obstacle { + private final JNoise drifts = JNoise.newBuilder() + .fastSimplex() + .setFrequency(0.14) + .build(); + + @Override + public void generate(GenerationUnit unit, Point start) { + Random rnd = seededRandom(start); + + forEachColumn(start, point -> { + int z = relZ(point, start); + int laneX = 7 + (int) Math.round(3 * Math.sin(z * Math.PI / 7.5)); + boolean onLane = relX(point, start) == laneX || relX(point, start) == laneX + 1; + double noise = this.drifts.getNoise(point.blockX(), point.blockZ()); + boolean drift = z >= 2 && z <= 13 && noise > (onLane ? 0.45 : 0.0); + + unit.modifier().setBlock(point.withY(start.blockY()), onLane ? Block.PACKED_ICE : Block.SNOW_BLOCK); + + if(drift) { + unit.modifier().setBlock(point.withY(start.blockY() + 1), Block.POWDER_SNOW); + if(noise > 0.6) { + unit.modifier().setBlock(point.withY(start.blockY() + 2), Block.POWDER_SNOW); + } + } else if(!onLane && rnd.nextInt(6) == 0) { + unit.modifier().setBlock(point.withY(start.blockY() + 1), Block.SNOW.withProperty("layers", "1")); + } + }); + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/SandDunes.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/SandDunes.java new file mode 100644 index 0000000..7e87c93 --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/SandDunes.java @@ -0,0 +1,44 @@ +package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace.obstacles; + +import de.articdive.jnoise.JNoise; +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.generator.GenerationUnit; + +import java.util.Random; + +/** + * Wüstendünen: Sanfte Sandhügel kosten beim Rauflaufen Sprungkraft und Tempo, + * vertrocknete Büsche krönen die Kämme. An den Chunk-Rändern flacht das Feld ab. + */ +class SandDunes extends Obstacle { + private final JNoise dunes = JNoise.newBuilder() + .fastSimplex() + .setFrequency(0.08) + .build(); + + @Override + public void generate(GenerationUnit unit, Point start) { + Random rnd = seededRandom(start); + + forEachColumn(start, point -> { + double taper = -1.25 * Math.cos(relZ(point, start) * Math.PI / 7.5) + 1.25; + double noise = (this.dunes.getNoise(point.blockX(), point.blockZ()) + 1) / 2; + int top = start.blockY() + (int) Math.round(noise * taper); + + for(int y = start.blockY() - 1; y < top; y++) { + unit.modifier().setBlock(point.withY(y), Block.SANDSTONE); + } + unit.modifier().setBlock(point.withY(top), Block.SAND); + + if(top > start.blockY() + 1 && rnd.nextInt(14) == 0) { + unit.modifier().setBlock(point.withY(top + 1), Block.DEAD_BUSH); + } else if(top == start.blockY() && relZ(point, start) >= 2 && relZ(point, start) <= 13 && rnd.nextInt(22) == 0) { + int cactusHeight = 1 + rnd.nextInt(2); + for(int y = 1; y <= cactusHeight; y++) { + unit.modifier().setBlock(point.withY(top + y), Block.CACTUS); + } + } + }); + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/SlimePatches.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/SlimePatches.java new file mode 100644 index 0000000..fe253a9 --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/SlimePatches.java @@ -0,0 +1,34 @@ +package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace.obstacles; + +import de.articdive.jnoise.JNoise; +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.generator.GenerationUnit; + +import java.util.Random; + +/** + * Schleimmorast: Der Großteil des Bodens ist Slime — bremst stark und lässt + * Springer unkontrolliert abprallen. Nur schmale Moos-Inseln bieten Halt. + */ +class SlimePatches extends Obstacle { + private final JNoise blobs = JNoise.newBuilder() + .fastSimplex() + .setFrequency(0.18) + .build(); + + @Override + public void generate(GenerationUnit unit, Point start) { + Random rnd = seededRandom(start); + + forEachColumn(start, point -> { + int z = relZ(point, start); + boolean slime = z >= 1 && z <= 14 && this.blobs.getNoise(point.blockX(), point.blockZ()) > -0.2; + unit.modifier().setBlock(point.withY(start.blockY()), slime ? Block.SLIME_BLOCK : Block.MOSS_BLOCK); + + if(!slime && z >= 2 && z <= 13 && rnd.nextInt(18) == 0) { + unit.modifier().setBlock(point.withY(start.blockY() + 1), Block.AZALEA); + } + }); + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/WallSlalom.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/WallSlalom.java new file mode 100644 index 0000000..634576a --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/WallSlalom.java @@ -0,0 +1,42 @@ +package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace.obstacles; + +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.generator.GenerationUnit; + +import java.util.Random; + +/** + * Mauer-Slalom: Steinmauern sind zu hoch zum Überspringen, die Durchgänge + * liegen abwechselnd links und rechts — volle Breite Zickzack. + */ +class WallSlalom extends Obstacle { + @Override + public void generate(GenerationUnit unit, Point start) { + Random rnd = seededRandom(start); + + forEachColumn(start, point -> + unit.modifier().setBlock(point.withY(start.blockY()), rnd.nextInt(4) == 0 ? Block.ANDESITE : Block.STONE)); + + int[] rows = {4, 8, 12}; + for(int i = 0; i < rows.length; i++) { + int gapStart = i % 2 == 0 ? 1 + rnd.nextInt(3) : 10 + rnd.nextInt(3); + + boolean[] hasWall = new boolean[16]; + for(int x = 0; x < 16; x++) { + hasWall[x] = x < gapStart || x >= gapStart + 2; + } + + for(int x = 0; x < 16; x++) { + if(!hasWall[x]) continue; + boolean westConnected = x > 0 && hasWall[x - 1]; + boolean eastConnected = x < 15 && hasWall[x + 1]; + Block wall = Block.STONE_BRICK_WALL + .withProperty("west", westConnected ? "low" : "none") + .withProperty("east", eastConnected ? "low" : "none") + .withProperty("up", String.valueOf(!westConnected || !eastConnected || x % 4 == 0)); + unit.modifier().setBlock(start.add(x, 1, rows[i]), wall); + } + } + } +} diff --git a/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/WaterChannel.java b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/WaterChannel.java new file mode 100644 index 0000000..02eec5b --- /dev/null +++ b/src/main/java/eu/mhsl/minenet/minigames/instance/game/stateless/types/playerRace/obstacles/WaterChannel.java @@ -0,0 +1,41 @@ +package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace.obstacles; + +import net.minestom.server.coordinate.Point; +import net.minestom.server.instance.block.Block; +import net.minestom.server.instance.generator.GenerationUnit; + +import java.util.Random; + +/** + * Breiter Wassergraben mit schmalen Sandufern: Fast der ganze Chunk muss + * durchschwommen werden, nur vereinzelte Seerosenblätter helfen Mutigen. + */ +class WaterChannel extends Obstacle { + private static final int[] DEPTHS = {0, 1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 1, 0, 0}; + + @Override + public void generate(GenerationUnit unit, Point start) { + Random rnd = seededRandom(start); + + forEachColumn(start, point -> { + int depth = DEPTHS[relZ(point, start)]; + if(depth == 0) { + unit.modifier().setBlock(point.withY(start.blockY()), rnd.nextInt(3) == 0 ? Block.SMOOTH_SANDSTONE : Block.SAND); + return; + } + + Block bottom = rnd.nextInt(5) == 0 ? Block.DARK_PRISMARINE : Block.PRISMARINE; + unit.modifier().setBlock(point.withY(start.blockY() - depth), bottom); + for(int y = start.blockY() - depth + 1; y <= start.blockY(); y++) { + unit.modifier().setBlock(point.withY(y), Block.WATER); + } + + if(depth >= 2 && rnd.nextInt(4) == 0) { + unit.modifier().setBlock(point.withY(start.blockY() - depth + 1), Block.SEAGRASS); + } + if(rnd.nextInt(20) == 0) { + unit.modifier().setBlock(point.withY(start.blockY() + 1), Block.LILY_PAD); + } + }); + } +}