added difficulty curve and themed stages from overworld to the end

- jumps start easy (2-3 blocks, rarely sideways/upwards) and get harder over the
  first 1000 jumps up to the previous difficulty (3-4 blocks, often sideways/upwards)
- block themes follow a full playthrough: meadow, forest, underground, caves, deepslate,
  nether portal, nether, fortress & bastion, stronghold, portal room, the end (from 1000)
- title and sound when a new stage is reached
- gold/diamond checkpoint markers unchanged

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
MineTecandClaude Opus 5.5 committed 2026-10-07 23:49:49 +02:00
1 parent 8e046c8603
commit 0faff2bd0c
2 files changed
+107 -13

No files matched your search

@@ -2,21 +2,80 @@ package eu.mhsl.minecraft.endlessjumper.generator;
import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.Block;
import java.util.ArrayList; import java.util.List;
import java.util.Random; import java.util.Random;
/**
* Block-Themen entlang des Fortschritts: einmal durch das komplette Spiel,
* von der Wiese über Untergrund, Nether und Stronghold bis ins End (ab Sprung 1000).
*/
public class BlockPalette { public class BlockPalette {
public static Block getRandom(int seed) { public record Stage(int from, String name, List<Block> blocks) {
Random r = new Random(seed); }
ArrayList<Block> blocks = new ArrayList<>();
blocks.add(Block.COPPER_BLOCK); public static final List<Stage> STAGES = List.of(
blocks.add(Block.CUT_COPPER); new Stage(0, "Wiese", List.of(
blocks.add(Block.WEATHERED_COPPER); Block.GRASS_BLOCK, Block.GRASS_BLOCK, Block.MOSS_BLOCK, Block.PODZOL, Block.ROOTED_DIRT
blocks.add(Block.WEATHERED_CUT_COPPER); )),
blocks.add(Block.OXIDIZED_COPPER); new Stage(75, "Wald", List.of(
blocks.add(Block.OXIDIZED_CUT_COPPER); Block.OAK_LOG, Block.BIRCH_LOG, Block.SPRUCE_LOG, Block.CHERRY_LOG,
Block.OAK_LEAVES, Block.BIRCH_LEAVES, Block.SPRUCE_LEAVES, Block.CHERRY_LEAVES
)),
new Stage(150, "Untergrund", List.of(
Block.DIRT, Block.COARSE_DIRT, Block.STONE, Block.STONE, Block.ANDESITE,
Block.GRANITE, Block.DIORITE, Block.COBBLESTONE
)),
new Stage(250, "Höhlen", List.of(
Block.STONE, Block.COAL_ORE, Block.IRON_ORE, Block.COPPER_ORE,
Block.GOLD_ORE, Block.REDSTONE_ORE, Block.LAPIS_ORE
)),
new Stage(350, "Tiefenschiefer", List.of(
Block.DEEPSLATE, Block.DEEPSLATE, Block.TUFF, Block.DEEPSLATE_DIAMOND_ORE,
Block.DEEPSLATE_IRON_ORE, Block.AMETHYST_BLOCK
)),
new Stage(450, "Netherportal", List.of(
Block.OBSIDIAN, Block.OBSIDIAN, Block.CRYING_OBSIDIAN
)),
new Stage(475, "Nether", List.of(
Block.NETHERRACK, Block.NETHERRACK, Block.CRIMSON_NYLIUM, Block.WARPED_NYLIUM,
Block.SOUL_SOIL, Block.GLOWSTONE, Block.NETHER_QUARTZ_ORE, Block.NETHER_GOLD_ORE
)),
new Stage(625, "Festung & Bastion", List.of(
Block.NETHER_BRICKS, Block.RED_NETHER_BRICKS, Block.BLACKSTONE,
Block.POLISHED_BLACKSTONE_BRICKS, Block.GILDED_BLACKSTONE, Block.BASALT
)),
new Stage(750, "Stronghold", List.of(
Block.STONE_BRICKS, Block.MOSSY_STONE_BRICKS, Block.CRACKED_STONE_BRICKS,
Block.CHISELED_STONE_BRICKS, Block.BOOKSHELF
)),
new Stage(925, "Portalraum", List.of(
Block.END_PORTAL_FRAME, Block.END_PORTAL_FRAME.withProperty("eye", "true"),
Block.STONE_BRICKS, Block.MOSSY_STONE_BRICKS
)),
new Stage(1000, "End", List.of(
Block.END_STONE, Block.END_STONE, Block.END_STONE_BRICKS,
Block.PURPUR_BLOCK, Block.PURPUR_PILLAR, Block.OBSIDIAN
))
);
return blocks.get(r.nextInt(0, blocks.size())); public static Stage stageAt(int index) {
Stage current = STAGES.getFirst();
for(Stage stage : STAGES) {
if(index >= stage.from()) current = stage;
}
return current;
}
/** Liefert die Stufe, die genau bei diesem Index beginnt, sonst null. */
public static Stage stageStartingAt(int index) {
for(Stage stage : STAGES) {
if(stage.from() == index) return stage;
}
return null;
}
public static Block getRandom(int index, Random random) {
List<Block> blocks = stageAt(index).blocks();
return blocks.get(random.nextInt(blocks.size()));
} }
} }
@@ -26,6 +26,8 @@ public class JumpGenerator {
private final int preview = 2; private final int preview = 2;
private static final int FULL_DIFFICULTY_AT = 1000;
public JumpGenerator(Pos starting, Player player, InstanceContainer world) { public JumpGenerator(Pos starting, Player player, InstanceContainer world) {
this.player = player; this.player = player;
@@ -45,8 +47,9 @@ public class JumpGenerator {
this.player.setLevel((this.blocks.size()-this.preview-1)); this.player.setLevel((this.blocks.size()-this.preview-1));
Random r = new Random(); Random r = new Random();
Pos target = blocks.get(blocks.size()-1).add(r.nextInt(-1, 2), r.nextInt(this.player.getPosition().y() < 80 ? 0 : -1, this.player.getPosition().y() > 120 ? 1 : 2), r.nextInt(3, 5)); int index = this.blocks.size();
this.world.setBlock(target, isHundredth ? Block.DIAMOND_BLOCK : isTenth ? Block.GOLD_BLOCK : BlockPalette.getRandom((this.blocks.size() / 10))); Pos target = blocks.get(blocks.size()-1).add(this.nextOffset(index, r));
this.world.setBlock(target, isHundredth ? Block.DIAMOND_BLOCK : isTenth ? Block.GOLD_BLOCK : BlockPalette.getRandom(index, r));
if(standingTenth) { if(standingTenth) {
Pos current = this.blocks.get(this.blocks.size()-this.preview-1); Pos current = this.blocks.get(this.blocks.size()-this.preview-1);
@@ -55,6 +58,12 @@ public class JumpGenerator {
this.player.showTitle(Title.title(Component.text(""), Component.text("Checkpoint", TextColor.color(0, 255, 0)))); this.player.showTitle(Title.title(Component.text(""), Component.text("Checkpoint", TextColor.color(0, 255, 0))));
this.player.sendPacket(new ParticlePacket(Particle.CRIT, current, current.add(0.5), 1.5f, 50)); this.player.sendPacket(new ParticlePacket(Particle.CRIT, current, current.add(0.5), 1.5f, 50));
} }
BlockPalette.Stage reachedStage = BlockPalette.stageStartingAt(this.blocks.size()-this.preview-1);
if(reachedStage != null && reachedStage.from() > 0) {
this.player.playSound(Sound.sound(SoundEvent.UI_TOAST_CHALLENGE_COMPLETE, Sound.Source.MASTER, 0.4f, 1f));
this.player.showTitle(Title.title(Component.text(reachedStage.name(), TextColor.color(255, 170, 0)), Component.text("")));
}
this.world.setBlock(target.add(0, 1 ,0), Block.LIGHT); this.world.setBlock(target.add(0, 1 ,0), Block.LIGHT);
this.blocks.add(target); this.blocks.add(target);
this.player.sendPacket(new ParticlePacket(Particle.CRIT, target, target.add(0.5), 1, 10)); this.player.sendPacket(new ParticlePacket(Particle.CRIT, target, target.add(0.5), 1, 10));
@@ -62,6 +71,32 @@ public class JumpGenerator {
this.combo++; this.combo++;
} }
/**
* Abstand zum nächsten Block. Die Schwierigkeit steigt über die ersten {@link #FULL_DIFFICULTY_AT}
* Sprünge von leicht (2–3 weit, kaum seitlich/aufwärts) bis zum bisherigen Niveau (3–4 weit,
* oft seitlich und aufwärts) und bleibt danach dort.
*/
private Pos nextOffset(int index, Random r) {
double difficulty = Math.min(1.0, (double) index / FULL_DIFFICULTY_AT);
int dz = 2;
if(r.nextDouble() < 0.5 + 0.5 * difficulty) dz++;
if(r.nextDouble() < 0.5 * difficulty) dz++;
int dx = r.nextDouble() < 0.1 + 0.57 * difficulty ? (r.nextBoolean() ? 1 : -1) : 0;
double y = this.player.getPosition().y();
double roll = r.nextDouble();
int dy = 0;
if(roll < 0.05 + 0.28 * difficulty && y <= 120) dy = 1;
else if(roll > 1 - (0.2 + 0.13 * difficulty) && y >= 80) dy = -1;
// weite Diagonalsprünge nach oben erst gegen Ende
if(dy == 1 && dz == 4 && dx != 0 && difficulty < 0.7) dx = 0;
return new Pos(dx, dy, dz);
}
public Pos getNextLanding() { public Pos getNextLanding() {
return this.blocks.get(this.blocks.size()-(preview+1)); return this.blocks.get(this.blocks.size()-(preview+1));
} }