Skip to main content

veloren_common/states/
shockwave.rs

1use crate::{
2    combat::{
3        self, Attack, AttackDamage, AttackEffect, CombatEffect, CombatRequirement, Damage,
4        DamageKind, GroupTarget, Knockback,
5    },
6    comp::{
7        CharacterState, StateUpdate, ability::Dodgeable, character_state::OutputEvents, shockwave,
8    },
9    event::{LocalEvent, ShockwaveEvent},
10    outcome::Outcome,
11    states::{
12        behavior::{CharacterBehavior, JoinData},
13        utils::*,
14    },
15};
16use serde::{Deserialize, Serialize};
17use std::time::Duration;
18
19/// Separated out to condense update portions of character state
20#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
21pub struct StaticData {
22    /// How long until state should deal damage
23    pub buildup_duration: Duration,
24    /// How long the state is swinging for
25    pub swing_duration: Duration,
26    /// How long the state has until exiting
27    pub recover_duration: Duration,
28    /// Base damage
29    pub damage: f32,
30    /// Base poise damage
31    pub poise_damage: f32,
32    /// Knockback
33    pub knockback: Knockback,
34    /// Angle of the shockwave
35    pub shockwave_angle: f32,
36    /// Vertical angle of the shockwave
37    pub shockwave_vertical_angle: f32,
38    /// Speed of the shockwave
39    pub shockwave_speed: f32,
40    /// How long the shockwave travels for
41    pub shockwave_duration: Duration,
42    /// If the shockwave can be dodged, and in what way
43    pub dodgeable: Dodgeable,
44    /// Movement speed efficiency
45    pub move_efficiency: f32,
46    /// What key is used to press ability
47    pub ability_info: AbilityInfo,
48    /// Adds an effect onto the main damage of the attack
49    pub damage_effect: Option<CombatEffect>,
50    /// What kind of damage the attack does
51    pub damage_kind: DamageKind,
52    /// Used to specify the shockwave to the frontend
53    pub specifier: shockwave::FrontendSpecifier,
54    /// Controls outcome emission
55    pub emit_outcome: bool,
56    /// How fast enemy can rotate
57    pub ori_rate: f32,
58    /// Timing of shockwave
59    pub timing: Timing,
60    pub minimum_combo: Option<u32>,
61    pub combo_on_use: u32,
62    pub combo_consumption: ComboConsumption,
63}
64
65#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
66pub struct Data {
67    /// Struct containing data that does not change over the course of the
68    /// character state
69    pub static_data: StaticData,
70    /// Timer for each stage
71    pub timer: Duration,
72    /// What section the character stage is in
73    pub stage_section: StageSection,
74}
75
76impl CharacterBehavior for Data {
77    fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
78        let mut update = StateUpdate::from(data);
79
80        handle_orientation(data, &mut update, self.static_data.ori_rate, None);
81        handle_move(data, &mut update, self.static_data.move_efficiency);
82
83        match self.stage_section {
84            StageSection::Buildup => {
85                if self.timer < self.static_data.buildup_duration {
86                    // Build up
87                    if let CharacterState::Shockwave(c) = &mut update.character {
88                        c.timer = tick_attack_or_default(data, self.timer, None);
89                    }
90                } else {
91                    // Attack
92                    if matches!(self.static_data.timing, Timing::PostBuildup) {
93                        self.attack(data, output_events);
94                    }
95
96                    // Transitions to swing
97                    if let CharacterState::Shockwave(c) = &mut update.character {
98                        c.timer = Duration::default();
99                        c.stage_section = StageSection::Action;
100                    }
101                }
102            },
103            StageSection::Action => {
104                if self.timer < self.static_data.swing_duration {
105                    // Swings
106                    if let CharacterState::Shockwave(c) = &mut update.character {
107                        c.timer = tick_attack_or_default(data, self.timer, None);
108                    }
109                    // Send local event used for frontend shenanigans
110                    if self.static_data.emit_outcome {
111                        match self.static_data.specifier {
112                            shockwave::FrontendSpecifier::IceSpikes => {
113                                output_events.emit_local(LocalEvent::CreateOutcome(
114                                    Outcome::FlashFreeze {
115                                        pos: data.pos.0
116                                            + *data.ori.look_dir() * (data.body.max_radius()),
117                                    },
118                                ));
119                            },
120                            shockwave::FrontendSpecifier::Ground => {
121                                output_events.emit_local(LocalEvent::CreateOutcome(
122                                    Outcome::GroundSlam {
123                                        pos: data.pos.0
124                                            + *data.ori.look_dir() * (data.body.max_radius()),
125                                    },
126                                ));
127                            },
128                            shockwave::FrontendSpecifier::Steam => {
129                                output_events.emit_local(LocalEvent::CreateOutcome(
130                                    Outcome::Steam {
131                                        pos: data.pos.0
132                                            + *data.ori.look_dir() * (data.body.max_radius()),
133                                    },
134                                ));
135                            },
136                            shockwave::FrontendSpecifier::Fire => {
137                                output_events.emit_local(LocalEvent::CreateOutcome(
138                                    Outcome::FireShockwave {
139                                        pos: data.pos.0
140                                            + *data.ori.look_dir() * (data.body.max_radius()),
141                                    },
142                                ));
143                            },
144                            shockwave::FrontendSpecifier::FireLow => {
145                                output_events.emit_local(LocalEvent::CreateOutcome(
146                                    Outcome::FireLowShockwave {
147                                        pos: data.pos.0
148                                            + *data.ori.look_dir() * (data.body.max_radius()),
149                                    },
150                                ));
151                            },
152                            _ => {
153                                output_events.emit_local(LocalEvent::CreateOutcome(
154                                    Outcome::Swoosh {
155                                        pos: data.pos.0
156                                            + *data.ori.look_dir() * (data.body.max_radius()),
157                                    },
158                                ));
159                            },
160                        }
161                    }
162                } else {
163                    // Attack
164                    if matches!(self.static_data.timing, Timing::PostAction) {
165                        self.attack(data, output_events);
166                    }
167
168                    // Transitions to recover
169                    if let CharacterState::Shockwave(c) = &mut update.character {
170                        c.timer = Duration::default();
171                        c.stage_section = StageSection::Recover;
172                    }
173                }
174            },
175            StageSection::Recover => {
176                if self.timer < self.static_data.recover_duration {
177                    // Recovers
178                    if let CharacterState::Shockwave(c) = &mut update.character {
179                        c.timer = tick_attack_or_default(data, self.timer, None);
180                    }
181                } else {
182                    // Done
183                    end_ability(data, &mut update);
184                }
185            },
186            _ => {
187                // If it somehow ends up in an incorrect stage section
188                end_ability(data, &mut update);
189            },
190        }
191
192        // At end of state logic so an interrupt isn't overwritten
193        handle_interrupts(data, &mut update, output_events);
194
195        update
196    }
197}
198
199impl Data {
200    fn attack(&self, data: &JoinData, output_events: &mut OutputEvents) {
201        if let Some(min_combo) = self.static_data.minimum_combo {
202            self.static_data
203                .combo_consumption
204                .consume(data, output_events, min_combo);
205        }
206
207        let poise = AttackEffect::new(
208            Some(GroupTarget::OutOfGroup),
209            CombatEffect::Poise(self.static_data.poise_damage),
210        )
211        .with_requirement(CombatRequirement::AnyDamage);
212        let knockback = AttackEffect::new(
213            Some(GroupTarget::OutOfGroup),
214            CombatEffect::Knockback(self.static_data.knockback),
215        )
216        .with_requirement(CombatRequirement::AnyDamage);
217        let mut damage = AttackDamage::new(
218            Damage {
219                kind: self.static_data.damage_kind,
220                value: self.static_data.damage,
221            },
222            Some(GroupTarget::OutOfGroup),
223            rand::random(),
224        );
225        if let Some(effect) = &self.static_data.damage_effect {
226            damage = damage.with_effect(effect.clone());
227        }
228        let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
229        let attack = Attack::new(Some(self.static_data.ability_info))
230            .with_damage(damage)
231            .with_precision(
232                precision_mult
233                    * self
234                        .static_data
235                        .ability_info
236                        .ability_meta
237                        .precision_power_mult
238                        .unwrap_or(1.0),
239            )
240            .with_effect(poise)
241            .with_effect(knockback)
242            .with_combo_increment();
243        let properties = shockwave::Properties {
244            angle: self.static_data.shockwave_angle,
245            vertical_angle: self.static_data.shockwave_vertical_angle,
246            speed: self.static_data.shockwave_speed,
247            duration: self.static_data.shockwave_duration,
248            attack,
249            dodgeable: self.static_data.dodgeable,
250            owner: Some(*data.uid),
251            specifier: self.static_data.specifier,
252        };
253        output_events.emit_server(ShockwaveEvent {
254            properties,
255            pos: *data.pos,
256            ori: *data.ori,
257        });
258    }
259}
260
261#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
262pub enum Timing {
263    PostBuildup,
264    PostAction,
265}