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#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
20pub struct StaticData {
21 pub buildup_duration: Duration,
23 pub swing_duration: Duration,
25 pub recover_duration: Duration,
27 pub melee_constructor: MeleeConstructor,
29 pub energy_cost: f32,
31 pub max_strikes: Option<u32>,
33 pub move_modifier: f32,
34 pub ori_modifier: f32,
35 pub minimum_combo: u32,
36 pub frontend_specifier: Option<FrontendSpecifier>,
39 pub ability_info: AbilityInfo,
41}
42
43#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
44pub struct Data {
45 pub static_data: StaticData,
48 pub timer: Duration,
50 pub current_strike: u32,
52 pub stage_section: StageSection,
54 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 if let CharacterState::RapidMelee(c) = &mut update.character {
71 c.timer = tick_attack_or_default(data, self.timer, None);
72 }
73 } else {
74 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 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)
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 if let CharacterState::RapidMelee(c) = &mut update.character {
150 c.timer = Duration::default();
151 c.stage_section = StageSection::Recover;
152 }
153 }
154
155 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 if let CharacterState::RapidMelee(c) = &mut update.character {
167 c.timer = tick_attack_or_default(data, self.timer, None);
168 }
169 } else {
170 end_melee_ability(data, &mut update);
172 }
173 },
174 _ => {
175 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}