veloren_common/states/
use_item.rs

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/// Separated out to condense update portions of character state
20#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
21pub struct StaticData {
22    /// Buildup to item use
23    pub buildup_duration: Duration,
24    /// Duration of item use
25    pub use_duration: Duration,
26    /// Recovery after item use
27    pub recover_duration: Duration,
28    /// Inventory slot to use item from
29    pub inv_slot: InvSlotId,
30    /// Item hash, used to verify that slot still has the correct item
31    pub item_hash: u64,
32    /// Kind of item used
33    pub item_kind: ItemUseKind,
34    /// Had weapon wielded
35    pub was_wielded: bool,
36    /// Was sneaking
37    pub was_sneak: bool,
38}
39
40#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
41pub struct Data {
42    /// Struct containing data that does not change over the course of the
43    /// character state
44    pub static_data: StaticData,
45    /// Timer for each stage
46    pub timer: Duration,
47    /// What section the character stage is in
48    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                    // Build up
81                    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                    // Transitions to use section of stage
88                    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                        // Create inventory manipulation event
95                        use_item(data, output_events, self);
96                    }
97                }
98            },
99            StageSection::Action => {
100                if self.timer < self.static_data.use_duration {
101                    // Item use
102                    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                    // Transitions to recover section of stage
109                    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                        // Create inventory manipulation event
116                        use_item(data, output_events, self);
117                    }
118                }
119            },
120            StageSection::Recover => {
121                if self.timer < self.static_data.recover_duration {
122                    // Recovery
123                    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                    // Done
134                    end_ability(data, &mut update);
135                }
136            },
137            _ => {
138                // If it somehow ends up in an incorrect stage section
139                end_ability(data, &mut update);
140            },
141        }
142
143        // At end of state logic so an interrupt isn't overwritten
144        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            // Remove potion/saturation effect if left the use item state early by rolling
150            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/// Used to control effects based off of the type of item used
165#[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    /// Returns (buildup, use, recover)
181    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
212/// Used to control when the item is used in the state
213enum UsePoint {
214    /// Between buildup and use
215    BuildupUse,
216    /// Between use and recover
217    UseRecover,
218}
219
220fn use_item(data: &JoinData, output_events: &mut OutputEvents, state: &Data) {
221    // Check if the same item is in the slot
222    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        // Create inventory manipulation event
228        let inv_manip = InventoryManip::Use(Slot::Inventory(state.static_data.inv_slot));
229        output_events.emit_server(InventoryManipEvent(data.entity, inv_manip));
230    }
231}