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(
133 data,
134 self.timer,
135 Some(data.stats.recovery_speed_modifier),
136 );
137 }
138 } else {
139 let (has_required_item, inv_slot) = self
141 .static_data
142 .required_item
143 .as_ref()
144 .map_or((true, None), |&(ref item_def_id, slot, consume)| {
145 let has_item = data
147 .inventory
148 .and_then(|inv| inv.get(slot))
149 .is_some_and(|item| item.item_definition_id() == *item_def_id);
150
151 (has_item, has_item.then_some((slot, consume)))
152 });
153 if has_required_item {
154 match self.static_data.interact {
155 InteractKind::Invalid => unreachable!(),
158 InteractKind::Entity { target, kind, .. } => match kind {
159 crate::interaction::InteractionKind::HelpDowned => {
160 output_events.emit_server(HelpDownedEvent {
161 target,
162 helper: Some(*data.uid),
163 });
164 },
165 crate::interaction::InteractionKind::Pet => {},
166 },
167 InteractKind::Sprite { pos, kind } => {
168 let inv_manip = InventoryManip::Collect {
169 sprite_pos: pos,
170 required_item: inv_slot,
171 };
172 match kind {
173 SpriteInteractKind::ToggleLight(enable) => output_events
174 .emit_server(ToggleSpriteLightEvent {
175 entity: data.entity,
176 pos,
177 enable,
178 }),
179 _ => output_events.emit_server(InventoryManipEvent(
180 data.entity,
181 inv_manip,
182 )),
183 }
184
185 if matches!(kind, SpriteInteractKind::Unlock) {
186 output_events.emit_local(LocalEvent::CreateOutcome(
187 Outcome::SpriteUnlocked { pos },
188 ));
189 }
190 },
191 }
192 }
193 end_ability(data, &mut update);
195 }
196 },
197 _ => {
198 end_ability(data, &mut update);
200 },
201 }
202 }
203
204 handle_wield(data, &mut update);
206
207 if input_is_pressed(data, InputKind::Roll) {
209 handle_input(data, output_events, &mut update, InputKind::Roll);
210 }
211
212 if handle_jump(data, output_events, &mut update, 1.0) {
213 end_ability(data, &mut update);
214 }
215
216 update
217 }
218}
219
220#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
221pub enum InteractKind {
222 Invalid,
223 Entity {
224 target: Uid,
225 kind: crate::interaction::InteractionKind,
226 },
227 Sprite {
228 pos: Vec3<i32>,
230 kind: SpriteInteractKind,
231 },
232}
233
234impl InteractKind {
235 pub fn movement(&self) -> Option<f32> {
236 match self {
237 Self::Invalid | Self::Sprite { .. } => None,
238 Self::Entity { kind, .. } => kind.movement(),
239 }
240 }
241}
242
243impl crate::interaction::InteractionKind {
244 pub fn movement(&self) -> Option<f32> {
245 match self {
246 Self::HelpDowned => Some(0.1),
247 Self::Pet => Some(0.7),
248 }
249 }
250
251 pub fn durations(&self) -> (Duration, Option<Duration>, Duration) {
252 match self {
253 Self::HelpDowned => (
254 Duration::from_secs_f32(0.5),
255 Some(Duration::from_secs_f32(4.0)),
256 Duration::from_secs_f32(0.5),
257 ),
258 Self::Pet => (
259 Duration::from_secs_f32(0.0),
260 None,
261 Duration::from_secs_f32(0.0),
262 ),
263 }
264 }
265}
266
267#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
269pub enum SpriteInteractKind {
270 Chest,
271 Harvestable,
272 Collectible,
273 Unlock,
274 Fallback,
275 ToggleLight(bool),
276}
277
278impl From<SpriteKind> for Option<SpriteInteractKind> {
279 fn from(sprite_kind: SpriteKind) -> Self {
280 match sprite_kind {
281 SpriteKind::Apple
282 | SpriteKind::Mushroom
283 | SpriteKind::RedFlower
284 | SpriteKind::Sunflower
285 | SpriteKind::Coconut
286 | SpriteKind::Beehive
287 | SpriteKind::Cotton
288 | SpriteKind::Moonbell
289 | SpriteKind::Pyrebloom
290 | SpriteKind::WildFlax
291 | SpriteKind::RoundCactus
292 | SpriteKind::ShortFlatCactus
293 | SpriteKind::MedFlatCactus
294 | SpriteKind::Wood
295 | SpriteKind::Bamboo
296 | SpriteKind::Hardwood
297 | SpriteKind::Ironwood
298 | SpriteKind::Frostwood
299 | SpriteKind::Eldwood => Some(SpriteInteractKind::Harvestable),
300 SpriteKind::Stones
301 | SpriteKind::Twigs
302 | SpriteKind::VialEmpty
303 | SpriteKind::Bowl
304 | SpriteKind::PotionMinor
305 | SpriteKind::Seashells
306 | SpriteKind::Bomb => Some(SpriteInteractKind::Collectible),
307 SpriteKind::Keyhole
308 | SpriteKind::BoneKeyhole
309 | SpriteKind::HaniwaKeyhole
310 | SpriteKind::SahaginKeyhole
311 | SpriteKind::VampireKeyhole
312 | SpriteKind::GlassKeyhole
313 | SpriteKind::KeyholeBars
314 | SpriteKind::TerracottaKeyhole
315 | SpriteKind::MyrmidonKeyhole
316 | SpriteKind::MinotaurKeyhole => Some(SpriteInteractKind::Unlock),
317 _ if sprite_kind.is_container() && sprite_kind.is_collectible() => {
320 Some(SpriteInteractKind::Chest)
321 },
322 _ if sprite_kind.is_collectible() => Some(SpriteInteractKind::Fallback),
323 _ => None,
324 }
325 }
326}
327
328impl SpriteInteractKind {
329 pub fn durations(&self) -> (Duration, Duration, Duration) {
331 match self {
332 Self::Chest => (
333 Duration::from_secs_f32(0.5),
334 Duration::from_secs_f32(2.0),
335 Duration::from_secs_f32(0.5),
336 ),
337 Self::Collectible => (
338 Duration::from_secs_f32(0.1),
339 Duration::from_secs_f32(0.2),
340 Duration::from_secs_f32(0.1),
341 ),
342 Self::Harvestable => (
343 Duration::from_secs_f32(0.3),
344 Duration::from_secs_f32(0.3),
345 Duration::from_secs_f32(0.2),
346 ),
347 Self::Fallback => (
348 Duration::from_secs_f32(5.0),
349 Duration::from_secs_f32(5.0),
350 Duration::from_secs_f32(5.0),
351 ),
352 Self::Unlock => (
353 Duration::from_secs_f32(0.8),
354 Duration::from_secs_f32(1.0),
355 Duration::from_secs_f32(0.3),
356 ),
357 Self::ToggleLight(_) => (
358 Duration::from_secs_f32(0.1),
359 Duration::from_secs_f32(0.2),
360 Duration::from_secs_f32(0.1),
361 ),
362 }
363 }
364}