feat: make totems work

This commit is contained in:
Alexander Medvedev
2025-08-21 16:43:13 +02:00
parent 75afdd059c
commit 94bdae9684
32 changed files with 265 additions and 199 deletions

View File

@@ -39,6 +39,8 @@ pub struct ItemComponents {
pub consumable: Option<Consumable>,
#[serde(rename = "minecraft:blocks_attacks")]
pub blocks_attacks: Option<BlocksAttacks>,
#[serde(rename = "minecraft:death_protection")]
pub death_protection: Option<DeathProtection>,
}
impl ToTokens for ItemComponents {
@@ -237,6 +239,10 @@ impl ToTokens for ItemComponents {
tokens.extend(quote! { (BlocksAttacks, &BlocksAttacksImpl), });
};
if self.death_protection.is_some() {
tokens.extend(quote! { (DeathProtection, &DeathProtectionImpl), });
};
if let Some(equippable) = &self.equippable {
let slot = match equippable.slot.as_str() {
"mainhand" => quote! { &EquipmentSlot::MAIN_HAND },
@@ -397,6 +403,11 @@ pub struct Consumable {
consume_seconds: Option<f32>, // TODO
}
#[derive(Deserialize, Clone, Debug)]
pub struct DeathProtection {
// TODO
}
#[derive(Deserialize, Clone, Debug)]
pub struct BlocksAttacks {
// TODO

View File

@@ -571,6 +571,9 @@ pub struct GliderImpl;
pub struct TooltipStyleImpl;
#[derive(Clone, Debug, Hash, PartialEq)]
pub struct DeathProtectionImpl;
impl DataComponentImpl for DeathProtectionImpl {
default_impl!(DeathProtection);
}
#[derive(Clone, Debug, Hash, PartialEq)]
pub struct BlocksAttacksImpl;

View File

@@ -12,4 +12,33 @@ pub mod slot;
pub mod sync_handler;
pub mod window_property;
use std::collections::HashMap;
pub use error::InventoryError;
use pumpkin_data::data_component_impl::EquipmentSlot;
use crate::player::player_inventory::PlayerInventory;
pub fn build_equipment_slots() -> HashMap<usize, EquipmentSlot> {
let mut equipment_slots = HashMap::new();
equipment_slots.insert(
EquipmentSlot::FEET.get_offset_entity_slot_id(PlayerInventory::MAIN_SIZE as i32) as usize,
EquipmentSlot::FEET,
);
equipment_slots.insert(
EquipmentSlot::LEGS.get_offset_entity_slot_id(PlayerInventory::MAIN_SIZE as i32) as usize,
EquipmentSlot::LEGS,
);
equipment_slots.insert(
EquipmentSlot::CHEST.get_offset_entity_slot_id(PlayerInventory::MAIN_SIZE as i32) as usize,
EquipmentSlot::CHEST,
);
equipment_slots.insert(
EquipmentSlot::HEAD.get_offset_entity_slot_id(PlayerInventory::MAIN_SIZE as i32) as usize,
EquipmentSlot::HEAD,
);
equipment_slots.insert(PlayerInventory::OFF_HAND_SLOT, EquipmentSlot::OFF_HAND);
equipment_slots.insert(PlayerInventory::OFF_HAND_SLOT, EquipmentSlot::OFF_HAND);
equipment_slots
}

View File

@@ -3,6 +3,7 @@ use crate::screen_handler::InventoryPlayer;
use async_trait::async_trait;
use pumpkin_data::data_component_impl::EquipmentSlot;
use pumpkin_protocol::java::client::play::CSetPlayerInventory;
use pumpkin_util::Hand;
use pumpkin_world::inventory::split_stack;
use pumpkin_world::inventory::{Clearable, Inventory};
use pumpkin_world::item::ItemStack;
@@ -16,22 +17,25 @@ use tokio::sync::Mutex;
#[derive(Debug)]
pub struct PlayerInventory {
pub main_inventory: [Arc<Mutex<ItemStack>>; Self::MAIN_SIZE],
pub equipment_slots: HashMap<usize, EquipmentSlot>,
pub equipment_slots: Arc<HashMap<usize, EquipmentSlot>>,
selected_slot: AtomicU8,
pub entity_equipment: Arc<Mutex<EntityEquipment>>,
}
impl PlayerInventory {
const MAIN_SIZE: usize = 36;
pub const MAIN_SIZE: usize = 36;
const HOTBAR_SIZE: usize = 9;
const OFF_HAND_SLOT: usize = 40;
pub const OFF_HAND_SLOT: usize = 40;
// TODO: Add inventory load from nbt
pub fn new(entity_equipment: Arc<Mutex<EntityEquipment>>) -> Self {
pub fn new(
entity_equipment: Arc<Mutex<EntityEquipment>>,
equipment_slots: Arc<HashMap<usize, EquipmentSlot>>,
) -> Self {
Self {
// Normal syntax can't be used here because Arc doesn't implement Copy
main_inventory: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
equipment_slots: Self::build_equipment_slots(),
equipment_slots,
selected_slot: AtomicU8::new(0),
entity_equipment,
}
@@ -45,6 +49,13 @@ impl PlayerInventory {
.clone()
}
pub async fn get_stack_in_hand(&self, hand: Hand) -> Arc<Mutex<ItemStack>> {
match hand {
Hand::Left => self.off_hand_item().await,
Hand::Right => self.held_item(),
}
}
/// getOffHandStack in source
pub async fn off_hand_item(&self) -> Arc<Mutex<ItemStack>> {
let slot = self
@@ -71,28 +82,6 @@ impl PlayerInventory {
slot < Self::HOTBAR_SIZE
}
fn build_equipment_slots() -> HashMap<usize, EquipmentSlot> {
let mut equipment_slots = HashMap::new();
equipment_slots.insert(
EquipmentSlot::FEET.get_offset_entity_slot_id(Self::MAIN_SIZE as i32) as usize,
EquipmentSlot::FEET,
);
equipment_slots.insert(
EquipmentSlot::LEGS.get_offset_entity_slot_id(Self::MAIN_SIZE as i32) as usize,
EquipmentSlot::LEGS,
);
equipment_slots.insert(
EquipmentSlot::CHEST.get_offset_entity_slot_id(Self::MAIN_SIZE as i32) as usize,
EquipmentSlot::CHEST,
);
equipment_slots.insert(
EquipmentSlot::HEAD.get_offset_entity_slot_id(Self::MAIN_SIZE as i32) as usize,
EquipmentSlot::HEAD,
);
equipment_slots.insert(PlayerInventory::OFF_HAND_SLOT, EquipmentSlot::OFF_HAND);
equipment_slots
}
async fn add_stack(&self, stack: ItemStack) -> usize {
let mut slot_index = self.get_occupied_slot_with_room_for_stack(&stack).await;

View File

@@ -61,10 +61,10 @@ struct AdditionsSound {
tick_chance: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Music {
sound: String,
min_delay: i32,
max_delay: i32,
replace_current_music: bool,
}
// #[derive(Debug, Clone, Serialize, Deserialize)]
// struct Music {
// sound: String,
// min_delay: i32,
// max_delay: i32,
// replace_current_music: bool,
// }

View File

@@ -73,12 +73,6 @@ pub struct SyncedRegistry {
instrument: IndexMap<String, Instrument>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct DataPool<T> {
data: T,
weight: i32,
}
// TODO: remove in favor of numerical registry ids for `minecraft:dimension_type`
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VanillaDimensionType {

View File

@@ -140,3 +140,32 @@ macro_rules! assert_eq_delta {
}
};
}
/// Represents the player's dominant hand.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Hand {
/// Usually the player's off-hand.
Left,
/// Usually the player's primary hand.
Right,
}
impl Hand {
pub fn all() -> [Self; 2] {
[Self::Right, Self::Left]
}
}
pub struct InvalidHand;
impl TryFrom<i32> for Hand {
type Error = InvalidHand;
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Left),
1 => Ok(Self::Right),
_ => Err(InvalidHand),
}
}
}

View File

@@ -2,7 +2,6 @@ use async_trait::async_trait;
use pumpkin_data::block_properties::{
BlockProperties, CactusLikeProperties, EnumVariants, Integer0To15,
};
use pumpkin_data::damage::DamageType;
use pumpkin_data::tag::Taggable;
use pumpkin_data::{Block, BlockDirection, tag};
use pumpkin_macros::pumpkin_block;
@@ -68,8 +67,9 @@ impl BlockBehaviour for CactusBlock {
}
}
async fn on_entity_collision(&self, args: OnEntityCollisionArgs<'_>) {
args.entity.damage(1.0, DamageType::CACTUS).await;
async fn on_entity_collision(&self, _args: OnEntityCollisionArgs<'_>) {
// TODO
//args.entity.damage(1.0, DamageType::CACTUS).await;
}
async fn get_state_for_neighbor_update(

View File

@@ -2,7 +2,6 @@ use async_trait::async_trait;
use pumpkin_data::{
Block, BlockDirection,
block_properties::{BlockProperties, CampfireLikeProperties},
damage::DamageType,
fluid::Fluid,
};
use pumpkin_world::{BlockStateId, tick::TickPriority};
@@ -37,7 +36,8 @@ impl BlockBehaviour for CampfireBlock {
if CampfireLikeProperties::from_state_id(args.state.id, args.block).lit
&& args.entity.get_living_entity().is_some()
{
args.entity.damage(1.0, DamageType::CAMPFIRE).await;
// TODO
//args.entity.damage(args.entity, 1.0, DamageType::CAMPFIRE).await;
}
}

View File

@@ -58,7 +58,7 @@ impl ArgumentConsumer for CommandTreeArgumentConsumer {
.commands
.keys()
.filter(|suggestion| suggestion.starts_with(input))
.map(|suggestion| CommandSuggestion::new(suggestion.to_string(), None))
.map(|suggestion| CommandSuggestion::new(suggestion.clone(), None))
.collect();
Ok(Some(suggestions))
}

View File

@@ -59,7 +59,7 @@ impl CommandExecutor for ReasonExecutor {
return Err(InvalidConsumption(Some(ARG_REASON.into())));
};
ban_player(sender, &targets[0], Some(reason.to_string())).await;
ban_player(sender, &targets[0], Some(reason.clone())).await;
Ok(())
}
}

View File

@@ -72,7 +72,7 @@ impl CommandExecutor for ReasonExecutor {
return Err(InvalidConsumption(Some(ARG_REASON.into())));
};
ban_ip(sender, server, target, Some(reason.to_string())).await;
ban_ip(sender, server, target, Some(reason.clone())).await;
Ok(())
}
}

View File

@@ -77,7 +77,7 @@ impl CommandExecutor for AddExecuter {
sender,
TextComponent::translate(
"commands.bossbar.create.failed",
[TextComponent::text(namespace.to_string())],
[TextComponent::text(namespace.clone())],
),
)
.await;
@@ -89,12 +89,12 @@ impl CommandExecutor for AddExecuter {
.bossbars
.lock()
.await
.create_bossbar(namespace.to_string(), bossbar.clone());
.create_bossbar(namespace.clone(), bossbar.clone());
sender
.send_message(TextComponent::translate(
"commands.bossbar.create.success",
[bossbar_prefix(bossbar.title.clone(), namespace.to_string())],
[bossbar_prefix(bossbar.title.clone(), namespace.clone())],
))
.await;
@@ -119,7 +119,7 @@ impl CommandExecutor for GetExecuter {
let Some(bossbar) = server.bossbars.lock().await.get_bossbar(&namespace) else {
handle_bossbar_error(
sender,
BossbarUpdateError::InvalidResourceLocation(namespace.to_string()),
BossbarUpdateError::InvalidResourceLocation(namespace.clone()),
)
.await;
return Ok(());
@@ -131,10 +131,7 @@ impl CommandExecutor for GetExecuter {
.send_message(TextComponent::translate(
"commands.bossbar.get.max",
[
bossbar_prefix(
bossbar.bossbar_data.title.clone(),
namespace.to_string(),
),
bossbar_prefix(bossbar.bossbar_data.title.clone(), namespace.clone()),
TextComponent::text(bossbar.max.to_string()),
],
))
@@ -147,10 +144,7 @@ impl CommandExecutor for GetExecuter {
.send_message(TextComponent::translate(
"commands.bossbar.get.value",
[
bossbar_prefix(
bossbar.bossbar_data.title.clone(),
namespace.to_string(),
),
bossbar_prefix(bossbar.bossbar_data.title.clone(), namespace.clone()),
TextComponent::text(bossbar.value.to_string()),
],
))
@@ -168,7 +162,7 @@ impl CommandExecutor for GetExecuter {
state,
[bossbar_prefix(
bossbar.bossbar_data.title.clone(),
namespace.to_string(),
namespace.clone(),
)],
))
.await;
@@ -215,13 +209,13 @@ impl CommandExecutor for ListExecuter {
if i == 0 {
bossbars_text = bossbars_text.add_child(bossbar_prefix(
bossbar.bossbar_data.title.clone(),
bossbar.namespace.to_string(),
bossbar.namespace.clone(),
));
} else {
bossbars_text =
bossbars_text.add_child(TextComponent::text(", ").add_child(bossbar_prefix(
bossbar.bossbar_data.title.clone(),
bossbar.namespace.to_string(),
bossbar.namespace.clone(),
)));
}
}
@@ -267,7 +261,7 @@ impl CommandExecutor for RemoveExecuter {
"commands.bossbar.remove.success",
[bossbar_prefix(
bossbar.bossbar_data.title.clone(),
namespace.to_string(),
namespace.clone(),
)],
))
.await;
@@ -276,7 +270,7 @@ impl CommandExecutor for RemoveExecuter {
.bossbars
.lock()
.await
.remove_bossbar(server, namespace.to_string())
.remove_bossbar(server, namespace.clone())
.await
{
Ok(()) => {}
@@ -470,7 +464,7 @@ impl CommandExecutor for SetExecuter {
namespace.to_string(),
),
TextComponent::text(count.to_string()),
TextComponent::text(player_names.join(", ").to_string()),
TextComponent::text(player_names.join(", ").clone()),
],
))
.await;

View File

@@ -89,7 +89,14 @@ impl CommandExecutor for LocationExecutor {
let location = Position3DArgumentConsumer::find_arg(args, ARG_LOCATION)?;
let success = target
.damage_with_context(amount, damage_type, Some(location), None, None)
.damage_with_context(
target.clone(),
amount,
damage_type,
Some(location),
None,
None,
)
.await;
send_damage_result(sender, success, amount, target.get_display_name().await).await;
@@ -134,6 +141,7 @@ impl CommandExecutor for EntityExecutor {
let success = target
.damage_with_context(
target.clone(),
amount,
damage_type,
None,

View File

@@ -154,9 +154,7 @@ pub fn snbt_colorful_display(tag: &NbtTag, depth: usize) -> Result<TextComponent
let item_display = snbt_colorful_display(item, depth + 1)
.map_err(|string| format!("Error displaying item.{key}: {string}"))?;
content = content
.add_child(
TextComponent::text(key.to_string()).color_named(NamedColor::Aqua),
)
.add_child(TextComponent::text(key.clone()).color_named(NamedColor::Aqua))
.add_child(TextComponent::text(": "))
.add_child(item_display);

View File

@@ -30,7 +30,7 @@ impl CommandExecutor for Executor {
let target_count = targets.len();
for target in targets {
target.kill().await;
target.kill(target.clone()).await;
}
let msg = if target_count == 1 {
@@ -62,7 +62,7 @@ impl CommandExecutor for SelfExecutor {
_args: &ConsumedArgs<'a>,
) -> Result<(), CommandError> {
let target = sender.as_player().ok_or(CommandError::InvalidRequirement)?;
target.kill().await;
target.kill(target.clone()).await;
sender
.send_message(TextComponent::translate(

View File

@@ -411,15 +411,13 @@ impl CommandDispatcher {
for name in names {
self.commands
.insert(name.to_string(), Command::Alias(primary_name.to_string()));
self.permissions
.insert(name.to_string(), permission.clone());
.insert(name.clone(), Command::Alias(primary_name.clone()));
self.permissions.insert(name.clone(), permission.clone());
}
self.permissions
.insert(primary_name.to_string(), permission);
self.permissions.insert(primary_name.clone(), permission);
self.commands
.insert(primary_name.to_string(), Command::Tree(tree));
.insert(primary_name.clone(), Command::Tree(tree));
}
/// Remove a command from the dispatcher by its primary name.

View File

@@ -1,5 +1,5 @@
use core::f32;
use std::sync::atomic::Ordering;
use std::sync::{Arc, atomic::Ordering};
use crate::entity::{Entity, EntityBase, NBTStorage, living::LivingEntity};
use async_trait::async_trait;
@@ -41,6 +41,7 @@ impl EntityBase for PaintingEntity {
async fn damage_with_context(
&self,
_caller: Arc<dyn EntityBase>,
_amount: f32,
_damage_type: DamageType,
_position: Option<Vector3<f64>>,

View File

@@ -5,7 +5,7 @@ use std::sync::{
};
use async_trait::async_trait;
use pumpkin_data::{damage::DamageType, entity::EntityType};
use pumpkin_data::entity::EntityType;
use pumpkin_util::math::vector3::Vector3;
use uuid::Uuid;
@@ -125,17 +125,6 @@ impl EntityBase for ExperienceOrbEntity {
}
}
async fn damage_with_context(
&self,
_amount: f32,
_damage_type: DamageType,
_position: Option<Vector3<f64>>,
_source: Option<&dyn EntityBase>,
_cause: Option<&dyn EntityBase>,
) -> bool {
false
}
fn get_living_entity(&self) -> Option<&LivingEntity> {
None
}

View File

@@ -1,8 +1,8 @@
use async_trait::async_trait;
use pumpkin_data::Block;
use pumpkin_data::{damage::DamageType, entity::EntityType};
use pumpkin_data::{entity::EntityType};
use pumpkin_protocol::java::client::play::{MetaDataType, Metadata};
use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
use pumpkin_util::math::{position::BlockPos};
use pumpkin_world::{BlockStateId, world::BlockFlags};
use std::sync::{Arc, atomic::Ordering};
use uuid::Uuid;
@@ -97,17 +97,6 @@ impl EntityBase for FallingEntity {
.await;
}
async fn damage_with_context(
&self,
_amount: f32,
_damage_type: DamageType,
_position: Option<Vector3<f64>>,
_source: Option<&dyn EntityBase>,
_cause: Option<&dyn EntityBase>,
) -> bool {
false
}
fn get_entity(&self) -> &Entity {
&self.entity
}

View File

@@ -1,3 +1,5 @@
use std::sync::Arc;
use super::{EntityBase, NBTStorage, NBTStorageInit, player::Player};
use async_trait::async_trait;
use crossbeam::atomic::AtomicCell;
@@ -27,7 +29,7 @@ impl Default for HungerManager {
}
impl HungerManager {
pub async fn tick(&self, player: &Player) {
pub async fn tick(&self, player: &Arc<Player>) {
let saturation = self.saturation.load();
let level = self.level.load();
let exhaustion = self.exhaustion.load();
@@ -68,7 +70,7 @@ impl HungerManager {
|| (difficulty == Difficulty::Hard)
|| (health > 1.0 && difficulty == Difficulty::Normal)
{
player.damage(1.0, DamageType::STARVE).await;
player.damage(player.clone(), 1.0, DamageType::STARVE).await;
}
self.tick_timer.store(0);
}

View File

@@ -312,7 +312,7 @@ impl EntityBase for ItemEntity {
2
};
if age % n == 0 && self.can_merge().await {
if age.is_multiple_of(n) && self.can_merge().await {
self.try_merge().await;
}
}
@@ -352,6 +352,7 @@ impl EntityBase for ItemEntity {
async fn damage_with_context(
&self,
_caller: Arc<dyn EntityBase>,
amount: f32,
_damage_type: DamageType,
_position: Option<Vector3<f64>>,
@@ -366,7 +367,12 @@ impl EntityBase for ItemEntity {
true
}
async fn damage(&self, _amount: f32, _damage_type: DamageType) -> bool {
async fn damage(
&self,
_caller: Arc<dyn EntityBase>,
_amount: f32,
_damage_type: DamageType,
) -> bool {
false
}

View File

@@ -1,4 +1,7 @@
use pumpkin_data::potion::Effect;
use pumpkin_inventory::build_equipment_slots;
use pumpkin_inventory::player::player_inventory::PlayerInventory;
use pumpkin_util::Hand;
use pumpkin_util::math::position::BlockPos;
use std::sync::Arc;
use std::sync::atomic::Ordering;
@@ -10,18 +13,17 @@ use std::{collections::HashMap, sync::atomic::AtomicI32};
use super::{Entity, NBTStorage};
use super::{EntityBase, NBTStorageInit};
use crate::entity::player::Hand;
use crate::server::Server;
use crate::world::loot::{LootContextParameters, LootTableExt};
use async_trait::async_trait;
use crossbeam::atomic::AtomicCell;
use pumpkin_config::advanced_config;
use pumpkin_data::Block;
use pumpkin_data::damage::DeathMessageType;
use pumpkin_data::data_component_impl::{EquipmentSlot, FoodImpl};
use pumpkin_data::data_component_impl::{DeathProtectionImpl, EquipmentSlot, FoodImpl};
use pumpkin_data::effect::StatusEffect;
use pumpkin_data::entity::{EntityPose, EntityStatus, EntityType};
use pumpkin_data::sound::SoundCategory;
use pumpkin_data::Block;
use pumpkin_data::{damage::DamageType, sound::Sound};
use pumpkin_inventory::entity_equipment::EntityEquipment;
use pumpkin_nbt::compound::NbtCompound;
@@ -59,6 +61,7 @@ pub struct LivingEntity {
pub active_effects: Mutex<HashMap<&'static StatusEffect, Effect>>,
pub entity_equipment: Arc<Mutex<EntityEquipment>>,
pub movement_input: AtomicCell<Vector3<f64>>,
pub equipment_slots: Arc<HashMap<usize, EquipmentSlot>>,
pub movement_speed: AtomicCell<f64>,
@@ -112,6 +115,7 @@ impl LivingEntity {
livings_flags: AtomicU8::new(0),
active_effects: Mutex::new(HashMap::new()),
entity_equipment: Arc::new(Mutex::new(EntityEquipment::new())),
equipment_slots: Arc::new(build_equipment_slots()),
jumping: AtomicBool::new(false),
jumping_cooldown: AtomicU8::new(0),
climbing: AtomicBool::new(false),
@@ -336,7 +340,7 @@ impl LivingEntity {
let suffocating = self.entity.tick_block_collisions(&caller, server).await;
if suffocating {
self.damage(1.0, DamageType::IN_WALL).await;
self.damage(caller, 1.0, DamageType::IN_WALL).await;
}
}
@@ -680,6 +684,7 @@ impl LivingEntity {
pub async fn update_fall_distance(
&self,
caller: Arc<dyn EntityBase>,
height_difference: f64,
ground: bool,
dont_damage: bool,
@@ -700,7 +705,7 @@ impl LivingEntity {
// TODO: Play block fall sound
if damage > 0.0 {
let check_damage = self.damage(damage, DamageType::FALL).await; // Fall
let check_damage = self.damage(caller, damage, DamageType::FALL).await; // Fall
if check_damage {
self.entity
.play_sound(Self::get_fall_sound(fall_distance as i32))
@@ -843,6 +848,56 @@ impl LivingEntity {
}
}
async fn try_use_death_protector(&self, caller: &Arc<dyn EntityBase>) -> bool {
for hand in Hand::all() {
let stack = self.get_stack_in_hand(caller, hand).await;
let mut stack = stack.lock().await;
// TODO: effects...
if stack.get_data_component::<DeathProtectionImpl>().is_some() {
stack.decrement(1);
self.set_health(1.0).await;
self.entity
.world
.send_entity_status(&self.entity, EntityStatus::UseTotemOfUndying)
.await;
return true;
}
}
false
}
pub async fn held_item(&self, caller: &Arc<dyn EntityBase>) -> Arc<Mutex<ItemStack>> {
if let Some(player) = caller.get_player() {
return player.inventory.held_item();
}
let slot = self
.equipment_slots
.get(&PlayerInventory::OFF_HAND_SLOT)
.unwrap();
self.entity_equipment.lock().await.get(slot)
}
pub async fn get_stack_in_hand(
&self,
caller: &Arc<dyn EntityBase>,
hand: Hand,
) -> Arc<Mutex<ItemStack>> {
match hand {
Hand::Left => self.off_hand_item().await,
Hand::Right => self.held_item(caller).await,
}
}
/// getOffHandStack in source
pub async fn off_hand_item(&self) -> Arc<Mutex<ItemStack>> {
let slot = self
.equipment_slots
.get(&PlayerInventory::OFF_HAND_SLOT)
.unwrap();
self.entity_equipment.lock().await.get(slot)
}
pub fn is_part_of_game(&self) -> bool {
self.is_spectator() && self.entity.is_alive()
}
@@ -914,6 +969,7 @@ impl NBTStorage for LivingEntity {
impl EntityBase for LivingEntity {
async fn damage_with_context(
&self,
caller: Arc<dyn EntityBase>,
amount: f32,
damage_type: DamageType,
position: Option<Vector3<f64>>,
@@ -996,7 +1052,7 @@ impl EntityBase for LivingEntity {
self.set_health(new_health).await;
}
if new_health <= 0.0 {
if new_health <= 0.0 && !self.try_use_death_protector(&caller).await {
self.on_death(damage_type, source, cause).await;
}

View File

@@ -113,6 +113,7 @@ where
async fn damage_with_context(
&self,
caller: Arc<dyn EntityBase>,
amount: f32,
damage_type: DamageType,
position: Option<Vector3<f64>>,
@@ -121,7 +122,7 @@ where
) -> bool {
self.get_mob_entity()
.living_entity
.damage_with_context(amount, damage_type, position, source, cause)
.damage_with_context(caller, amount, damage_type, position, source, cause)
.await
}

View File

@@ -106,8 +106,13 @@ pub trait EntityBase: Send + Sync + NBTStorage {
}
/// Returns if damage was successful or not
async fn damage(&self, amount: f32, damage_type: DamageType) -> bool {
self.damage_with_context(amount, damage_type, None, None, None)
async fn damage(
&self,
caller: Arc<dyn EntityBase>,
amount: f32,
damage_type: DamageType,
) -> bool {
self.damage_with_context(caller, amount, damage_type, None, None, None)
.await
}
@@ -129,12 +134,16 @@ pub trait EntityBase: Send + Sync + NBTStorage {
async fn damage_with_context(
&self,
amount: f32,
damage_type: DamageType,
position: Option<Vector3<f64>>,
source: Option<&dyn EntityBase>,
cause: Option<&dyn EntityBase>,
) -> bool;
_caller: Arc<dyn EntityBase>,
_amount: f32,
_damage_type: DamageType,
_position: Option<Vector3<f64>>,
_source: Option<&dyn EntityBase>,
_cause: Option<&dyn EntityBase>,
) -> bool {
// Just do nothing
false
}
/// Called when a player collides with a entity
async fn on_player_collision(&self, _player: &Arc<Player>) {}
@@ -181,9 +190,11 @@ pub trait EntityBase: Send + Sync + NBTStorage {
}
/// Kills the Entity.
async fn kill(&self) {
async fn kill(&self, caller: Arc<dyn EntityBase>) {
if let Some(living) = self.get_living_entity() {
living.damage(f32::MAX, DamageType::GENERIC_KILL).await;
living
.damage(caller, f32::MAX, DamageType::GENERIC_KILL)
.await;
} else {
// TODO this should be removed once all entities are implemented
self.get_entity().remove().await;
@@ -1174,7 +1185,12 @@ impl Entity {
if let Some(living) = caller.get_living_entity() {
living
.update_fall_distance(final_move.y, self.on_ground.load(Ordering::SeqCst), false)
.update_fall_distance(
caller.clone(),
final_move.y,
self.on_ground.load(Ordering::SeqCst),
false,
)
.await;
}
}
@@ -1694,10 +1710,12 @@ impl Entity {
vehicle.is_some()
}
pub async fn check_out_of_world(&self, dyn_self: &dyn EntityBase) {
pub async fn check_out_of_world(&self, dyn_self: Arc<dyn EntityBase>) {
if self.pos.load().y < f64::from(self.world.generation_settings().shape.min_y) - 64.0 {
// Tick out of world damage
dyn_self.damage(4.0, DamageType::OUT_OF_WORLD).await;
dyn_self
.damage(dyn_self.clone(), 4.0, DamageType::OUT_OF_WORLD)
.await;
}
}
@@ -1792,21 +1810,10 @@ impl NBTStorage for Entity {
#[async_trait]
impl EntityBase for Entity {
async fn damage_with_context(
&self,
_amount: f32,
_damage_type: DamageType,
_position: Option<Vector3<f64>>,
_source: Option<&dyn EntityBase>,
_cause: Option<&dyn EntityBase>,
) -> bool {
false
}
async fn tick(&self, caller: Arc<dyn EntityBase>, _server: &Server) {
self.tick_portal(&caller).await;
self.update_fluid_state(&caller).await;
self.check_out_of_world(&*caller).await;
self.check_out_of_world(caller.clone()).await;
let fire_ticks = self.fire_ticks.load(Ordering::Relaxed);
if fire_ticks > 0 {
if self.entity_type.fire_immune {
@@ -1816,7 +1823,9 @@ impl EntityBase for Entity {
}
} else {
if fire_ticks % 20 == 0 {
caller.damage(1.0, DamageType::ON_FIRE).await;
caller
.damage(caller.clone(), 1.0, DamageType::ON_FIRE)
.await;
}
self.fire_ticks.store(fire_ticks - 1, Ordering::Relaxed);

View File

@@ -53,7 +53,6 @@ use pumpkin_protocol::java::client::play::{
};
use pumpkin_protocol::java::server::play::SClickSlot;
use pumpkin_registry::VanillaDimensionType;
use pumpkin_util::GameMode;
use pumpkin_util::math::{
boundingbox::BoundingBox, experience, position::BlockPos, vector2::Vector2, vector3::Vector3,
};
@@ -62,6 +61,7 @@ use pumpkin_util::resource_location::ResourceLocation;
use pumpkin_util::text::TextComponent;
use pumpkin_util::text::click::ClickEvent;
use pumpkin_util::text::hover::HoverEvent;
use pumpkin_util::{GameMode, Hand};
use pumpkin_world::biome;
use pumpkin_world::cylindrical_chunk_iterator::Cylindrical;
use pumpkin_world::entity::entity_data_flags::{
@@ -308,7 +308,10 @@ impl Player {
matches!(gamemode, GameMode::Creative | GameMode::Spectator),
));
let inventory = Arc::new(PlayerInventory::new(living_entity.entity_equipment.clone()));
let inventory = Arc::new(PlayerInventory::new(
living_entity.entity_equipment.clone(),
living_entity.equipment_slots.clone(),
));
let player_screen_handler = Arc::new(Mutex::new(
PlayerScreenHandler::new(&inventory, None, 0).await,
@@ -492,6 +495,7 @@ impl Player {
if !victim
.damage_with_context(
victim.clone(),
damage as f32,
DamageType::PLAYER_ATTACK,
None,
@@ -829,7 +833,7 @@ impl Player {
self.last_attacked_ticks.fetch_add(1, Ordering::Relaxed);
self.living_entity.tick(self.clone(), server).await;
self.hunger_manager.tick(self.as_ref()).await;
self.hunger_manager.tick(self).await;
// experience handling
self.tick_experience().await;
@@ -2052,6 +2056,7 @@ impl NBTStorageInit for PlayerInventory {}
impl EntityBase for Player {
async fn damage_with_context(
&self,
caller: Arc<dyn EntityBase>,
amount: f32,
damage_type: DamageType,
position: Option<Vector3<f64>>,
@@ -2070,7 +2075,7 @@ impl EntityBase for Player {
.expect("Entity not found in world");
let result = self
.living_entity
.damage_with_context(amount, damage_type, position, source, cause)
.damage_with_context(caller, amount, damage_type, position, source, cause)
.await;
if result {
let health = self.living_entity.health.load();
@@ -2258,29 +2263,6 @@ impl Abilities {
}
}
/// Represents the player's dominant hand.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Hand {
/// Usually the player's off-hand.
Left,
/// Usually the player's primary hand.
Right,
}
pub struct InvalidHand;
impl TryFrom<i32> for Hand {
type Error = InvalidHand;
fn try_from(value: i32) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Left),
1 => Ok(Self::Right),
_ => Err(InvalidHand),
}
}
}
/// Represents the player's respawn point.
#[derive(Copy, Debug, Clone, PartialEq)]
pub struct RespawnPoint {

View File

@@ -5,7 +5,6 @@ use std::{
use super::{Entity, EntityBase, NBTStorage, living::LivingEntity};
use async_trait::async_trait;
use pumpkin_data::damage::DamageType;
use pumpkin_util::math::vector3::Vector3;
pub struct ThrownItemEntity {
@@ -85,17 +84,6 @@ impl EntityBase for ThrownItemEntity {
&self.entity
}
async fn damage_with_context(
&self,
_amount: f32,
_damage_type: DamageType,
_position: Option<Vector3<f64>>,
_source: Option<&dyn EntityBase>,
_cause: Option<&dyn EntityBase>,
) -> bool {
false
}
fn get_living_entity(&self) -> Option<&LivingEntity> {
None
}

View File

@@ -2,7 +2,7 @@ use super::{Entity, EntityBase, NBTStorage, living::LivingEntity};
use crate::server::Server;
use async_trait::async_trait;
use core::f32;
use pumpkin_data::{Block, damage::DamageType};
use pumpkin_data::{Block};
use pumpkin_protocol::{
codec::var_int::VarInt,
java::client::play::{MetaDataType, Metadata},
@@ -96,17 +96,6 @@ impl EntityBase for TNTEntity {
.await;
}
async fn damage_with_context(
&self,
_amount: f32,
_damage_type: DamageType,
_position: Option<Vector3<f64>>,
_source: Option<&dyn EntityBase>,
_cause: Option<&dyn EntityBase>,
) -> bool {
false
}
fn get_entity(&self) -> &Entity {
&self.entity
}

View File

@@ -18,7 +18,6 @@
#![deny(clippy::needless_pass_by_ref_mut)]
#![deny(clippy::needless_collect)]
#![deny(clippy::redundant_clone)]
#![deny(clippy::branches_sharing_code)]
#![deny(clippy::set_contains_or_insert)]
#![deny(clippy::significant_drop_in_scrutinee)]
// use log crate

View File

@@ -1,7 +1,7 @@
use std::{num::NonZeroU8, sync::Arc};
use crate::{
entity::player::{ChatMode, Hand},
entity::player::ChatMode,
net::{ClientPlatform, PlayerConfig, can_not_join, java::JavaClient},
server::Server,
};
@@ -15,7 +15,7 @@ use pumpkin_protocol::{
SConfigResourcePack, SKnownPacks, SPluginMessage,
},
};
use pumpkin_util::text::TextComponent;
use pumpkin_util::{Hand, text::TextComponent};
impl JavaClient {
pub async fn handle_client_information_config(

View File

@@ -1,5 +1,5 @@
use pumpkin_protocol::bedrock::server::text::SText;
use pumpkin_util::PermissionLvl;
use pumpkin_util::{Hand, PermissionLvl};
use rsa::pkcs1v15::{Signature as RsaPkcs1v15Signature, VerifyingKey};
use rsa::signature::Verifier;
use sha1::Sha1;
@@ -14,7 +14,7 @@ use crate::block::registry::BlockActionResult;
use crate::block::{self, BlockIsReplacing};
use crate::command::CommandSender;
use crate::entity::EntityBase;
use crate::entity::player::{ChatMode, ChatSession, Hand, Player};
use crate::entity::player::{ChatMode, ChatSession, Player};
use crate::entity::r#type::from_type;
use crate::error::PumpkinError;
use crate::net::PlayerConfig;
@@ -327,6 +327,7 @@ impl JavaClient {
if !player.abilities.lock().await.flying {
player.living_entity
.update_fall_distance(
player.clone(),
height_difference,
packet.collision & FLAG_ON_GROUND != 0,
player.gamemode.load() == GameMode::Creative,
@@ -451,6 +452,7 @@ impl JavaClient {
if !player.abilities.lock().await.flying {
player.living_entity
.update_fall_distance(
player.clone(),
height_difference,
(packet.collision & FLAG_ON_GROUND) != 0,
player.gamemode.load() == GameMode::Creative,

View File

@@ -9,13 +9,13 @@ use crate::{
banned_ip_data::BANNED_IP_LIST, banned_player_data::BANNED_PLAYER_LIST,
op_data::OPERATOR_CONFIG, whitelist_data::WHITELIST_CONFIG,
},
entity::player::{ChatMode, Hand},
entity::player::ChatMode,
net::{bedrock::BedrockClient, java::JavaClient},
server::Server,
};
use pumpkin_protocol::{ClientPacket, Property};
use pumpkin_util::{ProfileAction, text::TextComponent};
use pumpkin_util::{Hand, ProfileAction, text::TextComponent};
use serde::Deserialize;
use sha1::Digest;
use sha2::Sha256;