feat: add Anvil
This commit is contained in:
parent
81cffe19a2
commit
8060283970
@ -225,10 +225,18 @@ impl DataComponentImpl for UnbreakableImpl {
|
||||
}
|
||||
#[derive(Clone, Hash, PartialEq, Eq)]
|
||||
pub struct CustomNameImpl {
|
||||
// TODO make TextComponent const
|
||||
pub name: &'static str,
|
||||
// TODO make TextComponent
|
||||
pub name: String,
|
||||
}
|
||||
impl DataComponentImpl for CustomNameImpl {
|
||||
fn write_data(&self) -> NbtTag {
|
||||
NbtTag::String(self.name.clone())
|
||||
}
|
||||
|
||||
fn get_hash(&self) -> i32 {
|
||||
get_str_hash(self.name.as_str()) as i32
|
||||
}
|
||||
|
||||
default_impl!(CustomName);
|
||||
}
|
||||
#[derive(Clone, Hash, PartialEq, Eq)]
|
||||
|
||||
@ -288,6 +288,20 @@ impl ItemStack {
|
||||
self.item_count == 0 || self.item.id == Item::AIR.id
|
||||
}
|
||||
|
||||
pub fn set_custom_name(&mut self, name: String) {
|
||||
use crate::data_component_impl::CustomNameImpl;
|
||||
let component = Some(CustomNameImpl { name }.to_dyn());
|
||||
if let Some(pos) = self
|
||||
.patch
|
||||
.iter()
|
||||
.position(|(id, _)| *id == DataComponent::CustomName)
|
||||
{
|
||||
self.patch[pos].1 = component;
|
||||
} else {
|
||||
self.patch.push((DataComponent::CustomName, component));
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn split(&mut self, amount: u8) -> Self {
|
||||
let min = amount.min(self.item_count);
|
||||
|
||||
231
pumpkin-inventory/src/anvil/anvil_screen_handler.rs
Normal file
231
pumpkin-inventory/src/anvil/anvil_screen_handler.rs
Normal file
@ -0,0 +1,231 @@
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
use pumpkin_data::{item_stack::ItemStack, screen::WindowType};
|
||||
use pumpkin_world::inventory::Inventory;
|
||||
|
||||
use crate::{
|
||||
player::player_inventory::PlayerInventory,
|
||||
screen_handler::{
|
||||
InventoryPlayer, ItemStackFuture, ScreenHandler, ScreenHandlerBehaviour,
|
||||
ScreenHandlerFuture,
|
||||
},
|
||||
slot::NormalSlot,
|
||||
window_property::{Anvil, WindowProperty},
|
||||
};
|
||||
|
||||
pub struct AnvilScreenHandler {
|
||||
pub inventory: Arc<dyn Inventory>,
|
||||
behaviour: ScreenHandlerBehaviour,
|
||||
pub rename_text: String,
|
||||
pub repair_cost: i16,
|
||||
}
|
||||
|
||||
impl AnvilScreenHandler {
|
||||
#[expect(clippy::needless_pass_by_value)]
|
||||
pub fn new(
|
||||
sync_id: u8,
|
||||
player_inventory: &Arc<PlayerInventory>,
|
||||
inventory: Arc<dyn Inventory>,
|
||||
) -> Self {
|
||||
let mut handler = Self {
|
||||
inventory: inventory.clone(),
|
||||
behaviour: ScreenHandlerBehaviour::new(sync_id, Some(WindowType::Anvil)),
|
||||
rename_text: String::new(),
|
||||
repair_cost: 0,
|
||||
};
|
||||
|
||||
// Anvil specific slots: 2 input, 1 output
|
||||
for i in 0..3 {
|
||||
handler.add_slot(Arc::new(NormalSlot::new(inventory.clone(), i)));
|
||||
}
|
||||
|
||||
let player_inventory: Arc<dyn Inventory> = player_inventory.clone();
|
||||
handler.add_player_slots(&player_inventory);
|
||||
|
||||
handler
|
||||
}
|
||||
|
||||
pub async fn update_item_name(&mut self, name: String) {
|
||||
self.rename_text = name;
|
||||
self.update_result_slot().await;
|
||||
self.send_content_updates().await;
|
||||
}
|
||||
|
||||
pub async fn update_result_slot(&mut self) {
|
||||
let input_a = {
|
||||
let lock = self.inventory.get_stack(0).await;
|
||||
lock.lock().await.clone()
|
||||
};
|
||||
|
||||
if input_a.is_empty() {
|
||||
self.inventory.set_stack(2, ItemStack::EMPTY.clone()).await;
|
||||
self.set_repair_cost(0).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let mut result_item = input_a.clone();
|
||||
let mut cost = 0;
|
||||
|
||||
// Basic renaming logic for now
|
||||
if !self.rename_text.is_empty() {
|
||||
result_item.set_custom_name(self.rename_text.clone());
|
||||
cost += 1;
|
||||
}
|
||||
|
||||
// If combining with another item... we'll skip complex anvil logic for now
|
||||
// and just support renaming.
|
||||
if cost > 0 {
|
||||
self.inventory.set_stack(2, result_item).await;
|
||||
self.set_repair_cost(cost).await;
|
||||
} else {
|
||||
self.inventory.set_stack(2, ItemStack::EMPTY.clone()).await;
|
||||
self.set_repair_cost(0).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn set_repair_cost(&mut self, cost: i16) {
|
||||
self.repair_cost = cost;
|
||||
if let Some(sync_handler) = self.behaviour.sync_handler.as_ref() {
|
||||
let (property_id, property_value) =
|
||||
WindowProperty::new(Anvil::RepairCost, cost).into_tuple();
|
||||
sync_handler
|
||||
.update_property(&self.behaviour, property_id as i32, property_value as i32)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ScreenHandler for AnvilScreenHandler {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn get_behaviour(&self) -> &ScreenHandlerBehaviour {
|
||||
&self.behaviour
|
||||
}
|
||||
|
||||
fn get_behaviour_mut(&mut self) -> &mut ScreenHandlerBehaviour {
|
||||
&mut self.behaviour
|
||||
}
|
||||
|
||||
fn on_closed<'a>(&'a mut self, player: &'a dyn InventoryPlayer) -> ScreenHandlerFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
self.default_on_closed(player).await;
|
||||
self.inventory.on_close().await;
|
||||
// Drop inputs from anvil
|
||||
for i in 0..2 {
|
||||
let stack = self.inventory.remove_stack(i).await;
|
||||
if !stack.is_empty() {
|
||||
player.drop_item(stack, false).await;
|
||||
}
|
||||
}
|
||||
self.inventory.set_stack(2, ItemStack::EMPTY.clone()).await;
|
||||
})
|
||||
}
|
||||
|
||||
fn quick_move<'a>(
|
||||
&'a mut self,
|
||||
_player: &'a dyn InventoryPlayer,
|
||||
slot_index: i32,
|
||||
) -> ItemStackFuture<'a> {
|
||||
Box::pin(async move {
|
||||
let mut stack_left = ItemStack::EMPTY.clone();
|
||||
let slot = self.get_behaviour().slots[slot_index as usize].clone();
|
||||
|
||||
if slot.has_stack().await {
|
||||
let slot_stack_lock = slot.get_stack().await;
|
||||
let slot_stack_guard = slot_stack_lock.lock().await;
|
||||
stack_left = slot_stack_guard.clone();
|
||||
drop(slot_stack_guard);
|
||||
|
||||
let mut slot_stack_mut = slot_stack_lock.lock().await;
|
||||
|
||||
if slot_index < 3 {
|
||||
// From anvil to player
|
||||
if !self
|
||||
.insert_item(
|
||||
&mut slot_stack_mut,
|
||||
3,
|
||||
self.get_behaviour().slots.len() as i32,
|
||||
true,
|
||||
)
|
||||
.await
|
||||
{
|
||||
return ItemStack::EMPTY.clone();
|
||||
}
|
||||
} else {
|
||||
// From player to anvil input 0 and 1
|
||||
if !self.insert_item(&mut slot_stack_mut, 0, 2, false).await {
|
||||
return ItemStack::EMPTY.clone();
|
||||
}
|
||||
}
|
||||
|
||||
if slot_stack_mut.is_empty() {
|
||||
drop(slot_stack_mut);
|
||||
slot.set_stack(ItemStack::EMPTY.clone()).await;
|
||||
} else {
|
||||
drop(slot_stack_mut);
|
||||
slot.mark_dirty().await;
|
||||
}
|
||||
}
|
||||
|
||||
stack_left
|
||||
})
|
||||
}
|
||||
|
||||
fn on_slot_click<'a>(
|
||||
&'a mut self,
|
||||
slot_index: i32,
|
||||
button: i32,
|
||||
action_type: pumpkin_protocol::java::server::play::SlotActionType,
|
||||
player: &'a dyn InventoryPlayer,
|
||||
) -> ScreenHandlerFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
if slot_index == 2 {
|
||||
// Taking from output slot
|
||||
let result_slot = self.get_behaviour().slots[2].clone();
|
||||
if result_slot.has_stack().await {
|
||||
let result_stack = result_slot.get_cloned_stack().await;
|
||||
if !result_stack.is_empty() {
|
||||
if player.experience_level() >= self.repair_cost as i32
|
||||
|| player.is_creative()
|
||||
{
|
||||
// Consume experience
|
||||
if !player.is_creative() {
|
||||
player
|
||||
.add_experience_levels(-(self.repair_cost as i32))
|
||||
.await;
|
||||
}
|
||||
|
||||
// Consume inputs
|
||||
let input_a = self.inventory.get_stack(0).await;
|
||||
let mut input_a = input_a.lock().await;
|
||||
input_a.decrement(1); // Usually consumes 1
|
||||
if input_a.is_empty() {
|
||||
*input_a = ItemStack::EMPTY.clone();
|
||||
}
|
||||
drop(input_a);
|
||||
self.get_behaviour().slots[0].mark_dirty().await;
|
||||
} else {
|
||||
// Cancel click
|
||||
self.send_content_updates().await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.internal_on_slot_click(slot_index, button, action_type, player)
|
||||
.await;
|
||||
if slot_index == 0 || slot_index == 1 || slot_index == 2 {
|
||||
self.update_result_slot().await;
|
||||
self.send_content_updates().await;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
3
pumpkin-inventory/src/anvil/mod.rs
Normal file
3
pumpkin-inventory/src/anvil/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod anvil_screen_handler;
|
||||
|
||||
pub use anvil_screen_handler::*;
|
||||
@ -30,6 +30,7 @@
|
||||
//! [`PlayerInventory`]: PlayerInventory
|
||||
//! [`SyncHandler`]: sync_handler::SyncHandler
|
||||
|
||||
pub mod anvil;
|
||||
pub mod brewing;
|
||||
pub mod container_click;
|
||||
pub mod crafting;
|
||||
|
||||
@ -133,6 +133,15 @@ pub trait InventoryPlayer: Send + Sync {
|
||||
/// Checks if the player has infinite materials (creative mode).
|
||||
fn has_infinite_materials(&self) -> bool;
|
||||
|
||||
/// Checks if the player is in creative mode.
|
||||
fn is_creative(&self) -> bool;
|
||||
|
||||
/// Gets the player's experience level.
|
||||
fn experience_level(&self) -> i32;
|
||||
|
||||
/// Adds or removes experience levels.
|
||||
fn add_experience_levels(&self, levels: i32) -> PlayerFuture<'_, ()>;
|
||||
|
||||
/// Sends a full container content packet.
|
||||
fn enqueue_inventory_packet<'a>(
|
||||
&'a self,
|
||||
|
||||
@ -108,6 +108,14 @@ pub enum Anvil {
|
||||
RepairCost,
|
||||
}
|
||||
|
||||
impl WindowPropertyTrait for Anvil {
|
||||
fn to_id(self) -> i16 {
|
||||
match self {
|
||||
Self::RepairCost => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Brewing stand window properties.
|
||||
pub enum BrewingStand {
|
||||
/// Brewing progress (0-400).
|
||||
|
||||
@ -4,7 +4,7 @@ use crate::codec::var_int::VarInt;
|
||||
use pumpkin_data::Enchantment;
|
||||
use pumpkin_data::data_component::DataComponent;
|
||||
use pumpkin_data::data_component_impl::{
|
||||
ConsumableImpl, ConsumeAnimation, ConsumeEffect, DamageImpl, DataComponentImpl,
|
||||
ConsumableImpl, ConsumeAnimation, ConsumeEffect, CustomNameImpl, DamageImpl, DataComponentImpl,
|
||||
EnchantmentsImpl, EquipmentSlot, EquippableImpl, FireworkExplosionImpl, FireworkExplosionShape,
|
||||
FireworksImpl, IDSet, IDSetContent, IdOr, ItemModelImpl, MaxStackSizeImpl, PotionContentsImpl,
|
||||
SoundEvent, StatusEffectInstance, StoredEnchantmentsImpl, UnbreakableImpl, get,
|
||||
@ -346,6 +346,19 @@ impl DataComponentCodec<Self> for ItemModelImpl {
|
||||
}
|
||||
}
|
||||
|
||||
impl DataComponentCodec<Self> for CustomNameImpl {
|
||||
fn serialize<T: SerializeStruct>(&self, seq: &mut T) -> Result<(), T::Error> {
|
||||
seq.serialize_field::<String>("", &self.name)
|
||||
}
|
||||
|
||||
fn deserialize<'a, A: SeqAccess<'a>>(seq: &mut A) -> Result<Self, A::Error> {
|
||||
let name = seq
|
||||
.next_element::<String>()?
|
||||
.ok_or(de::Error::custom("No CustomNameImpl name string!"))?;
|
||||
Ok(Self { name })
|
||||
}
|
||||
}
|
||||
|
||||
impl DataComponentCodec<Self> for ConsumableImpl {
|
||||
fn serialize<T: SerializeStruct>(&self, seq: &mut T) -> Result<(), T::Error> {
|
||||
seq.serialize_field::<f32>("", &self.consume_seconds)?;
|
||||
@ -820,6 +833,7 @@ pub fn deserialize<'a, A: SeqAccess<'a>>(
|
||||
DataComponent::FireworkExplosion => Ok(FireworkExplosionImpl::deserialize(seq)?.to_dyn()),
|
||||
DataComponent::Fireworks => Ok(FireworksImpl::deserialize(seq)?.to_dyn()),
|
||||
DataComponent::ItemModel => Ok(ItemModelImpl::deserialize(seq)?.to_dyn()),
|
||||
DataComponent::CustomName => Ok(CustomNameImpl::deserialize(seq)?.to_dyn()),
|
||||
DataComponent::Consumable => Ok(ConsumableImpl::deserialize(seq)?.to_dyn()),
|
||||
DataComponent::Equippable => Ok(EquippableImpl::deserialize(seq)?.to_dyn()),
|
||||
DataComponent::StoredEnchantments => Ok(StoredEnchantmentsImpl::deserialize(seq)?.to_dyn()),
|
||||
@ -840,6 +854,7 @@ pub fn serialize<T: SerializeStruct>(
|
||||
DataComponent::FireworkExplosion => get::<FireworkExplosionImpl>(value).serialize(seq),
|
||||
DataComponent::Fireworks => get::<FireworksImpl>(value).serialize(seq),
|
||||
DataComponent::ItemModel => get::<ItemModelImpl>(value).serialize(seq),
|
||||
DataComponent::CustomName => get::<CustomNameImpl>(value).serialize(seq),
|
||||
DataComponent::Consumable => get::<ConsumableImpl>(value).serialize(seq),
|
||||
DataComponent::Equippable => get::<EquippableImpl>(value).serialize(seq),
|
||||
DataComponent::StoredEnchantments => get::<StoredEnchantmentsImpl>(value).serialize(seq),
|
||||
|
||||
@ -31,6 +31,7 @@ mod player_rotation;
|
||||
mod player_session;
|
||||
mod recipe_book_change_settings;
|
||||
mod recipe_book_seen_recipe;
|
||||
mod rename_item;
|
||||
mod select_trade;
|
||||
mod set_command_block;
|
||||
mod set_creative_slot;
|
||||
@ -73,6 +74,7 @@ pub use player_rotation::*;
|
||||
pub use player_session::*;
|
||||
pub use recipe_book_change_settings::*;
|
||||
pub use recipe_book_seen_recipe::*;
|
||||
pub use rename_item::*;
|
||||
pub use select_trade::*;
|
||||
pub use set_command_block::*;
|
||||
pub use set_creative_slot::*;
|
||||
|
||||
9
pumpkin-protocol/src/java/server/play/rename_item.rs
Normal file
9
pumpkin-protocol/src/java/server/play/rename_item.rs
Normal file
@ -0,0 +1,9 @@
|
||||
use pumpkin_data::packet::serverbound::PLAY_RENAME_ITEM;
|
||||
use pumpkin_macros::java_packet;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[java_packet(PLAY_RENAME_ITEM)]
|
||||
pub struct SRenameItem {
|
||||
pub item_name: String,
|
||||
}
|
||||
@ -1,16 +1,44 @@
|
||||
use crate::block::blocks::falling::FallingBlock;
|
||||
use crate::block::registry::BlockActionResult;
|
||||
use crate::block::{
|
||||
BlockBehaviour, BlockFuture, GetStateForNeighborUpdateArgs, OnPlaceArgs, OnScheduledTickArgs,
|
||||
PlacedArgs,
|
||||
BlockBehaviour, BlockFuture, GetStateForNeighborUpdateArgs, NormalUseArgs, OnPlaceArgs,
|
||||
OnScheduledTickArgs, PlacedArgs,
|
||||
};
|
||||
|
||||
use pumpkin_data::block_properties::{BlockProperties, WallTorchLikeProperties};
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_inventory::anvil::AnvilScreenHandler;
|
||||
use pumpkin_inventory::player::player_inventory::PlayerInventory;
|
||||
use pumpkin_inventory::screen_handler::{
|
||||
BoxFuture, InventoryPlayer, ScreenHandlerFactory, SharedScreenHandler,
|
||||
};
|
||||
use pumpkin_macros::pumpkin_block_from_tag;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use pumpkin_world::BlockStateId;
|
||||
use pumpkin_world::inventory::SimpleInventory;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
#[pumpkin_block_from_tag("minecraft:anvil")]
|
||||
pub struct AnvilBlock;
|
||||
|
||||
impl BlockBehaviour for AnvilBlock {
|
||||
fn normal_use<'a>(&'a self, args: NormalUseArgs<'a>) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
args.player
|
||||
.open_handled_screen(&AnvilScreenFactory, Some(*args.position))
|
||||
.await;
|
||||
|
||||
BlockActionResult::Success
|
||||
})
|
||||
}
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
FallingBlock::placed(&FallingBlock, args).await;
|
||||
})
|
||||
}
|
||||
|
||||
fn on_place<'a>(&'a self, args: OnPlaceArgs<'a>) -> BlockFuture<'a, BlockStateId> {
|
||||
Box::pin(async move {
|
||||
let dir = args
|
||||
@ -27,9 +55,9 @@ impl BlockBehaviour for AnvilBlock {
|
||||
})
|
||||
}
|
||||
|
||||
fn placed<'a>(&'a self, args: PlacedArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
FallingBlock::placed(&FallingBlock, args).await;
|
||||
FallingBlock::on_scheduled_tick(&FallingBlock, args).await;
|
||||
})
|
||||
}
|
||||
|
||||
@ -41,9 +69,27 @@ impl BlockBehaviour for AnvilBlock {
|
||||
async move { FallingBlock::get_state_for_neighbor_update(&FallingBlock, args).await },
|
||||
)
|
||||
}
|
||||
fn on_scheduled_tick<'a>(&'a self, args: OnScheduledTickArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
}
|
||||
|
||||
struct AnvilScreenFactory;
|
||||
|
||||
impl ScreenHandlerFactory for AnvilScreenFactory {
|
||||
fn create_screen_handler<'a>(
|
||||
&'a self,
|
||||
sync_id: u8,
|
||||
player_inventory: &'a Arc<PlayerInventory>,
|
||||
_player: &'a dyn InventoryPlayer,
|
||||
) -> BoxFuture<'a, Option<SharedScreenHandler>> {
|
||||
Box::pin(async move {
|
||||
FallingBlock::on_scheduled_tick(&FallingBlock, args).await;
|
||||
let inventory = Arc::new(SimpleInventory::new(3));
|
||||
let handler = AnvilScreenHandler::new(sync_id, player_inventory, inventory);
|
||||
let concrete_arc = Arc::new(Mutex::new(handler));
|
||||
|
||||
Some(concrete_arc as SharedScreenHandler)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_display_name(&self) -> TextComponent {
|
||||
TextComponent::translate(translation::CONTAINER_REPAIR, &[])
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +64,7 @@ use pumpkin_protocol::java::client::play::{
|
||||
CTitleAnimation, CTitleText, CUnloadChunk, CUpdateMobEffect, CUpdateTime, GameEvent, Metadata,
|
||||
PlayerAction, PlayerInfoFlags, PreviousMessage,
|
||||
};
|
||||
use pumpkin_protocol::java::server::play::{SClickSlot, SlotActionType};
|
||||
use pumpkin_protocol::java::server::play::{SClickSlot, SRenameItem, SlotActionType};
|
||||
use pumpkin_util::math::{
|
||||
boundingbox::BoundingBox, experience, position::BlockPos, vector2::Vector2, vector3::Vector3,
|
||||
};
|
||||
@ -2933,6 +2933,19 @@ impl Player {
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn on_rename_item(self: &Arc<Self>, packet: SRenameItem) {
|
||||
self.update_last_action_time();
|
||||
let screen_handler_arc = self.current_screen_handler.lock().await.clone();
|
||||
let mut screen_handler = screen_handler_arc.lock().await;
|
||||
|
||||
if let Some(anvil_handler) = screen_handler
|
||||
.as_any_mut()
|
||||
.downcast_mut::<pumpkin_inventory::anvil::AnvilScreenHandler>()
|
||||
{
|
||||
anvil_handler.update_item_name(packet.item_name).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn open_handled_screen(
|
||||
&self,
|
||||
screen_handler_factory: &dyn ScreenHandlerFactory,
|
||||
@ -4060,6 +4073,21 @@ impl InventoryPlayer for Player {
|
||||
self.gamemode.load() == GameMode::Creative
|
||||
}
|
||||
|
||||
fn is_creative(&self) -> bool {
|
||||
self.gamemode.load() == GameMode::Creative
|
||||
}
|
||||
|
||||
fn experience_level(&self) -> i32 {
|
||||
self.experience_level
|
||||
.load(std::sync::atomic::Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn add_experience_levels(&self, levels: i32) -> PlayerFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
self.add_experience_levels(levels).await;
|
||||
})
|
||||
}
|
||||
|
||||
fn get_inventory(&self) -> Arc<PlayerInventory> {
|
||||
self.inventory.clone()
|
||||
}
|
||||
|
||||
@ -30,7 +30,9 @@ impl ItemBehaviour for NameTagItem {
|
||||
&& let Some(name) = item.get_data_component::<CustomNameImpl>()
|
||||
{
|
||||
// TODO
|
||||
entity.set_custom_name(TextComponent::text(name.name)).await;
|
||||
entity
|
||||
.set_custom_name(TextComponent::text(name.name.clone()))
|
||||
.await;
|
||||
item.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
}
|
||||
})
|
||||
|
||||
@ -12,8 +12,8 @@ use pumpkin_protocol::java::server::play::{
|
||||
SPaddleBoat, SPickItemFromBlock, SPlaceRecipe, SPlayPingRequest, SPlayerAbilities,
|
||||
SPlayerAction, SPlayerCommand, SPlayerInput, SPlayerLoaded, SPlayerPosition,
|
||||
SPlayerPositionRotation, SPlayerRotation, SPlayerSession, SRecipeBookChangeSettings,
|
||||
SRecipeBookSeenRecipe, SSelectTrade, SSetCommandBlock, SSetCreativeSlot, SSetHeldItem,
|
||||
SSetPlayerGround, SSwingArm, SUpdateSign, SUseItem, SUseItemOn,
|
||||
SRecipeBookSeenRecipe, SRenameItem, SSelectTrade, SSetCommandBlock, SSetCreativeSlot,
|
||||
SSetHeldItem, SSetPlayerGround, SSwingArm, SUpdateSign, SUseItem, SUseItemOn,
|
||||
};
|
||||
use pumpkin_protocol::packet::MultiVersionJavaPacket;
|
||||
use pumpkin_protocol::{
|
||||
@ -887,6 +887,11 @@ impl JavaClient {
|
||||
)
|
||||
.await;
|
||||
}
|
||||
id if id == SRenameItem::to_id(version) => {
|
||||
player
|
||||
.on_rename_item(SRenameItem::read(payload, &version)?)
|
||||
.await;
|
||||
}
|
||||
id if id == SPlaceRecipe::to_id(version) => {
|
||||
self.handle_place_recipe(player, SPlaceRecipe::read(payload, &version)?)
|
||||
.await;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user