feat: add Leash
This commit is contained in:
parent
0dfaf5d213
commit
cba7578e28
22
pumpkin-protocol/src/bedrock/client/level_sound_event.rs
Normal file
22
pumpkin-protocol/src/bedrock/client/level_sound_event.rs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
use pumpkin_macros::packet;
|
||||||
|
use pumpkin_util::math::vector3::Vector3;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
codec::{var_int::VarInt, var_uint::VarUInt},
|
||||||
|
serial::PacketWrite,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Sent by the server to spawn a visual particle effect at a specific 3D location in the world.
|
||||||
|
///
|
||||||
|
/// Packet ID: `27`
|
||||||
|
/// Ref: <https://mojang.github.io/bedrock-protocol-docs/html/LevelSoundEventPacket.html>
|
||||||
|
#[derive(PacketWrite)]
|
||||||
|
#[packet(27)]
|
||||||
|
pub struct CLevelSoundEvent {
|
||||||
|
pub sound_id: VarUInt,
|
||||||
|
pub position: Vector3<f32>,
|
||||||
|
pub extra_data: VarInt,
|
||||||
|
pub entity_type: String,
|
||||||
|
pub is_baby_mob: bool,
|
||||||
|
pub is_global: bool,
|
||||||
|
}
|
||||||
@ -49,6 +49,9 @@ pub mod update_abilities;
|
|||||||
pub mod update_attributes;
|
pub mod update_attributes;
|
||||||
pub mod update_block;
|
pub mod update_block;
|
||||||
|
|
||||||
|
pub mod level_sound_event;
|
||||||
|
pub mod set_actor_link;
|
||||||
|
|
||||||
pub use add_actor::*;
|
pub use add_actor::*;
|
||||||
pub use add_item_actor::*;
|
pub use add_item_actor::*;
|
||||||
pub use add_player::*;
|
pub use add_player::*;
|
||||||
@ -70,6 +73,7 @@ pub use item_registry::*;
|
|||||||
pub use item_stack_response::*;
|
pub use item_stack_response::*;
|
||||||
pub use level_chunk::*;
|
pub use level_chunk::*;
|
||||||
pub use level_event::*;
|
pub use level_event::*;
|
||||||
|
pub use level_sound_event::*;
|
||||||
pub use modal_form_request::*;
|
pub use modal_form_request::*;
|
||||||
pub use move_actor_absolute::*;
|
pub use move_actor_absolute::*;
|
||||||
pub use move_actor_delta::*;
|
pub use move_actor_delta::*;
|
||||||
@ -85,6 +89,7 @@ pub use resource_pack_stack::*;
|
|||||||
pub use resource_packs_info::*;
|
pub use resource_packs_info::*;
|
||||||
pub use scoreboard::*;
|
pub use scoreboard::*;
|
||||||
pub use set_actor_data::*;
|
pub use set_actor_data::*;
|
||||||
|
pub use set_actor_link::*;
|
||||||
pub use set_actor_motion::*;
|
pub use set_actor_motion::*;
|
||||||
pub use set_difficulty::*;
|
pub use set_difficulty::*;
|
||||||
pub use set_health::*;
|
pub use set_health::*;
|
||||||
|
|||||||
@ -3,6 +3,9 @@ use std::net::SocketAddr;
|
|||||||
use pumpkin_macros::packet;
|
use pumpkin_macros::packet;
|
||||||
|
|
||||||
use crate::{bedrock::RAKNET_MAGIC, serial::PacketWrite};
|
use crate::{bedrock::RAKNET_MAGIC, serial::PacketWrite};
|
||||||
|
/// Sent in response to a `ConnectedPing` (`0x00`) to calculate round-trip latency and synchronize time across an established connection.
|
||||||
|
///
|
||||||
|
/// Ref: <https://minecraft.wiki/w/RakNet#Connected_Pong>
|
||||||
#[derive(PacketWrite)]
|
#[derive(PacketWrite)]
|
||||||
#[packet(0x03)]
|
#[packet(0x03)]
|
||||||
pub struct CConnectedPong {
|
pub struct CConnectedPong {
|
||||||
@ -18,6 +21,9 @@ impl CConnectedPong {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sent by the server to accept an incoming `ConnectionRequest` (`0x09`), confirming connection parameters and system addresses.
|
||||||
|
///
|
||||||
|
/// Ref: <https://minecraft.wiki/w/RakNet#Connection_Request_Accepted>
|
||||||
#[derive(PacketWrite)]
|
#[derive(PacketWrite)]
|
||||||
#[packet(0x10)]
|
#[packet(0x10)]
|
||||||
pub struct CConnectionRequestAccepted {
|
pub struct CConnectionRequestAccepted {
|
||||||
@ -47,6 +53,9 @@ impl CConnectionRequestAccepted {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sent by the server when a client attempts to connect while already being connected.
|
||||||
|
///
|
||||||
|
/// Ref: <https://minecraft.wiki/w/RakNet#Already_Connected>
|
||||||
#[derive(PacketWrite)]
|
#[derive(PacketWrite)]
|
||||||
#[packet(0x12)]
|
#[packet(0x12)]
|
||||||
pub struct CAlreadyConnected {
|
pub struct CAlreadyConnected {
|
||||||
@ -64,6 +73,9 @@ impl CAlreadyConnected {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sent by the server when it has reached its maximum connection capacity.
|
||||||
|
///
|
||||||
|
/// Ref: <https://minecraft.wiki/w/RakNet#No_Free_Incoming_Connections>
|
||||||
#[derive(PacketWrite)]
|
#[derive(PacketWrite)]
|
||||||
#[packet(0x14)]
|
#[packet(0x14)]
|
||||||
pub struct CNoFreeIncomingConnections {
|
pub struct CNoFreeIncomingConnections {
|
||||||
@ -81,6 +93,9 @@ impl CNoFreeIncomingConnections {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sent by the server when a client attempts to connect from a banned IP address or identifier.
|
||||||
|
///
|
||||||
|
/// Ref: <https://minecraft.wiki/w/RakNet#Connection_Banned>
|
||||||
#[derive(PacketWrite)]
|
#[derive(PacketWrite)]
|
||||||
#[packet(0x17)]
|
#[packet(0x17)]
|
||||||
pub struct CConnectionBanned {
|
pub struct CConnectionBanned {
|
||||||
@ -98,6 +113,9 @@ impl CConnectionBanned {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sent by the server when a client attempts to connect again too quickly after disconnecting.
|
||||||
|
///
|
||||||
|
/// Ref: <https://minecraft.wiki/w/RakNet#IP_Recently_Connected>
|
||||||
#[derive(PacketWrite)]
|
#[derive(PacketWrite)]
|
||||||
#[packet(0x1A)]
|
#[packet(0x1A)]
|
||||||
pub struct CIpRecentlyConnected {
|
pub struct CIpRecentlyConnected {
|
||||||
@ -115,6 +133,9 @@ impl CIpRecentlyConnected {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sent by the server to initiate graceful termination of the connection session.
|
||||||
|
///
|
||||||
|
/// Ref: <https://minecraft.wiki/w/RakNet#Disconnection_Notification>
|
||||||
#[derive(PacketWrite)]
|
#[derive(PacketWrite)]
|
||||||
#[packet(0x15)]
|
#[packet(0x15)]
|
||||||
pub struct CDisconnect;
|
pub struct CDisconnect;
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
use crate::{bedrock::RAKNET_MAGIC, serial::PacketWrite};
|
use crate::{bedrock::RAKNET_MAGIC, serial::PacketWrite};
|
||||||
use pumpkin_macros::packet;
|
use pumpkin_macros::packet;
|
||||||
|
|
||||||
|
/// Sent by the server when the client's `RakNet` protocol version does not match the server's expected protocol version (`11`).
|
||||||
|
///
|
||||||
|
/// Ref: <https://minecraft.wiki/w/RakNet#Incompatible_Protocol_Version>
|
||||||
#[derive(PacketWrite)]
|
#[derive(PacketWrite)]
|
||||||
#[packet(0x19)]
|
#[packet(0x19)]
|
||||||
pub struct CIncompatibleProtocolVersion {
|
pub struct CIncompatibleProtocolVersion {
|
||||||
|
|||||||
@ -4,6 +4,9 @@ use pumpkin_macros::packet;
|
|||||||
|
|
||||||
use crate::{bedrock::RAKNET_MAGIC, serial::PacketWrite};
|
use crate::{bedrock::RAKNET_MAGIC, serial::PacketWrite};
|
||||||
|
|
||||||
|
/// Sent by the server in response to `OpenConnectionRequest1` (`0x05`), negotiating security options, server GUID, and MTU.
|
||||||
|
///
|
||||||
|
/// Ref: <https://minecraft.wiki/w/RakNet#Open_Connection_Reply_1>
|
||||||
#[derive(PacketWrite)]
|
#[derive(PacketWrite)]
|
||||||
#[packet(0x06)]
|
#[packet(0x06)]
|
||||||
pub struct COpenConnectionReply1 {
|
pub struct COpenConnectionReply1 {
|
||||||
@ -28,6 +31,9 @@ impl COpenConnectionReply1 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sent by the server in response to `OpenConnectionRequest2` (`0x07`), confirming the connection setup and client address before establishing session state.
|
||||||
|
///
|
||||||
|
/// Ref: <https://minecraft.wiki/w/RakNet#Open_Connection_Reply_2>
|
||||||
#[derive(PacketWrite)]
|
#[derive(PacketWrite)]
|
||||||
#[packet(0x08)]
|
#[packet(0x08)]
|
||||||
pub struct COpenConnectionReply2 {
|
pub struct COpenConnectionReply2 {
|
||||||
|
|||||||
@ -5,6 +5,9 @@ use pumpkin_macros::packet;
|
|||||||
|
|
||||||
use crate::serial::PacketWrite;
|
use crate::serial::PacketWrite;
|
||||||
|
|
||||||
|
/// Sent by the server in response to an `UnconnectedPing` (`0x01` / `0x02`), containing server metadata (MOTD, protocol version, player counts, edition).
|
||||||
|
///
|
||||||
|
/// Ref: <https://minecraft.wiki/w/RakNet#Unconnected_Pong>
|
||||||
#[packet(0x1c)]
|
#[packet(0x1c)]
|
||||||
pub struct CUnconnectedPong {
|
pub struct CUnconnectedPong {
|
||||||
time: u64,
|
time: u64,
|
||||||
|
|||||||
13
pumpkin-protocol/src/bedrock/client/set_actor_link.rs
Normal file
13
pumpkin-protocol/src/bedrock/client/set_actor_link.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
use pumpkin_macros::packet;
|
||||||
|
|
||||||
|
use crate::{bedrock::client::common::EntityLink, serial::PacketWrite};
|
||||||
|
|
||||||
|
/// Sent by the server to set the entity an actor is riding or to unmount an actor.
|
||||||
|
///
|
||||||
|
/// Packet ID: `41`
|
||||||
|
/// Ref: <https://mojang.github.io/bedrock-protocol-docs/html/SetActorLinkPacket.html>
|
||||||
|
#[derive(PacketWrite)]
|
||||||
|
#[packet(41)]
|
||||||
|
pub struct CSetActorLink {
|
||||||
|
pub link: EntityLink,
|
||||||
|
}
|
||||||
@ -16,6 +16,7 @@ pub mod mob_equipment;
|
|||||||
pub mod modal_form_response;
|
pub mod modal_form_response;
|
||||||
pub mod player_action;
|
pub mod player_action;
|
||||||
pub mod player_auth_input;
|
pub mod player_auth_input;
|
||||||
|
pub mod player_hotbar;
|
||||||
pub mod raknet;
|
pub mod raknet;
|
||||||
pub mod request_ability;
|
pub mod request_ability;
|
||||||
pub mod request_chunk_radius;
|
pub mod request_chunk_radius;
|
||||||
@ -43,6 +44,7 @@ pub use mob_equipment::*;
|
|||||||
pub use modal_form_response::*;
|
pub use modal_form_response::*;
|
||||||
pub use player_action::{Action as PlayerActionType, SPlayerAction};
|
pub use player_action::{Action as PlayerActionType, SPlayerAction};
|
||||||
pub use player_auth_input::*;
|
pub use player_auth_input::*;
|
||||||
|
pub use player_hotbar::*;
|
||||||
pub use raknet::*;
|
pub use raknet::*;
|
||||||
pub use request_ability::*;
|
pub use request_ability::*;
|
||||||
pub use request_chunk_radius::*;
|
pub use request_chunk_radius::*;
|
||||||
|
|||||||
15
pumpkin-protocol/src/bedrock/server/player_hotbar.rs
Normal file
15
pumpkin-protocol/src/bedrock/server/player_hotbar.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use pumpkin_macros::packet;
|
||||||
|
|
||||||
|
use crate::serial::PacketRead;
|
||||||
|
|
||||||
|
/// Sent by the Bedrock client when the player changes their active hotbar slot.
|
||||||
|
///
|
||||||
|
/// Packet ID: `48`
|
||||||
|
/// Ref: <https://mojang.github.io/bedrock-protocol-docs/html/PlayerHotbarPacket.html>
|
||||||
|
#[derive(PacketRead)]
|
||||||
|
#[packet(48)]
|
||||||
|
pub struct SPlayerHotbar {
|
||||||
|
pub selected_slot: u32,
|
||||||
|
pub container_id: u8,
|
||||||
|
pub select_slot: bool,
|
||||||
|
}
|
||||||
@ -4,6 +4,9 @@ use pumpkin_macros::packet;
|
|||||||
|
|
||||||
use crate::serial::PacketRead;
|
use crate::serial::PacketRead;
|
||||||
|
|
||||||
|
/// Sent periodically by a connected client to measure round-trip time.
|
||||||
|
///
|
||||||
|
/// Ref: <https://minecraft.wiki/w/RakNet#Connected_Ping>
|
||||||
#[derive(PacketRead)]
|
#[derive(PacketRead)]
|
||||||
#[packet(0x00)]
|
#[packet(0x00)]
|
||||||
pub struct SConnectedPing {
|
pub struct SConnectedPing {
|
||||||
@ -12,6 +15,9 @@ pub struct SConnectedPing {
|
|||||||
pub time: u64,
|
pub time: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sent by the client after receiving `OpenConnectionReply2` to request formal session establishment.
|
||||||
|
///
|
||||||
|
/// Ref: <https://minecraft.wiki/w/RakNet#Connection_Request>
|
||||||
#[derive(PacketRead)]
|
#[derive(PacketRead)]
|
||||||
#[packet(0x09)]
|
#[packet(0x09)]
|
||||||
pub struct SConnectionRequest {
|
pub struct SConnectionRequest {
|
||||||
@ -22,6 +28,9 @@ pub struct SConnectionRequest {
|
|||||||
pub security: bool,
|
pub security: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sent by the client to confirm local network address and finish connection establishment.
|
||||||
|
///
|
||||||
|
/// Ref: <https://minecraft.wiki/w/RakNet#New_Incoming_Connection>
|
||||||
#[derive(PacketRead)]
|
#[derive(PacketRead)]
|
||||||
#[packet(0x13)]
|
#[packet(0x13)]
|
||||||
pub struct SNewIncomingConnection {
|
pub struct SNewIncomingConnection {
|
||||||
@ -33,8 +42,14 @@ pub struct SNewIncomingConnection {
|
|||||||
pub pong_time: u64,
|
pub pong_time: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sent by the client to notify the server of graceful disconnection.
|
||||||
|
///
|
||||||
|
/// Ref: <https://minecraft.wiki/w/RakNet#Disconnection_Notification>
|
||||||
#[packet(0x15)]
|
#[packet(0x15)]
|
||||||
pub struct SDisconnect;
|
pub struct SDisconnect;
|
||||||
|
|
||||||
|
/// Internal notification signal for a connection lost due to socket error or timeout.
|
||||||
|
///
|
||||||
|
/// Ref: <https://minecraft.wiki/w/RakNet#Disconnection_Notification>
|
||||||
#[packet(0x16)]
|
#[packet(0x16)]
|
||||||
pub struct SConnectionLost;
|
pub struct SConnectionLost;
|
||||||
|
|||||||
@ -4,9 +4,11 @@ use pumpkin_macros::packet;
|
|||||||
|
|
||||||
use crate::serial::PacketRead;
|
use crate::serial::PacketRead;
|
||||||
|
|
||||||
|
/// Sent by a connecting client to initiate `RakNet` handshake and check server MTU size.
|
||||||
|
///
|
||||||
|
/// Ref: <https://minecraft.wiki/w/RakNet#Open_Connection_Request_1>
|
||||||
#[derive(PacketRead)]
|
#[derive(PacketRead)]
|
||||||
#[packet(0x05)]
|
#[packet(0x05)]
|
||||||
/// The client sends this when attempting to join the server
|
|
||||||
pub struct SOpenConnectionRequest1 {
|
pub struct SOpenConnectionRequest1 {
|
||||||
pub magic: [u8; 16],
|
pub magic: [u8; 16],
|
||||||
pub protocol_version: u8,
|
pub protocol_version: u8,
|
||||||
@ -14,6 +16,9 @@ pub struct SOpenConnectionRequest1 {
|
|||||||
pub mtu: u16,
|
pub mtu: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sent by a connecting client following `OpenConnectionReply1` to verify server address, client GUID, and MTU.
|
||||||
|
///
|
||||||
|
/// Ref: <https://minecraft.wiki/w/RakNet#Open_Connection_Request_2>
|
||||||
#[derive(PacketRead)]
|
#[derive(PacketRead)]
|
||||||
#[packet(0x07)]
|
#[packet(0x07)]
|
||||||
pub struct SOpenConnectionRequest2 {
|
pub struct SOpenConnectionRequest2 {
|
||||||
|
|||||||
@ -2,9 +2,11 @@ use pumpkin_macros::packet;
|
|||||||
|
|
||||||
use crate::serial::PacketRead;
|
use crate::serial::PacketRead;
|
||||||
|
|
||||||
|
/// Sent by an unconnected client to request server information, status, and MOTD.
|
||||||
|
///
|
||||||
|
/// Ref: <https://minecraft.wiki/w/RakNet#Unconnected_Ping>
|
||||||
#[derive(PacketRead)]
|
#[derive(PacketRead)]
|
||||||
#[packet(0x01)]
|
#[packet(0x01)]
|
||||||
/// Used to request Server information like MOTD
|
|
||||||
pub struct SUnconnectedPing {
|
pub struct SUnconnectedPing {
|
||||||
#[serial(big_endian)]
|
#[serial(big_endian)]
|
||||||
pub time: u64,
|
pub time: u64,
|
||||||
@ -13,9 +15,11 @@ pub struct SUnconnectedPing {
|
|||||||
pub client_guid: u64,
|
pub client_guid: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sent by a client to query server information when connections are open.
|
||||||
|
///
|
||||||
|
/// Ref: <https://minecraft.wiki/w/RakNet#Unconnected_Ping_Open_Connections>
|
||||||
#[derive(PacketRead)]
|
#[derive(PacketRead)]
|
||||||
#[packet(0x02)]
|
#[packet(0x02)]
|
||||||
/// Used to request Server information like MOTD when connection is open?
|
|
||||||
pub struct SUnconnectedPingOpenConnections {
|
pub struct SUnconnectedPingOpenConnections {
|
||||||
#[serial(big_endian)]
|
#[serial(big_endian)]
|
||||||
pub time: u64,
|
pub time: u64,
|
||||||
|
|||||||
@ -182,7 +182,10 @@ pub use set_container_content::*;
|
|||||||
pub use set_container_property::*;
|
pub use set_container_property::*;
|
||||||
pub use set_container_slot::*;
|
pub use set_container_slot::*;
|
||||||
pub use set_cursor_slot::*;
|
pub use set_cursor_slot::*;
|
||||||
|
mod set_entity_link;
|
||||||
|
pub use set_entity_link::*;
|
||||||
pub use set_equipment::*;
|
pub use set_equipment::*;
|
||||||
|
|
||||||
pub use set_experience::*;
|
pub use set_experience::*;
|
||||||
pub use set_health::*;
|
pub use set_health::*;
|
||||||
pub use set_held_item::*;
|
pub use set_held_item::*;
|
||||||
|
|||||||
34
pumpkin-protocol/src/java/client/play/set_entity_link.rs
Normal file
34
pumpkin-protocol/src/java/client/play/set_entity_link.rs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
use pumpkin_data::packet::clientbound::PLAY_SET_ENTITY_LINK;
|
||||||
|
use pumpkin_macros::java_packet;
|
||||||
|
use pumpkin_util::version::JavaMinecraftVersion;
|
||||||
|
|
||||||
|
use crate::{ClientPacket, ser::NetworkWriteExt};
|
||||||
|
|
||||||
|
/// Sent by the server to attach (leash) or detach an entity to another entity (e.g. leash knot, player, mob).
|
||||||
|
#[java_packet(PLAY_SET_ENTITY_LINK)]
|
||||||
|
pub struct CSetEntityLink {
|
||||||
|
pub attached_entity_id: i32,
|
||||||
|
pub holding_entity_id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CSetEntityLink {
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new(attached_entity_id: i32, holding_entity_id: i32) -> Self {
|
||||||
|
Self {
|
||||||
|
attached_entity_id,
|
||||||
|
holding_entity_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClientPacket for CSetEntityLink {
|
||||||
|
fn write_packet_data(
|
||||||
|
&self,
|
||||||
|
mut write: impl std::io::Write,
|
||||||
|
_version: &JavaMinecraftVersion,
|
||||||
|
) -> Result<(), crate::ser::WritingError> {
|
||||||
|
write.write_i32_be(self.attached_entity_id)?;
|
||||||
|
write.write_i32_be(self.holding_entity_id)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
216
pumpkin/src/entity/decoration/leash_knot.rs
Normal file
216
pumpkin/src/entity/decoration/leash_knot.rs
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
use crate::entity::player::Player;
|
||||||
|
use crate::entity::{Entity, EntityBase, EntityBaseFuture, NBTStorage, living::LivingEntity};
|
||||||
|
use crate::world::World;
|
||||||
|
use pumpkin_data::entity::EntityType;
|
||||||
|
use pumpkin_data::item_stack::ItemStack;
|
||||||
|
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||||
|
use pumpkin_data::tag::Taggable;
|
||||||
|
use pumpkin_util::math::boundingbox::{BoundingBox, EntityDimensions};
|
||||||
|
use pumpkin_util::math::position::BlockPos;
|
||||||
|
use pumpkin_util::math::vector3::Vector3;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub struct LeashKnotEntity {
|
||||||
|
entity: Entity,
|
||||||
|
pos: BlockPos,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LeashKnotEntity {
|
||||||
|
pub const OFFSET_Y: f64 = 0.375;
|
||||||
|
|
||||||
|
pub const fn new(entity: Entity, pos: BlockPos) -> Self {
|
||||||
|
Self { entity, pos }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn block_pos(&self) -> BlockPos {
|
||||||
|
self.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_or_create(world: &Arc<World>, pos: BlockPos) -> Arc<Self> {
|
||||||
|
if let Some(existing) = Self::get_knot(world, pos) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
Self::create_knot(world, pos).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_knot(world: &Arc<World>, pos: BlockPos) -> Option<Arc<Self>> {
|
||||||
|
let center = Vector3::new(
|
||||||
|
f64::from(pos.0.x) + 0.5,
|
||||||
|
f64::from(pos.0.y) + Self::OFFSET_Y,
|
||||||
|
f64::from(pos.0.z) + 0.5,
|
||||||
|
);
|
||||||
|
|
||||||
|
let search_dim = EntityDimensions {
|
||||||
|
width: 2.0,
|
||||||
|
height: 2.0,
|
||||||
|
eye_height: 1.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let search_box = BoundingBox::new_from_pos(center.x, center.y, center.z, &search_dim);
|
||||||
|
let entities = world.get_entities_at_box(&search_box);
|
||||||
|
|
||||||
|
for entity_base in entities {
|
||||||
|
if entity_base.get_entity().entity_type == &EntityType::LEASH_KNOT
|
||||||
|
&& let Some(knot) = entity_base.cast_any().downcast_ref::<Arc<Self>>()
|
||||||
|
&& knot.pos == pos
|
||||||
|
{
|
||||||
|
return Some(knot.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_knot(world: &Arc<World>, pos: BlockPos) -> Arc<Self> {
|
||||||
|
let raw_pos = Vector3::new(
|
||||||
|
f64::from(pos.0.x) + 0.5,
|
||||||
|
f64::from(pos.0.y) + Self::OFFSET_Y,
|
||||||
|
f64::from(pos.0.z) + 0.5,
|
||||||
|
);
|
||||||
|
|
||||||
|
let entity = Entity::new(world.clone(), raw_pos, &EntityType::LEASH_KNOT);
|
||||||
|
let knot = Arc::new(Self::new(entity, pos));
|
||||||
|
world
|
||||||
|
.spawn_entity(knot.clone() as Arc<dyn EntityBase>)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
world.play_sound(Sound::ItemLeadTied, SoundCategory::Neutral, &raw_pos);
|
||||||
|
|
||||||
|
knot
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn play_placement_sound(&self, world: &World) {
|
||||||
|
let pos = self.entity.pos.load();
|
||||||
|
world.play_sound(Sound::ItemLeadTied, SoundCategory::Neutral, &pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NBTStorage for LeashKnotEntity {}
|
||||||
|
|
||||||
|
impl EntityBase for LeashKnotEntity {
|
||||||
|
fn get_entity(&self) -> &Entity {
|
||||||
|
&self.entity
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_living_entity(&self) -> Option<&LivingEntity> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tick<'a>(
|
||||||
|
&'a self,
|
||||||
|
_caller: &'a Arc<dyn EntityBase>,
|
||||||
|
_server: &'a crate::server::Server,
|
||||||
|
) -> EntityBaseFuture<'a, ()> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let world = self.entity.world.load();
|
||||||
|
let block = world.get_block(&self.pos);
|
||||||
|
if !block.has_tag(&pumpkin_data::tag::Block::MINECRAFT_FENCES) {
|
||||||
|
let knot_id = self.entity.entity_id;
|
||||||
|
let search_dim = EntityDimensions {
|
||||||
|
width: 32.0,
|
||||||
|
height: 32.0,
|
||||||
|
eye_height: 16.0,
|
||||||
|
};
|
||||||
|
let pos = self.entity.pos.load();
|
||||||
|
let search_box = BoundingBox::new_from_pos(pos.x, pos.y, pos.z, &search_dim);
|
||||||
|
let entities = world.get_entities_at_box(&search_box);
|
||||||
|
|
||||||
|
for entity_base in entities {
|
||||||
|
let ent = entity_base.get_entity();
|
||||||
|
let is_attached_to_knot = ent
|
||||||
|
.leashed_to
|
||||||
|
.try_lock()
|
||||||
|
.ok()
|
||||||
|
.and_then(|guard| {
|
||||||
|
guard
|
||||||
|
.as_ref()
|
||||||
|
.map(|holder| holder.get_entity().entity_id == knot_id)
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
if is_attached_to_knot {
|
||||||
|
ent.unleash().await;
|
||||||
|
let lead_item = pumpkin_data::item_stack::ItemStack::new(
|
||||||
|
1,
|
||||||
|
&pumpkin_data::item::Item::LEAD,
|
||||||
|
);
|
||||||
|
world.drop_stack(&ent.block_pos.load(), lead_item).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
world.play_sound(Sound::ItemLeadUntied, SoundCategory::Neutral, &pos);
|
||||||
|
|
||||||
|
self.entity.remove().await;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interact<'a>(
|
||||||
|
&'a self,
|
||||||
|
player: &'a Arc<Player>,
|
||||||
|
_item_stack: &'a mut ItemStack,
|
||||||
|
) -> EntityBaseFuture<'a, bool> {
|
||||||
|
Box::pin(async move {
|
||||||
|
let world = player.world();
|
||||||
|
let knot_id = self.entity.entity_id;
|
||||||
|
let player_id = player.entity_id();
|
||||||
|
|
||||||
|
let search_dim = EntityDimensions {
|
||||||
|
width: 32.0,
|
||||||
|
height: 32.0,
|
||||||
|
eye_height: 16.0,
|
||||||
|
};
|
||||||
|
let pos = self.entity.pos.load();
|
||||||
|
let search_box = BoundingBox::new_from_pos(pos.x, pos.y, pos.z, &search_dim);
|
||||||
|
let entities = world.get_entities_at_box(&search_box);
|
||||||
|
|
||||||
|
let mut attached_mob = false;
|
||||||
|
let mut player_leashed_mobs = Vec::new();
|
||||||
|
|
||||||
|
for entity_base in &entities {
|
||||||
|
let ent = entity_base.get_entity();
|
||||||
|
if let Ok(guard) = ent.leashed_to.try_lock()
|
||||||
|
&& let Some(holder) = guard.as_ref()
|
||||||
|
&& holder.get_entity().entity_id == player_id
|
||||||
|
{
|
||||||
|
player_leashed_mobs.push(ent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(self_knot) = Self::get_knot(&world, self.pos) {
|
||||||
|
for mob in player_leashed_mobs {
|
||||||
|
mob.leash_to(self_knot.clone() as Arc<dyn EntityBase>).await;
|
||||||
|
attached_mob = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut any_dropped = false;
|
||||||
|
if !attached_mob {
|
||||||
|
for entity_base in &entities {
|
||||||
|
let ent = entity_base.get_entity();
|
||||||
|
if let Ok(guard) = ent.leashed_to.try_lock()
|
||||||
|
&& let Some(holder) = guard.as_ref()
|
||||||
|
&& holder.get_entity().entity_id == knot_id
|
||||||
|
{
|
||||||
|
ent.leash_to(player.clone() as Arc<dyn EntityBase>).await;
|
||||||
|
any_dropped = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if attached_mob || any_dropped {
|
||||||
|
self.play_placement_sound(&world);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_nbt_storage(&self) -> &dyn NBTStorage {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cast_any(&self) -> &dyn std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
pub mod armor_stand;
|
pub mod armor_stand;
|
||||||
pub mod end_crystal;
|
pub mod end_crystal;
|
||||||
|
pub mod leash_knot;
|
||||||
pub mod painting;
|
pub mod painting;
|
||||||
|
|||||||
@ -203,7 +203,7 @@ impl Mob for CreeperEntity {
|
|||||||
) -> EntityBaseFuture<'a, bool> {
|
) -> EntityBaseFuture<'a, bool> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
if item_stack.item.id != Item::FLINT_AND_STEEL.id {
|
if item_stack.item.id != Item::FLINT_AND_STEEL.id {
|
||||||
return false;
|
return self.mob_entity.mob_interact(player, item_stack).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let entity = &self.mob_entity.living_entity.entity;
|
let entity = &self.mob_entity.living_entity.entity;
|
||||||
|
|||||||
@ -389,6 +389,45 @@ impl MobEntity {
|
|||||||
let entity = &self.living_entity.entity;
|
let entity = &self.living_entity.entity;
|
||||||
entity.set_on_fire_for(8.0);
|
entity.set_on_fire_for(8.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn mob_interact(&self, player: &Arc<Player>, item_stack: &mut ItemStack) -> bool {
|
||||||
|
let entity = &self.living_entity.entity;
|
||||||
|
|
||||||
|
// If already leashed to player, right-clicking unleashes the mob
|
||||||
|
let currently_leashed = {
|
||||||
|
let guard = entity.leashed_to.lock().await;
|
||||||
|
guard.is_some()
|
||||||
|
};
|
||||||
|
|
||||||
|
if currently_leashed {
|
||||||
|
entity.unleash().await;
|
||||||
|
let lead_item =
|
||||||
|
pumpkin_data::item_stack::ItemStack::new(1, &pumpkin_data::item::Item::LEAD);
|
||||||
|
entity
|
||||||
|
.world
|
||||||
|
.load()
|
||||||
|
.drop_stack(&entity.block_pos.load(), lead_item)
|
||||||
|
.await;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If holding a lead, leash the mob to the player
|
||||||
|
if item_stack.item.registry_key == "lead"
|
||||||
|
|| item_stack.item.registry_key == "minecraft:lead"
|
||||||
|
{
|
||||||
|
let diff = entity.pos.load() - player.get_entity().pos.load();
|
||||||
|
let dist_sq = diff.length_squared();
|
||||||
|
if dist_sq <= Entity::LEASH_SNAP_DISTANCE * Entity::LEASH_SNAP_DISTANCE {
|
||||||
|
entity.leash_to(player.clone() as Arc<dyn EntityBase>).await;
|
||||||
|
if player.gamemode.load() != pumpkin_util::GameMode::Creative {
|
||||||
|
item_stack.decrement(1);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Mob: EntityBase + Send + Sync {
|
pub trait Mob: EntityBase + Send + Sync {
|
||||||
@ -479,10 +518,10 @@ pub trait Mob: EntityBase + Send + Sync {
|
|||||||
|
|
||||||
fn mob_interact<'a>(
|
fn mob_interact<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
_player: &'a Arc<Player>,
|
player: &'a Arc<Player>,
|
||||||
_item_stack: &'a mut ItemStack,
|
item_stack: &'a mut ItemStack,
|
||||||
) -> EntityBaseFuture<'a, bool> {
|
) -> EntityBaseFuture<'a, bool> {
|
||||||
Box::pin(async { false })
|
Box::pin(async move { self.get_mob_entity().mob_interact(player, item_stack).await })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mob_player_collision<'a>(&'a self, _player: &'a Arc<Player>) -> EntityBaseFuture<'a, ()> {
|
fn mob_player_collision<'a>(&'a self, _player: &'a Arc<Player>) -> EntityBaseFuture<'a, ()> {
|
||||||
@ -554,6 +593,7 @@ impl<T: Mob + Send + 'static> EntityBase for T {
|
|||||||
) -> EntityBaseFuture<'a, ()> {
|
) -> EntityBaseFuture<'a, ()> {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let mob_entity = self.get_mob_entity();
|
let mob_entity = self.get_mob_entity();
|
||||||
|
mob_entity.living_entity.entity.tick_leash().await;
|
||||||
mob_entity.tick_sun_burn().await;
|
mob_entity.tick_sun_burn().await;
|
||||||
|
|
||||||
if mob_entity.breeding_cooldown.load(Relaxed) > 0 {
|
if mob_entity.breeding_cooldown.load(Relaxed) > 0 {
|
||||||
|
|||||||
@ -787,6 +787,8 @@ pub struct Entity {
|
|||||||
pub passengers: Mutex<Vec<Arc<dyn EntityBase>>>,
|
pub passengers: Mutex<Vec<Arc<dyn EntityBase>>>,
|
||||||
/// The vehicle that entity is in
|
/// The vehicle that entity is in
|
||||||
pub vehicle: Mutex<Option<Arc<dyn EntityBase>>>,
|
pub vehicle: Mutex<Option<Arc<dyn EntityBase>>>,
|
||||||
|
/// The entity this entity is attached/leashed to (if any)
|
||||||
|
pub leashed_to: Mutex<Option<Arc<dyn EntityBase>>>,
|
||||||
/// Cooldown before entity can mount again after dismounting
|
/// Cooldown before entity can mount again after dismounting
|
||||||
pub riding_cooldown: AtomicI32,
|
pub riding_cooldown: AtomicI32,
|
||||||
/// The age of the entity in ticks. Negative values indicate a baby.
|
/// The age of the entity in ticks. Negative values indicate a baby.
|
||||||
@ -930,6 +932,8 @@ impl Entity {
|
|||||||
removal_reason: AtomicCell::new(None),
|
removal_reason: AtomicCell::new(None),
|
||||||
passengers: Mutex::new(Vec::new()),
|
passengers: Mutex::new(Vec::new()),
|
||||||
vehicle: Mutex::new(None),
|
vehicle: Mutex::new(None),
|
||||||
|
leashed_to: Mutex::new(None),
|
||||||
|
|
||||||
riding_cooldown: AtomicI32::new(0),
|
riding_cooldown: AtomicI32::new(0),
|
||||||
age: AtomicI32::new(0),
|
age: AtomicI32::new(0),
|
||||||
current_biome: ArcSwap::new(Arc::new(&Biome::PLAINS)),
|
current_biome: ArcSwap::new(Arc::new(&Biome::PLAINS)),
|
||||||
@ -3013,6 +3017,103 @@ impl Entity {
|
|||||||
!self.is_removed()
|
!self.is_removed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const LEASH_SNAP_DISTANCE: f64 = 12.0;
|
||||||
|
pub const LEASH_ELASTIC_DISTANCE: f64 = 6.0;
|
||||||
|
|
||||||
|
pub async fn leash_to(&self, holder: Arc<dyn EntityBase>) {
|
||||||
|
let holder_entity = holder.get_entity();
|
||||||
|
*self.leashed_to.lock().await = Some(holder.clone());
|
||||||
|
|
||||||
|
let je_packet = pumpkin_protocol::java::client::play::CSetEntityLink::new(
|
||||||
|
self.entity_id,
|
||||||
|
holder_entity.entity_id,
|
||||||
|
);
|
||||||
|
let be_packet = pumpkin_protocol::bedrock::client::CSetActorLink {
|
||||||
|
link: pumpkin_protocol::bedrock::client::common::EntityLink {
|
||||||
|
ridden_unique_id: pumpkin_protocol::codec::var_long::VarLong(self.entity_id as i64),
|
||||||
|
rider_unique_id: pumpkin_protocol::codec::var_long::VarLong(
|
||||||
|
holder_entity.entity_id as i64,
|
||||||
|
),
|
||||||
|
link_type: 1, // Leash link
|
||||||
|
immediate: true,
|
||||||
|
rider_initiated: false,
|
||||||
|
vehicle_angular_velocity: 0.0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
self.world.load().broadcast_to_chunk_editioned_sync(
|
||||||
|
self.chunk_pos.load(),
|
||||||
|
&je_packet,
|
||||||
|
&be_packet,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn unleash(&self) {
|
||||||
|
let old_holder = self.leashed_to.lock().await.take();
|
||||||
|
if old_holder.is_none() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let je_packet =
|
||||||
|
pumpkin_protocol::java::client::play::CSetEntityLink::new(self.entity_id, -1);
|
||||||
|
let be_packet = pumpkin_protocol::bedrock::client::CSetActorLink {
|
||||||
|
link: pumpkin_protocol::bedrock::client::common::EntityLink {
|
||||||
|
ridden_unique_id: pumpkin_protocol::codec::var_long::VarLong(self.entity_id as i64),
|
||||||
|
rider_unique_id: pumpkin_protocol::codec::var_long::VarLong(-1),
|
||||||
|
link_type: 0, // Unlink
|
||||||
|
immediate: true,
|
||||||
|
rider_initiated: false,
|
||||||
|
vehicle_angular_velocity: 0.0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
self.world.load().broadcast_to_chunk_editioned_sync(
|
||||||
|
self.chunk_pos.load(),
|
||||||
|
&je_packet,
|
||||||
|
&be_packet,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn tick_leash(&self) {
|
||||||
|
let holder = {
|
||||||
|
let guard = self.leashed_to.lock().await;
|
||||||
|
guard.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(holder) = holder {
|
||||||
|
let holder_entity = holder.get_entity();
|
||||||
|
|
||||||
|
// Drop leash if entity or holder is removed or dead
|
||||||
|
if !self.is_alive() || !holder_entity.is_alive() {
|
||||||
|
self.unleash().await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let self_pos = self.pos.load();
|
||||||
|
let holder_pos = holder_entity.pos.load();
|
||||||
|
let diff = self_pos - holder_pos;
|
||||||
|
let distance = diff.length();
|
||||||
|
|
||||||
|
if distance > Self::LEASH_SNAP_DISTANCE {
|
||||||
|
// Too far: snap/break leash and drop lead item
|
||||||
|
self.unleash().await;
|
||||||
|
let lead_item =
|
||||||
|
pumpkin_data::item_stack::ItemStack::new(1, &pumpkin_data::item::Item::LEAD);
|
||||||
|
self.world
|
||||||
|
.load()
|
||||||
|
.drop_stack(&self.block_pos.load(), lead_item)
|
||||||
|
.await;
|
||||||
|
} else if distance > Self::LEASH_ELASTIC_DISTANCE {
|
||||||
|
// Elastic pull force towards leash holder
|
||||||
|
let dir = (holder_pos - self_pos).normalize();
|
||||||
|
let pull_strength = (distance - Self::LEASH_ELASTIC_DISTANCE) * 0.11;
|
||||||
|
let current_vel = self.velocity.load();
|
||||||
|
self.velocity.store(current_vel + dir * pull_strength);
|
||||||
|
self.velocity_dirty.store(true, Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn has_passengers(&self) -> bool {
|
pub async fn has_passengers(&self) -> bool {
|
||||||
!self.passengers.lock().await.is_empty()
|
!self.passengers.lock().await.is_empty()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -197,7 +197,7 @@ impl Mob for ChickenEntity {
|
|||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
false
|
self.mob_entity.mob_interact(player, item_stack).await
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -102,7 +102,7 @@ impl Mob for CowEntity {
|
|||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
false
|
self.mob_entity.mob_interact(player, item_stack).await
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -107,7 +107,7 @@ impl Mob for PigEntity {
|
|||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
false
|
self.mob_entity.mob_interact(player, item_stack).await
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -170,7 +170,7 @@ impl Mob for SheepEntity {
|
|||||||
);
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
false
|
self.mob_entity.mob_interact(player, item_stack).await
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
100
pumpkin/src/item/items/lead.rs
Normal file
100
pumpkin/src/item/items/lead.rs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
use std::pin::Pin;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::entity::EntityBase;
|
||||||
|
use crate::entity::decoration::leash_knot::LeashKnotEntity;
|
||||||
|
use crate::entity::player::Player;
|
||||||
|
use crate::item::{ItemBehaviour, ItemMetadata};
|
||||||
|
use crate::server::Server;
|
||||||
|
use pumpkin_data::Block;
|
||||||
|
use pumpkin_data::BlockDirection;
|
||||||
|
use pumpkin_data::item::Item;
|
||||||
|
use pumpkin_data::item_stack::ItemStack;
|
||||||
|
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||||
|
use pumpkin_data::tag::Taggable;
|
||||||
|
use pumpkin_util::math::boundingbox::{BoundingBox, EntityDimensions};
|
||||||
|
use pumpkin_util::math::position::BlockPos;
|
||||||
|
use pumpkin_util::math::vector3::Vector3;
|
||||||
|
|
||||||
|
pub struct LeadItem;
|
||||||
|
|
||||||
|
impl ItemMetadata for LeadItem {
|
||||||
|
fn ids() -> Box<[u16]> {
|
||||||
|
[Item::LEAD.id].into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ItemBehaviour for LeadItem {
|
||||||
|
fn use_on_block<'a>(
|
||||||
|
&'a self,
|
||||||
|
item: &'a mut ItemStack,
|
||||||
|
player: &'a Player,
|
||||||
|
location: BlockPos,
|
||||||
|
_face: BlockDirection,
|
||||||
|
_cursor_pos: Vector3<f32>,
|
||||||
|
block: &'a Block,
|
||||||
|
_server: &'a Server,
|
||||||
|
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||||
|
Box::pin(async move {
|
||||||
|
if !block.has_tag(&pumpkin_data::tag::Block::MINECRAFT_FENCES) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let world = player.world();
|
||||||
|
let center = Vector3::new(
|
||||||
|
f64::from(location.0.x) + 0.5,
|
||||||
|
f64::from(location.0.y) + 0.5,
|
||||||
|
f64::from(location.0.z) + 0.5,
|
||||||
|
);
|
||||||
|
|
||||||
|
let search_dim = EntityDimensions {
|
||||||
|
width: 32.0,
|
||||||
|
height: 32.0,
|
||||||
|
eye_height: 16.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let search_box = BoundingBox::new_from_pos(center.x, center.y, center.z, &search_dim);
|
||||||
|
|
||||||
|
let player_id = player.entity_id();
|
||||||
|
let entities = world.get_entities_at_box(&search_box);
|
||||||
|
|
||||||
|
let mut any_leashed = false;
|
||||||
|
let mut knot: Option<Arc<LeashKnotEntity>> = None;
|
||||||
|
|
||||||
|
for entity_base in entities {
|
||||||
|
let ent = entity_base.get_entity();
|
||||||
|
let is_leashed_to_player = ent
|
||||||
|
.leashed_to
|
||||||
|
.try_lock()
|
||||||
|
.ok()
|
||||||
|
.and_then(|guard| {
|
||||||
|
guard
|
||||||
|
.as_ref()
|
||||||
|
.map(|holder| holder.get_entity().entity_id == player_id)
|
||||||
|
})
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
if is_leashed_to_player {
|
||||||
|
if knot.is_none() {
|
||||||
|
knot = Some(LeashKnotEntity::get_or_create(&world, location).await);
|
||||||
|
}
|
||||||
|
if let Some(k) = &knot {
|
||||||
|
ent.leash_to(k.clone() as Arc<dyn EntityBase>).await;
|
||||||
|
any_leashed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if any_leashed {
|
||||||
|
if player.gamemode.load() != pumpkin_util::GameMode::Creative {
|
||||||
|
item.decrement(1);
|
||||||
|
}
|
||||||
|
world.play_sound(Sound::ItemLeadTied, SoundCategory::Neutral, ¢er);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -30,11 +30,15 @@ pub mod swords;
|
|||||||
pub mod trident;
|
pub mod trident;
|
||||||
pub mod wind_charge;
|
pub mod wind_charge;
|
||||||
|
|
||||||
|
pub mod lead;
|
||||||
|
|
||||||
use crate::item::items::armor_stand::ArmorStandItem;
|
use crate::item::items::armor_stand::ArmorStandItem;
|
||||||
use crate::item::items::boat::BoatItem;
|
use crate::item::items::boat::BoatItem;
|
||||||
use crate::item::items::bundle::BundleItem;
|
use crate::item::items::bundle::BundleItem;
|
||||||
use crate::item::items::end_crystal::EndCrystalItem;
|
use crate::item::items::end_crystal::EndCrystalItem;
|
||||||
|
use crate::item::items::lead::LeadItem;
|
||||||
use crate::item::items::map::MapItem;
|
use crate::item::items::map::MapItem;
|
||||||
|
|
||||||
use crate::item::items::minecart::MinecartItem;
|
use crate::item::items::minecart::MinecartItem;
|
||||||
use crate::item::items::name_tag::NameTagItem;
|
use crate::item::items::name_tag::NameTagItem;
|
||||||
use crate::item::items::spawn_egg::SpawnEggItem;
|
use crate::item::items::spawn_egg::SpawnEggItem;
|
||||||
@ -108,6 +112,7 @@ pub fn default_registry() -> Arc<ItemRegistry> {
|
|||||||
manager.register(SplashPotionItem);
|
manager.register(SplashPotionItem);
|
||||||
manager.register(LingeringPotionItem);
|
manager.register(LingeringPotionItem);
|
||||||
manager.register(BundleItem);
|
manager.register(BundleItem);
|
||||||
|
manager.register(LeadItem);
|
||||||
|
|
||||||
Arc::new(manager)
|
Arc::new(manager)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user