veloren_common/
interaction.rs

1use std::time::Duration;
2
3use hashbrown::HashMap;
4use serde::{Deserialize, Serialize};
5use specs::{
6    Component, DerefFlaggedStorage, Entities, Read, ReadStorage, WriteStorage,
7    storage::GenericWriteStorage,
8};
9
10use crate::{
11    comp::{Alignment, CharacterState, Health, Pos},
12    consts::{MAX_INTERACT_RANGE, MAX_MOUNT_RANGE},
13    link::{Is, Link, LinkHandle, Role},
14    uid::{IdMaps, Uid},
15};
16
17#[derive(Serialize, Deserialize, Debug)]
18pub struct Interactor;
19
20impl Role for Interactor {
21    type Link = Interaction;
22}
23
24#[derive(Default, Serialize, Deserialize, Debug, Clone)]
25pub struct Interactors {
26    interactors: HashMap<Uid, LinkHandle<Interaction>>,
27}
28
29impl Interactors {
30    pub fn get(&self, uid: Uid) -> Option<&LinkHandle<Interaction>> { self.interactors.get(&uid) }
31
32    pub fn iter(&self) -> impl Iterator<Item = &LinkHandle<Interaction>> {
33        self.interactors.values()
34    }
35
36    pub fn has_interaction(&self, kind: InteractionKind) -> bool {
37        self.iter().any(|i| i.kind == kind)
38    }
39}
40
41impl Component for Interactors {
42    type Storage = DerefFlaggedStorage<Interactors>;
43}
44
45#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
46pub enum InteractionKind {
47    HelpDowned,
48    Pet,
49}
50
51// TODO: Do we want to use this for sprite interactions too?
52#[derive(Serialize, Deserialize, Debug)]
53pub struct Interaction {
54    pub interactor: Uid,
55    pub target: Uid,
56    pub kind: InteractionKind,
57}
58
59#[derive(Debug)]
60pub enum InteractionError {
61    NoSuchEntity,
62    NotInteractable,
63    CannotInteract,
64}
65
66pub fn can_help_downed(pos: Pos, target_pos: Pos, target_health: Option<&Health>) -> bool {
67    let within_distance = pos.0.distance_squared(target_pos.0) <= MAX_INTERACT_RANGE.powi(2);
68    let consumed_death_protection =
69        target_health.is_some_and(|health| health.has_consumed_death_protection());
70
71    within_distance && consumed_death_protection
72}
73
74pub fn can_pet(pos: Pos, target_pos: Pos, target_alignment: Option<&Alignment>) -> bool {
75    let within_distance = pos.0.distance_squared(target_pos.0) <= MAX_MOUNT_RANGE.powi(2);
76    let valid_alignment = matches!(
77        target_alignment,
78        Some(Alignment::Owned(_) | Alignment::Tame)
79    );
80
81    within_distance && valid_alignment
82}
83
84impl Link for Interaction {
85    type CreateData<'a> = (
86        Read<'a, IdMaps>,
87        WriteStorage<'a, Is<Interactor>>,
88        WriteStorage<'a, Interactors>,
89        WriteStorage<'a, CharacterState>,
90        ReadStorage<'a, Health>,
91        ReadStorage<'a, Pos>,
92        ReadStorage<'a, Alignment>,
93    );
94    type DeleteData<'a> = (
95        Read<'a, IdMaps>,
96        WriteStorage<'a, Is<Interactor>>,
97        WriteStorage<'a, Interactors>,
98        WriteStorage<'a, CharacterState>,
99    );
100    type Error = InteractionError;
101    type PersistData<'a> = (
102        Read<'a, IdMaps>,
103        Entities<'a>,
104        ReadStorage<'a, Health>,
105        ReadStorage<'a, Is<Interactor>>,
106        ReadStorage<'a, Interactors>,
107        ReadStorage<'a, CharacterState>,
108        ReadStorage<'a, Pos>,
109        ReadStorage<'a, Alignment>,
110    );
111
112    fn create(
113        this: &crate::link::LinkHandle<Self>,
114        (id_maps, is_interactors, interactors, character_states, healths, positions, alignments): &mut Self::CreateData<
115            '_,
116        >,
117    ) -> Result<(), Self::Error> {
118        let entity = |uid: Uid| id_maps.uid_entity(uid);
119
120        if this.interactor == this.target {
121            // Can't interact with itself
122            Err(InteractionError::NotInteractable)
123        } else if let Some(interactor) = entity(this.interactor)
124            && let Some(target) = entity(this.target)
125        {
126            // Can only interact with one thing at a time.
127            if !is_interactors.contains(interactor)
128                && character_states
129                    .get(interactor)
130                    .is_none_or(|state| state.can_interact())
131                && let Some(pos) = positions.get(interactor)
132                && let Some(target_pos) = positions.get(target)
133                && match this.kind {
134                    InteractionKind::HelpDowned => {
135                        can_help_downed(*pos, *target_pos, healths.get(target))
136                    },
137                    InteractionKind::Pet => can_pet(*pos, *target_pos, alignments.get(target)),
138                }
139            {
140                if let Some(mut character_state) = character_states.get_mut(interactor) {
141                    let (buildup_duration, use_duration, recover_duration) = this.kind.durations();
142                    *character_state = CharacterState::Interact(crate::states::interact::Data {
143                        static_data: crate::states::interact::StaticData {
144                            buildup_duration,
145                            use_duration,
146                            recover_duration,
147                            interact: crate::states::interact::InteractKind::Entity {
148                                target: this.target,
149                                kind: this.kind,
150                            },
151                            was_wielded: character_state.is_wield(),
152                            was_sneak: character_state.is_stealthy(),
153                            required_item: None,
154                        },
155                        timer: Duration::default(),
156                        stage_section: crate::states::utils::StageSection::Buildup,
157                    });
158
159                    let _ = is_interactors.insert(interactor, this.make_role());
160                    if let Some(mut interactors) = interactors.get_mut_or_default(target) {
161                        interactors
162                            .interactors
163                            .insert(this.interactor, this.clone());
164                    } else {
165                        return Err(InteractionError::CannotInteract);
166                    }
167
168                    Ok(())
169                } else {
170                    Err(InteractionError::CannotInteract)
171                }
172            } else {
173                Err(InteractionError::CannotInteract)
174            }
175        } else {
176            Err(InteractionError::NoSuchEntity)
177        }
178    }
179
180    fn persist(
181        this: &crate::link::LinkHandle<Self>,
182        (
183            id_maps,
184            entities,
185            healths,
186            is_interactors,
187            interactors,
188            character_states,
189            positions,
190            alignments,
191        ): &mut Self::PersistData<'_>,
192    ) -> bool {
193        let entity = |uid: Uid| id_maps.uid_entity(uid);
194        let is_alive =
195            |entity| entities.is_alive(entity) && healths.get(entity).is_none_or(|h| !h.is_dead);
196
197        if let Some(interactor) = entity(this.interactor)
198            && let Some(target) = entity(this.target)
199            && is_interactors.contains(interactor)
200            && let Some(interactors) = interactors.get(target)
201            && interactors.interactors.contains_key(&this.interactor)
202            && is_alive(interactor)
203            && is_alive(target)
204            && let Some(pos) = positions.get(interactor)
205            && let Some(target_pos) = positions.get(target)
206            && match this.kind {
207                InteractionKind::HelpDowned => {
208                    can_help_downed(*pos, *target_pos, healths.get(target))
209                },
210                InteractionKind::Pet => can_pet(*pos, *target_pos, alignments.get(target)),
211            }
212            && let Some(CharacterState::Interact(crate::states::interact::Data {
213                static_data:
214                    crate::states::interact::StaticData {
215                        interact:
216                            crate::states::interact::InteractKind::Entity {
217                                target: state_target,
218                                kind: state_kind,
219                            },
220                        ..
221                    },
222                ..
223            })) = character_states.get(interactor)
224            && *state_target == this.target
225            && *state_kind == this.kind
226        {
227            true
228        } else {
229            false
230        }
231    }
232
233    fn delete(
234        this: &crate::link::LinkHandle<Self>,
235        (id_maps, is_interactors, interactors, character_states): &mut Self::DeleteData<'_>,
236    ) {
237        let entity = |uid: Uid| id_maps.uid_entity(uid);
238
239        let interactor = entity(this.interactor);
240        let target = entity(this.target);
241
242        interactor.map(|interactor| is_interactors.remove(interactor));
243        target.map(|target| {
244            if let Some(mut i) = interactors.get_mut(target) {
245                i.interactors.remove(&this.interactor);
246
247                if i.interactors.is_empty() {
248                    interactors.remove(target);
249                }
250            }
251        });
252
253        // yay pattern matching 🦀
254        if let Some(character_state) = interactor
255            .and_then(|interactor| character_states.get_mut(interactor))
256            .as_deref_mut()
257            && let CharacterState::Interact(crate::states::interact::Data {
258                static_data:
259                    crate::states::interact::StaticData {
260                        interact:
261                            ref mut interact @ crate::states::interact::InteractKind::Entity {
262                                target: state_target,
263                                kind: state_kind,
264                                ..
265                            },
266                        ..
267                    },
268                ..
269            }) = *character_state
270            && state_target == this.target
271            && state_kind == this.kind
272        {
273            // If the character state we created with this link still persists, the target
274            // has become invalid so we set it to that. And the character state decides how
275            // it handles that, be it ending or something else.
276            *interact = crate::states::interact::InteractKind::Invalid;
277        }
278    }
279}