1use crate::{
2 Explosion, KnockbackDir, RadiusEffect,
3 combat::{
4 self, Attack, AttackDamage, AttackEffect, CombatEffect, CombatRequirement, Damage,
5 DamageKind, GroupTarget, Knockback,
6 },
7 comp::{
8 CharacterState, StateUpdate, ability::Dodgeable, character_state::OutputEvents,
9 item::Reagent, shockwave,
10 },
11 event::{ExplosionEvent, LocalEvent, ShockwaveEvent},
12 outcome::Outcome,
13 states::{
14 behavior::{CharacterBehavior, JoinData},
15 utils::{StageSection, *},
16 },
17};
18use serde::{Deserialize, Serialize};
19use std::time::Duration;
20
21#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
23pub struct StaticData {
24 pub movement_duration: Duration,
26 pub buildup_duration: Duration,
28 pub swing_duration: Duration,
30 pub recover_duration: Duration,
32 pub damage: f32,
34 pub poise_damage: f32,
36 pub knockback: Knockback,
38 pub shockwave_angle: f32,
40 pub shockwave_vertical_angle: f32,
42 pub shockwave_speed: f32,
44 pub shockwave_duration: Duration,
46 pub dodgeable: Dodgeable,
48 pub move_efficiency: f32,
50 pub damage_effect: Option<CombatEffect>,
52 pub damage_kind: DamageKind,
54 pub specifier: shockwave::FrontendSpecifier,
56 pub forward_leap_strength: f32,
58 pub vertical_leap_strength: f32,
60 pub ability_info: AbilityInfo,
62}
63
64#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
65pub struct Data {
66 pub static_data: StaticData,
69 pub timer: Duration,
71 pub stage_section: StageSection,
73 pub exhausted: bool,
75}
76
77impl CharacterBehavior for Data {
78 fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
79 let mut update = StateUpdate::from(data);
80
81 handle_orientation(data, &mut update, 1.0, None);
82
83 match self.stage_section {
84 StageSection::Buildup => {
86 handle_move(data, &mut update, 0.3);
87 handle_jump(data, output_events, &mut update, 1.0);
88 if self.timer < self.static_data.buildup_duration {
90 if let CharacterState::LeapShockwave(c) = &mut update.character {
91 c.timer = tick_attack_or_default(data, self.timer, None);
92 }
93 } else {
94 if let CharacterState::LeapShockwave(c) = &mut update.character {
96 c.timer = Duration::default();
97 c.stage_section = StageSection::Movement;
98 }
99 }
100 },
101 StageSection::Movement => {
102 if self.timer < self.static_data.movement_duration {
103 let progress = 1.0
105 - self.timer.as_secs_f32()
106 / self.static_data.movement_duration.as_secs_f32();
107 handle_forced_movement(data, &mut update, ForcedMovement::Leap {
108 vertical: self.static_data.vertical_leap_strength,
109 forward: self.static_data.forward_leap_strength,
110 progress,
111 direction: MovementDirection::Look,
112 });
113
114 if let CharacterState::LeapShockwave(c) = &mut update.character {
119 c.timer = tick_attack_or_default(data, self.timer, None);
120 }
121 } else if data.physics.on_ground.is_some() | data.physics.in_liquid().is_some() {
122 if let CharacterState::LeapShockwave(c) = &mut update.character {
124 c.timer = Duration::default();
125 c.stage_section = StageSection::Action;
126 }
127 }
128 },
129 StageSection::Action => {
130 handle_move(data, &mut update, 0.3);
131 handle_jump(data, output_events, &mut update, 1.0);
132 if self.timer < self.static_data.swing_duration {
133 if let CharacterState::LeapShockwave(c) = &mut update.character {
135 c.timer = tick_attack_or_default(data, self.timer, None);
136 }
137
138 let poise = AttackEffect::new(
140 Some(GroupTarget::OutOfGroup),
141 CombatEffect::Poise(self.static_data.poise_damage),
142 )
143 .with_requirement(CombatRequirement::AnyDamage);
144 let knockback = AttackEffect::new(
145 Some(GroupTarget::OutOfGroup),
146 CombatEffect::Knockback(self.static_data.knockback),
147 )
148 .with_requirement(CombatRequirement::AnyDamage);
149 let mut damage = AttackDamage::new(
150 Damage {
151 kind: self.static_data.damage_kind,
152 value: self.static_data.damage,
153 },
154 Some(GroupTarget::OutOfGroup),
155 rand::random(),
156 );
157 if let Some(effect) = &self.static_data.damage_effect {
158 damage = damage.with_effect(effect.clone());
159 }
160 let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
161 let attack = Attack::new(Some(self.static_data.ability_info))
162 .with_damage(damage)
163 .with_precision(
164 precision_mult
165 * self
166 .static_data
167 .ability_info
168 .ability_meta
169 .precision_power_mult
170 .unwrap_or(1.0),
171 )
172 .with_effect(poise)
173 .with_effect(knockback)
174 .with_combo_increment();
175 let properties = shockwave::Properties {
176 angle: self.static_data.shockwave_angle,
177 vertical_angle: self.static_data.shockwave_vertical_angle,
178 speed: self.static_data.shockwave_speed,
179 duration: self.static_data.shockwave_duration,
180 attack,
181 dodgeable: self.static_data.dodgeable,
182 owner: Some(*data.uid),
183 specifier: self.static_data.specifier,
184 };
185 output_events.emit_server(ShockwaveEvent {
186 properties,
187 pos: *data.pos,
188 ori: *data.ori,
189 });
190 match self.static_data.specifier {
192 shockwave::FrontendSpecifier::IceSpikes => {
194 let damage = AttackDamage::new(
195 Damage {
196 kind: self.static_data.damage_kind,
197 value: self.static_data.damage / 2.,
198 },
199 Some(GroupTarget::OutOfGroup),
200 rand::random(),
201 );
202 let attack = Attack::new(Some(self.static_data.ability_info))
203 .with_damage(damage)
204 .with_effect(AttackEffect::new(
205 Some(GroupTarget::OutOfGroup),
206 CombatEffect::Knockback(Knockback {
207 direction: KnockbackDir::Away,
208 strength: 10.,
209 }),
210 ));
211 let explosion = Explosion {
212 effects: vec![RadiusEffect::Attack {
213 attack,
214 dodgeable: Dodgeable::Roll,
215 }],
216 radius: data.body.max_radius() * 3.0,
217 reagent: Some(Reagent::White),
218 min_falloff: 0.5,
219 };
220 output_events.emit_server(ExplosionEvent {
221 pos: data.pos.0,
222 explosion,
223 owner: Some(*data.uid),
224 });
225 output_events.emit_local(LocalEvent::CreateOutcome(
226 Outcome::IceSpikes {
227 pos: data.pos.0
228 + *data.ori.look_dir() * (data.body.max_radius()),
229 },
230 ));
231 },
232 shockwave::FrontendSpecifier::Steam => {
233 output_events.emit_local(LocalEvent::CreateOutcome(Outcome::Steam {
234 pos: data.pos.0 + *data.ori.look_dir() * (data.body.max_radius()),
235 }));
236 },
237 _ => {},
238 };
239 } else {
240 if let CharacterState::LeapShockwave(c) = &mut update.character {
242 c.timer = Duration::default();
243 c.stage_section = StageSection::Recover;
244 }
245 }
246 },
247 StageSection::Recover => {
248 handle_move(data, &mut update, 0.3);
249 handle_jump(data, output_events, &mut update, 1.0);
250 if self.timer < self.static_data.recover_duration {
251 if let CharacterState::LeapShockwave(c) = &mut update.character {
253 c.timer = tick_attack_or_default(
254 data,
255 self.timer,
256 Some(data.stats.recovery_speed_modifier),
257 );
258 }
259 } else {
260 end_ability(data, &mut update);
262 }
263 },
264 _ => {
265 end_ability(data, &mut update);
267 },
268 }
269
270 handle_interrupts(data, &mut update, output_events);
272
273 update
274 }
275}