From 137e162ea49276800137f2f014507dde54602380 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Thu, 8 Oct 2026 00:31:48 +0200 Subject: [PATCH] Give every stage its own time of day with smooth transitions, ambient particles and music --- .../endlessjumper/EndlessJumper.java | 2 +- .../endlessjumper/generator/BlockPalette.java | 34 +++++---- .../generator/JumpGenerator.java | 3 + .../generator/StageAtmosphere.java | 74 +++++++++++++++++++ 4 files changed, 98 insertions(+), 15 deletions(-) create mode 100644 src/main/java/eu/mhsl/minecraft/endlessjumper/generator/StageAtmosphere.java diff --git a/src/main/java/eu/mhsl/minecraft/endlessjumper/EndlessJumper.java b/src/main/java/eu/mhsl/minecraft/endlessjumper/EndlessJumper.java index 925767a..1060b34 100644 --- a/src/main/java/eu/mhsl/minecraft/endlessjumper/EndlessJumper.java +++ b/src/main/java/eu/mhsl/minecraft/endlessjumper/EndlessJumper.java @@ -47,8 +47,8 @@ public class EndlessJumper { final InstanceContainer world = instanceManager.createInstanceContainer(); world.setBlock(START, Block.DIAMOND_BLOCK); + // Tageszeit steuert der jeweilige Abschnitt world.defaultClock().pause(); - world.setTime(18000); p.setSkin(skin); event.setSpawningInstance(world); 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 85f83fa..47b9fdc 100644 --- a/src/main/java/eu/mhsl/minecraft/endlessjumper/generator/BlockPalette.java +++ b/src/main/java/eu/mhsl/minecraft/endlessjumper/generator/BlockPalette.java @@ -1,58 +1,64 @@ package eu.mhsl.minecraft.endlessjumper.generator; import net.minestom.server.instance.block.Block; +import net.minestom.server.particle.Particle; +import net.minestom.server.sound.SoundEvent; import java.util.List; 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). + * Abschnitte entlang des Fortschritts: einmal durch das komplette Spiel, von der Wiese über + * Untergrund, Nether und Stronghold bis ins End (ab Sprung 1000). Jeder Abschnitt hat eigene + * Blöcke und eine eigene Stimmung (Tageszeit, Partikel, Musik). */ public class BlockPalette { - public record Stage(int from, String name, List blocks) { + /** @param ambient Partikel um den Spieler, {@code null} für keine */ + public record Stage(int from, String name, long time, Particle ambient, SoundEvent music, List blocks) { } + private static final long MORNING = 1000, NOON = 6000, AFTERNOON = 11000, EVENING = 13000, NIGHT = 18000; + private static final List STAGES = List.of( - new Stage(0, "Wiese", List.of( + new Stage(0, "Wiese", MORNING, null, SoundEvent.MUSIC_OVERWORLD_MEADOW, List.of( Block.GRASS_BLOCK, Block.GRASS_BLOCK, Block.MOSS_BLOCK, Block.PODZOL, Block.ROOTED_DIRT )), - new Stage(75, "Wald", List.of( + new Stage(75, "Wald", NOON, Particle.CHERRY_LEAVES, SoundEvent.MUSIC_OVERWORLD_FOREST, List.of( 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( + new Stage(150, "Untergrund", AFTERNOON, null, SoundEvent.MUSIC_OVERWORLD_DRIPSTONE_CAVES, 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( + new Stage(250, "Höhlen", EVENING, Particle.DRIPPING_WATER, SoundEvent.MUSIC_OVERWORLD_LUSH_CAVES, 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( + new Stage(350, "Tiefenschiefer", NIGHT, Particle.SCULK_SOUL, SoundEvent.MUSIC_OVERWORLD_DEEP_DARK, 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( + new Stage(450, "Netherportal", NIGHT, Particle.PORTAL, SoundEvent.MUSIC_NETHER_NETHER_WASTES, List.of( Block.OBSIDIAN, Block.OBSIDIAN, Block.CRYING_OBSIDIAN )), - new Stage(475, "Nether", List.of( + new Stage(475, "Nether", NIGHT, Particle.CRIMSON_SPORE, SoundEvent.MUSIC_NETHER_CRIMSON_FOREST, 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( + new Stage(625, "Festung & Bastion", NIGHT, Particle.WHITE_ASH, SoundEvent.MUSIC_NETHER_BASALT_DELTAS, 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( + new Stage(750, "Stronghold", NIGHT, Particle.ENCHANT, SoundEvent.MUSIC_DISC_MELLOHI, 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( + new Stage(925, "Portalraum", NIGHT, Particle.REVERSE_PORTAL, SoundEvent.MUSIC_DRAGON, 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( + new Stage(1000, "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 )) 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 eeae989..b430926 100644 --- a/src/main/java/eu/mhsl/minecraft/endlessjumper/generator/JumpGenerator.java +++ b/src/main/java/eu/mhsl/minecraft/endlessjumper/generator/JumpGenerator.java @@ -60,6 +60,7 @@ public class JumpGenerator { private final Player player; private final Instance world; private final Random random = new Random(); + private final StageAtmosphere atmosphere; private final List course = new ArrayList<>(); private final Map courseIndex = new HashMap<>(); @@ -77,6 +78,7 @@ public class JumpGenerator { this.player = player; this.world = world; this.checkpoint = start; + this.atmosphere = new StageAtmosphere(player, world, BlockPalette.stageAt(0)); this.addStep(new Step(start, Block.DIAMOND_BLOCK)); // nacheinander animieren, damit jeder Block aus seinem bereits stehenden Vorgänger wächst this.ensureGenerated(PREVIEW + 1, BlockTransition.TOTAL_TICKS); @@ -128,6 +130,7 @@ public class JumpGenerator { // Abschnitt dauerhaft in der Actionbar (Titel werden von der Combo-Anzeige überschrieben) BlockPalette.Stage stage = BlockPalette.stageAt(index); if(stage != BlockPalette.stageAt(previous)) { + this.atmosphere.enter(stage); this.player.playSound(STAGE_SOUND); this.player.sendActionBar(Component.text(stage.name(), NamedTextColor.GOLD)); } else { diff --git a/src/main/java/eu/mhsl/minecraft/endlessjumper/generator/StageAtmosphere.java b/src/main/java/eu/mhsl/minecraft/endlessjumper/generator/StageAtmosphere.java new file mode 100644 index 0000000..0d9f8e7 --- /dev/null +++ b/src/main/java/eu/mhsl/minecraft/endlessjumper/generator/StageAtmosphere.java @@ -0,0 +1,74 @@ +package eu.mhsl.minecraft.endlessjumper.generator; + +import net.kyori.adventure.sound.Sound; +import net.kyori.adventure.sound.SoundStop; +import net.minestom.server.coordinate.Vec; +import net.minestom.server.entity.Player; +import net.minestom.server.instance.Instance; +import net.minestom.server.network.packet.server.play.ParticlePacket; +import net.minestom.server.timer.Task; +import net.minestom.server.timer.TaskSchedule; + +import java.util.function.Supplier; + +/** + * Stimmung des aktuellen Abschnitts: Tageszeit (fließender Übergang), Musik und Partikel, + * die dauerhaft um den Spieler schweben. + */ +class StageAtmosphere { + private static final int TIME_TRANSITION_TICKS = 100; + private static final Vec AMBIENT_SPREAD = new Vec(8, 3, 8); + + private final Player player; + private final Instance world; + private BlockPalette.Stage stage; + private Task timeTransition; + + StageAtmosphere(Player player, Instance world, BlockPalette.Stage initial) { + this.player = player; + this.world = world; + this.stage = initial; + this.world.setTime(initial.time()); + this.world.refreshTime(); + this.playMusic(); + // an den Spieler gebunden, endet also mit ihm + player.scheduler().buildTask(this::spawnAmbient).repeat(TaskSchedule.tick(4)).schedule(); + } + + void enter(BlockPalette.Stage next) { + if(next == this.stage) return; + this.stage = next; + this.playMusic(); + this.fadeTimeTo(next.time()); + } + + private void playMusic() { + this.player.stopSound(SoundStop.source(Sound.Source.MUSIC)); + this.player.playSound(Sound.sound(this.stage.music(), Sound.Source.MUSIC, 1f, 1f)); + } + + /** Läuft immer vorwärts zur Zielzeit, Tick für Tick, damit Himmel und Licht weich wechseln. */ + private void fadeTimeTo(long target) { + if(this.timeTransition != null) this.timeTransition.cancel(); + long start = this.world.getTime(); + long delta = Math.floorMod(target - start, 24000L); + if(delta == 0) return; + + this.timeTransition = this.world.scheduler().submitTask(new Supplier<>() { + private int tick = 0; + + @Override + public TaskSchedule get() { + this.tick++; + StageAtmosphere.this.world.setTime(start + delta * this.tick / TIME_TRANSITION_TICKS); + StageAtmosphere.this.world.refreshTime(); + return this.tick < TIME_TRANSITION_TICKS ? TaskSchedule.tick(1) : TaskSchedule.stop(); + } + }); + } + + private void spawnAmbient() { + if(this.stage.ambient() == null) return; + this.player.sendPacket(new ParticlePacket(this.stage.ambient(), this.player.getPosition().add(0, 1.5, 0), AMBIENT_SPREAD, 0.02f, 3)); + } +}