Skip to main content

veloren_common/states/
rapid_melee.rs

1use crate::{
2    Explosion, RadiusEffect,
3    combat::{self, Attack, AttackDamage, Damage, DamageKind::Crushing, GroupTarget},
4    comp::{
5        CharacterState, MeleeConstructor, StateUpdate, ability::Dodgeable,
6        character_state::OutputEvents, item::Reagent,
7    },
8    event::{ComboChangeEvent, ExplosionEvent},
9    states::{
10        behavior::{CharacterBehavior, JoinData},
11        utils::*,
12    },
13};
14use serde::{Deserialize, Serialize};
15use std::time::Duration;
16use vek::Vec3;
17
18/// Separated out to condense update portions of character state
19#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
20pub struct StaticData {
21    /// How long until the state attacks
22    pub buildup_duration: Duration,
23    /// How long the state is in the swing duration
24    pub swing_duration: Duration,
25    /// How long until state ends
26    pub recover_duration: Duration,
27    /// Used to construct the Melee attack
28    pub melee_constructor: MeleeConstructor,
29    /// Energy cost per attack
30    pub energy_cost: f32,
31    /// Maximum number of consecutive strikes, if there is a max
32    pub max_strikes: Option<u32>,
33    pub move_modifier: f32,
34    pub ori_modifier: f32,
35    pub minimum_combo: u32,
36    /// Used to indicate to the frontend what ability this is for any special
37    /// effects
38    pub frontend_specifier: Option<FrontendSpecifier>,
39    /// What key is used to press ability
40    pub ability_info: AbilityInfo,
41}
42
43#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
44pub struct Data {
45    /// Struct containing data that does not change over the course of the
46    /// character state
47    pub static_data: StaticData,
48    /// Timer for each stage
49    pub timer: Duration,
50    /// How many spins it has done
51    pub current_strike: u32,
52    /// What section the character stage is in
53    pub stage_section: StageSection,
54    /// Whether the state can deal damage
55    pub exhausted: bool,
56}
57
58impl CharacterBehavior for Data {
59    fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
60        let mut update = StateUpdate::from(data);
61
62        handle_orientation(data, &mut update, self.static_data.ori_modifier, None);
63        handle_move(data, &mut update, self.static_data.move_modifier);
64        handle_interrupts(data, &mut update, output_events);
65
66        match self.stage_section {
67            StageSection::Buildup => {
68                if self.timer < self.static_data.buildup_duration {
69                    // Build up
70                    if let CharacterState::RapidMelee(c) = &mut update.character {
71                        c.timer = tick_attack_or_default(data, self.timer, None);
72                    }
73                } else {
74                    // Transitions to swing section of stage
75                    if let CharacterState::RapidMelee(c) = &mut update.character {
76                        c.timer = Duration::default();
77                        c.stage_section = StageSection::Action;
78                    }
79                }
80            },
81            StageSection::Action => {
82                if !self.exhausted {
83                    if let CharacterState::RapidMelee(c) = &mut update.character {
84                        c.timer = Duration::default();
85                        c.exhausted = true;
86                    }
87
88                    let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
89
90                    data.updater.insert(
91                        data.entity,
92                        self.static_data
93                            .melee_constructor
94                            .clone()
95                            .create_melee(precision_mult, self.static_data.ability_info),
96                    );
97                } else if self.timer < self.static_data.swing_duration {
98                    // Swings
99                    if let CharacterState::RapidMelee(c) = &mut update.character {
100                        c.timer = tick_attack_or_default(data, self.timer, None);
101                    }
102                } else if match self.static_data.max_strikes {
103                    Some(max) => self.current_strike < max,
104                    None => input_is_pressed(data, self.static_data.ability_info.input),
105                } && update
106                    .energy
107                    .try_change_by(-self.static_data.energy_cost)
108                    .is_ok()
109                {
110                    // TODO: Remove this, this should never have been added this way
111                    if self.static_data.frontend_specifier == Some(FrontendSpecifier::CultistVortex)
112                    {
113                        let damage = AttackDamage::new(
114                            Damage {
115                                kind: Crushing,
116                                value: 10.0,
117                            },
118                            Some(GroupTarget::OutOfGroup),
119                            rand::random(),
120                        );
121                        let attack =
122                            Attack::new(Some(self.static_data.ability_info)).with_damage(damage);
123                        let explosion = Explosion {
124                            effects: vec![RadiusEffect::Attack {
125                                attack,
126                                dodgeable: Dodgeable::Roll,
127                            }],
128                            radius: data.body.max_radius() * 4.0,
129                            reagent: Some(Reagent::Purple),
130                            min_falloff: 0.5,
131                        };
132                        let pos =
133                            data.pos.0 + (*data.ori.look_dir() * (data.body.max_radius() * 3.0));
134                        let explosition =
135                            Vec3::new(pos.x, pos.y, pos.z + (data.body.height() / 4.0));
136                        output_events.emit_server(ExplosionEvent {
137                            pos: explosition,
138                            explosion,
139                            owner: Some(*data.uid),
140                        });
141                    }
142                    if let CharacterState::RapidMelee(c) = &mut update.character {
143                        c.timer = Duration::default();
144                        c.current_strike += 1;
145                        c.exhausted = false;
146                    }
147                } else {
148                    // Transitions to recover section of stage
149                    if let CharacterState::RapidMelee(c) = &mut update.character {
150                        c.timer = Duration::default();
151                        c.stage_section = StageSection::Recover;
152                    }
153                }
154
155                // Consume combo if any was required
156                if self.static_data.minimum_combo > 0 {
157                    output_events.emit_server(ComboChangeEvent {
158                        entity: data.entity,
159                        change: -data.combo.map_or(0, |c| c.counter() as i32),
160                    });
161                }
162            },
163            StageSection::Recover => {
164                if self.timer < self.static_data.recover_duration {
165                    // Recover
166                    if let CharacterState::RapidMelee(c) = &mut update.character {
167                        c.timer = tick_attack_or_default(data, self.timer, None);
168                    }
169                } else {
170                    // Done
171                    end_melee_ability(data, &mut update);
172                }
173            },
174            _ => {
175                // If it somehow ends up in an incorrect stage section
176                end_melee_ability(data, &mut update);
177            },
178        }
179
180        update
181    }
182}
183
184#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
185pub enum FrontendSpecifier {
186    CultistVortex,
187    IceWhirlwind,
188    ElephantVacuum,
189}