From 5661a37971a67cd67cd2061d9a45f5920188fc83 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 09:22:46 +0000 Subject: [PATCH 1/6] feat: Add /servers command for server selection This commit introduces a new `/servers` command that allows players to select a server from a configurable list. The server list is managed in a new `servers.yml` file. When a player executes the `/servers` command, a UI with a list of servers is displayed. Upon selecting a server, a 3-second countdown is initiated, with a message displayed on the player's screen. If the player moves during the countdown, the teleportation is cancelled. Otherwise, the player is transferred to the selected server. This feature is implemented using the `FormConstructor` library for the UI, a `PluginTask` for the countdown, and a `PlayerMoveEvent` listener to handle movement cancellation. --- src/main/java/com/indra87g/Main.java | 39 ++++++++ .../com/indra87g/commands/ServersCommand.java | 95 +++++++++++++++++++ .../listeners/PlayerMoveListener.java | 43 +++++++++ src/main/resources/plugin.yml | 7 ++ src/main/resources/servers.yml | 10 ++ 5 files changed, 194 insertions(+) create mode 100644 src/main/java/com/indra87g/commands/ServersCommand.java create mode 100644 src/main/java/com/indra87g/listeners/PlayerMoveListener.java create mode 100644 src/main/resources/servers.yml diff --git a/src/main/java/com/indra87g/Main.java b/src/main/java/com/indra87g/Main.java index a012b65..3944d16 100644 --- a/src/main/java/com/indra87g/Main.java +++ b/src/main/java/com/indra87g/Main.java @@ -1,13 +1,52 @@ package com.indra87g; +import cn.nukkit.Player; import cn.nukkit.plugin.PluginBase; +import cn.nukkit.scheduler.PluginTask; +import cn.nukkit.utils.Config; +import com.indra87g.commands.ServersCommand; import com.indra87g.commands.SetBlockCommand; +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + public class Main extends PluginBase { + private List> servers; + private final Map> countdowns = new HashMap<>(); + private final Map teleportingPlayers = new HashMap<>(); + private final Map playerLocations = new HashMap<>(); + @Override public void onEnable() { + this.saveDefaultConfig(); + this.saveResource("servers.yml"); + + Config serversConfig = new Config(new File(this.getDataFolder(), "servers.yml"), Config.YAML); + this.servers = serversConfig.getMapList("servers"); + getLogger().info("WaffleCoreNK has been enabled."); + this.getServer().getPluginManager().registerEvents(new com.indra87g.listeners.PlayerMoveListener(this), this); this.getServer().getCommandMap().register("setblock", new SetBlockCommand()); + this.getServer().getCommandMap().register("servers", new ServersCommand(this)); + } + + public List> getServers() { + return servers; + } + + public Map> getCountdowns() { + return countdowns; + } + + public Map getTeleportingPlayers() { + return teleportingPlayers; + } + + public Map getPlayerLocations() { + return playerLocations; } } diff --git a/src/main/java/com/indra87g/commands/ServersCommand.java b/src/main/java/com/indra87g/commands/ServersCommand.java new file mode 100644 index 0000000..4cc4113 --- /dev/null +++ b/src/main/java/com/indra87g/commands/ServersCommand.java @@ -0,0 +1,95 @@ +package com.indra87g.commands; + +import cn.nukkit.Player; +import cn.nukkit.command.Command; +import cn.nukkit.command.CommandSender; +import cn.nukkit.network.protocol.TransferPacket; +import cn.nukkit.scheduler.PluginTask; +import com.github.mefrreex.formconstructor.form.SimpleForm; +import com.indra87g.Main; + +import java.util.List; +import java.util.Map; + +public class ServersCommand extends Command { + + private final Main plugin; + + public ServersCommand(Main plugin) { + super("servers", "Display a list of servers to connect to.", "/servers"); + this.plugin = plugin; + } + + @Override + public boolean execute(CommandSender sender, String commandLabel, String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage("This command can only be used by a player."); + return false; + } + + Player player = (Player) sender; + + List> servers = plugin.getServers(); + if (servers == null || servers.isEmpty()) { + player.sendMessage("There are no servers available at the moment. Please try again later."); + return true; + } + + SimpleForm form = new SimpleForm.Builder("Server Selector", "Choose a server to connect to.").build(); + + for (Map serverData : servers) { + String name = (String) serverData.get("name"); + String address = (String) serverData.get("address"); + int port = (int) serverData.get("port"); + + form.addButton(name, (p, b) -> { + if (plugin.getTeleportingPlayers().containsKey(p.getUniqueId())) { + p.sendMessage("You are already being transferred to a server."); + return; + } + + plugin.getPlayerLocations().put(p.getUniqueId(), p.getLocation()); + plugin.getTeleportingPlayers().put(p.getUniqueId(), p); + CountdownTask task = new CountdownTask(plugin, p, address, port); + plugin.getServer().getScheduler().scheduleRepeatingTask(plugin, task, 20); + plugin.getCountdowns().put(p.getUniqueId(), task); + }); + } + + form.send(player); + + return true; + } + + private static class CountdownTask extends PluginTask
{ + + private final Player player; + private final String address; + private final int port; + private int countdown = 3; + + public CountdownTask(Main owner, Player player, String address, int port) { + super(owner); + this.player = player; + this.address = address; + this.port = port; + } + + @Override + public void onRun(int currentTick) { + if (countdown > 0) { + player.sendTitle("§eTeleporting in " + countdown + "...", "", 0, 25, 5); + countdown--; + } else { + player.sendTitle("§aTransferring...", "", 0, 20, 0); + TransferPacket pk = new TransferPacket(); + pk.address = address; + pk.port = port; + player.dataPacket(pk); + this.getOwner().getTeleportingPlayers().remove(player.getUniqueId()); + this.getOwner().getCountdowns().remove(player.getUniqueId()); + this.cancel(); + } + } + } +} diff --git a/src/main/java/com/indra87g/listeners/PlayerMoveListener.java b/src/main/java/com/indra87g/listeners/PlayerMoveListener.java new file mode 100644 index 0000000..7f805d4 --- /dev/null +++ b/src/main/java/com/indra87g/listeners/PlayerMoveListener.java @@ -0,0 +1,43 @@ +package com.indra87g.listeners; + +import cn.nukkit.Player; +import cn.nukkit.event.EventHandler; +import cn.nukkit.event.Listener; +import cn.nukkit.event.player.PlayerMoveEvent; +import cn.nukkit.level.Location; +import cn.nukkit.scheduler.PluginTask; +import com.indra87g.Main; + +import java.util.UUID; + +public class PlayerMoveListener implements Listener { + + private final Main plugin; + + public PlayerMoveListener(Main plugin) { + this.plugin = plugin; + } + + @EventHandler + public void onPlayerMove(PlayerMoveEvent event) { + Player player = event.getPlayer(); + UUID playerUUID = player.getUniqueId(); + + if (plugin.getTeleportingPlayers().containsKey(playerUUID)) { + Location from = plugin.getPlayerLocations().get(playerUUID); + Location to = event.getTo(); + + if (from.getFloorX() != to.getFloorX() || from.getFloorZ() != to.getFloorZ()) { + plugin.getTeleportingPlayers().remove(playerUUID); + plugin.getPlayerLocations().remove(playerUUID); + + PluginTask task = plugin.getCountdowns().remove(playerUUID); + if (task != null) { + task.cancel(); + } + + player.sendTitle("§cTeleportation cancelled.", "You moved.", 0, 20, 5); + } + } + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index d121440..5a604bf 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -9,7 +9,14 @@ commands: description: "Sets a block at a specific location" usage: "/setblock " permission: waffle.setblock + servers: + description: "Shows a list of servers" + usage: "/servers" + permission: waffle.servers permissions: waffle.setblock: description: "Allows the user to use the /setblock command" default: op + waffle.servers: + description: "Allows the user to use the /servers command" + default: true diff --git a/src/main/resources/servers.yml b/src/main/resources/servers.yml new file mode 100644 index 0000000..f14a7b3 --- /dev/null +++ b/src/main/resources/servers.yml @@ -0,0 +1,10 @@ +servers: + - name: "Lobby" + address: "lobby.example.com" + port: 19132 + - name: "Survival" + address: "survival.example.com" + port: 19133 + - name: "Creative" + address: "creative.example.com" + port: 19134 From fe0fff6b137eeac356f9b8cdd621b236f434f0b1 Mon Sep 17 00:00:00 2001 From: Indra Sah Noeldy Date: Tue, 23 Sep 2025 18:19:31 +0700 Subject: [PATCH 2/6] Update pom.xml --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 8a5ff2c..cc0c3a4 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,7 @@ jitpack.io https://jitpack.io + cn.nukkit From a87d5062a4053fcac05a1dfcaa2d198f0959ee2c Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 11:27:59 +0000 Subject: [PATCH 3/6] feat: Add /servers command for server selection This commit introduces a new `/servers` command that allows players to select a server from a configurable list. The server list is managed in a new `servers.yml` file. When a player executes the `/servers` command, a UI with a list of servers is displayed. Upon selecting a server, a 3-second countdown is initiated, with a message displayed on the player's screen. If the player moves during the countdown, the teleportation is cancelled. Otherwise, the player is transferred to the selected server. This feature is implemented using the `FormConstructor` library for the UI, a `PluginTask` for the countdown, and a `PlayerMoveEvent` listener to handle movement cancellation. Fixes compilation errors by: - Updating the FormConstructor dependency information in `pom.xml`. - Correcting the import path in `ServersCommand.java`. - Fixing a syntax error in `pom.xml` (unclosed `repositories` tag). --- pom.xml | 14 +++++++------- .../java/com/indra87g/commands/ServersCommand.java | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index cc0c3a4..e3d1e3c 100644 --- a/pom.xml +++ b/pom.xml @@ -35,9 +35,9 @@ nukkitx https://repo.opencollab.dev/main/ - - jitpack.io - https://jitpack.io + + luminiadev-repository-releases + https://repo.luminiadev.com/releases @@ -47,10 +47,10 @@ 1.0-SNAPSHOT provided - - com.github.MEFRREEX - FormConstructor - 2.0.0 + + com.mefrreex.formconstructor + formconstructor + 2.0.0 me.onebone diff --git a/src/main/java/com/indra87g/commands/ServersCommand.java b/src/main/java/com/indra87g/commands/ServersCommand.java index 4cc4113..7891b15 100644 --- a/src/main/java/com/indra87g/commands/ServersCommand.java +++ b/src/main/java/com/indra87g/commands/ServersCommand.java @@ -5,7 +5,7 @@ import cn.nukkit.command.CommandSender; import cn.nukkit.network.protocol.TransferPacket; import cn.nukkit.scheduler.PluginTask; -import com.github.mefrreex.formconstructor.form.SimpleForm; +import com.mefrreex.formconstructor.form.SimpleForm; import com.indra87g.Main; import java.util.List; From 3e7a59b340a1ed7325cf1ed93b429eb668da1013 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 11:36:12 +0000 Subject: [PATCH 4/6] feat: Add /servers command for server selection This commit introduces a new `/servers` command that allows players to select a server from a configurable list. The server list is managed in a new `servers.yml` file. When a player executes the `/servers` command, a UI with a list of servers is displayed. Upon selecting a server, a 3-second countdown is initiated, with a message displayed on the player's screen. If the player moves during the countdown, the teleportation is cancelled. Otherwise, the player is transferred to the selected server. This feature is implemented using the `FormConstructor` library for the UI, a `PluginTask` for the countdown, and a `PlayerMoveEvent` listener to handle movement cancellation. Fixes compilation errors by: - Updating the FormConstructor dependency information in `pom.xml` to use the correct repository (JitPack) and group/artifact IDs for version 2.0.0. - Correcting the import path in `ServersCommand.java`. - Fixing a syntax error in `pom.xml` (unclosed `repositories` tag). --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index e3d1e3c..d612b62 100644 --- a/pom.xml +++ b/pom.xml @@ -36,8 +36,8 @@ https://repo.opencollab.dev/main/ - luminiadev-repository-releases - https://repo.luminiadev.com/releases + jitpack.io + https://jitpack.io From c200cab7d399febfbc8835ae8e8cf31e0ac6253e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 11:47:10 +0000 Subject: [PATCH 5/6] refactor: Switch from FormConstructor to native FormAPI This commit refactors the server selector feature to use Nukkit's built-in FormAPI instead of the external `FormConstructor` library, which was causing persistent compilation issues. The functionality remains identical: - A `/servers` command displays a UI with a list of servers from `servers.yml`. - Selecting a server starts a 3-second countdown. - Moving during the countdown cancels the teleport. - Otherwise, the player is transferred to the selected server. The implementation has been updated to use `FormWindowSimple` for the UI and a `PlayerFormRespondedEvent` listener to handle player input, removing the need for the external dependency. --- pom.xml | 9 --- src/main/java/com/indra87g/Main.java | 5 +- .../com/indra87g/commands/ServersCommand.java | 78 ++++++++++++++----- 3 files changed, 63 insertions(+), 29 deletions(-) diff --git a/pom.xml b/pom.xml index d612b62..c37cb71 100644 --- a/pom.xml +++ b/pom.xml @@ -35,10 +35,6 @@ nukkitx https://repo.opencollab.dev/main/ - - jitpack.io - https://jitpack.io - @@ -47,11 +43,6 @@ 1.0-SNAPSHOT provided - - com.mefrreex.formconstructor - formconstructor - 2.0.0 - me.onebone EconomyAPI diff --git a/src/main/java/com/indra87g/Main.java b/src/main/java/com/indra87g/Main.java index 3944d16..34ce3af 100644 --- a/src/main/java/com/indra87g/Main.java +++ b/src/main/java/com/indra87g/Main.java @@ -29,9 +29,12 @@ public void onEnable() { this.servers = serversConfig.getMapList("servers"); getLogger().info("WaffleCoreNK has been enabled."); + + ServersCommand serversCommand = new ServersCommand(this); this.getServer().getPluginManager().registerEvents(new com.indra87g.listeners.PlayerMoveListener(this), this); + this.getServer().getPluginManager().registerEvents(serversCommand, this); this.getServer().getCommandMap().register("setblock", new SetBlockCommand()); - this.getServer().getCommandMap().register("servers", new ServersCommand(this)); + this.getServer().getCommandMap().register("servers", serversCommand); } public List> getServers() { diff --git a/src/main/java/com/indra87g/commands/ServersCommand.java b/src/main/java/com/indra87g/commands/ServersCommand.java index 7891b15..87dd3e3 100644 --- a/src/main/java/com/indra87g/commands/ServersCommand.java +++ b/src/main/java/com/indra87g/commands/ServersCommand.java @@ -3,17 +3,24 @@ import cn.nukkit.Player; import cn.nukkit.command.Command; import cn.nukkit.command.CommandSender; +import cn.nukkit.event.EventHandler; +import cn.nukkit.event.Listener; +import cn.nukkit.event.player.PlayerFormRespondedEvent; +import cn.nukkit.form.element.ElementButton; import cn.nukkit.network.protocol.TransferPacket; import cn.nukkit.scheduler.PluginTask; -import com.mefrreex.formconstructor.form.SimpleForm; +import cn.nukkit.form.window.FormWindowSimple; import com.indra87g.Main; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; -public class ServersCommand extends Command { +public class ServersCommand extends Command implements Listener { private final Main plugin; + private final Map formIDs = new HashMap<>(); public ServersCommand(Main plugin) { super("servers", "Display a list of servers to connect to.", "/servers"); @@ -35,30 +42,54 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) return true; } - SimpleForm form = new SimpleForm.Builder("Server Selector", "Choose a server to connect to.").build(); - + FormWindowSimple form = new FormWindowSimple("Server Selector", "Choose a server to connect to."); for (Map serverData : servers) { - String name = (String) serverData.get("name"); - String address = (String) serverData.get("address"); - int port = (int) serverData.get("port"); + form.addButton(new ElementButton((String) serverData.get("name"))); + } + + int formId = player.showFormWindow(form); + formIDs.put(player.getUniqueId(), formId); + + return true; + } - form.addButton(name, (p, b) -> { - if (plugin.getTeleportingPlayers().containsKey(p.getUniqueId())) { - p.sendMessage("You are already being transferred to a server."); + @EventHandler + public void onFormResponse(PlayerFormRespondedEvent event) { + Player player = event.getPlayer(); + UUID playerUUID = player.getUniqueId(); + + if (formIDs.containsKey(playerUUID) && formIDs.get(playerUUID) == event.getFormID()) { + formIDs.remove(playerUUID); // Prevent re-handling + + if (event.getResponse() == null) { + return; // Form was closed + } + + if (event.getWindow() instanceof FormWindowSimple) { + FormWindowSimple form = (FormWindowSimple) event.getWindow(); + int buttonIndex = form.getResponse().getClickedButtonId(); + + List> servers = plugin.getServers(); + if (servers == null || buttonIndex >= servers.size()) { + return; // Should not happen + } + + Map serverData = servers.get(buttonIndex); + String address = (String) serverData.get("address"); + int port = (int) serverData.get("port"); + + if (plugin.getTeleportingPlayers().containsKey(playerUUID)) { + player.sendMessage("You are already being transferred to a server."); return; } - plugin.getPlayerLocations().put(p.getUniqueId(), p.getLocation()); - plugin.getTeleportingPlayers().put(p.getUniqueId(), p); - CountdownTask task = new CountdownTask(plugin, p, address, port); + plugin.getPlayerLocations().put(playerUUID, player.getLocation()); + plugin.getTeleportingPlayers().put(playerUUID, player); + CountdownTask task = new CountdownTask(plugin, player, address, port); plugin.getServer().getScheduler().scheduleRepeatingTask(plugin, task, 20); - plugin.getCountdowns().put(p.getUniqueId(), task); - }); + plugin.getCountdowns().put(playerUUID, task); + } } - - form.send(player); - - return true; } private static class CountdownTask extends PluginTask
{ @@ -77,6 +108,14 @@ public CountdownTask(Main owner, Player player, String address, int port) { @Override public void onRun(int currentTick) { + if (!player.isOnline() || !this.getOwner().getTeleportingPlayers().containsKey(player.getUniqueId())) { + this.cancel(); + this.getOwner().getTeleportingPlayers().remove(player.getUniqueId()); + this.getOwner().getCountdowns().remove(player.getUniqueId()); + this.getOwner().getPlayerLocations().remove(player.getUniqueId()); + return; + } + if (countdown > 0) { player.sendTitle("§eTeleporting in " + countdown + "...", "", 0, 25, 5); countdown--; @@ -88,6 +127,7 @@ public void onRun(int currentTick) { player.dataPacket(pk); this.getOwner().getTeleportingPlayers().remove(player.getUniqueId()); this.getOwner().getCountdowns().remove(player.getUniqueId()); + this.getOwner().getPlayerLocations().remove(player.getUniqueId()); this.cancel(); } } From b616354791e5f3490399bd7a4b042f90eac4607f Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 11:57:22 +0000 Subject: [PATCH 6/6] refactor: Switch from FormConstructor to native FormAPI This commit refactors the server selector feature to use Nukkit's built-in FormAPI instead of the external `FormConstructor` library, which was causing persistent compilation issues. The functionality remains identical: - A `/servers` command displays a UI with a list of servers from `servers.yml`. - Selecting a server starts a 3-second countdown. - Moving during the countdown cancels the teleport. - Otherwise, the player is transferred to the selected server. This also fixes compilation errors related to incompatible types when reading the server list from the configuration by using the correct `List` data type. --- src/main/java/com/indra87g/Main.java | 4 ++-- .../java/com/indra87g/commands/ServersCommand.java | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/indra87g/Main.java b/src/main/java/com/indra87g/Main.java index 34ce3af..f654460 100644 --- a/src/main/java/com/indra87g/Main.java +++ b/src/main/java/com/indra87g/Main.java @@ -15,7 +15,7 @@ public class Main extends PluginBase { - private List> servers; + private List servers; private final Map> countdowns = new HashMap<>(); private final Map teleportingPlayers = new HashMap<>(); private final Map playerLocations = new HashMap<>(); @@ -37,7 +37,7 @@ public void onEnable() { this.getServer().getCommandMap().register("servers", serversCommand); } - public List> getServers() { + public List getServers() { return servers; } diff --git a/src/main/java/com/indra87g/commands/ServersCommand.java b/src/main/java/com/indra87g/commands/ServersCommand.java index 87dd3e3..417ac06 100644 --- a/src/main/java/com/indra87g/commands/ServersCommand.java +++ b/src/main/java/com/indra87g/commands/ServersCommand.java @@ -36,15 +36,15 @@ public boolean execute(CommandSender sender, String commandLabel, String[] args) Player player = (Player) sender; - List> servers = plugin.getServers(); + List servers = plugin.getServers(); if (servers == null || servers.isEmpty()) { player.sendMessage("There are no servers available at the moment. Please try again later."); return true; } FormWindowSimple form = new FormWindowSimple("Server Selector", "Choose a server to connect to."); - for (Map serverData : servers) { - form.addButton(new ElementButton((String) serverData.get("name"))); + for (Map serverData : servers) { + form.addButton(new ElementButton(String.valueOf(serverData.get("name")))); } int formId = player.showFormWindow(form); @@ -69,14 +69,14 @@ public void onFormResponse(PlayerFormRespondedEvent event) { FormWindowSimple form = (FormWindowSimple) event.getWindow(); int buttonIndex = form.getResponse().getClickedButtonId(); - List> servers = plugin.getServers(); + List servers = plugin.getServers(); if (servers == null || buttonIndex >= servers.size()) { return; // Should not happen } - Map serverData = servers.get(buttonIndex); - String address = (String) serverData.get("address"); - int port = (int) serverData.get("port"); + Map serverData = servers.get(buttonIndex); + String address = String.valueOf(serverData.get("address")); + int port = ((Number) serverData.get("port")).intValue(); if (plugin.getTeleportingPlayers().containsKey(playerUUID)) { player.sendMessage("You are already being transferred to a server.");