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) && !self.exhausted {
95                    let charge_frac = self.charge_frac();
96                    // Fire
97                    let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
98                    // Gets offsets
99                    let body_offsets = data
100                        .body
101                        .projectile_offsets(update.ori.look_vec(), data.scale.map_or(1.0, |s| s.0));
102                    let pos = Pos(data.pos.0 + body_offsets);
103                    let projectile = self
104                        .static_data
105                        .projectile
106                        .clone()
107                        .handle_scaling(charge_frac)
108                        .create_projectile(
109                            Some(*data.uid),
110                            precision_mult,
111                            Some(self.static_data.ability_info),
112                        );
113
114                    let num_projectiles = self
115                        .static_data
116                        .num_projectiles
117                        .compute(data.heads.map_or(1, |heads| heads.amount() as u32));
118
119                    let mut rng = rng();
120                    let dirs = if let Some(spread) = self.static_data.projectile_spread {
121                        Either::Left(spread.compute_directions(
122                            data.inputs.look_dir,
123                            *data.ori,
124                            num_projectiles,
125                            &mut rng,
126                        ))
127                    } else {
128                        Either::Right((0..num_projectiles).map(|_| data.inputs.look_dir))
129                    };
130
131                    for dir in dirs {
132                        output_events.emit_server(ShootEvent {
133                            entity: Some(data.entity),
134                            source_vel: Some(*data.vel),
135                            pos,
136                            dir,
137                            body: self.static_data.projectile_body,
138                            projectile: projectile.clone(),
139                            light: self.static_data.projectile_light,
140                            speed: self.static_data.initial_projectile_speed
141                                + charge_frac * self.static_data.scaled_projectile_speed,
142                            object: None,
143                            marker: self.static_data.marker,
144                        });
145                    }
146
147                    if let CharacterState::ChargedRanged(c) = &mut update.character {
148                        c.timer = Duration::default();
149                        c.stage_section = StageSection::Recover;
150                        c.exhausted = true;
151                    }
152                } else if self.timer < self.static_data.charge_duration
153                    && input_is_pressed(data, self.static_data.ability_info.input)
154                {
155                    // Charges
156                    if let CharacterState::ChargedRanged(c) = &mut update.character {
157                        c.timer = tick_attack_or_default(data, self.timer, None);
158                    }
159
160                    // Consumes energy if there's enough left and input is held down
161                    update
162                        .energy
163                        .change_by(-self.static_data.energy_drain * data.dt.0);
164                } else if input_is_pressed(data, self.static_data.ability_info.input) {
165                    // Holds charge
166                    if let CharacterState::ChargedRanged(c) = &mut update.character {
167                        c.timer = tick_attack_or_default(data, self.timer, None);
168                    }
169
170                    // Consumes energy if there's enough left and RMB is held down
171                    update
172                        .energy
173                        .change_by(-self.static_data.idle_drain * data.dt.0);
174                }
175            },
176            StageSection::Recover => {
177                if self.timer < self.static_data.recover_duration {
178                    // Recovers
179                    if let CharacterState::ChargedRanged(c) = &mut update.character {
180                        c.timer = tick_attack_or_default(
181                            data,
182                            self.timer,
183                            Some(data.stats.recovery_speed_modifier),
184                        );
185                    }
186                } else {
187                    // Done
188                    end_ability(data, &mut update);
189                }
190            },
191            _ => {
192                // If it somehow ends up in an incorrect stage section
193                end_ability(data, &mut update);
194            },
195        }
196
197        // At end of state logic so an interrupt isn't overwritten
198        handle_interrupts(data, &mut update, output_events);
199
200        update
201    }
202}