Skip to content

Commit

Permalink
Add transfer command (#285)
Browse files Browse the repository at this point in the history
* Add transfer command

* implement fmt::Display for CommandSender
  • Loading branch information
Commandcracker authored Nov 16, 2024
1 parent e419045 commit caff0df
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 11 deletions.
16 changes: 16 additions & 0 deletions pumpkin-protocol/src/client/play/c_transfer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use crate::VarInt;
use pumpkin_macros::client_packet;
use serde::Serialize;

#[derive(Serialize)]
#[client_packet("play:transfer")]
pub struct CTransfer<'a> {
host: &'a str,
port: &'a VarInt,
}

impl<'a> CTransfer<'a> {
pub fn new(host: &'a str, port: &'a VarInt) -> Self {
Self { host, port }
}
}
2 changes: 2 additions & 0 deletions pumpkin-protocol/src/client/play/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ mod c_subtitle;
mod c_sync_player_position;
mod c_system_chat_message;
mod c_teleport_entity;
mod c_transfer;
mod c_unload_chunk;
mod c_update_entity_pos;
mod c_update_entity_pos_rot;
Expand Down Expand Up @@ -113,6 +114,7 @@ pub use c_subtitle::*;
pub use c_sync_player_position::*;
pub use c_system_chat_message::*;
pub use c_teleport_entity::*;
pub use c_transfer::*;
pub use c_unload_chunk::*;
pub use c_update_entity_pos::*;
pub use c_update_entity_pos_rot::*;
Expand Down
6 changes: 0 additions & 6 deletions pumpkin/src/command/commands/cmd_say.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,6 @@ impl CommandExecutor for SayExecutor {
server: &crate::server::Server,
args: &ConsumedArgs<'a>,
) -> Result<(), CommandError> {
let sender = match sender {
CommandSender::Console => "Console",
CommandSender::Rcon(_) => "Rcon",
CommandSender::Player(player) => &player.gameprofile.name,
};

let Some(Arg::Msg(msg)) = args.get(ARG_MESSAGE) else {
return Err(InvalidConsumption(Some(ARG_MESSAGE.into())));
};
Expand Down
136 changes: 136 additions & 0 deletions pumpkin/src/command/commands/cmd_transfer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
use async_trait::async_trait;
use pumpkin_core::text::color::{Color, NamedColor};
use pumpkin_core::text::TextComponent;
use pumpkin_protocol::client::play::CTransfer;
use pumpkin_protocol::VarInt;

use crate::command::args::arg_bounded_num::BoundedNumArgumentConsumer;
use crate::command::args::arg_players::PlayersArgumentConsumer;
use crate::command::args::arg_simple::SimpleArgConsumer;
use crate::command::args::{Arg, FindArgDefaultName};
use crate::command::dispatcher::CommandError::{InvalidConsumption, InvalidRequirement};
use crate::command::tree_builder::{argument, argument_default_name, require};
use crate::command::{
args::ConsumedArgs, tree::CommandTree, CommandError, CommandExecutor, CommandSender,
};
use crate::entity::player::PermissionLvl;

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";

static PORT_CONSUMER: BoundedNumArgumentConsumer<i32> = BoundedNumArgumentConsumer::new()
.name("port")
.min(1)
.max(65535);

struct TransferTargetSelf;

#[async_trait]
impl CommandExecutor for TransferTargetSelf {
async fn execute<'a>(
&self,
sender: &mut CommandSender<'a>,
_server: &crate::server::Server,
args: &ConsumedArgs<'a>,
) -> Result<(), CommandError> {
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(())) => {
sender
.send_message(
TextComponent::text("Port must be between 1 and 65535.")
.color(Color::Named(NamedColor::Red)),
)
.await;
return Ok(());
}
};

if let CommandSender::Player(player) = sender {
let name = &player.gameprofile.name;
log::info!("[{name}: Transferring {name} to {hostname}:{port}]");
player
.client
.send_packet(&CTransfer::new(hostname, &VarInt(port)))
.await;
Ok(())
} else {
Err(InvalidRequirement)
}
}
}

struct TransferTargetPlayer;

#[async_trait]
impl CommandExecutor for TransferTargetPlayer {
async fn execute<'a>(
&self,
sender: &mut CommandSender<'a>,
_server: &crate::server::Server,
args: &ConsumedArgs<'a>,
) -> Result<(), CommandError> {
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(())) => {
sender
.send_message(
TextComponent::text("Port must be between 1 and 65535.")
.color(Color::Named(NamedColor::Red)),
)
.await;
return Ok(());
}
};

let Some(Arg::Players(players)) = args.get(ARG_PLAYERS) else {
return Err(InvalidConsumption(Some(ARG_PLAYERS.into())));
};

for p in players {
p.client
.send_packet(&CTransfer::new(hostname, &VarInt(port)))
.await;
log::info!(
"[{sender}: Transferring {} to {hostname}:{port}]",
p.gameprofile.name
);
}

Ok(())
}
}

pub fn init_command_tree<'a>() -> CommandTree<'a> {
CommandTree::new(NAMES, DESCRIPTION).with_child(
require(&|sender| sender.has_permission_lvl(PermissionLvl::Three)).with_child(
argument(ARG_HOSTNAME, &SimpleArgConsumer)
.with_child(require(&|sender| sender.is_player()).execute(&TransferTargetSelf))
.with_child(
argument_default_name(&PORT_CONSUMER)
.with_child(
require(&|sender| sender.is_player()).execute(&TransferTargetSelf),
)
.with_child(
argument(ARG_PLAYERS, &PlayersArgumentConsumer)
.execute(&TransferTargetPlayer),
),
),
),
)
}
1 change: 1 addition & 0 deletions pumpkin/src/command/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ pub mod cmd_say;
pub mod cmd_setblock;
pub mod cmd_stop;
pub mod cmd_teleport;
pub mod cmd_transfer;
pub mod cmd_worldborder;
26 changes: 21 additions & 5 deletions pumpkin/src/command/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
use std::fmt;
use std::sync::Arc;

use crate::command::commands::cmd_transfer;
use crate::command::dispatcher::CommandDispatcher;
use crate::entity::player::{PermissionLvl, Player};
use crate::server::Server;
use crate::world::World;
use args::ConsumedArgs;
use async_trait::async_trait;
use commands::{
Expand All @@ -10,11 +16,6 @@ use dispatcher::CommandError;
use pumpkin_core::math::vector3::Vector3;
use pumpkin_core::text::TextComponent;

use crate::command::dispatcher::CommandDispatcher;
use crate::entity::player::{PermissionLvl, Player};
use crate::server::Server;
use crate::world::World;

pub mod args;
pub mod client_cmd_suggestions;
mod commands;
Expand All @@ -29,6 +30,20 @@ pub enum CommandSender<'a> {
Player(Arc<Player>),
}

impl<'a> fmt::Display for CommandSender<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}",
match self {
CommandSender::Console => "Server",
CommandSender::Rcon(_) => "Rcon",
CommandSender::Player(p) => &p.gameprofile.name,
}
)
}
}

impl<'a> CommandSender<'a> {
pub async fn send_message(&self, text: TextComponent<'a>) {
match self {
Expand Down Expand Up @@ -108,6 +123,7 @@ pub fn default_dispatcher<'a>() -> Arc<CommandDispatcher<'a>> {
dispatcher.register(cmd_list::init_command_tree());
dispatcher.register(cmd_clear::init_command_tree());
dispatcher.register(cmd_setblock::init_command_tree());
dispatcher.register(cmd_transfer::init_command_tree());

Arc::new(dispatcher)
}
Expand Down

0 comments on commit caff0df

Please sign in to comment.