1use crate::{
2 combat,
3 comp::{
4 CharacterState, MeleeConstructor, StateUpdate, character_state::OutputEvents,
5 melee::CustomCombo,
6 },
7 event::LocalEvent,
8 outcome::Outcome,
9 states::{
10 behavior::{CharacterBehavior, JoinData},
11 utils::{StageSection, *},
12 },
13};
14use serde::{Deserialize, Serialize};
15use std::time::Duration;
16
17#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
18pub struct StaticData {
20 pub energy_drain: f32,
22 pub energy_cost: f32,
24 pub buildup_strike: Option<(Duration, MeleeConstructor)>,
27 pub charge_duration: Duration,
29 pub swing_duration: Duration,
31 pub hit_timing: f32,
33 pub recover_duration: Duration,
35 pub melee_constructor: MeleeConstructor,
37 pub ability_info: AbilityInfo,
39 pub specifier: Option<FrontendSpecifier>,
41 pub custom_combo: CustomCombo,
43 pub movement_modifier: MovementModifier,
45 pub ori_modifier: OrientationModifier,
47}
48
49#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
50pub struct Data {
51 pub static_data: StaticData,
54 pub stage_section: StageSection,
56 pub timer: Duration,
58 pub exhausted: bool,
60 pub charge_amount: f32,
62 pub movement_modifier: Option<f32>,
64 pub ori_modifier: Option<f32>,
66}
67
68impl Data {
69 pub fn charge_frac(&self) -> f32 {
71 if let StageSection::Charge = self.stage_section {
72 (self.timer.as_secs_f32() / self.static_data.charge_duration.as_secs_f32()).min(1.0)
73 } else {
74 0.0
75 }
76 }
77}
78
79impl CharacterBehavior for Data {
80 fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
81 let mut update = StateUpdate::from(data);
82
83 handle_orientation(data, &mut update, self.ori_modifier.unwrap_or(1.0), None);
84 handle_move(data, &mut update, self.movement_modifier.unwrap_or(0.7));
85 handle_jump(data, output_events, &mut update, 1.0);
86
87 match self.stage_section {
88 StageSection::Buildup => {
89 if let Some((buildup, strike)) = &self.static_data.buildup_strike {
90 if self.timer < *buildup {
91 if let CharacterState::ChargedMelee(c) = &mut update.character {
92 c.timer = tick_attack_or_default(data, self.timer, None);
93 }
94 } else {
95 let precision_mult =
96 combat::compute_precision_mult(data.inventory, data.msm);
97 let tool_stats = get_tool_stats(data, self.static_data.ability_info);
98 data.updater.insert(
99 data.entity,
100 strike.clone().create_melee(
101 precision_mult,
102 tool_stats,
103 self.static_data.ability_info,
104 ),
105 );
106
107 if let CharacterState::ChargedMelee(c) = &mut update.character {
108 c.stage_section = StageSection::Charge;
109 c.timer = Duration::default();
110 }
111 }
112 } else if let CharacterState::ChargedMelee(c) = &mut update.character {
113 c.stage_section = StageSection::Charge;
114 c.timer = Duration::default();
115 }
116 },
117 StageSection::Charge => {
118 if input_is_pressed(data, self.static_data.ability_info.input)
119 && update.energy.current() >= self.static_data.energy_cost
120 && self.timer < self.static_data.charge_duration
121 {
122 let charge = (self.timer.as_secs_f32()
123 / self.static_data.charge_duration.as_secs_f32())
124 .min(1.0);
125
126 if let CharacterState::ChargedMelee(c) = &mut update.character {
128 c.timer = tick_attack_or_default(data, self.timer, None);
129 c.charge_amount = charge;
130 }
131
132 update
134 .energy
135 .change_by(-self.static_data.energy_drain * data.dt.0);
136 } else if input_is_pressed(data, self.static_data.ability_info.input)
137 && update.energy.current() >= self.static_data.energy_cost
138 {
139 if let CharacterState::ChargedMelee(c) = &mut update.character {
141 c.timer = tick_attack_or_default(data, self.timer, None);
142 }
143
144 update
146 .energy
147 .change_by(-self.static_data.energy_drain * data.dt.0 / 5.0);
148 } else {
149 if let CharacterState::ChargedMelee(c) = &mut update.character {
151 c.stage_section = StageSection::Action;
152 c.timer = Duration::default();
153 c.movement_modifier = self.static_data.movement_modifier.swing;
154 c.ori_modifier = self.static_data.ori_modifier.swing;
155 }
156 }
157 },
158 StageSection::Action => {
159 if self.timer.as_millis() as f32
160 > self.static_data.hit_timing
161 * self.static_data.swing_duration.as_millis() as f32
162 && !self.exhausted
163 {
164 if let CharacterState::ChargedMelee(c) = &mut update.character {
166 c.timer = tick_attack_or_default(data, self.timer, None);
167 c.exhausted = true;
168 }
169
170 let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
171 let tool_stats = get_tool_stats(data, self.static_data.ability_info);
172 let custom_combo = CustomCombo {
173 base: self
174 .static_data
175 .custom_combo
176 .base
177 .map(|b| (self.charge_amount * b as f32).round() as i32),
178 conditional: self
179 .static_data
180 .custom_combo
181 .conditional
182 .map(|c| ((self.charge_amount * c.0 as f32).round() as i32, c.1)),
183 };
184
185 data.updater.insert(
186 data.entity,
187 self.static_data
188 .melee_constructor
189 .clone()
190 .custom_combo(custom_combo)
191 .handle_scaling(self.charge_amount)
192 .create_melee(
193 precision_mult,
194 tool_stats,
195 self.static_data.ability_info,
196 ),
197 );
198
199 if let Some(FrontendSpecifier::GroundCleave) = self.static_data.specifier {
200 output_events.emit_local(LocalEvent::CreateOutcome(Outcome::GroundSlam {
202 pos: data.pos.0
203 + *data.ori.look_dir()
204 * (data.body.max_radius()
205 + self.static_data.melee_constructor.range),
206 }));
207 }
208 } else if self.timer < self.static_data.swing_duration {
209 if let CharacterState::ChargedMelee(c) = &mut update.character {
211 c.timer = tick_attack_or_default(data, self.timer, None);
212 }
213 } else {
214 if let CharacterState::ChargedMelee(c) = &mut update.character {
216 c.stage_section = StageSection::Recover;
217 c.timer = Duration::default();
218 c.movement_modifier = self.static_data.movement_modifier.recover;
219 c.ori_modifier = self.static_data.ori_modifier.recover;
220 }
221 }
222 },
223 StageSection::Recover => {
224 if self.timer < self.static_data.recover_duration {
225 if let CharacterState::ChargedMelee(c) = &mut update.character {
227 c.timer = tick_attack_or_default(
228 data,
229 self.timer,
230 Some(data.stats.recovery_speed_modifier),
231 );
232 }
233 } else {
234 end_melee_ability(data, &mut update);
236 }
237 },
238 _ => {
239 end_melee_ability(data, &mut update);
241 },
242 }
243
244 handle_interrupts(data, &mut update, output_events);
246
247 update
248 }
249}
250
251#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
253pub enum FrontendSpecifier {
254 GroundCleave,
255}