valence-rs / valence

A Rust framework for building Minecraft servers.
http://valence.rs/
MIT License
2.77k stars 142 forks source link

Brigadier (Command API) #332

Open Jenya705 opened 1 year ago

Jenya705 commented 1 year ago

Describe the problem related to your feature request.

There is no command api for valence. I would start with porting brigadier (library that mojang uses to handle commands)

What solution would you like?

A command object which will contain nodes. Command object will handle:

What alternative(s) have you considered?

Just do like in bukkit. All arguments passed into an array of strings.

Additional context

Brigadier: https://github.com/Mojang/brigadier

dyc3 commented 1 year ago

We've already discussed this in discord quite a bit, but I can't seem to find the thread right now.

Edit: Here's the thread (thanks @AviiNL): https://discord.com/channels/998132822239870997/1072277129183625256 I'd recommend reading through it to get caught up.

rj00a commented 1 year ago

We discussed this a bit on discord in the past, but the way Brigadier relies on mutable callback functions and dynamic typing for the executes context doesn't seem like a great fit for Rust or ECS (or any language really). For example:

CommandDispatcher<CommandSourceStack> dispatcher = new CommandDispatcher<>();

dispatcher.register(
    literal("foo")
        .then(
            argument("bar", integer())
                .executes(c -> {
                    System.out.println("Bar is " + getInteger(c, "bar"));
                    return 1;
                })
        )
        .executes(c -> {
            System.out.println("Called foo with no arguments");
            return 1;
        })
);

Instead of the above, I would rather see something roughly like this:

#[derive(Command)]
struct MyFooCommand {
    bar: Option<i32>,
}

fn handle_foo_commands(foos: EventReader<CommandEvent<MyFooCommand>>) {
    for event in foos.iter_mut() {
        match event.command.bar {
            Some(n) => println!("Bar is {n}"),
            None => println!("Called foo with no arguments"),
        }
    }
}

In other words, you describe the structure of the command graph as a Rust data type and pattern match on it to do the execution.

You'd register MyFooCommand as "foo" somewhere (CommandContext resource or something) and off you go.

You could also represent cyclic commands this way:

#[derive(Command)]
struct MyCyclicCommand {
    blah: i32,
    recursion: Option<Box<MyCyclicCommand>>,
}

(Disclaimer: I have not actually used Brigadier)

Jenya705 commented 1 year ago

We discussed this a bit on discord in the past, but the way Brigadier relies on mutable callback functions and dynamic typing for the executes context doesn't seem like a great fit for Rust or ECS (or any language really). For example:

CommandDispatcher<CommandSourceStack> dispatcher = new CommandDispatcher<>();

dispatcher.register(
    literal("foo")
        .then(
            argument("bar", integer())
                .executes(c -> {
                    System.out.println("Bar is " + getInteger(c, "bar"));
                    return 1;
                })
        )
        .executes(c -> {
            System.out.println("Called foo with no arguments");
            return 1;
        })
);

Instead of the above, I would rather see something roughly like this:

#[derive(Command)]
struct MyFooCommand {
    bar: Option<i32>,
}

fn handle_foo_commands(foos: EventReader<CommandEvent<MyFooCommand>>) {
    for event in foos.iter_mut() {
        match event.command.bar {
            Some(n) => println!("Bar is {n}"),
            None => println!("Called foo with no arguments"),
        }
    }
}

In other words, you describe the structure of the command graph as a Rust data type and pattern match on it to do the execution.

You'd register MyFooCommand as "foo" somewhere (CommandContext resource or something) and off you go.

You could also represent cyclic commands this way:

#[derive(Command)]
struct MyCyclicCommand {
    blah: i32,
    recursion: Option<Box<MyCyclicCommand>>,
}

(Disclaimer: I have not actually used Brigadier)

I've thought we could use a Execute Id, which is just a number. So execute function returns this id and fill the vector with arguments, which were calculated during finding this id.

match command_dispatcher.execute("gamemode @s creative", &mut arguments) {
    Ok(0) => {
        let (entity_selector, game_mode_enum) = parse_arguments!(arguments);

        for entity in entity_selector.iter() {
             let mut gamemode = gamemode_query.get(entity) else { continue; };
             gamemode.0 = game_mode_enum; 
        }
    } // set creative gamemode to @s player
    // possibly others execute ids
    Err(err) => {} // some error
}

This ids are set when the user is 'building' nodes. (Like in brigadier but instead of callbacks a number)

dyc3 commented 1 week ago

To speed up development, I think we should try to avoid reinventing the wheel. Azalea seems to have a workable crate already, we should evaluate it if we could use it in Valence. https://github.com/azalea-rs/azalea/tree/main/azalea-brigadier

JackCrumpLeys commented 1 week ago

To speed up development, I think we should try to avoid reinventing the wheel. Azalea seems to have a workable crate already, we should evaluate it if we could use it in Valence. https://github.com/azalea-rs/azalea/tree/main/azalea-brigadier

I already did this. See merged pr #446. We haven't done server side completion yet tho.