1use crate::{
2 Damage, DamageKind, 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::new(Some(self.static_data.ability_info))
121 .with_damage(AttackDamage::new(
122 Damage {
123 kind: DamageKind::Crushing,
124 value: self.static_data.damage,
125 },
126 Some(GroupTarget::OutOfGroup),
127 rand::random(),
128 ))
129 .with_effect(
130 AttackEffect::new(
131 Some(GroupTarget::OutOfGroup),
132 CombatEffect::Poise(self.static_data.poise),
133 )
134 .with_requirement(CombatRequirement::AnyDamage),
135 )
136 .with_effect(
137 AttackEffect::new(
138 Some(GroupTarget::OutOfGroup),
139 CombatEffect::Knockback(self.static_data.knockback),
140 )
141 .with_requirement(CombatRequirement::AnyDamage),
142 ),
143 dodgeable: self.static_data.dodgeable,
144 }];
145
146 if let Some((power, color)) = self.static_data.destroy_terrain {
147 effects.push(RadiusEffect::TerrainDestruction(power, color.to_rgb()));
148 }
149
150 if let Some((radius, replacement_preset)) = self.static_data.replace_terrain {
151 effects.push(RadiusEffect::ReplaceTerrain(radius, replacement_preset));
152 }
153
154 output_events.emit_server(ExplosionEvent {
155 pos,
156 explosion: Explosion {
157 effects,
158 radius: self.static_data.radius,
159 reagent: self.static_data.reagent,
160 min_falloff: self.static_data.min_falloff,
161 },
162 owner: Some(*data.uid),
163 });
164
165 update.character = CharacterState::Explosion(Data {
167 timer: Duration::default(),
168 stage_section: StageSection::Recover,
169 movement_modifier: self.static_data.movement_modifier.recover,
170 ori_modifier: self.static_data.ori_modifier.recover,
171 ..*self
172 });
173 }
174 },
175 StageSection::Recover => {
176 if self.timer < self.static_data.recover_duration {
177 update.character = CharacterState::Explosion(Data {
179 timer: tick_attack_or_default(
180 data,
181 self.timer,
182 Some(data.stats.recovery_speed_modifier),
183 ),
184 movement_modifier: self.static_data.movement_modifier.recover,
185 ori_modifier: self.static_data.ori_modifier.recover,
186 ..*self
187 });
188 } else {
189 if input_is_pressed(data, self.static_data.ability_info.input) {
191 reset_state(self, data, output_events, &mut update);
192 } else {
193 end_ability(data, &mut update);
194 }
195 }
196 },
197 _ => {
198 end_ability(data, &mut update);
200 },
201 }
202
203 handle_interrupts(data, &mut update, output_events);
205
206 update
207 }
208}
209
210fn reset_state(
211 data: &Data,
212 join: &JoinData,
213 output_events: &mut OutputEvents,
214 update: &mut StateUpdate,
215) {
216 handle_input(
217 join,
218 output_events,
219 update,
220 data.static_data.ability_info.input,
221 );
222}