-
-
Notifications
You must be signed in to change notification settings - Fork 148
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add broadcast command #148
Conversation
pumpkin/src/world/mod.rs
Outdated
pub fn get_players(&self) -> Vec<Arc<Player>> { | ||
self.current_players | ||
.lock() | ||
.values() | ||
.cloned() | ||
.collect::<Vec<_>>() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like copying the Vec here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it. Changed it to clone just the name string instead of the entire player
pumpkin/src/commands/cmd_say.rs
Outdated
fn parse_selectors(content: &str, sender: &CommandSender, server: &Server) -> String { | ||
let mut final_message = String::new(); | ||
|
||
let tokens: Vec<&str> = content.split_whitespace().collect(); | ||
|
||
for token in tokens { | ||
// TODO impl @e | ||
let result = match token { | ||
"@p" => { | ||
let position = match sender { | ||
CommandSender::Player(p) => p.last_position.load(), | ||
_ => Vector3::new(0., 0., 0.), | ||
}; | ||
vec![server | ||
.get_nearest_player(&position) | ||
.map_or_else(|| String::from("nobody"), |p| p.gameprofile.name.clone())] | ||
} | ||
"@r" => { | ||
let online_players: Vec<String> = server | ||
.get_online_players() | ||
.map(|p| p.gameprofile.name.clone()) | ||
.collect(); | ||
|
||
if online_players.is_empty() { | ||
vec![String::from("nobody")] | ||
} else { | ||
vec![online_players[rand::random::<usize>() % online_players.len()].clone()] | ||
} | ||
} | ||
"@s" => match sender { | ||
CommandSender::Player(p) => vec![p.gameprofile.name.clone()], | ||
_ => vec![String::from("console")], | ||
}, | ||
"@a" => server | ||
.get_online_players() | ||
.map(|p| p.gameprofile.name.clone()) | ||
.collect::<Vec<_>>(), | ||
"@here" => server | ||
.get_online_players() | ||
.map(|p| p.gameprofile.name.clone()) | ||
.collect::<Vec<_>>(), | ||
_ => vec![token.to_string()], | ||
}; | ||
|
||
// formatted player names | ||
final_message.push_str(&format_player_names(&result)); | ||
final_message.push(' '); | ||
} | ||
|
||
final_message.trim_end().to_string() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already have player arguments implemented, See for example gamemode command or kick command...
You can implement is easily by providing the target as an argument .with_child(argument(ARG_TARGET, consume_arg_player)
and then parsing it let target = parse_arg_player(sender, server, ARG_TARGET, args)?;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The say
command supports multiple target specifiers, it's not a command argument, but a way the content gets formatted
pumpkin/src/server/mod.rs
Outdated
|
||
/// Gets the nearest player from the world position | ||
pub fn get_nearest_player(&self, target: &Vector3<f64>) -> Option<Arc<Player>> { | ||
// TODO respect which world the player is in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can easily get the World from a player by calling player.living_enitity.entity.world
. But i the current plan is to search for Player on all world (e.g. Gamemode, Kick Command...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In minecraft, the nearest player has to be in the same world. If that's the plan though, then I can change it
…odule This function is specific to the command, we don't want to check any other world since a target specifier only checks the world the player is in
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your efforts trying to implement this! I do have some quibbles and things that would be good to see changed.
@@ -0,0 +1,9 @@ | |||
use super::vector3::Vector3; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I'm a fan of having something as trivial as "distance" be its own file unless we have a larger set of traits. Is there a reason this isn't in Vector3
?
Something like:
pub fn distance_to(&self, other: &Vector3<T>) -> T {
(self - other).length()
}
pub fn broadcast_message(&self, content: &TextComponent) { | ||
self.current_players.lock().values().for_each(|player| { | ||
player.send_system_message(content.clone()); | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Can we make this generic over an arbitrary ChatMessage trait implemented for strings, textcomponents, etc? Or make it generic over something like Into<TextComponent>
?
@@ -77,6 +78,7 @@ pub fn default_dispatcher<'a>() -> CommandDispatcher<'a> { | |||
dispatcher.register(cmd_echest::init_command_tree()); | |||
dispatcher.register(cmd_kill::init_command_tree()); | |||
dispatcher.register(cmd_kick::init_command_tree()); | |||
dispatcher.register(cmd_say::init_command_tree()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't your problem per-se, but When I see large blocks of repetitive code like this my eyes start to glaze over and I stop being able to read.
It sets off alarm bells that the code could be simplified some. It might not be getting to that point right now, but with how large and repetitive this is getting, another abstraction would be nice.
List Option
Something like
fn register_commands(&mut self, commands: impl IntoIterator<C>)
where C: CommandTree
{
for command in commands {
dispatcher.register(command::default());
}
}
// in default_dispatcher
dispatcher.register_commands(vec![ cmd_pumpkin, cmd_gamemode, ... ]);
Macro Option
macro_rules! init_commands {
(($command:ident),*) => { $( dispatcher.register($command::init_command_tree()); )*}
}
// In default_dispatcher
init_commands!(cmd_pumpkin, cmd_gamemode, cmd_stop, cmd_help, cmd_echest, cmd_kill);
Say what you will about macros, but as long as they're explained, they make the boilerplate much more legible.
In the current code, dispatcher.register(...::init_command_tree());
is all noise, and what you're actually reading needs to be that tiny cmd_say
The macro option can be implemented as-is, but the list list one requires you to do some more significant refactors. Neither has to be implemented right now, but it's getting there.
.collect::<Vec<_>>() | ||
} | ||
|
||
pub fn get_nearest_player_name(&self, target: &Vector3<f64>) -> Option<String> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand this PR is not intended to give us the functionality for everything, but this should not be get_nearest_player_name
, but
let Some(name) = get_nearest_player().map(Player::get_name) else {
return Err(...)
}
or something.
for a single get_nearest_...
it works, but implementing this functionality for EVERYTHING you can get from a "nearest player" would be terrifying
Implementation for #15.
Supports target selectors.
Also added functions to get
all_players
andnearest_player
.