Add /reset to start over from scratch and hint at it in the endless stage

This commit is contained in:
MineTec committed 2026-10-08 00:48:44 +02:00
1 parent e2c574bf0b
commit 249cd0c08a
5 files changed
+69 -17

No files matched your search

@@ -3,6 +3,8 @@ package eu.mhsl.minecraft.endlessjumper;
import eu.mhsl.minecraft.endlessjumper.generator.JumpGenerator;
import net.minestom.server.Auth;
import net.minestom.server.MinecraftServer;
import net.minestom.server.command.builder.Command;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.PlayerSkin;
@@ -20,7 +22,6 @@ import net.minestom.server.event.player.PlayerUseItemEvent;
import net.minestom.server.event.player.PlayerUseItemOnBlockEvent;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.InstanceContainer;
import net.minestom.server.instance.InstanceManager;
import net.minestom.server.instance.block.Block;
import net.minestom.server.item.ItemStack;
@@ -32,26 +33,20 @@ import java.util.concurrent.ConcurrentHashMap;
public class EndlessJumper {
private static final Pos START = new Pos(0, 100, 0);
private static final Pos SPAWN = START.add(0.5, 1, 0.5);
private static final Map<Player, JumpGenerator> games = new ConcurrentHashMap<>();
public static void main(String[] args) throws IOException {
MinecraftServer server = MinecraftServer.init(new Auth.Velocity(readForwardingSecret()));
Progress.load();
InstanceManager instanceManager = MinecraftServer.getInstanceManager();
GlobalEventHandler eventHandler = MinecraftServer.getGlobalEventHandler();
PlayerSkin skin = PlayerSkin.fromUsername("Notch");
eventHandler.addListener(AsyncPlayerConfigurationEvent.class, event -> {
final Player p = event.getPlayer();
p.setRespawnPoint(START.add(0.5, 1.5, 0.5));
final InstanceContainer world = instanceManager.createInstanceContainer();
world.setBlock(START, Block.DIAMOND_BLOCK);
// Tageszeit steuert der jeweilige Abschnitt
world.defaultClock().pause();
p.setRespawnPoint(SPAWN);
p.setSkin(skin);
event.setSpawningInstance(world);
event.setSpawningInstance(createWorld());
});
eventHandler.addListener(PlayerSpawnEvent.class, event -> {
@@ -94,13 +89,46 @@ public class EndlessJumper {
games.remove(p);
ScoreTab.leave(p);
// jede Partie hat eine eigene Welt: nach dem Verlassen freigeben
Instance world = p.getInstance();
if(world != null) MinecraftServer.getSchedulerManager().scheduleNextTick(() -> instanceManager.unregisterInstance(world));
releaseLater(p.getInstance());
});
Command reset = new Command("reset");
reset.setDefaultExecutor((sender, context) -> {
if(sender instanceof Player player) resetGame(player);
});
MinecraftServer.getCommandManager().register(reset);
server.start("0.0.0.0", 25501);
}
private static InstanceContainer createWorld() {
InstanceContainer world = MinecraftServer.getInstanceManager().createInstanceContainer();
world.setBlock(START, Block.DIAMOND_BLOCK);
// Tageszeit steuert der jeweilige Abschnitt
world.defaultClock().pause();
return world;
}
private static void releaseLater(Instance world) {
if(world != null) MinecraftServer.getSchedulerManager().scheduleNextTick(() -> MinecraftServer.getInstanceManager().unregisterInstance(world));
}
/** /reset: Fortschritt, Strecke, Score und Joker zurücksetzen und in einer frischen Welt bei 0 beginnen. */
private static void resetGame(Player player) {
JumpGenerator old = games.remove(player);
if(old != null) old.close();
Progress.reset(player.getUuid());
Instance oldWorld = player.getInstance();
InstanceContainer world = createWorld();
player.setVelocity(Vec.ZERO);
player.setInstance(world, SPAWN).thenRun(() -> {
Jokers.giveStart(player);
games.put(player, new JumpGenerator(START, 0, player, world));
releaseLater(oldWorld);
});
}
private static void useJoker(Player player, ItemStack item) {
JumpGenerator generator = games.get(player);
if(generator != null && Jokers.BRIDGE.matches(item) && generator.useBridge()) Jokers.BRIDGE.take(player);
@@ -40,6 +40,11 @@ public class Progress {
writer.execute(Progress::write);
}
public static void reset(UUID player) {
checkpoints.remove(player.toString());
writer.execute(Progress::write);
}
/** Erst in eine Temp-Datei, dann atomar umbenennen: ein Absturz hinterlässt nie eine halbe Datei. */
private static void write() {
try {
@@ -75,6 +75,11 @@ public class BlockPalette {
return STAGES.getFirst();
}
/** Der letzte Abschnitt geht endlos weiter. */
public static boolean isEndless(Stage stage) {
return stage == STAGES.getLast();
}
public static Block getRandom(int index, Random random) {
List<Block> blocks = stageAt(index).blocks();
return blocks.get(random.nextInt(blocks.size()));
@@ -53,6 +53,7 @@ public class JumpGenerator {
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 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);
@@ -154,13 +155,14 @@ 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)) {
boolean newStage = stage != BlockPalette.stageAt(previous);
if(newStage) {
this.atmosphere.enter(stage);
this.player.playSound(STAGE_SOUND);
this.player.sendActionBar(Component.text(stage.name(), NamedTextColor.GOLD));
} else {
this.player.sendActionBar(Component.text(stage.name(), NamedTextColor.GRAY));
}
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();
@@ -348,6 +350,11 @@ public class JumpGenerator {
})).delay(TaskSchedule.seconds(8)).schedule();
}
/** Beendet alles, was an den Spieler gebunden weiterläuft (Partikel, Musik). */
public void close() {
this.atmosphere.stop();
}
/** Streckenblöcke und ihr Licht sind geschützt, alles andere (Brücke, Rettung) darf abgebaut werden. */
public boolean isBreakable(Point pos) {
return !this.world.getBlock(pos).compare(Block.LIGHT) && !this.courseIndex.containsKey(BlockKey.of(pos));
@@ -23,6 +23,7 @@ class StageAtmosphere {
private final Instance world;
private BlockPalette.Stage stage;
private Task timeTransition;
private final Task ambient;
StageAtmosphere(Player player, Instance world, BlockPalette.Stage initial) {
this.player = player;
@@ -32,7 +33,13 @@ class StageAtmosphere {
this.world.refreshTime();
this.playMusic();
// an den Spieler gebunden, endet also mit ihm
player.scheduler().buildTask(this::spawnAmbient).repeat(TaskSchedule.tick(4)).schedule();
this.ambient = player.scheduler().buildTask(this::spawnAmbient).repeat(TaskSchedule.tick(4)).schedule();
}
void stop() {
this.ambient.cancel();
if(this.timeTransition != null) this.timeTransition.cancel();
this.player.stopSound(SoundStop.source(Sound.Source.MUSIC));
}
void enter(BlockPalette.Stage next) {