From 72a9670ba90065e06e8bcd1c6a5eb1d57ed4b78a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nico=20G=C3=B6bel?= <147538729+leobeg@users.noreply.github.com> Date: Mon, 30 Dec 2024 22:30:12 +0100 Subject: [PATCH] Changed bossbar title to TextComponent instead of String (#435) * Changed bossbar title to TextComponent instead of String * Clippy and fmt --- pumpkin/src/command/args/arg_textcomponent.rs | 81 +++++++++ pumpkin/src/command/args/mod.rs | 3 + pumpkin/src/command/commands/cmd_bossbar.rs | 170 +++++++++--------- pumpkin/src/world/bossbar.rs | 10 +- pumpkin/src/world/custom_bossbar.rs | 5 +- 5 files changed, 177 insertions(+), 92 deletions(-) create mode 100644 pumpkin/src/command/args/arg_textcomponent.rs diff --git a/pumpkin/src/command/args/arg_textcomponent.rs b/pumpkin/src/command/args/arg_textcomponent.rs new file mode 100644 index 000000000..fbe8ba97a --- /dev/null +++ b/pumpkin/src/command/args/arg_textcomponent.rs @@ -0,0 +1,81 @@ +use crate::command::args::{Arg, ArgumentConsumer, FindArg, GetClientSideArgParser}; +use crate::command::dispatcher::CommandError; +use crate::command::tree::RawArgs; +use crate::command::CommandSender; +use crate::server::Server; +use async_trait::async_trait; +use pumpkin_core::text::TextComponent; +use pumpkin_protocol::client::play::{ + CommandSuggestion, ProtoCmdArgParser, ProtoCmdArgSuggestionType, +}; + +pub(crate) struct TextComponentArgConsumer; + +impl GetClientSideArgParser for TextComponentArgConsumer { + fn get_client_side_parser(&self) -> ProtoCmdArgParser { + ProtoCmdArgParser::Component + } + + fn get_client_side_suggestion_type_override(&self) -> Option { + None + } +} + +#[async_trait] +impl ArgumentConsumer for TextComponentArgConsumer { + async fn consume<'a>( + &'a self, + _sender: &CommandSender<'a>, + _server: &'a Server, + args: &mut RawArgs<'a>, + ) -> Option> { + let s = args.pop()?; + + let text_component = parse_text_component(s); + + let Some(text_component) = text_component else { + if s.starts_with('"') && s.ends_with('"') { + let s = s.replace('"', ""); + return Some(Arg::TextComponent(TextComponent::text(s))); + } + return None; + }; + + Some(Arg::TextComponent(text_component)) + } + + async fn suggest<'a>( + &'a self, + _sender: &CommandSender<'a>, + _server: &'a Server, + _input: &'a str, + ) -> Result>, CommandError> { + Ok(None) + } +} + +impl FindArg<'_> for TextComponentArgConsumer { + type Data = TextComponent; + + fn find_arg(args: &super::ConsumedArgs, name: &str) -> Result { + match args.get(name) { + Some(Arg::TextComponent(data)) => Ok(data.clone()), + _ => Err(CommandError::InvalidConsumption(Some(name.to_string()))), + } + } +} + +fn parse_text_component(input: &str) -> Option { + if input.starts_with('[') && input.ends_with(']') { + let text_component_array: Option> = + serde_json::from_str(input).unwrap_or(None); + let mut text_component_array = text_component_array?; + let mut constructed_text_component = text_component_array[0].clone(); + text_component_array.remove(0); + constructed_text_component.extra = text_component_array; + + Some(constructed_text_component) + } else { + serde_json::from_str(input).unwrap_or(None) + } +} diff --git a/pumpkin/src/command/args/mod.rs b/pumpkin/src/command/args/mod.rs index f1fa8f85b..145aaba05 100644 --- a/pumpkin/src/command/args/mod.rs +++ b/pumpkin/src/command/args/mod.rs @@ -2,6 +2,7 @@ use std::{collections::HashMap, hash::Hash, sync::Arc}; use arg_bounded_num::{NotInBounds, Number}; use async_trait::async_trait; +use pumpkin_core::text::TextComponent; use pumpkin_core::{ math::{position::WorldPosition, vector2::Vector2, vector3::Vector3}, GameMode, @@ -36,6 +37,7 @@ pub(crate) mod arg_position_block; pub(crate) mod arg_resource_location; pub(crate) mod arg_rotation; pub(crate) mod arg_simple; +pub(crate) mod arg_textcomponent; mod coordinate; /// see [`crate::commands::tree_builder::argument`] @@ -87,6 +89,7 @@ pub(crate) enum Arg<'a> { BossbarColor(BossbarColor), BossbarStyle(BossbarDivisions), Msg(String), + TextComponent(TextComponent), Num(Result), Bool(bool), #[allow(unused)] diff --git a/pumpkin/src/command/commands/cmd_bossbar.rs b/pumpkin/src/command/commands/cmd_bossbar.rs index aa7297406..9e3291136 100644 --- a/pumpkin/src/command/commands/cmd_bossbar.rs +++ b/pumpkin/src/command/commands/cmd_bossbar.rs @@ -4,12 +4,11 @@ use crate::command::args::arg_bossbar_style::BossbarStyleArgumentConsumer; use crate::command::args::arg_bounded_num::BoundedNumArgumentConsumer; use crate::command::args::arg_players::PlayersArgumentConsumer; use crate::command::args::arg_resource_location::ResourceLocationArgumentConsumer; -use crate::command::args::arg_simple::SimpleArgConsumer; -use crate::command::args::{ - Arg, ConsumedArgs, DefaultNameArgConsumer, FindArg, FindArgDefaultName, -}; + +use crate::command::args::{ConsumedArgs, DefaultNameArgConsumer, FindArg, FindArgDefaultName}; use crate::command::dispatcher::CommandError; -use crate::command::dispatcher::CommandError::InvalidConsumption; + +use crate::command::args::arg_textcomponent::TextComponentArgConsumer; use crate::command::tree::CommandTree; use crate::command::tree_builder::{argument, argument_default_name, literal}; use crate::command::{CommandExecutor, CommandSender}; @@ -55,7 +54,6 @@ enum CommandValueSet { struct BossbarAddExecuter; #[async_trait] -#[expect(clippy::inefficient_to_string)] impl CommandExecutor for BossbarAddExecuter { async fn execute<'a>( &self, @@ -64,9 +62,7 @@ impl CommandExecutor for BossbarAddExecuter { args: &ConsumedArgs<'a>, ) -> Result<(), CommandError> { let namespace = non_autocomplete_consumer().find_arg_default_name(args)?; - let Some(Arg::Simple(name)) = args.get(ARG_NAME) else { - return Err(InvalidConsumption(Some(ARG_NAME.into()))); - }; + let text_component = TextComponentArgConsumer::find_arg(args, ARG_NAME)?; if server.bossbars.lock().await.has_bossbar(namespace) { send_error_message( @@ -77,13 +73,21 @@ impl CommandExecutor for BossbarAddExecuter { return Ok(()); } - let bossbar = Bossbar::new(name.to_string()); + let bossbar = Bossbar::new(text_component); server .bossbars .lock() .await .create_bossbar(namespace.to_string(), bossbar.clone()); + sender + .send_message( + TextComponent::text("Created custom bossbar [") + .add_child(bossbar.title) + .add_child(TextComponent::text("]")), + ) + .await; + Ok(()) } } @@ -111,36 +115,30 @@ impl CommandExecutor for BossbarGetExecuter { match self.0 { CommandValueGet::Max => { - send_success_message( + send_prefix_success_message( sender, - format!( - "Custom bossbar [{}] has a maximum of {}", - bossbar.bossbar_data.title, bossbar.max - ), + bossbar.bossbar_data.title, + format!("has a maximum of {}", bossbar.max), ) .await; return Ok(()); } CommandValueGet::Players => {} CommandValueGet::Value => { - send_success_message( + send_prefix_success_message( sender, - format!( - "Custom bossbar [{}] has a value of {}", - bossbar.bossbar_data.title, bossbar.value - ), + bossbar.bossbar_data.title, + format!("has a value of {}", bossbar.value), ) .await; return Ok(()); } CommandValueGet::Visible => { let state = if bossbar.visible { "shown" } else { "hidden" }; - send_success_message( + send_prefix_success_message( sender, - format!( - "Custom bossbar [{}] is currently {state}", - bossbar.bossbar_data.title - ), + bossbar.bossbar_data.title, + format!("is currently {state}"), ) .await; return Ok(()); @@ -167,24 +165,25 @@ impl CommandExecutor for BossbarListExecuter { return Ok(()); }; - let mut bossbars_string = String::new(); + let mut bossbars_text = TextComponent::text(format!( + "There are {} custom bossbar(s) active: ", + bossbars.len() + )); for (i, bossbar) in bossbars.iter().enumerate() { if i == 0 { - bossbars_string += format!("[{}]", bossbar.bossbar_data.title).as_str(); + bossbars_text = bossbars_text + .add_child(TextComponent::text("[")) + .add_child(bossbar.bossbar_data.title.clone()) + .add_child(TextComponent::text("]")); } else { - bossbars_string += format!(", [{}]", bossbar.bossbar_data.title).as_str(); + bossbars_text = bossbars_text + .add_child(TextComponent::text(", [")) + .add_child(bossbar.bossbar_data.title.clone()) + .add_child(TextComponent::text("]")); } } - send_success_message( - sender, - format!( - "There are {} custom bossbar(s) active: {}", - bossbars.len(), - bossbars_string - ), - ) - .await; + sender.send_message(bossbars_text).await; Ok(()) } } @@ -266,12 +265,10 @@ impl CommandExecutor for BossbarSetExecuter { return Ok(()); } } - send_success_message( + send_prefix_success_message( sender, - format!( - "Custom bossbar [{}] has changed color", - bossbar.bossbar_data.title - ), + bossbar.bossbar_data.title, + String::from("has changed color"), ) .await; Ok(()) @@ -305,25 +302,21 @@ impl CommandExecutor for BossbarSetExecuter { } } - send_success_message( + send_prefix_success_message( sender, - format!( - "Custom bossbar [{}] has changed maximum to {}", - bossbar.bossbar_data.title, max_value - ), + bossbar.bossbar_data.title, + format!("has changed maximum to {max_value}"), ) .await; Ok(()) } CommandValueSet::Name => { - let Some(Arg::Simple(name)) = args.get(ARG_NAME) else { - return Err(InvalidConsumption(Some(ARG_NAME.into()))); - }; + let text_component = TextComponentArgConsumer::find_arg(args, ARG_NAME)?; match server .bossbars .lock() .await - .update_name(server, namespace, name) + .update_name(server, namespace, text_component.clone()) .await { Ok(()) => {} @@ -333,8 +326,12 @@ impl CommandExecutor for BossbarSetExecuter { } } - send_success_message(sender, format!("Custom bossbar [{name}] has been renamed")) - .await; + send_prefix_success_message( + sender, + text_component, + String::from("has been renamed"), + ) + .await; Ok(()) } CommandValueSet::Players(has_players) => { @@ -352,12 +349,10 @@ impl CommandExecutor for BossbarSetExecuter { return Ok(()); } } - send_success_message( + send_prefix_success_message( sender, - format!( - "Custom bossbar [{}] no longer has any players", - bossbar.bossbar_data.title - ), + bossbar.bossbar_data.title, + String::from("no longer has any players"), ) .await; return Ok(()); @@ -388,13 +383,10 @@ impl CommandExecutor for BossbarSetExecuter { .map(|player| player.gameprofile.name.clone()) .collect(); - send_success_message( + send_prefix_success_message( sender, - format!( - "Custom bossbar [{}] now has {count} player(s): {}", - bossbar.bossbar_data.title, - player_names.join(", ") - ), + bossbar.bossbar_data.title, + format!("now has {count} player(s): {}", player_names.join(", ")), ) .await; Ok(()) @@ -414,12 +406,10 @@ impl CommandExecutor for BossbarSetExecuter { return Ok(()); } } - send_success_message( + send_prefix_success_message( sender, - format!( - "Custom bossbar [{}] has changed style", - bossbar.bossbar_data.title - ), + bossbar.bossbar_data.title, + String::from("has changed style"), ) .await; Ok(()) @@ -448,12 +438,10 @@ impl CommandExecutor for BossbarSetExecuter { } } - send_success_message( + send_prefix_success_message( sender, - format!( - "Custom bossbar [{}] has changed value to {}", - bossbar.bossbar_data.title, value - ), + bossbar.bossbar_data.title, + format!("changed value to {value}"), ) .await; Ok(()) @@ -476,12 +464,10 @@ impl CommandExecutor for BossbarSetExecuter { } let state = if visibility { "visible" } else { "hidden" }; - send_success_message( + send_prefix_success_message( sender, - format!( - "Custom bossbar [{}] is now {state}", - bossbar.bossbar_data.title - ), + bossbar.bossbar_data.title, + format!("is now {state}"), ) .await; Ok(()) @@ -500,12 +486,11 @@ fn value_consumer() -> BoundedNumArgumentConsumer { pub fn init_command_tree() -> CommandTree { CommandTree::new(NAMES, DESCRIPTION) - .with_child( - literal("add").with_child( - argument_default_name(non_autocomplete_consumer()) - .with_child(argument(ARG_NAME, SimpleArgConsumer).execute(BossbarAddExecuter)), + .with_child(literal("add").with_child( + argument_default_name(non_autocomplete_consumer()).with_child( + argument(ARG_NAME, TextComponentArgConsumer).execute(BossbarAddExecuter), ), - ) + )) .with_child( literal("get").with_child( argument_default_name(autocomplete_consumer()) @@ -542,7 +527,7 @@ pub fn init_command_tree() -> CommandTree { ) .with_child( literal("name").with_child( - argument(ARG_NAME, SimpleArgConsumer) + argument(ARG_NAME, TextComponentArgConsumer) .execute(BossbarSetExecuter(CommandValueSet::Name)), ), ) @@ -576,6 +561,21 @@ pub fn init_command_tree() -> CommandTree { ) } +fn bossbar_prefix(title: TextComponent, trailing: &str) -> TextComponent { + TextComponent::text("Custom bossbar [") + .add_child(title) + .add_child(TextComponent::text(format!("] {trailing}"))) +} + +async fn send_prefix_success_message( + sender: &CommandSender<'_>, + title: TextComponent, + message: String, +) { + sender + .send_message(bossbar_prefix(title, message.as_str())) + .await; +} async fn send_success_message(sender: &CommandSender<'_>, message: String) { sender.send_message(TextComponent::text(message)).await; } diff --git a/pumpkin/src/world/bossbar.rs b/pumpkin/src/world/bossbar.rs index 1c0a9bc00..cd51a2fa1 100644 --- a/pumpkin/src/world/bossbar.rs +++ b/pumpkin/src/world/bossbar.rs @@ -34,7 +34,7 @@ pub enum BossbarFlags { #[derive(Clone)] pub struct Bossbar { pub uuid: Uuid, - pub title: String, + pub title: TextComponent, pub health: f32, pub color: BossbarColor, pub division: BossbarDivisions, @@ -43,7 +43,7 @@ pub struct Bossbar { impl Bossbar { #[must_use] - pub fn new(title: String) -> Self { + pub fn new(title: TextComponent) -> Self { let uuid = Uuid::new_v4(); Self { @@ -63,7 +63,7 @@ impl Player { // Maybe this section could be implemented. feel free to change let bossbar = bossbar.clone(); let boss_action = BosseventAction::Add { - title: TextComponent::text(bossbar.title), + title: bossbar.title, health: bossbar.health, color: (bossbar.color as u8).into(), division: (bossbar.division as u8).into(), @@ -87,8 +87,8 @@ impl Player { self.client.send_packet(&packet).await; } - pub async fn update_bossbar_title(&self, uuid: &Uuid, title: String) { - let boss_action = BosseventAction::UpdateTile(TextComponent::text(title)); + pub async fn update_bossbar_title(&self, uuid: &Uuid, title: TextComponent) { + let boss_action = BosseventAction::UpdateTile(title); let packet = CBossEvent::new(uuid, boss_action); self.client.send_packet(&packet).await; diff --git a/pumpkin/src/world/custom_bossbar.rs b/pumpkin/src/world/custom_bossbar.rs index b1e623401..b801063d3 100644 --- a/pumpkin/src/world/custom_bossbar.rs +++ b/pumpkin/src/world/custom_bossbar.rs @@ -2,6 +2,7 @@ use crate::command::args::GetCloned; use crate::entity::player::Player; use crate::server::Server; use crate::world::bossbar::{Bossbar, BossbarColor, BossbarDivisions}; +use pumpkin_core::text::TextComponent; use std::collections::HashMap; use std::sync::Arc; use thiserror::Error; @@ -236,7 +237,7 @@ impl CustomBossbars { &mut self, server: &Server, resource_location: &str, - new_title: &str, + new_title: TextComponent, ) -> Result<(), BossbarUpdateError> { let bossbar = self.custom_bossbars.get_mut(resource_location); if let Some(bossbar) = bossbar { @@ -246,7 +247,7 @@ impl CustomBossbars { ))); } - bossbar.bossbar_data.title = new_title.to_string(); + bossbar.bossbar_data.title = new_title; if !bossbar.visible { return Ok(());