From bf7663b60aff43ab4894ef4eb82668abb40da640 Mon Sep 17 00:00:00 2001 From: Clicks <58398364+CuzImClicks@users.noreply.github.com> Date: Wed, 30 Oct 2024 19:58:32 +0100 Subject: [PATCH] feat: add amount to `/give` (#525) Co-authored-by: Andrew Gazelka --- events/proof-of-concept/src/module/command.rs | 81 ++++++++++++++----- .../src/module/command/parse.rs | 67 +++++++++++++-- 2 files changed, 121 insertions(+), 27 deletions(-) diff --git a/events/proof-of-concept/src/module/command.rs b/events/proof-of-concept/src/module/command.rs index f1a2d3c7..ea08f88a 100644 --- a/events/proof-of-concept/src/module/command.rs +++ b/events/proof-of-concept/src/module/command.rs @@ -2,20 +2,21 @@ use std::borrow::Cow; use flecs_ecs::prelude::*; use hyperion::{ + chat, egress::player_join::{PlayerListActions, PlayerListEntry, PlayerListS2c}, net::{Compose, NetworkStreamRef}, simulation::{ blocks::Blocks, - command::{add_command, get_root_command, Command, Parser}, - event, Health, InGameName, Position, Uuid, + command::{add_command, cmd_with, get_root_command, Command, Parser}, + event, Health, IgnMap, InGameName, Position, Uuid, }, storage::EventQueue, system_registry::SystemId, uuid, + valence_ident::ident, valence_protocol::{ self, game_mode::OptGameMode, - ident, math::IVec3, nbt, packets::play::{ @@ -43,9 +44,26 @@ pub fn add_to_tree(world: &World) { // add to tree add_command(world, Command::literal("team"), root_command); add_command(world, Command::literal("zombie"), root_command); - add_command(world, Command::literal("give"), root_command); add_command(world, Command::literal("upgrade"), root_command); + cmd_with(world, "give", |scope| { + scope.argument_with( + "player", + Parser::Entity { + single: true, + only_players: true, + }, + |scope| { + scope.argument_with("", Parser::ItemStack, |scope| { + scope.argument("count", Parser::Integer { + min: Some(1), + max: None, + }); + }); + }, + ); + }); + let speed = add_command(world, Command::literal("speed"), root_command); add_command( world, @@ -101,6 +119,7 @@ struct CommandContext<'a> { inventory: &'a mut PlayerInventory, level: &'a mut Level, health: &'a mut Health, + ign_map: &'a IgnMap, } fn process_command(command: &ParsedCommand, context: &mut CommandContext<'_>) { @@ -109,7 +128,11 @@ fn process_command(command: &ParsedCommand, context: &mut CommandContext<'_>) { ParsedCommand::Team => handle_team_command(context), ParsedCommand::Zombie => handle_zombie_command(context), ParsedCommand::Dirt { x, y, z } => handle_dirt_command(*x, *y, *z, context), - ParsedCommand::Give => handle_give_command(context), + ParsedCommand::Give { + username, + item, + count, + } => handle_give_command(username, item, *count, context), ParsedCommand::Upgrade => handle_upgrade_command(context), ParsedCommand::Stats(stat, amount) => handle_stats(*stat, *amount, context), ParsedCommand::Health(amount) => handle_health_command(*amount, context), @@ -351,24 +374,39 @@ fn handle_stats(stat: Stat, amount: f32, context: &CommandContext<'_>) { }); } -fn handle_give_command(context: &mut CommandContext<'_>) { - let mut blue_wool_nbt = nbt::Compound::new(); +fn handle_give_command(username: &str, item_name: &str, count: i8, context: &CommandContext<'_>) { + let Some(item) = ItemKind::from_str(item_name) else { + let packet = chat!("Unknown item '{item_name:?}'"); + context + .compose + .unicast(&packet, context.stream, context.system_id, context.world) + .unwrap(); + return; + }; + + let Some(player) = context.ign_map.get(username) else { + let chat = chat!("Player {username} does not exist"); + context + .compose + .unicast(&chat, context.stream, context.system_id, context.world) + .unwrap(); + return; + }; - let can_place_on = [ - "minecraft:stone", - "minecraft:dirt", - "minecraft:grass_block", - "minecraft:blue_wool", - ] - .into_iter() - .map(std::convert::Into::into) - .collect(); + context + .world + .entity_from_id(*player) + .get::<&mut PlayerInventory>(|inventory| { + inventory.try_add_item(ItemStack::new(item, count, None)); + }); - blue_wool_nbt.insert("CanPlaceOn", nbt::List::String(can_place_on)); + let name = item.to_str(); + let packet = chat!("Gave {count} [{name}] to {username}"); context - .inventory - .try_add_item(ItemStack::new(ItemKind::BlueWool, 4, Some(blue_wool_nbt))); + .compose + .unicast(&packet, context.stream, context.system_id, context.world) + .unwrap(); } fn handle_dirt_command(x: i32, y: i32, z: i32, context: &mut CommandContext<'_>) { @@ -603,9 +641,9 @@ impl Module for CommandModule { let system_id = SystemId(8); - system!("handle_poc_events_player", world, &Compose($), &mut EventQueue>($), &mut Blocks($)) + system!("handle_poc_events_player", world, &Compose($), &mut EventQueue>($), &mut Blocks($), &IgnMap($)) .multi_threaded() - .each_iter(move |it: TableIter<'_, false>, _, (compose, event_queue, mc)| { + .each_iter(move |it: TableIter<'_, false>, _, (compose, event_queue, mc, ign_map)| { let span = trace_span!("handle_poc_events_player"); let _enter = span.enter(); @@ -644,6 +682,7 @@ impl Module for CommandModule { level, health, position, + ign_map, }; process_command(&command, &mut context); }, diff --git a/events/proof-of-concept/src/module/command/parse.rs b/events/proof-of-concept/src/module/command/parse.rs index 5b30b0ca..9e0e8fee 100644 --- a/events/proof-of-concept/src/module/command/parse.rs +++ b/events/proof-of-concept/src/module/command/parse.rs @@ -1,8 +1,8 @@ use nom::{ branch::alt, - bytes::complete::{tag, take_until}, + bytes::complete::{tag, take_until, take_while1}, character::complete::space1, - combinator::{map, map_res}, + combinator::{map, map_res, opt}, number::complete::float, sequence::preceded, IResult, Parser, @@ -21,13 +21,33 @@ pub enum ParsedCommand { Speed(f32), Team, Zombie, - Dirt { x: i32, y: i32, z: i32 }, - Give, + Dirt { + x: i32, + y: i32, + z: i32, + }, + Give { + username: String, + item: String, + count: i8, + }, Upgrade, Stats(Stat, f32), Health(f32), TpHere, - Tp { x: f32, y: f32, z: f32 }, + Tp { + x: f32, + y: f32, + z: f32, + }, +} + +fn is_valid_player_char(c: char) -> bool { + c.is_alphanumeric() || c == '_' +} + +fn space1_str(input: &str) -> IResult<&str, &str> { + space1::<&str, nom::error::Error<&str>>(input) } fn parse_speed(input: &str) -> IResult<&str, ParsedCommand> { @@ -62,7 +82,23 @@ fn parse_dirt(input: &str) -> IResult<&str, ParsedCommand> { } fn parse_give(input: &str) -> IResult<&str, ParsedCommand> { - map(tag("give"), |_| ParsedCommand::Give).parse(input) + map( + ( + tag("give "), + take_while1(is_valid_player_char), + preceded( + space1_str, + preceded(opt(tag("minecraft:")), take_while1(is_valid_player_char)), + ), + opt(preceded(space1_str, nom::character::complete::i8)), + ), + |(_, username, item, count)| ParsedCommand::Give { + username: username.to_string(), + item: item.to_string(), + count: count.unwrap_or(1), + }, + ) + .parse(input) } fn parse_upgrade(input: &str) -> IResult<&str, ParsedCommand> { @@ -124,3 +160,22 @@ pub fn command(input: &str) -> IResult<&str, ParsedCommand> { )) .parse(input) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_give_command() { + let input = "give Cuz_Im_Clicks minecraft:dirt 64"; + let result = parse_give(input); + assert!(result.is_ok()); + } + + #[test] + fn test_parse_give_command_no_minecraft() { + let input = "give Cuz_Im_Clicks acacia_button 64"; + let result = parse_give(input); + assert!(result.is_ok()); + } +}