1use std::sync::Arc;
2
3use crate::entity::entities::LeashFenceKnotEntity;
4use crate::entity::{Entity, Mob, SharedEntity, WeakEntity};
5use glam::DVec3;
6use simdnbt::borrow::NbtCompound as BorrowedNbtCompoundView;
7use simdnbt::owned::{NbtCompound, NbtTag};
8use steel_math::{DEG_TO_RAD, vector::y_rot};
9use steel_protocol::packets::game::SoundSource;
10use steel_registry::blocks::block_state_ext::BlockStateExt;
11use steel_registry::item_stack::ItemStack;
12use steel_registry::vanilla_game_rules::ENTITY_DROPS;
13use steel_registry::{sound_events, vanilla_items};
14use steel_utils::locks::SyncMutex;
15use steel_utils::{BlockPos, Downcast, UuidExt as _};
16use uuid::Uuid;
17
18pub const LEASH_SNAP_DISTANCE: f64 = 12.0;
19pub const LEASH_ELASTIC_DISTANCE: f64 = 6.0;
20pub const LEASH_AXIS_SPECIFIC_ELASTICITY: DVec3 = DVec3::new(0.8, 0.2, 0.8);
21pub const LEASH_SPRING_DAMPENING: f64 = 0.7;
22pub const LEASH_TORSIONAL_ELASTICITY: f64 = 10.0;
23pub const LEASH_STIFFNESS: f64 = 0.11;
24pub const ENTITY_LEASH_ATTACHMENT_POINT: DVec3 = DVec3::new(0.0, 0.5, 0.5);
25pub const LEASHER_ATTACHMENT_POINT: DVec3 = DVec3::new(0.0, 0.5, 0.0);
26pub const DELAYED_LEASH_DROP_TICKS: i32 = 100;
27pub const BASE_HORIZONTAL_FRICTION: f64 = 0.91;
28pub const LEASH_LIQUID_ANGULAR_FRICTION: f64 = 0.8;
30
31pub trait Leashable: Entity {
35 fn leash_data(&self) -> &SyncMutex<Option<LeashData>>;
37
38 fn is_leashed(&self) -> bool {
40 self.leash_holder().is_some()
41 }
42
43 fn may_be_leashed(&self) -> bool {
46 self.leash_data().lock().is_some()
47 }
48
49 fn leash_holder(&self) -> Option<SharedEntity> {
51 self.leash_data()
52 .lock()
53 .as_ref()
54 .and_then(LeashData::holder)
55 }
56
57 fn leash_attachment(&self) -> Option<LeashAttachment> {
58 self.leash_data()
59 .lock()
60 .as_ref()
61 .and_then(LeashData::attachment)
62 }
63
64 fn set_delayed_leash_attachment(&self, attachment: LeashAttachment) {
65 *self.leash_data().lock() = Some(LeashData::from_delayed_attachment(attachment));
66 self.remove_leash();
67 }
68
69 fn can_be_leashed(&self) -> bool {
72 true
73 }
74
75 fn leash_distance_to(&self, holder: &dyn Entity) -> f64 {
79 leash_bounding_box_center(self.as_entity_event_source())
80 .distance(leash_bounding_box_center(holder))
81 }
82
83 fn leash_snap_distance(&self) -> f64 {
85 LEASH_SNAP_DISTANCE
86 }
87
88 fn leash_elastic_distance(&self) -> f64 {
91 LEASH_ELASTIC_DISTANCE
92 }
93
94 fn when_leashed_to(&self, holder: &dyn Entity) {
96 holder.notify_leash_holder(self.as_entity_event_source());
97 }
98
99 fn on_leash_removed(&self) {}
101
102 fn leash_too_far_behaviour(&self) {
104 self.drop_leash();
105 }
106
107 fn on_elastic_leash_pull(&self) {
109 self.check_fall_distance_accumulation();
110 }
111
112 fn close_range_leash_behaviour(&self, _holder: &dyn Entity) {}
114
115 fn check_elastic_interactions(&self, holder: &dyn Entity) -> bool {
118 let Some(wrench) = compute_elastic_interaction(
119 self.as_entity_event_source(),
120 holder,
121 self.leash_elastic_distance(),
122 ) else {
123 return false;
124 };
125
126 {
127 let mut leash_data = self.leash_data().lock();
128 let Some(leash_data) = leash_data.as_mut() else {
129 return false;
130 };
131 leash_data.angular_momentum += LEASH_TORSIONAL_ELASTICITY * wrench.torque;
132 }
133
134 let relative_velocity_to_leasher =
135 leash_holder_movement(holder) - leash_holder_movement(self.as_entity_event_source());
136 self.push_impulse(
137 axis_specific_leash_elasticity(wrench.force)
138 + relative_velocity_to_leasher * LEASH_STIFFNESS,
139 );
140 true
141 }
142
143 fn apply_leash_angular_momentum(&self) -> bool {
145 let angular_friction = self.leash_angular_friction();
146 let angular_momentum = {
147 let mut leash_data = self.leash_data().lock();
148 let Some(leash_data) = leash_data.as_mut() else {
149 return false;
150 };
151 let angular_momentum = leash_data.angular_momentum;
152 leash_data.angular_momentum *= angular_friction;
153 angular_momentum
154 };
155 self.rotate_by_leash_angular_momentum(angular_momentum);
156 true
157 }
158
159 fn rotate_by_leash_angular_momentum(&self, angular_momentum: f64) {
161 let (yaw, pitch) = self.rotation();
162 self.set_rotation((yaw - angular_momentum as f32, pitch));
163 }
164
165 fn leash_angular_momentum(&self) -> Option<f64> {
167 self.leash_data()
168 .lock()
169 .as_ref()
170 .map(|leash_data| leash_data.angular_momentum)
171 }
172
173 fn leash_angular_friction(&self) -> f64 {
177 if self.on_ground() {
178 let Some(world) = self.level() else {
179 return BASE_HORIZONTAL_FRICTION;
180 };
181 let Some(pos) = self.block_pos_below_that_affects_movement() else {
182 return BASE_HORIZONTAL_FRICTION;
183 };
184 return f64::from(
185 world.get_block_state(pos).get_block().config.friction
186 * BASE_HORIZONTAL_FRICTION as f32,
187 );
188 }
189
190 if self.is_in_water() || self.is_in_lava() {
191 return LEASH_LIQUID_ANGULAR_FRICTION;
192 }
193
194 BASE_HORIZONTAL_FRICTION
195 }
196
197 fn can_have_a_leash_attached_to(&self, holder: &dyn Entity) -> bool {
199 self.id() != holder.id()
200 && self.leash_distance_to(holder) <= self.leash_snap_distance()
201 && self.can_be_leashed()
202 }
203
204 fn set_leashed_to(&self, holder: &SharedEntity) -> bool {
206 if self.id() == holder.id() {
207 return false;
208 }
209
210 let old_holder = self.leash_holder();
211 {
212 let mut leash_data = self.leash_data().lock();
213 if let Some(leash_data) = leash_data.as_mut() {
214 leash_data.set_holder(holder);
215 } else {
216 *leash_data = Some(LeashData::from_entity(holder));
217 }
218 }
219
220 if self.is_passenger() {
221 self.stop_riding();
222 }
223 if let Some(old_holder) = old_holder
224 && old_holder.id() != holder.id()
225 {
226 old_holder.notify_leashee_removed(self.as_entity_event_source());
227 }
228 true
229 }
230
231 fn restore_leash_from_save(&self) {
234 if let Some(attachment) = self.leash_attachment()
235 && let Some(world) = self.level()
236 {
237 match attachment {
238 LeashAttachment::Entity(uuid) => {
239 if let Some(holder) = world.get_entity_by_uuid(&uuid) {
240 let _ = self.set_leashed_to(&holder);
241 return;
242 }
243 }
244 LeashAttachment::FenceKnot(pos) => {
245 if let Some(holder) = LeashFenceKnotEntity::get_or_create_knot(&world, pos) {
246 let _ = self.set_leashed_to(&holder);
247 return;
248 }
249 }
250 }
251
252 if self.tick_count() > DELAYED_LEASH_DROP_TICKS {
253 let _ = self.spawn_at_location(ItemStack::new(&vanilla_items::LEAD), 0.0);
254 self.remove_leash_state();
255 }
256 }
257 }
258
259 fn tick_leash(&self) {
261 self.restore_leash_from_save();
262
263 if let Some(holder) = self.leash_holder() {
264 if !self.can_interact_with_level() || !holder.can_interact_with_level() {
265 if let Some(world) = self.level()
266 && world.get_game_rule(&ENTITY_DROPS)
267 {
268 self.drop_leash();
269 } else {
270 self.remove_leash();
271 }
272 return;
273 }
274 if let Some(holder) = self.leash_holder()
275 && holder.level().map(|level| level.key.clone())
276 == self.level().map(|level| level.key.clone())
277 {
278 let distance_to = self.leash_distance_to(holder.as_ref());
279 self.when_leashed_to(holder.as_ref());
280 let angular_momentum_before_distance_action = self.leash_angular_momentum();
281 if distance_to > self.leash_snap_distance() {
282 if let Some(world) = self.level() {
283 world.play_sound_at(
284 &sound_events::ITEM_LEAD_BREAK,
285 SoundSource::Neutral,
286 holder.position(),
287 1.0,
288 1.0,
289 None,
290 );
291 }
292 self.leash_too_far_behaviour();
293 } else if distance_to
294 > self.leash_elastic_distance()
295 - f64::from(holder.base().dimensions().width)
296 - f64::from(self.base().dimensions().width)
297 && self.check_elastic_interactions(holder.as_ref())
298 {
299 self.on_elastic_leash_pull();
300 } else {
301 self.close_range_leash_behaviour(holder.as_ref());
302 }
303 if !self.apply_leash_angular_momentum()
304 && let Some(angular_momentum) = angular_momentum_before_distance_action
305 {
306 self.rotate_by_leash_angular_momentum(angular_momentum);
307 }
308 }
309 }
310 }
311
312 fn drop_leash(&self) {
314 if self.leash_holder().is_none() {
315 return;
316 }
317
318 let holder = self.remove_leash_state();
319 self.on_leash_removed();
320 let _ = self.spawn_at_location(ItemStack::new(&vanilla_items::LEAD), 0.0);
321 if let Some(holder) = holder {
322 holder.notify_leashee_removed(self.as_entity_event_source());
323 }
324 }
325
326 fn remove_leash(&self) {
328 if self.leash_holder().is_some()
329 && let Some(holder) = self.remove_leash_state()
330 {
331 self.on_leash_removed();
332 holder.notify_leashee_removed(self.as_entity_event_source());
333 }
334 }
335
336 fn remove_leash_state(&self) -> Option<SharedEntity> {
339 self.leash_data()
340 .lock()
341 .take()
342 .and_then(|leash_data| leash_data.holder())
343 }
344}
345
346#[derive(Debug, Clone, Copy, PartialEq, Eq)]
347pub enum LeashAttachment {
348 Entity(Uuid),
349 FenceKnot(BlockPos),
350}
351
352#[derive(Debug, Clone)]
353pub struct LeashData {
354 pub holder: LeashHolder,
355 pub angular_momentum: f64,
356}
357
358#[derive(Debug, Clone)]
360pub enum LeashHolder {
361 Entity(WeakEntity),
363 Delayed(LeashAttachment),
365}
366
367#[derive(Debug, Clone, Copy, PartialEq)]
368pub(super) struct LeashWrench {
369 pub(super) force: DVec3,
370 pub(super) torque: f64,
371}
372
373impl LeashWrench {
374 pub const fn new(force: DVec3, torque: f64) -> Self {
375 Self { force, torque }
376 }
377}
378
379impl LeashData {
380 pub(crate) fn from_entity(holder: &SharedEntity) -> Self {
381 Self {
382 holder: LeashHolder::Entity(Arc::downgrade(holder)),
383 angular_momentum: 0.0,
384 }
385 }
386
387 pub(crate) const fn from_delayed_attachment(attachment: LeashAttachment) -> Self {
388 Self {
389 holder: LeashHolder::Delayed(attachment),
390 angular_momentum: 0.0,
391 }
392 }
393
394 pub(super) fn holder(&self) -> Option<SharedEntity> {
395 let LeashHolder::Entity(entity) = &self.holder else {
396 return None;
397 };
398 entity.upgrade()
399 }
400
401 pub(super) const fn attachment(&self) -> Option<LeashAttachment> {
402 let LeashHolder::Delayed(attachment) = self.holder else {
403 return None;
404 };
405 Some(attachment)
406 }
407
408 pub(super) fn saved_attachment(&self) -> Option<LeashAttachment> {
409 match &self.holder {
410 LeashHolder::Entity(holder) => {
411 let upgraded = holder.upgrade()?;
412 if let Some(knot) = upgraded.downcast_ref::<LeashFenceKnotEntity>() {
413 Some(LeashAttachment::FenceKnot(knot.block_pos()))
415 } else {
416 Some(LeashAttachment::Entity(upgraded.uuid()))
418 }
419 }
420 LeashHolder::Delayed(attachment) => Some(*attachment),
421 }
422 }
423
424 pub(super) fn set_holder(&mut self, holder: &SharedEntity) {
425 self.holder = LeashHolder::Entity(Arc::downgrade(holder));
426 self.angular_momentum = 0.0;
427 }
428
429 pub(super) fn save(&self, nbt: &mut NbtCompound) {
430 if let Some(attachment) = self.saved_attachment() {
431 match attachment {
432 LeashAttachment::Entity(uuid) => {
433 let mut leash = NbtCompound::new();
434 leash.insert("UUID", NbtTag::IntArray(uuid.to_int_array().to_vec()));
435 nbt.insert("leash", NbtTag::Compound(leash));
436 }
437 LeashAttachment::FenceKnot(pos) => {
438 nbt.insert("leash", NbtTag::IntArray(vec![pos.x(), pos.y(), pos.z()]));
439 }
440 }
441 }
442 }
443
444 pub(super) fn load(nbt: BorrowedNbtCompoundView<'_, '_>) -> Option<Self> {
445 if let Some(leash) = nbt.compound("leash")
446 && let Some(uuid_array) = leash.int_array("UUID")
447 && let Some(uuid) = Uuid::from_int_array(&uuid_array)
448 {
449 return Some(Self::from_delayed_attachment(LeashAttachment::Entity(uuid)));
450 }
451
452 nbt.int_array("leash")
453 .filter(|position| position.len() == 3)
454 .map(|position| {
455 Self::from_delayed_attachment(LeashAttachment::FenceKnot(BlockPos::new(
456 position[0],
457 position[1],
458 position[2],
459 )))
460 })
461 }
462}
463
464pub(super) fn leash_dimensions(entity: &dyn Entity) -> DVec3 {
465 let dimensions = entity.base().dimensions();
466 DVec3::new(
467 f64::from(dimensions.width),
468 f64::from(dimensions.height),
469 f64::from(dimensions.width),
470 )
471}
472
473pub(super) fn leash_bounding_box_center(entity: &dyn Entity) -> DVec3 {
474 let bounding_box = entity.bounding_box();
475 DVec3::new(
476 f64::midpoint(bounding_box.min_x(), bounding_box.max_x()),
477 f64::midpoint(bounding_box.min_y(), bounding_box.max_y()),
478 f64::midpoint(bounding_box.min_z(), bounding_box.max_z()),
479 )
480}
481
482pub(super) fn leash_holder_movement(entity: &dyn Entity) -> DVec3 {
483 if entity.as_mob().is_some_and(Mob::is_no_ai) {
484 return DVec3::ZERO;
485 }
486
487 entity.known_movement()
488}
489
490pub(super) fn axis_specific_leash_elasticity(force: DVec3) -> DVec3 {
491 force * LEASH_AXIS_SPECIFIC_ELASTICITY
492}
493
494pub(super) fn compute_elastic_interaction(
495 entity: &dyn Entity,
496 holder: &dyn Entity,
497 slack_distance: f64,
498) -> Option<LeashWrench> {
499 let entity_y_rot = entity.rotation().0 * DEG_TO_RAD;
500 let entity_attach_vector = y_rot(
501 ENTITY_LEASH_ATTACHMENT_POINT * leash_dimensions(entity),
502 -entity_y_rot,
503 );
504 let entity_attach_pos = entity.position() + entity_attach_vector;
505
506 let holder_y_rot = holder.rotation().0 * DEG_TO_RAD;
507 let holder_attach_vector = y_rot(
508 LEASHER_ATTACHMENT_POINT * leash_dimensions(holder),
509 -holder_y_rot,
510 );
511 let holder_attach_pos = holder.position() + holder_attach_vector;
512
513 compute_dampened_spring_interaction(
514 holder_attach_pos,
515 entity_attach_pos,
516 slack_distance,
517 leash_holder_movement(entity),
518 entity_attach_vector,
519 )
520}
521
522pub(super) fn compute_dampened_spring_interaction(
523 pivot_point: DVec3,
524 object_position: DVec3,
525 spring_slack: f64,
526 object_motion: DVec3,
527 lever_arm: DVec3,
528) -> Option<LeashWrench> {
529 let distance = object_position.distance(pivot_point);
530 if distance < spring_slack {
531 return None;
532 }
533
534 let mut displacement = (pivot_point - object_position).normalize() * (distance - spring_slack);
535 let torque = torque_from_force(lever_arm, displacement);
536 if object_motion.dot(displacement) >= 0.0 {
537 displacement *= 1.0 - LEASH_SPRING_DAMPENING;
538 }
539
540 Some(LeashWrench::new(displacement, torque))
541}
542
543pub(super) fn torque_from_force(lever_arm: DVec3, force: DVec3) -> f64 {
544 lever_arm.z * force.x - lever_arm.x * force.z
545}