added 20 obstacle types, adjustable track length and forked obstacle generation

This commit is contained in:
2026-07-18 22:13:49 +02:00
parent 57cd5acdfd
commit 3bdc4113c0
25 changed files with 970 additions and 35 deletions
@@ -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.instance.game.stateless.StatelessGame;
import eu.mhsl.minenet.minigames.score.LastWinsScore; import eu.mhsl.minenet.minigames.score.LastWinsScore;
import net.minestom.server.coordinate.Pos; import net.minestom.server.coordinate.Pos;
import net.minestom.server.instance.batch.AbsoluteBlockBatch;
import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.Block;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
public class PlayerRace extends StatelessGame { public class PlayerRace extends StatelessGame {
public PlayerRace() { public PlayerRace(int obstacleCount) {
super(Dimension.THE_END.key, "PlayerRace", new LastWinsScore()); super(Dimension.THE_END.key, "PlayerRace", new LastWinsScore());
this.setGenerator(new PlayerRaceChunkgenerator(64)); this.setGenerator(new PlayerRaceChunkgenerator(64, obstacleCount));
} }
@Override @Override
@@ -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.featureEnriched.ValeGenerator;
import eu.mhsl.minenet.minigames.world.generator.terrain.BaseGenerator; import eu.mhsl.minenet.minigames.world.generator.terrain.BaseGenerator;
import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.generator.GenerationUnit;
import java.util.function.BiConsumer;
public class PlayerRaceChunkgenerator extends BaseGenerator { 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(); ValeGenerator vale = new ValeGenerator();
vale.setXShiftMultiplier(integer -> 0.5d); vale.setXShiftMultiplier(integer -> 0.5d);
vale.setHeightNoiseMultiplier(integer -> 2); vale.setHeightNoiseMultiplier(integer -> 2);
this.addMixIn(vale); this.addMixIn(vale);
int finishStart = START_LENGTH + obstacleCount * PERIOD;
int finishEnd = finishStart + 16;
this.addMixIn(unit -> { this.addMixIn(unit -> {
if ( Point chunkStart = unit.absoluteStart();
unit.absoluteStart().chunkX() != 0 || if(
unit.absoluteStart().chunkZ() < 0 || chunkStart.chunkX() != 0 ||
unit.absoluteStart().chunkZ() > 16 chunkStart.blockZ() + 16 <= 0 ||
chunkStart.blockZ() >= finishEnd
) return; ) 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) { for(int i = 0; i < obstacleCount; i++) {
Obstacle.getRandomObstacle().generate(unit, start); int obstacleZ = START_LENGTH + i * PERIOD;
return; 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<Point, Block> 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;
}
} }
@@ -1,8 +1,10 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace; 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.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.GameFactory;
import eu.mhsl.minenet.minigames.instance.game.stateless.config.Option; 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.instance.room.Room;
import eu.mhsl.minenet.minigames.message.component.TranslatedComponent; import eu.mhsl.minenet.minigames.message.component.TranslatedComponent;
import net.minestom.server.item.Material; import net.minestom.server.item.Material;
@@ -15,9 +17,15 @@ public class PlayerRaceFactory implements GameFactory {
return TranslatedComponent.byId("game_PlayerRace#name"); 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 @Override
public Game manufacture(Room parent, Map<String, Option<?>> configuration) throws Exception { public Game manufacture(Room parent, Map<String, Option<?>> configuration) throws Exception {
return new PlayerRace().setParent(parent); return new PlayerRace(configuration.get("length").getAsInt()).setParent(parent);
} }
@Override @Override
@@ -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)
);
}
});
}
}
@@ -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);
}
}
}
}
@@ -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"));
}
});
}
}
@@ -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);
}
}
}
@@ -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);
}
}
}
}
@@ -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);
}
}
}
}
@@ -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")
);
}
}
}
@@ -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);
}
}
}
}
@@ -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);
}
}
}
}
}
@@ -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);
}
}
}
}
@@ -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"));
}
}
}
@@ -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);
}
}
}
}
@@ -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);
}
}
}
}
@@ -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);
}
});
}
}
@@ -1,8 +1,6 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace.obstacles; package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace.obstacles;
import de.articdive.jnoise.JNoise; 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.coordinate.Point;
import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.generator.GenerationUnit; import net.minestom.server.instance.generator.GenerationUnit;
@@ -15,8 +13,8 @@ class Mudpit extends Obstacle {
@Override @Override
public void generate(GenerationUnit unit, Point start) { public void generate(GenerationUnit unit, Point start) {
GeneratorUtils.foreachXZ(unit, point -> { forEachColumn(start, point -> {
double relative = CoordConversion.globalToSectionRelative(point.blockZ()); double relative = relZ(point, start);
double modifier = -1.5 * Math.cos(relative * Math.PI/7.5) + 1.5; 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; double heightAtBlock = start.blockY() + this.curves.getNoise(point.blockX(), point.blockZ()) * modifier;
unit.modifier().setBlock(point.withY(heightAtBlock), Block.SOUL_SAND); unit.modifier().setBlock(point.withY(heightAtBlock), Block.SOUL_SAND);
@@ -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);
}
}
}
}
}
@@ -1,18 +1,66 @@
package eu.mhsl.minenet.minigames.instance.game.stateless.types.playerRace.obstacles; 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.coordinate.Point;
import net.minestom.server.instance.generator.GenerationUnit; import net.minestom.server.instance.generator.GenerationUnit;
import java.util.List; import java.util.List;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Consumer;
public abstract class Obstacle { public abstract class Obstacle {
@SuppressWarnings("StaticInitializerReferencesSubClass") public static final int LENGTH = 16;
private static final List<Obstacle> obstacles = List.of(new Mudpit());
@SuppressWarnings("StaticInitializerReferencesSubClass")
private static final List<Obstacle> 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 abstract void generate(GenerationUnit unit, Point start);
public static Obstacle getRandomObstacle() { public static Obstacle getRandomObstacle() {
return obstacles.get(ThreadLocalRandom.current().nextInt(obstacles.size())); return obstacles.get(ThreadLocalRandom.current().nextInt(obstacles.size()));
} }
protected static void forEachColumn(Point start, Consumer<Point> 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);
}
} }
@@ -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"));
}
});
}
}
@@ -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);
}
}
});
}
}
@@ -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);
}
});
}
}
@@ -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);
}
}
}
}
@@ -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);
}
});
}
}