diff --git a/src/main/java/eu/mhsl/minecraft/endlessjumper/Jokers.java b/src/main/java/eu/mhsl/minecraft/endlessjumper/Jokers.java index df15c34..ed561de 100644 --- a/src/main/java/eu/mhsl/minecraft/endlessjumper/Jokers.java +++ b/src/main/java/eu/mhsl/minecraft/endlessjumper/Jokers.java @@ -12,11 +12,11 @@ import java.util.Random; /** * Joker-Items in der Hotbar, die Anzahl ist die Stapelgröße. - * Brücke: Rechtsklick baut einen Weg bis zum nächsten Checkpoint. + * Brücke: Rechtsklick baut einen Weg über die nächsten 10 Blöcke. * Totem: rettet automatisch beim Runterfallen (Plattform mit Leiter). */ public enum Jokers { - BRIDGE(0, Material.BRICK, "Brücke", NamedTextColor.AQUA, "Rechtsklick: Weg bis zum nächsten Checkpoint"), + BRIDGE(0, Material.BRICK, "Brücke", NamedTextColor.AQUA, "Rechtsklick: Weg über die nächsten 10 Blöcke"), TOTEM(8, Material.TOTEM_OF_UNDYING, "Rettungstotem", NamedTextColor.GOLD, "Rettet dich automatisch beim Runterfallen"); private final int slot; 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 707b96a..e550d15 100644 --- a/src/main/java/eu/mhsl/minecraft/endlessjumper/generator/BlockPalette.java +++ b/src/main/java/eu/mhsl/minecraft/endlessjumper/generator/BlockPalette.java @@ -9,7 +9,7 @@ import java.util.Random; /** * 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 + * Untergrund, Nether und Stronghold bis ins End; ab Sprung 1000 geht es endlos weiter. Jeder Abschnitt hat eigene * Blöcke und eine eigene Stimmung (Tageszeit, Partikel, Musik). */ public class BlockPalette { @@ -23,47 +23,47 @@ public class BlockPalette { 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", NOON, Particle.CHERRY_LEAVES, SoundEvent.MUSIC_OVERWORLD_FOREST, List.of( + new Stage(70, "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", NIGHT, null, SoundEvent.MUSIC_OVERWORLD_DRIPSTONE_CAVES, List.of( + new Stage(140, "Untergrund", NIGHT, 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", NIGHT, Particle.DRIPPING_WATER, SoundEvent.MUSIC_OVERWORLD_LUSH_CAVES, List.of( + new Stage(220, "Höhlen", NIGHT, 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", NIGHT, Particle.SCULK_SOUL, SoundEvent.MUSIC_OVERWORLD_DEEP_DARK, List.of( + new Stage(300, "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", NIGHT, Particle.PORTAL, SoundEvent.MUSIC_NETHER_NETHER_WASTES, List.of( + new Stage(390, "Netherportal", NIGHT, Particle.PORTAL, SoundEvent.MUSIC_NETHER_NETHER_WASTES, List.of( Block.OBSIDIAN, Block.OBSIDIAN, Block.CRYING_OBSIDIAN )), - new Stage(475, "Nether", NIGHT, Particle.CRIMSON_SPORE, SoundEvent.MUSIC_NETHER_CRIMSON_FOREST, List.of( + new Stage(410, "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", NIGHT, Particle.WHITE_ASH, SoundEvent.MUSIC_NETHER_BASALT_DELTAS, List.of( + new Stage(540, "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", NIGHT, Particle.ENCHANT, SoundEvent.MUSIC_DISC_MELLOHI, List.of( + new Stage(660, "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", NIGHT, Particle.REVERSE_PORTAL, SoundEvent.MUSIC_DRAGON, List.of( + new Stage(800, "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", NIGHT, Particle.END_ROD, SoundEvent.MUSIC_END, List.of( + 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(1100, "Unendlich", NIGHT, null, SoundEvent.MUSIC_MENU, List.of( + new Stage(1000, "Unendlich", NIGHT, null, SoundEvent.MUSIC_MENU, List.of( Block.SMOOTH_QUARTZ )) ); 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 89973f3..e0a5a2e 100644 --- a/src/main/java/eu/mhsl/minecraft/endlessjumper/generator/JumpGenerator.java +++ b/src/main/java/eu/mhsl/minecraft/endlessjumper/generator/JumpGenerator.java @@ -48,13 +48,13 @@ public class JumpGenerator { private static final Block BRIDGE_BLOCK = Block.WHITE_STAINED_GLASS; private static final int BRIDGE_STEP_TICKS = 4; + private static final int BRIDGE_LENGTH = 10; private static final Block RESCUE_BLOCK = Block.OAK_PLANKS; private static final int RESCUE_DEPTH = 6; private static final TextColor COMBO_COLOR = TextColor.color(255, 69, 0); private static final Title CHECKPOINT_TITLE = Title.title(Component.empty(), Component.text("Checkpoint", NamedTextColor.GREEN)); - private static final Component ENDLESS_HINT = Component.text(" · /reset für einen Neustart", NamedTextColor.DARK_GRAY); - private static final Title FINALE_TITLE = Title.title(Component.text("Geschafft!", NamedTextColor.GOLD), Component.text("Du hast das End erreicht", NamedTextColor.YELLOW)); + private static final Title FINALE_TITLE = Title.title(Component.text("Geschafft!", NamedTextColor.GOLD), Component.text("Durchgespielt – ab jetzt geht es endlos weiter", NamedTextColor.YELLOW)); private static final Sound CHECKPOINT_SOUND = Sound.sound(SoundEvent.ENTITY_PLAYER_LEVELUP, Sound.Source.BLOCK, 0.3f, 1f); private static final Sound STAGE_SOUND = Sound.sound(SoundEvent.UI_TOAST_CHALLENGE_COMPLETE, Sound.Source.MASTER, 0.4f, 1f); private static final Sound BRIDGE_SOUND = Sound.sound(SoundEvent.BLOCK_AMETHYST_BLOCK_CHIME, Sound.Source.MASTER, 1f, 1f); @@ -73,7 +73,8 @@ public class JumpGenerator { private int standing; private int combo = 0; private Pos checkpoint; - // bis zu diesem Index wurde per Brücke gebaut: diese Blöcke zählen nicht zur Combo + // zuletzt per Brücke überbrückter Bereich: diese Blöcke zählen nicht zur Combo + private int bridgeStart = -1; private int bridgeEnd = -1; // Rettung durch das Totem: Plattform mit Leiter unter dem Block, von dem man gefallen ist @@ -139,7 +140,7 @@ public class JumpGenerator { this.player.setLevel(index); ScoreTab.update(this.player, index); - if(index > this.bridgeEnd) { + if(index < this.bridgeStart || index > this.bridgeEnd) { this.combo++; this.player.playSound(Sound.sound(SoundEvent.BLOCK_NOTE_BLOCK_PLING, Sound.Source.MASTER, 0.1f, 1 + this.combo * 0.025f)); this.player.showTitle(Title.title(Component.empty(), Component.text(this.combo, COMBO_COLOR))); @@ -155,23 +156,19 @@ public class JumpGenerator { this.player.sendPacket(new ParticlePacket(Particle.CRIT, this.checkpoint, this.checkpoint.add(0.5), 1.5f, 50)); } - // Abschnitt dauerhaft in der Actionbar (Titel werden von der Combo-Anzeige überschrieben) + // neuer Abschnitt: Stimmung wechseln (die Actionbar zeigt der Abschnitt selbst dauerhaft) BlockPalette.Stage stage = BlockPalette.stageAt(index); - boolean newStage = stage != BlockPalette.stageAt(previous); - if(newStage) { + if(stage != BlockPalette.stageAt(previous)) { this.atmosphere.enter(stage); this.player.playSound(STAGE_SOUND); } - Component stageText = Component.text(stage.name(), newStage ? NamedTextColor.GOLD : NamedTextColor.GRAY); - if(BlockPalette.isEndless(stage)) stageText = stageText.append(ENDLESS_HINT); - this.player.sendActionBar(stageText); if(previous < FINALE_AT && index >= FINALE_AT) this.celebrate(); this.ensureGenerated(index + PREVIEW + 1, 0); } - /** Ankunft im End: Feuerwerk rund um den Spieler. Danach geht es endlos weiter. */ + /** Durchgespielt (Beginn von „Unendlich“): Feuerwerk rund um den Spieler. */ private void celebrate() { this.player.showTitle(FINALE_TITLE); for(int i = 0; i < 6; i++) { @@ -249,20 +246,22 @@ public class JumpGenerator { } /** - * Joker „Brücke“: erzeugt die Strecke bis zum nächsten Checkpoint und verbindet sie mit einem - * begehbaren Weg. Diese Blöcke zählen nicht zur Combo. Mehrfacher Aufruf baut nichts doppelt. + * Joker „Brücke“: verbindet die nächsten {@link #BRIDGE_LENGTH} Blöcke ab der aktuellen Position + * mit einem begehbaren Weg, auch über früher schon geschaffte Stücke (z. B. nach einem Reset). + * Diese Blöcke zählen nicht zur Combo. Ist der Bereich schon überbrückt, passiert nichts. */ public boolean useBridge() { - int nextCheckpoint = (this.standing / 10 + 1) * 10; - if(this.rescueSafe != null || this.bridgeEnd >= nextCheckpoint) return false; - this.bridgeEnd = nextCheckpoint; + int target = this.standing + BRIDGE_LENGTH; + if(this.rescueSafe != null || (this.standing >= this.bridgeStart && target <= this.bridgeEnd)) return false; + this.bridgeStart = this.standing; + this.bridgeEnd = target; // bereits stehende Abschnitte sofort, neue Blöcke bekommen ihren Weg bei Ankunft - for(int i = this.standing + 1; i < this.generated() && i <= nextCheckpoint; i++) { + for(int i = this.standing + 1; i < this.generated() && i <= target; i++) { this.buildPath(this.step(i - 1).pos(), this.step(i).pos()); } int delay = 0; - while(this.generated() <= nextCheckpoint) { + while(this.generated() <= target) { Pos from = this.course.getLast().pos(); int index = this.generated(); this.generateStep(delay, () -> this.buildPath(from, this.step(index).pos())); diff --git a/src/main/java/eu/mhsl/minecraft/endlessjumper/generator/StageAtmosphere.java b/src/main/java/eu/mhsl/minecraft/endlessjumper/generator/StageAtmosphere.java index 40dbf77..b282682 100644 --- a/src/main/java/eu/mhsl/minecraft/endlessjumper/generator/StageAtmosphere.java +++ b/src/main/java/eu/mhsl/minecraft/endlessjumper/generator/StageAtmosphere.java @@ -2,6 +2,8 @@ package eu.mhsl.minecraft.endlessjumper.generator; import net.kyori.adventure.sound.Sound; import net.kyori.adventure.sound.SoundStop; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import net.minestom.server.coordinate.Vec; import net.minestom.server.entity.Player; import net.minestom.server.instance.Instance; @@ -12,20 +14,27 @@ 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. + * Stimmung des aktuellen Abschnitts: Tageszeit (fließender Übergang), Musik, Partikel auf der + * Strecke voraus und der Abschnittsname dauerhaft in der Actionbar. */ class StageAtmosphere { private static final int TIME_TRANSITION_TICKS = 100; // die Strecke läuft immer in +z: Partikel vom Spieler bis etwa 10 Sprünge voraus private static final Vec AMBIENT_AHEAD = new Vec(0, 1.5, 14); private static final Vec AMBIENT_SPREAD = new Vec(7, 4, 16); + private static final int AMBIENT_EVERY_TICKS = 4; + // die Actionbar blendet nach ~3 s aus: jede Sekunde erneuern, nach einem Wechsel 5 s golden + private static final int ACTION_BAR_TICKS = 20; + private static final int HIGHLIGHT_TICKS = 100; + private static final Component ENDLESS_HINT = Component.text(" · /reset für einen Neustart", NamedTextColor.DARK_GRAY); private final Player player; private final Instance world; private BlockPalette.Stage stage; private Task timeTransition; private final Task ambient; + private int ticks = 0; + private int highlightUntil = 0; StageAtmosphere(Player player, Instance world, BlockPalette.Stage initial) { this.player = player; @@ -35,7 +44,7 @@ class StageAtmosphere { this.world.refreshTime(); this.playMusic(); // an den Spieler gebunden, endet also mit ihm - this.ambient = player.scheduler().buildTask(this::spawnAmbient).repeat(TaskSchedule.tick(4)).schedule(); + this.ambient = player.scheduler().buildTask(this::tick).repeat(TaskSchedule.tick(AMBIENT_EVERY_TICKS)).schedule(); } void stop() { @@ -47,8 +56,10 @@ class StageAtmosphere { void enter(BlockPalette.Stage next) { if(next == this.stage) return; this.stage = next; + this.highlightUntil = this.ticks + HIGHLIGHT_TICKS; this.playMusic(); this.fadeTimeTo(next.time()); + this.showStage(); } private void playMusic() { @@ -76,8 +87,17 @@ class StageAtmosphere { }); } - private void spawnAmbient() { - if(this.stage.ambient() == null) return; - this.player.sendPacket(new ParticlePacket(this.stage.ambient(), this.player.getPosition().add(AMBIENT_AHEAD), AMBIENT_SPREAD, 0.02f, 8)); + private void tick() { + this.ticks += AMBIENT_EVERY_TICKS; + if(this.ticks % ACTION_BAR_TICKS == 0) this.showStage(); + if(this.stage.ambient() != null) { + this.player.sendPacket(new ParticlePacket(this.stage.ambient(), this.player.getPosition().add(AMBIENT_AHEAD), AMBIENT_SPREAD, 0.02f, 8)); + } + } + + private void showStage() { + Component text = Component.text(this.stage.name(), this.ticks < this.highlightUntil ? NamedTextColor.GOLD : NamedTextColor.GRAY); + if(BlockPalette.isEndless(this.stage)) text = text.append(ENDLESS_HINT); + this.player.sendActionBar(text); } }