veloren_common/states/
leap_explosion_shockwave.rs

1use crate::{
2    Explosion, RadiusEffect,
3    combat::{
4        self, Attack, AttackDamage, AttackEffect, CombatEffect, CombatRequirement, Damage,
5        DamageKind, 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(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(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                    if let CharacterState::LeapExplosionShockwave(c) = &mut update.character {
119                        c.timer = tick_attack_or_default(data, self.timer, None);
120                    }
121                } else {
122                    // Transitions to leap portion of state after buildup delay
123                    if let CharacterState::LeapExplosionShockwave(c) = &mut update.character {
124                        c.timer = Duration::default();
125                        c.stage_section = StageSection::Movement;
126                    }
127                }
128            },
129            StageSection::Movement => {
130                if self.timer < self.static_data.movement_duration {
131                    // Apply jumping force
132                    let progress = 1.0
133                        - self.timer.as_secs_f32()
134                            / self.static_data.movement_duration.as_secs_f32();
135                    handle_forced_movement(data, &mut update, ForcedMovement::Leap {
136                        vertical: self.static_data.vertical_leap_strength,
137                        forward: self.static_data.forward_leap_strength,
138                        progress,
139                        direction: MovementDirection::Look,
140                    });
141
142                    // Increment duration
143                    // If we were to set a timeout for state, this would be
144                    // outside if block and have else check for > movement
145                    // duration * some multiplier
146                    if let CharacterState::LeapExplosionShockwave(c) = &mut update.character {
147                        c.timer = tick_attack_or_default(data, self.timer, None);
148                    }
149                } else if data.physics.on_ground.is_some() | data.physics.in_liquid().is_some() {
150                    // Transitions to swing portion of state upon hitting ground
151                    if let CharacterState::LeapExplosionShockwave(c) = &mut update.character {
152                        c.timer = Duration::default();
153                        c.stage_section = StageSection::Action;
154                    }
155                }
156            },
157            StageSection::Action => {
158                if self.timer < self.static_data.swing_duration {
159                    // Swings
160                    if let CharacterState::LeapExplosionShockwave(c) = &mut update.character {
161                        c.timer = tick_attack_or_default(data, self.timer, None);
162                    }
163                } else {
164                    // Explosion
165                    let explosion_pos = if self.static_data.eye_height {
166                        data.pos.0
167                            + Vec3::unit_z() * data.body.eye_height(data.scale.map_or(1.0, |s| s.0))
168                    } else {
169                        data.pos.0
170                    };
171
172                    let mut effects = vec![RadiusEffect::Attack {
173                        attack: Attack::new(Some(self.static_data.ability_info))
174                            .with_damage(AttackDamage::new(
175                                Damage {
176                                    kind: DamageKind::Crushing,
177                                    value: self.static_data.explosion_damage,
178                                },
179                                Some(GroupTarget::OutOfGroup),
180                                rand::random(),
181                            ))
182                            .with_effect(
183                                AttackEffect::new(
184                                    Some(GroupTarget::OutOfGroup),
185                                    CombatEffect::Poise(self.static_data.explosion_poise),
186                                )
187                                .with_requirement(CombatRequirement::AnyDamage),
188                            )
189                            .with_effect(
190                                AttackEffect::new(
191                                    Some(GroupTarget::OutOfGroup),
192                                    CombatEffect::Knockback(self.static_data.explosion_knockback),
193                                )
194                                .with_requirement(CombatRequirement::AnyDamage),
195                            ),
196                        dodgeable: self.static_data.explosion_dodgeable,
197                    }];
198
199                    if let Some((power, color)) = self.static_data.destroy_terrain {
200                        effects.push(RadiusEffect::TerrainDestruction(power, color.to_rgb()));
201                    }
202
203                    if let Some((radius, replacement_preset)) = self.static_data.replace_terrain {
204                        effects.push(RadiusEffect::ReplaceTerrain(radius, replacement_preset));
205                    }
206
207                    output_events.emit_server(ExplosionEvent {
208                        pos: explosion_pos,
209                        explosion: Explosion {
210                            effects,
211                            radius: self.static_data.explosion_radius,
212                            reagent: self.static_data.reagent,
213                            min_falloff: self.static_data.min_falloff,
214                        },
215                        owner: Some(*data.uid),
216                    });
217
218                    // Shockwave
219                    let shockwave_poise = AttackEffect::new(
220                        Some(GroupTarget::OutOfGroup),
221                        CombatEffect::Poise(self.static_data.shockwave_poise),
222                    )
223                    .with_requirement(CombatRequirement::AnyDamage);
224                    let shockwave_knockback = AttackEffect::new(
225                        Some(GroupTarget::OutOfGroup),
226                        CombatEffect::Knockback(self.static_data.shockwave_knockback),
227                    )
228                    .with_requirement(CombatRequirement::AnyDamage);
229                    let mut shockwave_damage = AttackDamage::new(
230                        Damage {
231                            kind: self.static_data.shockwave_damage_kind,
232                            value: self.static_data.shockwave_damage,
233                        },
234                        Some(GroupTarget::OutOfGroup),
235                        rand::random(),
236                    );
237                    if let Some(effect) = &self.static_data.shockwave_damage_effect {
238                        shockwave_damage = shockwave_damage.with_effect(effect.clone());
239                    }
240                    let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
241                    let shockwave_attack = Attack::new(Some(self.static_data.ability_info))
242                        .with_damage(shockwave_damage)
243                        .with_precision(
244                            precision_mult
245                                * self
246                                    .static_data
247                                    .ability_info
248                                    .ability_meta
249                                    .precision_power_mult
250                                    .unwrap_or(1.0),
251                        )
252                        .with_effect(shockwave_poise)
253                        .with_effect(shockwave_knockback)
254                        .with_combo_increment();
255                    let properties = shockwave::Properties {
256                        angle: self.static_data.shockwave_angle,
257                        vertical_angle: self.static_data.shockwave_vertical_angle,
258                        speed: self.static_data.shockwave_speed,
259                        duration: self.static_data.shockwave_duration,
260                        attack: shockwave_attack,
261                        dodgeable: self.static_data.shockwave_dodgeable,
262                        owner: Some(*data.uid),
263                        specifier: self.static_data.shockwave_specifier,
264                    };
265                    output_events.emit_server(ShockwaveEvent {
266                        properties,
267                        pos: *data.pos,
268                        ori: *data.ori,
269                    });
270
271                    // Transitions to recover
272                    if let CharacterState::LeapExplosionShockwave(c) = &mut update.character {
273                        c.timer = Duration::default();
274                        c.stage_section = StageSection::Recover;
275                    }
276                }
277            },
278            StageSection::Recover => {
279                if self.timer < self.static_data.recover_duration {
280                    // Recovers
281                    if let CharacterState::LeapExplosionShockwave(c) = &mut update.character {
282                        c.timer = tick_attack_or_default(
283                            data,
284                            self.timer,
285                            Some(data.stats.recovery_speed_modifier),
286                        );
287                    }
288                } else {
289                    // Done
290                    end_ability(data, &mut update);
291                }
292            },
293            _ => {
294                // If it somehow ends up in an incorrect stage section
295                end_ability(data, &mut update);
296            },
297        }
298
299        // At end of state logic so an interrupt isn't overwritten
300        handle_interrupts(data, &mut update, output_events);
301
302        update
303    }
304}