veloren_common/states/
throw.rs

1use crate::{
2    combat::{self, CombatEffect},
3    comp::{
4        CharacterState, LightEmitter, Pos, ProjectileConstructor, StateUpdate,
5        character_state::OutputEvents, item::ToolKind, slot::EquipSlot,
6    },
7    event::ThrowEvent,
8    states::{
9        behavior::{CharacterBehavior, JoinData},
10        utils::*,
11    },
12    util::Dir,
13};
14use serde::{Deserialize, Serialize};
15use std::time::Duration;
16
17#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
18pub enum ProjectileDir {
19    // Projectile is thrown in the direction the entity is looking
20    LookDir,
21    // Projectile is thrown in the direction of an upwards facing vector slerped by some factor
22    // towards the look direction
23    Upwards(f32),
24}
25
26/// Separated out to condense update portions of character state
27#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
28pub struct StaticData {
29    /// How long the weapon needs to be prepared for
30    pub buildup_duration: Duration,
31    /// How long it takes to charge the weapon to max damage and knockback
32    pub charge_duration: Duration,
33    /// How long the action stage lasts
34    pub throw_duration: Duration,
35    /// How long the state has until exiting
36    pub recover_duration: Duration,
37    /// How much energy is drained per second when charging
38    pub energy_drain: f32,
39    /// Projectile information
40    pub projectile: ProjectileConstructor,
41    pub projectile_light: Option<LightEmitter>,
42    pub projectile_dir: ProjectileDir,
43    pub initial_projectile_speed: f32,
44    pub scaled_projectile_speed: f32,
45    /// Move speed efficiency
46    pub move_speed: f32,
47    /// What key is used to press ability
48    pub ability_info: AbilityInfo,
49    /// Adds an effect onto the main damage of the attack
50    pub damage_effect: Option<CombatEffect>,
51    /// Inventory slot to use item from
52    pub equip_slot: EquipSlot,
53    /// Item hash, used to verify that slot still has the correct item
54    pub item_hash: u64,
55    /// Type of tool thrown, stored here since it cannot be recalculated once
56    /// the thrown item is removed from the entity's inventory
57    pub tool_kind: ToolKind,
58    /// Hand info for the thrown tool, stored here since it cannot be
59    /// recalculated once the thrown item is removed from the entity's inventory
60    pub hand_info: HandInfo,
61}
62
63#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
64pub struct Data {
65    /// Struct containing data that does not change over the course of the
66    /// character state
67    pub static_data: StaticData,
68    /// Timer for each stage
69    pub timer: Duration,
70    /// What section the character stage is in
71    pub stage_section: StageSection,
72    /// Whether the attack fired already
73    pub exhausted: bool,
74}
75
76impl Data {
77    /// How complete the charge is, on a scale of 0.0 to 1.0
78    pub fn charge_frac(&self) -> f32 {
79        if let StageSection::Charge = self.stage_section {
80            (self.timer.as_secs_f32() / self.static_data.charge_duration.as_secs_f32()).min(1.0)
81        } else {
82            0.0
83        }
84    }
85}
86
87impl CharacterBehavior for Data {
88    fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
89        let mut update = StateUpdate::from(data);
90
91        handle_orientation(data, &mut update, 1.0, None);
92        handle_move(data, &mut update, self.static_data.move_speed);
93        handle_jump(data, output_events, &mut update, 1.0);
94
95        match self.stage_section {
96            StageSection::Buildup => {
97                if self.timer < self.static_data.buildup_duration {
98                    // Build up
99                    if let CharacterState::Throw(c) = &mut update.character {
100                        c.timer = tick_attack_or_default(data, self.timer, None);
101                    }
102                } else {
103                    // Transitions to swing section of stage
104                    if let CharacterState::Throw(c) = &mut update.character {
105                        c.timer = Duration::default();
106                        c.stage_section = StageSection::Charge;
107                    }
108                }
109            },
110            StageSection::Charge => {
111                if !input_is_pressed(data, self.static_data.ability_info.input)
112                    && !self.exhausted
113                    && data
114                        .inventory
115                        .and_then(|inv| inv.equipped(self.static_data.equip_slot))
116                        .is_some_and(|item| item.item_hash() == self.static_data.item_hash)
117                {
118                    let charge_frac = self.charge_frac();
119
120                    let body_offsets = data
121                        .body
122                        .projectile_offsets(update.ori.look_vec(), data.scale.map_or(1.0, |s| s.0));
123                    let pos = Pos(data.pos.0 + body_offsets);
124
125                    let dir = match self.static_data.projectile_dir {
126                        ProjectileDir::LookDir => data.inputs.look_dir,
127                        ProjectileDir::Upwards(interpolation_factor) => {
128                            Dir::up().slerped_to(data.inputs.look_dir, interpolation_factor)
129                        },
130                    };
131
132                    let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
133                    let projectile = {
134                        let projectile = self.static_data.projectile.clone();
135                        if self.static_data.projectile.scaled.is_some() {
136                            projectile.handle_scaling(charge_frac)
137                        } else {
138                            projectile
139                        }
140                    };
141                    let projectile = projectile.create_projectile(
142                        Some(*data.uid),
143                        precision_mult,
144                        Some(self.static_data.ability_info),
145                    );
146
147                    // Removes the thrown item from the entity's inventory and creates a
148                    // projectile
149                    output_events.emit_server(ThrowEvent {
150                        entity: data.entity,
151                        pos,
152                        dir,
153                        projectile,
154                        light: self.static_data.projectile_light,
155                        speed: self.static_data.initial_projectile_speed
156                            + charge_frac * self.static_data.scaled_projectile_speed,
157                        object: None,
158                        equip_slot: self.static_data.equip_slot,
159                    });
160
161                    // Exhausts ability and transitions to action
162                    if let CharacterState::Throw(c) = &mut update.character {
163                        c.timer = Duration::default();
164                        c.stage_section = StageSection::Action;
165                        c.exhausted = true;
166                    }
167                } else if self.timer < self.static_data.charge_duration
168                    && input_is_pressed(data, self.static_data.ability_info.input)
169                {
170                    // Charges
171                    if let CharacterState::Throw(c) = &mut update.character {
172                        c.timer = tick_attack_or_default(data, self.timer, None);
173                    }
174
175                    // Consumes energy if there's enough left and input is held down
176                    update
177                        .energy
178                        .change_by(-self.static_data.energy_drain * data.dt.0);
179                } else if input_is_pressed(data, self.static_data.ability_info.input) {
180                    // Holds charge
181                    if let CharacterState::Throw(c) = &mut update.character {
182                        c.timer = tick_attack_or_default(data, self.timer, None);
183                    }
184
185                    // Consumes energy if there's enough left and RMB is held down
186                    update
187                        .energy
188                        .change_by(-self.static_data.energy_drain * data.dt.0 / 5.0);
189                }
190            },
191            StageSection::Action => {
192                if self.timer < self.static_data.throw_duration {
193                    // Throws
194                    if let CharacterState::Throw(c) = &mut update.character {
195                        c.timer = tick_attack_or_default(data, self.timer, None);
196                    }
197                } else {
198                    // Transition to recover
199                    if let CharacterState::Throw(c) = &mut update.character {
200                        c.timer = Duration::default();
201                        c.stage_section = StageSection::Recover;
202                    }
203                }
204            },
205            StageSection::Recover => {
206                if self.timer < self.static_data.recover_duration {
207                    // Recovers
208                    if let CharacterState::Throw(c) = &mut update.character {
209                        c.timer = tick_attack_or_default(
210                            data,
211                            self.timer,
212                            Some(data.stats.recovery_speed_modifier),
213                        );
214                    }
215                } else {
216                    // Done
217                    end_ability(data, &mut update);
218                }
219            },
220            _ => {
221                // If it somehow ends up in an incorrect stage section
222                end_ability(data, &mut update);
223            },
224        }
225
226        // At end of state logic so an interrupt isn't overwritten
227        handle_interrupts(data, &mut update, output_events);
228
229        update
230    }
231}