Pumpkin/pumpkin/src/command/commands/ban.rs
2026-05-03 21:31:46 +02:00

163 lines
4.6 KiB
Rust

use crate::command::CommandResult;
use crate::{
command::{
CommandError, CommandExecutor, CommandSender,
args::{
Arg, ConsumedArgs,
gameprofile::{GameProfileSuggestionMode, GameProfilesArgumentConsumer},
message::MsgArgConsumer,
},
tree::{CommandTree, builder::argument},
},
data::{SaveJSONConfiguration, banlist_serializer::BannedPlayerEntry},
net::{DisconnectReason, GameProfile},
};
use CommandError::InvalidConsumption;
use pumpkin_data::translation;
use pumpkin_util::text::TextComponent;
const NAMES: [&str; 1] = ["ban"];
const DESCRIPTION: &str = "bans a player";
const ARG_TARGET: &str = "player";
const ARG_REASON: &str = "reason";
struct NoReasonExecutor;
impl CommandExecutor for NoReasonExecutor {
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::GameProfiles(targets)) = args.get(&ARG_TARGET) else {
return Err(InvalidConsumption(Some(ARG_TARGET.into())));
};
ban_players(sender, server, targets.as_slice(), None).await
})
}
}
struct ReasonExecutor;
impl CommandExecutor for ReasonExecutor {
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::GameProfiles(targets)) = args.get(&ARG_TARGET) else {
return Err(InvalidConsumption(Some(ARG_TARGET.into())));
};
let Some(Arg::Msg(reason)) = args.get(ARG_REASON) else {
return Err(InvalidConsumption(Some(ARG_REASON.into())));
};
ban_players(sender, server, targets.as_slice(), Some(reason)).await
})
}
}
/// Returns the number of players successfully banned.
async fn ban_players(
sender: &CommandSender,
server: &crate::server::Server,
targets: &[GameProfile],
reason: Option<&String>,
) -> Result<i32, CommandError> {
let mut count: usize = 0;
for target in targets {
if ban_profile(sender, server, target, reason.cloned()).await {
count += 1;
}
}
if count == 0 {
Err(CommandError::CommandFailed(TextComponent::translate_cross(
translation::java::COMMANDS_BAN_FAILED,
translation::bedrock::COMMANDS_BAN_FAILED,
[],
)))
} else {
Ok(count as i32)
}
}
/// Returns `true` if the player was successfully banned.
async fn ban_profile(
sender: &CommandSender,
server: &crate::server::Server,
profile: &GameProfile,
reason: Option<String>,
) -> bool {
let mut banned_players = server.data.banned_player_list.write().await;
let reason = reason.unwrap_or_else(|| "Banned by an operator.".to_string());
if let Some(entry) = banned_players
.banned_players
.iter_mut()
.find(|entry| entry.uuid == profile.id)
{
if entry.name != profile.name {
entry.name.clone_from(&profile.name);
banned_players.save();
}
return false;
}
banned_players.banned_players.push(BannedPlayerEntry::new(
profile,
sender.to_string(),
None,
reason.clone(),
));
banned_players.save();
drop(banned_players);
// Send messages
sender
.send_message(TextComponent::translate_cross(
translation::java::COMMANDS_BAN_SUCCESS,
translation::bedrock::COMMANDS_BAN_SUCCESS,
[
TextComponent::text(profile.name.clone()),
TextComponent::text(reason),
],
))
.await;
if let Some(player) = server.get_player_by_uuid(profile.id) {
player
.kick(
DisconnectReason::Kicked,
TextComponent::translate_cross(
translation::java::MULTIPLAYER_DISCONNECT_BANNED,
translation::bedrock::DISCONNECTIONSCREEN_TITLE_BANNEDBYHOST,
[],
),
)
.await;
}
true
}
pub fn init_command_tree() -> CommandTree {
CommandTree::new(NAMES, DESCRIPTION).then(
argument(
ARG_TARGET,
GameProfilesArgumentConsumer::new(GameProfileSuggestionMode::OnlinePlayers, true),
)
.execute(NoReasonExecutor)
.then(argument(ARG_REASON, MsgArgConsumer).execute(ReasonExecutor)),
)
}