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.command.builder.arguments.ArgumentType; import net.minestom.server.command.builder.arguments.ArgumentWord; import net.minestom.server.coordinate.Vec; import net.minestom.server.coordinate.Pos; import net.minestom.server.entity.Player; import net.minestom.server.entity.PlayerSkin; import net.minestom.server.event.GlobalEventHandler; import net.minestom.server.event.item.ItemDropEvent; import net.minestom.server.event.item.PickupItemEvent; import net.minestom.server.event.player.AsyncPlayerConfigurationEvent; import net.minestom.server.event.player.PlayerBlockBreakEvent; import net.minestom.server.event.player.PlayerBlockPlaceEvent; import net.minestom.server.event.player.PlayerChatEvent; import net.minestom.server.event.player.PlayerDisconnectEvent; import net.minestom.server.event.player.PlayerMoveEvent; import net.minestom.server.event.player.PlayerSpawnEvent; import net.minestom.server.event.player.PlayerSwapItemEvent; import net.minestom.server.event.player.PlayerUseItemEvent; import net.minestom.server.instance.Instance; import net.minestom.server.instance.InstanceContainer; import net.minestom.server.instance.block.Block; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; 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 games = new ConcurrentHashMap<>(); public static void main(String[] args) throws IOException { MinecraftServer server = MinecraftServer.init(new Auth.Velocity(readForwardingSecret())); Progress.load(); Admins.load(); GlobalEventHandler eventHandler = MinecraftServer.getGlobalEventHandler(); PlayerSkin skin = PlayerSkin.fromUsername("Notch"); eventHandler.addListener(AsyncPlayerConfigurationEvent.class, event -> { final Player p = event.getPlayer(); p.setRespawnPoint(SPAWN); p.setSkin(skin); event.setSpawningInstance(createWorld()); }); eventHandler.addListener(PlayerSpawnEvent.class, event -> { if(!event.isFirstSpawn()) return; Player p = event.getPlayer(); p.setCanPickupItem(true); ScoreTab.join(p); Jokers.clear(p); // sobald der Spieler in der Welt steht, geht es los games.put(p, new JumpGenerator(START, Progress.checkpoint(p.getUuid()), p, p.getInstance())); }); eventHandler.addListener(PlayerMoveEvent.class, event -> { JumpGenerator generator = games.get(event.getPlayer()); if(generator == null) event.setCancelled(true); else generator.onMove(event.getNewPosition()); }); eventHandler.addListener(PlayerBlockBreakEvent.class, event -> event.setCancelled(true)); eventHandler.addListener(PlayerBlockPlaceEvent.class, event -> event.setCancelled(true)); eventHandler.addListener(ItemDropEvent.class, event -> event.setCancelled(true)); eventHandler.addListener(PlayerSwapItemEvent.class, event -> event.setCancelled(true)); // den Chat verteilt Velocity (chat-bridge) an das ganze Netzwerk, hier würde er doppelt stehen eventHandler.addListener(PlayerChatEvent.class, event -> event.setCancelled(true)); // der Client meldet Rechtsklicks auch auf Blöcke als Item-Nutzung, solange der Block sie nicht // selbst verarbeitet (auf der Strecke nie) – ein Event genügt, Joker werden nicht doppelt verbraucht eventHandler.addListener(PlayerUseItemEvent.class, event -> { JumpGenerator generator = games.get(event.getPlayer()); Jokers joker = Jokers.of(event.getItemStack()); if(generator != null && joker != null && generator.useJoker(joker)) joker.take(event.getPlayer()); }); eventHandler.addListener(PickupItemEvent.class, event -> { Jokers joker = Jokers.of(event.getItemStack()); if(event.getLivingEntity() instanceof Player player && joker != null) joker.add(player); else event.setCancelled(true); }); eventHandler.addListener(PlayerDisconnectEvent.class, event -> { Player p = event.getPlayer(); games.remove(p); ScoreTab.leave(p); // jede Partie hat eine eigene Welt: nach dem Verlassen freigeben releaseLater(p.getInstance()); }); Command reset = new Command("reset"); reset.setDefaultExecutor((sender, context) -> { if(sender instanceof Player player) resetGame(player); }); MinecraftServer.getCommandManager().register(reset); // nur für Admins (admins.txt): Joker zum Testen direkt geben Command joker = new Command("joker"); joker.setCondition((sender, commandString) -> sender instanceof Player player && Admins.is(player)); ArgumentWord type = ArgumentType.Word("typ").from(Jokers.keys()); joker.addSyntax((sender, context) -> Jokers.byKey(context.get(type)).add((Player) sender), type); MinecraftServer.getCommandManager().register(joker); 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.clear(player); games.put(player, new JumpGenerator(START, 0, player, world)); releaseLater(oldWorld); }); } // Wie bei Velocity: Secret aus forwarding.secret im Arbeitsverzeichnis, alternativ per Umgebungsvariable private static String readForwardingSecret() throws IOException { String fromEnv = System.getenv("VELOCITY_FORWARDING_SECRET"); if(fromEnv != null && !fromEnv.isBlank()) return fromEnv.trim(); Path file = Path.of("forwarding.secret"); if(!Files.exists(file)) throw new IllegalStateException("forwarding.secret fehlt im Arbeitsverzeichnis (oder VELOCITY_FORWARDING_SECRET setzen)"); return Files.readString(file).trim(); } }