1use crate::{
2 combat::{
3 self, Attack, AttackDamage, AttackEffect, CombatEffect, CombatRequirement, Damage,
4 DamageKind, DamageSource, 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(Copy, 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(Copy, 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 update.character = CharacterState::Shockwave(Data {
88 timer: tick_attack_or_default(data, self.timer, None),
89 ..*self
90 });
91 } else {
92 if matches!(self.static_data.timing, Timing::PostBuildup) {
94 self.attack(data, output_events);
95 }
96
97 update.character = CharacterState::Shockwave(Data {
99 timer: Duration::default(),
100 stage_section: StageSection::Action,
101 ..*self
102 });
103 }
104 },
105 StageSection::Action => {
106 if self.timer < self.static_data.swing_duration {
107 update.character = CharacterState::Shockwave(Data {
109 timer: tick_attack_or_default(data, self.timer, None),
110 ..*self
111 });
112 if self.static_data.emit_outcome {
114 match self.static_data.specifier {
115 shockwave::FrontendSpecifier::IceSpikes => {
116 output_events.emit_local(LocalEvent::CreateOutcome(
117 Outcome::FlashFreeze {
118 pos: data.pos.0
119 + *data.ori.look_dir() * (data.body.max_radius()),
120 },
121 ));
122 },
123 shockwave::FrontendSpecifier::Ground => {
124 output_events.emit_local(LocalEvent::CreateOutcome(
125 Outcome::GroundSlam {
126 pos: data.pos.0
127 + *data.ori.look_dir() * (data.body.max_radius()),
128 },
129 ));
130 },
131 shockwave::FrontendSpecifier::Steam => {
132 output_events.emit_local(LocalEvent::CreateOutcome(
133 Outcome::Steam {
134 pos: data.pos.0
135 + *data.ori.look_dir() * (data.body.max_radius()),
136 },
137 ));
138 },
139 shockwave::FrontendSpecifier::Fire => {
140 output_events.emit_local(LocalEvent::CreateOutcome(
141 Outcome::FireShockwave {
142 pos: data.pos.0
143 + *data.ori.look_dir() * (data.body.max_radius()),
144 },
145 ));
146 },
147 shockwave::FrontendSpecifier::FireLow => {
148 output_events.emit_local(LocalEvent::CreateOutcome(
149 Outcome::FireLowShockwave {
150 pos: data.pos.0
151 + *data.ori.look_dir() * (data.body.max_radius()),
152 },
153 ));
154 },
155 _ => {
156 output_events.emit_local(LocalEvent::CreateOutcome(
157 Outcome::Swoosh {
158 pos: data.pos.0
159 + *data.ori.look_dir() * (data.body.max_radius()),
160 },
161 ));
162 },
163 }
164 }
165 } else {
166 if matches!(self.static_data.timing, Timing::PostAction) {
168 self.attack(data, output_events);
169 }
170
171 update.character = CharacterState::Shockwave(Data {
173 timer: Duration::default(),
174 stage_section: StageSection::Recover,
175 ..*self
176 });
177 }
178 },
179 StageSection::Recover => {
180 if self.timer < self.static_data.recover_duration {
181 update.character = CharacterState::Shockwave(Data {
183 timer: tick_attack_or_default(
184 data,
185 self.timer,
186 Some(data.stats.recovery_speed_modifier),
187 ),
188 ..*self
189 });
190 } else {
191 end_ability(data, &mut update);
193 }
194 },
195 _ => {
196 end_ability(data, &mut update);
198 },
199 }
200
201 handle_interrupts(data, &mut update, output_events);
203
204 update
205 }
206}
207
208impl Data {
209 fn attack(&self, data: &JoinData, output_events: &mut OutputEvents) {
210 if let Some(min_combo) = self.static_data.minimum_combo {
211 self.static_data
212 .combo_consumption
213 .consume(data, output_events, min_combo);
214 }
215
216 let poise = AttackEffect::new(
217 Some(GroupTarget::OutOfGroup),
218 CombatEffect::Poise(self.static_data.poise_damage),
219 )
220 .with_requirement(CombatRequirement::AnyDamage);
221 let knockback = AttackEffect::new(
222 Some(GroupTarget::OutOfGroup),
223 CombatEffect::Knockback(self.static_data.knockback),
224 )
225 .with_requirement(CombatRequirement::AnyDamage);
226 let mut damage = AttackDamage::new(
227 Damage {
228 source: DamageSource::Shockwave,
229 kind: self.static_data.damage_kind,
230 value: self.static_data.damage,
231 },
232 Some(GroupTarget::OutOfGroup),
233 rand::random(),
234 );
235 if let Some(effect) = self.static_data.damage_effect {
236 damage = damage.with_effect(effect);
237 }
238 let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
239 let attack = Attack::default()
240 .with_damage(damage)
241 .with_precision(precision_mult)
242 .with_effect(poise)
243 .with_effect(knockback)
244 .with_combo_increment();
245 let properties = shockwave::Properties {
246 angle: self.static_data.shockwave_angle,
247 vertical_angle: self.static_data.shockwave_vertical_angle,
248 speed: self.static_data.shockwave_speed,
249 duration: self.static_data.shockwave_duration,
250 attack,
251 dodgeable: self.static_data.dodgeable,
252 owner: Some(*data.uid),
253 specifier: self.static_data.specifier,
254 };
255 output_events.emit_server(ShockwaveEvent {
256 properties,
257 pos: *data.pos,
258 ori: *data.ori,
259 });
260 }
261}
262
263#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
264pub enum Timing {
265 PostBuildup,
266 PostAction,
267}