fix: 26.1 attack packet

This commit is contained in:
Alexander Medvedev 2026-04-02 20:55:03 +02:00
parent 7f95d43982
commit 8a6698cd21
18 changed files with 164 additions and 89 deletions

View File

@ -110,6 +110,13 @@ impl ItemStack {
patch: Vec::new(), patch: Vec::new(),
}; };
pub fn split_off(&mut self, amount: u8) -> Self {
let count = amount.min(self.item_count);
let result = self.copy_with_count(count);
self.decrement(count);
result
}
#[must_use] #[must_use]
pub fn get_max_stack_size(&self) -> u8 { pub fn get_max_stack_size(&self) -> u8 {
self.get_data_component::<MaxStackSizeImpl>() self.get_data_component::<MaxStackSizeImpl>()

View File

@ -1,7 +1,7 @@
use std::{any::Any, pin::Pin, sync::Arc}; use std::{any::Any, pin::Pin, sync::Arc};
use pumpkin_data::screen::WindowType;
use pumpkin_data::tag::Taggable; use pumpkin_data::tag::Taggable;
use pumpkin_data::{screen::WindowType, tag};
use pumpkin_world::{block::entities::PropertyDelegate, inventory::Inventory}; use pumpkin_world::{block::entities::PropertyDelegate, inventory::Inventory};
use crate::{ use crate::{
@ -122,10 +122,7 @@ impl ScreenHandler for BrewingScreenHandler {
.is_some(); .is_some();
// Check if item is brewing fuel (for slot 4) // Check if item is brewing fuel (for slot 4)
let is_fuel = stack let is_fuel = stack.get_item().has_tag(&tag::Item::MINECRAFT_BREWING_FUEL);
.get_item()
.is_tagged_with("minecraft:brewing_fuel")
.is_some_and(|b| b);
if has_potion_contents { if has_potion_contents {
// Try to insert into potion slots (0-2) // Try to insert into potion slots (0-2)

View File

@ -13,9 +13,9 @@ use serde::{
}; };
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct Velocity(pub Vector3<f64>); pub struct LpVector3d(pub Vector3<f64>);
impl Velocity { impl LpVector3d {
pub fn write<W: std::io::Write>(&self, writer: &mut W) -> Result<(), WritingError> { pub fn write<W: std::io::Write>(&self, writer: &mut W) -> Result<(), WritingError> {
let velocity = self.0; let velocity = self.0;
@ -153,7 +153,7 @@ fn from_long(quantized: i64, scale: f64) -> f64 {
(normalized / 0.5) * scale (normalized / 0.5) * scale
} }
impl Serialize for Velocity { impl Serialize for LpVector3d {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
let mut buf = Vec::new(); let mut buf = Vec::new();
self.write(&mut buf).unwrap(); self.write(&mut buf).unwrap();
@ -161,7 +161,7 @@ impl Serialize for Velocity {
} }
} }
impl<'de> de::Deserialize<'de> for Velocity { impl<'de> de::Deserialize<'de> for LpVector3d {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
@ -169,7 +169,7 @@ impl<'de> de::Deserialize<'de> for Velocity {
struct VelocityVisitor; struct VelocityVisitor;
impl Visitor<'_> for VelocityVisitor { impl Visitor<'_> for VelocityVisitor {
type Value = Velocity; type Value = LpVector3d;
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("a byte array representing bit-packed velocity") formatter.write_str("a byte array representing bit-packed velocity")
@ -180,7 +180,7 @@ impl<'de> de::Deserialize<'de> for Velocity {
E: de::Error, E: de::Error,
{ {
let mut cursor = std::io::Cursor::new(v); let mut cursor = std::io::Cursor::new(v);
Velocity::read(&mut cursor).map_err(de::Error::custom) LpVector3d::read(&mut cursor).map_err(de::Error::custom)
} }
} }
@ -190,7 +190,7 @@ impl<'de> de::Deserialize<'de> for Velocity {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{Velocity, encode_legacy_velocity_component}; use super::{LpVector3d, encode_legacy_velocity_component};
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
#[test] #[test]
@ -203,7 +203,7 @@ mod tests {
#[test] #[test]
fn write_legacy_writes_three_i16_be_components() { fn write_legacy_writes_three_i16_be_components() {
let velocity = Velocity(Vector3::new(0.5, -0.5, 0.0)); let velocity = LpVector3d(Vector3::new(0.5, -0.5, 0.0));
let mut buf = Vec::new(); let mut buf = Vec::new();
velocity.write_legacy(&mut buf).unwrap(); velocity.write_legacy(&mut buf).unwrap();
assert_eq!(buf, vec![0x0F, 0xA0, 0xF0, 0x60, 0x00, 0x00]); assert_eq!(buf, vec![0x0F, 0xA0, 0xF0, 0x60, 0x00, 0x00]);

View File

@ -4,6 +4,7 @@ pub mod bitset;
pub mod data_component; pub mod data_component;
pub mod item_stack_seralizer; pub mod item_stack_seralizer;
pub mod little_endian; pub mod little_endian;
pub mod lp_vector_3d;
pub mod optional_int; pub mod optional_int;
mod u24_type; mod u24_type;
pub mod uuid; pub mod uuid;
@ -11,6 +12,5 @@ pub mod var_int;
pub mod var_long; pub mod var_long;
pub mod var_uint; pub mod var_uint;
pub mod var_ulong; pub mod var_ulong;
pub mod velocity;
pub use u24_type::u24; pub use u24_type::u24;

View File

@ -4,7 +4,9 @@ use pumpkin_data::packet::clientbound::PLAY_SET_ENTITY_MOTION;
use pumpkin_macros::java_packet; use pumpkin_macros::java_packet;
use pumpkin_util::{math::vector3::Vector3, version::MinecraftVersion}; use pumpkin_util::{math::vector3::Vector3, version::MinecraftVersion};
use crate::{ClientPacket, VarInt, WritingError, codec::velocity::Velocity, ser::NetworkWriteExt}; use crate::{
ClientPacket, VarInt, WritingError, codec::lp_vector_3d::LpVector3d, ser::NetworkWriteExt,
};
/// Updates the velocity of an entity. /// Updates the velocity of an entity.
/// ///
@ -15,7 +17,7 @@ pub struct CEntityVelocity {
/// The Entity ID of the entity whose velocity is being set /// The Entity ID of the entity whose velocity is being set
pub entity_id: VarInt, pub entity_id: VarInt,
/// The velocity vector /// The velocity vector
pub velocity: Velocity, pub velocity: LpVector3d,
} }
impl CEntityVelocity { impl CEntityVelocity {
@ -23,7 +25,7 @@ impl CEntityVelocity {
pub const fn new(entity_id: VarInt, velocity: Vector3<f64>) -> Self { pub const fn new(entity_id: VarInt, velocity: Vector3<f64>) -> Self {
Self { Self {
entity_id, entity_id,
velocity: Velocity(velocity), velocity: LpVector3d(velocity),
} }
} }
} }
@ -56,7 +58,7 @@ mod tests {
use super::CEntityVelocity; use super::CEntityVelocity;
use crate::{ use crate::{
ClientPacket, VarInt, ClientPacket, VarInt,
codec::velocity::{Velocity, encode_legacy_velocity_component}, codec::lp_vector_3d::{LpVector3d, encode_legacy_velocity_component},
}; };
fn encode_packet(version: MinecraftVersion) -> Vec<u8> { fn encode_packet(version: MinecraftVersion) -> Vec<u8> {
@ -95,7 +97,7 @@ mod tests {
// Ensure the packed bytes can still be decoded back to a velocity. // Ensure the packed bytes can still be decoded back to a velocity.
let mut cursor = std::io::Cursor::new(&encoded[1..]); let mut cursor = std::io::Cursor::new(&encoded[1..]);
let decoded = Velocity::read(&mut cursor).unwrap(); let decoded = LpVector3d::read(&mut cursor).unwrap();
assert!(decoded.0.x.is_finite()); assert!(decoded.0.x.is_finite());
assert!(decoded.0.y.is_finite()); assert!(decoded.0.y.is_finite());
assert!(decoded.0.z.is_finite()); assert!(decoded.0.z.is_finite());

View File

@ -9,7 +9,7 @@ use pumpkin_util::{math::vector3::Vector3, version::MinecraftVersion};
use crate::{ use crate::{
ClientPacket, VarInt, ClientPacket, VarInt,
codec::velocity::Velocity, codec::lp_vector_3d::LpVector3d,
ser::{NetworkWriteExt, WritingError}, ser::{NetworkWriteExt, WritingError},
}; };
@ -19,7 +19,7 @@ pub struct CSpawnEntity {
pub entity_uuid: uuid::Uuid, pub entity_uuid: uuid::Uuid,
pub r#type: VarInt, pub r#type: VarInt,
pub position: Vector3<f64>, pub position: Vector3<f64>,
pub velocity: Velocity, pub velocity: LpVector3d,
pub pitch: u8, // angle pub pitch: u8, // angle
pub yaw: u8, // angle pub yaw: u8, // angle
pub head_yaw: u8, // angle pub head_yaw: u8, // angle
@ -49,7 +49,7 @@ impl CSpawnEntity {
yaw: (yaw.rem_euclid(360.0) * 256.0 / 360.0).floor() as u8, yaw: (yaw.rem_euclid(360.0) * 256.0 / 360.0).floor() as u8,
head_yaw: (head_yaw.rem_euclid(360.0) * 256.0 / 360.0).floor() as u8, head_yaw: (head_yaw.rem_euclid(360.0) * 256.0 / 360.0).floor() as u8,
data, data,
velocity: Velocity(velocity), velocity: LpVector3d(velocity),
} }
} }
} }
@ -100,7 +100,7 @@ impl ClientPacket for CSpawnEntity {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::CSpawnEntity; use super::CSpawnEntity;
use crate::{ClientPacket, VarInt, codec::velocity::encode_legacy_velocity_component}; use crate::{ClientPacket, VarInt, codec::lp_vector_3d::encode_legacy_velocity_component};
use pumpkin_util::version::MinecraftVersion; use pumpkin_util::version::MinecraftVersion;
fn legacy_tail(velocity: pumpkin_util::math::vector3::Vector3<f64>) -> [u8; 6] { fn legacy_tail(velocity: pumpkin_util::math::vector3::Vector3<f64>) -> [u8; 6] {

View File

@ -0,0 +1,11 @@
use pumpkin_data::packet::serverbound::PLAY_ATTACK;
use pumpkin_macros::java_packet;
use serde::Deserialize;
use crate::codec::var_int::VarInt;
#[derive(Deserialize)]
#[java_packet(PLAY_ATTACK)]
pub struct SAttack {
pub entity_id: VarInt,
}

View File

@ -6,7 +6,7 @@ use pumpkin_util::{math::vector3::Vector3, version::MinecraftVersion};
use crate::{ use crate::{
ServerPacket, ServerPacket,
codec::var_int::VarInt, codec::{lp_vector_3d::LpVector3d, var_int::VarInt},
ser::{NetworkReadExt, ReadingError}, ser::{NetworkReadExt, ReadingError},
}; };
@ -14,26 +14,42 @@ use crate::{
pub struct SInteract { pub struct SInteract {
pub entity_id: VarInt, pub entity_id: VarInt,
pub r#type: VarInt, pub r#type: VarInt,
pub target_position: Option<Vector3<f32>>, pub target_position: Option<Vector3<f64>>,
pub hand: Option<VarInt>, pub hand: Option<VarInt>,
pub sneaking: bool, pub sneaking: bool,
} }
// Great job Mojang ;D // Great job Mojang ;D
impl ServerPacket for SInteract { impl ServerPacket for SInteract {
fn read(mut read: impl Read, _version: &MinecraftVersion) -> Result<Self, ReadingError> { fn read(mut read: impl Read, version: &MinecraftVersion) -> Result<Self, ReadingError> {
// 26.1+ removes the 'type' field and uses doubles for location
if version >= &MinecraftVersion::V_26_1 {
let entity_id = read.get_var_int()?;
let hand = Some(read.get_var_int()?);
let target_position = Some(LpVector3d::read(&mut read)?.0);
let sneaking = read.get_bool()?;
return Ok(Self {
entity_id,
r#type: VarInt(2), // InteractAt for compatibility
target_position,
hand,
sneaking,
});
}
let entity_id = read.get_var_int()?; let entity_id = read.get_var_int()?;
let r#type = read.get_var_int()?; let r#type = read.get_var_int()?;
let action = ActionType::try_from(r#type.0) let action = ActionType::try_from(r#type.0)
.map_err(|_| ReadingError::Message("invalid action type".to_string()))?; .map_err(|_| ReadingError::Message("invalid action type".to_string()))?;
let target_position: Option<Vector3<f32>> = match action {
let target_position: Option<Vector3<f64>> = match action {
ActionType::Interact | ActionType::Attack => None, ActionType::Interact | ActionType::Attack => None,
ActionType::InteractAt => Some(Vector3::new( ActionType::InteractAt => Some(
read.get_f32_be()?, Vector3::new(read.get_f32_be()?, read.get_f32_be()?, read.get_f32_be()?).to_f64(),
read.get_f32_be()?, ),
read.get_f32_be()?,
)),
}; };
let hand = match action { let hand = match action {
ActionType::Interact | ActionType::InteractAt => Some(read.get_var_int()?), ActionType::Interact | ActionType::InteractAt => Some(read.get_var_int()?),
ActionType::Attack => None, ActionType::Attack => None,

View File

@ -1,3 +1,4 @@
mod attack;
mod change_game_mode; mod change_game_mode;
mod chat_command; mod chat_command;
mod chat_message; mod chat_message;
@ -35,6 +36,7 @@ mod update_sign;
mod use_item; mod use_item;
mod use_item_on; mod use_item_on;
pub use attack::*;
pub use change_game_mode::*; pub use change_game_mode::*;
pub use chat_command::*; pub use chat_command::*;
pub use chat_message::*; pub use chat_message::*;

View File

@ -15,7 +15,7 @@ use pumpkin_data::item::Item;
use pumpkin_data::item_stack::ItemStack; use pumpkin_data::item_stack::ItemStack;
use pumpkin_data::potion_brewing::{ITEM_RECIPES, POTION_RECIPES}; use pumpkin_data::potion_brewing::{ITEM_RECIPES, POTION_RECIPES};
use pumpkin_data::sound::{Sound, SoundCategory}; use pumpkin_data::sound::{Sound, SoundCategory};
use pumpkin_data::tag::Taggable; use pumpkin_data::tag::{self, Taggable};
use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::compound::NbtCompound;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
@ -294,20 +294,14 @@ impl crate::inventory::Inventory for BrewingStandBlockEntity {
// Slot 3 - ingredient (must be tagged as brewable) // Slot 3 - ingredient (must be tagged as brewable)
3 => { 3 => {
// Check if item is a valid brewing ingredient // Check if item is a valid brewing ingredient
if let Some(is_brewing_fuel) = if stack.get_item().has_tag(&tag::Item::MINECRAFT_BREWING_FUEL) {
stack.get_item().is_tagged_with("minecraft:brewing_fuel")
&& is_brewing_fuel
{
return false; // Fuel should not go in ingredient slot return false; // Fuel should not go in ingredient slot
} }
// Allow any item that's not fuel (ingredient validation happens during brewing) // Allow any item that's not fuel (ingredient validation happens during brewing)
true true
} }
// Slot 4 - fuel // Slot 4 - fuel
4 => stack 4 => stack.get_item().has_tag(&tag::Item::MINECRAFT_BREWING_FUEL),
.get_item()
.is_tagged_with("minecraft:brewing_fuel")
.is_some_and(|b| b),
_ => false, _ => false,
} }
} }
@ -422,8 +416,7 @@ impl crate::block::entities::BlockEntity for BrewingStandBlockEntity {
if !fuel_stack.is_empty() if !fuel_stack.is_empty()
&& fuel_stack && fuel_stack
.get_item() .get_item()
.is_tagged_with("minecraft:brewing_fuel") .has_tag(&tag::Item::MINECRAFT_BREWING_FUEL)
.is_some_and(|b| b)
{ {
self.fuel.store(20, Ordering::Relaxed); self.fuel.store(20, Ordering::Relaxed);
fuel_stack.decrement(1); fuel_stack.decrement(1);

View File

@ -14,7 +14,6 @@ pub mod positions;
pub mod proto_chunk; pub mod proto_chunk;
pub mod proto_chunk_test; pub mod proto_chunk_test;
pub mod rule; pub mod rule;
mod rule_test;
pub mod structure; pub mod structure;
mod surface; mod surface;

View File

@ -1,41 +0,0 @@
use pumpkin_data::{Block, tag::Taggable};
use serde::Deserialize;
/// Rule tests are used in structure or features generation to check if a block state matches some condition.
#[derive(Deserialize)]
pub enum RuleTest {
TagMatch(TagMatchTest),
BlockMatch(BlockMatchRuleTest),
// TODO: add more
}
pub struct AlwaysTrueRuleTest;
impl AlwaysTrueRuleTest {
pub const fn test() -> bool {
true
}
}
#[derive(Deserialize)]
pub struct BlockMatchRuleTest {
block: String,
}
impl BlockMatchRuleTest {
pub fn test(&self, block: &'static Block) -> bool {
let test_block = Block::from_registry_key(&self.block).expect("Failed to find block");
test_block == block
}
}
#[derive(Deserialize)]
pub struct TagMatchTest {
tag: String,
}
impl TagMatchTest {
pub fn test(&self, block: &'static Block) -> bool {
block.is_tagged_with(&self.tag).unwrap()
}
}

View File

@ -0,0 +1,26 @@
use std::sync::Arc;
use crate::entity::{
Entity, NBTStorage,
mob::{Mob, MobEntity},
};
pub struct EnderDragonEntity {
pub mob_entity: MobEntity,
}
impl EnderDragonEntity {
pub fn new(entity: Entity) -> Arc<Self> {
let mob_entity = MobEntity::new(entity);
let dragon = Self { mob_entity };
Arc::new(dragon)
}
}
impl NBTStorage for EnderDragonEntity {}
impl Mob for EnderDragonEntity {
fn get_mob_entity(&self) -> &MobEntity {
&self.mob_entity
}
}

View File

@ -1 +1,2 @@
pub mod ender_dragon;
pub mod wither; pub mod wither;

View File

@ -4,6 +4,7 @@ use pumpkin_data::entity::EntityType;
use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
use uuid::Uuid; use uuid::Uuid;
use crate::entity::boss::ender_dragon::EnderDragonEntity;
use crate::entity::mob::zombie::zombie_villager::ZombieVillagerEntity; use crate::entity::mob::zombie::zombie_villager::ZombieVillagerEntity;
use crate::{ use crate::{
entity::{ entity::{
@ -66,7 +67,10 @@ pub async fn from_type(
id if id == EntityType::IRON_GOLEM.id => IronGolemEntity::new(entity).await, id if id == EntityType::IRON_GOLEM.id => IronGolemEntity::new(entity).await,
id if id == EntityType::SHEEP.id => SheepEntity::new(entity).await, id if id == EntityType::SHEEP.id => SheepEntity::new(entity).await,
id if id == EntityType::WOLF.id => WolfEntity::new(entity).await, id if id == EntityType::WOLF.id => WolfEntity::new(entity).await,
id if id == EntityType::WITHER.id => WitherEntity::new(entity).await, id if id == EntityType::WITHER.id => WitherEntity::new(entity).await,
id if id == EntityType::ENDER_DRAGON.id => EnderDragonEntity::new(entity),
id if id == EntityType::AREA_EFFECT_CLOUD.id => { id if id == EntityType::AREA_EFFECT_CLOUD.id => {
crate::entity::area_effect_cloud::AreaEffectCloudEntity::new(entity) crate::entity::area_effect_cloud::AreaEffectCloudEntity::new(entity)
} }

View File

@ -6,7 +6,7 @@ use crossbeam::atomic::AtomicCell;
use pumpkin_config::networking::compression::CompressionInfo; use pumpkin_config::networking::compression::CompressionInfo;
use pumpkin_data::packet::CURRENT_MC_VERSION; use pumpkin_data::packet::CURRENT_MC_VERSION;
use pumpkin_protocol::java::server::play::{ use pumpkin_protocol::java::server::play::{
SChangeGameMode, SChatCommand, SChatMessage, SChunkBatch, SClickSlot, SClientCommand, SAttack, SChangeGameMode, SChatCommand, SChatMessage, SChunkBatch, SClickSlot, SClientCommand,
SClientInformationPlay, SClientTickEnd, SCloseContainer, SCommandSuggestion, SConfirmTeleport, SClientInformationPlay, SClientTickEnd, SCloseContainer, SCommandSuggestion, SConfirmTeleport,
SCookieResponse as SPCookieResponse, SCustomPayload, SInteract, SKeepAlive, SMoveVehicle, SCookieResponse as SPCookieResponse, SCustomPayload, SInteract, SKeepAlive, SMoveVehicle,
SPaddleBoat, SPickItemFromBlock, SPlayPingRequest, SPlayerAbilities, SPlayerAction, SPaddleBoat, SPickItemFromBlock, SPlayPingRequest, SPlayerAbilities, SPlayerAction,
@ -727,6 +727,10 @@ impl JavaClient {
self.handle_interact(player, SInteract::read(payload, &version)?, server) self.handle_interact(player, SInteract::read(payload, &version)?, server)
.await; .await;
} }
id if id == SAttack::to_id(version) => {
self.handle_attack(player, SAttack::read(payload, &version)?, server)
.await;
}
id if id == SKeepAlive::to_id(version) => { id if id == SKeepAlive::to_id(version) => {
self.handle_keep_alive(player, SKeepAlive::read(payload, &version)?) self.handle_keep_alive(player, SKeepAlive::read(payload, &version)?)
.await; .await;

View File

@ -52,7 +52,7 @@ use pumpkin_protocol::java::client::play::{
CUpdateEntityPos, CUpdateEntityPosRot, CUpdateEntityRot, InitChat, PlayerAction, CUpdateEntityPos, CUpdateEntityPosRot, CUpdateEntityRot, InitChat, PlayerAction,
}; };
use pumpkin_protocol::java::server::play::{ use pumpkin_protocol::java::server::play::{
Action, ActionType, CommandBlockMode, FLAG_ON_GROUND, SChangeGameMode, SChatCommand, Action, ActionType, CommandBlockMode, FLAG_ON_GROUND, SAttack, SChangeGameMode, SChatCommand,
SChatMessage, SChunkBatch, SClientCommand, SClientInformationPlay, SCloseContainer, SChatMessage, SChunkBatch, SClientCommand, SClientInformationPlay, SCloseContainer,
SCommandSuggestion, SConfirmTeleport, SCookieResponse as SPCookieResponse, SInteract, SCommandSuggestion, SConfirmTeleport, SCookieResponse as SPCookieResponse, SInteract,
SKeepAlive, SMoveVehicle, SPaddleBoat, SPickItemFromBlock, SPlayPingRequest, SPlayerAbilities, SKeepAlive, SMoveVehicle, SPaddleBoat, SPickItemFromBlock, SPlayPingRequest, SPlayerAbilities,
@ -1249,6 +1249,60 @@ impl JavaClient {
} }
} }
pub async fn handle_attack(&self, player: &Arc<Player>, attack: SAttack, server: &Arc<Server>) {
if !player.has_client_loaded() {
return;
}
player.update_last_action_time();
let entity_id = attack.entity_id;
let player_entity = &player.living_entity.entity;
let world = player_entity.world.load_full();
let config = &server.advanced_config.pvp;
if !config.enabled {
return;
}
if entity_id.0 == player.entity_id() {
self.kick(TextComponent::translate(
translation::MULTIPLAYER_DISCONNECT_INVALID_ENTITY_ATTACKED,
[],
))
.await;
return;
}
let player_target = world.get_player_by_id(entity_id.0);
let target: Option<Arc<dyn EntityBase>> = player_target
.as_ref()
.map(|p| Arc::clone(p) as Arc<dyn EntityBase>)
.or_else(|| world.get_entity_by_id(entity_id.0));
let Some(target) = target else {
self.kick(TextComponent::translate(
translation::MULTIPLAYER_DISCONNECT_INVALID_ENTITY_ATTACKED,
[],
))
.await;
return;
};
if let Some(player_victim) = &player_target {
if player_victim.living_entity.health.load() <= 0.0 {
return;
}
if config.protect_creative && player_victim.gamemode.load() == GameMode::Creative {
world
.play_sound(
Sound::EntityPlayerAttackNodamage,
SoundCategory::Players,
&player_victim.position(),
)
.await;
return;
}
}
player.attack(target).await;
}
pub async fn handle_interact( pub async fn handle_interact(
&self, &self,
player: &Arc<Player>, player: &Arc<Player>,

View File

@ -26,7 +26,7 @@ pub struct PlayerInteractEntityEvent {
pub action: ActionType, pub action: ActionType,
/// The position on the entity that was clicked (only for `InteractAt`). /// The position on the entity that was clicked (only for `InteractAt`).
pub target_position: Option<Vector3<f32>>, pub target_position: Option<Vector3<f64>>,
/// Whether the player was sneaking during the interaction. /// Whether the player was sneaking during the interaction.
pub sneaking: bool, pub sneaking: bool,
@ -37,7 +37,7 @@ impl PlayerInteractEntityEvent {
player: &Arc<Player>, player: &Arc<Player>,
target: Arc<dyn EntityBase>, target: Arc<dyn EntityBase>,
action: ActionType, action: ActionType,
target_position: Option<Vector3<f32>>, target_position: Option<Vector3<f64>>,
sneaking: bool, sneaking: bool,
) -> Self { ) -> Self {
Self { Self {