1use super::utils::*;
2use crate::{
3 comp::{
4 CharacterState, InventoryManip, StateUpdate, character_state::OutputEvents,
5 controller::InputKind, item::ItemDefinitionIdOwned, slot::InvSlotId,
6 },
7 consts::MAX_INTERACT_RANGE,
8 event::{HelpDownedEvent, InventoryManipEvent, LocalEvent, ToggleSpriteLightEvent},
9 outcome::Outcome,
10 states::behavior::{CharacterBehavior, JoinData},
11 terrain::SpriteKind,
12 uid::Uid,
13 util::Dir,
14};
15use serde::{Deserialize, Serialize};
16use std::time::Duration;
17use vek::Vec3;
18
19#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
21pub struct StaticData {
22 pub buildup_duration: Duration,
24 pub use_duration: Option<Duration>,
26 pub recover_duration: Duration,
28 pub interact: InteractKind,
30 pub was_wielded: bool,
32 pub was_sneak: bool,
34 pub required_item: Option<(ItemDefinitionIdOwned, InvSlotId, bool)>,
42}
43
44#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
45pub struct Data {
46 pub static_data: StaticData,
49 pub timer: Duration,
51 pub stage_section: StageSection,
53}
54
55impl CharacterBehavior for Data {
56 fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
57 let mut update = StateUpdate::from(data);
58
59 'logic: {
60 let interact_pos = match &self.static_data.interact {
61 InteractKind::Invalid => {
62 end_ability(data, &mut update);
63 break 'logic;
64 },
65 InteractKind::Entity { target, .. } => {
66 if let Some(pos) = data
67 .id_maps
68 .uid_entity(*target)
69 .and_then(|target| data.prev_phys_caches.get(target))
70 .and_then(|prev| prev.pos)
71 {
72 pos.0
73 } else {
74 end_ability(data, &mut update);
76 break 'logic;
77 }
78 },
79 InteractKind::Sprite { pos, .. } => pos.as_() + 0.5,
80 };
81
82 if interact_pos.distance_squared(data.pos.0) > MAX_INTERACT_RANGE.powi(2) {
83 end_ability(data, &mut update);
84 break 'logic;
85 }
86
87 let ori_dir = Dir::from_unnormalized(Vec3::from((interact_pos - data.pos.0).xy()));
88 handle_orientation(data, &mut update, 1.0, ori_dir);
89 handle_move(
90 data,
91 &mut update,
92 self.static_data.interact.movement().unwrap_or(0.0),
93 );
94
95 match self.stage_section {
96 StageSection::Buildup => {
97 if self.timer < self.static_data.buildup_duration {
98 if let CharacterState::Interact(c) = &mut update.character {
100 c.timer = tick_attack_or_default(data, self.timer, None);
101 }
102 } else {
103 if let CharacterState::Interact(c) = &mut update.character {
105 c.timer = Duration::default();
106 c.stage_section = StageSection::Action;
107 }
108 }
109 },
110 StageSection::Action => {
111 if self
112 .static_data
113 .use_duration
114 .is_none_or(|use_duration| self.timer < use_duration)
115 {
116 if let CharacterState::Interact(c) = &mut update.character {
118 c.timer = tick_attack_or_default(data, self.timer, None);
119 }
120 } else {
121 if let CharacterState::Interact(c) = &mut update.character {
123 c.timer = Duration::default();
124 c.stage_section = StageSection::Recover;
125 }
126 }
127 },
128 StageSection::Recover => {
129 if self.timer < self.static_data.recover_duration {
130 if let CharacterState::Interact(c) = &mut update.character {
132 c.timer = tick_attack_or_default(data, self.timer, None);
133 }
134 } else {
135 let (has_required_item, inv_slot) = self
137 .static_data
138 .required_item
139 .as_ref()
140 .map_or((true, None), |&(ref item_def_id, slot, consume)| {
141 let has_item = data
143 .inventory
144 .and_then(|inv| inv.get(slot))
145 .is_some_and(|item| item.item_definition_id() == *item_def_id);
146
147 (has_item, has_item.then_some((slot, consume)))
148 });
149 if has_required_item {
150 match self.static_data.interact {
151 InteractKind::Invalid => unreachable!(),
154 InteractKind::Entity { target, kind, .. } => match kind {
155 crate::interaction::InteractionKind::HelpDowned => {
156 output_events.emit_server(HelpDownedEvent {
157 target,
158 helper: Some(*data.uid),
159 });
160 },
161 crate::interaction::InteractionKind::Pet => {},
162 },
163 InteractKind::Sprite { pos, kind } => {
164 let inv_manip = InventoryManip::Collect {
165 sprite_pos: pos,
166 required_item: inv_slot,
167 };
168 match kind {
169 SpriteInteractKind::ToggleLight(enable) => output_events
170 .emit_server(ToggleSpriteLightEvent {
171 entity: data.entity,
172 pos,
173 enable,
174 }),
175 _ => output_events.emit_server(InventoryManipEvent(
176 data.entity,
177 inv_manip,
178 )),
179 }
180
181 if matches!(kind, SpriteInteractKind::Unlock) {
182 output_events.emit_local(LocalEvent::CreateOutcome(
183 Outcome::SpriteUnlocked { pos },
184 ));
185 }
186 },
187 }
188 }
189 end_ability(data, &mut update);
191 }
192 },
193 _ => {
194 end_ability(data, &mut update);
196 },
197 }
198 }
199
200 handle_wield(data, &mut update);
202
203 if input_is_pressed(data, InputKind::Roll) {
205 handle_input(data, output_events, &mut update, InputKind::Roll);
206 }
207
208 if handle_jump(data, output_events, &mut update, 1.0) {
209 end_ability(data, &mut update);
210 }
211
212 update
213 }
214}
215
216#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
217pub enum InteractKind {
218 Invalid,
219 Entity {
220 target: Uid,
221 kind: crate::interaction::InteractionKind,
222 },
223 Sprite {
224 pos: Vec3<i32>,
226 kind: SpriteInteractKind,
227 },
228}
229
230impl InteractKind {
231 pub fn movement(&self) -> Option<f32> {
232 match self {
233 Self::Invalid | Self::Sprite { .. } => None,
234 Self::Entity { kind, .. } => kind.movement(),
235 }
236 }
237}
238
239impl crate::interaction::InteractionKind {
240 pub fn movement(&self) -> Option<f32> {
241 match self {
242 Self::HelpDowned => Some(0.1),
243 Self::Pet => Some(0.7),
244 }
245 }
246
247 pub fn durations(&self) -> (Duration, Option<Duration>, Duration) {
248 match self {
249 Self::HelpDowned => (
250 Duration::from_secs_f32(0.5),
251 Some(Duration::from_secs_f32(4.0)),
252 Duration::from_secs_f32(0.5),
253 ),
254 Self::Pet => (
255 Duration::from_secs_f32(0.0),
256 None,
257 Duration::from_secs_f32(0.0),
258 ),
259 }
260 }
261}
262
263#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
265pub enum SpriteInteractKind {
266 Chest,
267 Harvestable,
268 Collectible,
269 Unlock,
270 Fallback,
271 ToggleLight(bool),
272}
273
274impl From<SpriteKind> for Option<SpriteInteractKind> {
275 fn from(sprite_kind: SpriteKind) -> Self {
276 match sprite_kind {
277 SpriteKind::Apple
278 | SpriteKind::Mushroom
279 | SpriteKind::RedFlower
280 | SpriteKind::Sunflower
281 | SpriteKind::Coconut
282 | SpriteKind::Beehive
283 | SpriteKind::Cotton
284 | SpriteKind::Moonbell
285 | SpriteKind::Pyrebloom
286 | SpriteKind::WildFlax
287 | SpriteKind::RoundCactus
288 | SpriteKind::ShortFlatCactus
289 | SpriteKind::MedFlatCactus
290 | SpriteKind::Wood
291 | SpriteKind::Bamboo
292 | SpriteKind::Hardwood
293 | SpriteKind::Ironwood
294 | SpriteKind::Frostwood
295 | SpriteKind::Eldwood
296 | SpriteKind::Lettuce => Some(SpriteInteractKind::Harvestable),
297 SpriteKind::Stones
298 | SpriteKind::Twigs
299 | SpriteKind::VialEmpty
300 | SpriteKind::Bowl
301 | SpriteKind::PotionMinor
302 | SpriteKind::Seashells
303 | SpriteKind::Bomb => Some(SpriteInteractKind::Collectible),
304 SpriteKind::Keyhole
305 | SpriteKind::BoneKeyhole
306 | SpriteKind::HaniwaKeyhole
307 | SpriteKind::SahaginKeyhole
308 | SpriteKind::VampireKeyhole
309 | SpriteKind::GlassKeyhole
310 | SpriteKind::KeyholeBars
311 | SpriteKind::TerracottaKeyhole
312 | SpriteKind::MyrmidonKeyhole
313 | SpriteKind::MinotaurKeyhole => Some(SpriteInteractKind::Unlock),
314 _ if sprite_kind.is_defined_as_container()
317 && sprite_kind.collectible_info() == Some(None) =>
318 {
319 Some(SpriteInteractKind::Chest)
320 },
321 _ if sprite_kind.collectible_info() == Some(None) => Some(SpriteInteractKind::Fallback),
322 _ => None,
323 }
324 }
325}
326
327impl SpriteInteractKind {
328 pub fn durations(&self) -> (Duration, Duration, Duration) {
330 match self {
331 Self::Chest => (
332 Duration::from_secs_f32(0.5),
333 Duration::from_secs_f32(2.0),
334 Duration::from_secs_f32(0.5),
335 ),
336 Self::Collectible => (
337 Duration::from_secs_f32(0.1),
338 Duration::from_secs_f32(0.2),
339 Duration::from_secs_f32(0.1),
340 ),
341 Self::Harvestable => (
342 Duration::from_secs_f32(0.3),
343 Duration::from_secs_f32(0.3),
344 Duration::from_secs_f32(0.2),
345 ),
346 Self::Fallback => (
347 Duration::from_secs_f32(5.0),
348 Duration::from_secs_f32(5.0),
349 Duration::from_secs_f32(5.0),
350 ),
351 Self::Unlock => (
352 Duration::from_secs_f32(0.8),
353 Duration::from_secs_f32(1.0),
354 Duration::from_secs_f32(0.3),
355 ),
356 Self::ToggleLight(_) => (
357 Duration::from_secs_f32(0.1),
358 Duration::from_secs_f32(0.2),
359 Duration::from_secs_f32(0.1),
360 ),
361 }
362 }
363}