1use crate::{
2 Damage, DamageKind, DamageSource, Explosion, GroupTarget, Knockback, RadiusEffect,
3 combat::{Attack, AttackDamage, AttackEffect, CombatEffect, CombatRequirement},
4 comp::{
5 CharacterState, StateUpdate, ability::Dodgeable, character_state::OutputEvents,
6 item::Reagent,
7 },
8 event::ExplosionEvent,
9 explosion::{ColorPreset, TerrainReplacementPreset},
10 states::{
11 behavior::{CharacterBehavior, JoinData},
12 utils::*,
13 },
14};
15use serde::{Deserialize, Serialize};
16use std::time::Duration;
17use vek::Vec3;
18
19#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
21pub struct StaticData {
22 pub buildup_duration: Duration,
24 pub action_duration: Duration,
26 pub recover_duration: Duration,
28 pub damage: f32,
30 pub poise: f32,
32 pub knockback: Knockback,
34 pub radius: f32,
36 pub min_falloff: f32,
38 #[serde(default)]
40 pub dodgeable: Dodgeable,
41 pub destroy_terrain: Option<(f32, ColorPreset)>,
43 pub replace_terrain: Option<(f32, TerrainReplacementPreset)>,
45 #[serde(default)]
48 pub eye_height: bool,
49 pub reagent: Option<Reagent>,
51 #[serde(default)]
53 pub movement_modifier: MovementModifier,
54 #[serde(default)]
56 pub ori_modifier: OrientationModifier,
57 pub ability_info: AbilityInfo,
59}
60
61#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
62pub struct Data {
63 pub static_data: StaticData,
66 pub timer: Duration,
68 pub stage_section: StageSection,
70 pub movement_modifier: Option<f32>,
72 pub ori_modifier: Option<f32>,
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.ori_modifier.unwrap_or(1.0), None);
81 handle_move(data, &mut update, self.movement_modifier.unwrap_or(0.7));
82 handle_jump(data, output_events, &mut update, 1.0);
83
84 match self.stage_section {
85 StageSection::Buildup => {
86 if self.timer < self.static_data.buildup_duration {
87 update.character = CharacterState::Explosion(Data {
89 timer: tick_attack_or_default(data, self.timer, None),
90 ..*self
91 });
92 } else {
93 update.character = CharacterState::Explosion(Data {
95 timer: Duration::default(),
96 stage_section: StageSection::Action,
97 movement_modifier: self.static_data.movement_modifier.swing,
98 ori_modifier: self.static_data.ori_modifier.swing,
99 ..*self
100 });
101 }
102 },
103 StageSection::Action => {
104 if self.timer < self.static_data.action_duration {
105 update.character = CharacterState::Explosion(Data {
107 timer: tick_attack_or_default(data, self.timer, None),
108 ..*self
109 });
110 } else {
111 let pos = if self.static_data.eye_height {
113 data.pos.0
114 + Vec3::unit_z() * data.body.eye_height(data.scale.map_or(1.0, |s| s.0))
115 } else {
116 data.pos.0
117 };
118
119 let mut effects = vec![RadiusEffect::Attack {
120 attack: Attack::default()
121 .with_damage(AttackDamage::new(
122 Damage {
123 source: DamageSource::Explosion,
124 kind: DamageKind::Crushing,
125 value: self.static_data.damage,
126 },
127 Some(GroupTarget::OutOfGroup),
128 rand::random(),
129 ))
130 .with_effect(
131 AttackEffect::new(
132 Some(GroupTarget::OutOfGroup),
133 CombatEffect::Poise(self.static_data.poise),
134 )
135 .with_requirement(CombatRequirement::AnyDamage),
136 )
137 .with_effect(
138 AttackEffect::new(
139 Some(GroupTarget::OutOfGroup),
140 CombatEffect::Knockback(self.static_data.knockback),
141 )
142 .with_requirement(CombatRequirement::AnyDamage),
143 ),
144 dodgeable: self.static_data.dodgeable,
145 }];
146
147 if let Some((power, color)) = self.static_data.destroy_terrain {
148 effects.push(RadiusEffect::TerrainDestruction(power, color.to_rgb()));
149 }
150
151 if let Some((radius, replacement_preset)) = self.static_data.replace_terrain {
152 effects.push(RadiusEffect::ReplaceTerrain(radius, replacement_preset));
153 }
154
155 output_events.emit_server(ExplosionEvent {
156 pos,
157 explosion: Explosion {
158 effects,
159 radius: self.static_data.radius,
160 reagent: self.static_data.reagent,
161 min_falloff: self.static_data.min_falloff,
162 },
163 owner: Some(*data.uid),
164 });
165
166 update.character = CharacterState::Explosion(Data {
168 timer: Duration::default(),
169 stage_section: StageSection::Recover,
170 movement_modifier: self.static_data.movement_modifier.recover,
171 ori_modifier: self.static_data.ori_modifier.recover,
172 ..*self
173 });
174 }
175 },
176 StageSection::Recover => {
177 if self.timer < self.static_data.recover_duration {
178 update.character = CharacterState::Explosion(Data {
180 timer: tick_attack_or_default(
181 data,
182 self.timer,
183 Some(data.stats.recovery_speed_modifier),
184 ),
185 movement_modifier: self.static_data.movement_modifier.recover,
186 ori_modifier: self.static_data.ori_modifier.recover,
187 ..*self
188 });
189 } else {
190 if input_is_pressed(data, self.static_data.ability_info.input) {
192 reset_state(self, data, output_events, &mut update);
193 } else {
194 end_ability(data, &mut update);
195 }
196 }
197 },
198 _ => {
199 end_ability(data, &mut update);
201 },
202 }
203
204 handle_interrupts(data, &mut update, output_events);
206
207 update
208 }
209}
210
211fn reset_state(
212 data: &Data,
213 join: &JoinData,
214 output_events: &mut OutputEvents,
215 update: &mut StateUpdate,
216) {
217 handle_input(
218 join,
219 output_events,
220 update,
221 data.static_data.ability_info.input,
222 );
223}