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(data, self.timer, None),
126 ..*self
127 });
128 } else {
129 end_ability(data, &mut update);
131 }
132 },
133 _ => {
134 end_ability(data, &mut update);
136 },
137 }
138
139 if input_is_pressed(data, InputKind::Roll) {
141 handle_input(data, output_events, &mut update, InputKind::Roll);
142 }
143
144 if matches!(update.character, CharacterState::Roll(_)) {
145 output_events.emit_server(BuffEvent {
147 entity: data.entity,
148 buff_change: BuffChange::RemoveByKind(BuffKind::Potion),
149 });
150 output_events.emit_server(BuffEvent {
151 entity: data.entity,
152 buff_change: BuffChange::RemoveByKind(BuffKind::Saturation),
153 });
154 }
155
156 update
157 }
158}
159
160#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
162pub enum ItemUseKind {
163 Consumable(ConsumableKind),
164}
165
166impl From<&ItemKind> for Option<ItemUseKind> {
167 fn from(item_kind: &ItemKind) -> Self {
168 match item_kind {
169 ItemKind::Consumable { kind, .. } => Some(ItemUseKind::Consumable(*kind)),
170 _ => None,
171 }
172 }
173}
174
175impl ItemUseKind {
176 pub fn durations(&self) -> (Duration, Duration, Duration) {
178 match self {
179 Self::Consumable(ConsumableKind::Drink) => (
180 Duration::from_secs_f32(0.1),
181 Duration::from_secs_f32(1.1),
182 Duration::from_secs_f32(0.1),
183 ),
184 Self::Consumable(ConsumableKind::Food) => (
185 Duration::from_secs_f32(1.0),
186 Duration::from_secs_f32(4.0),
187 Duration::from_secs_f32(0.5),
188 ),
189 Self::Consumable(ConsumableKind::ComplexFood) => (
190 Duration::from_secs_f32(1.0),
191 Duration::from_secs_f32(4.5),
192 Duration::from_secs_f32(0.5),
193 ),
194 Self::Consumable(ConsumableKind::Charm) => (
195 Duration::from_secs_f32(0.1),
196 Duration::from_secs_f32(0.8),
197 Duration::from_secs_f32(0.1),
198 ),
199 Self::Consumable(ConsumableKind::Recipe) => (
200 Duration::from_secs_f32(0.0),
201 Duration::from_secs_f32(0.0),
202 Duration::from_secs_f32(0.0),
203 ),
204 }
205 }
206}
207
208enum UsePoint {
210 BuildupUse,
212 UseRecover,
214}
215
216fn use_item(data: &JoinData, output_events: &mut OutputEvents, state: &Data) {
217 let item_is_same = data
219 .inventory
220 .and_then(|inv| inv.get(state.static_data.inv_slot))
221 .is_some_and(|item| item.item_hash() == state.static_data.item_hash);
222 if item_is_same {
223 let inv_manip = InventoryManip::Use(Slot::Inventory(state.static_data.inv_slot));
225 output_events.emit_server(InventoryManipEvent(data.entity, inv_manip));
226 }
227}