veloren_common/states/
basic_ranged.rs

1use crate::{
2    combat::{self, CombatEffect},
3    comp::{
4        Body, CharacterState, LightEmitter, Pos, ProjectileConstructor, StateUpdate,
5        ability::Amount,
6        character_state::OutputEvents,
7        object::Body::{GrenadeClay, LaserBeam, LaserBeamSmall},
8    },
9    event::{LocalEvent, ShootEvent},
10    outcome::Outcome,
11    states::{
12        behavior::{CharacterBehavior, JoinData},
13        utils::*,
14    },
15    util::Dir,
16};
17use rand::{Rng, thread_rng};
18use serde::{Deserialize, Serialize};
19use std::time::Duration;
20
21/// Separated out to condense update portions of character state
22#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
23pub struct StaticData {
24    /// How much buildup is required before the attack
25    pub buildup_duration: Duration,
26    /// How long the state has until exiting
27    pub recover_duration: Duration,
28    /// How much spread there is when more than 1 projectile is created
29    pub projectile_spread: f32,
30    /// Projectile variables
31    pub projectile: ProjectileConstructor,
32    pub projectile_body: Body,
33    pub projectile_light: Option<LightEmitter>,
34    pub projectile_speed: f32,
35    /// How many projectiles are simultaneously fired
36    pub num_projectiles: Amount,
37    /// What key is used to press ability
38    pub ability_info: AbilityInfo,
39    pub damage_effect: Option<CombatEffect>,
40    pub move_efficiency: f32,
41}
42
43#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
44pub struct Data {
45    /// Struct containing data that does not change over the course of the
46    /// character state
47    pub static_data: StaticData,
48    /// Timer for each stage
49    pub timer: Duration,
50    /// What section the character stage is in
51    pub stage_section: StageSection,
52    /// Whether the attack fired already
53    pub exhausted: bool,
54}
55
56impl CharacterBehavior for Data {
57    fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
58        let mut update = StateUpdate::from(data);
59
60        handle_orientation(data, &mut update, 1.0, None);
61        handle_move(data, &mut update, self.static_data.move_efficiency);
62        handle_jump(data, output_events, &mut update, 1.0);
63
64        match self.stage_section {
65            StageSection::Buildup => {
66                if self.timer < self.static_data.buildup_duration {
67                    // Build up
68                    update.character = CharacterState::BasicRanged(Data {
69                        timer: tick_attack_or_default(data, self.timer, None),
70                        ..*self
71                    });
72                    match self.static_data.projectile_body {
73                        Body::Object(LaserBeam) => {
74                            // Send local event used for frontend shenanigans
75                            output_events.emit_local(LocalEvent::CreateOutcome(
76                                Outcome::CyclopsCharge {
77                                    pos: data.pos.0
78                                        + *data.ori.look_dir() * (data.body.max_radius()),
79                                },
80                            ));
81                        },
82                        Body::Object(GrenadeClay) => {
83                            // Send local event used for frontend shenanigans
84                            output_events.emit_local(LocalEvent::CreateOutcome(
85                                Outcome::FuseCharge {
86                                    pos: data.pos.0
87                                        + *data.ori.look_dir() * (2.5 * data.body.max_radius()),
88                                },
89                            ));
90                        },
91                        Body::Object(LaserBeamSmall) => {
92                            output_events.emit_local(LocalEvent::CreateOutcome(
93                                Outcome::TerracottaStatueCharge {
94                                    pos: data.pos.0
95                                        + *data.ori.look_dir() * (data.body.max_radius()),
96                                },
97                            ));
98                        },
99                        _ => {},
100                    }
101                } else {
102                    // Transitions to recover section of stage
103                    update.character = CharacterState::BasicRanged(Data {
104                        timer: Duration::default(),
105                        stage_section: StageSection::Recover,
106                        ..*self
107                    });
108                }
109            },
110            StageSection::Recover => {
111                if !self.exhausted {
112                    // Fire
113                    let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
114                    let projectile = self.static_data.projectile.create_projectile(
115                        Some(*data.uid),
116                        precision_mult,
117                        self.static_data.damage_effect,
118                    );
119                    // Shoots all projectiles simultaneously
120                    let num_projectiles = self
121                        .static_data
122                        .num_projectiles
123                        .compute(data.heads.map_or(1, |heads| heads.amount() as u32));
124
125                    for i in 0..num_projectiles {
126                        // Gets offsets
127                        let body_offsets = data.body.projectile_offsets(
128                            update.ori.look_vec(),
129                            data.scale.map_or(1.0, |s| s.0),
130                        );
131                        let pos = Pos(data.pos.0 + body_offsets);
132                        // Adds a slight spread to the projectiles. First projectile has no spread,
133                        // and spread increases linearly with number of projectiles created.
134                        let dir = Dir::from_unnormalized(data.inputs.look_dir.map(|x| {
135                            let offset = (2.0 * thread_rng().gen::<f32>() - 1.0)
136                                * self.static_data.projectile_spread
137                                * i as f32;
138                            x + offset
139                        }))
140                        .unwrap_or(data.inputs.look_dir);
141                        // Tells server to create and shoot the projectile
142                        output_events.emit_server(ShootEvent {
143                            entity: data.entity,
144                            pos,
145                            dir,
146                            body: self.static_data.projectile_body,
147                            projectile: projectile.clone(),
148                            light: self.static_data.projectile_light,
149                            speed: self.static_data.projectile_speed,
150                            object: None,
151                        });
152                    }
153
154                    update.character = CharacterState::BasicRanged(Data {
155                        exhausted: true,
156                        ..*self
157                    });
158                } else if self.timer < self.static_data.recover_duration {
159                    // Recovers
160                    update.character = CharacterState::BasicRanged(Data {
161                        timer: tick_attack_or_default(
162                            data,
163                            self.timer,
164                            Some(data.stats.recovery_speed_modifier),
165                        ),
166                        ..*self
167                    });
168                } else {
169                    // Done
170                    if input_is_pressed(data, self.static_data.ability_info.input) {
171                        reset_state(self, data, output_events, &mut update);
172                    } else {
173                        end_ability(data, &mut update);
174                    }
175                }
176            },
177            _ => {
178                // If it somehow ends up in an incorrect stage section
179                end_ability(data, &mut update);
180            },
181        }
182
183        // At end of state logic so an interrupt isn't overwritten
184        handle_interrupts(data, &mut update, output_events);
185
186        update
187    }
188}
189
190fn reset_state(
191    data: &Data,
192    join: &JoinData,
193    output_events: &mut OutputEvents,
194    update: &mut StateUpdate,
195) {
196    handle_input(
197        join,
198        output_events,
199        update,
200        data.static_data.ability_info.input,
201    );
202}