1use crate::{
2 Explosion, 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, 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(Copy, 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(Copy, 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 update.character = CharacterState::LeapExplosionShockwave(Data {
119 timer: tick_attack_or_default(data, self.timer, None),
120 ..*self
121 });
122 } else {
123 update.character = CharacterState::LeapExplosionShockwave(Data {
125 timer: Duration::default(),
126 stage_section: StageSection::Movement,
127 ..*self
128 });
129 }
130 },
131 StageSection::Movement => {
132 if self.timer < self.static_data.movement_duration {
133 let progress = 1.0
135 - self.timer.as_secs_f32()
136 / self.static_data.movement_duration.as_secs_f32();
137 handle_forced_movement(data, &mut update, ForcedMovement::Leap {
138 vertical: self.static_data.vertical_leap_strength,
139 forward: self.static_data.forward_leap_strength,
140 progress,
141 direction: MovementDirection::Look,
142 });
143
144 update.character = CharacterState::LeapExplosionShockwave(Data {
149 timer: tick_attack_or_default(data, self.timer, None),
150 ..*self
151 });
152 } else if data.physics.on_ground.is_some() | data.physics.in_liquid().is_some() {
153 update.character = CharacterState::LeapExplosionShockwave(Data {
155 timer: Duration::default(),
156 stage_section: StageSection::Action,
157 ..*self
158 });
159 }
160 },
161 StageSection::Action => {
162 if self.timer < self.static_data.swing_duration {
163 update.character = CharacterState::LeapExplosionShockwave(Data {
165 timer: tick_attack_or_default(data, self.timer, None),
166 ..*self
167 });
168 } else {
169 let explosion_pos = if self.static_data.eye_height {
171 data.pos.0
172 + Vec3::unit_z() * data.body.eye_height(data.scale.map_or(1.0, |s| s.0))
173 } else {
174 data.pos.0
175 };
176
177 let mut effects = vec![RadiusEffect::Attack {
178 attack: Attack::default()
179 .with_damage(AttackDamage::new(
180 Damage {
181 source: DamageSource::Explosion,
182 kind: DamageKind::Crushing,
183 value: self.static_data.explosion_damage,
184 },
185 Some(GroupTarget::OutOfGroup),
186 rand::random(),
187 ))
188 .with_effect(
189 AttackEffect::new(
190 Some(GroupTarget::OutOfGroup),
191 CombatEffect::Poise(self.static_data.explosion_poise),
192 )
193 .with_requirement(CombatRequirement::AnyDamage),
194 )
195 .with_effect(
196 AttackEffect::new(
197 Some(GroupTarget::OutOfGroup),
198 CombatEffect::Knockback(self.static_data.explosion_knockback),
199 )
200 .with_requirement(CombatRequirement::AnyDamage),
201 ),
202 dodgeable: self.static_data.explosion_dodgeable,
203 }];
204
205 if let Some((power, color)) = self.static_data.destroy_terrain {
206 effects.push(RadiusEffect::TerrainDestruction(power, color.to_rgb()));
207 }
208
209 if let Some((radius, replacement_preset)) = self.static_data.replace_terrain {
210 effects.push(RadiusEffect::ReplaceTerrain(radius, replacement_preset));
211 }
212
213 output_events.emit_server(ExplosionEvent {
214 pos: explosion_pos,
215 explosion: Explosion {
216 effects,
217 radius: self.static_data.explosion_radius,
218 reagent: self.static_data.reagent,
219 min_falloff: self.static_data.min_falloff,
220 },
221 owner: Some(*data.uid),
222 });
223
224 let shockwave_poise = AttackEffect::new(
226 Some(GroupTarget::OutOfGroup),
227 CombatEffect::Poise(self.static_data.shockwave_poise),
228 )
229 .with_requirement(CombatRequirement::AnyDamage);
230 let shockwave_knockback = AttackEffect::new(
231 Some(GroupTarget::OutOfGroup),
232 CombatEffect::Knockback(self.static_data.shockwave_knockback),
233 )
234 .with_requirement(CombatRequirement::AnyDamage);
235 let mut shockwave_damage = AttackDamage::new(
236 Damage {
237 source: DamageSource::Shockwave,
238 kind: self.static_data.shockwave_damage_kind,
239 value: self.static_data.shockwave_damage,
240 },
241 Some(GroupTarget::OutOfGroup),
242 rand::random(),
243 );
244 if let Some(effect) = self.static_data.shockwave_damage_effect {
245 shockwave_damage = shockwave_damage.with_effect(effect);
246 }
247 let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
248 let shockwave_attack = Attack::default()
249 .with_damage(shockwave_damage)
250 .with_precision(precision_mult)
251 .with_effect(shockwave_poise)
252 .with_effect(shockwave_knockback)
253 .with_combo_increment();
254 let properties = shockwave::Properties {
255 angle: self.static_data.shockwave_angle,
256 vertical_angle: self.static_data.shockwave_vertical_angle,
257 speed: self.static_data.shockwave_speed,
258 duration: self.static_data.shockwave_duration,
259 attack: shockwave_attack,
260 dodgeable: self.static_data.shockwave_dodgeable,
261 owner: Some(*data.uid),
262 specifier: self.static_data.shockwave_specifier,
263 };
264 output_events.emit_server(ShockwaveEvent {
265 properties,
266 pos: *data.pos,
267 ori: *data.ori,
268 });
269
270 update.character = CharacterState::LeapExplosionShockwave(Data {
272 timer: Duration::default(),
273 stage_section: StageSection::Recover,
274 ..*self
275 });
276 }
277 },
278 StageSection::Recover => {
279 if self.timer < self.static_data.recover_duration {
280 update.character = CharacterState::LeapExplosionShockwave(Data {
282 timer: tick_attack_or_default(
283 data,
284 self.timer,
285 Some(data.stats.recovery_speed_modifier),
286 ),
287 ..*self
288 });
289 } else {
290 end_ability(data, &mut update);
292 }
293 },
294 _ => {
295 end_ability(data, &mut update);
297 },
298 }
299
300 handle_interrupts(data, &mut update, output_events);
302
303 update
304 }
305}