mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
Remove non-vanilla Slot and replace with ItemStackSeralizer (#726)
* replace slot with ItemStackSeralizer * Fix ItemStack * Fix pickup and remove faulty crafting (getting reworked in inv revamp) * re-add component check --------- Co-authored-by: kralverde <github@email.kralverde.dev>
This commit is contained in:
@@ -1,103 +0,0 @@
|
||||
use pumpkin_data::{
|
||||
item::Item,
|
||||
tag::{RegistryKey, get_tag_values},
|
||||
};
|
||||
use pumpkin_registry::{RECIPES, RecipeResult, flatten_3x3};
|
||||
use pumpkin_util::registry::{RegistryEntryList, TagType};
|
||||
use pumpkin_world::item::ItemStack;
|
||||
use rayon::prelude::*;
|
||||
|
||||
#[inline(always)]
|
||||
fn check_ingredient_type(ingredient_type: &TagType, input: &ItemStack) -> bool {
|
||||
match ingredient_type {
|
||||
TagType::Tag(tag) => {
|
||||
let _items = match get_tag_values(RegistryKey::Item, tag) {
|
||||
Some(items) => items,
|
||||
None => return false,
|
||||
};
|
||||
// TODO
|
||||
false
|
||||
// items.iter().any(|tag| check_ingredient_type(&tag, input))
|
||||
}
|
||||
TagType::Item(item) => {
|
||||
Item::from_registry_key(item).is_some_and(|item| item.id == input.item.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn check_if_matches_crafting(input: [[Option<&ItemStack>; 3]; 3]) -> Option<ItemStack> {
|
||||
let input = flatten_3x3(input);
|
||||
RECIPES
|
||||
.par_iter()
|
||||
.find_any(|recipe| {
|
||||
let patterns = recipe.pattern();
|
||||
if patterns
|
||||
.iter()
|
||||
.flatten()
|
||||
.flatten()
|
||||
.all(|slot| slot.is_none())
|
||||
{
|
||||
false
|
||||
} else if recipe.recipe_type.is_shapeless() {
|
||||
shapeless_crafting_match(input, recipe.pattern())
|
||||
} else {
|
||||
patterns.par_iter().any(|pattern| {
|
||||
pattern.iter().enumerate().all(|(i, row)| {
|
||||
row.iter()
|
||||
.enumerate()
|
||||
.all(|(j, item)| match (item, input[i][j]) {
|
||||
(Some(item), Some(input)) => ingredient_slot_check(item, input),
|
||||
(None, None) => true,
|
||||
(Some(_), None) | (None, Some(_)) => false,
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
.map(|recipe| match recipe.result() {
|
||||
RecipeResult::Single { id, .. } => Some(ItemStack {
|
||||
item: Item::from_registry_key(id).unwrap(),
|
||||
item_count: 1,
|
||||
}),
|
||||
RecipeResult::Many { id, count, .. } => Some(ItemStack {
|
||||
item: Item::from_registry_key(id).unwrap(),
|
||||
item_count: *count,
|
||||
}),
|
||||
RecipeResult::Special => None,
|
||||
})?
|
||||
}
|
||||
|
||||
fn ingredient_slot_check(recipe_item: &RegistryEntryList, input: &ItemStack) -> bool {
|
||||
match recipe_item {
|
||||
RegistryEntryList::Single(ingredient) => check_ingredient_type(ingredient, input),
|
||||
RegistryEntryList::Many(ingredients) => ingredients
|
||||
.iter()
|
||||
.any(|ingredient| check_ingredient_type(ingredient, input)),
|
||||
}
|
||||
}
|
||||
fn shapeless_crafting_match(
|
||||
input: [[Option<&ItemStack>; 3]; 3],
|
||||
pattern: &[[[Option<RegistryEntryList>; 3]; 3]],
|
||||
) -> bool {
|
||||
let mut pattern: Vec<RegistryEntryList> = pattern
|
||||
.iter()
|
||||
.flatten()
|
||||
.flatten()
|
||||
.flatten()
|
||||
.cloned()
|
||||
.collect();
|
||||
for item in input.into_iter().flatten().flatten() {
|
||||
if let Some(index) = pattern.iter().enumerate().find_map(|(i, recipe_item)| {
|
||||
if ingredient_slot_check(recipe_item, item) {
|
||||
Some(i)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
pattern.remove(index);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
pattern.is_empty()
|
||||
}
|
||||
@@ -4,7 +4,6 @@ use pumpkin_data::screen::WindowType;
|
||||
use pumpkin_world::item::ItemStack;
|
||||
|
||||
pub mod container_click;
|
||||
mod crafting;
|
||||
pub mod drag_handler;
|
||||
mod error;
|
||||
mod open_container;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::Container;
|
||||
use crate::crafting::check_if_matches_crafting;
|
||||
use pumpkin_data::block::Block;
|
||||
use pumpkin_data::screen::WindowType;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
@@ -162,7 +161,7 @@ impl Container for CraftingTable {
|
||||
|
||||
fn craft(&mut self) -> bool {
|
||||
// TODO: Is there a better way to do this?
|
||||
let check = [
|
||||
let _check = [
|
||||
[
|
||||
self.input[0][0].as_ref(),
|
||||
self.input[0][1].as_ref(),
|
||||
@@ -180,7 +179,7 @@ impl Container for CraftingTable {
|
||||
],
|
||||
];
|
||||
|
||||
let new_output = check_if_matches_crafting(check);
|
||||
let new_output = None; //check_if_matches_crafting(check);
|
||||
let result = new_output != self.output
|
||||
|| self.input.iter().flatten().any(|s| s.is_some())
|
||||
|| new_output.is_some();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::container_click::MouseClick;
|
||||
use crate::crafting::check_if_matches_crafting;
|
||||
use crate::{Container, InventoryError, WindowType, handle_item_change};
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_world::item::ItemStack;
|
||||
@@ -366,9 +365,9 @@ impl Container for PlayerInventory {
|
||||
let v1 = [self.crafting[0].as_ref(), self.crafting[1].as_ref(), None];
|
||||
let v2 = [self.crafting[2].as_ref(), self.crafting[3].as_ref(), None];
|
||||
let v3 = [const { None }; 3];
|
||||
let together = [v1, v2, v3];
|
||||
let _together = [v1, v2, v3];
|
||||
|
||||
self.crafting_output = check_if_matches_crafting(together);
|
||||
self.crafting_output = None; //check_if_matches_crafting(together);
|
||||
self.crafting.iter().any(|s| s.is_some())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::VarInt;
|
||||
use crate::codec::slot::Slot;
|
||||
use crate::codec::item_stack_seralizer::ItemStackSerializer;
|
||||
|
||||
use pumpkin_data::packet::clientbound::PLAY_CONTAINER_SET_CONTENT;
|
||||
use pumpkin_macros::packet;
|
||||
@@ -10,16 +10,16 @@ use serde::Serialize;
|
||||
pub struct CSetContainerContent<'a> {
|
||||
window_id: VarInt,
|
||||
state_id: VarInt,
|
||||
slot_data: &'a [Slot],
|
||||
carried_item: &'a Slot,
|
||||
slot_data: &'a [ItemStackSerializer<'a>],
|
||||
carried_item: &'a ItemStackSerializer<'a>,
|
||||
}
|
||||
|
||||
impl<'a> CSetContainerContent<'a> {
|
||||
pub fn new(
|
||||
window_id: VarInt,
|
||||
state_id: VarInt,
|
||||
slots: &'a [Slot],
|
||||
carried_item: &'a Slot,
|
||||
slots: &'a [ItemStackSerializer],
|
||||
carried_item: &'a ItemStackSerializer,
|
||||
) -> Self {
|
||||
Self {
|
||||
window_id,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::VarInt;
|
||||
use crate::codec::slot::Slot;
|
||||
use crate::codec::item_stack_seralizer::ItemStackSerializer;
|
||||
|
||||
use pumpkin_data::packet::clientbound::PLAY_CONTAINER_SET_SLOT;
|
||||
use pumpkin_macros::packet;
|
||||
@@ -11,11 +11,16 @@ pub struct CSetContainerSlot<'a> {
|
||||
window_id: i8,
|
||||
state_id: VarInt,
|
||||
slot: i16,
|
||||
slot_data: &'a Slot,
|
||||
slot_data: &'a ItemStackSerializer<'a>,
|
||||
}
|
||||
|
||||
impl<'a> CSetContainerSlot<'a> {
|
||||
pub fn new(window_id: i8, state_id: i32, slot: i16, slot_data: &'a Slot) -> Self {
|
||||
pub fn new(
|
||||
window_id: i8,
|
||||
state_id: i32,
|
||||
slot: i16,
|
||||
slot_data: &'a ItemStackSerializer<'a>,
|
||||
) -> Self {
|
||||
Self {
|
||||
window_id,
|
||||
state_id: state_id.into(),
|
||||
|
||||
@@ -7,17 +7,20 @@ use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
ClientPacket,
|
||||
codec::{slot::Slot, var_int::VarInt},
|
||||
codec::{item_stack_seralizer::ItemStackSerializer, var_int::VarInt},
|
||||
};
|
||||
|
||||
#[packet(PLAY_SET_EQUIPMENT)]
|
||||
pub struct CSetEquipment {
|
||||
entity_id: VarInt,
|
||||
equipment: Vec<(EquipmentSlot, Slot)>,
|
||||
equipment: Vec<(EquipmentSlot, ItemStackSerializer<'static>)>,
|
||||
}
|
||||
|
||||
impl CSetEquipment {
|
||||
pub fn new(entity_id: VarInt, equipment: Vec<(EquipmentSlot, Slot)>) -> Self {
|
||||
pub fn new(
|
||||
entity_id: VarInt,
|
||||
equipment: Vec<(EquipmentSlot, ItemStackSerializer<'static>)>,
|
||||
) -> Self {
|
||||
Self {
|
||||
entity_id,
|
||||
equipment,
|
||||
|
||||
121
pumpkin-protocol/src/codec/item_stack_seralizer.rs
Normal file
121
pumpkin-protocol/src/codec/item_stack_seralizer.rs
Normal file
@@ -0,0 +1,121 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::VarInt;
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_world::item::ItemStack;
|
||||
use serde::{
|
||||
Deserialize, Serialize, Serializer,
|
||||
de::{self, SeqAccess},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ItemStackSerializer<'a>(pub Cow<'a, ItemStack>);
|
||||
|
||||
impl<'de> Deserialize<'de> for ItemStackSerializer<'static> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: de::Deserializer<'de>,
|
||||
{
|
||||
struct Visitor;
|
||||
impl<'de> de::Visitor<'de> for Visitor {
|
||||
type Value = ItemStackSerializer<'static>;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("a valid Slot encoded in a byte sequence")
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
{
|
||||
let item_count = seq
|
||||
.next_element::<VarInt>()?
|
||||
.ok_or(de::Error::custom("Failed to decode VarInt"))?;
|
||||
|
||||
let slot = if item_count.0 == 0 {
|
||||
ItemStackSerializer(Cow::Borrowed(&ItemStack::EMPTY))
|
||||
} else {
|
||||
let item_id = seq
|
||||
.next_element::<VarInt>()?
|
||||
.ok_or(de::Error::custom("No item id VarInt!"))?;
|
||||
let num_components_to_add = seq
|
||||
.next_element::<VarInt>()?
|
||||
.ok_or(de::Error::custom("No component add length VarInt!"))?;
|
||||
let num_components_to_remove = seq
|
||||
.next_element::<VarInt>()?
|
||||
.ok_or(de::Error::custom("No component remove length VarInt!"))?;
|
||||
|
||||
if num_components_to_add.0 != 0 || num_components_to_remove.0 != 0 {
|
||||
return Err(de::Error::custom(
|
||||
"Slot components are currently unsupported",
|
||||
));
|
||||
}
|
||||
|
||||
let item_id: u16 = item_id
|
||||
.0
|
||||
.try_into()
|
||||
.map_err(|_| de::Error::custom("Invalid item id!"))?;
|
||||
|
||||
ItemStackSerializer(Cow::Owned(ItemStack::new(
|
||||
item_count.0 as u8,
|
||||
Item::from_id(item_id).unwrap_or(Item::AIR),
|
||||
)))
|
||||
};
|
||||
|
||||
Ok(slot)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_seq(Visitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ItemStackSerializer<'_> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
if self.0.is_empty() {
|
||||
VarInt(0).serialize(serializer)
|
||||
} else {
|
||||
// TODO: Components
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct NetworkRepr {
|
||||
item_count: VarInt,
|
||||
item_id: VarInt,
|
||||
components_to_add: VarInt,
|
||||
components_to_remove: VarInt,
|
||||
}
|
||||
|
||||
NetworkRepr {
|
||||
item_count: self.0.item_count.into(),
|
||||
item_id: self.0.item.id.into(),
|
||||
components_to_add: 0.into(),
|
||||
components_to_remove: 0.into(),
|
||||
}
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemStackSerializer<'_> {
|
||||
pub fn to_stack(self) -> ItemStack {
|
||||
self.0.into_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ItemStack> for ItemStackSerializer<'_> {
|
||||
fn from(item: ItemStack) -> Self {
|
||||
ItemStackSerializer(Cow::Owned(item))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<ItemStack>> for ItemStackSerializer<'_> {
|
||||
fn from(item: Option<ItemStack>) -> Self {
|
||||
match item {
|
||||
Some(item) => ItemStackSerializer::from(item),
|
||||
None => ItemStackSerializer(Cow::Borrowed(&ItemStack::EMPTY)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
pub mod bit_set;
|
||||
pub mod identifier;
|
||||
pub mod slot;
|
||||
pub mod item_stack_seralizer;
|
||||
pub mod var_int;
|
||||
pub mod var_long;
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
use crate::VarInt;
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_world::item::ItemStack;
|
||||
use serde::{
|
||||
Deserialize, Serialize, Serializer,
|
||||
de::{self, SeqAccess},
|
||||
ser,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Slot {
|
||||
NoItem,
|
||||
Item {
|
||||
// This also handles items on the ground which can have >64 items
|
||||
item_count: u32,
|
||||
item_id: u16,
|
||||
// TODO: Implement item components
|
||||
},
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Slot {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: de::Deserializer<'de>,
|
||||
{
|
||||
struct Visitor;
|
||||
impl<'de> de::Visitor<'de> for Visitor {
|
||||
type Value = Slot;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("a valid Slot encoded in a byte sequence")
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
{
|
||||
let item_count = seq
|
||||
.next_element::<VarInt>()?
|
||||
.ok_or(de::Error::custom("Failed to decode VarInt"))?;
|
||||
|
||||
let slot = if item_count.0 == 0 {
|
||||
Slot::NoItem
|
||||
} else {
|
||||
let item_id = seq
|
||||
.next_element::<VarInt>()?
|
||||
.ok_or(de::Error::custom("No item id VarInt!"))?;
|
||||
let num_components_to_add = seq
|
||||
.next_element::<VarInt>()?
|
||||
.ok_or(de::Error::custom("No component add length VarInt!"))?;
|
||||
let num_components_to_remove = seq
|
||||
.next_element::<VarInt>()?
|
||||
.ok_or(de::Error::custom("No component remove length VarInt!"))?;
|
||||
|
||||
if num_components_to_add.0 != 0 || num_components_to_remove.0 != 0 {
|
||||
return Err(de::Error::custom(
|
||||
"Slot components are currently unsupported",
|
||||
));
|
||||
}
|
||||
|
||||
let item_id: u16 = item_id
|
||||
.0
|
||||
.try_into()
|
||||
.map_err(|_| de::Error::custom("Invalid item id!"))?;
|
||||
|
||||
Slot::Item {
|
||||
// i32 can always be u32
|
||||
item_count: item_count.0 as u32,
|
||||
item_id,
|
||||
}
|
||||
};
|
||||
|
||||
Ok(slot)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_seq(Visitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Slot {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
match self {
|
||||
Self::NoItem => VarInt(0).serialize(serializer),
|
||||
Self::Item {
|
||||
item_count,
|
||||
item_id,
|
||||
} => {
|
||||
// TODO: Components
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct NetworkRepr {
|
||||
item_count: VarInt,
|
||||
item_id: VarInt,
|
||||
components_to_add: VarInt,
|
||||
components_to_remove: VarInt,
|
||||
}
|
||||
|
||||
let item_count: i32 = (*item_count)
|
||||
.try_into()
|
||||
.map_err(|_| ser::Error::custom("Item count overflows an i32!"))?;
|
||||
|
||||
NetworkRepr {
|
||||
item_count: item_count.into(),
|
||||
item_id: (*item_id).into(),
|
||||
components_to_add: 0.into(),
|
||||
components_to_remove: 0.into(),
|
||||
}
|
||||
.serialize(serializer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Slot {
|
||||
pub fn new(item_id: u16, count: u32) -> Self {
|
||||
Self::Item {
|
||||
item_count: count,
|
||||
item_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_stack(self) -> Result<Option<ItemStack>, &'static str> {
|
||||
match self {
|
||||
Self::NoItem => Ok(None),
|
||||
Self::Item {
|
||||
item_count,
|
||||
item_id,
|
||||
} => {
|
||||
let item = Item::from_id(item_id).ok_or("Item id invalid")?;
|
||||
if item_count > item.components.max_stack_size as u32 {
|
||||
Err("Stack item count greater than allowed")
|
||||
} else {
|
||||
let stack = ItemStack {
|
||||
item,
|
||||
// This is checked above
|
||||
item_count: item_count as u8,
|
||||
};
|
||||
Ok(Some(stack))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn empty() -> Self {
|
||||
Self::NoItem
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ItemStack> for Slot {
|
||||
fn from(item: &ItemStack) -> Self {
|
||||
Slot::new(item.item.id, item.item_count as u32)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<&ItemStack>> for Slot {
|
||||
fn from(item: Option<&ItemStack>) -> Self {
|
||||
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,5 +1,5 @@
|
||||
use crate::VarInt;
|
||||
use crate::codec::slot::Slot;
|
||||
use crate::codec::item_stack_seralizer::ItemStackSerializer;
|
||||
use pumpkin_data::packet::serverbound::PLAY_CONTAINER_CLICK;
|
||||
use pumpkin_macros::packet;
|
||||
use serde::de::SeqAccess;
|
||||
@@ -14,8 +14,8 @@ pub struct SClickContainer {
|
||||
pub button: i8,
|
||||
pub mode: SlotActionType,
|
||||
pub length_of_array: VarInt,
|
||||
pub array_of_changed_slots: Vec<(i16, Slot)>,
|
||||
pub carried_item: Slot,
|
||||
pub array_of_changed_slots: Vec<(i16, ItemStackSerializer<'static>)>,
|
||||
pub carried_item: ItemStackSerializer<'static>,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for SClickContainer {
|
||||
@@ -60,13 +60,13 @@ impl<'de> Deserialize<'de> for SClickContainer {
|
||||
.next_element::<i16>()?
|
||||
.ok_or(de::Error::custom("Unable to parse slot"))?;
|
||||
let slot = seq
|
||||
.next_element::<Slot>()?
|
||||
.next_element::<ItemStackSerializer>()?
|
||||
.ok_or(de::Error::custom("Unable to parse item"))?;
|
||||
array_of_changed_slots.push((slot_number, slot));
|
||||
}
|
||||
|
||||
let carried_item = seq
|
||||
.next_element::<Slot>()?
|
||||
.next_element::<ItemStackSerializer>()?
|
||||
.ok_or(de::Error::custom("Failed to decode carried item"))?;
|
||||
|
||||
Ok(SClickContainer {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use pumpkin_data::packet::serverbound::PLAY_SET_CREATIVE_MODE_SLOT;
|
||||
use pumpkin_macros::packet;
|
||||
|
||||
use crate::codec::slot::Slot;
|
||||
use crate::codec::item_stack_seralizer::ItemStackSerializer;
|
||||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
#[packet(PLAY_SET_CREATIVE_MODE_SLOT)]
|
||||
pub struct SSetCreativeSlot {
|
||||
pub slot: i16,
|
||||
pub clicked_item: Slot,
|
||||
pub clicked_item: ItemStackSerializer<'static>,
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ use jukebox_song::JukeboxSong;
|
||||
use paint::Painting;
|
||||
use pig::PigVariant;
|
||||
use pumpkin_protocol::{client::config::RegistryEntry, codec::identifier::Identifier};
|
||||
pub use recipe::{RECIPES, Recipe, RecipeResult, RecipeType, flatten_3x3};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use trim_material::TrimMaterial;
|
||||
use trim_pattern::TrimPattern;
|
||||
@@ -36,7 +35,6 @@ mod instrument;
|
||||
mod jukebox_song;
|
||||
mod paint;
|
||||
mod pig;
|
||||
mod recipe;
|
||||
mod trim_material;
|
||||
mod trim_pattern;
|
||||
mod wolf;
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
mod read;
|
||||
mod recipe_formats;
|
||||
|
||||
pub use read::{Recipe, RecipeResult, RecipeType};
|
||||
use std::sync::LazyLock;
|
||||
pub fn flatten_3x3<T: Clone>(input: [[Option<T>; 3]; 3]) -> [[Option<T>; 3]; 3] {
|
||||
let mut final_output = [const { [const { None }; 3] }; 3];
|
||||
|
||||
let mut row_alignment = 0;
|
||||
let mut column_alignment = 2;
|
||||
|
||||
for (i, row) in input.iter().enumerate() {
|
||||
let mut row_values = [false; 3];
|
||||
for (i, item) in row.iter().enumerate().take(column_alignment + 1) {
|
||||
if item.is_some() {
|
||||
row_values[i] = true;
|
||||
if i < column_alignment {
|
||||
column_alignment = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if i == row_alignment && row_values.iter().all(|val| !val) {
|
||||
row_alignment += 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (i, row) in &mut final_output.iter_mut().enumerate() {
|
||||
let input_row = input.get(i + row_alignment);
|
||||
for (j, item) in row.iter_mut().enumerate() {
|
||||
let val = input_row.and_then(|val| val.get(j + column_alignment));
|
||||
*item = match val {
|
||||
None => None,
|
||||
Some(None) => None,
|
||||
Some(Some(val)) => Some(val.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final_output
|
||||
}
|
||||
pub static RECIPES: LazyLock<Vec<Recipe>> =
|
||||
LazyLock::new(|| serde_json::from_str(include_str!("../../../assets/recipes.json")).unwrap());
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::flatten_3x3;
|
||||
|
||||
#[test]
|
||||
fn row_flatten() {
|
||||
let input = [[None; 3], [None; 3], [Some(()), Some(()), Some(())]];
|
||||
let out = [[Some(()), Some(()), Some(())], [None; 3], [None; 3]];
|
||||
assert_eq!(flatten_3x3(input), out);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn column_flatten() {
|
||||
let one_row_right = [None, None, Some(())];
|
||||
let one_row_left = [Some(()), None, None];
|
||||
let input = [one_row_right, one_row_right, one_row_right];
|
||||
let output = [one_row_left, one_row_left, one_row_left];
|
||||
assert_eq!(flatten_3x3(input), output)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn full_flatten() {
|
||||
let input_1 = [[None; 3], [None; 3], [None, None, Some(())]];
|
||||
let output_1 = [[Some(()), None, None], [None; 3], [None; 3]];
|
||||
let input_2 = [
|
||||
[None; 3],
|
||||
[None, None, Some(())],
|
||||
[None, Some(()), Some(())],
|
||||
];
|
||||
let output_2 = [
|
||||
[None, Some(()), None],
|
||||
[Some(()), Some(()), None],
|
||||
[None; 3],
|
||||
];
|
||||
let input_3 = [[Some(()), None, None], [Some(()), None, None], [None; 3]];
|
||||
assert_eq!(flatten_3x3(input_1), output_1);
|
||||
assert_eq!(flatten_3x3(input_2), output_2);
|
||||
assert_eq!(flatten_3x3(input_3), input_3);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// // This makes sure that all recipes are able to be deserialized properly.
|
||||
// fn check_parsing() {
|
||||
// assert!(!RECIPES.is_empty())
|
||||
// }
|
||||
}
|
||||
@@ -1,534 +0,0 @@
|
||||
use crate::flatten_3x3;
|
||||
use crate::recipe::read::SpecialCraftingType::{
|
||||
ArmorDye, BannerDuplicate, BookCloning, Firework, RepairItem, ShieldDecoration,
|
||||
ShulkerboxColoring, SuspiciousStew, TippedArrow,
|
||||
};
|
||||
use crate::recipe::read::ingredients::Ingredients;
|
||||
use crate::recipe::recipe_formats::{ShapedCrafting, ShapelessCrafting};
|
||||
use pumpkin_util::registry::RegistryEntryList;
|
||||
use serde::de::{Error, MapAccess, Visitor};
|
||||
use serde::{Deserialize, Deserializer, de};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Formatter;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum RecipeType {
|
||||
Blasting,
|
||||
CampfireCooking,
|
||||
Crafting(CraftingType),
|
||||
Smelting,
|
||||
Smithing(SmithingType),
|
||||
Smoking,
|
||||
StoneCutting,
|
||||
}
|
||||
|
||||
impl RecipeType {
|
||||
pub const fn is_shapeless(&self) -> bool {
|
||||
// I have not checked exactly which ones require shape and which don't!
|
||||
!matches!(self, Self::Crafting(CraftingType::Shaped))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for RecipeType {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
use CraftingType::*;
|
||||
use FireworkCrafting::*;
|
||||
use MapCrafting::*;
|
||||
use RecipeType::*;
|
||||
let s = s.trim_start_matches("minecraft:");
|
||||
match s {
|
||||
"blasting" => Ok(Blasting),
|
||||
"campfire_cooking" => Ok(CampfireCooking),
|
||||
"crafting_shaped" => Ok(Crafting(Shaped)),
|
||||
"crafting_shapeless" => Ok(Crafting(Shapeless)),
|
||||
"crafting_special_bookcloning" => Ok(Crafting(Special(BookCloning))),
|
||||
"crafting_special_repairitem" => Ok(Crafting(Special(RepairItem))),
|
||||
"crafting_special_armordye" => Ok(Crafting(Special(ArmorDye))),
|
||||
"crafting_special_firework_rocket" => Ok(Crafting(Special(Firework(Rocket)))),
|
||||
"crafting_special_firework_star" => Ok(Crafting(Special(Firework(Star)))),
|
||||
"crafting_special_firework_star_fade" => Ok(Crafting(Special(Firework(StarFade)))),
|
||||
"crafting_special_suspiciousstew" => Ok(Crafting(Special(SuspiciousStew))),
|
||||
"crafting_special_mapextending" => {
|
||||
Ok(Crafting(Special(SpecialCraftingType::Map(Extending))))
|
||||
}
|
||||
"crafting_special_mapcloning" => {
|
||||
Ok(Crafting(Special(SpecialCraftingType::Map(Cloning))))
|
||||
}
|
||||
"crafting_special_shulkerboxcoloring" => Ok(Crafting(Special(ShulkerboxColoring))),
|
||||
"crafting_special_bannerduplicate" => Ok(Crafting(Special(BannerDuplicate))),
|
||||
"crafting_special_shielddecoration" => Ok(Crafting(Special(ShieldDecoration))),
|
||||
"crafting_special_tippedarrow" => Ok(Crafting(Special(TippedArrow))),
|
||||
"crafting_decorated_pot" => Ok(Crafting(DecoratedPot)),
|
||||
"crafting_transmute" => Ok(Crafting(Transmute)),
|
||||
"smelting" => Ok(Smelting),
|
||||
"smithing" => Ok(Smithing(SmithingType::Normal)),
|
||||
"smithing_trim" => Ok(Smithing(SmithingType::Trim)),
|
||||
"smithing_transform" => Ok(Smithing(SmithingType::Transform)),
|
||||
"smoking" => Ok(Smoking),
|
||||
"stonecutting" => Ok(StoneCutting),
|
||||
_ => Err(format!("Could not find recipe with id: \"{s}\"")),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub mod ingredients {
|
||||
use pumpkin_util::registry::RegistryEntryList;
|
||||
use serde::de::{SeqAccess, Visitor};
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use std::fmt::Formatter;
|
||||
|
||||
pub struct Ingredients(pub Vec<RegistryEntryList>);
|
||||
impl<'de> Deserialize<'de> for Ingredients {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct IngredientsVisitor;
|
||||
impl<'de> Visitor<'de> for IngredientsVisitor {
|
||||
type Value = Ingredients;
|
||||
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
|
||||
write!(formatter, "valid ingredients")
|
||||
}
|
||||
|
||||
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: SeqAccess<'de>,
|
||||
{
|
||||
let mut ingredients = vec![];
|
||||
while let Some(element) = seq.next_element()? {
|
||||
ingredients.push(element)
|
||||
}
|
||||
|
||||
Ok(Ingredients(ingredients))
|
||||
}
|
||||
}
|
||||
deserializer.deserialize_seq(IngredientsVisitor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RecipeResult {
|
||||
Many {
|
||||
count: u8,
|
||||
id: String,
|
||||
// TODO
|
||||
components: Option<serde_json::Value>,
|
||||
},
|
||||
Single {
|
||||
id: String,
|
||||
// TODO
|
||||
components: Option<serde_json::Value>,
|
||||
},
|
||||
Special,
|
||||
}
|
||||
|
||||
impl RecipeResult {
|
||||
pub fn id(&self) -> &str {
|
||||
match self {
|
||||
Self::Many { id, .. } | Self::Single { id, .. } => id,
|
||||
Self::Special => "minecraft:air",
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'de> Deserialize<'de> for RecipeResult {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
#[serde(field_identifier, rename_all = "lowercase")]
|
||||
enum Fields {
|
||||
Count,
|
||||
Id,
|
||||
Components,
|
||||
}
|
||||
struct ResultVisitor;
|
||||
impl<'de> Visitor<'de> for ResultVisitor {
|
||||
type Value = RecipeResult;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
|
||||
write!(formatter, "valid recipe result")
|
||||
}
|
||||
|
||||
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: MapAccess<'de>,
|
||||
{
|
||||
let mut id: Option<&str> = None;
|
||||
let mut count: Option<u8> = None;
|
||||
let mut components: Option<serde_json::Value> = None;
|
||||
while let Some(key) = map.next_key()? {
|
||||
match key {
|
||||
Fields::Id => visit_option(&mut map, &mut id, "id")?,
|
||||
Fields::Count => visit_option(&mut map, &mut count, "count")?,
|
||||
Fields::Components => {
|
||||
visit_option(&mut map, &mut components, "components")?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let id = id
|
||||
.ok_or_else(|| de::Error::missing_field("id"))?
|
||||
.to_string();
|
||||
if let Some(count) = count {
|
||||
Ok(RecipeResult::Many {
|
||||
id,
|
||||
count,
|
||||
components,
|
||||
})
|
||||
} else {
|
||||
Ok(RecipeResult::Single { id, components })
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
Ok(RecipeResult::Single {
|
||||
id: v.to_string(),
|
||||
components: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate putting type constraint on `RecipeResult`, because only Crafting Transmute can call visit_str
|
||||
deserializer.deserialize_any(ResultVisitor)
|
||||
}
|
||||
}
|
||||
pub struct RecipeKeys(pub(super) HashMap<char, RegistryEntryList>);
|
||||
|
||||
impl<'de> Deserialize<'de> for RecipeKeys {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct KeyVisitor;
|
||||
impl<'de> Visitor<'de> for KeyVisitor {
|
||||
type Value = HashMap<char, RegistryEntryList>;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
|
||||
write!(formatter, "existing key inside recipe")
|
||||
}
|
||||
|
||||
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: MapAccess<'de>,
|
||||
{
|
||||
let mut return_map = HashMap::new();
|
||||
while let Some(next) = map.next_key()? {
|
||||
let test: &str = next;
|
||||
let c: char = test.chars().next().unwrap();
|
||||
let ingredient_type: RegistryEntryList = map.next_value()?;
|
||||
return_map.insert(c, ingredient_type);
|
||||
}
|
||||
Ok(return_map)
|
||||
}
|
||||
}
|
||||
deserializer.deserialize_map(KeyVisitor).map(Self)
|
||||
}
|
||||
}
|
||||
impl<'de> Deserialize<'de> for Recipe {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
#[serde(field_identifier, rename_all = "lowercase")]
|
||||
enum Fields {
|
||||
Type,
|
||||
Category,
|
||||
Group,
|
||||
Key,
|
||||
Pattern,
|
||||
Result,
|
||||
Ingredients,
|
||||
Ingredient,
|
||||
|
||||
// Only exists sometimes, at least on Shaped crafting
|
||||
#[serde(rename = "show_notification")]
|
||||
ShowNotification,
|
||||
|
||||
// Armor
|
||||
Addition,
|
||||
Base,
|
||||
Template,
|
||||
|
||||
// Smelting
|
||||
CookingTime,
|
||||
Experience,
|
||||
|
||||
// Transmute
|
||||
Input,
|
||||
Material,
|
||||
}
|
||||
|
||||
struct RecipeVisitor;
|
||||
impl<'de> Visitor<'de> for RecipeVisitor {
|
||||
type Value = Recipe;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
|
||||
write!(formatter, "valid recipe")
|
||||
}
|
||||
|
||||
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
|
||||
where
|
||||
A: MapAccess<'de>,
|
||||
{
|
||||
let mut recipe_type: Option<&str> = None;
|
||||
let mut category: Option<&str> = None;
|
||||
let mut group: Option<&str> = None;
|
||||
let mut keys: Option<RecipeKeys> = None;
|
||||
let mut pattern: Option<Vec<&str>> = None;
|
||||
let mut result: Option<RecipeResult> = None;
|
||||
let mut ingredients: Option<Ingredients> = None;
|
||||
let mut ingredient: Option<RegistryEntryList> = None;
|
||||
let mut addition: Option<RegistryEntryList> = None;
|
||||
let mut base: Option<RegistryEntryList> = None;
|
||||
let mut template: Option<RegistryEntryList> = None;
|
||||
let mut cookingtime: Option<u16> = None;
|
||||
let mut experience: Option<f32> = None;
|
||||
let mut show_notification: Option<bool> = None;
|
||||
let mut transmute_input: Option<RegistryEntryList> = None;
|
||||
let mut transmute_material: Option<RegistryEntryList> = None;
|
||||
while let Some(key) = map.next_key()? {
|
||||
(match key {
|
||||
Fields::Type => visit_option(&mut map, &mut recipe_type, "type"),
|
||||
Fields::Group => visit_option(&mut map, &mut group, "group"),
|
||||
Fields::Category => visit_option(&mut map, &mut category, "group"),
|
||||
Fields::Key => visit_option(&mut map, &mut keys, "key"),
|
||||
Fields::Pattern => visit_option(&mut map, &mut pattern, "pattern"),
|
||||
Fields::Result => visit_option(&mut map, &mut result, "result"),
|
||||
Fields::Ingredients => {
|
||||
visit_option(&mut map, &mut ingredients, "ingredients")
|
||||
}
|
||||
Fields::Ingredient => visit_option(&mut map, &mut ingredient, "ingredient"),
|
||||
Fields::Addition => visit_option(&mut map, &mut addition, "addition"),
|
||||
Fields::Base => visit_option(&mut map, &mut base, "base"),
|
||||
Fields::Template => visit_option(&mut map, &mut template, "template"),
|
||||
Fields::CookingTime => {
|
||||
visit_option(&mut map, &mut cookingtime, "cookingtime")
|
||||
}
|
||||
Fields::Experience => visit_option(&mut map, &mut experience, "experience"),
|
||||
Fields::ShowNotification => {
|
||||
visit_option(&mut map, &mut show_notification, "show_notification")
|
||||
}
|
||||
Fields::Input => visit_option(&mut map, &mut transmute_input, "input"),
|
||||
Fields::Material => {
|
||||
visit_option(&mut map, &mut transmute_material, "material")
|
||||
}
|
||||
})?
|
||||
}
|
||||
|
||||
let recipe_type: RecipeType = recipe_type
|
||||
.ok_or_else(|| de::Error::missing_field("type"))?
|
||||
.parse()
|
||||
.unwrap();
|
||||
|
||||
let result = match recipe_type {
|
||||
RecipeType::Crafting(CraftingType::Special(_))
|
||||
| RecipeType::Crafting(CraftingType::DecoratedPot)
|
||||
| RecipeType::Smithing(_) => RecipeResult::Special,
|
||||
_ => result.ok_or_else(|| de::Error::missing_field("result"))?,
|
||||
};
|
||||
|
||||
match recipe_type {
|
||||
RecipeType::Crafting(CraftingType::Shaped) => {
|
||||
let mut rows = [[None; 3], [None; 3], [None; 3]];
|
||||
pattern
|
||||
.ok_or_else(|| de::Error::missing_field("pattern"))?
|
||||
.into_iter()
|
||||
.map(|s| {
|
||||
let mut chars = [None; 3];
|
||||
s.chars()
|
||||
.enumerate()
|
||||
.for_each(|(i, char)| chars[i] = Some(char));
|
||||
chars
|
||||
})
|
||||
.enumerate()
|
||||
.for_each(|(i, row)| rows[i] = row);
|
||||
let keys = keys.ok_or_else(|| de::Error::missing_field("keys"))?;
|
||||
Ok(Recipe::from(ShapedCrafting::new(keys, rows, result)))
|
||||
}
|
||||
RecipeType::Crafting(CraftingType::Shapeless) => {
|
||||
let ingredients =
|
||||
ingredients.ok_or_else(|| de::Error::missing_field("ingredients"))?;
|
||||
Ok(Recipe::from(ShapelessCrafting::new(ingredients.0, result)))
|
||||
}
|
||||
RecipeType::Crafting(CraftingType::Special(_)) => Ok(Recipe::from(Test {
|
||||
recipe_type,
|
||||
result,
|
||||
})),
|
||||
RecipeType::Crafting(CraftingType::DecoratedPot) => Ok(Recipe::from(Test {
|
||||
recipe_type,
|
||||
result,
|
||||
})),
|
||||
RecipeType::Crafting(CraftingType::Transmute) => {
|
||||
let _input =
|
||||
transmute_input.ok_or_else(|| de::Error::missing_field("input"))?;
|
||||
// Maybe also has material
|
||||
Ok(Recipe::from(Test {
|
||||
recipe_type,
|
||||
result,
|
||||
}))
|
||||
}
|
||||
RecipeType::Smithing(_) => Ok(Recipe::from(Test {
|
||||
recipe_type,
|
||||
result: RecipeResult::Special,
|
||||
})),
|
||||
_ => Ok(Recipe::from(Test {
|
||||
recipe_type,
|
||||
result,
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const FIELDS: &[&str] = &[
|
||||
"type",
|
||||
"category",
|
||||
"group",
|
||||
"key",
|
||||
"pattern",
|
||||
"result",
|
||||
"ingredients",
|
||||
"ingredient",
|
||||
"addition",
|
||||
"base",
|
||||
"template",
|
||||
"cookingtime",
|
||||
"experience",
|
||||
"show_notification",
|
||||
"input",
|
||||
"material",
|
||||
];
|
||||
|
||||
deserializer.deserialize_struct("Recipe", FIELDS, RecipeVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn visit_option<'de, T: Deserialize<'de>, Map: MapAccess<'de>>(
|
||||
map: &mut Map,
|
||||
option: &mut Option<T>,
|
||||
field: &'static str,
|
||||
) -> Result<(), Map::Error> {
|
||||
match option {
|
||||
Some(_) => Err(<Map as MapAccess>::Error::duplicate_field(field)),
|
||||
None => {
|
||||
*option = Some(map.next_value()?);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Recipe {
|
||||
pub recipe_type: RecipeType,
|
||||
pattern: Vec<[[Option<RegistryEntryList>; 3]; 3]>,
|
||||
result: RecipeResult,
|
||||
}
|
||||
|
||||
impl Recipe {
|
||||
pub fn pattern(&self) -> &[[[Option<RegistryEntryList>; 3]; 3]] {
|
||||
&self.pattern
|
||||
}
|
||||
|
||||
pub fn result(&self) -> &RecipeResult {
|
||||
&self.result
|
||||
}
|
||||
|
||||
pub fn implemented(&self) -> bool {
|
||||
match self.recipe_type {
|
||||
RecipeType::Crafting(crafting_type) => {
|
||||
matches!(
|
||||
crafting_type,
|
||||
CraftingType::Shapeless | CraftingType::Shaped
|
||||
)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Test {
|
||||
recipe_type: RecipeType,
|
||||
result: RecipeResult,
|
||||
}
|
||||
impl RecipeTrait for Test {
|
||||
fn recipe_type(&self) -> RecipeType {
|
||||
self.recipe_type
|
||||
}
|
||||
|
||||
fn pattern(&self) -> Vec<[[Option<RegistryEntryList>; 3]; 3]> {
|
||||
vec![[const { [const { None }; 3] }; 3]]
|
||||
}
|
||||
|
||||
fn result(self) -> RecipeResult {
|
||||
self.result
|
||||
}
|
||||
}
|
||||
impl<T: RecipeTrait> From<T> for Recipe {
|
||||
fn from(recipe_type: T) -> Self {
|
||||
recipe_type.to_recipe()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RecipeTrait: Sized {
|
||||
fn recipe_type(&self) -> RecipeType;
|
||||
|
||||
fn pattern(&self) -> Vec<[[Option<RegistryEntryList>; 3]; 3]>;
|
||||
|
||||
fn result(self) -> RecipeResult;
|
||||
|
||||
fn to_recipe(self) -> Recipe {
|
||||
Recipe {
|
||||
recipe_type: self.recipe_type(),
|
||||
pattern: self.pattern().into_iter().map(flatten_3x3).collect(),
|
||||
result: self.result(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum CraftingType {
|
||||
Shapeless,
|
||||
Shaped,
|
||||
Special(SpecialCraftingType),
|
||||
DecoratedPot,
|
||||
Transmute,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum SpecialCraftingType {
|
||||
BookCloning,
|
||||
RepairItem,
|
||||
ArmorDye,
|
||||
Firework(FireworkCrafting),
|
||||
SuspiciousStew,
|
||||
ShulkerboxColoring,
|
||||
Map(MapCrafting),
|
||||
BannerDuplicate,
|
||||
ShieldDecoration,
|
||||
TippedArrow,
|
||||
}
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum FireworkCrafting {
|
||||
Rocket,
|
||||
Star,
|
||||
StarFade,
|
||||
}
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum MapCrafting {
|
||||
Extending,
|
||||
Cloning,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum SmithingType {
|
||||
Normal,
|
||||
Trim,
|
||||
Transform,
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
use pumpkin_util::registry::RegistryEntryList;
|
||||
|
||||
use super::super::recipe::RecipeType;
|
||||
use super::read::{CraftingType, RecipeKeys, RecipeResult, RecipeTrait};
|
||||
pub struct ShapedCrafting {
|
||||
keys: RecipeKeys,
|
||||
pattern: [[Option<char>; 3]; 3],
|
||||
output: RecipeResult,
|
||||
}
|
||||
impl RecipeKeys {
|
||||
fn pattern_to_thing(
|
||||
&self,
|
||||
pattern: [[Option<char>; 3]; 3],
|
||||
) -> [[Option<RegistryEntryList>; 3]; 3] {
|
||||
pattern
|
||||
.map(|row| row.map(|maybe_char| maybe_char.and_then(|char| self.0.get(&char).cloned())))
|
||||
}
|
||||
}
|
||||
impl ShapedCrafting {
|
||||
pub fn new(keys: RecipeKeys, pattern: [[Option<char>; 3]; 3], output: RecipeResult) -> Self {
|
||||
Self {
|
||||
keys,
|
||||
pattern,
|
||||
output,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RecipeTrait for ShapedCrafting {
|
||||
fn recipe_type(&self) -> RecipeType {
|
||||
RecipeType::Crafting(CraftingType::Shaped)
|
||||
}
|
||||
|
||||
fn pattern(&self) -> Vec<[[Option<RegistryEntryList>; 3]; 3]> {
|
||||
vec![self.keys.pattern_to_thing(self.pattern)]
|
||||
}
|
||||
|
||||
fn result(self) -> RecipeResult {
|
||||
self.output
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ShapelessCrafting {
|
||||
ingredients: Vec<RegistryEntryList>,
|
||||
output: RecipeResult,
|
||||
}
|
||||
|
||||
impl ShapelessCrafting {
|
||||
pub(crate) fn new(ingredients: Vec<RegistryEntryList>, output: RecipeResult) -> Self {
|
||||
Self {
|
||||
ingredients,
|
||||
output,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RecipeTrait for ShapelessCrafting {
|
||||
fn recipe_type(&self) -> RecipeType {
|
||||
RecipeType::Crafting(CraftingType::Shapeless)
|
||||
}
|
||||
|
||||
// Iterating over all permutations is cheaper than resolving and iterating over all tags when trying to check if a recipe
|
||||
// is correct. Otherwise, we would have to backtrack and check for each item in the recipe input, which tags they are inside,
|
||||
// and then sort those permutations.
|
||||
fn pattern(&self) -> Vec<[[std::option::Option<RegistryEntryList>; 3]; 3]> {
|
||||
vec![
|
||||
self.ingredients.clone(), //.permutations(self.ingredients.len())
|
||||
]
|
||||
.into_iter()
|
||||
.map(|thing| {
|
||||
let mut v1 = [const { None }; 3];
|
||||
let mut v2 = [const { None }; 3];
|
||||
let mut v3 = [const { None }; 3];
|
||||
for (i, thing) in thing.into_iter().enumerate() {
|
||||
if i < 3 {
|
||||
v1[i] = Some(thing)
|
||||
} else if i < 6 {
|
||||
v2[i - 3] = Some(thing)
|
||||
} else {
|
||||
v3[i - 6] = Some(thing)
|
||||
}
|
||||
}
|
||||
|
||||
[v1, v2, v3]
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn result(self) -> RecipeResult {
|
||||
self.output
|
||||
}
|
||||
}
|
||||
@@ -28,10 +28,56 @@ impl PartialEq for ItemStack {
|
||||
}
|
||||
|
||||
impl ItemStack {
|
||||
pub const EMPTY: ItemStack = ItemStack {
|
||||
item_count: 0,
|
||||
item: Item::AIR,
|
||||
};
|
||||
|
||||
pub fn new(item_count: u8, item: Item) -> Self {
|
||||
Self { item_count, item }
|
||||
}
|
||||
|
||||
pub fn get_max_stack_size(&self) -> u8 {
|
||||
self.item.components.max_stack_size
|
||||
}
|
||||
|
||||
pub fn get_item(&self) -> &Item {
|
||||
if self.is_empty() {
|
||||
&Item::AIR
|
||||
} else {
|
||||
&self.item
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.item_count == 0 || self.item.id == Item::AIR.id
|
||||
}
|
||||
|
||||
pub fn split(&mut self, amount: u8) -> Self {
|
||||
let min = amount.min(self.item_count);
|
||||
let stack = self.copy_with_count(min);
|
||||
self.decrement(min);
|
||||
stack
|
||||
}
|
||||
|
||||
pub fn copy_with_count(&self, count: u8) -> Self {
|
||||
let mut stack = self.clone();
|
||||
stack.item_count = count;
|
||||
stack
|
||||
}
|
||||
|
||||
pub fn decrement(&mut self, amount: u8) {
|
||||
self.item_count = self.item_count.saturating_sub(amount);
|
||||
}
|
||||
|
||||
pub fn increment(&mut self, amount: u8) {
|
||||
self.item_count = self.item_count.saturating_add(amount);
|
||||
}
|
||||
|
||||
pub fn are_items_and_components_equal(&self, other: &Self) -> bool {
|
||||
self.item == other.item //TODO: && self.item.components == other.item.components
|
||||
}
|
||||
|
||||
/// Determines the mining speed for a block based on tool rules.
|
||||
/// Direct matches return immediately, tagged blocks are checked separately.
|
||||
/// If no match is found, returns the tool's default mining speed or `1.0`.
|
||||
|
||||
@@ -4,7 +4,7 @@ use async_trait::async_trait;
|
||||
use pumpkin_data::{damage::DamageType, item::Item};
|
||||
use pumpkin_protocol::{
|
||||
client::play::{CTakeItemEntity, MetaDataType, Metadata},
|
||||
codec::slot::Slot,
|
||||
codec::item_stack_seralizer::ItemStackSerializer,
|
||||
};
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_world::item::ItemStack;
|
||||
@@ -16,11 +16,10 @@ use super::{Entity, EntityBase, living::LivingEntity, player::Player};
|
||||
|
||||
pub struct ItemEntity {
|
||||
entity: Entity,
|
||||
item: Item,
|
||||
item_age: AtomicU32,
|
||||
// These cannot be atomic values because we mutate their state based on what they are; we run
|
||||
// into the ABA problem
|
||||
item_count: Mutex<u32>,
|
||||
item_stack: Mutex<ItemStack>,
|
||||
pickup_delay: Mutex<u8>,
|
||||
}
|
||||
|
||||
@@ -36,16 +35,21 @@ impl ItemEntity {
|
||||
entity.yaw.store(rand::random::<f32>() * 360.0);
|
||||
Self {
|
||||
entity,
|
||||
item: Item::from_id(item_id).expect("We passed a bad item id into ItemEntity"),
|
||||
item_stack: Mutex::new(ItemStack::new(
|
||||
count as u8,
|
||||
Item::from_id(item_id).expect("We passed a bad item id into ItemEntity"),
|
||||
)),
|
||||
item_age: AtomicU32::new(0),
|
||||
item_count: Mutex::new(count),
|
||||
pickup_delay: Mutex::new(10), // Vanilla pickup delay is 10 ticks
|
||||
}
|
||||
}
|
||||
pub async fn send_meta_packet(&self) {
|
||||
let slot = Slot::new(self.item.id, *self.item_count.lock().await);
|
||||
self.entity
|
||||
.send_meta_data(&[Metadata::new(8, MetaDataType::ItemStack, &slot)])
|
||||
.send_meta_data(&[Metadata::new(
|
||||
8,
|
||||
MetaDataType::ItemStack,
|
||||
&ItemStackSerializer::from(self.item_stack.lock().await.clone()),
|
||||
)])
|
||||
.await;
|
||||
}
|
||||
}
|
||||
@@ -81,10 +85,11 @@ impl EntityBase for ItemEntity {
|
||||
let mut total_pick_up = 0;
|
||||
let mut slot_updates = Vec::new();
|
||||
let remove_entity = {
|
||||
let mut stack_size = self.item_count.lock().await;
|
||||
let max_stack = self.item.components.max_stack_size;
|
||||
while *stack_size > 0 {
|
||||
if let Some(slot) = inv.get_pickup_item_slot(self.item.id) {
|
||||
let item_stack = self.item_stack.lock().await.clone();
|
||||
let mut stack_size = item_stack.item_count;
|
||||
let max_stack = item_stack.item.components.max_stack_size;
|
||||
while stack_size > 0 {
|
||||
if let Some(slot) = inv.get_pickup_item_slot(item_stack.item.id) {
|
||||
// Fill the inventory while there are items in the stack and space in the inventory
|
||||
let maybe_stack = inv
|
||||
.get_slot(slot)
|
||||
@@ -96,7 +101,7 @@ impl EntityBase for ItemEntity {
|
||||
// This is bounded to `u8::MAX`
|
||||
let amount_to_fill = u32::from(max_stack - existing_stack.item_count);
|
||||
// This is also bounded to `u8::MAX` since `amount_to_fill` is max `u8::MAX`
|
||||
let amount_to_add = amount_to_fill.min(*stack_size);
|
||||
let amount_to_add = amount_to_fill.min(u32::from(stack_size));
|
||||
// Therefore this is safe
|
||||
|
||||
// Update referenced stack so next call to `get_pickup_item_slot` is
|
||||
@@ -105,7 +110,7 @@ impl EntityBase for ItemEntity {
|
||||
total_pick_up += amount_to_add;
|
||||
|
||||
debug_assert!(amount_to_add > 0);
|
||||
*stack_size -= amount_to_add;
|
||||
stack_size = stack_size.saturating_sub(amount_to_add as u8);
|
||||
|
||||
slot_updates.push((slot, existing_stack.clone()));
|
||||
} else {
|
||||
@@ -114,20 +119,13 @@ impl EntityBase for ItemEntity {
|
||||
// This is bounded to `u8::MAX`
|
||||
let amount_to_fill = u32::from(max_stack);
|
||||
// This is also bounded to `u8::MAX` since `amount_to_fill` is max `u8::MAX`
|
||||
let amount_to_add = amount_to_fill.min(*stack_size);
|
||||
let amount_to_add = amount_to_fill.min(u32::from(stack_size));
|
||||
total_pick_up += amount_to_add;
|
||||
|
||||
debug_assert!(amount_to_add > 0);
|
||||
*stack_size -= amount_to_add;
|
||||
stack_size = stack_size.saturating_sub(amount_to_add as u8);
|
||||
|
||||
// Therefore this is safe
|
||||
let item_stack = ItemStack::new(amount_to_add as u8, self.item.clone());
|
||||
|
||||
// Update referenced stack so next call to `get_pickup_item_slot` is
|
||||
// correct
|
||||
*maybe_stack = Some(item_stack.clone());
|
||||
|
||||
slot_updates.push((slot, item_stack));
|
||||
slot_updates.push((slot, self.item_stack.lock().await.clone()));
|
||||
}
|
||||
} else {
|
||||
// We can't pick anything else up
|
||||
@@ -135,7 +133,7 @@ impl EntityBase for ItemEntity {
|
||||
}
|
||||
}
|
||||
|
||||
*stack_size == 0
|
||||
stack_size == 0
|
||||
};
|
||||
|
||||
if total_pick_up > 0 {
|
||||
|
||||
@@ -12,7 +12,7 @@ use pumpkin_protocol::client::play::{CHurtAnimation, CTakeItemEntity};
|
||||
use pumpkin_protocol::codec::var_int::VarInt;
|
||||
use pumpkin_protocol::{
|
||||
client::play::{CDamageEvent, CSetEquipment, EquipmentSlot, MetaDataType, Metadata},
|
||||
codec::slot::Slot,
|
||||
codec::item_stack_seralizer::ItemStackSerializer,
|
||||
};
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_world::item::ItemStack;
|
||||
@@ -55,9 +55,9 @@ impl LivingEntity {
|
||||
}
|
||||
|
||||
pub async fn send_equipment_changes(&self, equipment: &[(EquipmentSlot, ItemStack)]) {
|
||||
let equipment: Vec<(EquipmentSlot, Slot)> = equipment
|
||||
let equipment: Vec<(EquipmentSlot, ItemStackSerializer)> = equipment
|
||||
.iter()
|
||||
.map(|(slot, stack)| (*slot, Slot::from(stack)))
|
||||
.map(|(slot, stack)| (*slot, ItemStackSerializer::from(stack.clone())))
|
||||
.collect();
|
||||
self.entity
|
||||
.world
|
||||
|
||||
@@ -13,7 +13,7 @@ use pumpkin_inventory::{InventoryError, OptionallyCombinedContainer, container_c
|
||||
use pumpkin_protocol::client::play::{
|
||||
CCloseContainer, COpenScreen, CSetContainerContent, CSetContainerProperty, CSetContainerSlot,
|
||||
};
|
||||
use pumpkin_protocol::codec::slot::Slot;
|
||||
use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer;
|
||||
use pumpkin_protocol::codec::var_int::VarInt;
|
||||
use pumpkin_protocol::server::play::SClickContainer;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
@@ -61,16 +61,17 @@ impl Player {
|
||||
|
||||
let container = OptionallyCombinedContainer::new(&mut inventory, container);
|
||||
|
||||
let slots: Vec<Slot> = container
|
||||
let slots: Vec<ItemStackSerializer> = container
|
||||
.all_slots_ref()
|
||||
.into_iter()
|
||||
.map(Slot::from)
|
||||
.map(|i| ItemStackSerializer::from(i.unwrap_or(&ItemStack::EMPTY).clone()))
|
||||
.collect();
|
||||
|
||||
let carried_item = self.carried_item.lock().await;
|
||||
let carried_item = carried_item
|
||||
.as_ref()
|
||||
.map_or_else(Slot::empty, std::convert::Into::into);
|
||||
let carried_item = carried_item.as_ref().map_or_else(
|
||||
|| ItemStackSerializer::from(ItemStack::EMPTY.clone()),
|
||||
|item| ItemStackSerializer::from(item.clone()),
|
||||
);
|
||||
|
||||
inventory.increment_state_id();
|
||||
let packet = CSetContainerContent::new(
|
||||
@@ -192,7 +193,7 @@ impl Player {
|
||||
let combined_container =
|
||||
OptionallyCombinedContainer::new(&mut inventory, Some(&mut opened_container));
|
||||
if let Some(slot) = combined_container.get_slot_excluding_inventory(slot_index) {
|
||||
let slot = Slot::from(slot);
|
||||
let slot = ItemStackSerializer::from(slot.cloned());
|
||||
drop(opened_container);
|
||||
self.send_container_changes(server, slot_index, slot)
|
||||
.await?;
|
||||
@@ -211,7 +212,7 @@ impl Player {
|
||||
) -> Result<(), InventoryError> {
|
||||
// TODO: this will not update hotbar when server admin is peeking
|
||||
// TODO: check and iterate over all players in player inventory
|
||||
let slot = Slot::from(item_stack);
|
||||
let slot = ItemStackSerializer::from(item_stack.cloned());
|
||||
*state_id += 1;
|
||||
let packet = CSetContainerSlot::new(0, *state_id as i32, slot_index, &slot);
|
||||
self.client.enqueue_packet(&packet).await;
|
||||
@@ -624,7 +625,7 @@ impl Player {
|
||||
&self,
|
||||
server: &Server,
|
||||
slot_index: usize,
|
||||
slot: Slot,
|
||||
slot: ItemStackSerializer<'_>,
|
||||
) -> Result<(), InventoryError> {
|
||||
for player in self.get_current_players_in_container(server).await {
|
||||
let mut inventory = player.inventory().lock().await;
|
||||
|
||||
@@ -38,7 +38,7 @@ use pumpkin_protocol::client::play::{
|
||||
CBlockEntityData, CBlockUpdate, COpenSignEditor, CPlayerInfoUpdate, CPlayerPosition,
|
||||
CSetContainerSlot, CSetHeldItem, CSystemChatMessage, EquipmentSlot, InitChat, PlayerAction,
|
||||
};
|
||||
use pumpkin_protocol::codec::slot::Slot;
|
||||
use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer;
|
||||
use pumpkin_protocol::codec::var_int::VarInt;
|
||||
use pumpkin_protocol::server::play::{
|
||||
SChunkBatch, SCookieResponse as SPCookieResponse, SPlayerSession, SUpdateSign,
|
||||
@@ -554,7 +554,7 @@ impl Player {
|
||||
stack: ItemStack,
|
||||
) {
|
||||
inventory.increment_state_id();
|
||||
let slot_data = Slot::from(&stack);
|
||||
let slot_data = ItemStackSerializer::from(stack.clone());
|
||||
if let Err(err) = inventory.set_slot(slot, Some(stack), false) {
|
||||
log::error!("Pick item set slot error: {err}");
|
||||
} else {
|
||||
@@ -1511,13 +1511,13 @@ impl Player {
|
||||
}
|
||||
let valid_slot = packet.slot >= 0 && packet.slot as usize <= SLOT_OFFHAND;
|
||||
// TODO: Handle error
|
||||
let item_stack = packet.clicked_item.to_stack().unwrap();
|
||||
let item_stack = packet.clicked_item.to_stack();
|
||||
if valid_slot {
|
||||
self.inventory()
|
||||
.lock()
|
||||
.await
|
||||
.set_slot(packet.slot as usize, item_stack, true)?;
|
||||
} else if let Some(item_stack) = item_stack {
|
||||
.set_slot(packet.slot as usize, Some(item_stack), true)?;
|
||||
} else {
|
||||
// Item drop
|
||||
self.drop_item(item_stack.item.id, u32::from(item_stack.item_count))
|
||||
.await;
|
||||
|
||||
Reference in New Issue
Block a user