diff --git a/src/main/java/eu/mhsl/minecraft/endlessjumper/generator/BlockPalette.java b/src/main/java/eu/mhsl/minecraft/endlessjumper/generator/BlockPalette.java index e550d15..882de1e 100644 --- a/src/main/java/eu/mhsl/minecraft/endlessjumper/generator/BlockPalette.java +++ b/src/main/java/eu/mhsl/minecraft/endlessjumper/generator/BlockPalette.java @@ -5,11 +5,14 @@ import net.minestom.server.particle.Particle; import net.minestom.server.sound.SoundEvent; import java.util.List; +import java.util.Map; import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; /** * Abschnitte entlang des Fortschritts: einmal durch das komplette Spiel, von der Wiese über - * Untergrund, Nether und Stronghold bis ins End; ab Sprung 1000 geht es endlos weiter. Jeder Abschnitt hat eigene + * Untergrund, Nether und Stronghold bis ins End; ab Sprung 1000 geht es endlos weiter, alle 50 Sprünge + * mit einem zufälligen Thema aus diesen Abschnitten. Jeder Abschnitt hat eigene * Blöcke und eine eigene Stimmung (Tageszeit, Partikel, Musik). */ public class BlockPalette { @@ -61,23 +64,48 @@ public class BlockPalette { new Stage(850, "End", NIGHT, Particle.END_ROD, SoundEvent.MUSIC_END, List.of( Block.END_STONE, Block.END_STONE, Block.END_STONE_BRICKS, Block.PURPUR_BLOCK, Block.PURPUR_PILLAR, Block.OBSIDIAN - )), - // nach dem End endlos weiter mit einem neutralen Block - new Stage(1000, "Unendlich", NIGHT, null, SoundEvent.MUSIC_MENU, List.of( - Block.SMOOTH_QUARTZ )) ); + // ab hier endlos: alle ENDLESS_SEGMENT Sprünge ein zufälliges Thema aus den Abschnitten oben + private static final int ENDLESS_FROM = 1000; + private static final int ENDLESS_SEGMENT = 50; + private static final Map endlessStages = new ConcurrentHashMap<>(); + public static Stage stageAt(int index) { + if(index >= ENDLESS_FROM) return endlessStage((index - ENDLESS_FROM) / ENDLESS_SEGMENT); for(Stage stage : STAGES.reversed()) { if(index >= stage.from()) return stage; } return STAGES.getFirst(); } - /** Der letzte Abschnitt geht endlos weiter. */ public static boolean isEndless(Stage stage) { - return stage == STAGES.getLast(); + return stage.from() >= ENDLESS_FROM; + } + + /** + * Thema eines endlosen Abschnitts. Fest an die Abschnittsnummer gebunden (gleiches Thema beim + * Weiterspielen) und nie zweimal dasselbe hintereinander. Zwischengespeichert, damit jeder + * Abschnitt genau ein Stage-Objekt hat (Abschnittswechsel werden per Identität erkannt). + */ + private static Stage endlessStage(int segment) { + return endlessStages.computeIfAbsent(segment, n -> { + Stage theme = themeOf(n); + return new Stage(ENDLESS_FROM + n * ENDLESS_SEGMENT, "Unendlich · " + theme.name(), theme.time(), theme.ambient(), theme.music(), theme.blocks()); + }); + } + + /** Würfelt die Themen ab Abschnitt 0 der Reihe nach, damit „nie zweimal hintereinander“ sicher gilt. */ + private static Stage themeOf(int segment) { + int previous = -1; + int pick = 0; + for(int n = 0; n <= segment; n++) { + pick = new Random(n * 31L + 7).nextInt(STAGES.size()); + if(pick == previous) pick = (pick + 1) % STAGES.size(); + previous = pick; + } + return STAGES.get(pick); } public static Block getRandom(int index, Random random) { diff --git a/src/main/java/eu/mhsl/minecraft/endlessjumper/generator/JumpGenerator.java b/src/main/java/eu/mhsl/minecraft/endlessjumper/generator/JumpGenerator.java index facf5cf..eabe72b 100644 --- a/src/main/java/eu/mhsl/minecraft/endlessjumper/generator/JumpGenerator.java +++ b/src/main/java/eu/mhsl/minecraft/endlessjumper/generator/JumpGenerator.java @@ -13,6 +13,7 @@ import net.minestom.server.coordinate.Pos; import net.minestom.server.coordinate.Vec; import net.minestom.server.entity.ItemEntity; import net.minestom.server.entity.Player; +import net.minestom.server.instance.Chunk; import net.minestom.server.instance.Instance; import net.minestom.server.instance.block.Block; import net.minestom.server.network.packet.server.play.ParticlePacket; @@ -164,6 +165,7 @@ public class JumpGenerator { if(reachedCheckpoint > previous) { this.checkpoint = this.step(reachedCheckpoint).pos(); Progress.save(this.player.getUuid(), reachedCheckpoint); + this.unloadChunksBehind(); this.player.playSound(CHECKPOINT_SOUND); this.player.showTitle(CHECKPOINT_TITLE); this.player.sendPacket(new ParticlePacket(Particle.CRIT, this.checkpoint, this.checkpoint.add(0.5), 1.5f, 50)); @@ -200,6 +202,20 @@ public class JumpGenerator { } } + /** + * Die Strecke wandert immer in +z. Chunks hinter dem Checkpoint und außerhalb der Sichtweite + * werden nie wieder gebraucht (Reset und Rettung liegen davor): freigeben, sonst sammeln sich + * bei langen Läufen tausende an. + */ + private void unloadChunksBehind() { + int playerChunk = this.player.getPosition().blockZ() >> 4; + int checkpointChunk = this.checkpoint.blockZ() >> 4; + int keepFrom = Math.min(checkpointChunk, playerChunk - this.player.effectiveViewDistance()) - 1; + for(Chunk chunk : List.copyOf(this.world.getChunks())) { + if(chunk.getChunkZ() < keepFrom) this.world.unloadChunk(chunk); + } + } + private void ensureGenerated(int upTo, int stepTicks) { int delay = 0; while(this.generated() <= upTo) {