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#[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 Err(InteractionError::NotInteractable)
123 } else if let Some(interactor) = entity(this.interactor)
124 && let Some(target) = entity(this.target)
125 {
126 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 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 *interact = crate::states::interact::InteractKind::Invalid;
277 }
278 }
279}