1use super::utils::*;
2use crate::{
3 comp::{
4 CharacterState, InventoryManip, StateUpdate,
5 buff::{BuffChange, BuffKind},
6 character_state::OutputEvents,
7 controller::InputKind,
8 inventory::{
9 item::{ConsumableKind, ItemKind},
10 slot::{InvSlotId, Slot},
11 },
12 },
13 event::{BuffEvent, InventoryManipEvent},
14 states::behavior::{CharacterBehavior, JoinData},
15};
16use serde::{Deserialize, Serialize};
17use std::time::Duration;
18
19#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
21pub struct StaticData {
22 pub buildup_duration: Duration,
24 pub use_duration: Duration,
26 pub recover_duration: Duration,
28 pub inv_slot: InvSlotId,
30 pub item_hash: u64,
32 pub item_kind: ItemUseKind,
34 pub was_wielded: bool,
36 pub was_sneak: bool,
38}
39
40#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
41pub struct Data {
42 pub static_data: StaticData,
45 pub timer: Duration,
47 pub stage_section: StageSection,
49}
50
51impl CharacterBehavior for Data {
52 fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
53 let mut update = StateUpdate::from(data);
54
55 match self.static_data.item_kind {
56 ItemUseKind::Consumable(
57 ConsumableKind::Drink | ConsumableKind::Charm | ConsumableKind::Recipe,
58 ) => {
59 handle_orientation(data, &mut update, 1.0, None);
60 handle_move(data, &mut update, 1.0);
61 },
62 ItemUseKind::Consumable(ConsumableKind::Food | ConsumableKind::ComplexFood) => {
63 handle_orientation(data, &mut update, 0.0, None);
64 handle_move(data, &mut update, 0.0);
65 },
66 }
67
68 let use_point = match self.static_data.item_kind {
69 ItemUseKind::Consumable(
70 ConsumableKind::Drink | ConsumableKind::Food | ConsumableKind::Recipe,
71 ) => UsePoint::BuildupUse,
72 ItemUseKind::Consumable(ConsumableKind::ComplexFood | ConsumableKind::Charm) => {
73 UsePoint::UseRecover
74 },
75 };
76
77 match self.stage_section {
78 StageSection::Buildup => {
79 if self.timer < self.static_data.buildup_duration {
80 update.character = CharacterState::UseItem(Data {
82 static_data: self.static_data.clone(),
83 timer: tick_attack_or_default(data, self.timer, None),
84 ..*self
85 });
86 } else {
87 update.character = CharacterState::UseItem(Data {
89 static_data: self.static_data.clone(),
90 timer: Duration::default(),
91 stage_section: StageSection::Action,
92 });
93 if let UsePoint::BuildupUse = use_point {
94 use_item(data, output_events, self);
96 }
97 }
98 },
99 StageSection::Action => {
100 if self.timer < self.static_data.use_duration {
101 update.character = CharacterState::UseItem(Data {
103 static_data: self.static_data.clone(),
104 timer: tick_attack_or_default(data, self.timer, None),
105 ..*self
106 });
107 } else {
108 update.character = CharacterState::UseItem(Data {
110 static_data: self.static_data.clone(),
111 timer: Duration::default(),
112 stage_section: StageSection::Recover,
113 });
114 if let UsePoint::UseRecover = use_point {
115 use_item(data, output_events, self);
117 }
118 }
119 },
120 StageSection::Recover => {
121 if self.timer < self.static_data.recover_duration {
122 update.character = CharacterState::UseItem(Data {
124 static_data: self.static_data.clone(),
125 timer: tick_attack_or_default(
126 data,
127 self.timer,
128 Some(data.stats.recovery_speed_modifier),
129 ),
130 ..*self
131 });
132 } else {
133 end_ability(data, &mut update);
135 }
136 },
137 _ => {
138 end_ability(data, &mut update);
140 },
141 }
142
143 if input_is_pressed(data, InputKind::Roll) {
145 handle_input(data, output_events, &mut update, InputKind::Roll);
146 }
147
148 if matches!(update.character, CharacterState::Roll(_)) {
149 output_events.emit_server(BuffEvent {
151 entity: data.entity,
152 buff_change: BuffChange::RemoveByKind(BuffKind::Potion),
153 });
154 output_events.emit_server(BuffEvent {
155 entity: data.entity,
156 buff_change: BuffChange::RemoveByKind(BuffKind::Saturation),
157 });
158 }
159
160 update
161 }
162}
163
164#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
166pub enum ItemUseKind {
167 Consumable(ConsumableKind),
168}
169
170impl From<&ItemKind> for Option<ItemUseKind> {
171 fn from(item_kind: &ItemKind) -> Self {
172 match item_kind {
173 ItemKind::Consumable { kind, .. } => Some(ItemUseKind::Consumable(*kind)),
174 _ => None,
175 }
176 }
177}
178
179impl ItemUseKind {
180 pub fn durations(&self) -> (Duration, Duration, Duration) {
182 match self {
183 Self::Consumable(ConsumableKind::Drink) => (
184 Duration::from_secs_f32(0.1),
185 Duration::from_secs_f32(1.1),
186 Duration::from_secs_f32(0.1),
187 ),
188 Self::Consumable(ConsumableKind::Food) => (
189 Duration::from_secs_f32(1.0),
190 Duration::from_secs_f32(4.0),
191 Duration::from_secs_f32(0.5),
192 ),
193 Self::Consumable(ConsumableKind::ComplexFood) => (
194 Duration::from_secs_f32(1.0),
195 Duration::from_secs_f32(4.5),
196 Duration::from_secs_f32(0.5),
197 ),
198 Self::Consumable(ConsumableKind::Charm) => (
199 Duration::from_secs_f32(0.1),
200 Duration::from_secs_f32(0.8),
201 Duration::from_secs_f32(0.1),
202 ),
203 Self::Consumable(ConsumableKind::Recipe) => (
204 Duration::from_secs_f32(0.0),
205 Duration::from_secs_f32(0.0),
206 Duration::from_secs_f32(0.0),
207 ),
208 }
209 }
210}
211
212enum UsePoint {
214 BuildupUse,
216 UseRecover,
218}
219
220fn use_item(data: &JoinData, output_events: &mut OutputEvents, state: &Data) {
221 let item_is_same = data
223 .inventory
224 .and_then(|inv| inv.get(state.static_data.inv_slot))
225 .is_some_and(|item| item.item_hash() == state.static_data.item_hash);
226 if item_is_same {
227 let inv_manip = InventoryManip::Use(Slot::Inventory(state.static_data.inv_slot));
229 output_events.emit_server(InventoryManipEvent(data.entity, inv_manip));
230 }
231}