mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
Implement equipping armor and resolve difficulty deadlock (#1170)
* feat: implement equipping armour for players * fix: resolve deadlock when updating world difficulty * fix: typo && forgot to clippy * fix: fmt
This commit is contained in:
@@ -129,6 +129,10 @@ impl ScreenHandler for PlayerScreenHandler {
|
||||
if !self.insert_item(&mut slot_stack, 9, 45, false).await {
|
||||
return ItemStack::EMPTY.clone();
|
||||
}
|
||||
|
||||
player
|
||||
.enqueue_equipment_change(equipment_slot, ItemStack::EMPTY)
|
||||
.await;
|
||||
} else if equipment_slot.slot_type() == EquipmentType::HumanoidArmor
|
||||
&& self
|
||||
.get_slot((8 - equipment_slot.get_entity_slot_id()) as usize)
|
||||
@@ -145,6 +149,10 @@ impl ScreenHandler for PlayerScreenHandler {
|
||||
{
|
||||
return ItemStack::EMPTY.clone();
|
||||
}
|
||||
|
||||
player
|
||||
.enqueue_equipment_change(equipment_slot, &stack_prev)
|
||||
.await;
|
||||
} else if matches!(equipment_slot, EquipmentSlot::OffHand(_))
|
||||
&& slot_index != 45
|
||||
&& self.get_slot(45).await.get_cloned_stack().await.is_empty()
|
||||
|
||||
@@ -6,7 +6,10 @@ use crate::{
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use log::warn;
|
||||
use pumpkin_data::screen::WindowType;
|
||||
use pumpkin_data::{
|
||||
data_component_impl::{EquipmentSlot, EquipmentType, EquippableImpl},
|
||||
screen::WindowType,
|
||||
};
|
||||
use pumpkin_protocol::{
|
||||
codec::item_stack_seralizer::OptionalItemStackHash,
|
||||
java::{
|
||||
@@ -72,6 +75,7 @@ pub trait InventoryPlayer: Send + Sync {
|
||||
async fn enqueue_property_packet(&self, packet: &CSetContainerProperty);
|
||||
async fn enqueue_slot_set_packet(&self, packet: &CSetPlayerInventory);
|
||||
async fn enqueue_set_held_item_packet(&self, packet: &CSetSelectedSlot);
|
||||
async fn enqueue_equipment_change(&self, slot: &EquipmentSlot, stack: &ItemStack);
|
||||
}
|
||||
|
||||
pub async fn offer_or_drop_stack(player: &dyn InventoryPlayer, stack: ItemStack) {
|
||||
@@ -738,6 +742,10 @@ pub trait ScreenHandler: Send + Sync {
|
||||
let slot_stack = slot.get_cloned_stack().await;
|
||||
let mut cursor_stack = self.get_behaviour().cursor_stack.lock().await;
|
||||
|
||||
let equipment_slot = cursor_stack
|
||||
.get_data_component::<EquippableImpl>()
|
||||
.map_or(&EquipmentSlot::MAIN_HAND, |equippable| equippable.slot);
|
||||
|
||||
if self
|
||||
.handle_slot_click(
|
||||
player,
|
||||
@@ -753,6 +761,14 @@ pub trait ScreenHandler: Send + Sync {
|
||||
|
||||
if slot_stack.is_empty() {
|
||||
if !cursor_stack.is_empty() {
|
||||
if equipment_slot.slot_type() == EquipmentType::HumanoidArmor
|
||||
&& (5..9).contains(&slot_index)
|
||||
{
|
||||
player
|
||||
.enqueue_equipment_change(equipment_slot, &cursor_stack)
|
||||
.await;
|
||||
}
|
||||
|
||||
let transfer_count = if click_type == MouseClick::Left {
|
||||
cursor_stack.item_count
|
||||
} else {
|
||||
@@ -774,8 +790,27 @@ pub trait ScreenHandler: Send + Sync {
|
||||
// Reverse order of operations, shouldn't affect anything
|
||||
*cursor_stack = taken.clone();
|
||||
slot.on_take_item(player, &taken).await;
|
||||
|
||||
if (5..9).contains(&slot_index) {
|
||||
let equipment_slot = cursor_stack
|
||||
.get_data_component::<EquippableImpl>()
|
||||
.map_or(&EquipmentSlot::MAIN_HAND, |equippable| {
|
||||
equippable.slot
|
||||
});
|
||||
player
|
||||
.enqueue_equipment_change(equipment_slot, ItemStack::EMPTY)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
} else if slot.can_insert(&cursor_stack).await {
|
||||
if equipment_slot.slot_type() == EquipmentType::HumanoidArmor
|
||||
&& (5..9).contains(&slot_index)
|
||||
{
|
||||
player
|
||||
.enqueue_equipment_change(equipment_slot, &cursor_stack)
|
||||
.await;
|
||||
}
|
||||
|
||||
if ItemStack::are_items_and_components_equal(&slot_stack, &cursor_stack) {
|
||||
let insert_count = if click_type == MouseClick::Left {
|
||||
cursor_stack.item_count
|
||||
|
||||
@@ -37,7 +37,7 @@ impl ClientPacket for CSetEquipment {
|
||||
let equipment = &self.equipment[i];
|
||||
let slot = &equipment.0;
|
||||
if i != self.equipment.len() - 1 {
|
||||
write.write_i8(-128)?;
|
||||
write.write_i8(*slot | -128)?;
|
||||
} else {
|
||||
write.write_i8(*slot)?;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ use pumpkin_protocol::bedrock::client::update_abilities::{
|
||||
Ability, AbilityLayer, CUpdateAbilities,
|
||||
};
|
||||
use pumpkin_protocol::bedrock::server::text::SText;
|
||||
use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer;
|
||||
use pumpkin_world::chunk::{ChunkData, ChunkEntityData};
|
||||
use pumpkin_world::inventory::Inventory;
|
||||
use tokio::sync::{Mutex, RwLock};
|
||||
@@ -23,8 +24,8 @@ use uuid::Uuid;
|
||||
|
||||
use pumpkin_config::{BASIC_CONFIG, advanced_config};
|
||||
use pumpkin_data::damage::DamageType;
|
||||
use pumpkin_data::data_component_impl::EquipmentSlot;
|
||||
use pumpkin_data::data_component_impl::{AttributeModifiersImpl, Operation};
|
||||
use pumpkin_data::data_component_impl::{EquipmentSlot, EquippableImpl};
|
||||
use pumpkin_data::effect::StatusEffect;
|
||||
use pumpkin_data::entity::{EntityPose, EntityStatus, EntityType};
|
||||
use pumpkin_data::particle::Particle;
|
||||
@@ -49,10 +50,10 @@ use pumpkin_protocol::java::client::play::{
|
||||
CChunkBatchStart, CChunkData, CCloseContainer, CCombatDeath, CDisguisedChatMessage,
|
||||
CEntityAnimation, CEntityPositionSync, CGameEvent, CKeepAlive, COpenScreen, CParticle,
|
||||
CPlayerAbilities, CPlayerInfoUpdate, CPlayerPosition, CPlayerSpawnPosition, CRespawn,
|
||||
CSetContainerContent, CSetContainerProperty, CSetContainerSlot, CSetCursorItem, CSetExperience,
|
||||
CSetHealth, CSetPlayerInventory, CSetSelectedSlot, CSoundEffect, CStopSound, CSubtitle,
|
||||
CSystemChatMessage, CTitleText, CUnloadChunk, CUpdateMobEffect, CUpdateTime, GameEvent,
|
||||
MetaDataType, Metadata, PlayerAction, PlayerInfoFlags, PreviousMessage,
|
||||
CSetContainerContent, CSetContainerProperty, CSetContainerSlot, CSetCursorItem, CSetEquipment,
|
||||
CSetExperience, CSetHealth, CSetPlayerInventory, CSetSelectedSlot, CSoundEffect, CStopSound,
|
||||
CSubtitle, CSystemChatMessage, CTitleText, CUnloadChunk, CUpdateMobEffect, CUpdateTime,
|
||||
GameEvent, MetaDataType, Metadata, PlayerAction, PlayerInfoFlags, PreviousMessage,
|
||||
};
|
||||
use pumpkin_protocol::java::server::play::SClickSlot;
|
||||
use pumpkin_registry::VanillaDimensionType;
|
||||
@@ -2539,4 +2540,32 @@ impl InventoryPlayer for Player {
|
||||
async fn enqueue_set_held_item_packet(&self, packet: &CSetSelectedSlot) {
|
||||
self.client.enqueue_packet(packet).await;
|
||||
}
|
||||
|
||||
async fn enqueue_equipment_change(&self, slot: &EquipmentSlot, stack: &ItemStack) {
|
||||
self.world()
|
||||
.broadcast_packet_except(
|
||||
&[self.get_entity().entity_uuid],
|
||||
&CSetEquipment::new(
|
||||
self.entity_id().into(),
|
||||
vec![(
|
||||
slot.discriminant(),
|
||||
ItemStackSerializer::from(stack.clone()),
|
||||
)],
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Some(equippable) = stack.get_data_component::<EquippableImpl>()
|
||||
&& let Some(sound) = Sound::from_name(
|
||||
equippable
|
||||
.equip_sound
|
||||
.strip_prefix("minecraft:")
|
||||
.unwrap_or(equippable.equip_sound),
|
||||
)
|
||||
{
|
||||
self.world()
|
||||
.play_sound(sound, SoundCategory::Players, &self.position())
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,13 +26,13 @@ use crate::server::{Server, seasonal_events};
|
||||
use crate::world::{World, chunker};
|
||||
use pumpkin_config::{BASIC_CONFIG, advanced_config};
|
||||
use pumpkin_data::block_properties::{BlockProperties, WaterLikeProperties};
|
||||
use pumpkin_data::data_component_impl::{ConsumableImpl, EquipmentSlot, FoodImpl};
|
||||
use pumpkin_data::data_component_impl::{ConsumableImpl, EquipmentSlot, EquippableImpl, FoodImpl};
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||
use pumpkin_data::{Block, BlockDirection, BlockState};
|
||||
use pumpkin_inventory::InventoryError;
|
||||
use pumpkin_inventory::player::player_inventory::PlayerInventory;
|
||||
use pumpkin_inventory::screen_handler::ScreenHandler;
|
||||
use pumpkin_inventory::screen_handler::{InventoryPlayer, ScreenHandler};
|
||||
use pumpkin_macros::send_cancellable;
|
||||
use pumpkin_protocol::codec::var_int::VarInt;
|
||||
use pumpkin_protocol::java::client::play::{
|
||||
@@ -1602,7 +1602,7 @@ impl JavaClient {
|
||||
None,
|
||||
)
|
||||
};
|
||||
let held = item_in_hand.lock().await;
|
||||
let mut held = item_in_hand.lock().await;
|
||||
if held.get_data_component::<ConsumableImpl>().is_some() {
|
||||
// If its food we want to make sure we can actually consume it
|
||||
if let Some(food) = held.get_data_component::<FoodImpl>() {
|
||||
@@ -1622,6 +1622,23 @@ impl JavaClient {
|
||||
.await;
|
||||
}
|
||||
}
|
||||
if let Some(equippable) = held.get_data_component::<EquippableImpl>() {
|
||||
// If it can be equipped we want to makr sure we can actually equip it
|
||||
player
|
||||
.enqueue_equipment_change(equippable.slot, &held)
|
||||
.await;
|
||||
|
||||
let binding = inventory.entity_equipment.lock().await.get(equippable.slot);
|
||||
let mut equip_item = binding.lock().await;
|
||||
if equip_item.is_empty() {
|
||||
*equip_item = held.clone();
|
||||
held.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
} else {
|
||||
let binding = held.clone();
|
||||
*held = equip_item.clone();
|
||||
*equip_item = binding;
|
||||
}
|
||||
}
|
||||
|
||||
send_cancellable! {{
|
||||
event;
|
||||
@@ -1662,6 +1679,38 @@ impl JavaClient {
|
||||
|
||||
if valid_slot && is_legal {
|
||||
let mut player_screen_handler = player.player_screen_handler.lock().await;
|
||||
|
||||
let is_armor_equipped = player_screen_handler
|
||||
.get_slot(packet.slot as usize)
|
||||
.await
|
||||
.get_stack()
|
||||
.await
|
||||
.lock()
|
||||
.await
|
||||
.are_equal(&item_stack);
|
||||
if !is_armor_equipped {
|
||||
if (5..9).contains(&packet.slot) {
|
||||
player
|
||||
.enqueue_equipment_change(
|
||||
&match packet.slot {
|
||||
5 => EquipmentSlot::HEAD,
|
||||
6 => EquipmentSlot::CHEST,
|
||||
7 => EquipmentSlot::LEGS,
|
||||
8 => EquipmentSlot::FEET,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
&item_stack,
|
||||
)
|
||||
.await;
|
||||
} else if (36..45).contains(&packet.slot) {
|
||||
let slot = packet.slot - 36;
|
||||
if player.inventory().get_selected_slot() == slot as u8 {
|
||||
let equipment = &[(EquipmentSlot::MAIN_HAND, item_stack.clone())];
|
||||
player.living_entity.send_equipment_changes(equipment).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
player_screen_handler
|
||||
.get_slot(packet.slot as usize)
|
||||
.await
|
||||
|
||||
@@ -445,27 +445,26 @@ impl Server {
|
||||
/// This function does not handle the actual mob spawn options update, which is a TODO item for future implementation.
|
||||
pub async fn set_difficulty(&self, difficulty: Difficulty, force_update: Option<bool>) {
|
||||
let mut level_info = self.level_info.write().await;
|
||||
if force_update.unwrap_or_default() || !level_info.difficulty_locked {
|
||||
level_info.difficulty = if BASIC_CONFIG.hardcore {
|
||||
Difficulty::Hard
|
||||
} else {
|
||||
difficulty
|
||||
};
|
||||
// Minecraft server updates mob spawn options here
|
||||
// but its not implemented in Pumpkin yet
|
||||
// todo: update mob spawn options
|
||||
|
||||
for world in &*self.worlds.read().await {
|
||||
world.level_info.write().await.difficulty = level_info.difficulty;
|
||||
}
|
||||
|
||||
self.broadcast_packet_all(&CChangeDifficulty::new(
|
||||
level_info.difficulty as u8,
|
||||
level_info.difficulty_locked,
|
||||
))
|
||||
.await;
|
||||
drop(level_info);
|
||||
if level_info.difficulty_locked && !force_update.unwrap_or_default() {
|
||||
return;
|
||||
}
|
||||
|
||||
let difficulty = if BASIC_CONFIG.hardcore {
|
||||
Difficulty::Hard
|
||||
} else {
|
||||
difficulty
|
||||
};
|
||||
|
||||
level_info.difficulty = difficulty;
|
||||
let locked = level_info.difficulty_locked;
|
||||
drop(level_info);
|
||||
|
||||
for world in &*self.worlds.read().await {
|
||||
world.level_info.write().await.difficulty = difficulty;
|
||||
}
|
||||
|
||||
self.broadcast_packet_all(&CChangeDifficulty::new(difficulty as u8, locked))
|
||||
.await;
|
||||
}
|
||||
|
||||
/// Searches for a player by their username across all worlds.
|
||||
|
||||
@@ -1600,6 +1600,38 @@ impl World {
|
||||
buf.into(),
|
||||
))
|
||||
.await;
|
||||
};
|
||||
|
||||
{
|
||||
let mut equipment_list = Vec::new();
|
||||
|
||||
equipment_list.push((
|
||||
EquipmentSlot::MAIN_HAND.discriminant(),
|
||||
existing_player.inventory.held_item().lock().await.clone(),
|
||||
));
|
||||
|
||||
for (slot, item_arc_mutex) in &existing_player
|
||||
.inventory
|
||||
.entity_equipment
|
||||
.lock()
|
||||
.await
|
||||
.equipment
|
||||
{
|
||||
let item_stack = item_arc_mutex.lock().await.clone();
|
||||
equipment_list.push((slot.discriminant(), item_stack));
|
||||
}
|
||||
|
||||
let equipment: Vec<(i8, ItemStackSerializer)> = equipment_list
|
||||
.iter()
|
||||
.map(|(slot, stack)| (*slot, ItemStackSerializer::from(stack.clone())))
|
||||
.collect();
|
||||
|
||||
client
|
||||
.enqueue_packet(&CSetEquipment::new(
|
||||
existing_player.entity_id().into(),
|
||||
equipment,
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
player.send_client_information().await;
|
||||
|
||||
Reference in New Issue
Block a user