veloren_common/states/
rapid_melee.rs

1use crate::{
2    Explosion, RadiusEffect,
3    combat::{self, Attack, AttackDamage, Damage, DamageKind::Crushing, DamageSource, 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(Copy, 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(Copy, 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                    let tool_stats = get_tool_stats(data, self.static_data.ability_info);
90
91                    data.updater.insert(
92                        data.entity,
93                        self.static_data
94                            .melee_constructor
95                            .create_melee(precision_mult, tool_stats),
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                    if self.static_data.frontend_specifier == Some(FrontendSpecifier::CultistVortex)
111                    {
112                        let damage = AttackDamage::new(
113                            Damage {
114                                source: DamageSource::Explosion,
115                                kind: Crushing,
116                                value: 10.0,
117                            },
118                            Some(GroupTarget::OutOfGroup),
119                            rand::random(),
120                        );
121                        let attack = Attack::default().with_damage(damage);
122                        let explosion = Explosion {
123                            effects: vec![RadiusEffect::Attack {
124                                attack,
125                                dodgeable: Dodgeable::Roll,
126                            }],
127                            radius: data.body.max_radius() * 4.0,
128                            reagent: Some(Reagent::Purple),
129                            min_falloff: 0.5,
130                        };
131                        let pos =
132                            data.pos.0 + (*data.ori.look_dir() * (data.body.max_radius() * 3.0));
133                        let explosition =
134                            Vec3::new(pos.x, pos.y, pos.z + (data.body.height() / 4.0));
135                        output_events.emit_server(ExplosionEvent {
136                            pos: explosition,
137                            explosion,
138                            owner: Some(*data.uid),
139                        });
140                    }
141                    if let CharacterState::RapidMelee(c) = &mut update.character {
142                        c.timer = Duration::default();
143                        c.current_strike += 1;
144                        c.exhausted = false;
145                    }
146                } else {
147                    // Transitions to recover section of stage
148                    if let CharacterState::RapidMelee(c) = &mut update.character {
149                        c.timer = Duration::default();
150                        c.stage_section = StageSection::Recover;
151                    }
152                }
153
154                // Consume combo if any was required
155                if self.static_data.minimum_combo > 0 {
156                    output_events.emit_server(ComboChangeEvent {
157                        entity: data.entity,
158                        change: -data.combo.map_or(0, |c| c.counter() as i32),
159                    });
160                }
161            },
162            StageSection::Recover => {
163                if self.timer < self.static_data.recover_duration {
164                    // Recover
165                    if let CharacterState::RapidMelee(c) = &mut update.character {
166                        c.timer = tick_attack_or_default(
167                            data,
168                            self.timer,
169                            Some(data.stats.recovery_speed_modifier),
170                        );
171                    }
172                } else {
173                    // Done
174                    end_melee_ability(data, &mut update);
175                }
176            },
177            _ => {
178                // If it somehow ends up in an incorrect stage section
179                end_melee_ability(data, &mut update);
180            },
181        }
182
183        update
184    }
185}
186
187#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
188pub enum FrontendSpecifier {
189    CultistVortex,
190    IceWhirlwind,
191}