veloren_common/states/
leap_shockwave.rs

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