From e7f1420da335a50540c36e3a3524e20f5826fa52 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Wed, 22 Jan 2025 23:31:56 +0100 Subject: [PATCH] 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 --- pumpkin-util/src/text/mod.rs | 23 ++- pumpkin/src/command/commands/cmd_deop.rs | 6 +- pumpkin/src/command/commands/cmd_fill.rs | 8 +- pumpkin/src/command/commands/cmd_gamemode.rs | 47 +++--- pumpkin/src/command/commands/cmd_give.rs | 33 +++-- pumpkin/src/command/commands/cmd_kick.rs | 18 +-- pumpkin/src/command/commands/cmd_kill.rs | 10 +- pumpkin/src/command/commands/cmd_list.rs | 22 ++- pumpkin/src/command/commands/cmd_op.rs | 17 ++- pumpkin/src/command/commands/cmd_setblock.rs | 12 +- pumpkin/src/command/commands/cmd_stop.rs | 4 +- pumpkin/src/command/commands/cmd_time.rs | 14 +- .../src/command/commands/cmd_worldborder.rs | 134 ++++++++++-------- pumpkin/src/entity/player.rs | 9 +- pumpkin/src/net/mod.rs | 21 ++- pumpkin/src/net/packet/config.rs | 12 +- pumpkin/src/net/packet/handshake.rs | 13 +- pumpkin/src/net/packet/login.rs | 33 +++-- pumpkin/src/net/packet/play.rs | 73 ++++++---- pumpkin/src/world/mod.rs | 18 ++- 20 files changed, 309 insertions(+), 218 deletions(-) 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.