From c440ecdb1d63e8b5050c2b45a97bd3470851894e Mon Sep 17 00:00:00 2001 From: PauMAVA Date: Wed, 24 Feb 2021 19:47:05 +0100 Subject: [PATCH 01/13] Non-function base struct and functions. --- crates/items/src/item_stack.rs | 171 +++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 crates/items/src/item_stack.rs diff --git a/crates/items/src/item_stack.rs b/crates/items/src/item_stack.rs new file mode 100644 index 0000000..ad2cfbf --- /dev/null +++ b/crates/items/src/item_stack.rs @@ -0,0 +1,171 @@ +#![forbid(unsafe_code, warnings)] + +use crate::Enchantment; + +// TODO Remove when all codegen has been migrated and generated. DUMMY STRUCT +#[derive(Copy, Clone)] +pub struct Item {} +impl Item { + pub fn durability(&self) -> Option { None } + pub fn stack_size(&self) -> u32 { 64 } +} + +/// Represents an item stack. +/// +/// An item stack includes an item type, an amount and a bunch o properties (enchantments, etc.) +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ItemStack { + + // TODO Migrate all the codegen boilerplate. + /// The item type of this `ItemStack`. + pub item: Item, + + /// The number of items in the `ItemStack`. + pub count: u32, + + /// Damage to the item, if it's damageable. + pub damage: Option, + + /// Repair cost of the item, if it's repairable. + pub repair_cost: Option, + + /// Enchantments applied to the item. + pub enchantments: Vec, + + // TODO Add other properties like title and lore. +} + +impl ItemStack { + + /// Creates a new `ItemStack` + pub fn new(item: Item, count: u32) -> Self { + Self { + item, + count, + damage: item.durability().map(|_| 0) + } + } + + /// Returns whether the given item stack has + /// the same type as (but not necessarily the same + /// amount as) `self`. + pub fn has_same_type(&self, other: &Self) -> bool { + self.item == other.item + } + + /// Returns whether the given item stack has the same damage + /// as `self`. + pub fn has_same_damage(&self, other: &Self) -> bool { + self.damage == other.damage + } + + /// Returns whether the given `ItemStack` has + /// the same count as (but not necessarily the same + /// type as) `self`. + pub fn has_same_count(&self, other: &Self) -> bool { + self.count == self.count + } + + /// Returns whether the given `ItemStack` has the same + /// type and count as `self`. + pub fn has_same_type_and_count(&self, other: &Self) -> bool { + self.item == other.item && self.count == other.count + } + + /// Returns whether the given `ItemStack` has + /// the same type and damage as `self`. + pub fn has_same_type_and_damage(&self, other: &Self) -> bool { + self.item == other.item && self.damage == other.damage + } + + /// Returns the item type for this `ItemStack`. + pub fn item(&self) -> Item { + self.item + } + + /// Returns the number of items in this `ItemStack`. + pub fn count(&self) -> u32 { + self.count + } + + /// Adds more items to this `ItemStack`. Returns the new count. + pub fn add(&mut self, count: u32) -> u32 { + self.count += count; + self.cout + } + + /// Removes some items from this `ItemStack`. + pub fn remove(&mut self, count: u32) -> Result { + self.count = match self.count.checked_sub(count) { + Some(count) => count, + None => return Err(()) + }; + Ok(self.count) + } + + /// Sets the item type for this `ItemStack`. + pub fn set_item(&mut self, item: Item) { + self.item = item + } + + /// Sets the count for this `ItemStack`. + pub fn set_count(&mut self, count: u32) { + self.count = count + } + + /// Splits this `ItemStack` in half, returning the + /// removed half. If the amount is odd, `self` + /// will be left with the least items. + pub fn take_half(&mut self) -> ItemStack { + self.take((self.count as f64 / 2 as f64).ceil() as u32).unwrap() + } + + /// Splits this `ItemStack` by removing the + /// specified amount. + pub fn take(&mut self, amount: u32) -> Result { + let count_left = self.count - amount; + if count_left < 0 { return Err(()) } + let other_half = ItemStack { + count: self.count - count_left, + ..self.clone() + }; + self.count = count_left; + Ok(other_half) + } + + /// Merges another `ItemStack` with this one. + pub fn merge_with(&mut self, other: &mut Self) -> bool { + if !self.has_same_type_and_damage(other) { + return false + } + let new_count = (self.count + other.count).min(self.item.stack_size()); + let amount_added = new_count - self.count; + self.count = new_count; + other.count -= amount_added; + true + } + + /// Transfers up to `n` items to `other`. + pub fn transfer_to(&mut self, n: u32, other: &mut Self) { + let max_transfer = other.item.stack_size().saturating_sub(other.count); + let transfer = max_transfer.min(self.count).min(n); + self.count -= transfer; + other.count += transfer; + } + + /// Damages the item by the specified amount. + /// If this function returns `true`, then the item is broken. + pub fn damage(&mut self, amount: u32) -> bool { + match &mut self.damage { + Some(damage) => { + *damage += amount; + if let Some(durability) = self.item.durability() { + *damage >= durability + } else { + false + } + } + None => false + } + } +} \ No newline at end of file From 7f8370416e4dfa809c7a0721b51e2ec984692e53 Mon Sep 17 00:00:00 2001 From: PauMAVA Date: Wed, 24 Feb 2021 19:52:37 +0100 Subject: [PATCH 02/13] Remove dummy struct and move enchantments.rs into items crate. --- crates/core/src/lib.rs | 2 -- crates/{core => items}/src/enchantment.rs | 0 crates/items/src/item_stack.rs | 10 +--------- crates/items/src/lib.rs | 4 ++++ 4 files changed, 5 insertions(+), 11 deletions(-) rename crates/{core => items}/src/enchantment.rs (100%) diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 1b68de7..c1ea296 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -2,13 +2,11 @@ pub mod block; mod consts; -mod enchantment; mod gamemode; mod gamerules; mod positions; pub use consts::*; -pub use enchantment::{Enchantment, EnchantmentKind}; pub use gamemode::Gamemode; pub use gamerules::GameRules; pub use positions::{ diff --git a/crates/core/src/enchantment.rs b/crates/items/src/enchantment.rs similarity index 100% rename from crates/core/src/enchantment.rs rename to crates/items/src/enchantment.rs diff --git a/crates/items/src/item_stack.rs b/crates/items/src/item_stack.rs index ad2cfbf..ce46bb3 100644 --- a/crates/items/src/item_stack.rs +++ b/crates/items/src/item_stack.rs @@ -1,14 +1,6 @@ #![forbid(unsafe_code, warnings)] -use crate::Enchantment; - -// TODO Remove when all codegen has been migrated and generated. DUMMY STRUCT -#[derive(Copy, Clone)] -pub struct Item {} -impl Item { - pub fn durability(&self) -> Option { None } - pub fn stack_size(&self) -> u32 { 64 } -} +use crate::{Item}; /// Represents an item stack. /// diff --git a/crates/items/src/lib.rs b/crates/items/src/lib.rs index 95e0a69..0086c89 100644 --- a/crates/items/src/lib.rs +++ b/crates/items/src/lib.rs @@ -1,3 +1,7 @@ mod item; +mod item_stack; +mod enchantment; pub use item::*; +pub use item_stack::ItemStack; +pub use enchantment::{Enchantment, EnchantmentKind}; From c677b9490bd44fd56f921378ce0499c736b33733 Mon Sep 17 00:00:00 2001 From: PauMAVA Date: Wed, 24 Feb 2021 20:39:20 +0100 Subject: [PATCH 03/13] Add ItemStackMeta, handle ItemStack serialization and deserialization. --- crates/items/Cargo.toml | 3 +- crates/items/src/enchantment.rs | 4 +- crates/items/src/item_stack.rs | 391 +++++++++++++++++++------------- crates/items/src/lib.rs | 4 +- 4 files changed, 234 insertions(+), 168 deletions(-) diff --git a/crates/items/Cargo.toml b/crates/items/Cargo.toml index 63f9640..ea9e1fd 100644 --- a/crates/items/Cargo.toml +++ b/crates/items/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "libcraft-items" version = "0.1.0" -authors = ["Kalle Kankaanpää"] +authors = ["Kalle Kankaanpää", "Pau Machetti "] edition = "2018" [dependencies] +serde = { version = "1", features = ["derive"] } diff --git a/crates/items/src/enchantment.rs b/crates/items/src/enchantment.rs index a0e6c44..5796f3c 100644 --- a/crates/items/src/enchantment.rs +++ b/crates/items/src/enchantment.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; /// An enchantment attached to an item. -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Enchantment { #[serde(rename = "id")] kind: EnchantmentKind, @@ -54,7 +54,7 @@ impl Enchantment { } /// Kind of an enchantment. -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum EnchantmentKind { AquaAffinity, diff --git a/crates/items/src/item_stack.rs b/crates/items/src/item_stack.rs index ce46bb3..794e111 100644 --- a/crates/items/src/item_stack.rs +++ b/crates/items/src/item_stack.rs @@ -1,163 +1,228 @@ -#![forbid(unsafe_code, warnings)] - -use crate::{Item}; - -/// Represents an item stack. -/// -/// An item stack includes an item type, an amount and a bunch o properties (enchantments, etc.) -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ItemStack { - - // TODO Migrate all the codegen boilerplate. - /// The item type of this `ItemStack`. - pub item: Item, - - /// The number of items in the `ItemStack`. - pub count: u32, - - /// Damage to the item, if it's damageable. - pub damage: Option, - - /// Repair cost of the item, if it's repairable. - pub repair_cost: Option, - - /// Enchantments applied to the item. - pub enchantments: Vec, - - // TODO Add other properties like title and lore. -} - -impl ItemStack { - - /// Creates a new `ItemStack` - pub fn new(item: Item, count: u32) -> Self { - Self { - item, - count, - damage: item.durability().map(|_| 0) - } - } - - /// Returns whether the given item stack has - /// the same type as (but not necessarily the same - /// amount as) `self`. - pub fn has_same_type(&self, other: &Self) -> bool { - self.item == other.item - } - - /// Returns whether the given item stack has the same damage - /// as `self`. - pub fn has_same_damage(&self, other: &Self) -> bool { - self.damage == other.damage - } - - /// Returns whether the given `ItemStack` has - /// the same count as (but not necessarily the same - /// type as) `self`. - pub fn has_same_count(&self, other: &Self) -> bool { - self.count == self.count - } - - /// Returns whether the given `ItemStack` has the same - /// type and count as `self`. - pub fn has_same_type_and_count(&self, other: &Self) -> bool { - self.item == other.item && self.count == other.count - } - - /// Returns whether the given `ItemStack` has - /// the same type and damage as `self`. - pub fn has_same_type_and_damage(&self, other: &Self) -> bool { - self.item == other.item && self.damage == other.damage - } - - /// Returns the item type for this `ItemStack`. - pub fn item(&self) -> Item { - self.item - } - - /// Returns the number of items in this `ItemStack`. - pub fn count(&self) -> u32 { - self.count - } - - /// Adds more items to this `ItemStack`. Returns the new count. - pub fn add(&mut self, count: u32) -> u32 { - self.count += count; - self.cout - } - - /// Removes some items from this `ItemStack`. - pub fn remove(&mut self, count: u32) -> Result { - self.count = match self.count.checked_sub(count) { - Some(count) => count, - None => return Err(()) - }; - Ok(self.count) - } - - /// Sets the item type for this `ItemStack`. - pub fn set_item(&mut self, item: Item) { - self.item = item - } - - /// Sets the count for this `ItemStack`. - pub fn set_count(&mut self, count: u32) { - self.count = count - } - - /// Splits this `ItemStack` in half, returning the - /// removed half. If the amount is odd, `self` - /// will be left with the least items. - pub fn take_half(&mut self) -> ItemStack { - self.take((self.count as f64 / 2 as f64).ceil() as u32).unwrap() - } - - /// Splits this `ItemStack` by removing the - /// specified amount. - pub fn take(&mut self, amount: u32) -> Result { - let count_left = self.count - amount; - if count_left < 0 { return Err(()) } - let other_half = ItemStack { - count: self.count - count_left, - ..self.clone() - }; - self.count = count_left; - Ok(other_half) - } - - /// Merges another `ItemStack` with this one. - pub fn merge_with(&mut self, other: &mut Self) -> bool { - if !self.has_same_type_and_damage(other) { - return false - } - let new_count = (self.count + other.count).min(self.item.stack_size()); - let amount_added = new_count - self.count; - self.count = new_count; - other.count -= amount_added; - true - } - - /// Transfers up to `n` items to `other`. - pub fn transfer_to(&mut self, n: u32, other: &mut Self) { - let max_transfer = other.item.stack_size().saturating_sub(other.count); - let transfer = max_transfer.min(self.count).min(n); - self.count -= transfer; - other.count += transfer; - } - - /// Damages the item by the specified amount. - /// If this function returns `true`, then the item is broken. - pub fn damage(&mut self, amount: u32) -> bool { - match &mut self.damage { - Some(damage) => { - *damage += amount; - if let Some(durability) = self.item.durability() { - *damage >= durability - } else { - false - } - } - None => false - } - } -} \ No newline at end of file +#![forbid(unsafe_code, warnings)] + +use crate::{Enchantment, Item}; +use serde::{Serialize, Deserialize, Serializer, Deserializer}; +use serde::de::{Visitor, Error}; +use core::fmt; + +/// Represents an item stack. +/// +/// An item stack includes an item type, an amount and a bunch of properties (enchantments, etc.) +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct ItemStack { + /// The item type of this `ItemStack`. + #[serde(rename = "id")] + pub item: Item, + + /// The number of items in the `ItemStack`. + pub count: u32, + + /// The `ItemStack` metadata, containing data such as damage, + /// repair cost, enchantments... + #[serde(rename = "tag")] + pub meta: ItemStackMeta, +} + +/// Represents the metadata of an `ItemStack`. Contains: +/// * Item title +/// * Item lore (Optional) +/// * Item damage (Optional) +/// * Item repair cost (Optional) +/// * Item enchantments +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct ItemStackMeta { + /// The displayed title (name) of the associated `ItemStack`. + pub title: String, + + /// The displayed lore of the associated `ItemStack`. + pub lore: String, + + /// The damage taken by the `ItemStack`. + pub damage: Option, + + /// The cost of repairing the `ItemStack`. + pub repair_cost: Option, + + /// The enchantments applied to this `ItemStack`. + pub enchantments: Vec, +} + +impl ItemStack { + /// Creates a new `ItemStack` with the default name (title) + /// no lore, no damage, no repair cost and no enchantments. + pub fn new(item: Item, count: u32) -> Self { + Self { + item, + count, + meta: ItemStackMeta { + title: String::from(item.name()), + lore: "".to_string(), + damage: None, + repair_cost: None, + enchantments: vec![], + }, + } + } + + /// Returns whether the given item stack has + /// the same type as (but not necessarily the same + /// amount as) `self`. + pub fn has_same_type(&self, other: &Self) -> bool { + self.item == other.item + } + + /// Returns whether the given item stack has the same damage + /// as `self`. + pub fn has_same_damage(&self, other: &Self) -> bool { + self.meta.damage == other.meta.damage + } + + /// Returns whether the given `ItemStack` has + /// the same count as (but not necessarily the same + /// type as) `self`. + pub fn has_same_count(&self, other: &Self) -> bool { + self.count == other.count + } + + /// Returns whether the given `ItemStack` has the same + /// type and count as `self`. + pub fn has_same_type_and_count(&self, other: &Self) -> bool { + self.item == other.item && self.count == other.count + } + + /// Returns whether the given `ItemStack` has + /// the same type and damage as `self`. + pub fn has_same_type_and_damage(&self, other: &Self) -> bool { + self.item == other.item && self.meta.damage == other.meta.damage + } + + /// Returns the item type for this `ItemStack`. + pub fn item(&self) -> Item { + self.item + } + + /// Returns the number of items in this `ItemStack`. + pub fn count(&self) -> u32 { + self.count + } + + /// Adds more items to this `ItemStack`. Returns the new count. + pub fn add(&mut self, count: u32) -> u32 { + self.count += count; + self.count + } + + /// Removes some items from this `ItemStack`. + pub fn remove(&mut self, count: u32) -> Result { + self.count = match self.count.checked_sub(count) { + Some(count) => count, + None => return Err(()), + }; + Ok(self.count) + } + + /// Sets the item type for this `ItemStack`. + pub fn set_item(&mut self, item: Item) { + self.item = item + } + + /// Sets the count for this `ItemStack`. + pub fn set_count(&mut self, count: u32) { + self.count = count + } + + /// Splits this `ItemStack` in half, returning the + /// removed half. If the amount is odd, `self` + /// will be left with the least items. Returns the taken + /// half. + pub fn take_half(&mut self) -> ItemStack { + self.take((self.count as f64 / 2 as f64).ceil() as u32) + .unwrap() + } + + /// Splits this `ItemStack` by removing the + /// specified amount. Returns the taken part. + pub fn take(&mut self, amount: u32) -> Result { + let count_left: i32 = self.count as i32 - amount as i32; + if count_left < 0 { + return Err(()) + } + let taken = ItemStack { + count: amount, + ..self.clone() + }; + self.count = count_left as u32; + Ok(taken) + } + + /// Merges another `ItemStack` with this one. + pub fn merge_with(&mut self, other: &mut Self) -> bool { + if !self.has_same_type_and_damage(other) { + return false; + } + let new_count = (self.count + other.count).min(self.item.stack_size()); + let amount_added = new_count - self.count; + self.count = new_count; + other.count -= amount_added; + true + } + + /// Transfers up to `n` items to `other`. + pub fn transfer_to(&mut self, n: u32, other: &mut Self) { + let max_transfer = other.item.stack_size().saturating_sub(other.count); + let transfer = max_transfer.min(self.count).min(n); + self.count -= transfer; + other.count += transfer; + } + + /// Damages the item by the specified amount. + /// If this function returns `true`, then the item is broken. + pub fn damage(&mut self, amount: u32) -> bool { + match &mut self.meta.damage { + Some(damage) => { + *damage += amount; + if let Some(durability) = self.item.durability() { + *damage >= durability + } else { + false + } + } + None => false, + } + } +} + +impl Serialize for Item { + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where + S: Serializer { + serializer.serialize_str(self.name()) + } +} + +struct ItemVisitor; + +impl<'de> Visitor<'de> for ItemVisitor { + type Value = Item; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "a string") + } + + fn visit_str(self, string: &str) -> Result + where E: Error { + if let Some(item) = Item::from_name(string) { + Ok(item) + } else { + Err(E::custom("Unknown item name.")) + } + } +} + +impl<'de> Deserialize<'de> for Item { + fn deserialize(deserializer: D) -> Result>::Error> where + D: Deserializer<'de> { + deserializer.deserialize_str(ItemVisitor) + } +} diff --git a/crates/items/src/lib.rs b/crates/items/src/lib.rs index 0086c89..1cccbeb 100644 --- a/crates/items/src/lib.rs +++ b/crates/items/src/lib.rs @@ -1,7 +1,7 @@ +mod enchantment; mod item; mod item_stack; -mod enchantment; +pub use enchantment::{Enchantment, EnchantmentKind}; pub use item::*; pub use item_stack::ItemStack; -pub use enchantment::{Enchantment, EnchantmentKind}; From 54294b339cd64a61067f682d78d4ca7e3ed43a8e Mon Sep 17 00:00:00 2001 From: PauMAVA Date: Wed, 24 Feb 2021 21:02:07 +0100 Subject: [PATCH 04/13] Add some stack count limitations. --- crates/items/src/item_stack.rs | 37 +++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/crates/items/src/item_stack.rs b/crates/items/src/item_stack.rs index 794e111..fde8e16 100644 --- a/crates/items/src/item_stack.rs +++ b/crates/items/src/item_stack.rs @@ -1,9 +1,9 @@ #![forbid(unsafe_code, warnings)] use crate::{Enchantment, Item}; -use serde::{Serialize, Deserialize, Serializer, Deserializer}; -use serde::de::{Visitor, Error}; use core::fmt; +use serde::de::{Error, Visitor}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// Represents an item stack. /// @@ -109,17 +109,20 @@ impl ItemStack { } /// Adds more items to this `ItemStack`. Returns the new count. - pub fn add(&mut self, count: u32) -> u32 { + pub fn add(&mut self, count: u32) -> Result { + if self.count + count > self.item.stack_size() { + return Err(()); + } self.count += count; - self.count + Ok(self.count) } /// Removes some items from this `ItemStack`. pub fn remove(&mut self, count: u32) -> Result { - self.count = match self.count.checked_sub(count) { - Some(count) => count, - None => return Err(()), - }; + if self.count < count { + return Err(()); + } + self.count -= count; Ok(self.count) } @@ -147,7 +150,7 @@ impl ItemStack { pub fn take(&mut self, amount: u32) -> Result { let count_left: i32 = self.count as i32 - amount as i32; if count_left < 0 { - return Err(()) + return Err(()); } let taken = ItemStack { count: amount, @@ -195,8 +198,10 @@ impl ItemStack { } impl Serialize for Item { - fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> where - S: Serializer { + fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> + where + S: Serializer, + { serializer.serialize_str(self.name()) } } @@ -211,7 +216,9 @@ impl<'de> Visitor<'de> for ItemVisitor { } fn visit_str(self, string: &str) -> Result - where E: Error { + where + E: Error, + { if let Some(item) = Item::from_name(string) { Ok(item) } else { @@ -221,8 +228,10 @@ impl<'de> Visitor<'de> for ItemVisitor { } impl<'de> Deserialize<'de> for Item { - fn deserialize(deserializer: D) -> Result>::Error> where - D: Deserializer<'de> { + fn deserialize(deserializer: D) -> Result>::Error> + where + D: Deserializer<'de>, + { deserializer.deserialize_str(ItemVisitor) } } From a8025d200433429aee83929709c1ed4936411347 Mon Sep 17 00:00:00 2001 From: PauMAVA Date: Wed, 24 Feb 2021 21:12:39 +0100 Subject: [PATCH 05/13] Restricted stack sizes on set_count and set_item. Add unchecked_add and unchecked_set_count. --- crates/items/src/item_stack.rs | 40 +++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/crates/items/src/item_stack.rs b/crates/items/src/item_stack.rs index fde8e16..35115fa 100644 --- a/crates/items/src/item_stack.rs +++ b/crates/items/src/item_stack.rs @@ -117,6 +117,14 @@ impl ItemStack { Ok(self.count) } + /// Adds more items to this `ItemStack`. Does not check if the + /// addition will make the count to be greater than the + /// stack size. Returns the new count. + pub fn unchecked_add(&mut self, count: u32) -> u32 { + self.count += count; + self.count + } + /// Removes some items from this `ItemStack`. pub fn remove(&mut self, count: u32) -> Result { if self.count < count { @@ -126,14 +134,34 @@ impl ItemStack { Ok(self.count) } - /// Sets the item type for this `ItemStack`. - pub fn set_item(&mut self, item: Item) { - self.item = item + /// Sets the item type for this `ItemStack`. Returns the new + /// item type or fails if the current item count exceeds the + /// new item type stack size. + pub fn set_item(&mut self, item: Item) -> Result { + if self.count > item.stack_size() { + return Err(()); + } + self.item = item; + Ok(self.item) + } + + /// Sets the count for this `ItemStack`. Returns the updated + /// count or fails if the new count would exceed the stack + /// size for that item type. + pub fn set_count(&mut self, count: u32) -> Result { + if count > self.item.stack_size() { + return Err(()); + } + self.count = count; + Ok(self.count) } - /// Sets the count for this `ItemStack`. - pub fn set_count(&mut self, count: u32) { - self.count = count + /// Sets the count for this `ItemStack`. It will not check if + /// the desired count exceeds the current item type stack size. + /// Returns the updated count. + pub fn unchecked_set_count(&mut self, count: u32) -> u32 { + self.count = count; + self.count } /// Splits this `ItemStack` in half, returning the From 0bb498c6710dfb5c7321ba0c9fb135abf9f4776d Mon Sep 17 00:00:00 2001 From: PauMAVA Date: Wed, 24 Feb 2021 21:15:40 +0100 Subject: [PATCH 06/13] Add unchecked_set_item. --- crates/items/src/item_stack.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/items/src/item_stack.rs b/crates/items/src/item_stack.rs index 35115fa..2ce46a5 100644 --- a/crates/items/src/item_stack.rs +++ b/crates/items/src/item_stack.rs @@ -145,6 +145,14 @@ impl ItemStack { Ok(self.item) } + /// Sets the item type for this `ItemStack`. Does not check if + /// the new item type stack size will be lower than the current + /// item count. Returns the new item type. + pub fn unchecked_set_item(&mut self, item: Item) -> Item { + self.item = item; + self.item + } + /// Sets the count for this `ItemStack`. Returns the updated /// count or fails if the new count would exceed the stack /// size for that item type. From 1e064eab88c388f4908148d35b0c4bdaeb0aedc0 Mon Sep 17 00:00:00 2001 From: PauMAVA Date: Wed, 24 Feb 2021 21:31:57 +0100 Subject: [PATCH 07/13] Make ItemMeta optional. --- crates/items/src/item_stack.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/crates/items/src/item_stack.rs b/crates/items/src/item_stack.rs index 2ce46a5..184e867 100644 --- a/crates/items/src/item_stack.rs +++ b/crates/items/src/item_stack.rs @@ -9,19 +9,19 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; /// /// An item stack includes an item type, an amount and a bunch of properties (enchantments, etc.) #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "PascalCase")] pub struct ItemStack { /// The item type of this `ItemStack`. #[serde(rename = "id")] pub item: Item, /// The number of items in the `ItemStack`. + #[serde(rename = "Count")] pub count: u32, /// The `ItemStack` metadata, containing data such as damage, /// repair cost, enchantments... #[serde(rename = "tag")] - pub meta: ItemStackMeta, + pub meta: Option, } /// Represents the metadata of an `ItemStack`. Contains: @@ -56,13 +56,13 @@ impl ItemStack { Self { item, count, - meta: ItemStackMeta { + meta: Some(ItemStackMeta { title: String::from(item.name()), lore: "".to_string(), damage: None, repair_cost: None, enchantments: vec![], - }, + }), } } @@ -76,7 +76,11 @@ impl ItemStack { /// Returns whether the given item stack has the same damage /// as `self`. pub fn has_same_damage(&self, other: &Self) -> bool { - self.meta.damage == other.meta.damage + if let (Some(self_meta), Some(other_meta)) = (self.meta.clone(), other.meta.clone()) { + self_meta.damage == other_meta.damage + } else { + self.meta.is_none() && other.meta.is_none() + } } /// Returns whether the given `ItemStack` has @@ -95,7 +99,7 @@ impl ItemStack { /// Returns whether the given `ItemStack` has /// the same type and damage as `self`. pub fn has_same_type_and_damage(&self, other: &Self) -> bool { - self.item == other.item && self.meta.damage == other.meta.damage + self.item == other.item && self.has_same_damage(other) } /// Returns the item type for this `ItemStack`. @@ -219,7 +223,10 @@ impl ItemStack { /// Damages the item by the specified amount. /// If this function returns `true`, then the item is broken. pub fn damage(&mut self, amount: u32) -> bool { - match &mut self.meta.damage { + if self.meta.is_none() { + return false; + } + match &mut self.meta.clone().unwrap().damage { Some(damage) => { *damage += amount; if let Some(durability) = self.item.durability() { From d008bfad948b1c7c016d775da04fa71e45bd78f1 Mon Sep 17 00:00:00 2001 From: PauMAVA Date: Wed, 24 Feb 2021 23:10:20 +0100 Subject: [PATCH 08/13] Use TryFrom and Into in serialization and deserialization. Autogenerate required trait impl and derives. --- crates/generators/minecraft-data | 2 +- crates/generators/python/common.py | 27 ++++++++++++++++--- crates/generators/python/item.py | 33 +++++++++++++++++++++-- crates/items/src/item.rs | 26 ++++++++++++++++-- crates/items/src/item_stack.rs | 43 +----------------------------- 5 files changed, 80 insertions(+), 51 deletions(-) diff --git a/crates/generators/minecraft-data b/crates/generators/minecraft-data index 647a848..fdf518c 160000 --- a/crates/generators/minecraft-data +++ b/crates/generators/minecraft-data @@ -1 +1 @@ -Subproject commit 647a8481c18040fb0e4b11e1b12b239150c5cadc +Subproject commit fdf518c4ff9dfddcb762f6195d18c94cb381a3ed diff --git a/crates/generators/python/common.py b/crates/generators/python/common.py index d086028..9994311 100644 --- a/crates/generators/python/common.py +++ b/crates/generators/python/common.py @@ -5,6 +5,7 @@ from re import split from pathlib import Path +from typing import List LIBCRAFT_ROOT = Path(__file__).parents[3] PRISMARINEJS_BASE_PATH = Path(__file__).parents[1] / "minecraft-data" / "data" / "pc" @@ -141,16 +142,34 @@ def generate_enum_property( return result -def generate_enum(name: str, variants: list[str]) -> str: +def generate_enum(name: str, variants: List[str]) -> str: """Generates an enum definition with the provided variants.""" - body = ','.join(variants) + ',' + return generate_enum_with_derives(name, variants, []) + + +def generate_enum_with_derives(name: str, variants: List[str], derives: List[str]) -> str: + return generate_enum_with_derives_and_prelude(name, variants, derives, "") + - return f""" - #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +def generate_enum_with_prelude(name: str, variants: List[str], prelude: str) -> str: + return generate_enum_with_derives_and_prelude(name, variants, [], prelude) + + +def generate_enum_with_derives_and_prelude(name: str, variants: List[str], derives: List[str], prelude: str) -> str: + """Generates an enum definition with the provided variants and extra derives.""" + body = ','.join(variants) + ',' + extra_derives = "" if len(derives) == 0 else ',' + ','.join(derives) + output = f""" + #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord{extra_derives})]""" + if len(prelude) != 0: + output += f""" + {prelude}""" + output += f""" pub enum {name} {{ {body} }} """ + return output def camel_case(string: str) -> str: diff --git a/crates/generators/python/item.py b/crates/generators/python/item.py index 24051fe..a43af60 100644 --- a/crates/generators/python/item.py +++ b/crates/generators/python/item.py @@ -1,4 +1,4 @@ -from common import load_minecraft_json, camel_case, generate_enum, generate_enum_property, output +from common import load_minecraft_json, camel_case, generate_enum_with_derives_and_prelude, generate_enum_property, output items = [] ids = {} @@ -21,11 +21,40 @@ else: durabilities[variant] = f"Some({durability})" -output_data = generate_enum("Item", items) +output_data = "use serde::{Serialize, Deserialize};" + +output_data += generate_enum_with_derives_and_prelude("Item", items, ["Serialize", "Deserialize"], + "#[serde(try_from = \"String\", into = \"String\")]") output_data += generate_enum_property("Item", "id", "u32", ids, True) output_data += generate_enum_property("Item", "name", "&str", names, True, "&'static str") output_data += generate_enum_property("Item", "display_name", "&str", display_names, True, "&'static str") output_data += generate_enum_property("Item", "stack_size", "u32", stack_sizes) output_data += generate_enum_property("Item", "durability", "Option", durabilities) +output_data += f""" + use std::convert::TryFrom; + + impl TryFrom for Item {{ + type Error = String; + + fn try_from(value: String) -> Result {{ + if let Some(item) = Item::from_name(value.as_str()) {{ + Ok(item) + }} else {{ + Err(String::from("Unknown item name.")) + }} + }} + }} +""" + +output_data += f""" + use std::convert::Into; + + impl Into for Item {{ + fn into(self) -> String {{ + String::from(self.name()) + }} + }} +""" + output("crates/items/src/item.rs", output_data) diff --git a/crates/items/src/item.rs b/crates/items/src/item.rs index 10ee6ce..8d3ece4 100644 --- a/crates/items/src/item.rs +++ b/crates/items/src/item.rs @@ -1,6 +1,7 @@ // This file is @generated. Please do not edit. - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +use serde::{Deserialize, Serialize}; +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +#[serde(try_from = "String", into = "String")] pub enum Item { Air, Stone, @@ -8854,3 +8855,24 @@ impl Item { } } } +use std::convert::TryFrom; + +impl TryFrom for Item { + type Error = String; + + fn try_from(value: String) -> Result { + if let Some(item) = Item::from_name(value.as_str()) { + Ok(item) + } else { + Err(String::from("Unknown item name.")) + } + } +} + +use std::convert::Into; + +impl Into for Item { + fn into(self) -> String { + String::from(self.name()) + } +} diff --git a/crates/items/src/item_stack.rs b/crates/items/src/item_stack.rs index 184e867..c95ee03 100644 --- a/crates/items/src/item_stack.rs +++ b/crates/items/src/item_stack.rs @@ -1,9 +1,7 @@ #![forbid(unsafe_code, warnings)] use crate::{Enchantment, Item}; -use core::fmt; -use serde::de::{Error, Visitor}; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserialize, Serialize}; /// Represents an item stack. /// @@ -239,42 +237,3 @@ impl ItemStack { } } } - -impl Serialize for Item { - fn serialize(&self, serializer: S) -> Result<::Ok, ::Error> - where - S: Serializer, - { - serializer.serialize_str(self.name()) - } -} - -struct ItemVisitor; - -impl<'de> Visitor<'de> for ItemVisitor { - type Value = Item; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - write!(formatter, "a string") - } - - fn visit_str(self, string: &str) -> Result - where - E: Error, - { - if let Some(item) = Item::from_name(string) { - Ok(item) - } else { - Err(E::custom("Unknown item name.")) - } - } -} - -impl<'de> Deserialize<'de> for Item { - fn deserialize(deserializer: D) -> Result>::Error> - where - D: Deserializer<'de>, - { - deserializer.deserialize_str(ItemVisitor) - } -} From f6a23042b2952916eb4655eb2047cde6d223635f Mon Sep 17 00:00:00 2001 From: PauMAVA Date: Thu, 25 Feb 2021 21:47:17 +0100 Subject: [PATCH 09/13] Use &'static str instead of String and delete extra generate_enum_* functions. --- crates/generators/python/common.py | 15 +-------------- crates/generators/python/item.py | 18 +++++++++--------- crates/items/src/item.rs | 12 ++++++------ 3 files changed, 16 insertions(+), 29 deletions(-) diff --git a/crates/generators/python/common.py b/crates/generators/python/common.py index 9994311..df32ca0 100644 --- a/crates/generators/python/common.py +++ b/crates/generators/python/common.py @@ -142,20 +142,7 @@ def generate_enum_property( return result -def generate_enum(name: str, variants: List[str]) -> str: - """Generates an enum definition with the provided variants.""" - return generate_enum_with_derives(name, variants, []) - - -def generate_enum_with_derives(name: str, variants: List[str], derives: List[str]) -> str: - return generate_enum_with_derives_and_prelude(name, variants, derives, "") - - -def generate_enum_with_prelude(name: str, variants: List[str], prelude: str) -> str: - return generate_enum_with_derives_and_prelude(name, variants, [], prelude) - - -def generate_enum_with_derives_and_prelude(name: str, variants: List[str], derives: List[str], prelude: str) -> str: +def generate_enum(name: str, variants: List[str], derives: List[str] = [], prelude: str = "") -> str: """Generates an enum definition with the provided variants and extra derives.""" body = ','.join(variants) + ',' extra_derives = "" if len(derives) == 0 else ',' + ','.join(derives) diff --git a/crates/generators/python/item.py b/crates/generators/python/item.py index a43af60..13f5241 100644 --- a/crates/generators/python/item.py +++ b/crates/generators/python/item.py @@ -1,4 +1,4 @@ -from common import load_minecraft_json, camel_case, generate_enum_with_derives_and_prelude, generate_enum_property, output +from common import load_minecraft_json, camel_case, generate_enum, generate_enum_property, output items = [] ids = {} @@ -23,8 +23,8 @@ output_data = "use serde::{Serialize, Deserialize};" -output_data += generate_enum_with_derives_and_prelude("Item", items, ["Serialize", "Deserialize"], - "#[serde(try_from = \"String\", into = \"String\")]") +output_data += generate_enum("Item", items, derives=["Serialize", "Deserialize"], + prelude="#[serde(try_from = \"String\", into = \"&'static str\")]") output_data += generate_enum_property("Item", "id", "u32", ids, True) output_data += generate_enum_property("Item", "name", "&str", names, True, "&'static str") output_data += generate_enum_property("Item", "display_name", "&str", display_names, True, "&'static str") @@ -35,13 +35,13 @@ use std::convert::TryFrom; impl TryFrom for Item {{ - type Error = String; - + type Error = &'static str; + fn try_from(value: String) -> Result {{ if let Some(item) = Item::from_name(value.as_str()) {{ Ok(item) }} else {{ - Err(String::from("Unknown item name.")) + Err("Unknown item name.") }} }} }} @@ -50,9 +50,9 @@ output_data += f""" use std::convert::Into; - impl Into for Item {{ - fn into(self) -> String {{ - String::from(self.name()) + impl Into<&'static str> for Item {{ + fn into(self) -> &'static str {{ + self.name() }} }} """ diff --git a/crates/items/src/item.rs b/crates/items/src/item.rs index 8d3ece4..b892784 100644 --- a/crates/items/src/item.rs +++ b/crates/items/src/item.rs @@ -1,7 +1,7 @@ // This file is @generated. Please do not edit. use serde::{Deserialize, Serialize}; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(try_from = "String", into = "String")] +#[serde(try_from = "String", into = "&'static str")] pub enum Item { Air, Stone, @@ -8858,21 +8858,21 @@ impl Item { use std::convert::TryFrom; impl TryFrom for Item { - type Error = String; + type Error = &'static str; fn try_from(value: String) -> Result { if let Some(item) = Item::from_name(value.as_str()) { Ok(item) } else { - Err(String::from("Unknown item name.")) + Err("Unknown item name.") } } } use std::convert::Into; -impl Into for Item { - fn into(self) -> String { - String::from(self.name()) +impl Into<&'static str> for Item { + fn into(self) -> &'static str { + self.name() } } From 816782e3db1e94e4d8b5da316e515f75bf12c3d2 Mon Sep 17 00:00:00 2001 From: PauMAVA Date: Fri, 26 Feb 2021 00:24:32 +0100 Subject: [PATCH 10/13] Item operations error handling. Slot type WIP. --- crates/items/src/item_stack.rs | 61 +++++++++++++++++++++++----------- crates/items/src/lib.rs | 2 ++ crates/items/src/slot.rs | 24 +++++++++++++ 3 files changed, 68 insertions(+), 19 deletions(-) create mode 100644 crates/items/src/slot.rs diff --git a/crates/items/src/item_stack.rs b/crates/items/src/item_stack.rs index c95ee03..bccb1ff 100644 --- a/crates/items/src/item_stack.rs +++ b/crates/items/src/item_stack.rs @@ -111,26 +111,22 @@ impl ItemStack { } /// Adds more items to this `ItemStack`. Returns the new count. - pub fn add(&mut self, count: u32) -> Result { - if self.count + count > self.item.stack_size() { - return Err(()); - } - self.count += count; - Ok(self.count) + pub fn add(&mut self, count: u32) -> Result { + self.set_count(self.count + count) } /// Adds more items to this `ItemStack`. Does not check if the /// addition will make the count to be greater than the - /// stack size. Returns the new count. + /// stack size. Does not check count overflows. Returns the new count. pub fn unchecked_add(&mut self, count: u32) -> u32 { self.count += count; self.count } /// Removes some items from this `ItemStack`. - pub fn remove(&mut self, count: u32) -> Result { + pub fn remove(&mut self, count: u32) -> Result { if self.count < count { - return Err(()); + return Err(ItemStackError::NotEnoughAmount); } self.count -= count; Ok(self.count) @@ -139,9 +135,9 @@ impl ItemStack { /// Sets the item type for this `ItemStack`. Returns the new /// item type or fails if the current item count exceeds the /// new item type stack size. - pub fn set_item(&mut self, item: Item) -> Result { + pub fn set_item(&mut self, item: Item) -> Result { if self.count > item.stack_size() { - return Err(()); + return Err(ItemStackError::ExceedsStackSize); } self.item = item; Ok(self.item) @@ -158,9 +154,11 @@ impl ItemStack { /// Sets the count for this `ItemStack`. Returns the updated /// count or fails if the new count would exceed the stack /// size for that item type. - pub fn set_count(&mut self, count: u32) -> Result { + pub fn set_count(&mut self, count: u32) -> Result { if count > self.item.stack_size() { - return Err(()); + return Err(ItemStackError::ExceedsStackSize); + } else if count > i32::MAX as u32 { + return Err(ItemStackError::ClientOverflow); } self.count = count; Ok(self.count) @@ -168,7 +166,7 @@ impl ItemStack { /// Sets the count for this `ItemStack`. It will not check if /// the desired count exceeds the current item type stack size. - /// Returns the updated count. + /// Does not check count overflows. Returns the updated count. pub fn unchecked_set_count(&mut self, count: u32) -> u32 { self.count = count; self.count @@ -185,11 +183,15 @@ impl ItemStack { /// Splits this `ItemStack` by removing the /// specified amount. Returns the taken part. - pub fn take(&mut self, amount: u32) -> Result { - let count_left: i32 = self.count as i32 - amount as i32; - if count_left < 0 { - return Err(()); + pub fn take(&mut self, amount: u32) -> Result { + if self.count <= amount { + return Err(if self.count == amount { + ItemStackError::EmptyStack + } else { + ItemStackError::NotEnoughAmount + }); } + let count_left: u32 = self.count - amount; let taken = ItemStack { count: amount, ..self.clone() @@ -211,11 +213,22 @@ impl ItemStack { } /// Transfers up to `n` items to `other`. - pub fn transfer_to(&mut self, n: u32, other: &mut Self) { + pub fn transfer_to(&mut self, n: u32, other: &mut Self) -> Result<(), ItemStackError> { + if self.count <= n { + return Err(if self.count == n { + ItemStackError::EmptyStack + } else { + ItemStackError::NotEnoughAmount + }); + } let max_transfer = other.item.stack_size().saturating_sub(other.count); let transfer = max_transfer.min(self.count).min(n); + if other.count + transfer > i32::MAX as u32 { + return Err(ItemStackError::ClientOverflow); + } self.count -= transfer; other.count += transfer; + Ok(()) } /// Damages the item by the specified amount. @@ -237,3 +250,13 @@ impl ItemStack { } } } + +/// An error type that may be returned when performing +/// operations over an `ItemStack`. +#[derive(Debug)] +pub enum ItemStackError { + ClientOverflow, + EmptyStack, + ExceedsStackSize, + NotEnoughAmount, +} diff --git a/crates/items/src/lib.rs b/crates/items/src/lib.rs index 1cccbeb..024dcac 100644 --- a/crates/items/src/lib.rs +++ b/crates/items/src/lib.rs @@ -1,7 +1,9 @@ mod enchantment; mod item; mod item_stack; +mod slot; pub use enchantment::{Enchantment, EnchantmentKind}; pub use item::*; pub use item_stack::ItemStack; +pub use slot::Slot; diff --git a/crates/items/src/slot.rs b/crates/items/src/slot.rs new file mode 100644 index 0000000..937b826 --- /dev/null +++ b/crates/items/src/slot.rs @@ -0,0 +1,24 @@ +use crate::item_stack::ItemStackError; +use crate::ItemStack; + +/// Represents an `Inventory` slot. None if it's empty, Some if +/// it's not empty. +pub type Slot = Option; + +/// Trait of methods for the `Slot` type. +trait SlotMethods { + fn take(&mut self, amount: u32) -> Result<(), ItemStackError>; +} + +impl SlotMethods for Slot { + /// Try to take a certain amount of items from a `Slot`. Returns an error + /// if the slot is empty. + fn take(&mut self, amount: u32) -> Result<(), ItemStackError> { + if let Some(mut stack) = self.take() { + *self = Some(stack.take(amount)?); + Ok(()) + } else { + Err(ItemStackError::EmptyStack) + } + } +} From c64f1037da677ec9905280dcfbbe958ad4ffa79c Mon Sep 17 00:00:00 2001 From: PauMAVA Date: Fri, 26 Feb 2021 10:35:33 +0100 Subject: [PATCH 11/13] Impl FromStr for Item, improve take_half, use as_ref instead of clone, make fields private, clarify docs. --- crates/generators/python/item.py | 16 ++++++++++++ crates/items/src/item.rs | 14 ++++++++++ crates/items/src/item_stack.rs | 45 ++++++++++++++++++++------------ 3 files changed, 59 insertions(+), 16 deletions(-) diff --git a/crates/generators/python/item.py b/crates/generators/python/item.py index 13f5241..29fdbbd 100644 --- a/crates/generators/python/item.py +++ b/crates/generators/python/item.py @@ -57,4 +57,20 @@ }} """ +output_data += f""" + use std::str::FromStr; + + impl FromStr for Item {{ + type Err = &'static str; + + fn from_str(s: &str) -> Result {{ + if let Some(item) = Item::from_name(s) {{ + Ok(item) + }} else {{ + Err("Unknown item name.") + }} + }} + }} +""" + output("crates/items/src/item.rs", output_data) diff --git a/crates/items/src/item.rs b/crates/items/src/item.rs index b892784..db0c248 100644 --- a/crates/items/src/item.rs +++ b/crates/items/src/item.rs @@ -8876,3 +8876,17 @@ impl Into<&'static str> for Item { self.name() } } + +use std::str::FromStr; + +impl FromStr for Item { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + if let Some(item) = Item::from_name(s) { + Ok(item) + } else { + Err("Unknown item name.") + } + } +} diff --git a/crates/items/src/item_stack.rs b/crates/items/src/item_stack.rs index bccb1ff..691e563 100644 --- a/crates/items/src/item_stack.rs +++ b/crates/items/src/item_stack.rs @@ -1,7 +1,11 @@ #![forbid(unsafe_code, warnings)] use crate::{Enchantment, Item}; +use core::fmt::Display; +use serde::__private::Formatter; use serde::{Deserialize, Serialize}; +use std::error::Error; +use std::fmt; /// Represents an item stack. /// @@ -10,16 +14,16 @@ use serde::{Deserialize, Serialize}; pub struct ItemStack { /// The item type of this `ItemStack`. #[serde(rename = "id")] - pub item: Item, + item: Item, /// The number of items in the `ItemStack`. #[serde(rename = "Count")] - pub count: u32, + count: u32, /// The `ItemStack` metadata, containing data such as damage, /// repair cost, enchantments... #[serde(rename = "tag")] - pub meta: Option, + meta: Option, } /// Represents the metadata of an `ItemStack`. Contains: @@ -32,19 +36,19 @@ pub struct ItemStack { #[serde(rename_all = "PascalCase")] pub struct ItemStackMeta { /// The displayed title (name) of the associated `ItemStack`. - pub title: String, + title: String, /// The displayed lore of the associated `ItemStack`. - pub lore: String, + lore: String, /// The damage taken by the `ItemStack`. - pub damage: Option, + damage: Option, /// The cost of repairing the `ItemStack`. - pub repair_cost: Option, + repair_cost: Option, /// The enchantments applied to this `ItemStack`. - pub enchantments: Vec, + enchantments: Vec, } impl ItemStack { @@ -74,7 +78,7 @@ impl ItemStack { /// Returns whether the given item stack has the same damage /// as `self`. pub fn has_same_damage(&self, other: &Self) -> bool { - if let (Some(self_meta), Some(other_meta)) = (self.meta.clone(), other.meta.clone()) { + if let (Some(self_meta), Some(other_meta)) = (self.meta.as_ref(), other.meta.as_ref()) { self_meta.damage == other_meta.damage } else { self.meta.is_none() && other.meta.is_none() @@ -89,7 +93,8 @@ impl ItemStack { } /// Returns whether the given `ItemStack` has the same - /// type and count as `self`. + /// type and count as (but not necessarily the same meta + /// as) `self`. pub fn has_same_type_and_count(&self, other: &Self) -> bool { self.item == other.item && self.count == other.count } @@ -177,8 +182,7 @@ impl ItemStack { /// will be left with the least items. Returns the taken /// half. pub fn take_half(&mut self) -> ItemStack { - self.take((self.count as f64 / 2 as f64).ceil() as u32) - .unwrap() + self.take((self.count + 1) / 2).unwrap() } /// Splits this `ItemStack` by removing the @@ -201,15 +205,15 @@ impl ItemStack { } /// Merges another `ItemStack` with this one. - pub fn merge_with(&mut self, other: &mut Self) -> bool { + pub fn merge_with(&mut self, other: &mut Self) -> Result<(), ItemStackError> { if !self.has_same_type_and_damage(other) { - return false; + return Err(ItemStackError::IncompatibleStacks); } let new_count = (self.count + other.count).min(self.item.stack_size()); let amount_added = new_count - self.count; self.count = new_count; other.count -= amount_added; - true + Ok(()) } /// Transfers up to `n` items to `other`. @@ -253,10 +257,19 @@ impl ItemStack { /// An error type that may be returned when performing /// operations over an `ItemStack`. -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum ItemStackError { ClientOverflow, EmptyStack, ExceedsStackSize, + IncompatibleStacks, NotEnoughAmount, } + +impl Display for ItemStackError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self) + } +} + +impl Error for ItemStackError {} From b0afab3be89c6533c80ad6063135fb630068ad44 Mon Sep 17 00:00:00 2001 From: PauMAVA Date: Mon, 1 Mar 2021 00:06:18 +0100 Subject: [PATCH 12/13] WIP Does not work. Migrating to NonZeroU32 and implementing InventorySlot. --- crates/items/src/inventory_slot.rs | 50 +++++++++++++ crates/items/src/item_stack.rs | 108 +++++++++++++++++------------ crates/items/src/lib.rs | 4 +- crates/items/src/slot.rs | 24 ------- 4 files changed, 115 insertions(+), 71 deletions(-) create mode 100644 crates/items/src/inventory_slot.rs delete mode 100644 crates/items/src/slot.rs diff --git a/crates/items/src/inventory_slot.rs b/crates/items/src/inventory_slot.rs new file mode 100644 index 0000000..15f3ff0 --- /dev/null +++ b/crates/items/src/inventory_slot.rs @@ -0,0 +1,50 @@ +use crate::item_stack::ItemStackError; +use crate::ItemStack; +use core::mem; + +/// Represents an Inventory slot. May be empty +/// or filled (contains an `ItemStack`). +pub enum InventorySlot { + Filled(ItemStack), + Empty, +} + +impl Default for InventorySlot { + fn default() -> Self { + InventorySlot::Empty + } +} + +impl InventorySlot { + /// Tries to take all items from the `InventorySlot`. + /// If the `InventorySlot` is filled returns the `ItemStack`. + /// If the `InventorySlot` is empty returns `None`. + pub fn take_all(&mut self) -> Option { + match mem::take(self) { + Self::Filled(stack) => Some(stack), + Self::Empty => None, + } + } + + /// Tries to take (split) the specified amount from the associated + /// `ItemStack` to this `InventorySlot`. If this `InventorySlot` is + /// empty, the specified amount is zero, or the resulting stack is + /// empty will return the `ItemStackError::EmptyStack` error. If the + /// amount to take is bigger than the current amount it will return + /// the `ItemStackError::NotEnoughAmount` error. On success returns + /// the taken part of the `ItemStack` as a new one. + pub fn take(&mut self, amount: u32) -> Result { + let split = match mem::take(self) { + Self::Empty => return Err(ItemStackError::EmptyStack), + Self::Filled(stack) => stack.split(amount), + }; + let (stack, res) = match split { + Ok((original, new)) => (original, Ok(new)), + Err((original, error)) => (Some(original), Err(error)), + }; + if let Some(stack) = stack { + *self = Self::Filled(stack) + } + res + } +} diff --git a/crates/items/src/item_stack.rs b/crates/items/src/item_stack.rs index 691e563..3d5183e 100644 --- a/crates/items/src/item_stack.rs +++ b/crates/items/src/item_stack.rs @@ -2,10 +2,10 @@ use crate::{Enchantment, Item}; use core::fmt::Display; -use serde::__private::Formatter; use serde::{Deserialize, Serialize}; use std::error::Error; use std::fmt; +use std::num::NonZeroU32; /// Represents an item stack. /// @@ -18,7 +18,7 @@ pub struct ItemStack { /// The number of items in the `ItemStack`. #[serde(rename = "Count")] - count: u32, + count: NonZeroU32, /// The `ItemStack` metadata, containing data such as damage, /// repair cost, enchantments... @@ -54,10 +54,14 @@ pub struct ItemStackMeta { impl ItemStack { /// Creates a new `ItemStack` with the default name (title) /// no lore, no damage, no repair cost and no enchantments. - pub fn new(item: Item, count: u32) -> Self { - Self { + pub fn new(item: Item, count: u32) -> Result { + let count = NonZeroU32::new(count); + if count.is_none() { + return Err(ItemStackError::EmptyStack); + } + Ok(Self { item, - count, + count: count.unwrap(), meta: Some(ItemStackMeta { title: String::from(item.name()), lore: "".to_string(), @@ -65,7 +69,7 @@ impl ItemStack { repair_cost: None, enchantments: vec![], }), - } + }) } /// Returns whether the given item stack has @@ -112,36 +116,40 @@ impl ItemStack { /// Returns the number of items in this `ItemStack`. pub fn count(&self) -> u32 { - self.count + self.count.get() } /// Adds more items to this `ItemStack`. Returns the new count. pub fn add(&mut self, count: u32) -> Result { - self.set_count(self.count + count) + self.set_count(self.count.get() + count) } /// Adds more items to this `ItemStack`. Does not check if the /// addition will make the count to be greater than the /// stack size. Does not check count overflows. Returns the new count. pub fn unchecked_add(&mut self, count: u32) -> u32 { - self.count += count; - self.count + self.count = NonZeroU32::new(self.count.get() + count).unwrap(); + self.count.get() } /// Removes some items from this `ItemStack`. pub fn remove(&mut self, count: u32) -> Result { - if self.count < count { - return Err(ItemStackError::NotEnoughAmount); + if self.count.get() <= count { + return Err(if self.count.get() == count { + ItemStackError::EmptyStack + } else { + ItemStackError::NotEnoughAmount + }); } - self.count -= count; - Ok(self.count) + self.count = NonZeroU32::new(self.count.get() - count).unwrap(); + Ok(self.count.get()) } /// Sets the item type for this `ItemStack`. Returns the new /// item type or fails if the current item count exceeds the /// new item type stack size. pub fn set_item(&mut self, item: Item) -> Result { - if self.count > item.stack_size() { + if self.count.get() > item.stack_size() { return Err(ItemStackError::ExceedsStackSize); } self.item = item; @@ -160,48 +168,58 @@ impl ItemStack { /// count or fails if the new count would exceed the stack /// size for that item type. pub fn set_count(&mut self, count: u32) -> Result { - if count > self.item.stack_size() { + let count = NonZeroU32::new(count); + if count.is_none() { + return Err(ItemStackError::EmptyStack); + } + let count = count.unwrap(); + if count.get() > self.item.stack_size() { return Err(ItemStackError::ExceedsStackSize); - } else if count > i32::MAX as u32 { + } else if count.get() > i32::MAX as u32 { return Err(ItemStackError::ClientOverflow); } self.count = count; - Ok(self.count) + Ok(self.count.get()) } /// Sets the count for this `ItemStack`. It will not check if /// the desired count exceeds the current item type stack size. - /// Does not check count overflows. Returns the updated count. + /// Does not check count overflows or if the parameter is zero. + /// Returns the updated count. pub fn unchecked_set_count(&mut self, count: u32) -> u32 { - self.count = count; - self.count + self.count = NonZeroU32::new(count).unwrap(); + self.count.get() } /// Splits this `ItemStack` in half, returning the /// removed half. If the amount is odd, `self` /// will be left with the least items. Returns the taken /// half. - pub fn take_half(&mut self) -> ItemStack { - self.take((self.count + 1) / 2).unwrap() + pub fn split_half(&self) -> (Option, ItemStack) { + self.split((self.count.get() + 1) / 2).unwrap() } /// Splits this `ItemStack` by removing the /// specified amount. Returns the taken part. - pub fn take(&mut self, amount: u32) -> Result { - if self.count <= amount { - return Err(if self.count == amount { - ItemStackError::EmptyStack - } else { - ItemStackError::NotEnoughAmount - }); + pub fn split( + mut self, + amount: u32, + ) -> Result<(Option, ItemStack), (ItemStack, ItemStackError)> { + let amount = NonZeroU32::new(amount); + if amount.is_none() { + return Err((self, ItemStackError::EmptyStack)); + } + let amount = amount.unwrap(); + if self.count < amount { + return Err((self, ItemStackError::NotEnoughAmount)); } - let count_left: u32 = self.count - amount; + let count_left: u32 = self.count.get() - amount.get(); let taken = ItemStack { count: amount, ..self.clone() }; - self.count = count_left as u32; - Ok(taken) + self.count = NonZeroU32::new(count_left).unwrap(); + Ok((if count_left == 0 { None } else { Some(self) }, taken)) } /// Merges another `ItemStack` with this one. @@ -209,29 +227,29 @@ impl ItemStack { if !self.has_same_type_and_damage(other) { return Err(ItemStackError::IncompatibleStacks); } - let new_count = (self.count + other.count).min(self.item.stack_size()); - let amount_added = new_count - self.count; - self.count = new_count; - other.count -= amount_added; + let new_count = (self.count.get() + other.count.get()).min(self.item.stack_size()); + let amount_added = new_count - self.count.get(); + self.count = NonZeroU32::new(new_count).unwrap(); + other.count = NonZeroU32::new(other.count() - amount_added).unwrap(); Ok(()) } /// Transfers up to `n` items to `other`. pub fn transfer_to(&mut self, n: u32, other: &mut Self) -> Result<(), ItemStackError> { - if self.count <= n { - return Err(if self.count == n { + if self.count.get() <= n || n == 0 { + return Err(if self.count.get() == n || n == 0 { ItemStackError::EmptyStack } else { ItemStackError::NotEnoughAmount }); } - let max_transfer = other.item.stack_size().saturating_sub(other.count); - let transfer = max_transfer.min(self.count).min(n); - if other.count + transfer > i32::MAX as u32 { + let max_transfer = other.item.stack_size().saturating_sub(other.count.get()); + let transfer = max_transfer.min(self.count.get()).min(n); + if other.count.get() + transfer > i32::MAX as u32 { return Err(ItemStackError::ClientOverflow); } - self.count -= transfer; - other.count += transfer; + self.count = NonZeroU32::new(transfer).unwrap(); + other.count = NonZeroU32::new(other.count.get() + transfer).unwrap(); Ok(()) } @@ -267,7 +285,7 @@ pub enum ItemStackError { } impl Display for ItemStackError { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self) } } diff --git a/crates/items/src/lib.rs b/crates/items/src/lib.rs index 024dcac..0333954 100644 --- a/crates/items/src/lib.rs +++ b/crates/items/src/lib.rs @@ -1,9 +1,9 @@ mod enchantment; +mod inventory_slot; mod item; mod item_stack; -mod slot; pub use enchantment::{Enchantment, EnchantmentKind}; +pub use inventory_slot::InventorySlot; pub use item::*; pub use item_stack::ItemStack; -pub use slot::Slot; diff --git a/crates/items/src/slot.rs b/crates/items/src/slot.rs deleted file mode 100644 index 937b826..0000000 --- a/crates/items/src/slot.rs +++ /dev/null @@ -1,24 +0,0 @@ -use crate::item_stack::ItemStackError; -use crate::ItemStack; - -/// Represents an `Inventory` slot. None if it's empty, Some if -/// it's not empty. -pub type Slot = Option; - -/// Trait of methods for the `Slot` type. -trait SlotMethods { - fn take(&mut self, amount: u32) -> Result<(), ItemStackError>; -} - -impl SlotMethods for Slot { - /// Try to take a certain amount of items from a `Slot`. Returns an error - /// if the slot is empty. - fn take(&mut self, amount: u32) -> Result<(), ItemStackError> { - if let Some(mut stack) = self.take() { - *self = Some(stack.take(amount)?); - Ok(()) - } else { - Err(ItemStackError::EmptyStack) - } - } -} From 7e4a013f0dfcbc1e4b73acaa32e3c6289a0a7019 Mon Sep 17 00:00:00 2001 From: PauMAVA Date: Mon, 1 Mar 2021 00:21:59 +0100 Subject: [PATCH 13/13] Now split and split_half will clone. --- crates/items/src/inventory_slot.rs | 2 +- crates/items/src/item_stack.rs | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/crates/items/src/inventory_slot.rs b/crates/items/src/inventory_slot.rs index 15f3ff0..78c6817 100644 --- a/crates/items/src/inventory_slot.rs +++ b/crates/items/src/inventory_slot.rs @@ -36,7 +36,7 @@ impl InventorySlot { pub fn take(&mut self, amount: u32) -> Result { let split = match mem::take(self) { Self::Empty => return Err(ItemStackError::EmptyStack), - Self::Filled(stack) => stack.split(amount), + Self::Filled(mut stack) => stack.split(amount), }; let (stack, res) = match split { Ok((original, new)) => (original, Ok(new)), diff --git a/crates/items/src/item_stack.rs b/crates/items/src/item_stack.rs index 3d5183e..0e8d63b 100644 --- a/crates/items/src/item_stack.rs +++ b/crates/items/src/item_stack.rs @@ -195,23 +195,23 @@ impl ItemStack { /// removed half. If the amount is odd, `self` /// will be left with the least items. Returns the taken /// half. - pub fn split_half(&self) -> (Option, ItemStack) { + pub fn split_half(&mut self) -> (Option, ItemStack) { self.split((self.count.get() + 1) / 2).unwrap() } /// Splits this `ItemStack` by removing the /// specified amount. Returns the taken part. pub fn split( - mut self, + &mut self, amount: u32, ) -> Result<(Option, ItemStack), (ItemStack, ItemStackError)> { let amount = NonZeroU32::new(amount); if amount.is_none() { - return Err((self, ItemStackError::EmptyStack)); + return Err((self.clone(), ItemStackError::EmptyStack)); } let amount = amount.unwrap(); if self.count < amount { - return Err((self, ItemStackError::NotEnoughAmount)); + return Err((self.clone(), ItemStackError::NotEnoughAmount)); } let count_left: u32 = self.count.get() - amount.get(); let taken = ItemStack { @@ -219,7 +219,14 @@ impl ItemStack { ..self.clone() }; self.count = NonZeroU32::new(count_left).unwrap(); - Ok((if count_left == 0 { None } else { Some(self) }, taken)) + Ok(( + if count_left == 0 { + None + } else { + Some(self.clone()) + }, + taken, + )) } /// Merges another `ItemStack` with this one.