mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
Add /give Command (#234)
* add prototype for /give command * fix /give namespace * fix accidentally replaced text * remove dbg! statements * replace block_registry with item_registry * sync added items with client * warn!(..) => log::warn!(..) --------- Co-authored-by: user622628252416 <nnew8715@gmail.com>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
use std::slice::IterMut;
|
||||
use std::sync::atomic::AtomicU32;
|
||||
|
||||
use crate::container_click::MouseClick;
|
||||
@@ -25,6 +26,8 @@ impl Default for PlayerInventory {
|
||||
}
|
||||
|
||||
impl PlayerInventory {
|
||||
pub const CONTAINER_ID: i8 = 0;
|
||||
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
crafting: [None; 4],
|
||||
@@ -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 {
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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")]
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
@@ -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,
|
||||
@@ -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.")
|
||||
|
||||
@@ -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`])
|
||||
|
||||
60
pumpkin/src/command/args/arg_item.rs
Normal file
60
pumpkin/src/command/args/arg_item.rs
Normal file
@@ -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(),
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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},
|
||||
@@ -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;
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
89
pumpkin/src/command/commands/cmd_give.rs
Normal file
89
pumpkin/src/command/commands/cmd_give.rs
Normal file
@@ -0,0 +1,89 @@
|
||||
use async_trait::async_trait;
|
||||
use pumpkin_core::text::color::{Color, NamedColor};
|
||||
use pumpkin_core::text::TextComponent;
|
||||
use pumpkin_world::item::item_registry;
|
||||
|
||||
use crate::command::args::arg_bounded_num::BoundedNumArgumentConsumer;
|
||||
use crate::command::args::arg_item::ItemArgumentConsumer;
|
||||
use crate::command::args::arg_players::PlayersArgumentConsumer;
|
||||
use crate::command::args::{ConsumedArgs, FindArg, FindArgDefaultName};
|
||||
use crate::command::tree::CommandTree;
|
||||
use crate::command::tree_builder::{argument, argument_default_name, require};
|
||||
use crate::command::{CommandExecutor, CommandSender, InvalidTreeError};
|
||||
|
||||
const NAMES: [&str; 1] = ["give"];
|
||||
|
||||
const DESCRIPTION: &str = "Give items to player(s).";
|
||||
|
||||
const ARG_ITEM: &str = "item";
|
||||
|
||||
static ITEM_COUNT_CONSUMER: BoundedNumArgumentConsumer<u32> =
|
||||
BoundedNumArgumentConsumer::new().name("count").max(6400);
|
||||
|
||||
struct GiveExecutor;
|
||||
|
||||
#[async_trait]
|
||||
impl CommandExecutor for GiveExecutor {
|
||||
async fn execute<'a>(
|
||||
&self,
|
||||
sender: &mut CommandSender<'a>,
|
||||
_server: &crate::server::Server,
|
||||
args: &ConsumedArgs<'a>,
|
||||
) -> Result<(), InvalidTreeError> {
|
||||
let targets = PlayersArgumentConsumer.find_arg_default_name(args)?;
|
||||
|
||||
let item_name = ItemArgumentConsumer::find_arg(args, ARG_ITEM)?;
|
||||
|
||||
let Some(item) = item_registry::get_item(item_name) else {
|
||||
sender
|
||||
.send_message(
|
||||
TextComponent::text_string(format!("Item {item_name} does not exist."))
|
||||
.color(Color::Named(NamedColor::Red)),
|
||||
)
|
||||
.await;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let item_count = match ITEM_COUNT_CONSUMER.find_arg_default_name(args) {
|
||||
Err(_) => 1,
|
||||
Ok(Ok(count)) => count,
|
||||
Ok(Err(())) => {
|
||||
sender
|
||||
.send_message(
|
||||
TextComponent::text("Item count is too large or too small.")
|
||||
.color(Color::Named(NamedColor::Red)),
|
||||
)
|
||||
.await;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
for target in targets {
|
||||
target.give_items(item, item_count).await;
|
||||
}
|
||||
|
||||
sender
|
||||
.send_message(TextComponent::text_string(match targets {
|
||||
[target] => format!(
|
||||
"Gave {item_count} {item_name} to {}",
|
||||
target.gameprofile.name
|
||||
),
|
||||
_ => format!("Gave {item_count} {item_name} to {} players", targets.len()),
|
||||
}))
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_command_tree<'a>() -> CommandTree<'a> {
|
||||
CommandTree::new(NAMES, DESCRIPTION).with_child(
|
||||
require(&|sender| sender.permission_lvl() >= 2).with_child(
|
||||
argument_default_name(&PlayersArgumentConsumer).with_child(
|
||||
argument(ARG_ITEM, &ItemArgumentConsumer)
|
||||
.execute(&GiveExecutor)
|
||||
.with_child(argument_default_name(&ITEM_COUNT_CONSUMER).execute(&GiveExecutor)),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -2,7 +2,7 @@ use async_trait::async_trait;
|
||||
use pumpkin_core::text::color::NamedColor;
|
||||
use pumpkin_core::text::TextComponent;
|
||||
|
||||
use crate::command::args::arg_player::PlayersArgumentConsumer;
|
||||
use crate::command::args::arg_players::PlayersArgumentConsumer;
|
||||
use crate::command::args::{Arg, ConsumedArgs};
|
||||
use crate::command::tree::CommandTree;
|
||||
use crate::command::tree_builder::argument;
|
||||
|
||||
@@ -11,7 +11,8 @@ use crate::{
|
||||
command::{
|
||||
args::{
|
||||
arg_bounded_num::BoundedNumArgumentConsumer,
|
||||
arg_position_2d::Position2DArgumentConsumer, ConsumedArgs, FindArgDefaultName,
|
||||
arg_position_2d::Position2DArgumentConsumer, ConsumedArgs, DefaultNameArgConsumer,
|
||||
FindArgDefaultName,
|
||||
},
|
||||
tree::CommandTree,
|
||||
tree_builder::{argument_default_name, literal},
|
||||
@@ -66,7 +67,18 @@ impl CommandExecutor for WorldborderSetExecutor {
|
||||
.expect("There should always be atleast one world");
|
||||
let mut border = world.worldborder.lock().await;
|
||||
|
||||
let distance = DISTANCE_CONSUMER.find_arg_default_name(args)?;
|
||||
let Ok(distance) = DISTANCE_CONSUMER.find_arg_default_name(args)? else {
|
||||
sender
|
||||
.send_message(
|
||||
TextComponent::text_string(format!(
|
||||
"{} is out of bounds.",
|
||||
DISTANCE_CONSUMER.default_name()
|
||||
))
|
||||
.color(Color::Named(NamedColor::Red)),
|
||||
)
|
||||
.await;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if (distance - border.new_diameter).abs() < f64::EPSILON {
|
||||
sender
|
||||
@@ -104,8 +116,30 @@ impl CommandExecutor for WorldborderSetTimeExecutor {
|
||||
.expect("There should always be atleast one world");
|
||||
let mut border = world.worldborder.lock().await;
|
||||
|
||||
let distance = DISTANCE_CONSUMER.find_arg_default_name(args)?;
|
||||
let time = TIME_CONSUMER.find_arg_default_name(args)?;
|
||||
let Ok(distance) = DISTANCE_CONSUMER.find_arg_default_name(args)? else {
|
||||
sender
|
||||
.send_message(
|
||||
TextComponent::text_string(format!(
|
||||
"{} is out of bounds.",
|
||||
DISTANCE_CONSUMER.default_name()
|
||||
))
|
||||
.color(Color::Named(NamedColor::Red)),
|
||||
)
|
||||
.await;
|
||||
return Ok(());
|
||||
};
|
||||
let Ok(time) = TIME_CONSUMER.find_arg_default_name(args)? else {
|
||||
sender
|
||||
.send_message(
|
||||
TextComponent::text_string(format!(
|
||||
"{} is out of bounds.",
|
||||
TIME_CONSUMER.default_name()
|
||||
))
|
||||
.color(Color::Named(NamedColor::Red)),
|
||||
)
|
||||
.await;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
match distance.total_cmp(&border.new_diameter) {
|
||||
std::cmp::Ordering::Equal => {
|
||||
@@ -154,7 +188,18 @@ impl CommandExecutor for WorldborderAddExecutor {
|
||||
.expect("There should always be atleast one world");
|
||||
let mut border = world.worldborder.lock().await;
|
||||
|
||||
let distance = DISTANCE_CONSUMER.find_arg_default_name(args)?;
|
||||
let Ok(distance) = DISTANCE_CONSUMER.find_arg_default_name(args)? else {
|
||||
sender
|
||||
.send_message(
|
||||
TextComponent::text_string(format!(
|
||||
"{} is out of bounds.",
|
||||
DISTANCE_CONSUMER.default_name()
|
||||
))
|
||||
.color(Color::Named(NamedColor::Red)),
|
||||
)
|
||||
.await;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if distance == 0.0 {
|
||||
sender
|
||||
@@ -194,8 +239,30 @@ impl CommandExecutor for WorldborderAddTimeExecutor {
|
||||
.expect("There should always be atleast one world");
|
||||
let mut border = world.worldborder.lock().await;
|
||||
|
||||
let distance = DISTANCE_CONSUMER.find_arg_default_name(args)?;
|
||||
let time = TIME_CONSUMER.find_arg_default_name(args)?;
|
||||
let Ok(distance) = DISTANCE_CONSUMER.find_arg_default_name(args)? else {
|
||||
sender
|
||||
.send_message(
|
||||
TextComponent::text_string(format!(
|
||||
"{} is out of bounds.",
|
||||
DISTANCE_CONSUMER.default_name()
|
||||
))
|
||||
.color(Color::Named(NamedColor::Red)),
|
||||
)
|
||||
.await;
|
||||
return Ok(());
|
||||
};
|
||||
let Ok(time) = TIME_CONSUMER.find_arg_default_name(args)? else {
|
||||
sender
|
||||
.send_message(
|
||||
TextComponent::text_string(format!(
|
||||
"{} is out of bounds.",
|
||||
TIME_CONSUMER.default_name()
|
||||
))
|
||||
.color(Color::Named(NamedColor::Red)),
|
||||
)
|
||||
.await;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let distance = distance + border.new_diameter;
|
||||
|
||||
@@ -274,7 +341,18 @@ impl CommandExecutor for WorldborderDamageAmountExecutor {
|
||||
.expect("There should always be atleast one world");
|
||||
let mut border = world.worldborder.lock().await;
|
||||
|
||||
let damage_per_block = DAMAGE_PER_BLOCK_CONSUMER.find_arg_default_name(args)?;
|
||||
let Ok(damage_per_block) = DAMAGE_PER_BLOCK_CONSUMER.find_arg_default_name(args)? else {
|
||||
sender
|
||||
.send_message(
|
||||
TextComponent::text_string(format!(
|
||||
"{} is out of bounds.",
|
||||
DAMAGE_PER_BLOCK_CONSUMER.default_name()
|
||||
))
|
||||
.color(Color::Named(NamedColor::Red)),
|
||||
)
|
||||
.await;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if (damage_per_block - border.damage_per_block).abs() < f32::EPSILON {
|
||||
sender
|
||||
@@ -314,7 +392,18 @@ impl CommandExecutor for WorldborderDamageBufferExecutor {
|
||||
.expect("There should always be atleast one world");
|
||||
let mut border = world.worldborder.lock().await;
|
||||
|
||||
let buffer = DAMAGE_BUFFER_CONSUMER.find_arg_default_name(args)?;
|
||||
let Ok(buffer) = DAMAGE_BUFFER_CONSUMER.find_arg_default_name(args)? else {
|
||||
sender
|
||||
.send_message(
|
||||
TextComponent::text_string(format!(
|
||||
"{} is out of bounds.",
|
||||
DAMAGE_BUFFER_CONSUMER.default_name()
|
||||
))
|
||||
.color(Color::Named(NamedColor::Red)),
|
||||
)
|
||||
.await;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if (buffer - border.buffer).abs() < f32::EPSILON {
|
||||
sender
|
||||
@@ -354,7 +443,18 @@ impl CommandExecutor for WorldborderWarningDistanceExecutor {
|
||||
.expect("There should always be atleast one world");
|
||||
let mut border = world.worldborder.lock().await;
|
||||
|
||||
let distance = WARNING_DISTANCE_CONSUMER.find_arg_default_name(args)?;
|
||||
let Ok(distance) = WARNING_DISTANCE_CONSUMER.find_arg_default_name(args)? else {
|
||||
sender
|
||||
.send_message(
|
||||
TextComponent::text_string(format!(
|
||||
"{} is out of bounds.",
|
||||
WARNING_DISTANCE_CONSUMER.default_name()
|
||||
))
|
||||
.color(Color::Named(NamedColor::Red)),
|
||||
)
|
||||
.await;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if distance == border.warning_blocks {
|
||||
sender
|
||||
@@ -394,7 +494,18 @@ impl CommandExecutor for WorldborderWarningTimeExecutor {
|
||||
.expect("There should always be atleast one world");
|
||||
let mut border = world.worldborder.lock().await;
|
||||
|
||||
let time = TIME_CONSUMER.find_arg_default_name(args)?;
|
||||
let Ok(time) = TIME_CONSUMER.find_arg_default_name(args)? else {
|
||||
sender
|
||||
.send_message(
|
||||
TextComponent::text_string(format!(
|
||||
"{} is out of bounds.",
|
||||
TIME_CONSUMER.default_name()
|
||||
))
|
||||
.color(Color::Named(NamedColor::Red)),
|
||||
)
|
||||
.await;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if time == border.warning_time {
|
||||
sender
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod cmd_echest;
|
||||
pub mod cmd_gamemode;
|
||||
pub mod cmd_give;
|
||||
pub mod cmd_help;
|
||||
pub mod cmd_kick;
|
||||
pub mod cmd_kill;
|
||||
|
||||
@@ -3,8 +3,8 @@ use std::sync::Arc;
|
||||
use args::ConsumedArgs;
|
||||
use async_trait::async_trait;
|
||||
use commands::{
|
||||
cmd_echest, cmd_gamemode, cmd_help, cmd_kick, cmd_kill, cmd_list, cmd_pumpkin, cmd_say,
|
||||
cmd_stop, cmd_teleport, cmd_worldborder,
|
||||
cmd_echest, cmd_gamemode, cmd_give, cmd_help, cmd_kick, cmd_kill, cmd_list, cmd_pumpkin,
|
||||
cmd_say, cmd_stop, cmd_teleport, cmd_worldborder,
|
||||
};
|
||||
use dispatcher::InvalidTreeError;
|
||||
use pumpkin_core::math::vector3::Vector3;
|
||||
@@ -82,6 +82,7 @@ pub fn default_dispatcher<'a>() -> Arc<CommandDispatcher<'a>> {
|
||||
dispatcher.register(cmd_kick::init_command_tree());
|
||||
dispatcher.register(cmd_worldborder::init_command_tree());
|
||||
dispatcher.register(cmd_teleport::init_command_tree());
|
||||
dispatcher.register(cmd_give::init_command_tree());
|
||||
dispatcher.register(cmd_list::init_command_tree());
|
||||
|
||||
Arc::new(dispatcher)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::{
|
||||
cmp::min,
|
||||
collections::{HashMap, VecDeque},
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicI32, AtomicI64, AtomicU32, AtomicU8},
|
||||
@@ -17,14 +18,14 @@ use pumpkin_core::{
|
||||
GameMode,
|
||||
};
|
||||
use pumpkin_entity::{entity_type::EntityType, EntityId};
|
||||
use pumpkin_inventory::player::PlayerInventory;
|
||||
use pumpkin_inventory::{player::PlayerInventory, InventoryError};
|
||||
use pumpkin_macros::sound;
|
||||
use pumpkin_protocol::{
|
||||
bytebuf::packet_id::Packet,
|
||||
client::play::{
|
||||
CCombatDeath, CGameEvent, CHurtAnimation, CKeepAlive, CPlayDisconnect, CPlayerAbilities,
|
||||
CPlayerInfoUpdate, CRespawn, CSetHealth, CSpawnEntity, CSyncPlayerPosition,
|
||||
CSystemChatMessage, GameEvent, PlayerAction,
|
||||
CPlayerInfoUpdate, CRespawn, CSetContainerSlot, CSetHealth, CSpawnEntity,
|
||||
CSyncPlayerPosition, CSystemChatMessage, GameEvent, PlayerAction,
|
||||
},
|
||||
server::play::{
|
||||
SChatCommand, SChatMessage, SClientCommand, SClientInformationPlay, SClientTickEnd,
|
||||
@@ -38,7 +39,10 @@ use tokio::sync::{Mutex, Notify};
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
use pumpkin_protocol::server::play::SKeepAlive;
|
||||
use pumpkin_world::{cylindrical_chunk_iterator::Cylindrical, item::ItemStack};
|
||||
use pumpkin_world::{
|
||||
cylindrical_chunk_iterator::Cylindrical,
|
||||
item::{item_registry::Item, ItemStack},
|
||||
};
|
||||
|
||||
use super::Entity;
|
||||
use crate::{
|
||||
@@ -841,6 +845,90 @@ impl Player {
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Syncs inventory slot with client.
|
||||
pub async fn send_inventory_slot_update(
|
||||
&self,
|
||||
inventory: &mut PlayerInventory,
|
||||
slot_index: usize,
|
||||
) -> Result<(), InventoryError> {
|
||||
let slot = (&*inventory.get_slot(slot_index)?).into();
|
||||
|
||||
// Returns previous value
|
||||
let i = inventory
|
||||
.state_id
|
||||
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
let packet = CSetContainerSlot::new(
|
||||
PlayerInventory::CONTAINER_ID,
|
||||
(i + 1) as i32,
|
||||
slot_index,
|
||||
&slot,
|
||||
);
|
||||
self.client.send_packet(&packet).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add items to inventory if there's space, else drop them to the ground.
|
||||
///
|
||||
/// This method automatically syncs changes with the client.
|
||||
pub async fn give_items(&self, item: &Item, count: u32) {
|
||||
let mut remaining_items: u32 = count;
|
||||
let max_stack_size = item.max_stack as u8;
|
||||
|
||||
let mut inventory = self.inventory.lock().await;
|
||||
|
||||
// try filling existing ItemStacks first
|
||||
for slot_index in (36..45).chain(9..36) {
|
||||
let Some(stack) = inventory.get_slot(slot_index).unwrap() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if stack.item_id != item.id {
|
||||
continue;
|
||||
}
|
||||
|
||||
if stack.item_count < max_stack_size {
|
||||
let space = max_stack_size - stack.item_count;
|
||||
let deposit_amount = min(remaining_items, space.into());
|
||||
|
||||
stack.item_count += deposit_amount as u8;
|
||||
remaining_items -= deposit_amount;
|
||||
|
||||
self.send_inventory_slot_update(&mut inventory, slot_index)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if remaining_items == 0 {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// then try filling empty slots
|
||||
for slot_index in (36..45).chain(9..36) {
|
||||
let slot = inventory.get_slot(slot_index).unwrap();
|
||||
|
||||
if slot.is_some() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let deposit_amount = min(remaining_items, max_stack_size.into());
|
||||
|
||||
*slot = Some(ItemStack::new(deposit_amount as u8, item.id));
|
||||
remaining_items -= deposit_amount;
|
||||
|
||||
self.send_inventory_slot_update(&mut inventory, slot_index)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if remaining_items == 0 {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
log::warn!("{remaining_items} items ({}) were discarded because dropping them to the ground is not implemented", item.name);
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a player's abilities and special powers.
|
||||
|
||||
Reference in New Issue
Block a user