1use std::f32::consts::PI;
2use std::sync::Arc;
3
4use glam::DVec3;
5use simdnbt::borrow::NbtCompound as BorrowedNbtCompoundView;
6use simdnbt::owned::{NbtCompound, NbtTag};
7use steel_utils::{BlockPos, Downcast as _, UuidExt as _};
8use uuid::Uuid;
9
10use crate::entity::entities::LeashFenceKnotEntity;
11use crate::entity::{Entity, Mob, SharedEntity, WeakEntity};
12
13pub(super) const LEASH_SNAP_DISTANCE: f64 = 12.0;
14pub(super) const LEASH_ELASTIC_DISTANCE: f64 = 6.0;
15pub(super) const LEASH_AXIS_SPECIFIC_ELASTICITY: DVec3 = DVec3::new(0.8, 0.2, 0.8);
16pub(super) const LEASH_SPRING_DAMPENING: f64 = 0.7;
17pub(super) const LEASH_TORSIONAL_ELASTICITY: f64 = 10.0;
18pub(super) const LEASH_STIFFNESS: f64 = 0.11;
19pub(super) const ENTITY_LEASH_ATTACHMENT_POINT: DVec3 = DVec3::new(0.0, 0.5, 0.5);
20pub(super) const LEASHER_ATTACHMENT_POINT: DVec3 = DVec3::new(0.0, 0.5, 0.0);
21pub(super) const DELAYED_LEASH_DROP_TICKS: i32 = 100;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum LeashAttachment {
25 Entity(Uuid),
26 FenceKnot(BlockPos),
27}
28
29#[derive(Debug, Clone)]
30pub(super) struct LeashData {
31 pub(super) attachment: LeashAttachment,
32 pub(super) holder: Option<WeakEntity>,
33 pub(super) angular_momentum: f64,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq)]
37pub(super) struct LeashWrench {
38 pub(super) force: DVec3,
39 pub(super) torque: f64,
40}
41
42impl LeashWrench {
43 pub(super) const fn new(force: DVec3, torque: f64) -> Self {
44 Self { force, torque }
45 }
46}
47
48impl LeashData {
49 pub(super) fn from_entity(holder: &SharedEntity) -> Self {
50 let attachment = holder.downcast_ref::<LeashFenceKnotEntity>().map_or_else(
51 || LeashAttachment::Entity(holder.uuid()),
52 |knot| LeashAttachment::FenceKnot(knot.block_pos()),
53 );
54 Self {
55 attachment,
56 holder: Some(Arc::downgrade(holder)),
57 angular_momentum: 0.0,
58 }
59 }
60
61 pub(super) const fn from_delayed_attachment(attachment: LeashAttachment) -> Self {
62 Self {
63 attachment,
64 holder: None,
65 angular_momentum: 0.0,
66 }
67 }
68
69 pub(super) fn holder(&self) -> Option<SharedEntity> {
70 self.holder.as_ref().and_then(WeakEntity::upgrade)
71 }
72
73 pub(super) fn saved_attachment(&self) -> LeashAttachment {
74 self.holder().map_or(self.attachment, |holder| {
75 holder.downcast_ref::<LeashFenceKnotEntity>().map_or_else(
76 || LeashAttachment::Entity(holder.uuid()),
77 |knot| LeashAttachment::FenceKnot(knot.block_pos()),
78 )
79 })
80 }
81
82 pub(super) fn set_holder(&mut self, holder: &SharedEntity) {
83 self.attachment = holder.downcast_ref::<LeashFenceKnotEntity>().map_or_else(
84 || LeashAttachment::Entity(holder.uuid()),
85 |knot| LeashAttachment::FenceKnot(knot.block_pos()),
86 );
87 self.holder = Some(Arc::downgrade(holder));
88 self.angular_momentum = 0.0;
89 }
90
91 pub(super) fn save(&self, nbt: &mut NbtCompound) {
92 match self.saved_attachment() {
93 LeashAttachment::Entity(uuid) => {
94 let mut leash = NbtCompound::new();
95 leash.insert("UUID", NbtTag::IntArray(uuid.to_int_array().to_vec()));
96 nbt.insert("leash", NbtTag::Compound(leash));
97 }
98 LeashAttachment::FenceKnot(pos) => {
99 nbt.insert("leash", NbtTag::IntArray(vec![pos.x(), pos.y(), pos.z()]));
100 }
101 }
102 }
103
104 pub(super) fn load(nbt: BorrowedNbtCompoundView<'_, '_>) -> Option<Self> {
105 if let Some(leash) = nbt.compound("leash")
106 && let Some(uuid_array) = leash.int_array("UUID")
107 && let Some(uuid) = Uuid::from_int_array(&uuid_array)
108 {
109 return Some(Self::from_delayed_attachment(LeashAttachment::Entity(uuid)));
110 }
111
112 nbt.int_array("leash")
113 .filter(|position| position.len() == 3)
114 .map(|position| {
115 Self::from_delayed_attachment(LeashAttachment::FenceKnot(BlockPos::new(
116 position[0],
117 position[1],
118 position[2],
119 )))
120 })
121 }
122}
123
124pub(super) fn leash_dimensions(entity: &dyn Entity) -> DVec3 {
125 let dimensions = entity.base().dimensions();
126 DVec3::new(
127 f64::from(dimensions.width),
128 f64::from(dimensions.height),
129 f64::from(dimensions.width),
130 )
131}
132
133pub(super) fn leash_bounding_box_center(entity: &dyn Entity) -> DVec3 {
134 let bounding_box = entity.bounding_box();
135 DVec3::new(
136 f64::midpoint(bounding_box.min_x(), bounding_box.max_x()),
137 f64::midpoint(bounding_box.min_y(), bounding_box.max_y()),
138 f64::midpoint(bounding_box.min_z(), bounding_box.max_z()),
139 )
140}
141
142pub(super) fn leash_holder_movement(entity: &dyn Entity) -> DVec3 {
143 if entity.as_mob().is_some_and(Mob::is_no_ai) {
144 return DVec3::ZERO;
145 }
146
147 entity.known_movement()
148}
149
150pub(super) fn rotate_y(vector: DVec3, radians: f32) -> DVec3 {
151 let cos = f64::from(radians.cos());
152 let sin = f64::from(radians.sin());
153 DVec3::new(
154 vector.x * cos + vector.z * sin,
155 vector.y,
156 vector.z * cos - vector.x * sin,
157 )
158}
159
160pub(super) fn axis_specific_leash_elasticity(force: DVec3) -> DVec3 {
161 force * LEASH_AXIS_SPECIFIC_ELASTICITY
162}
163
164pub(super) fn compute_elastic_interaction(
165 entity: &dyn Entity,
166 holder: &dyn Entity,
167 slack_distance: f64,
168) -> Option<LeashWrench> {
169 let entity_y_rot = entity.rotation().0 * PI / 180.0;
170 let entity_attach_vector = rotate_y(
171 ENTITY_LEASH_ATTACHMENT_POINT * leash_dimensions(entity),
172 -entity_y_rot,
173 );
174 let entity_attach_pos = entity.position() + entity_attach_vector;
175
176 let holder_y_rot = holder.rotation().0 * PI / 180.0;
177 let holder_attach_vector = rotate_y(
178 LEASHER_ATTACHMENT_POINT * leash_dimensions(holder),
179 -holder_y_rot,
180 );
181 let holder_attach_pos = holder.position() + holder_attach_vector;
182
183 compute_dampened_spring_interaction(
184 holder_attach_pos,
185 entity_attach_pos,
186 slack_distance,
187 leash_holder_movement(entity),
188 entity_attach_vector,
189 )
190}
191
192pub(super) fn compute_dampened_spring_interaction(
193 pivot_point: DVec3,
194 object_position: DVec3,
195 spring_slack: f64,
196 object_motion: DVec3,
197 lever_arm: DVec3,
198) -> Option<LeashWrench> {
199 let distance = object_position.distance(pivot_point);
200 if distance < spring_slack {
201 return None;
202 }
203
204 let mut displacement = (pivot_point - object_position).normalize() * (distance - spring_slack);
205 let torque = torque_from_force(lever_arm, displacement);
206 if object_motion.dot(displacement) >= 0.0 {
207 displacement *= 1.0 - LEASH_SPRING_DAMPENING;
208 }
209
210 Some(LeashWrench::new(displacement, torque))
211}
212
213pub(super) fn torque_from_force(lever_arm: DVec3, force: DVec3) -> f64 {
214 lever_arm.z * force.x - lever_arm.x * force.z
215}