Skip to content
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

Closed
wants to merge 15 commits into from
Closed

Conversation

van-sprundel
Copy link

@van-sprundel van-sprundel commented Oct 18, 2024

Implementation for #15.

Supports target selectors.
Also added functions to get all_players and nearest_player.

@van-sprundel van-sprundel changed the title Add say command Add broadcast command Oct 18, 2024
Comment on lines 270 to 276
pub fn get_players(&self) -> Vec<Arc<Player>> {
self.current_players
.lock()
.values()
.cloned()
.collect::<Vec<_>>()
}
Copy link
Member

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

Copy link
Author

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

Comment on lines 47 to 97
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()
}
Copy link
Member

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)?;

Copy link
Author

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/commands/cmd_say.rs Outdated Show resolved Hide resolved

/// 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
Copy link
Member

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...)

Copy link
Author

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

pumpkin/src/server/mod.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@StripedMonkey StripedMonkey left a 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;
Copy link
Contributor

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()
    }

Comment on lines +266 to +270
pub fn broadcast_message(&self, content: &TextComponent) {
self.current_players.lock().values().for_each(|player| {
player.send_system_message(content.clone());
});
}
Copy link
Contributor

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());
Copy link
Contributor

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> {
Copy link
Contributor

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

@van-sprundel van-sprundel closed this by deleting the head repository Oct 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants