Skip to main content

steel_core/entity/mob/
leash.rs

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}