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(data, self.timer, None);
180 }
181 } else {
182 end_ability(data, &mut update);
184 }
185 },
186 _ => {
187 end_ability(data, &mut update);
189 },
190 }
191
192 handle_interrupts(data, &mut update, output_events);
194
195 update
196 }
197}
198
199impl Data {
200 fn attack(&self, data: &JoinData, output_events: &mut OutputEvents) {
201 if let Some(min_combo) = self.static_data.minimum_combo {
202 self.static_data
203 .combo_consumption
204 .consume(data, output_events, min_combo);
205 }
206
207 let poise = AttackEffect::new(
208 Some(GroupTarget::OutOfGroup),
209 CombatEffect::Poise(self.static_data.poise_damage),
210 )
211 .with_requirement(CombatRequirement::AnyDamage);
212 let knockback = AttackEffect::new(
213 Some(GroupTarget::OutOfGroup),
214 CombatEffect::Knockback(self.static_data.knockback),
215 )
216 .with_requirement(CombatRequirement::AnyDamage);
217 let mut damage = AttackDamage::new(
218 Damage {
219 kind: self.static_data.damage_kind,
220 value: self.static_data.damage,
221 },
222 Some(GroupTarget::OutOfGroup),
223 rand::random(),
224 );
225 if let Some(effect) = &self.static_data.damage_effect {
226 damage = damage.with_effect(effect.clone());
227 }
228 let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
229 let attack = Attack::new(Some(self.static_data.ability_info))
230 .with_damage(damage)
231 .with_precision(
232 precision_mult
233 * self
234 .static_data
235 .ability_info
236 .ability_meta
237 .precision_power_mult
238 .unwrap_or(1.0),
239 )
240 .with_effect(poise)
241 .with_effect(knockback)
242 .with_combo_increment();
243 let properties = shockwave::Properties {
244 angle: self.static_data.shockwave_angle,
245 vertical_angle: self.static_data.shockwave_vertical_angle,
246 speed: self.static_data.shockwave_speed,
247 duration: self.static_data.shockwave_duration,
248 attack,
249 dodgeable: self.static_data.dodgeable,
250 owner: Some(*data.uid),
251 specifier: self.static_data.specifier,
252 };
253 output_events.emit_server(ShockwaveEvent {
254 properties,
255 pos: *data.pos,
256 ori: *data.ori,
257 });
258 }
259}
260
261#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
262pub enum Timing {
263 PostBuildup,
264 PostAction,
265}