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 /give Command #234

Merged
merged 12 commits into from
Nov 7, 2024
7 changes: 7 additions & 0 deletions pumpkin-inventory/src/player.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::slice::IterMut;
use std::sync::atomic::AtomicU32;

use crate::container_click::MouseClick;
Expand Down Expand Up @@ -25,6 +26,8 @@ impl Default for PlayerInventory {
}

impl PlayerInventory {
pub const CONTAINER_ID: i8 = 0;

pub fn new() -> Self {
Self {
crafting: [None; 4],
Expand Down Expand Up @@ -134,6 +137,10 @@ impl PlayerInventory {
slots.push(&mut self.offhand);
slots
}

pub fn iter_items_mut(&mut self) -> IterMut<Option<ItemStack>> {
self.items.iter_mut()
}
}

impl Container for PlayerInventory {
Expand Down
7 changes: 7 additions & 0 deletions pumpkin-protocol/src/slot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,10 @@ impl From<Option<&ItemStack>> for Slot {
item.map(Slot::from).unwrap_or(Slot::empty())
}
}

impl From<&Option<ItemStack>> for Slot {
fn from(item: &Option<ItemStack>) -> Self {
item.map(|stack| Self::from(&stack))
.unwrap_or(Slot::empty())
}
}
14 changes: 9 additions & 5 deletions pumpkin-world/src/item/item_registry.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
use std::{collections::HashMap, sync::LazyLock};
use std::sync::LazyLock;

use serde::Deserialize;

const ITEMS_JSON: &str = include_str!("../../../assets/items.json");

pub static ITEMS: LazyLock<HashMap<String, Item>> = LazyLock::new(|| {
pub static ITEMS: LazyLock<Vec<Item>> = LazyLock::new(|| {
serde_json::from_str(ITEMS_JSON).expect("Could not parse items.json registry.")
});

pub fn get_item<'a>(name: &str) -> Option<&'a Item> {
ITEMS.iter().find(|item| item.name == name)
}

#[expect(dead_code)]
#[derive(Deserialize, Clone, Debug)]
pub struct Item {
id: u16,
name: String,
pub id: u16,
pub name: String,
translation_key: String,
max_stack: i8,
pub max_stack: i8,
max_durability: u16,
break_sound: String,
food: Option<FoodComponent>,
Expand Down
11 changes: 10 additions & 1 deletion pumpkin-world/src/item/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
mod item_categories;
mod item_registry;
pub mod item_registry;
pub use item_registry::ITEMS;
#[derive(serde::Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
Expand All @@ -24,3 +24,12 @@ impl PartialEq for ItemStack {
self.item_id == other.item_id
}
}

impl ItemStack {
pub fn new(item_count: u8, item_id: u16) -> Self {
Self {
item_count,
item_id,
}
}
}
104 changes: 62 additions & 42 deletions pumpkin/src/command/args/arg_bounded_num.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ use super::super::args::ArgumentConsumer;
use super::{Arg, DefaultNameArgConsumer, FindArg};

/// Consumes a single generic num, but only if it's in bounds.
pub(crate) struct BoundedNumArgumentConsumer<T: ArgNum> {
pub(crate) struct BoundedNumArgumentConsumer<T: ToFromNumber> {
min_inclusive: Option<T>,
max_inclusive: Option<T>,
name: Option<&'static str>,
}

#[async_trait]
impl<T: ArgNum> ArgumentConsumer for BoundedNumArgumentConsumer<T> {
impl<T: ToFromNumber> ArgumentConsumer for BoundedNumArgumentConsumer<T> {
async fn consume<'a>(
&self,
_src: &CommandSender<'a>,
Expand All @@ -30,39 +30,59 @@ impl<T: ArgNum> ArgumentConsumer for BoundedNumArgumentConsumer<T> {

if let Some(max) = self.max_inclusive {
if x > max {
return None;
return Some(Arg::Num(Err(())));
}
}

if let Some(min) = self.min_inclusive {
if x < min {
return None;
return Some(Arg::Num(Err(())));
}
}

Some(x.to_arg())
Some(Arg::Num(Ok(x.to_number())))
}
}

impl<'a, T: ArgNum> FindArg<'a> for BoundedNumArgumentConsumer<T> {
type Data = T;
impl<'a, T: ToFromNumber> FindArg<'a> for BoundedNumArgumentConsumer<T> {
type Data = Result<T, NotInBounds>;

fn find_arg(args: &super::ConsumedArgs, name: &str) -> Result<Self::Data, InvalidTreeError> {
match args.get(name) {
Some(arg) => match T::from_arg(arg) {
Some(x) => Ok(x),
_ => Err(InvalidTreeError::InvalidConsumptionError(Some(
name.to_string(),
))),
},
_ => Err(InvalidTreeError::InvalidConsumptionError(Some(
let Some(Arg::Num(result)) = args.get(name) else {
return Err(InvalidTreeError::InvalidConsumptionError(Some(
name.to_string(),
))),
}
)));
};

let data: Self::Data = match result {
Ok(num) => {
if let Some(x) = T::from_number(num) {
Ok(x)
} else {
return Err(InvalidTreeError::InvalidConsumptionError(Some(
name.to_string(),
)));
}
}
Err(()) => Err(()),
};

Ok(data)
}
}

impl<T: ArgNum> BoundedNumArgumentConsumer<T> {
pub(crate) type NotInBounds = ();

#[derive(Clone, Copy)]
pub(crate) enum Number {
F64(f64),
F32(f32),
I32(i32),
#[allow(unused)]
U32(u32),
}

impl<T: ToFromNumber> BoundedNumArgumentConsumer<T> {
pub(crate) const fn new() -> Self {
Self {
min_inclusive: None,
Expand All @@ -88,64 +108,64 @@ impl<T: ArgNum> BoundedNumArgumentConsumer<T> {
}
}

pub(crate) trait ArgNum: PartialOrd + Copy + Send + Sync + FromStr {
fn to_arg<'a>(self) -> Arg<'a>;
fn from_arg(arg: &Arg<'_>) -> Option<Self>;
pub(crate) trait ToFromNumber: PartialOrd + Copy + Send + Sync + FromStr {
fn to_number(self) -> Number;
fn from_number(arg: &Number) -> Option<Self>;
}

impl ArgNum for f64 {
fn to_arg<'a>(self) -> Arg<'a> {
Arg::F64(self)
impl ToFromNumber for f64 {
fn to_number(self) -> Number {
Number::F64(self)
}

fn from_arg(arg: &Arg<'_>) -> Option<Self> {
fn from_number(arg: &Number) -> Option<Self> {
match arg {
Arg::F64(x) => Some(*x),
Number::F64(x) => Some(*x),
_ => None,
}
}
}

impl ArgNum for f32 {
fn to_arg<'a>(self) -> Arg<'a> {
Arg::F32(self)
impl ToFromNumber for f32 {
fn to_number(self) -> Number {
Number::F32(self)
}

fn from_arg(arg: &Arg<'_>) -> Option<Self> {
fn from_number(arg: &Number) -> Option<Self> {
match arg {
Arg::F32(x) => Some(*x),
Number::F32(x) => Some(*x),
_ => None,
}
}
}

impl ArgNum for i32 {
fn to_arg<'a>(self) -> Arg<'a> {
Arg::I32(self)
impl ToFromNumber for i32 {
fn to_number(self) -> Number {
Number::I32(self)
}

fn from_arg(arg: &Arg<'_>) -> Option<Self> {
fn from_number(arg: &Number) -> Option<Self> {
match arg {
Arg::I32(x) => Some(*x),
Number::I32(x) => Some(*x),
_ => None,
}
}
}

impl ArgNum for u32 {
fn to_arg<'a>(self) -> Arg<'a> {
Arg::U32(self)
impl ToFromNumber for u32 {
fn to_number(self) -> Number {
Number::U32(self)
}

fn from_arg(arg: &Arg<'_>) -> Option<Self> {
fn from_number(arg: &Number) -> Option<Self> {
match arg {
Arg::U32(x) => Some(*x),
Number::U32(x) => Some(*x),
_ => None,
}
}
}

impl<T: ArgNum> DefaultNameArgConsumer for BoundedNumArgumentConsumer<T> {
impl<T: ToFromNumber> DefaultNameArgConsumer for BoundedNumArgumentConsumer<T> {
fn default_name(&self) -> &'static str {
// setting a single default name for all BoundedNumArgumentConsumer variants is probably a bad idea since it would lead to confusion
self.name.expect("Only use *_default variants of methods with a BoundedNumArgumentConsumer that has a name.")
Expand Down
2 changes: 1 addition & 1 deletion pumpkin/src/command/args/arg_entities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::entity::player::Player;
use crate::server::Server;

use super::super::args::ArgumentConsumer;
use super::arg_player::PlayersArgumentConsumer;
use super::arg_players::PlayersArgumentConsumer;
use super::{Arg, DefaultNameArgConsumer, FindArg};

/// todo: implement (currently just calls [`super::arg_player::PlayerArgumentConsumer`])
Expand Down
60 changes: 60 additions & 0 deletions pumpkin/src/command/args/arg_item.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use async_trait::async_trait;

use crate::{command::dispatcher::InvalidTreeError, server::Server};

use super::{
super::{
args::{ArgumentConsumer, RawArgs},
CommandSender,
},
Arg, DefaultNameArgConsumer, FindArg,
};

pub(crate) struct ItemArgumentConsumer;

#[async_trait]
impl ArgumentConsumer for ItemArgumentConsumer {
async fn consume<'a>(
&self,
_sender: &CommandSender<'a>,
_server: &'a Server,
args: &mut RawArgs<'a>,
) -> Option<Arg<'a>> {
let s = args.pop()?;

let name = if s.contains(':') {
s.to_string()
} else {
format!("minecraft:{s}")
};

// todo: get an actual item
Some(Arg::Item(name))
}
}

impl DefaultNameArgConsumer for ItemArgumentConsumer {
fn default_name(&self) -> &'static str {
"item"
}

fn get_argument_consumer(&self) -> &dyn ArgumentConsumer {
self
}
}

impl<'a> FindArg<'a> for ItemArgumentConsumer {
type Data = &'a str;

fn find_arg(
args: &'a super::ConsumedArgs,
name: &'a str,
) -> Result<Self::Data, InvalidTreeError> {
match args.get(name) {
Some(Arg::Item(name)) => Ok(name),
_ => Err(InvalidTreeError::InvalidConsumptionError(Some(
name.to_string(),
))),
}
}
}
11 changes: 5 additions & 6 deletions pumpkin/src/command/args/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::{collections::HashMap, hash::Hash, sync::Arc};

use arg_bounded_num::{NotInBounds, Number};
use async_trait::async_trait;
use pumpkin_core::{
math::{vector2::Vector2, vector3::Vector3},
Expand All @@ -19,8 +20,9 @@ pub(crate) mod arg_command;
pub(crate) mod arg_entities;
pub(crate) mod arg_entity;
pub(crate) mod arg_gamemode;
pub(crate) mod arg_item;
pub(crate) mod arg_message;
pub(crate) mod arg_player;
pub(crate) mod arg_players;
pub(crate) mod arg_position_2d;
pub(crate) mod arg_position_3d;
pub(crate) mod arg_rotation;
Expand Down Expand Up @@ -55,12 +57,9 @@ pub(crate) enum Arg<'a> {
Rotation(f32, f32),
GameMode(GameMode),
CommandTree(&'a CommandTree<'a>),
Item(String),
Msg(String),
F64(f64),
F32(f32),
I32(i32),
#[allow(unused)]
U32(u32),
Num(Result<Number, NotInBounds>),
#[allow(unused)]
Simple(String),
}
Expand Down
2 changes: 1 addition & 1 deletion pumpkin/src/command/commands/cmd_gamemode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::command::args::GetCloned;

use crate::TextComponent;

use crate::command::args::arg_player::PlayersArgumentConsumer;
use crate::command::args::arg_players::PlayersArgumentConsumer;

use crate::command::args::{Arg, ConsumedArgs};
use crate::command::dispatcher::InvalidTreeError;
Expand Down
Loading