veloren_common/states/
explosion.rs

1use crate::{
2    Damage, DamageKind, Explosion, GroupTarget, Knockback, RadiusEffect,
3    combat::{Attack, AttackDamage, AttackEffect, CombatEffect, CombatRequirement},
4    comp::{
5        CharacterState, StateUpdate, ability::Dodgeable, character_state::OutputEvents,
6        item::Reagent,
7    },
8    event::ExplosionEvent,
9    explosion::{ColorPreset, TerrainReplacementPreset},
10    states::{
11        behavior::{CharacterBehavior, JoinData},
12        utils::*,
13    },
14};
15use serde::{Deserialize, Serialize};
16use std::time::Duration;
17use vek::Vec3;
18
19/// Separated out to condense update portions of character state
20#[derive(Copy, 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 casting the explosion for
25    pub action_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: f32,
32    /// Knockback
33    pub knockback: Knockback,
34    /// Range of the explosion
35    pub radius: f32,
36    /// Minimum falloff of explosion strength
37    pub min_falloff: f32,
38    /// If the explosion can be dodged, and in what way
39    #[serde(default)]
40    pub dodgeable: Dodgeable,
41    /// Power and color of terrain destruction
42    pub destroy_terrain: Option<(f32, ColorPreset)>,
43    /// Range and kind of terrain replacement
44    pub replace_terrain: Option<(f32, TerrainReplacementPreset)>,
45    /// Whether the explosion is created at eye height instead of the entity's
46    /// pos
47    #[serde(default)]
48    pub eye_height: bool,
49    /// Controls visual effects
50    pub reagent: Option<Reagent>,
51    /// Adjusts move speed during the attack per stage
52    #[serde(default)]
53    pub movement_modifier: MovementModifier,
54    /// Adjusts turning rate during the attack per stage
55    #[serde(default)]
56    pub ori_modifier: OrientationModifier,
57    /// What key is used to press ability
58    pub ability_info: AbilityInfo,
59}
60
61#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
62pub struct Data {
63    /// Struct containing data that does not change over the course of the
64    /// character state
65    pub static_data: StaticData,
66    /// Timer for each stage
67    pub timer: Duration,
68    /// What section the character stage is in
69    pub stage_section: StageSection,
70    /// Adjusts move speed during the attack
71    pub movement_modifier: Option<f32>,
72    /// How fast the entity should turn
73    pub ori_modifier: Option<f32>,
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.ori_modifier.unwrap_or(1.0), None);
81        handle_move(data, &mut update, self.movement_modifier.unwrap_or(0.7));
82        handle_jump(data, output_events, &mut update, 1.0);
83
84        match self.stage_section {
85            StageSection::Buildup => {
86                if self.timer < self.static_data.buildup_duration {
87                    // Build up
88                    update.character = CharacterState::Explosion(Data {
89                        timer: tick_attack_or_default(data, self.timer, None),
90                        ..*self
91                    });
92                } else {
93                    // Transitions to swing section of stage
94                    update.character = CharacterState::Explosion(Data {
95                        timer: Duration::default(),
96                        stage_section: StageSection::Action,
97                        movement_modifier: self.static_data.movement_modifier.swing,
98                        ori_modifier: self.static_data.ori_modifier.swing,
99                        ..*self
100                    });
101                }
102            },
103            StageSection::Action => {
104                if self.timer < self.static_data.action_duration {
105                    // Swings
106                    update.character = CharacterState::Explosion(Data {
107                        timer: tick_attack_or_default(data, self.timer, None),
108                        ..*self
109                    });
110                } else {
111                    // Create an explosion after action before transitioning to recover
112                    let pos = if self.static_data.eye_height {
113                        data.pos.0
114                            + Vec3::unit_z() * data.body.eye_height(data.scale.map_or(1.0, |s| s.0))
115                    } else {
116                        data.pos.0
117                    };
118
119                    let mut effects = vec![RadiusEffect::Attack {
120                        attack: Attack::new(Some(self.static_data.ability_info))
121                            .with_damage(AttackDamage::new(
122                                Damage {
123                                    kind: DamageKind::Crushing,
124                                    value: self.static_data.damage,
125                                },
126                                Some(GroupTarget::OutOfGroup),
127                                rand::random(),
128                            ))
129                            .with_effect(
130                                AttackEffect::new(
131                                    Some(GroupTarget::OutOfGroup),
132                                    CombatEffect::Poise(self.static_data.poise),
133                                )
134                                .with_requirement(CombatRequirement::AnyDamage),
135                            )
136                            .with_effect(
137                                AttackEffect::new(
138                                    Some(GroupTarget::OutOfGroup),
139                                    CombatEffect::Knockback(self.static_data.knockback),
140                                )
141                                .with_requirement(CombatRequirement::AnyDamage),
142                            ),
143                        dodgeable: self.static_data.dodgeable,
144                    }];
145
146                    if let Some((power, color)) = self.static_data.destroy_terrain {
147                        effects.push(RadiusEffect::TerrainDestruction(power, color.to_rgb()));
148                    }
149
150                    if let Some((radius, replacement_preset)) = self.static_data.replace_terrain {
151                        effects.push(RadiusEffect::ReplaceTerrain(radius, replacement_preset));
152                    }
153
154                    output_events.emit_server(ExplosionEvent {
155                        pos,
156                        explosion: Explosion {
157                            effects,
158                            radius: self.static_data.radius,
159                            reagent: self.static_data.reagent,
160                            min_falloff: self.static_data.min_falloff,
161                        },
162                        owner: Some(*data.uid),
163                    });
164
165                    // Transitions to recover section of stage
166                    update.character = CharacterState::Explosion(Data {
167                        timer: Duration::default(),
168                        stage_section: StageSection::Recover,
169                        movement_modifier: self.static_data.movement_modifier.recover,
170                        ori_modifier: self.static_data.ori_modifier.recover,
171                        ..*self
172                    });
173                }
174            },
175            StageSection::Recover => {
176                if self.timer < self.static_data.recover_duration {
177                    // Recovery
178                    update.character = CharacterState::Explosion(Data {
179                        timer: tick_attack_or_default(
180                            data,
181                            self.timer,
182                            Some(data.stats.recovery_speed_modifier),
183                        ),
184                        movement_modifier: self.static_data.movement_modifier.recover,
185                        ori_modifier: self.static_data.ori_modifier.recover,
186                        ..*self
187                    });
188                } else {
189                    // Done
190                    if input_is_pressed(data, self.static_data.ability_info.input) {
191                        reset_state(self, data, output_events, &mut update);
192                    } else {
193                        end_ability(data, &mut update);
194                    }
195                }
196            },
197            _ => {
198                // If it somehow ends up in an incorrect stage section
199                end_ability(data, &mut update);
200            },
201        }
202
203        // At end of state logic so an interrupt isn't overwritten
204        handle_interrupts(data, &mut update, output_events);
205
206        update
207    }
208}
209
210fn reset_state(
211    data: &Data,
212    join: &JoinData,
213    output_events: &mut OutputEvents,
214    update: &mut StateUpdate,
215) {
216    handle_input(
217        join,
218        output_events,
219        update,
220        data.static_data.ability_info.input,
221    );
222}