1use crate::{
2 Explosion, 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, ShockwaveEvent},
12 explosion::{ColorPreset, TerrainReplacementPreset},
13 states::{
14 behavior::{CharacterBehavior, JoinData},
15 utils::{StageSection, *},
16 },
17};
18use serde::{Deserialize, Serialize};
19use std::time::Duration;
20use vek::Vec3;
21
22#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
24pub struct StaticData {
25 pub movement_duration: Duration,
27 pub buildup_duration: Duration,
29 pub swing_duration: Duration,
31 pub recover_duration: Duration,
33 pub forward_leap_strength: f32,
35 pub vertical_leap_strength: f32,
37
38 pub explosion_damage: f32,
40 pub explosion_poise: f32,
42 pub explosion_knockback: Knockback,
44 pub explosion_radius: f32,
46 pub min_falloff: f32,
48 #[serde(default)]
50 pub explosion_dodgeable: Dodgeable,
51 pub destroy_terrain: Option<(f32, ColorPreset)>,
53 pub replace_terrain: Option<(f32, TerrainReplacementPreset)>,
55 #[serde(default)]
58 pub eye_height: bool,
59 pub reagent: Option<Reagent>,
61 pub shockwave_damage: f32,
65 pub shockwave_poise: f32,
67 pub shockwave_knockback: Knockback,
69 pub shockwave_angle: f32,
71 pub shockwave_vertical_angle: f32,
73 pub shockwave_speed: f32,
75 pub shockwave_duration: Duration,
77 pub shockwave_dodgeable: Dodgeable,
79 pub shockwave_damage_effect: Option<CombatEffect>,
81 pub shockwave_damage_kind: DamageKind,
83 pub shockwave_specifier: shockwave::FrontendSpecifier,
85
86 pub move_efficiency: f32,
88 pub ability_info: AbilityInfo,
90}
91
92#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
93pub struct Data {
94 pub static_data: StaticData,
97 pub timer: Duration,
99 pub stage_section: StageSection,
101 pub exhausted: bool,
103}
104
105impl CharacterBehavior for Data {
106 fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
107 let mut update = StateUpdate::from(data);
108
109 handle_orientation(data, &mut update, 1.0, None);
110 handle_move(data, &mut update, 0.3);
111 handle_jump(data, output_events, &mut update, 1.0);
112
113 match self.stage_section {
114 StageSection::Buildup => {
116 if self.timer < self.static_data.buildup_duration {
118 if let CharacterState::LeapExplosionShockwave(c) = &mut update.character {
119 c.timer = tick_attack_or_default(data, self.timer, None);
120 }
121 } else {
122 if let CharacterState::LeapExplosionShockwave(c) = &mut update.character {
124 c.timer = Duration::default();
125 c.stage_section = StageSection::Movement;
126 }
127 }
128 },
129 StageSection::Movement => {
130 if self.timer < self.static_data.movement_duration {
131 let progress = 1.0
133 - self.timer.as_secs_f32()
134 / self.static_data.movement_duration.as_secs_f32();
135 handle_forced_movement(data, &mut update, ForcedMovement::Leap {
136 vertical: self.static_data.vertical_leap_strength,
137 forward: self.static_data.forward_leap_strength,
138 progress,
139 direction: MovementDirection::Look,
140 });
141
142 if let CharacterState::LeapExplosionShockwave(c) = &mut update.character {
147 c.timer = tick_attack_or_default(data, self.timer, None);
148 }
149 } else if data.physics.on_ground.is_some() | data.physics.in_liquid().is_some() {
150 if let CharacterState::LeapExplosionShockwave(c) = &mut update.character {
152 c.timer = Duration::default();
153 c.stage_section = StageSection::Action;
154 }
155 }
156 },
157 StageSection::Action => {
158 if self.timer < self.static_data.swing_duration {
159 if let CharacterState::LeapExplosionShockwave(c) = &mut update.character {
161 c.timer = tick_attack_or_default(data, self.timer, None);
162 }
163 } else {
164 let explosion_pos = if self.static_data.eye_height {
166 data.pos.0
167 + Vec3::unit_z() * data.body.eye_height(data.scale.map_or(1.0, |s| s.0))
168 } else {
169 data.pos.0
170 };
171
172 let mut effects = vec![RadiusEffect::Attack {
173 attack: Attack::new(Some(self.static_data.ability_info))
174 .with_damage(AttackDamage::new(
175 Damage {
176 kind: DamageKind::Crushing,
177 value: self.static_data.explosion_damage,
178 },
179 Some(GroupTarget::OutOfGroup),
180 rand::random(),
181 ))
182 .with_effect(
183 AttackEffect::new(
184 Some(GroupTarget::OutOfGroup),
185 CombatEffect::Poise(self.static_data.explosion_poise),
186 )
187 .with_requirement(CombatRequirement::AnyDamage),
188 )
189 .with_effect(
190 AttackEffect::new(
191 Some(GroupTarget::OutOfGroup),
192 CombatEffect::Knockback(self.static_data.explosion_knockback),
193 )
194 .with_requirement(CombatRequirement::AnyDamage),
195 ),
196 dodgeable: self.static_data.explosion_dodgeable,
197 }];
198
199 if let Some((power, color)) = self.static_data.destroy_terrain {
200 effects.push(RadiusEffect::TerrainDestruction(power, color.to_rgb()));
201 }
202
203 if let Some((radius, replacement_preset)) = self.static_data.replace_terrain {
204 effects.push(RadiusEffect::ReplaceTerrain(radius, replacement_preset));
205 }
206
207 output_events.emit_server(ExplosionEvent {
208 pos: explosion_pos,
209 explosion: Explosion {
210 effects,
211 radius: self.static_data.explosion_radius,
212 reagent: self.static_data.reagent,
213 min_falloff: self.static_data.min_falloff,
214 },
215 owner: Some(*data.uid),
216 });
217
218 let shockwave_poise = AttackEffect::new(
220 Some(GroupTarget::OutOfGroup),
221 CombatEffect::Poise(self.static_data.shockwave_poise),
222 )
223 .with_requirement(CombatRequirement::AnyDamage);
224 let shockwave_knockback = AttackEffect::new(
225 Some(GroupTarget::OutOfGroup),
226 CombatEffect::Knockback(self.static_data.shockwave_knockback),
227 )
228 .with_requirement(CombatRequirement::AnyDamage);
229 let mut shockwave_damage = AttackDamage::new(
230 Damage {
231 kind: self.static_data.shockwave_damage_kind,
232 value: self.static_data.shockwave_damage,
233 },
234 Some(GroupTarget::OutOfGroup),
235 rand::random(),
236 );
237 if let Some(effect) = &self.static_data.shockwave_damage_effect {
238 shockwave_damage = shockwave_damage.with_effect(effect.clone());
239 }
240 let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
241 let shockwave_attack = Attack::new(Some(self.static_data.ability_info))
242 .with_damage(shockwave_damage)
243 .with_precision(
244 precision_mult
245 * self
246 .static_data
247 .ability_info
248 .ability_meta
249 .precision_power_mult
250 .unwrap_or(1.0),
251 )
252 .with_effect(shockwave_poise)
253 .with_effect(shockwave_knockback)
254 .with_combo_increment();
255 let properties = shockwave::Properties {
256 angle: self.static_data.shockwave_angle,
257 vertical_angle: self.static_data.shockwave_vertical_angle,
258 speed: self.static_data.shockwave_speed,
259 duration: self.static_data.shockwave_duration,
260 attack: shockwave_attack,
261 dodgeable: self.static_data.shockwave_dodgeable,
262 owner: Some(*data.uid),
263 specifier: self.static_data.shockwave_specifier,
264 };
265 output_events.emit_server(ShockwaveEvent {
266 properties,
267 pos: *data.pos,
268 ori: *data.ori,
269 });
270
271 if let CharacterState::LeapExplosionShockwave(c) = &mut update.character {
273 c.timer = Duration::default();
274 c.stage_section = StageSection::Recover;
275 }
276 }
277 },
278 StageSection::Recover => {
279 if self.timer < self.static_data.recover_duration {
280 if let CharacterState::LeapExplosionShockwave(c) = &mut update.character {
282 c.timer = tick_attack_or_default(
283 data,
284 self.timer,
285 Some(data.stats.recovery_speed_modifier),
286 );
287 }
288 } else {
289 end_ability(data, &mut update);
291 }
292 },
293 _ => {
294 end_ability(data, &mut update);
296 },
297 }
298
299 handle_interrupts(data, &mut update, output_events);
301
302 update
303 }
304}