Skip to main content

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(data, self.timer, None),
126                        ..*self
127                    });
128                } else {
129                    // Done
130                    end_ability(data, &mut update);
131                }
132            },
133            _ => {
134                // If it somehow ends up in an incorrect stage section
135                end_ability(data, &mut update);
136            },
137        }
138
139        // At end of state logic so an interrupt isn't overwritten
140        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            // Remove potion/saturation effect if left the use item state early by rolling
146            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/// Used to control effects based off of the type of item used
161#[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    /// Returns (buildup, use, recover)
177    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
208/// Used to control when the item is used in the state
209enum UsePoint {
210    /// Between buildup and use
211    BuildupUse,
212    /// Between use and recover
213    UseRecover,
214}
215
216fn use_item(data: &JoinData, output_events: &mut OutputEvents, state: &Data) {
217    // Check if the same item is in the slot
218    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        // Create inventory manipulation event
224        let inv_manip = InventoryManip::Use(Slot::Inventory(state.static_data.inv_slot));
225        output_events.emit_server(InventoryManipEvent(data.entity, inv_manip));
226    }
227}