1use crate::{
2 Explosion, KnockbackDir, RadiusEffect,
3 combat::{
4 self, Attack, AttackDamage, AttackEffect, CombatEffect, CombatRequirement, Damage,
5 DamageKind, DamageSource, 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(Copy, 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(Copy, 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 update.character = CharacterState::LeapShockwave(Data {
91 timer: tick_attack_or_default(data, self.timer, None),
92 ..*self
93 });
94 } else {
95 update.character = CharacterState::LeapShockwave(Data {
97 timer: Duration::default(),
98 stage_section: StageSection::Movement,
99 ..*self
100 });
101 }
102 },
103 StageSection::Movement => {
104 if self.timer < self.static_data.movement_duration {
105 let progress = 1.0
107 - self.timer.as_secs_f32()
108 / self.static_data.movement_duration.as_secs_f32();
109 handle_forced_movement(data, &mut update, ForcedMovement::Leap {
110 vertical: self.static_data.vertical_leap_strength,
111 forward: self.static_data.forward_leap_strength,
112 progress,
113 direction: MovementDirection::Look,
114 });
115
116 update.character = CharacterState::LeapShockwave(Data {
121 timer: tick_attack_or_default(data, self.timer, None),
122 ..*self
123 });
124 } else if data.physics.on_ground.is_some() | data.physics.in_liquid().is_some() {
125 update.character = CharacterState::LeapShockwave(Data {
127 timer: Duration::default(),
128 stage_section: StageSection::Action,
129 ..*self
130 });
131 }
132 },
133 StageSection::Action => {
134 handle_move(data, &mut update, 0.3);
135 handle_jump(data, output_events, &mut update, 1.0);
136 if self.timer < self.static_data.swing_duration {
137 update.character = CharacterState::LeapShockwave(Data {
139 timer: tick_attack_or_default(data, self.timer, None),
140 ..*self
141 });
142
143 let poise = AttackEffect::new(
145 Some(GroupTarget::OutOfGroup),
146 CombatEffect::Poise(self.static_data.poise_damage),
147 )
148 .with_requirement(CombatRequirement::AnyDamage);
149 let knockback = AttackEffect::new(
150 Some(GroupTarget::OutOfGroup),
151 CombatEffect::Knockback(self.static_data.knockback),
152 )
153 .with_requirement(CombatRequirement::AnyDamage);
154 let mut damage = AttackDamage::new(
155 Damage {
156 source: DamageSource::Shockwave,
157 kind: self.static_data.damage_kind,
158 value: self.static_data.damage,
159 },
160 Some(GroupTarget::OutOfGroup),
161 rand::random(),
162 );
163 if let Some(effect) = self.static_data.damage_effect {
164 damage = damage.with_effect(effect);
165 }
166 let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
167 let attack = Attack::default()
168 .with_damage(damage)
169 .with_precision(precision_mult)
170 .with_effect(poise)
171 .with_effect(knockback)
172 .with_combo_increment();
173 let properties = shockwave::Properties {
174 angle: self.static_data.shockwave_angle,
175 vertical_angle: self.static_data.shockwave_vertical_angle,
176 speed: self.static_data.shockwave_speed,
177 duration: self.static_data.shockwave_duration,
178 attack,
179 dodgeable: self.static_data.dodgeable,
180 owner: Some(*data.uid),
181 specifier: self.static_data.specifier,
182 };
183 output_events.emit_server(ShockwaveEvent {
184 properties,
185 pos: *data.pos,
186 ori: *data.ori,
187 });
188 match self.static_data.specifier {
190 shockwave::FrontendSpecifier::IceSpikes => {
191 let damage = AttackDamage::new(
192 Damage {
193 source: DamageSource::Explosion,
194 kind: self.static_data.damage_kind,
195 value: self.static_data.damage / 2.,
196 },
197 Some(GroupTarget::OutOfGroup),
198 rand::random(),
199 );
200 let attack = Attack::default().with_damage(damage).with_effect(
201 AttackEffect::new(
202 Some(GroupTarget::OutOfGroup),
203 CombatEffect::Knockback(Knockback {
204 direction: KnockbackDir::Away,
205 strength: 10.,
206 }),
207 ),
208 );
209 let explosion = Explosion {
210 effects: vec![RadiusEffect::Attack(attack)],
211 radius: data.body.max_radius() * 3.0,
212 reagent: Some(Reagent::White),
213 min_falloff: 0.5,
214 };
215 output_events.emit_server(ExplosionEvent {
216 pos: data.pos.0,
217 explosion,
218 owner: Some(*data.uid),
219 });
220 output_events.emit_local(LocalEvent::CreateOutcome(
221 Outcome::IceSpikes {
222 pos: data.pos.0
223 + *data.ori.look_dir() * (data.body.max_radius()),
224 },
225 ));
226 },
227 shockwave::FrontendSpecifier::Steam => {
228 output_events.emit_local(LocalEvent::CreateOutcome(Outcome::Steam {
229 pos: data.pos.0 + *data.ori.look_dir() * (data.body.max_radius()),
230 }));
231 },
232 _ => {},
233 };
234 } else {
235 update.character = CharacterState::LeapShockwave(Data {
237 timer: Duration::default(),
238 stage_section: StageSection::Recover,
239 ..*self
240 });
241 }
242 },
243 StageSection::Recover => {
244 handle_move(data, &mut update, 0.3);
245 handle_jump(data, output_events, &mut update, 1.0);
246 if self.timer < self.static_data.recover_duration {
247 update.character = CharacterState::LeapShockwave(Data {
249 timer: tick_attack_or_default(
250 data,
251 self.timer,
252 Some(data.stats.recovery_speed_modifier),
253 ),
254 ..*self
255 });
256 } else {
257 end_ability(data, &mut update);
259 }
260 },
261 _ => {
262 end_ability(data, &mut update);
264 },
265 }
266
267 handle_interrupts(data, &mut update, output_events);
269
270 update
271 }
272}