Translate almost everything
The command output in Console/RCON is a bit broken now, i will fix that tomorrow, But in-game it works great
This commit is contained in:
parent
1a3b7e0ad6
commit
e7f1420da3
@ -42,14 +42,15 @@ impl TextComponent {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn translate<P>(key: P, with: Vec<Cow<'static, str>>) -> Self
|
||||
pub fn translate<K, W>(key: K, with: W) -> Self
|
||||
where
|
||||
P: Into<Cow<'static, str>>,
|
||||
K: Into<Cow<'static, str>>,
|
||||
W: Into<Vec<Cow<'static, str>>>,
|
||||
{
|
||||
Self {
|
||||
content: TextContent::Translate {
|
||||
translate: key.into(),
|
||||
with,
|
||||
with: with.into(),
|
||||
},
|
||||
style: Style::default(),
|
||||
extra: vec![],
|
||||
@ -61,9 +62,19 @@ impl TextComponent {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_text(self) -> String {
|
||||
match self.content {
|
||||
TextContent::Text { text } => text.into_owned(),
|
||||
TextContent::Translate { translate, with: _ } => translate.into_owned(),
|
||||
TextContent::EntityNames {
|
||||
selector,
|
||||
separator: _,
|
||||
} => selector.into_owned(),
|
||||
TextContent::Keybind { keybind } => keybind.into_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_pretty_console(self) -> String {
|
||||
let style = self.style;
|
||||
let color = style.color;
|
||||
let mut text = match self.content {
|
||||
TextContent::Text { text } => text.into_owned(),
|
||||
TextContent::Translate { translate, with: _ } => translate.into_owned(),
|
||||
@ -73,6 +84,8 @@ impl TextComponent {
|
||||
} => selector.into_owned(),
|
||||
TextContent::Keybind { keybind } => keybind.into_owned(),
|
||||
};
|
||||
let style = self.style;
|
||||
let color = style.color;
|
||||
if let Some(color) = color {
|
||||
text = color.console_color(&text).to_string();
|
||||
}
|
||||
|
||||
@ -51,12 +51,8 @@ impl CommandExecutor for DeopExecutor {
|
||||
.await;
|
||||
|
||||
let player_name = &player.gameprofile.name;
|
||||
let message = format!("Revoked {player_name}'s server operator status.");
|
||||
let msg = TextComponent::text(message);
|
||||
let msg = TextComponent::translate("commands.deop.success", [player_name.clone().into()]);
|
||||
sender.send_message(msg).await;
|
||||
player
|
||||
.send_system_message(&TextComponent::text("You are no longer a server operator."))
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -144,10 +144,10 @@ impl CommandExecutor for SetblockExecutor {
|
||||
};
|
||||
|
||||
sender
|
||||
.send_message(TextComponent::text(format!(
|
||||
"Placed {} blocks of {} from {from} to {to}",
|
||||
placed_blocks, block.name
|
||||
)))
|
||||
.send_message(TextComponent::translate(
|
||||
"commands.fill.success",
|
||||
[placed_blocks.to_string().into()],
|
||||
))
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
|
||||
@ -38,18 +38,15 @@ impl CommandExecutor for GamemodeTargetSelf {
|
||||
};
|
||||
|
||||
if let Player(target) = sender {
|
||||
if target.gamemode.load() == gamemode {
|
||||
target
|
||||
.send_system_message(&TextComponent::text(format!(
|
||||
"You already in {gamemode:?} gamemode"
|
||||
)))
|
||||
.await;
|
||||
} else {
|
||||
if target.gamemode.load() != gamemode {
|
||||
target.set_gamemode(gamemode).await;
|
||||
let gamemode_string = format!("{gamemode:?}");
|
||||
// TODO: translate gamemode
|
||||
target
|
||||
.send_system_message(&TextComponent::text(format!(
|
||||
"Game mode was set to {gamemode:?}"
|
||||
)))
|
||||
.send_system_message(&TextComponent::translate(
|
||||
"commands.gamemode.success.self",
|
||||
[gamemode_string.into()],
|
||||
))
|
||||
.await;
|
||||
}
|
||||
Ok(())
|
||||
@ -79,23 +76,25 @@ impl CommandExecutor for GamemodeTargetPlayer {
|
||||
let target_count = targets.len();
|
||||
|
||||
for target in targets {
|
||||
if target.gamemode.load() == gamemode {
|
||||
if target_count == 1 {
|
||||
sender
|
||||
.send_message(TextComponent::text(format!(
|
||||
"{} is already in {:?} gamemode",
|
||||
target.gameprofile.name, gamemode
|
||||
)))
|
||||
.await;
|
||||
}
|
||||
} else {
|
||||
if target.gamemode.load() != gamemode {
|
||||
target.set_gamemode(gamemode).await;
|
||||
let gamemode_string = format!("{gamemode:?}");
|
||||
// TODO: translate gamemode
|
||||
target
|
||||
.send_system_message(&TextComponent::translate(
|
||||
"gameMode.changed",
|
||||
[gamemode_string.clone().into()],
|
||||
))
|
||||
.await;
|
||||
if target_count == 1 {
|
||||
sender
|
||||
.send_message(TextComponent::text(format!(
|
||||
"{}'s Game mode was set to {:?}",
|
||||
target.gameprofile.name, gamemode
|
||||
)))
|
||||
.send_message(TextComponent::translate(
|
||||
"commands.gamemode.success.other",
|
||||
[
|
||||
target.gameprofile.name.clone().into(),
|
||||
gamemode_string.into(),
|
||||
],
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,19 +55,26 @@ impl CommandExecutor for GiveExecutor {
|
||||
target.give_items(item, item_count as u32).await;
|
||||
}
|
||||
|
||||
sender
|
||||
.send_message(TextComponent::text(match targets {
|
||||
[target] => format!(
|
||||
"Gave {item_count} {} to {}",
|
||||
item_name, target.gameprofile.name
|
||||
),
|
||||
_ => format!(
|
||||
"Gave {item_count} {} to {} players",
|
||||
item_name,
|
||||
targets.len()
|
||||
),
|
||||
}))
|
||||
.await;
|
||||
let msg = if targets.len() == 1 {
|
||||
TextComponent::translate(
|
||||
"commands.give.success.single",
|
||||
[
|
||||
item_count.to_string().into(),
|
||||
item_name.to_string().into(),
|
||||
targets[0].gameprofile.name.to_string().into(),
|
||||
],
|
||||
)
|
||||
} else {
|
||||
TextComponent::translate(
|
||||
"commands.give.success.single",
|
||||
[
|
||||
item_count.to_string().into(),
|
||||
item_name.to_string().into(),
|
||||
targets.len().to_string().into(),
|
||||
],
|
||||
)
|
||||
};
|
||||
sender.send_message(msg).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -29,21 +29,17 @@ impl CommandExecutor for KickExecutor {
|
||||
return Err(InvalidConsumption(Some(ARG_TARGET.into())));
|
||||
};
|
||||
|
||||
let target_count = targets.len();
|
||||
|
||||
for target in targets {
|
||||
target
|
||||
.kick(TextComponent::text("Kicked by an operator"))
|
||||
.kick(TextComponent::translate(
|
||||
"multiplayer.disconnect.kicked",
|
||||
[],
|
||||
))
|
||||
.await;
|
||||
}
|
||||
|
||||
let msg = if target_count == 1 {
|
||||
TextComponent::text("Player has been kicked.")
|
||||
} else {
|
||||
TextComponent::text(format!("{target_count} players have been kicked."))
|
||||
};
|
||||
|
||||
let name = &target.gameprofile.name;
|
||||
let msg = TextComponent::text(format!("Kicked: {name}"));
|
||||
sender.send_message(msg.color_named(NamedColor::Blue)).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -29,15 +29,19 @@ impl CommandExecutor for KillExecutor {
|
||||
};
|
||||
|
||||
let target_count = targets.len();
|
||||
|
||||
let mut name = String::new();
|
||||
for target in targets {
|
||||
target.living_entity.kill().await;
|
||||
name.clone_from(&target.gameprofile.name);
|
||||
}
|
||||
|
||||
let msg = if target_count == 1 {
|
||||
TextComponent::text("Entity has been killed.")
|
||||
TextComponent::translate("commands.kill.success.single", [name.into()])
|
||||
} else {
|
||||
TextComponent::text(format!("{target_count} entities have been killed."))
|
||||
TextComponent::translate(
|
||||
"commands.kill.success.multiple",
|
||||
[target_count.to_string().into()],
|
||||
)
|
||||
};
|
||||
|
||||
sender.send_message(msg.color_named(NamedColor::Blue)).await;
|
||||
|
||||
@ -27,18 +27,16 @@ impl CommandExecutor for ListExecutor {
|
||||
) -> Result<(), CommandError> {
|
||||
let players: Vec<Arc<Player>> = server.get_all_players().await;
|
||||
|
||||
let message = if players.is_empty() {
|
||||
"There are no players online.".to_owned()
|
||||
} else {
|
||||
format!(
|
||||
"There are {} of a max of {} players online: {}",
|
||||
players.len(),
|
||||
BASIC_CONFIG.max_players,
|
||||
get_player_names(players)
|
||||
)
|
||||
};
|
||||
|
||||
sender.send_message(TextComponent::text(message)).await;
|
||||
sender
|
||||
.send_message(TextComponent::translate(
|
||||
"commands.list.players",
|
||||
[
|
||||
players.len().to_string().into(),
|
||||
BASIC_CONFIG.max_players.to_string().into(),
|
||||
get_player_names(players).into(),
|
||||
],
|
||||
))
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -39,6 +39,13 @@ impl CommandExecutor for OpExecutor {
|
||||
.op_permission_level
|
||||
.min(sender.permission_lvl());
|
||||
|
||||
if player.permission_lvl.load() == new_level {
|
||||
sender
|
||||
.send_message(TextComponent::translate("commands.op.failed", []))
|
||||
.await;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(op) = config
|
||||
.ops
|
||||
.iter_mut()
|
||||
@ -62,11 +69,11 @@ impl CommandExecutor for OpExecutor {
|
||||
.await;
|
||||
|
||||
let player_name = &player.gameprofile.name;
|
||||
let message = format!("Made {player_name} a server operator.");
|
||||
let msg = TextComponent::text(message);
|
||||
sender.send_message(msg).await;
|
||||
player
|
||||
.send_system_message(&TextComponent::text("You are now a server operator."))
|
||||
sender
|
||||
.send_message(TextComponent::translate(
|
||||
"commands.op.success",
|
||||
[player_name.clone().into()],
|
||||
))
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
use async_trait::async_trait;
|
||||
use pumpkin_util::text::color::NamedColor;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
use crate::command::args::arg_block::BlockArgumentConsumer;
|
||||
@ -67,9 +66,16 @@ impl CommandExecutor for SetblockExecutor {
|
||||
|
||||
sender
|
||||
.send_message(if success {
|
||||
TextComponent::text(format!("Placed block {} at {pos}", block.name,))
|
||||
TextComponent::translate(
|
||||
"commands.setblock.success",
|
||||
[
|
||||
pos.0.x.to_string().into(),
|
||||
pos.0.y.to_string().into(),
|
||||
pos.0.z.to_string().into(),
|
||||
],
|
||||
)
|
||||
} else {
|
||||
TextComponent::text(format!("Kept block at {pos}")).color_named(NamedColor::Red)
|
||||
TextComponent::translate("commands.setblock.failed", [])
|
||||
})
|
||||
.await;
|
||||
|
||||
|
||||
@ -21,7 +21,9 @@ impl CommandExecutor for StopExecutor {
|
||||
_args: &ConsumedArgs<'a>,
|
||||
) -> Result<(), CommandError> {
|
||||
sender
|
||||
.send_message(TextComponent::text("Stopping Server").color_named(NamedColor::Red))
|
||||
.send_message(
|
||||
TextComponent::translate("commands.stop.stopping", []).color_named(NamedColor::Red),
|
||||
)
|
||||
.await;
|
||||
|
||||
// TODO: Gracefully stop
|
||||
|
||||
@ -62,19 +62,19 @@ impl CommandExecutor for TimeQueryExecutor {
|
||||
let msg = match mode {
|
||||
QueryMode::DayTime => {
|
||||
let curr_time = level_time.query_daytime();
|
||||
format!("Daytime is: {curr_time}")
|
||||
TextComponent::translate("commands.time.query", [curr_time.to_string().into()])
|
||||
}
|
||||
QueryMode::GameTime => {
|
||||
let curr_time = level_time.query_gametime();
|
||||
format!("Gametime is: {curr_time}")
|
||||
TextComponent::translate("commands.time.query", [curr_time.to_string().into()])
|
||||
}
|
||||
QueryMode::Day => {
|
||||
let curr_time = level_time.query_day();
|
||||
format!("Day is: {curr_time}")
|
||||
TextComponent::translate("commands.time.query", [curr_time.to_string().into()])
|
||||
}
|
||||
};
|
||||
|
||||
sender.send_message(TextComponent::text(msg)).await;
|
||||
sender.send_message(msg).await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -124,17 +124,17 @@ impl CommandExecutor for TimeChangeExecutor {
|
||||
level_time.add_time(time_count.into());
|
||||
level_time.send_time(world).await;
|
||||
let curr_time = level_time.query_daytime();
|
||||
format!("Added {time_count} time for result: {curr_time}")
|
||||
TextComponent::translate("commands.time.add", [curr_time.to_string().into()])
|
||||
}
|
||||
Mode::Set(_) => {
|
||||
// set
|
||||
level_time.set_time(time_count.into());
|
||||
level_time.send_time(world).await;
|
||||
format!("Changed time to: {time_count}")
|
||||
TextComponent::translate("commands.time.set", [time_count.to_string().into()])
|
||||
}
|
||||
};
|
||||
|
||||
sender.send_message(TextComponent::text(msg)).await;
|
||||
sender.send_message(msg).await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,6 +25,8 @@ const NAMES: [&str; 1] = ["worldborder"];
|
||||
|
||||
const DESCRIPTION: &str = "Worldborder command.";
|
||||
|
||||
const NOTHING_CHANGED_EXCEPTION: &str = "commands.worldborder.set.failed.nochange";
|
||||
|
||||
fn distance_consumer() -> BoundedNumArgumentConsumer<f64> {
|
||||
BoundedNumArgumentConsumer::new().min(0.0).name("distance")
|
||||
}
|
||||
@ -65,9 +67,10 @@ impl CommandExecutor for WorldborderGetExecutor {
|
||||
|
||||
let diameter = border.new_diameter.round() as i32;
|
||||
sender
|
||||
.send_message(TextComponent::text(format!(
|
||||
"The world border is currently {diameter} block(s) wide"
|
||||
)))
|
||||
.send_message(TextComponent::translate(
|
||||
"commands.worldborder.get",
|
||||
[diameter.to_string().into()],
|
||||
))
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
@ -105,17 +108,19 @@ impl CommandExecutor for WorldborderSetExecutor {
|
||||
if (distance - border.new_diameter).abs() < f64::EPSILON {
|
||||
sender
|
||||
.send_message(
|
||||
TextComponent::text("Nothing changed. The world border is already that size")
|
||||
TextComponent::translate(NOTHING_CHANGED_EXCEPTION, [])
|
||||
.color(Color::Named(NamedColor::Red)),
|
||||
)
|
||||
.await;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let dist = format!("{distance:.1}");
|
||||
sender
|
||||
.send_message(TextComponent::text(format!(
|
||||
"Set the world border to {distance:.1} block(s) wide"
|
||||
)))
|
||||
.send_message(TextComponent::translate(
|
||||
"commands.worldborder.set.immediate",
|
||||
[dist.into()],
|
||||
))
|
||||
.await;
|
||||
border.set_diameter(world, distance, None).await;
|
||||
Ok(())
|
||||
@ -167,22 +172,28 @@ impl CommandExecutor for WorldborderSetTimeExecutor {
|
||||
std::cmp::Ordering::Equal => {
|
||||
sender
|
||||
.send_message(
|
||||
TextComponent::text(
|
||||
"Nothing changed. The world border is already that size",
|
||||
)
|
||||
TextComponent::translate(NOTHING_CHANGED_EXCEPTION, [])
|
||||
.color(Color::Named(NamedColor::Red)),
|
||||
)
|
||||
.await;
|
||||
return Ok(());
|
||||
}
|
||||
std::cmp::Ordering::Less => {
|
||||
sender.send_message(TextComponent::text(format!("Shrinking the world border to {distance:.2} blocks wide over {time} second(s)"))).await;
|
||||
let dist = format!("{distance:.1}");
|
||||
sender
|
||||
.send_message(TextComponent::translate(
|
||||
"commands.worldborder.set.shrink",
|
||||
[dist.into(), time.to_string().into()],
|
||||
))
|
||||
.await;
|
||||
}
|
||||
std::cmp::Ordering::Greater => {
|
||||
let dist = format!("{distance:.1}");
|
||||
sender
|
||||
.send_message(TextComponent::text(format!(
|
||||
"Growing the world border to {distance:.2} blocks wide over {time} seconds"
|
||||
)))
|
||||
.send_message(TextComponent::translate(
|
||||
"commands.worldborder.set.grow",
|
||||
[dist.into(), time.to_string().into()],
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
@ -226,7 +237,7 @@ impl CommandExecutor for WorldborderAddExecutor {
|
||||
if distance == 0.0 {
|
||||
sender
|
||||
.send_message(
|
||||
TextComponent::text("Nothing changed. The world border is already that size")
|
||||
TextComponent::translate(NOTHING_CHANGED_EXCEPTION, [])
|
||||
.color(Color::Named(NamedColor::Red)),
|
||||
)
|
||||
.await;
|
||||
@ -235,10 +246,12 @@ impl CommandExecutor for WorldborderAddExecutor {
|
||||
|
||||
let distance = border.new_diameter + distance;
|
||||
|
||||
let dist = format!("{distance:.1}");
|
||||
sender
|
||||
.send_message(TextComponent::text(format!(
|
||||
"Set the world border to {distance:.1} block(s) wide"
|
||||
)))
|
||||
.send_message(TextComponent::translate(
|
||||
"commands.worldborder.set.immediate",
|
||||
[dist.into()],
|
||||
))
|
||||
.await;
|
||||
border.set_diameter(world, distance, None).await;
|
||||
Ok(())
|
||||
@ -292,22 +305,28 @@ impl CommandExecutor for WorldborderAddTimeExecutor {
|
||||
std::cmp::Ordering::Equal => {
|
||||
sender
|
||||
.send_message(
|
||||
TextComponent::text(
|
||||
"Nothing changed. The world border is already that size",
|
||||
)
|
||||
TextComponent::translate(NOTHING_CHANGED_EXCEPTION, [])
|
||||
.color(Color::Named(NamedColor::Red)),
|
||||
)
|
||||
.await;
|
||||
return Ok(());
|
||||
}
|
||||
std::cmp::Ordering::Less => {
|
||||
sender.send_message(TextComponent::text(format!("Shrinking the world border to {distance:.2} blocks wide over {time} second(s)"))).await;
|
||||
let dist = format!("{distance:.1}");
|
||||
sender
|
||||
.send_message(TextComponent::translate(
|
||||
"commands.worldborder.set.shrink",
|
||||
[dist.into(), time.to_string().into()],
|
||||
))
|
||||
.await;
|
||||
}
|
||||
std::cmp::Ordering::Greater => {
|
||||
let dist = format!("{distance:.1}");
|
||||
sender
|
||||
.send_message(TextComponent::text(format!(
|
||||
"Growing the world border to {distance:.2} blocks wide over {time} seconds"
|
||||
)))
|
||||
.send_message(TextComponent::translate(
|
||||
"commands.worldborder.set.grow",
|
||||
[dist.into(), time.to_string().into()],
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
@ -338,9 +357,10 @@ impl CommandExecutor for WorldborderCenterExecutor {
|
||||
let Vector2 { x, z } = Position2DArgumentConsumer.find_arg_default_name(args)?;
|
||||
|
||||
sender
|
||||
.send_message(TextComponent::text(format!(
|
||||
"Set the center of world border to {x:.2}, {z:.2}"
|
||||
)))
|
||||
.send_message(TextComponent::translate(
|
||||
"commands.worldborder.center.success",
|
||||
[format!("{x:.2}").into(), format!("{z:.2}").into()],
|
||||
))
|
||||
.await;
|
||||
border.set_center(world, x, z).await;
|
||||
Ok(())
|
||||
@ -379,19 +399,19 @@ impl CommandExecutor for WorldborderDamageAmountExecutor {
|
||||
if (damage_per_block - border.damage_per_block).abs() < f32::EPSILON {
|
||||
sender
|
||||
.send_message(
|
||||
TextComponent::text(
|
||||
"Nothing changed. The world border damage is already that amount",
|
||||
)
|
||||
TextComponent::translate("commands.worldborder.damage.amount.failed", [])
|
||||
.color(Color::Named(NamedColor::Red)),
|
||||
)
|
||||
.await;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let damage = format!("{damage_per_block:.2}");
|
||||
sender
|
||||
.send_message(TextComponent::text(format!(
|
||||
"Set the world border damage to {damage_per_block:.2} per block each second"
|
||||
)))
|
||||
.send_message(TextComponent::translate(
|
||||
"commands.worldborder.damage.amount.success",
|
||||
[damage.into()],
|
||||
))
|
||||
.await;
|
||||
border.damage_per_block = damage_per_block;
|
||||
Ok(())
|
||||
@ -430,19 +450,19 @@ impl CommandExecutor for WorldborderDamageBufferExecutor {
|
||||
if (buffer - border.buffer).abs() < f32::EPSILON {
|
||||
sender
|
||||
.send_message(
|
||||
TextComponent::text(
|
||||
"Nothing changed. The world border damage buffer is already that distance",
|
||||
)
|
||||
TextComponent::translate("commands.worldborder.damage.buffer.failed", [])
|
||||
.color(Color::Named(NamedColor::Red)),
|
||||
)
|
||||
.await;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let buf = format!("{buffer:.2}");
|
||||
sender
|
||||
.send_message(TextComponent::text(format!(
|
||||
"Set the world border damage buffer to {buffer:.2} block(s)"
|
||||
)))
|
||||
.send_message(TextComponent::translate(
|
||||
"commands.worldborder.damage.buffer.success",
|
||||
[buf.into()],
|
||||
))
|
||||
.await;
|
||||
border.buffer = buffer;
|
||||
Ok(())
|
||||
@ -481,9 +501,7 @@ impl CommandExecutor for WorldborderWarningDistanceExecutor {
|
||||
if distance == border.warning_blocks {
|
||||
sender
|
||||
.send_message(
|
||||
TextComponent::text(
|
||||
"Nothing changed. The world border warning is already that distance",
|
||||
)
|
||||
TextComponent::translate("commands.worldborder.warning.distance.failed", [])
|
||||
.color(Color::Named(NamedColor::Red)),
|
||||
)
|
||||
.await;
|
||||
@ -491,9 +509,10 @@ impl CommandExecutor for WorldborderWarningDistanceExecutor {
|
||||
}
|
||||
|
||||
sender
|
||||
.send_message(TextComponent::text(format!(
|
||||
"Set the world border warning distance to {distance} block(s)"
|
||||
)))
|
||||
.send_message(TextComponent::translate(
|
||||
"commands.worldborder.warning.distance.success",
|
||||
[distance.to_string().into()],
|
||||
))
|
||||
.await;
|
||||
border.set_warning_distance(world, distance).await;
|
||||
Ok(())
|
||||
@ -532,9 +551,7 @@ impl CommandExecutor for WorldborderWarningTimeExecutor {
|
||||
if time == border.warning_time {
|
||||
sender
|
||||
.send_message(
|
||||
TextComponent::text(
|
||||
"Nothing changed. The world border warning is already that amount of time",
|
||||
)
|
||||
TextComponent::translate("commands.worldborder.warning.time.failed", [])
|
||||
.color(Color::Named(NamedColor::Red)),
|
||||
)
|
||||
.await;
|
||||
@ -542,9 +559,10 @@ impl CommandExecutor for WorldborderWarningTimeExecutor {
|
||||
}
|
||||
|
||||
sender
|
||||
.send_message(TextComponent::text(format!(
|
||||
"Set the world border warning time to {time} second(s)"
|
||||
)))
|
||||
.send_message(TextComponent::translate(
|
||||
"commands.worldborder.warning.time.success",
|
||||
[time.to_string().into()],
|
||||
))
|
||||
.await;
|
||||
border.set_warning_delay(world, time).await;
|
||||
Ok(())
|
||||
|
||||
@ -419,7 +419,8 @@ impl Player {
|
||||
.wait_for_keep_alive
|
||||
.load(std::sync::atomic::Ordering::Relaxed)
|
||||
{
|
||||
self.kick(TextComponent::text("Timeout")).await;
|
||||
self.kick(TextComponent::translate("disconnect.timeout", []))
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
self.wait_for_keep_alive
|
||||
@ -697,8 +698,12 @@ impl Player {
|
||||
}
|
||||
|
||||
pub async fn send_system_message(&self, text: &TextComponent) {
|
||||
self.send_system_message_raw(text, false).await;
|
||||
}
|
||||
|
||||
pub async fn send_system_message_raw(&self, text: &TextComponent, overlay: bool) {
|
||||
self.client
|
||||
.send_packet(&CSystemChatMessage::new(text, false))
|
||||
.send_packet(&CSystemChatMessage::new(text, overlay))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
@ -248,7 +248,7 @@ impl Client {
|
||||
|
||||
let mut enc = self.enc.lock().await;
|
||||
if let Err(error) = enc.append_packet(packet) {
|
||||
self.kick(&error.to_string()).await;
|
||||
self.kick(&TextComponent::text(error.to_string())).await;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -336,7 +336,7 @@ impl Client {
|
||||
i32::from(packet.id),
|
||||
error
|
||||
);
|
||||
self.kick(&text).await;
|
||||
self.kick(&TextComponent::text(text)).await;
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -564,23 +564,20 @@ impl Client {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `reason`: A string describing the reason for kicking the client.
|
||||
pub async fn kick(&self, reason: &str) {
|
||||
log::info!("Kicking Client id {} for {}", self.id, reason);
|
||||
pub async fn kick(&self, reason: &TextComponent) {
|
||||
let text = reason.clone().get_text();
|
||||
log::info!("Kicking Client id {} for {}", self.id, &text);
|
||||
let result = match self.connection_state.load() {
|
||||
ConnectionState::Login => {
|
||||
// TextComponent implements Serialze and writes in bytes instead of String, thats the reasib we only use content
|
||||
self.try_send_packet(&CLoginDisconnect::new(
|
||||
&serde_json::to_string_pretty(&reason).unwrap_or_else(|_| String::new()),
|
||||
&serde_json::to_string(&reason.content).unwrap_or_else(|_| String::new()),
|
||||
))
|
||||
.await
|
||||
}
|
||||
ConnectionState::Config => self.try_send_packet(&CConfigDisconnect::new(reason)).await,
|
||||
ConnectionState::Config => self.try_send_packet(&CConfigDisconnect::new(&text)).await,
|
||||
// This way players get kicked when players using client functions (e.g. poll, send_packet)
|
||||
ConnectionState::Play => {
|
||||
self.try_send_packet(&CPlayDisconnect::new(&TextComponent::text(
|
||||
reason.to_owned(),
|
||||
)))
|
||||
.await
|
||||
}
|
||||
ConnectionState::Play => self.try_send_packet(&CPlayDisconnect::new(reason)).await,
|
||||
_ => {
|
||||
log::warn!("Can't kick in {:?} State", self.connection_state);
|
||||
Ok(())
|
||||
|
||||
@ -14,6 +14,7 @@ use pumpkin_protocol::{
|
||||
},
|
||||
ConnectionState,
|
||||
};
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
impl Client {
|
||||
pub async fn handle_client_information_config(
|
||||
@ -22,7 +23,9 @@ impl Client {
|
||||
) {
|
||||
log::debug!("Handling client settings");
|
||||
if client_information.view_distance <= 0 {
|
||||
self.kick("Cannot have zero or negative view distance!")
|
||||
self.kick(&TextComponent::text(
|
||||
"Cannot have zero or negative view distance!",
|
||||
))
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
@ -44,7 +47,8 @@ impl Client {
|
||||
server_listing: client_information.server_listing,
|
||||
});
|
||||
} else {
|
||||
self.kick("Invalid hand or chat type").await;
|
||||
self.kick(&TextComponent::text("Invalid hand or chat type"))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,7 +62,7 @@ impl Client {
|
||||
log::debug!("got a client brand");
|
||||
match str::from_utf8(&plugin_message.data) {
|
||||
Ok(brand) => *self.brand.lock().await = Some(brand.to_string()),
|
||||
Err(e) => self.kick(&e.to_string()).await,
|
||||
Err(e) => self.kick(&TextComponent::text(e.to_string())).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
use std::num::NonZeroI32;
|
||||
|
||||
use pumpkin_protocol::{server::handshake::SHandShake, ConnectionState, CURRENT_MC_PROTOCOL};
|
||||
use pumpkin_util::text::TextComponent;
|
||||
|
||||
use crate::{net::Client, server::CURRENT_MC_VERSION};
|
||||
|
||||
@ -17,11 +18,19 @@ impl Client {
|
||||
let protocol = version;
|
||||
match protocol.cmp(&NonZeroI32::from(CURRENT_MC_PROTOCOL).get()) {
|
||||
std::cmp::Ordering::Less => {
|
||||
self.kick(&format!("Client outdated ({protocol}), Server uses Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL}")).await;
|
||||
self.kick(&TextComponent::translate(
|
||||
"multiplayer.disconnect.outdated_client",
|
||||
[CURRENT_MC_VERSION.to_string().into()],
|
||||
))
|
||||
.await;
|
||||
}
|
||||
std::cmp::Ordering::Equal => {}
|
||||
std::cmp::Ordering::Greater => {
|
||||
self.kick(&format!("Server outdated, Server uses Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL}")).await;
|
||||
self.kick(&TextComponent::translate(
|
||||
"multiplayer.disconnect.incompatible",
|
||||
[CURRENT_MC_VERSION.to_string().into()],
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,13 +93,17 @@ impl Client {
|
||||
// TODO: If client is an operator or otherwise suitable elevated permissions, allow client to bypass this requirement.
|
||||
let max_players = BASIC_CONFIG.max_players;
|
||||
if max_players > 0 && server.get_player_count().await >= max_players as usize {
|
||||
self.kick("The server is currently full, please try again later")
|
||||
self.kick(&TextComponent::translate(
|
||||
"multiplayer.disconnect.server_full",
|
||||
[],
|
||||
))
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
if !is_valid_player_name(&login_start.name) {
|
||||
self.kick("Invalid characters in username").await;
|
||||
self.kick(&TextComponent::text("Invalid characters in username"))
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
// default game profile, when no online mode
|
||||
@ -122,7 +126,7 @@ impl Client {
|
||||
self.finish_login(&profile).await;
|
||||
*gameprofile = Some(profile);
|
||||
}
|
||||
Err(error) => self.kick(&error.to_string()).await,
|
||||
Err(error) => self.kick(&TextComponent::text(error.to_string())).await,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -165,14 +169,14 @@ impl Client {
|
||||
let shared_secret = server.decrypt(&encryption_response.shared_secret).unwrap();
|
||||
|
||||
if let Err(error) = self.set_encryption(Some(&shared_secret)).await {
|
||||
self.kick(&error.to_string()).await;
|
||||
self.kick(&TextComponent::text(error.to_string())).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let mut gameprofile = self.gameprofile.lock().await;
|
||||
|
||||
let Some(profile) = gameprofile.as_mut() else {
|
||||
self.kick("No Game profile").await;
|
||||
self.kick(&TextComponent::text("No Game profile")).await;
|
||||
return;
|
||||
};
|
||||
|
||||
@ -184,7 +188,7 @@ impl Client {
|
||||
{
|
||||
Ok(new_profile) => *profile = new_profile,
|
||||
Err(e) => {
|
||||
self.kick(&e.to_string()).await;
|
||||
self.kick(&TextComponent::text(e.to_string())).await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -193,14 +197,21 @@ impl Client {
|
||||
// Don't allow duplicate UUIDs
|
||||
if let Some(online_player) = &server.get_player_by_uuid(profile.id).await {
|
||||
log::debug!("Player (IP '{}', username '{}') tried to log in with the same UUID ('{}') as an online player (IP '{}', username '{}')", &self.address.lock().await, &profile.name, &profile.id, &online_player.client.address.lock().await, &online_player.gameprofile.name);
|
||||
self.kick("You are already connected to this server").await;
|
||||
self.kick(&TextComponent::translate(
|
||||
"multiplayer.disconnect.duplicate_login",
|
||||
[],
|
||||
))
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't allow a duplicate username
|
||||
if let Some(online_player) = &server.get_player_by_name(&profile.name).await {
|
||||
log::debug!("A player (IP '{}', attempted username '{}') tried to log in with the same username as an online player (UUID '{}', IP '{}', username '{}')", &self.address.lock().await, &profile.name, &profile.id, &online_player.client.address.lock().await, &online_player.gameprofile.name);
|
||||
self.kick("A player with this username is already connected")
|
||||
self.kick(&TextComponent::translate(
|
||||
"multiplayer.disconnect.duplicate_login",
|
||||
[],
|
||||
))
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
@ -300,7 +311,7 @@ impl Client {
|
||||
*self.gameprofile.lock().await = Some(profile);
|
||||
*address = new_address;
|
||||
}
|
||||
Err(error) => self.kick(&error.to_string()).await,
|
||||
Err(error) => self.kick(&TextComponent::text(error.to_string())).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,9 +15,7 @@ use pumpkin_config::ADVANCED_CONFIG;
|
||||
use pumpkin_data::entity::EntityType;
|
||||
use pumpkin_inventory::player::PlayerInventory;
|
||||
use pumpkin_inventory::InventoryError;
|
||||
use pumpkin_protocol::client::play::{
|
||||
CSetContainerSlot, CSetHeldItem, CSpawnEntity, CSystemChatMessage,
|
||||
};
|
||||
use pumpkin_protocol::client::play::{CSetContainerSlot, CSetHeldItem, CSpawnEntity};
|
||||
use pumpkin_protocol::codec::slot::Slot;
|
||||
use pumpkin_protocol::codec::var_int::VarInt;
|
||||
use pumpkin_protocol::server::play::SCookieResponse as SPCookieResponse;
|
||||
@ -81,11 +79,8 @@ impl PumpkinError for BlockPlacingError {
|
||||
|
||||
fn severity(&self) -> log::Level {
|
||||
match self {
|
||||
Self::BlockOutOfReach
|
||||
| Self::BlockOutOfWorld
|
||||
| Self::InvalidBlockFace
|
||||
| Self::InvalidGamemode
|
||||
| Self::NoBaseBlock => log::Level::Warn,
|
||||
Self::BlockOutOfWorld | Self::InvalidGamemode | Self::NoBaseBlock => log::Level::Trace,
|
||||
Self::BlockOutOfReach | Self::InvalidBlockFace => log::Level::Warn,
|
||||
Self::InventoryInvalid => log::Level::Error,
|
||||
}
|
||||
}
|
||||
@ -141,7 +136,11 @@ impl Player {
|
||||
// y = feet Y
|
||||
let position = packet.position;
|
||||
if position.x.is_nan() || position.y.is_nan() || position.z.is_nan() {
|
||||
self.kick(TextComponent::text("Invalid movement")).await;
|
||||
self.kick(TextComponent::translate(
|
||||
"multiplayer.disconnect.invalid_player_movement",
|
||||
[],
|
||||
))
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
let position = Vector3::new(
|
||||
@ -199,15 +198,20 @@ impl Player {
|
||||
}
|
||||
// y = feet Y
|
||||
let position = packet.position;
|
||||
if position.x.is_nan() || position.y.is_nan() || position.z.is_nan() {
|
||||
self.kick(TextComponent::text("Invalid movement")).await;
|
||||
if position.x.is_nan()
|
||||
|| position.y.is_nan()
|
||||
|| position.z.is_nan()
|
||||
|| packet.yaw.is_infinite()
|
||||
|| packet.pitch.is_infinite()
|
||||
{
|
||||
self.kick(TextComponent::translate(
|
||||
"multiplayer.disconnect.invalid_player_movement",
|
||||
[],
|
||||
))
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
|
||||
if packet.yaw.is_infinite() || packet.pitch.is_infinite() {
|
||||
self.kick(TextComponent::text("Invalid rotation")).await;
|
||||
return;
|
||||
}
|
||||
let position = Vector3::new(
|
||||
Self::clamp_horizontal(position.x),
|
||||
Self::clamp_vertical(position.y),
|
||||
@ -280,7 +284,11 @@ impl Player {
|
||||
return;
|
||||
}
|
||||
if !rotation.yaw.is_finite() || !rotation.pitch.is_finite() {
|
||||
self.kick(TextComponent::text("Invalid rotation")).await;
|
||||
self.kick(TextComponent::translate(
|
||||
"multiplayer.disconnect.invalid_player_movement",
|
||||
[],
|
||||
))
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
let entity = &self.living_entity.entity;
|
||||
@ -528,7 +536,10 @@ impl Player {
|
||||
}
|
||||
|
||||
if message.chars().any(|c| c == '§' || c < ' ' || c == '\x7F') {
|
||||
self.kick(TextComponent::text("Illegal characters in chat"))
|
||||
self.kick(TextComponent::translate(
|
||||
"multiplayer.disconnect.illegal_characters",
|
||||
[],
|
||||
))
|
||||
.await;
|
||||
return;
|
||||
}
|
||||
@ -705,7 +716,10 @@ impl Player {
|
||||
self.entity_id(),
|
||||
entity_id.0
|
||||
);
|
||||
self.kick(TextComponent::text("Interacted with invalid entity id"))
|
||||
self.kick(TextComponent::translate(
|
||||
"multiplayer.disconnect.invalid_entity_attacked",
|
||||
[],
|
||||
))
|
||||
.await;
|
||||
return;
|
||||
};
|
||||
@ -1134,15 +1148,14 @@ impl Player {
|
||||
|
||||
//check max world build height
|
||||
if location.0.y + face.to_offset().y >= WORLD_MAX_Y.into() {
|
||||
self.client
|
||||
.send_packet(&CSystemChatMessage::new(
|
||||
self.send_system_message_raw(
|
||||
&TextComponent::translate(
|
||||
"build.tooHigh",
|
||||
vec![(WORLD_MAX_Y - 1).to_string().into()],
|
||||
)
|
||||
.color_named(NamedColor::Red),
|
||||
true,
|
||||
))
|
||||
)
|
||||
.await;
|
||||
self.client
|
||||
.send_packet(&CAcknowledgeBlockChange::new(use_item_on.sequence))
|
||||
|
||||
@ -745,8 +745,11 @@ impl World {
|
||||
|
||||
// Handle join message
|
||||
// TODO: Config
|
||||
let msg_txt = format!("{} joined the game.", player.gameprofile.name.as_str());
|
||||
let msg_comp = TextComponent::text(msg_txt).color_named(NamedColor::Yellow);
|
||||
let msg_comp = TextComponent::translate(
|
||||
"multiplayer.player.joined",
|
||||
[player.gameprofile.name.clone().into()],
|
||||
)
|
||||
.color_named(NamedColor::Yellow);
|
||||
for player in current_players.values() {
|
||||
player.send_system_message(&msg_comp).await;
|
||||
}
|
||||
@ -787,12 +790,15 @@ impl World {
|
||||
|
||||
// Send disconnect message / quit message to players in the same world
|
||||
// TODO: Config
|
||||
let disconn_msg_txt = format!("{} left the game.", player.gameprofile.name.as_str());
|
||||
let disconn_msg_cmp = TextComponent::text(disconn_msg_txt).color_named(NamedColor::Yellow);
|
||||
let disconn_msg = TextComponent::translate(
|
||||
"multiplayer.player.left",
|
||||
[player.gameprofile.name.clone().into()],
|
||||
)
|
||||
.color_named(NamedColor::Yellow);
|
||||
for player in self.current_players.lock().await.values() {
|
||||
player.send_system_message(&disconn_msg_cmp).await;
|
||||
player.send_system_message(&disconn_msg).await;
|
||||
}
|
||||
log::info!("{}", disconn_msg_cmp.to_pretty_console());
|
||||
log::info!("{}", disconn_msg.to_pretty_console());
|
||||
}
|
||||
|
||||
/// Adds a living entity to the world.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user