diff --git a/core/item/Cargo.toml b/core/item/Cargo.toml new file mode 100644 index 0000000..727aaf7 --- /dev/null +++ b/core/item/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "rimecraft-item" +version = "0.1.0" +edition = "2021" +authors = ["JieningYu "] +description = "Minecraft Item primitives and registry" +repository = "https://github.com/rimecraft-rs/rimecraft/" +license = "AGPL-3.0-or-later" +categories = [] + +[badges] +maintenance = { status = "passively-maintained" } + +[dependencies] +rimecraft-registry = { path = "../../util/registry" } +rimecraft-fmt = { path = "../../util/fmt" } +rimecraft-freezer = { path = "../../util/freezer" } +rimecraft-attachment = { path = "../../util/attachment" } +rimecraft-serde-update = { path = "../../util/serde-update", optional = true } +fastnbt = "2.4" +rimecraft-nbt-ext = { path = "../../util/nbt-ext" } +serde = { version = "1.0", optional = true, features = ["derive"] } + +[features] +default = ["serde"] +serde = [ + "dep:serde", + "rimecraft-registry/serde", + "rimecraft-attachment/serde", + "dep:rimecraft-serde-update", +] + +[lints] +workspace = true diff --git a/core/item/src/lib.rs b/core/item/src/lib.rs new file mode 100644 index 0000000..153b381 --- /dev/null +++ b/core/item/src/lib.rs @@ -0,0 +1,115 @@ +//! Minecraft Item primitives and registry. + +use std::{marker::PhantomData, num::NonZeroU32}; + +use rimecraft_fmt::Formatting; +use rimecraft_registry::{ProvideRegistry, Reg}; + +mod stack; + +pub use stack::ItemStack; + +/// Item containing settings. +#[derive(Debug)] +pub struct RawItem

{ + settings: Settings, + _marker: PhantomData

, +} + +impl

RawItem

{ + /// Creates a new `Item` with the given settings. + #[inline] + pub const fn new(settings: Settings) -> Self { + Self { + settings, + _marker: PhantomData, + } + } + + /// Returns the settings of the item. + #[inline] + pub fn settings(&self) -> &Settings { + &self.settings + } +} + +impl

From for RawItem

{ + #[inline] + fn from(settings: Settings) -> Self { + Self::new(settings) + } +} + +impl<'r, K, P> ProvideRegistry<'r, K, Self> for RawItem

+where + P: ProvideRegistry<'r, K, Self>, +{ + #[inline] + fn registry() -> &'r rimecraft_registry::Registry { + P::registry() + } +} + +/// An item usable by players and other entities. +pub type Item<'r, K, P> = Reg<'r, K, RawItem

>; + +/// A trait for converting a value to an [`Item`]. +pub trait ToItem<'s, 'r, K, P> { + /// Converts the value to an [`Item`]. + fn to_item(&'s self) -> Item<'r, K, P>; +} + +/// The max item count of an `ItemStack`. +pub const MAX_STACK_COUNT: u32 = 64; + +/// Settings of an [`Item`]. +/// +/// A setting configure behaviors common to all items, such as the +/// stack's max count. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Settings { + /// The maximum count of the item that can be stacked in a single slot. + pub max_count: NonZeroU32, + /// The maximum amount of damage the item can take. + pub max_damage: Option, + + /// The rarity of the item. + pub rarity: Rarity, +} + +impl Default for Settings { + #[inline] + fn default() -> Self { + Self { + max_count: NonZeroU32::new(MAX_STACK_COUNT).unwrap(), + max_damage: None, + rarity: Default::default(), + } + } +} + +/// Rarity of an item. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)] +pub enum Rarity { + /// Common rarity. + #[default] + Common, + /// Uncommon rarity. + Uncommon, + /// Rare rarity. + Rare, + /// Epic rarity. + Epic, +} + +impl From for Formatting { + #[inline] + fn from(value: Rarity) -> Self { + match value { + Rarity::Common => Formatting::White, + Rarity::Uncommon => Formatting::Yellow, + Rarity::Rare => Self::Aqua, + Rarity::Epic => Self::LightPurple, + } + } +} diff --git a/core/item/src/stack.rs b/core/item/src/stack.rs new file mode 100644 index 0000000..fc3ac4c --- /dev/null +++ b/core/item/src/stack.rs @@ -0,0 +1,169 @@ +use rimecraft_attachment::Attachments; +use rimecraft_nbt_ext::Compound; + +use std::{hash::Hash, marker::PhantomData}; + +use crate::{Item, ToItem}; + +/// A stack of items. +/// +/// This is a data container that holds the item count and the stack's NBT. +#[derive(Debug)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), + serde(bound( + serialize = "K: serde::Serialize + Hash + Eq", + deserialize = r#" + 'r: 'de, + K: serde::Deserialize<'de> + rimecraft_serde_update::Update<'de> + Hash + Eq + std::fmt::Debug + 'r, + P: InitAttachments + rimecraft_registry::ProvideRegistry<'r, K, crate::RawItem

> + 'r"# + )) +)] +pub struct ItemStack<'r, K, P> { + #[cfg_attr(feature = "serde", serde(rename = "id"))] + item: Item<'r, K, P>, + + #[cfg_attr(feature = "serde", serde(rename = "Count"))] + count: u32, + + /// Item stack's custom NBT. + #[cfg_attr(feature = "serde", serde(rename = "tag"), serde(default))] + nbt: Option, + + #[cfg_attr( + feature = "serde", + serde(skip_serializing_if = "should_skip_attachment_ser"), + serde(default), + serde(serialize_with = "ser_attachments"), + serde(deserialize_with = "deser_attachments") + )] + attachments: (Attachments, PhantomData

), +} + +#[cfg(feature = "serde")] +fn should_skip_attachment_ser(attachments: &(Attachments, PhantomData

)) -> bool { + attachments.0.is_persistent_data_empty() +} + +#[cfg(feature = "serde")] +fn ser_attachments( + attachments: &(Attachments, PhantomData

), + serializer: S, +) -> Result +where + S: serde::Serializer, + K: serde::Serialize + Hash + Eq, +{ + serde::Serialize::serialize(&attachments.0, serializer) +} + +#[cfg(feature = "serde")] +fn deser_attachments<'de, K, P, D>( + deserializer: D, +) -> Result<(Attachments, PhantomData

), >::Error> +where + D: serde::Deserializer<'de>, + P: InitAttachments, + K: serde::Deserialize<'de> + rimecraft_serde_update::Update<'de> + Hash + Eq, +{ + use rimecraft_serde_update::Update; + let mut attachments = Attachments::new(); + P::init_attachments(&mut attachments); + attachments.update(deserializer)?; + Ok((attachments, PhantomData)) +} + +impl<'r, K, P> ItemStack<'r, K, P> +where + P: InitAttachments, +{ + /// Creates a new item stack with the given item and count. + #[inline] + pub fn new(item: Item<'r, K, P>, count: u32) -> Self { + Self::with_nbt(item, count, None) + } + + /// Creates a new item stack with the given item, count, + /// and custom NBT tag. + pub fn with_nbt(item: Item<'r, K, P>, count: u32, nbt: Option) -> Self { + let mut attachments = Attachments::new(); + P::init_attachments(&mut attachments); + + Self { + item, + count, + nbt, + attachments: (attachments, PhantomData), + } + } +} + +impl<'r, K, P> ItemStack<'r, K, P> { + /// Returns the item of the stack. + #[inline] + pub fn item(&self) -> Item<'r, K, P> { + self.item + } + + /// Returns the count of the stack. + #[inline] + pub fn count(&self) -> u32 { + self.count + } + + /// Returns the custom NBT of the stack. + #[inline] + pub fn nbt(&self) -> Option<&Compound> { + self.nbt.as_ref() + } + + /// Returns a mutable reference to the custom NBT of the stack. + #[inline] + pub fn nbt_mut(&mut self) -> Option<&mut Compound> { + self.nbt.as_mut() + } + + /// Returns the custom NBT of the stack, create one if it does not exist. + #[inline] + pub fn get_or_create_nbt(&mut self) -> &mut Compound { + self.nbt.get_or_insert_with(Compound::new) + } + + /// Sets the count of the stack. + #[inline] + pub fn set_count(&mut self, count: u32) { + self.count = count; + } + + /// Sets the custom NBT of the stack. + #[inline] + pub fn set_nbt(&mut self, nbt: Option) { + self.nbt = nbt; + } + + /// Returns the attachments of the stack. + #[inline] + pub fn attachments(&self) -> &Attachments { + &self.attachments.0 + } + + /// Returns the mutable view of attachments of the stack. + #[inline] + pub fn attachments_mut(&mut self) -> &mut Attachments { + &mut self.attachments.0 + } +} + +impl<'s, 'r, K, P> ToItem<'s, 'r, K, P> for ItemStack<'r, K, P> { + #[inline] + fn to_item(&'s self) -> Item<'r, K, P> { + self.item + } +} + +/// A trait for initializing attachments of an item stack. +pub trait InitAttachments { + /// Initializes the attachments of the item stack. + fn init_attachments(attachments: &mut Attachments); +} diff --git a/core/state/Cargo.toml b/core/state/Cargo.toml index e515b56..06bab29 100644 --- a/core/state/Cargo.toml +++ b/core/state/Cargo.toml @@ -14,11 +14,9 @@ maintenance = { status = "passively-maintained" } [dependencies] regex-lite = "0.1" serde = { version = "1.0", optional = true } -rimecraft-serde-update = { path = "../../util/serde-update", optional = true } [features] -default = ["serde"] -serde = ["dep:serde", "dep:rimecraft-serde-update"] +serde = ["dep:serde"] [lints] workspace = true diff --git a/util/attachment/src/lib.rs b/util/attachment/src/lib.rs index 2a1ebb1..1a54b4d 100644 --- a/util/attachment/src/lib.rs +++ b/util/attachment/src/lib.rs @@ -75,7 +75,7 @@ pub struct Attachments { serde_state: crate::serde::State, } -impl Attachments { +impl Attachments { /// Creates a new [`Attachments`] instance. #[inline] pub fn new() -> Self { @@ -87,7 +87,7 @@ impl Attachments { } } -impl Default for Attachments { +impl Default for Attachments { #[inline] fn default() -> Self { Self::new() @@ -170,6 +170,15 @@ impl Attachments { } } +impl Attachments { + /// Whether the persistent data queue is empty. + #[cfg(feature = "serde")] + #[inline] + pub fn is_persistent_data_empty(&self) -> bool { + self.serde_state.ser.is_empty() && self.serde_state.update.is_empty() + } +} + impl Clone for Type where K: Clone, diff --git a/util/attachment/src/serde.rs b/util/attachment/src/serde.rs index 2cbca4a..5683a3e 100644 --- a/util/attachment/src/serde.rs +++ b/util/attachment/src/serde.rs @@ -18,8 +18,8 @@ type SerState = Vec<(K, Box Box>)>; type UpdateState = HashMap Box>>; pub(crate) struct State { - ser: SerState, - update: UpdateState, + pub ser: SerState, + pub update: UpdateState, } impl State { @@ -121,7 +121,7 @@ impl<'a, T: 'a> AsAttachmentMut<'a> for Persistent { } } -trait AsErasedSerialize { +pub(crate) trait AsErasedSerialize { fn as_serialize(&self) -> &dyn erased_serde::Serialize; } @@ -136,7 +136,7 @@ where } } -trait AsErasedUpdate { +pub(crate) trait AsErasedUpdate { fn as_update(&mut self) -> &mut dyn for<'de> rimecraft_serde_update::erased::ErasedUpdate<'de>; } diff --git a/util/identifier/src/vanilla.rs b/util/identifier/src/vanilla.rs index 9c32ac7..36f48f6 100644 --- a/util/identifier/src/vanilla.rs +++ b/util/identifier/src/vanilla.rs @@ -20,7 +20,10 @@ impl Namespace { /// /// Panics if the given namespace is invalid. #[inline] - pub fn new(value: impl Into>) -> Self { + pub fn new(value: T) -> Self + where + T: Into>, + { let value = value.into(); validate_namespace(&value).unwrap(); Self(ArcCowStr::Arc(value)) @@ -82,7 +85,10 @@ impl Path { /// /// Panics if the given path is invalid. #[inline] - pub fn new(value: impl Into>) -> Self { + pub fn new(value: T) -> Self + where + T: Into>, + { let value = value.into(); validate_path(&value).unwrap(); Self(ArcCowStr::Arc(value)) diff --git a/util/registry/src/lib.rs b/util/registry/src/lib.rs index 7affce3..ba25315 100644 --- a/util/registry/src/lib.rs +++ b/util/registry/src/lib.rs @@ -8,6 +8,7 @@ use std::{ collections::{HashMap, HashSet}, hash::Hash, ops::{Deref, Index}, + sync::OnceLock, }; use entry::RefEntry; @@ -228,6 +229,22 @@ impl Deref for Reg<'_, K, T> { } } +impl Hash for Reg<'_, K, T> { + #[inline] + fn hash(&self, state: &mut H) { + self.raw.hash(state) + } +} + +impl PartialEq for Reg<'_, K, T> { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.raw == other.raw + } +} + +impl Eq for Reg<'_, K, T> {} + /// Trait for converting to a key. pub trait AsKey { /// Converts to a key. @@ -360,17 +377,17 @@ impl<'a, K, T> IntoIterator for &'a Registry { pub struct RegistryMut { key: Key>, entries: Vec<(T, RefEntry)>, - keys: HashSet>, + keys: OnceLock>, } impl RegistryMut { /// Creates a new mutable registry. #[inline] - pub fn new(key: Key>) -> Self { + pub const fn new(key: Key>) -> Self { Self { key, entries: Vec::new(), - keys: HashSet::new(), + keys: OnceLock::new(), } } @@ -392,12 +409,17 @@ where /// /// Returns back the given key and value if /// registration with the key already exists. + #[allow(clippy::missing_panics_doc)] pub fn register(&mut self, key: Key, value: T) -> Result, T)> { - if self.keys.contains(&key) { + if self.keys.get_mut().is_none() { + self.keys = HashSet::new().into(); + } + let keys = self.keys.get_mut().expect("keys not initialized"); + if keys.contains(key.value()) { return Err((key, value)); } + keys.insert(key.value().clone()); let raw = self.entries.len(); - self.keys.insert(key.clone()); self.entries.push(( value, RefEntry { @@ -664,14 +686,19 @@ pub mod edcode { #[allow(dead_code)] type BoxedError = Box; +#[cfg(feature = "vanilla-identifier")] +/// Vanilla root registry key. +pub const VANILLA_ROOT_KEY: rimecraft_identifier::vanilla::Identifier = + rimecraft_identifier::vanilla::Identifier::new( + rimecraft_identifier::vanilla::MINECRAFT, + rimecraft_identifier::vanilla::Path::new_unchecked("root"), + ); + #[cfg(feature = "vanilla-identifier")] impl crate::key::Root for rimecraft_identifier::vanilla::Identifier { #[inline] fn root() -> Self { - Self::new( - Default::default(), - rimecraft_identifier::vanilla::Path::new_unchecked("root"), - ) + VANILLA_ROOT_KEY } }