Give every stage its own time of day with smooth transitions, ambient particles and music

This commit is contained in:
MineTec committed 2026-10-08 00:31:48 +02:00
1 parent de6b987ffd
commit 137e162ea4
4 files changed
+98 -15

No files matched your search

@@ -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);
@@ -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<Block> 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<Block> blocks) {
}
private static final long MORNING = 1000, NOON = 6000, AFTERNOON = 11000, EVENING = 13000, NIGHT = 18000;
private static final List<Stage> 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
))
@@ -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<Step> course = new ArrayList<>();
private final Map<BlockKey, Integer> 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 {
@@ -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));
}
}