Skip to content

implement item durability #714

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions pumpkin-data/build/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ pub struct ItemComponents {
pub max_damage: Option<u16>,
#[serde(rename = "minecraft:attribute_modifiers")]
pub attribute_modifiers: Option<Vec<Modifier>>,
#[serde(rename = "minecraft:break_sound")]
pub break_sound: Option<String>,
#[serde(rename = "minecraft:tool")]
pub tool: Option<ToolComponent>,
}
Expand All @@ -53,6 +55,15 @@ impl ToTokens for ItemComponents {
None => quote! { None },
};

let break_sound = match self.break_sound.clone() {
Some(text) => {
let text = text.replace("minecraft:", "");
let break_sound = LitStr::new(&text, Span::call_site());
quote! { Some(#break_sound) }
}
None => quote! { None },
};

let damage = match self.damage {
Some(d) => {
let damage_lit = LitInt::new(&d.to_string(), Span::call_site());
Expand Down Expand Up @@ -153,6 +164,7 @@ impl ToTokens for ItemComponents {
tokens.extend(quote! {
ItemComponents {
item_name: #item_name,
break_sound: #break_sound,
max_stack_size: #max_stack_size,
jukebox_playable: #jukebox_playable,
damage: #damage,
Expand Down Expand Up @@ -252,6 +264,7 @@ pub(crate) fn build() -> TokenStream {

#[derive(Clone, Copy, Debug)]
pub struct ItemComponents {
pub break_sound: Option<&'static str>,
pub item_name: Option<&'static str>,
pub max_stack_size: u8,
pub jukebox_playable: Option<&'static str>,
Expand Down Expand Up @@ -315,6 +328,22 @@ pub(crate) fn build() -> TokenStream {
_ => None
}
}

pub fn has_property_changed(&self, field: &str) -> bool {
let components = &self.components;

if let Some(original_item) = Item::from_id(self.id) {
let original_components = &original_item.components;

return match field {
"damage" => original_components.damage != components.damage,
_ => false
};
}

false
}

}

impl Tagable for Item {
Expand Down
2 changes: 1 addition & 1 deletion pumpkin-protocol/src/client/play/set_container_content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use pumpkin_data::packet::clientbound::PLAY_CONTAINER_SET_CONTENT;
use pumpkin_macros::packet;
use serde::Serialize;

#[derive(Serialize)]
#[derive(Clone, Debug, Serialize)]
#[packet(PLAY_CONTAINER_SET_CONTENT)]
pub struct CSetContainerContent<'a> {
window_id: VarInt,
Expand Down
2 changes: 1 addition & 1 deletion pumpkin-protocol/src/client/play/set_container_slot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use pumpkin_data::packet::clientbound::PLAY_CONTAINER_SET_SLOT;
use pumpkin_macros::packet;
use serde::Serialize;

#[derive(Serialize)]
#[derive(Clone, Debug, Serialize)]
#[packet(PLAY_CONTAINER_SET_SLOT)]
pub struct CSetContainerSlot<'a> {
window_id: i8,
Expand Down
82 changes: 77 additions & 5 deletions pumpkin-protocol/src/codec/slot.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,64 @@
use crate::VarInt;
use pumpkin_data::item::Item;
use pumpkin_util::constants::structured_component_constants::{
DAMAGE, DAMAGE_COMPONENT_TYPE, MAX_DAMAGE, MAX_DAMAGE_COMPONENT_TYPE,
};
use pumpkin_world::item::ItemStack;
use serde::ser::SerializeSeq;
use serde::{
Deserialize, Serialize, Serializer,
de::{self, SeqAccess},
};

// TODO: add other types for this enum. I don't think we need a trait.
//
// If a plugin wants to send their own data, they can use the custom data
// component_type that uses the NBT type.
//
// Refer to https://minecraft.wiki/w/Minecraft_Wiki:Projects/wiki.vg_merge/Slot_Data
#[derive(Debug, Clone)]
pub enum ComponentData {
VarInt(VarInt),
}

impl Serialize for ComponentData {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
ComponentData::VarInt(var_int) => var_int.serialize(serializer),
}
}
}

#[derive(Debug, Clone)]
struct StructuredComponent {
component_type: VarInt,
value: ComponentData,
}

impl Serialize for StructuredComponent {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut s = serializer.serialize_seq(Some(2))?;

s.serialize_element(&self.component_type)?;
s.serialize_element(&self.value)?;

s.end()
}
}

#[derive(Debug, Clone)]
pub struct Slot {
pub item_count: VarInt,
item_id: Option<VarInt>,
num_components_to_add: Option<VarInt>,
num_components_to_remove: Option<VarInt>,
components_to_add: Option<Vec<(VarInt, ())>>, // The second type depends on the varint
components_to_add: Option<Vec<StructuredComponent>>,
components_to_remove: Option<Vec<VarInt>>,
}

Expand Down Expand Up @@ -130,16 +175,43 @@ impl Serialize for Slot {
}

impl Slot {
pub fn new(item_id: u16, count: u32) -> Self {
Slot {
pub fn new(item: &Item, count: u32) -> Self {
let mut slot = Slot {
item_count: count.into(),
item_id: Some((item_id as i32).into()),
item_id: Some((item.id as i32).into()),
// TODO: add these
num_components_to_add: None,
num_components_to_remove: None,
components_to_add: None,
components_to_remove: None,
};

let mut components_to_add = Vec::new();

if item.has_property_changed(MAX_DAMAGE) {
if let Some(max_damage) = item.components.max_damage {
components_to_add.push(StructuredComponent {
component_type: MAX_DAMAGE_COMPONENT_TYPE.into(),
value: ComponentData::VarInt((max_damage as i32).into()),
});
}
}

if item.has_property_changed(DAMAGE) {
if let Some(damage) = item.components.damage {
components_to_add.push(StructuredComponent {
component_type: DAMAGE_COMPONENT_TYPE.into(),
value: ComponentData::VarInt((damage as i32).into()),
});
}
}

if !components_to_add.is_empty() {
slot.num_components_to_add = Some(components_to_add.len().into());
slot.components_to_add = Some(components_to_add);
}

slot
}

pub fn to_stack(self) -> Result<Option<ItemStack>, &'static str> {
Expand Down Expand Up @@ -178,7 +250,7 @@ impl Slot {

impl From<&ItemStack> for Slot {
fn from(item: &ItemStack) -> Self {
Slot::new(item.item.id, item.item_count as u32)
Slot::new(&item.item, item.item_count as u32)
}
}

Expand Down
1 change: 1 addition & 0 deletions pumpkin-util/src/constants/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod structured_component_constants;
8 changes: 8 additions & 0 deletions pumpkin-util/src/constants/structured_component_constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
pub const MAX_DAMAGE: &str = "max_damage";
pub const DAMAGE: &str = "damage";

pub const MINECRAFT_MAX_DAMAGE: &str = "minecraft:max_damage";
pub const MINECRAFT_DAMAGE: &str = "minecraft:damage";

pub const MAX_DAMAGE_COMPONENT_TYPE: i32 = 2;
pub const DAMAGE_COMPONENT_TYPE: i32 = 3;
1 change: 1 addition & 0 deletions pumpkin-util/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod biome;
pub mod constants;
pub mod gamemode;
pub mod loot_table;
pub mod math;
Expand Down
61 changes: 49 additions & 12 deletions pumpkin-world/src/item/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,34 @@ impl ItemStack {
Self { item_count, item }
}

pub fn damage_item(&mut self) {
let components = &mut self.item.components;

if let (Some(_damage), Some(_max_damage)) = (components.damage, components.max_damage) {
if _max_damage == 0 {
return;
}

// TODO: we probably have to consider the unbreakable enchantment here
if _damage >= _max_damage {
return;
}

components.damage = Some(_damage + 1);
}
}

pub fn is_broken(&self) -> bool {
let components = self.item.components;

// TODO: we probably have to consider the unbreakable enchantment here
if let (Some(_damage), Some(_max_damage)) = (components.damage, components.max_damage) {
return _max_damage > 0 && _damage >= _max_damage;
}

false
}

/// 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`.
Expand Down Expand Up @@ -108,11 +136,17 @@ impl ItemStack {
compound.put_int("count", self.item_count as i32);

// Create a tag compound for additional data
let tag = NbtCompound::new();
let mut tag = NbtCompound::new();

// TODO: Store custom data like enchantments, display name, etc.
if let Some(damage) = self.item.components.damage {
tag.put_int("damage", damage as i32);
}

// TODO: Store custom data like enchantments, display name, etc. would go here
if let Some(max_damage) = self.item.components.max_damage {
tag.put_int("max_damage", max_damage as i32);
}

// Store custom data like enchantments, display name, etc. would go here
compound.put_component("components", tag);
}

Expand All @@ -124,18 +158,21 @@ impl ItemStack {
let registry_key = full_id.strip_prefix("minecraft:").unwrap_or(full_id);

// Try to get item by registry key
let item = Item::from_registry_key(registry_key)?;

let count = compound.get_int("count")? as u8;
let mut item = Item::from_registry_key(registry_key)?.clone();

// Create the item stack
let item_stack = Self::new(count, item);

// Process any additional data in the components compound
// TODO: Process additional components like damage, enchantments, etc.
if let Some(_tag) = compound.get_compound("components") {
// TODO: Process additional components like damage, enchantments, etc.
if let Some(_damage) = _tag.get_int("damage") {
item.components.damage = Some(_damage as u16);
}

if let Some(_max_damage) = _tag.get_int("max_damage") {
item.components.max_damage = Some(_max_damage as u16);
}
}

Some(item_stack)
let count = compound.get_int("count")? as u8;

Some(Self::new(count, item))
}
}
5 changes: 4 additions & 1 deletion pumpkin/src/block/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,10 @@ trait ItemEntryExt {
impl ItemEntryExt for ItemEntry {
fn get_items(&self) -> Vec<ItemStack> {
let item = &self.name.replace("minecraft:", "");
vec![ItemStack::new(1, Item::from_registry_key(item).unwrap())]
vec![ItemStack::new(
1,
Item::from_registry_key(item).unwrap().clone(),
)]
}
}

Expand Down
2 changes: 1 addition & 1 deletion pumpkin/src/entity/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ impl ItemEntity {
}
}
pub async fn send_meta_packet(&self) {
let slot = Slot::new(self.item.id, *self.item_count.lock().await);
let slot = Slot::new(&self.item, *self.item_count.lock().await);
self.entity
.send_meta_data(&[Metadata::new(8, MetaDataType::ItemStack, &slot)])
.await;
Expand Down
16 changes: 14 additions & 2 deletions pumpkin/src/entity/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ impl Player {
//self.world().level.list_cached();
}

pub async fn attack(&self, victim: Arc<dyn EntityBase>) {
pub async fn attack(&self, server: &Server, victim: Arc<dyn EntityBase>) {
let world = self.world().await;
let victim_entity = victim.get_entity();
let attacker_entity = &self.living_entity.entity;
Expand Down Expand Up @@ -474,6 +474,17 @@ impl Player {
)
.await;
}

let inventory = self.inventory().lock().await;

if let Some(item) = inventory.held_item().cloned() {
drop(inventory);

server
.item_registry
.on_attack(victim_entity, &item.item, self)
.await;
}
}

if config.swing {}
Expand Down Expand Up @@ -1619,7 +1630,8 @@ impl Player {
// TODO
}
SInteract::PACKET_ID => {
self.handle_interact(SInteract::read(payload)?).await;
self.handle_interact(SInteract::read(payload)?, server)
.await;
}
SKeepAlive::PACKET_ID => {
self.handle_keep_alive(SKeepAlive::read(payload)?).await;
Expand Down
Loading