1use crate::{
2 combat::{
3 self, Attack, AttackDamage, AttackEffect, CombatEffect, CombatRequirement, Damage,
4 DamageKind, GroupTarget, Knockback,
5 },
6 comp::{
7 CharacterState, StateUpdate, ability::Dodgeable, character_state::OutputEvents, shockwave,
8 },
9 event::{LocalEvent, ShockwaveEvent},
10 outcome::Outcome,
11 states::{
12 behavior::{CharacterBehavior, JoinData},
13 utils::*,
14 },
15};
16use serde::{Deserialize, Serialize};
17use std::time::Duration;
18
19#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
21pub struct StaticData {
22 pub buildup_duration: Duration,
24 pub swing_duration: Duration,
26 pub recover_duration: Duration,
28 pub damage: f32,
30 pub poise_damage: f32,
32 pub knockback: Knockback,
34 pub shockwave_angle: f32,
36 pub shockwave_vertical_angle: f32,
38 pub shockwave_speed: f32,
40 pub shockwave_duration: Duration,
42 pub dodgeable: Dodgeable,
44 pub move_efficiency: f32,
46 pub ability_info: AbilityInfo,
48 pub damage_effect: Option<CombatEffect>,
50 pub damage_kind: DamageKind,
52 pub specifier: shockwave::FrontendSpecifier,
54 pub emit_outcome: bool,
56 pub ori_rate: f32,
58 pub timing: Timing,
60 pub minimum_combo: Option<u32>,
61 pub combo_on_use: u32,
62 pub combo_consumption: ComboConsumption,
63}
64
65#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
66pub struct Data {
67 pub static_data: StaticData,
70 pub timer: Duration,
72 pub stage_section: StageSection,
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.static_data.ori_rate, None);
81 handle_move(data, &mut update, self.static_data.move_efficiency);
82
83 match self.stage_section {
84 StageSection::Buildup => {
85 if self.timer < self.static_data.buildup_duration {
86 if let CharacterState::Shockwave(c) = &mut update.character {
88 c.timer = tick_attack_or_default(data, self.timer, None);
89 }
90 } else {
91 if matches!(self.static_data.timing, Timing::PostBuildup) {
93 self.attack(data, output_events);
94 }
95
96 if let CharacterState::Shockwave(c) = &mut update.character {
98 c.timer = Duration::default();
99 c.stage_section = StageSection::Action;
100 }
101 }
102 },
103 StageSection::Action => {
104 if self.timer < self.static_data.swing_duration {
105 if let CharacterState::Shockwave(c) = &mut update.character {
107 c.timer = tick_attack_or_default(data, self.timer, None);
108 }
109 if self.static_data.emit_outcome {
111 match self.static_data.specifier {
112 shockwave::FrontendSpecifier::IceSpikes => {
113 output_events.emit_local(LocalEvent::CreateOutcome(
114 Outcome::FlashFreeze {
115 pos: data.pos.0
116 + *data.ori.look_dir() * (data.body.max_radius()),
117 },
118 ));
119 },
120 shockwave::FrontendSpecifier::Ground => {
121 output_events.emit_local(LocalEvent::CreateOutcome(
122 Outcome::GroundSlam {
123 pos: data.pos.0
124 + *data.ori.look_dir() * (data.body.max_radius()),
125 },
126 ));
127 },
128 shockwave::FrontendSpecifier::Steam => {
129 output_events.emit_local(LocalEvent::CreateOutcome(
130 Outcome::Steam {
131 pos: data.pos.0
132 + *data.ori.look_dir() * (data.body.max_radius()),
133 },
134 ));
135 },
136 shockwave::FrontendSpecifier::Fire => {
137 output_events.emit_local(LocalEvent::CreateOutcome(
138 Outcome::FireShockwave {
139 pos: data.pos.0
140 + *data.ori.look_dir() * (data.body.max_radius()),
141 },
142 ));
143 },
144 shockwave::FrontendSpecifier::FireLow => {
145 output_events.emit_local(LocalEvent::CreateOutcome(
146 Outcome::FireLowShockwave {
147 pos: data.pos.0
148 + *data.ori.look_dir() * (data.body.max_radius()),
149 },
150 ));
151 },
152 _ => {
153 output_events.emit_local(LocalEvent::CreateOutcome(
154 Outcome::Swoosh {
155 pos: data.pos.0
156 + *data.ori.look_dir() * (data.body.max_radius()),
157 },
158 ));
159 },
160 }
161 }
162 } else {
163 if matches!(self.static_data.timing, Timing::PostAction) {
165 self.attack(data, output_events);
166 }
167
168 if let CharacterState::Shockwave(c) = &mut update.character {
170 c.timer = Duration::default();
171 c.stage_section = StageSection::Recover;
172 }
173 }
174 },
175 StageSection::Recover => {
176 if self.timer < self.static_data.recover_duration {
177 if let CharacterState::Shockwave(c) = &mut update.character {
179 c.timer = tick_attack_or_default(
180 data,
181 self.timer,
182 Some(data.stats.recovery_speed_modifier),
183 );
184 }
185 } else {
186 end_ability(data, &mut update);
188 }
189 },
190 _ => {
191 end_ability(data, &mut update);
193 },
194 }
195
196 handle_interrupts(data, &mut update, output_events);
198
199 update
200 }
201}
202
203impl Data {
204 fn attack(&self, data: &JoinData, output_events: &mut OutputEvents) {
205 if let Some(min_combo) = self.static_data.minimum_combo {
206 self.static_data
207 .combo_consumption
208 .consume(data, output_events, min_combo);
209 }
210
211 let poise = AttackEffect::new(
212 Some(GroupTarget::OutOfGroup),
213 CombatEffect::Poise(self.static_data.poise_damage),
214 )
215 .with_requirement(CombatRequirement::AnyDamage);
216 let knockback = AttackEffect::new(
217 Some(GroupTarget::OutOfGroup),
218 CombatEffect::Knockback(self.static_data.knockback),
219 )
220 .with_requirement(CombatRequirement::AnyDamage);
221 let mut damage = AttackDamage::new(
222 Damage {
223 kind: self.static_data.damage_kind,
224 value: self.static_data.damage,
225 },
226 Some(GroupTarget::OutOfGroup),
227 rand::random(),
228 );
229 if let Some(effect) = &self.static_data.damage_effect {
230 damage = damage.with_effect(effect.clone());
231 }
232 let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
233 let attack = Attack::new(Some(self.static_data.ability_info))
234 .with_damage(damage)
235 .with_precision(
236 precision_mult
237 * self
238 .static_data
239 .ability_info
240 .ability_meta
241 .precision_power_mult
242 .unwrap_or(1.0),
243 )
244 .with_effect(poise)
245 .with_effect(knockback)
246 .with_combo_increment();
247 let properties = shockwave::Properties {
248 angle: self.static_data.shockwave_angle,
249 vertical_angle: self.static_data.shockwave_vertical_angle,
250 speed: self.static_data.shockwave_speed,
251 duration: self.static_data.shockwave_duration,
252 attack,
253 dodgeable: self.static_data.dodgeable,
254 owner: Some(*data.uid),
255 specifier: self.static_data.specifier,
256 };
257 output_events.emit_server(ShockwaveEvent {
258 properties,
259 pos: *data.pos,
260 ori: *data.ori,
261 });
262 }
263}
264
265#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
266pub enum Timing {
267 PostBuildup,
268 PostAction,
269}