Skip to main content

veloren_common/states/
charged_ranged.rs

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