Add support for transfers; closes #81

Signed-off-by: Joshua Castle <26531652+Kas-tle@users.noreply.github.com>
This commit is contained in:
Joshua Castle 2025-07-05 23:58:46 -07:00
parent 1bfe4cf7d4
commit ec697a133f
No known key found for this signature in database
GPG Key ID: 7ECA1A2FC38ABA9F
7 changed files with 193 additions and 1 deletions

View File

@ -24,7 +24,7 @@
<groupId>com.loohp</groupId>
<artifactId>Limbo</artifactId>
<name>Limbo</name>
<version>0.7.15-ALPHA</version>
<version>0.7.16-ALPHA</version>
<description>Standalone Limbo Minecraft Server.</description>
<url>https://github.com/LOOHP/Limbo</url>

View File

@ -41,6 +41,7 @@ import com.loohp.limbo.player.Player;
import com.loohp.limbo.plugins.LimboPlugin;
import com.loohp.limbo.plugins.PluginManager;
import com.loohp.limbo.scheduler.LimboScheduler;
import com.loohp.limbo.scheduler.LimboTask;
import com.loohp.limbo.scheduler.Tick;
import com.loohp.limbo.utils.CustomStringUtils;
import com.loohp.limbo.utils.ImageUtils;
@ -542,4 +543,31 @@ public final class Limbo {
}
}
public void transferAll(String host, int port, int batchDelayTicks, int batchSize) {
if (batchDelayTicks <= 0 || batchSize <= 0) {
for (Player player : getPlayers()) {
player.transfer(host, port);
}
} else {
long totalDelay = 0;
Set<Player> currentBatch = new HashSet<>();
List<Player> players = new ArrayList<>(getPlayers());
for (int i = 0; i < players.size(); i++) {
currentBatch.add(players.get(i));
if (currentBatch.size() < batchSize && i != players.size() - 1) {
continue;
}
Set<Player> batch = new HashSet<>(currentBatch);
LimboTask task = () -> {
for (Player p : batch) {
p.transfer(host, port);
}
};
Limbo.getInstance().getScheduler().runTaskLater(null, task, totalDelay);
totalDelay += batchDelayTicks;
currentBatch.clear();
}
}
}
}

View File

@ -180,6 +180,77 @@ public class DefaultCommands implements CommandExecutor, TabCompletor {
}
return;
}
if (args[0].equalsIgnoreCase("transfer")) {
if (sender.hasPermission("limboserver.transfer")) {
if (args.length == 4) {
Player player = Limbo.getInstance().getPlayer(args[1]);
if (player != null) {
String host = args[2];
int port;
try {
port = Integer.parseInt(args[3]);
} catch (NumberFormatException e) {
sender.sendMessage(ChatColor.RED + "Invalid port number!");
return;
}
player.transfer(host, port);
sender.sendMessage(ChatColor.GOLD + "Transferred " + player.getName() + " to " + host + ":" + port);
} else {
sender.sendMessage(ChatColor.RED + "Player is not online!");
}
} else {
sender.sendMessage(ChatColor.RED + "Invalid usage! Use: /transfer <player> <host> <port>");
}
} else {
sender.sendMessage(ChatColor.RED + "You do not have permission to use that command!");
}
return;
}
if (args[0].equalsIgnoreCase("transferall")) {
if (sender.hasPermission("limboserver.transfer")) {
if (args.length >= 3 && args.length <= 5) {
String host = args[1];
int port;
int batchDelay;
int batchSize;
try {
port = Integer.parseInt(args[2]);
} catch (NumberFormatException e) {
sender.sendMessage(ChatColor.RED + "Invalid port!");
return;
}
if (args.length == 4) {
try {
batchDelay = Integer.parseInt(args[3]);
} catch (NumberFormatException e) {
sender.sendMessage(ChatColor.RED + "Invalid batch delay ticks!");
return;
}
batchSize = 1;
} else if (args.length == 5) {
try {
batchDelay = Integer.parseInt(args[3]);
batchSize = Integer.parseInt(args[4]);
} catch (NumberFormatException e) {
sender.sendMessage(ChatColor.RED + "Invalid batch delay ticks or size!");
return;
}
} else {
batchDelay = 1;
batchSize = 10;
}
Limbo.getInstance().transferAll(host, port, batchDelay, batchSize);
sender.sendMessage(ChatColor.GOLD + "Transferred all players to " + host + ":" + port);
} else {
sender.sendMessage(ChatColor.RED + "Invalid usage! Use: /transferall <host> <port> [<batchDelayTicks> <batchSize>]");
}
} else {
sender.sendMessage(ChatColor.RED + "You do not have permission to use that command!");
}
return;
}
}
@Override
@ -202,6 +273,10 @@ public class DefaultCommands implements CommandExecutor, TabCompletor {
if (sender.hasPermission("limboserver.gamemode")) {
tab.add("gamemode");
}
if (sender.hasPermission("limboserver.transfer")) {
tab.add("transfer");
tab.add("transferall");
}
break;
case 1:
if (sender.hasPermission("limboserver.spawn")) {
@ -229,6 +304,14 @@ public class DefaultCommands implements CommandExecutor, TabCompletor {
tab.add("gamemode");
}
}
if (sender.hasPermission("limboserver.transfer")) {
if ("transfer".startsWith(args[0].toLowerCase())) {
tab.add("transfer");
}
if ("transferall".startsWith(args[0].toLowerCase())) {
tab.add("transferall");
}
}
break;
case 2:
if (sender.hasPermission("limboserver.kick")) {
@ -249,6 +332,15 @@ public class DefaultCommands implements CommandExecutor, TabCompletor {
}
}
}
if (sender.hasPermission("limboserver.transfer")) {
if (args[0].equalsIgnoreCase("transfer")) {
for (Player player : Limbo.getInstance().getPlayers()) {
if (player.getName().toLowerCase().startsWith(args[1].toLowerCase())) {
tab.add(player.getName());
}
}
}
}
break;
case 3:
if (sender.hasPermission("limboserver.gamemode")) {

View File

@ -0,0 +1,59 @@
/*
* This file is part of Limbo.
*
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
* Copyright (C) 2022. Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.loohp.limbo.network.protocol.packets;
import com.loohp.limbo.registry.PacketRegistry;
import com.loohp.limbo.utils.DataTypeIO;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class ClientboundTransferPacket extends PacketOut {
private final String host;
private final int port;
public ClientboundTransferPacket(String host, int port) {
this.host = host;
this.port = port;
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
@Override
public byte[] serializePacket() throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
DataOutputStream output = new DataOutputStream(buffer);
output.writeByte(PacketRegistry.getPacketId(getClass()));
DataTypeIO.writeString(output, host, StandardCharsets.UTF_8);
DataTypeIO.writeVarInt(output, port);
return buffer.toByteArray();
}
}

View File

@ -45,6 +45,7 @@ import com.loohp.limbo.network.protocol.packets.ClientboundSetSubtitleTextPacket
import com.loohp.limbo.network.protocol.packets.ClientboundSetTitleTextPacket;
import com.loohp.limbo.network.protocol.packets.ClientboundSetTitlesAnimationPacket;
import com.loohp.limbo.network.protocol.packets.ClientboundSystemChatPacket;
import com.loohp.limbo.network.protocol.packets.ClientboundTransferPacket;
import com.loohp.limbo.network.protocol.packets.PacketOut;
import com.loohp.limbo.network.protocol.packets.PacketPlayOutCloseWindow;
import com.loohp.limbo.network.protocol.packets.PacketPlayOutGameStateChange;
@ -650,4 +651,13 @@ public class Player extends LivingEntity implements CommandSender, InventoryHold
public InventoryHolder getHolder() {
return this;
}
public void transfer(String host, int port) {
try {
ClientboundTransferPacket transferPacket = new ClientboundTransferPacket(host, port);
clientConnection.sendPacket(transferPacket);
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@ -35,6 +35,7 @@ import com.loohp.limbo.network.protocol.packets.ClientboundSetSubtitleTextPacket
import com.loohp.limbo.network.protocol.packets.ClientboundSetTitleTextPacket;
import com.loohp.limbo.network.protocol.packets.ClientboundSetTitlesAnimationPacket;
import com.loohp.limbo.network.protocol.packets.ClientboundSystemChatPacket;
import com.loohp.limbo.network.protocol.packets.ClientboundTransferPacket;
import com.loohp.limbo.network.protocol.packets.Packet;
import com.loohp.limbo.network.protocol.packets.PacketHandshakingIn;
import com.loohp.limbo.network.protocol.packets.PacketLoginInLoginStart;
@ -220,6 +221,7 @@ public class PacketRegistry {
registerClass(PacketPlayOutWindowData.class, "minecraft:container_set_data", NetworkPhase.PLAY, PacketBound.CLIENTBOUND);
registerClass(ClientboundChunkBatchFinishedPacket.class, "minecraft:chunk_batch_finished", NetworkPhase.PLAY, PacketBound.CLIENTBOUND);
registerClass(ClientboundChunkBatchStartPacket.class, "minecraft:chunk_batch_start", NetworkPhase.PLAY, PacketBound.CLIENTBOUND);
registerClass(ClientboundTransferPacket.class, "minecraft:transfer", NetworkPhase.PLAY, PacketBound.CLIENTBOUND);
}
private static void registerClass(Class<? extends Packet> packetClass, String key, NetworkPhase networkPhase, PacketBound packetBound) {

View File

@ -4,6 +4,7 @@ groups:
- limboserver.kick
- limboserver.say
- limboserver.gamemode
- limboserver.transfer
default:
- limboserver.spawn
- limboserver.chat