fix: 26.1 attack packet
This commit is contained in:
parent
7f95d43982
commit
8a6698cd21
@ -110,6 +110,13 @@ impl ItemStack {
|
||||
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]
|
||||
pub fn get_max_stack_size(&self) -> u8 {
|
||||
self.get_data_component::<MaxStackSizeImpl>()
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use std::{any::Any, pin::Pin, sync::Arc};
|
||||
|
||||
use pumpkin_data::screen::WindowType;
|
||||
use pumpkin_data::tag::Taggable;
|
||||
use pumpkin_data::{screen::WindowType, tag};
|
||||
use pumpkin_world::{block::entities::PropertyDelegate, inventory::Inventory};
|
||||
|
||||
use crate::{
|
||||
@ -122,10 +122,7 @@ impl ScreenHandler for BrewingScreenHandler {
|
||||
.is_some();
|
||||
|
||||
// Check if item is brewing fuel (for slot 4)
|
||||
let is_fuel = stack
|
||||
.get_item()
|
||||
.is_tagged_with("minecraft:brewing_fuel")
|
||||
.is_some_and(|b| b);
|
||||
let is_fuel = stack.get_item().has_tag(&tag::Item::MINECRAFT_BREWING_FUEL);
|
||||
|
||||
if has_potion_contents {
|
||||
// Try to insert into potion slots (0-2)
|
||||
|
||||
@ -13,9 +13,9 @@ use serde::{
|
||||
};
|
||||
|
||||
#[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> {
|
||||
let velocity = self.0;
|
||||
|
||||
@ -153,7 +153,7 @@ fn from_long(quantized: i64, scale: f64) -> f64 {
|
||||
(normalized / 0.5) * scale
|
||||
}
|
||||
|
||||
impl Serialize for Velocity {
|
||||
impl Serialize for LpVector3d {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
let mut buf = Vec::new();
|
||||
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>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
@ -169,7 +169,7 @@ impl<'de> de::Deserialize<'de> for Velocity {
|
||||
struct VelocityVisitor;
|
||||
|
||||
impl Visitor<'_> for VelocityVisitor {
|
||||
type Value = Velocity;
|
||||
type Value = LpVector3d;
|
||||
|
||||
fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("a byte array representing bit-packed velocity")
|
||||
@ -180,7 +180,7 @@ impl<'de> de::Deserialize<'de> for Velocity {
|
||||
E: de::Error,
|
||||
{
|
||||
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)]
|
||||
mod tests {
|
||||
use super::{Velocity, encode_legacy_velocity_component};
|
||||
use super::{LpVector3d, encode_legacy_velocity_component};
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
|
||||
#[test]
|
||||
@ -203,7 +203,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
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();
|
||||
velocity.write_legacy(&mut buf).unwrap();
|
||||
assert_eq!(buf, vec![0x0F, 0xA0, 0xF0, 0x60, 0x00, 0x00]);
|
||||
@ -4,6 +4,7 @@ pub mod bitset;
|
||||
pub mod data_component;
|
||||
pub mod item_stack_seralizer;
|
||||
pub mod little_endian;
|
||||
pub mod lp_vector_3d;
|
||||
pub mod optional_int;
|
||||
mod u24_type;
|
||||
pub mod uuid;
|
||||
@ -11,6 +12,5 @@ pub mod var_int;
|
||||
pub mod var_long;
|
||||
pub mod var_uint;
|
||||
pub mod var_ulong;
|
||||
pub mod velocity;
|
||||
|
||||
pub use u24_type::u24;
|
||||
|
||||
@ -4,7 +4,9 @@ use pumpkin_data::packet::clientbound::PLAY_SET_ENTITY_MOTION;
|
||||
use pumpkin_macros::java_packet;
|
||||
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.
|
||||
///
|
||||
@ -15,7 +17,7 @@ pub struct CEntityVelocity {
|
||||
/// The Entity ID of the entity whose velocity is being set
|
||||
pub entity_id: VarInt,
|
||||
/// The velocity vector
|
||||
pub velocity: Velocity,
|
||||
pub velocity: LpVector3d,
|
||||
}
|
||||
|
||||
impl CEntityVelocity {
|
||||
@ -23,7 +25,7 @@ impl CEntityVelocity {
|
||||
pub const fn new(entity_id: VarInt, velocity: Vector3<f64>) -> Self {
|
||||
Self {
|
||||
entity_id,
|
||||
velocity: Velocity(velocity),
|
||||
velocity: LpVector3d(velocity),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -56,7 +58,7 @@ mod tests {
|
||||
use super::CEntityVelocity;
|
||||
use crate::{
|
||||
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> {
|
||||
@ -95,7 +97,7 @@ mod tests {
|
||||
|
||||
// Ensure the packed bytes can still be decoded back to a velocity.
|
||||
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.y.is_finite());
|
||||
assert!(decoded.0.z.is_finite());
|
||||
|
||||
@ -9,7 +9,7 @@ use pumpkin_util::{math::vector3::Vector3, version::MinecraftVersion};
|
||||
|
||||
use crate::{
|
||||
ClientPacket, VarInt,
|
||||
codec::velocity::Velocity,
|
||||
codec::lp_vector_3d::LpVector3d,
|
||||
ser::{NetworkWriteExt, WritingError},
|
||||
};
|
||||
|
||||
@ -19,7 +19,7 @@ pub struct CSpawnEntity {
|
||||
pub entity_uuid: uuid::Uuid,
|
||||
pub r#type: VarInt,
|
||||
pub position: Vector3<f64>,
|
||||
pub velocity: Velocity,
|
||||
pub velocity: LpVector3d,
|
||||
pub pitch: u8, // angle
|
||||
pub 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,
|
||||
head_yaw: (head_yaw.rem_euclid(360.0) * 256.0 / 360.0).floor() as u8,
|
||||
data,
|
||||
velocity: Velocity(velocity),
|
||||
velocity: LpVector3d(velocity),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -100,7 +100,7 @@ impl ClientPacket for CSpawnEntity {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
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;
|
||||
|
||||
fn legacy_tail(velocity: pumpkin_util::math::vector3::Vector3<f64>) -> [u8; 6] {
|
||||
|
||||
11
pumpkin-protocol/src/java/server/play/attack.rs
Normal file
11
pumpkin-protocol/src/java/server/play/attack.rs
Normal 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,
|
||||
}
|
||||
@ -6,7 +6,7 @@ use pumpkin_util::{math::vector3::Vector3, version::MinecraftVersion};
|
||||
|
||||
use crate::{
|
||||
ServerPacket,
|
||||
codec::var_int::VarInt,
|
||||
codec::{lp_vector_3d::LpVector3d, var_int::VarInt},
|
||||
ser::{NetworkReadExt, ReadingError},
|
||||
};
|
||||
|
||||
@ -14,26 +14,42 @@ use crate::{
|
||||
pub struct SInteract {
|
||||
pub entity_id: VarInt,
|
||||
pub r#type: VarInt,
|
||||
pub target_position: Option<Vector3<f32>>,
|
||||
pub target_position: Option<Vector3<f64>>,
|
||||
pub hand: Option<VarInt>,
|
||||
pub sneaking: bool,
|
||||
}
|
||||
|
||||
// Great job Mojang ;D
|
||||
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 r#type = read.get_var_int()?;
|
||||
let action = ActionType::try_from(r#type.0)
|
||||
.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::InteractAt => Some(Vector3::new(
|
||||
read.get_f32_be()?,
|
||||
read.get_f32_be()?,
|
||||
read.get_f32_be()?,
|
||||
)),
|
||||
ActionType::InteractAt => Some(
|
||||
Vector3::new(read.get_f32_be()?, read.get_f32_be()?, read.get_f32_be()?).to_f64(),
|
||||
),
|
||||
};
|
||||
|
||||
let hand = match action {
|
||||
ActionType::Interact | ActionType::InteractAt => Some(read.get_var_int()?),
|
||||
ActionType::Attack => None,
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
mod attack;
|
||||
mod change_game_mode;
|
||||
mod chat_command;
|
||||
mod chat_message;
|
||||
@ -35,6 +36,7 @@ mod update_sign;
|
||||
mod use_item;
|
||||
mod use_item_on;
|
||||
|
||||
pub use attack::*;
|
||||
pub use change_game_mode::*;
|
||||
pub use chat_command::*;
|
||||
pub use chat_message::*;
|
||||
|
||||
@ -15,7 +15,7 @@ use pumpkin_data::item::Item;
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_data::potion_brewing::{ITEM_RECIPES, POTION_RECIPES};
|
||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||
use pumpkin_data::tag::Taggable;
|
||||
use pumpkin_data::tag::{self, Taggable};
|
||||
use pumpkin_nbt::compound::NbtCompound;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
@ -294,20 +294,14 @@ impl crate::inventory::Inventory for BrewingStandBlockEntity {
|
||||
// Slot 3 - ingredient (must be tagged as brewable)
|
||||
3 => {
|
||||
// Check if item is a valid brewing ingredient
|
||||
if let Some(is_brewing_fuel) =
|
||||
stack.get_item().is_tagged_with("minecraft:brewing_fuel")
|
||||
&& is_brewing_fuel
|
||||
{
|
||||
if stack.get_item().has_tag(&tag::Item::MINECRAFT_BREWING_FUEL) {
|
||||
return false; // Fuel should not go in ingredient slot
|
||||
}
|
||||
// Allow any item that's not fuel (ingredient validation happens during brewing)
|
||||
true
|
||||
}
|
||||
// Slot 4 - fuel
|
||||
4 => stack
|
||||
.get_item()
|
||||
.is_tagged_with("minecraft:brewing_fuel")
|
||||
.is_some_and(|b| b),
|
||||
4 => stack.get_item().has_tag(&tag::Item::MINECRAFT_BREWING_FUEL),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -422,8 +416,7 @@ impl crate::block::entities::BlockEntity for BrewingStandBlockEntity {
|
||||
if !fuel_stack.is_empty()
|
||||
&& fuel_stack
|
||||
.get_item()
|
||||
.is_tagged_with("minecraft:brewing_fuel")
|
||||
.is_some_and(|b| b)
|
||||
.has_tag(&tag::Item::MINECRAFT_BREWING_FUEL)
|
||||
{
|
||||
self.fuel.store(20, Ordering::Relaxed);
|
||||
fuel_stack.decrement(1);
|
||||
|
||||
@ -14,7 +14,6 @@ pub mod positions;
|
||||
pub mod proto_chunk;
|
||||
pub mod proto_chunk_test;
|
||||
pub mod rule;
|
||||
mod rule_test;
|
||||
pub mod structure;
|
||||
mod surface;
|
||||
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
26
pumpkin/src/entity/boss/ender_dragon.rs
Normal file
26
pumpkin/src/entity/boss/ender_dragon.rs
Normal 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
|
||||
}
|
||||
}
|
||||
@ -1 +1,2 @@
|
||||
pub mod ender_dragon;
|
||||
pub mod wither;
|
||||
|
||||
@ -4,6 +4,7 @@ use pumpkin_data::entity::EntityType;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::entity::boss::ender_dragon::EnderDragonEntity;
|
||||
use crate::entity::mob::zombie::zombie_villager::ZombieVillagerEntity;
|
||||
use crate::{
|
||||
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::SHEEP.id => SheepEntity::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::ENDER_DRAGON.id => EnderDragonEntity::new(entity),
|
||||
|
||||
id if id == EntityType::AREA_EFFECT_CLOUD.id => {
|
||||
crate::entity::area_effect_cloud::AreaEffectCloudEntity::new(entity)
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ use crossbeam::atomic::AtomicCell;
|
||||
use pumpkin_config::networking::compression::CompressionInfo;
|
||||
use pumpkin_data::packet::CURRENT_MC_VERSION;
|
||||
use pumpkin_protocol::java::server::play::{
|
||||
SChangeGameMode, SChatCommand, SChatMessage, SChunkBatch, SClickSlot, SClientCommand,
|
||||
SAttack, SChangeGameMode, SChatCommand, SChatMessage, SChunkBatch, SClickSlot, SClientCommand,
|
||||
SClientInformationPlay, SClientTickEnd, SCloseContainer, SCommandSuggestion, SConfirmTeleport,
|
||||
SCookieResponse as SPCookieResponse, SCustomPayload, SInteract, SKeepAlive, SMoveVehicle,
|
||||
SPaddleBoat, SPickItemFromBlock, SPlayPingRequest, SPlayerAbilities, SPlayerAction,
|
||||
@ -727,6 +727,10 @@ impl JavaClient {
|
||||
self.handle_interact(player, SInteract::read(payload, &version)?, server)
|
||||
.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) => {
|
||||
self.handle_keep_alive(player, SKeepAlive::read(payload, &version)?)
|
||||
.await;
|
||||
|
||||
@ -52,7 +52,7 @@ use pumpkin_protocol::java::client::play::{
|
||||
CUpdateEntityPos, CUpdateEntityPosRot, CUpdateEntityRot, InitChat, PlayerAction,
|
||||
};
|
||||
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,
|
||||
SCommandSuggestion, SConfirmTeleport, SCookieResponse as SPCookieResponse, SInteract,
|
||||
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(
|
||||
&self,
|
||||
player: &Arc<Player>,
|
||||
|
||||
@ -26,7 +26,7 @@ pub struct PlayerInteractEntityEvent {
|
||||
pub action: ActionType,
|
||||
|
||||
/// 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.
|
||||
pub sneaking: bool,
|
||||
@ -37,7 +37,7 @@ impl PlayerInteractEntityEvent {
|
||||
player: &Arc<Player>,
|
||||
target: Arc<dyn EntityBase>,
|
||||
action: ActionType,
|
||||
target_position: Option<Vector3<f32>>,
|
||||
target_position: Option<Vector3<f64>>,
|
||||
sneaking: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user