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