Pumpkin/crates/pumpkin/src/command/commands/transfer.rs
2026-08-26 16:45:10 +02:00

171 lines
6.0 KiB
Rust

use pumpkin_protocol::bedrock::client::transfer::CTransfer as BedrockCTransfer;
use pumpkin_protocol::codec::var_int::VarInt;
use pumpkin_protocol::java::client::play::CTransfer as JavaCTransfer;
use pumpkin_util::text::TextComponent;
use tracing::info;
use crate::command::CommandResult;
use crate::command::args::bounded_num::BoundedNumArgumentConsumer;
use crate::command::args::players::PlayersArgumentConsumer;
use crate::command::args::simple::SimpleArgConsumer;
use crate::command::args::{Arg, FindArgDefaultName};
use crate::command::dispatcher::CommandError::{self, InvalidConsumption, InvalidRequirement};
use crate::command::tree::builder::{argument, argument_default_name, require};
use crate::command::{CommandExecutor, CommandSender, args::ConsumedArgs, tree::CommandTree};
use crate::entity::EntityBase;
const NAMES: [&str; 1] = ["transfer"];
const DESCRIPTION: &str = "Triggers a transfer of a player to another server.";
const ARG_HOSTNAME: &str = "hostname";
const ARG_PLAYERS: &str = "players";
const fn port_consumer() -> BoundedNumArgumentConsumer<i32> {
BoundedNumArgumentConsumer::new()
.name("port")
.min(1)
.max(65535)
}
struct TargetSelfExecutor;
impl CommandExecutor for TargetSelfExecutor {
fn execute<'a>(
&'a self,
sender: &'a CommandSender,
_server: &'a crate::server::Server,
args: &'a ConsumedArgs<'a>,
) -> CommandResult<'a> {
Box::pin(async move {
let Some(Arg::Simple(hostname)) = args.get(ARG_HOSTNAME) else {
return Err(InvalidConsumption(Some(ARG_HOSTNAME.into())));
};
let port = match port_consumer().find_arg_default_name(args) {
Err(_) => 25565,
Ok(Ok(count)) => count,
Ok(Err(_)) => {
return Err(InvalidConsumption(Some(
"Port must be between 1 and 65535.".into(),
)));
}
};
if let CommandSender::Player(player) = sender {
let name = &player.gameprofile.name;
info!("[{name}: Transferring {name} to {hostname}:{port}]");
player
.enqueue_packet_editioned(
&JavaCTransfer::new(hostname, VarInt(port)),
&BedrockCTransfer::new(hostname.to_string(), port as u16, false),
)
.await;
Ok(1)
} else {
Err(InvalidRequirement)
}
})
}
}
struct TargetPlayerExecutor;
impl CommandExecutor for TargetPlayerExecutor {
fn execute<'a>(
&'a self,
sender: &'a CommandSender,
_server: &'a crate::server::Server,
args: &'a ConsumedArgs<'a>,
) -> CommandResult<'a> {
Box::pin(async move {
let Some(Arg::Simple(hostname)) = args.get(ARG_HOSTNAME) else {
return Err(InvalidConsumption(Some(ARG_HOSTNAME.into())));
};
let hostname = *hostname;
let port = match port_consumer().find_arg_default_name(args) {
Err(_) => 25565,
Ok(Ok(count)) => count,
Ok(Err(_)) => {
return Err(InvalidConsumption(Some(
"Port must be between 1 and 65535.".into(),
)));
}
};
let Some(Arg::Players(players)) = args.get(ARG_PLAYERS) else {
return Err(InvalidConsumption(Some(ARG_PLAYERS.into())));
};
if players.is_empty() {
return Err(CommandError::CommandFailed(TextComponent::translate_cross(
"commands.transfer.error.no_players",
"commands.transfer.error.no_players",
[],
)));
}
for p in players {
p.enqueue_packet_editioned(
&JavaCTransfer::new(hostname, VarInt(port)),
&BedrockCTransfer::new(hostname.to_string(), port as u16, false),
)
.await;
info!(
"[{sender}: Transferring {} to {hostname}:{port}]",
p.gameprofile.name
);
}
if players.len() == 1 {
sender
.send_message(TextComponent::translate_cross(
"commands.transfer.success.single",
"commands.transfer.success.single",
[
players[0].get_display_name(),
TextComponent::text(hostname.to_owned()),
TextComponent::text(port.to_string()),
],
))
.await;
} else {
sender
.send_message(TextComponent::translate_cross(
"commands.transfer.success.multiple",
"commands.transfer.success.multiple",
[
TextComponent::text(players.len().to_string()),
TextComponent::text(hostname.to_owned()),
TextComponent::text(port.to_string()),
],
))
.await;
}
Ok(players.len() as i32)
})
}
}
#[expect(clippy::redundant_closure_for_method_calls)]
pub fn init_command_tree() -> CommandTree {
CommandTree::new(NAMES, DESCRIPTION).then(
argument(ARG_HOSTNAME, SimpleArgConsumer)
.then(require(|sender| sender.is_player()).execute(TargetSelfExecutor))
.then(
argument_default_name(port_consumer())
.then(require(|sender| sender.is_player()).execute(TargetSelfExecutor))
.then(
argument(ARG_PLAYERS, PlayersArgumentConsumer)
.execute(TargetPlayerExecutor),
),
),
)
}