diff --git a/pumpkin-util/src/text/mod.rs b/pumpkin-util/src/text/mod.rs
index 9468ab9f8..937825f58 100644
--- a/pumpkin-util/src/text/mod.rs
+++ b/pumpkin-util/src/text/mod.rs
@@ -42,14 +42,15 @@ impl TextComponent {
}
}
- pub fn translate
(key: P, with: Vec>) -> Self
+ pub fn translate(key: K, with: W) -> Self
where
- P: Into>,
+ K: Into>,
+ W: Into>>,
{
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();
}
diff --git a/pumpkin/src/command/commands/cmd_deop.rs b/pumpkin/src/command/commands/cmd_deop.rs
index 78ffcfea9..cbeb7547d 100644
--- a/pumpkin/src/command/commands/cmd_deop.rs
+++ b/pumpkin/src/command/commands/cmd_deop.rs
@@ -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(())
}
diff --git a/pumpkin/src/command/commands/cmd_fill.rs b/pumpkin/src/command/commands/cmd_fill.rs
index ec77cff91..8e7dc440f 100644
--- a/pumpkin/src/command/commands/cmd_fill.rs
+++ b/pumpkin/src/command/commands/cmd_fill.rs
@@ -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(())
diff --git a/pumpkin/src/command/commands/cmd_gamemode.rs b/pumpkin/src/command/commands/cmd_gamemode.rs
index b45f4403f..3f3d25f6f 100644
--- a/pumpkin/src/command/commands/cmd_gamemode.rs
+++ b/pumpkin/src/command/commands/cmd_gamemode.rs
@@ -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;
}
}
diff --git a/pumpkin/src/command/commands/cmd_give.rs b/pumpkin/src/command/commands/cmd_give.rs
index 40cd1af00..6a0380dcd 100644
--- a/pumpkin/src/command/commands/cmd_give.rs
+++ b/pumpkin/src/command/commands/cmd_give.rs
@@ -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(())
}
diff --git a/pumpkin/src/command/commands/cmd_kick.rs b/pumpkin/src/command/commands/cmd_kick.rs
index 259e536a7..a94eabcaa 100644
--- a/pumpkin/src/command/commands/cmd_kick.rs
+++ b/pumpkin/src/command/commands/cmd_kick.rs
@@ -29,22 +29,18 @@ 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 name = &target.gameprofile.name;
+ let msg = TextComponent::text(format!("Kicked: {name}"));
+ sender.send_message(msg.color_named(NamedColor::Blue)).await;
}
- let msg = if target_count == 1 {
- TextComponent::text("Player has been kicked.")
- } else {
- TextComponent::text(format!("{target_count} players have been kicked."))
- };
-
- sender.send_message(msg.color_named(NamedColor::Blue)).await;
-
Ok(())
}
}
diff --git a/pumpkin/src/command/commands/cmd_kill.rs b/pumpkin/src/command/commands/cmd_kill.rs
index 6b6ada252..18a247de9 100644
--- a/pumpkin/src/command/commands/cmd_kill.rs
+++ b/pumpkin/src/command/commands/cmd_kill.rs
@@ -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;
diff --git a/pumpkin/src/command/commands/cmd_list.rs b/pumpkin/src/command/commands/cmd_list.rs
index 918de10cc..b5110d7bf 100644
--- a/pumpkin/src/command/commands/cmd_list.rs
+++ b/pumpkin/src/command/commands/cmd_list.rs
@@ -27,18 +27,16 @@ impl CommandExecutor for ListExecutor {
) -> Result<(), CommandError> {
let players: Vec> = 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(())
}
diff --git a/pumpkin/src/command/commands/cmd_op.rs b/pumpkin/src/command/commands/cmd_op.rs
index 91f7a05aa..713147524 100644
--- a/pumpkin/src/command/commands/cmd_op.rs
+++ b/pumpkin/src/command/commands/cmd_op.rs
@@ -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(())
diff --git a/pumpkin/src/command/commands/cmd_setblock.rs b/pumpkin/src/command/commands/cmd_setblock.rs
index cf93abe25..8ead46754 100644
--- a/pumpkin/src/command/commands/cmd_setblock.rs
+++ b/pumpkin/src/command/commands/cmd_setblock.rs
@@ -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;
diff --git a/pumpkin/src/command/commands/cmd_stop.rs b/pumpkin/src/command/commands/cmd_stop.rs
index 59bcc7dc9..994ae1e12 100644
--- a/pumpkin/src/command/commands/cmd_stop.rs
+++ b/pumpkin/src/command/commands/cmd_stop.rs
@@ -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
diff --git a/pumpkin/src/command/commands/cmd_time.rs b/pumpkin/src/command/commands/cmd_time.rs
index 98b937469..77c13aa5e 100644
--- a/pumpkin/src/command/commands/cmd_time.rs
+++ b/pumpkin/src/command/commands/cmd_time.rs
@@ -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(())
}
}
diff --git a/pumpkin/src/command/commands/cmd_worldborder.rs b/pumpkin/src/command/commands/cmd_worldborder.rs
index c0f47997f..61c41aaca 100644
--- a/pumpkin/src/command/commands/cmd_worldborder.rs
+++ b/pumpkin/src/command/commands/cmd_worldborder.rs
@@ -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 {
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",
- )
- .color(Color::Named(NamedColor::Red)),
+ 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",
- )
- .color(Color::Named(NamedColor::Red)),
+ 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",
- )
- .color(Color::Named(NamedColor::Red)),
+ 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",
- )
- .color(Color::Named(NamedColor::Red)),
+ 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,19 +501,18 @@ impl CommandExecutor for WorldborderWarningDistanceExecutor {
if distance == border.warning_blocks {
sender
.send_message(
- TextComponent::text(
- "Nothing changed. The world border warning is already that distance",
- )
- .color(Color::Named(NamedColor::Red)),
+ TextComponent::translate("commands.worldborder.warning.distance.failed", [])
+ .color(Color::Named(NamedColor::Red)),
)
.await;
return Ok(());
}
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,19 +551,18 @@ 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",
- )
- .color(Color::Named(NamedColor::Red)),
+ TextComponent::translate("commands.worldborder.warning.time.failed", [])
+ .color(Color::Named(NamedColor::Red)),
)
.await;
return Ok(());
}
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(())
diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs
index eb8e68b9f..c24ef7f8f 100644
--- a/pumpkin/src/entity/player.rs
+++ b/pumpkin/src/entity/player.rs
@@ -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;
}
}
diff --git a/pumpkin/src/net/mod.rs b/pumpkin/src/net/mod.rs
index 4bf4166fa..2f06d283b 100644
--- a/pumpkin/src/net/mod.rs
+++ b/pumpkin/src/net/mod.rs
@@ -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(())
diff --git a/pumpkin/src/net/packet/config.rs b/pumpkin/src/net/packet/config.rs
index 4db8ffc2e..0a1236ec3 100644
--- a/pumpkin/src/net/packet/config.rs
+++ b/pumpkin/src/net/packet/config.rs
@@ -14,6 +14,7 @@ use pumpkin_protocol::{
},
ConnectionState,
};
+use pumpkin_util::text::TextComponent;
impl Client {
pub async fn handle_client_information_config(
@@ -22,8 +23,10 @@ impl Client {
) {
log::debug!("Handling client settings");
if client_information.view_distance <= 0 {
- self.kick("Cannot have zero or negative view distance!")
- .await;
+ 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,
}
}
}
diff --git a/pumpkin/src/net/packet/handshake.rs b/pumpkin/src/net/packet/handshake.rs
index 58705210f..fbeaa1c3b 100644
--- a/pumpkin/src/net/packet/handshake.rs
+++ b/pumpkin/src/net/packet/handshake.rs
@@ -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;
}
}
}
diff --git a/pumpkin/src/net/packet/login.rs b/pumpkin/src/net/packet/login.rs
index 3fb61ce9e..e02074198 100644
--- a/pumpkin/src/net/packet/login.rs
+++ b/pumpkin/src/net/packet/login.rs
@@ -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")
- .await;
+ 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,15 +197,22 @@ 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")
- .await;
+ 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,
}
}
}
diff --git a/pumpkin/src/net/packet/play.rs b/pumpkin/src/net/packet/play.rs
index 5bc237f7f..0fc2fa1a3 100644
--- a/pumpkin/src/net/packet/play.rs
+++ b/pumpkin/src/net/packet/play.rs
@@ -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,8 +536,11 @@ impl Player {
}
if message.chars().any(|c| c == 'ยง' || c < ' ' || c == '\x7F') {
- self.kick(TextComponent::text("Illegal characters in chat"))
- .await;
+ self.kick(TextComponent::translate(
+ "multiplayer.disconnect.illegal_characters",
+ [],
+ ))
+ .await;
return;
}
@@ -705,8 +716,11 @@ impl Player {
self.entity_id(),
entity_id.0
);
- self.kick(TextComponent::text("Interacted with invalid entity id"))
- .await;
+ self.kick(TextComponent::translate(
+ "multiplayer.disconnect.invalid_entity_attacked",
+ [],
+ ))
+ .await;
return;
};
@@ -1134,16 +1148,15 @@ 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(
- &TextComponent::translate(
- "build.tooHigh",
- vec![(WORLD_MAX_Y - 1).to_string().into()],
- )
- .color_named(NamedColor::Red),
- true,
- ))
- .await;
+ 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))
.await;
diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs
index 243b26385..23f649eae 100644
--- a/pumpkin/src/world/mod.rs
+++ b/pumpkin/src/world/mod.rs
@@ -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.