veloren_common/states/
charged_ranged.rs

1use crate::{
2    combat::{self, CombatEffect},
3    comp::{
4        Body, CharacterState, LightEmitter, Pos, StateUpdate, character_state::OutputEvents,
5        projectile::ProjectileConstructor,
6    },
7    event::ShootEvent,
8    states::{
9        behavior::{CharacterBehavior, JoinData},
10        utils::*,
11    },
12};
13use serde::{Deserialize, Serialize};
14use std::time::Duration;
15
16/// Separated out to condense update portions of character state
17#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
18pub struct StaticData {
19    /// How long the weapon needs to be prepared for
20    pub buildup_duration: Duration,
21    /// How long it takes to charge the weapon to max damage and knockback
22    pub charge_duration: Duration,
23    /// How long the state has until exiting
24    pub recover_duration: Duration,
25    /// How much energy is drained per second when charging
26    pub energy_drain: f32,
27    /// Projectile information
28    pub projectile: ProjectileConstructor,
29    pub projectile_body: Body,
30    pub projectile_light: Option<LightEmitter>,
31    pub initial_projectile_speed: f32,
32    pub scaled_projectile_speed: f32,
33    /// Move speed efficiency
34    pub move_speed: f32,
35    /// What key is used to press ability
36    pub ability_info: AbilityInfo,
37    /// Adds an effect onto the main damage of the attack
38    pub damage_effect: Option<CombatEffect>,
39}
40
41#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
42pub struct Data {
43    /// Struct containing data that does not change over the course of the
44    /// character state
45    pub static_data: StaticData,
46    /// Timer for each stage
47    pub timer: Duration,
48    /// What section the character stage is in
49    pub stage_section: StageSection,
50    /// Whether the attack fired already
51    pub exhausted: bool,
52}
53
54impl Data {
55    /// How complete the charge is, on a scale of 0.0 to 1.0
56    pub fn charge_frac(&self) -> f32 {
57        if let StageSection::Charge = self.stage_section {
58            (self.timer.as_secs_f32() / self.static_data.charge_duration.as_secs_f32()).min(1.0)
59        } else {
60            0.0
61        }
62    }
63}
64
65impl CharacterBehavior for Data {
66    fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
67        let mut update = StateUpdate::from(data);
68
69        handle_orientation(data, &mut update, 1.0, None);
70        handle_move(data, &mut update, self.static_data.move_speed);
71        handle_jump(data, output_events, &mut update, 1.0);
72
73        match self.stage_section {
74            StageSection::Buildup => {
75                if self.timer < self.static_data.buildup_duration {
76                    // Build up
77                    update.character = CharacterState::ChargedRanged(Data {
78                        timer: tick_attack_or_default(data, self.timer, None),
79                        ..*self
80                    });
81                } else {
82                    // Transitions to swing section of stage
83                    update.character = CharacterState::ChargedRanged(Data {
84                        timer: Duration::default(),
85                        stage_section: StageSection::Charge,
86                        ..*self
87                    });
88                }
89            },
90            StageSection::Charge => {
91                if !input_is_pressed(data, self.static_data.ability_info.input) && !self.exhausted {
92                    let charge_frac = self.charge_frac();
93                    // Fire
94                    let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
95                    // Gets offsets
96                    let body_offsets = data
97                        .body
98                        .projectile_offsets(update.ori.look_vec(), data.scale.map_or(1.0, |s| s.0));
99                    let pos = Pos(data.pos.0 + body_offsets);
100                    let projectile = self
101                        .static_data
102                        .projectile
103                        .handle_scaling(charge_frac)
104                        .create_projectile(
105                            Some(*data.uid),
106                            precision_mult,
107                            self.static_data.damage_effect,
108                        );
109                    output_events.emit_server(ShootEvent {
110                        entity: data.entity,
111                        pos,
112                        dir: data.inputs.look_dir,
113                        body: self.static_data.projectile_body,
114                        projectile,
115                        light: self.static_data.projectile_light,
116                        speed: self.static_data.initial_projectile_speed
117                            + charge_frac * self.static_data.scaled_projectile_speed,
118                        object: None,
119                    });
120
121                    update.character = CharacterState::ChargedRanged(Data {
122                        timer: Duration::default(),
123                        stage_section: StageSection::Recover,
124                        exhausted: true,
125                        ..*self
126                    });
127                } else if self.timer < self.static_data.charge_duration
128                    && input_is_pressed(data, self.static_data.ability_info.input)
129                {
130                    // Charges
131                    update.character = CharacterState::ChargedRanged(Data {
132                        timer: tick_attack_or_default(data, self.timer, None),
133                        ..*self
134                    });
135
136                    // Consumes energy if there's enough left and input is held down
137                    update
138                        .energy
139                        .change_by(-self.static_data.energy_drain * data.dt.0);
140                } else if input_is_pressed(data, self.static_data.ability_info.input) {
141                    // Holds charge
142                    update.character = CharacterState::ChargedRanged(Data {
143                        timer: tick_attack_or_default(data, self.timer, None),
144                        ..*self
145                    });
146
147                    // Consumes energy if there's enough left and RMB is held down
148                    update
149                        .energy
150                        .change_by(-self.static_data.energy_drain * data.dt.0 / 5.0);
151                }
152            },
153            StageSection::Recover => {
154                if self.timer < self.static_data.recover_duration {
155                    // Recovers
156                    update.character = CharacterState::ChargedRanged(Data {
157                        timer: tick_attack_or_default(
158                            data,
159                            self.timer,
160                            Some(data.stats.recovery_speed_modifier),
161                        ),
162                        ..*self
163                    });
164                } else {
165                    // Done
166                    end_ability(data, &mut update);
167                }
168            },
169            _ => {
170                // If it somehow ends up in an incorrect stage section
171                end_ability(data, &mut update);
172            },
173        }
174
175        // At end of state logic so an interrupt isn't overwritten
176        handle_interrupts(data, &mut update, output_events);
177
178        update
179    }
180}