veloren_common/states/
leap_explosion_shockwave.rs

1use crate::{
2    Explosion, RadiusEffect,
3    combat::{
4        self, Attack, AttackDamage, AttackEffect, CombatEffect, CombatRequirement, Damage,
5        DamageKind, DamageSource, GroupTarget, Knockback,
6    },
7    comp::{
8        CharacterState, StateUpdate, ability::Dodgeable, character_state::OutputEvents,
9        item::Reagent, shockwave,
10    },
11    event::{ExplosionEvent, ShockwaveEvent},
12    explosion::{ColorPreset, TerrainReplacementPreset},
13    states::{
14        behavior::{CharacterBehavior, JoinData},
15        utils::{StageSection, *},
16    },
17};
18use serde::{Deserialize, Serialize};
19use std::time::Duration;
20use vek::Vec3;
21
22/// Separated out to condense update portions of character state
23#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
24pub struct StaticData {
25    /// How long the state is moving
26    pub movement_duration: Duration,
27    /// How long until state should deal damage
28    pub buildup_duration: Duration,
29    /// How long the state is swinging for
30    pub swing_duration: Duration,
31    /// How long the state has until exiting
32    pub recover_duration: Duration,
33    /// Affects how far forward the player leaps
34    pub forward_leap_strength: f32,
35    /// Affects how high the player leaps
36    pub vertical_leap_strength: f32,
37
38    /// Base explosion damage
39    pub explosion_damage: f32,
40    /// Base explosion poise damage
41    pub explosion_poise: f32,
42    /// Explosion knockback
43    pub explosion_knockback: Knockback,
44    /// Range of the explosion
45    pub explosion_radius: f32,
46    /// Minimum falloff of explosion strength
47    pub min_falloff: f32,
48    /// If the explosion can be dodged, and in what way
49    #[serde(default)]
50    pub explosion_dodgeable: Dodgeable,
51    /// Power and color of terrain destruction
52    pub destroy_terrain: Option<(f32, ColorPreset)>,
53    /// Range and kind of terrain replacement
54    pub replace_terrain: Option<(f32, TerrainReplacementPreset)>,
55    /// Whether the explosion is created at eye height instead of the entity's
56    /// pos
57    #[serde(default)]
58    pub eye_height: bool,
59    /// Controls visual effects
60    pub reagent: Option<Reagent>,
61    /// Adjusts move speed during the attack per stage
62
63    /// Base shockwave damage
64    pub shockwave_damage: f32,
65    /// Base shockwave poise damage
66    pub shockwave_poise: f32,
67    /// Shockwave knockback
68    pub shockwave_knockback: Knockback,
69    /// Angle of the shockwave
70    pub shockwave_angle: f32,
71    /// Vertical angle of the shockwave
72    pub shockwave_vertical_angle: f32,
73    /// Speed of the shockwave
74    pub shockwave_speed: f32,
75    /// How long the shockwave travels for
76    pub shockwave_duration: Duration,
77    /// If the shockwave can be dodged, and in what way
78    pub shockwave_dodgeable: Dodgeable,
79    /// Adds an effect onto the main damage of the shockwave
80    pub shockwave_damage_effect: Option<CombatEffect>,
81    /// What kind of damage the shockwave does
82    pub shockwave_damage_kind: DamageKind,
83    /// Used to specify the shockwave to the frontend
84    pub shockwave_specifier: shockwave::FrontendSpecifier,
85
86    /// Movement speed efficiency
87    pub move_efficiency: f32,
88    /// What key is used to press ability
89    pub ability_info: AbilityInfo,
90}
91
92#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
93pub struct Data {
94    /// Struct containing data that does not change over the course of the
95    /// character state
96    pub static_data: StaticData,
97    /// Timer for each stage
98    pub timer: Duration,
99    /// What section the character stage is in
100    pub stage_section: StageSection,
101    /// Whether the attack can deal more damage
102    pub exhausted: bool,
103}
104
105impl CharacterBehavior for Data {
106    fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
107        let mut update = StateUpdate::from(data);
108
109        handle_orientation(data, &mut update, 1.0, None);
110        handle_move(data, &mut update, 0.3);
111        handle_jump(data, output_events, &mut update, 1.0);
112
113        match self.stage_section {
114            // Delay before leaping into the air
115            StageSection::Buildup => {
116                // Wait for `buildup_duration` to expire
117                if self.timer < self.static_data.buildup_duration {
118                    update.character = CharacterState::LeapExplosionShockwave(Data {
119                        timer: tick_attack_or_default(data, self.timer, None),
120                        ..*self
121                    });
122                } else {
123                    // Transitions to leap portion of state after buildup delay
124                    update.character = CharacterState::LeapExplosionShockwave(Data {
125                        timer: Duration::default(),
126                        stage_section: StageSection::Movement,
127                        ..*self
128                    });
129                }
130            },
131            StageSection::Movement => {
132                if self.timer < self.static_data.movement_duration {
133                    // Apply jumping force
134                    let progress = 1.0
135                        - self.timer.as_secs_f32()
136                            / self.static_data.movement_duration.as_secs_f32();
137                    handle_forced_movement(data, &mut update, ForcedMovement::Leap {
138                        vertical: self.static_data.vertical_leap_strength,
139                        forward: self.static_data.forward_leap_strength,
140                        progress,
141                        direction: MovementDirection::Look,
142                    });
143
144                    // Increment duration
145                    // If we were to set a timeout for state, this would be
146                    // outside if block and have else check for > movement
147                    // duration * some multiplier
148                    update.character = CharacterState::LeapExplosionShockwave(Data {
149                        timer: tick_attack_or_default(data, self.timer, None),
150                        ..*self
151                    });
152                } else if data.physics.on_ground.is_some() | data.physics.in_liquid().is_some() {
153                    // Transitions to swing portion of state upon hitting ground
154                    update.character = CharacterState::LeapExplosionShockwave(Data {
155                        timer: Duration::default(),
156                        stage_section: StageSection::Action,
157                        ..*self
158                    });
159                }
160            },
161            StageSection::Action => {
162                if self.timer < self.static_data.swing_duration {
163                    // Swings
164                    update.character = CharacterState::LeapExplosionShockwave(Data {
165                        timer: tick_attack_or_default(data, self.timer, None),
166                        ..*self
167                    });
168                } else {
169                    // Explosion
170                    let explosion_pos = if self.static_data.eye_height {
171                        data.pos.0
172                            + Vec3::unit_z() * data.body.eye_height(data.scale.map_or(1.0, |s| s.0))
173                    } else {
174                        data.pos.0
175                    };
176
177                    let mut effects = vec![RadiusEffect::Attack {
178                        attack: Attack::default()
179                            .with_damage(AttackDamage::new(
180                                Damage {
181                                    source: DamageSource::Explosion,
182                                    kind: DamageKind::Crushing,
183                                    value: self.static_data.explosion_damage,
184                                },
185                                Some(GroupTarget::OutOfGroup),
186                                rand::random(),
187                            ))
188                            .with_effect(
189                                AttackEffect::new(
190                                    Some(GroupTarget::OutOfGroup),
191                                    CombatEffect::Poise(self.static_data.explosion_poise),
192                                )
193                                .with_requirement(CombatRequirement::AnyDamage),
194                            )
195                            .with_effect(
196                                AttackEffect::new(
197                                    Some(GroupTarget::OutOfGroup),
198                                    CombatEffect::Knockback(self.static_data.explosion_knockback),
199                                )
200                                .with_requirement(CombatRequirement::AnyDamage),
201                            ),
202                        dodgeable: self.static_data.explosion_dodgeable,
203                    }];
204
205                    if let Some((power, color)) = self.static_data.destroy_terrain {
206                        effects.push(RadiusEffect::TerrainDestruction(power, color.to_rgb()));
207                    }
208
209                    if let Some((radius, replacement_preset)) = self.static_data.replace_terrain {
210                        effects.push(RadiusEffect::ReplaceTerrain(radius, replacement_preset));
211                    }
212
213                    output_events.emit_server(ExplosionEvent {
214                        pos: explosion_pos,
215                        explosion: Explosion {
216                            effects,
217                            radius: self.static_data.explosion_radius,
218                            reagent: self.static_data.reagent,
219                            min_falloff: self.static_data.min_falloff,
220                        },
221                        owner: Some(*data.uid),
222                    });
223
224                    // Shockwave
225                    let shockwave_poise = AttackEffect::new(
226                        Some(GroupTarget::OutOfGroup),
227                        CombatEffect::Poise(self.static_data.shockwave_poise),
228                    )
229                    .with_requirement(CombatRequirement::AnyDamage);
230                    let shockwave_knockback = AttackEffect::new(
231                        Some(GroupTarget::OutOfGroup),
232                        CombatEffect::Knockback(self.static_data.shockwave_knockback),
233                    )
234                    .with_requirement(CombatRequirement::AnyDamage);
235                    let mut shockwave_damage = AttackDamage::new(
236                        Damage {
237                            source: DamageSource::Shockwave,
238                            kind: self.static_data.shockwave_damage_kind,
239                            value: self.static_data.shockwave_damage,
240                        },
241                        Some(GroupTarget::OutOfGroup),
242                        rand::random(),
243                    );
244                    if let Some(effect) = self.static_data.shockwave_damage_effect {
245                        shockwave_damage = shockwave_damage.with_effect(effect);
246                    }
247                    let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
248                    let shockwave_attack = Attack::default()
249                        .with_damage(shockwave_damage)
250                        .with_precision(precision_mult)
251                        .with_effect(shockwave_poise)
252                        .with_effect(shockwave_knockback)
253                        .with_combo_increment();
254                    let properties = shockwave::Properties {
255                        angle: self.static_data.shockwave_angle,
256                        vertical_angle: self.static_data.shockwave_vertical_angle,
257                        speed: self.static_data.shockwave_speed,
258                        duration: self.static_data.shockwave_duration,
259                        attack: shockwave_attack,
260                        dodgeable: self.static_data.shockwave_dodgeable,
261                        owner: Some(*data.uid),
262                        specifier: self.static_data.shockwave_specifier,
263                    };
264                    output_events.emit_server(ShockwaveEvent {
265                        properties,
266                        pos: *data.pos,
267                        ori: *data.ori,
268                    });
269
270                    // Transitions to recover
271                    update.character = CharacterState::LeapExplosionShockwave(Data {
272                        timer: Duration::default(),
273                        stage_section: StageSection::Recover,
274                        ..*self
275                    });
276                }
277            },
278            StageSection::Recover => {
279                if self.timer < self.static_data.recover_duration {
280                    // Recovers
281                    update.character = CharacterState::LeapExplosionShockwave(Data {
282                        timer: tick_attack_or_default(
283                            data,
284                            self.timer,
285                            Some(data.stats.recovery_speed_modifier),
286                        ),
287                        ..*self
288                    });
289                } else {
290                    // Done
291                    end_ability(data, &mut update);
292                }
293            },
294            _ => {
295                // If it somehow ends up in an incorrect stage section
296                end_ability(data, &mut update);
297            },
298        }
299
300        // At end of state logic so an interrupt isn't overwritten
301        handle_interrupts(data, &mut update, output_events);
302
303        update
304    }
305}