1use crate::{
2 combat::{self, CombatEffect},
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, Copy, 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 damage_effect: Option<CombatEffect>,
43 pub custom_combo: CustomCombo,
45 pub movement_modifier: MovementModifier,
47 pub ori_modifier: OrientationModifier,
49}
50
51#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
52pub struct Data {
53 pub static_data: StaticData,
56 pub stage_section: StageSection,
58 pub timer: Duration,
60 pub exhausted: bool,
62 pub charge_amount: f32,
64 pub movement_modifier: Option<f32>,
66 pub ori_modifier: Option<f32>,
68}
69
70impl Data {
71 pub fn charge_frac(&self) -> f32 {
73 if let StageSection::Charge = self.stage_section {
74 (self.timer.as_secs_f32() / self.static_data.charge_duration.as_secs_f32()).min(1.0)
75 } else {
76 0.0
77 }
78 }
79}
80
81impl CharacterBehavior for Data {
82 fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
83 let mut update = StateUpdate::from(data);
84
85 handle_orientation(data, &mut update, self.ori_modifier.unwrap_or(1.0), None);
86 handle_move(data, &mut update, self.movement_modifier.unwrap_or(0.7));
87 handle_jump(data, output_events, &mut update, 1.0);
88
89 match self.stage_section {
90 StageSection::Buildup => {
91 if let Some((buildup, strike)) = self.static_data.buildup_strike {
92 if self.timer < buildup {
93 if let CharacterState::ChargedMelee(c) = &mut update.character {
94 c.timer = tick_attack_or_default(data, self.timer, None);
95 }
96 } else {
97 let precision_mult =
98 combat::compute_precision_mult(data.inventory, data.msm);
99 let tool_stats = get_tool_stats(data, self.static_data.ability_info);
100 data.updater
101 .insert(data.entity, strike.create_melee(precision_mult, tool_stats));
102
103 if let CharacterState::ChargedMelee(c) = &mut update.character {
104 c.stage_section = StageSection::Charge;
105 c.timer = Duration::default();
106 }
107 }
108 } else if let CharacterState::ChargedMelee(c) = &mut update.character {
109 c.stage_section = StageSection::Charge;
110 c.timer = Duration::default();
111 }
112 },
113 StageSection::Charge => {
114 if input_is_pressed(data, self.static_data.ability_info.input)
115 && update.energy.current() >= self.static_data.energy_cost
116 && self.timer < self.static_data.charge_duration
117 {
118 let charge = (self.timer.as_secs_f32()
119 / self.static_data.charge_duration.as_secs_f32())
120 .min(1.0);
121
122 update.character = CharacterState::ChargedMelee(Data {
124 timer: tick_attack_or_default(data, self.timer, None),
125 charge_amount: charge,
126 ..*self
127 });
128
129 update
131 .energy
132 .change_by(-self.static_data.energy_drain * data.dt.0);
133 } else if input_is_pressed(data, self.static_data.ability_info.input)
134 && update.energy.current() >= self.static_data.energy_cost
135 {
136 update.character = CharacterState::ChargedMelee(Data {
138 timer: tick_attack_or_default(data, self.timer, None),
139 ..*self
140 });
141
142 update
144 .energy
145 .change_by(-self.static_data.energy_drain * data.dt.0 / 5.0);
146 } else {
147 update.character = CharacterState::ChargedMelee(Data {
149 stage_section: StageSection::Action,
150 timer: Duration::default(),
151 movement_modifier: self.static_data.movement_modifier.swing,
152 ori_modifier: self.static_data.ori_modifier.swing,
153 ..*self
154 });
155 }
156 },
157 StageSection::Action => {
158 if self.timer.as_millis() as f32
159 > self.static_data.hit_timing
160 * self.static_data.swing_duration.as_millis() as f32
161 && !self.exhausted
162 {
163 update.character = CharacterState::ChargedMelee(Data {
165 timer: tick_attack_or_default(data, self.timer, None),
166 exhausted: true,
167 ..*self
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 .custom_combo(custom_combo)
190 .handle_scaling(self.charge_amount)
191 .create_melee(precision_mult, tool_stats),
192 );
193
194 if let Some(FrontendSpecifier::GroundCleave) = self.static_data.specifier {
195 output_events.emit_local(LocalEvent::CreateOutcome(Outcome::GroundSlam {
197 pos: data.pos.0
198 + *data.ori.look_dir()
199 * (data.body.max_radius()
200 + self.static_data.melee_constructor.range),
201 }));
202 }
203 } else if self.timer < self.static_data.swing_duration {
204 update.character = CharacterState::ChargedMelee(Data {
206 timer: tick_attack_or_default(data, self.timer, None),
207 ..*self
208 });
209 } else {
210 update.character = CharacterState::ChargedMelee(Data {
212 stage_section: StageSection::Recover,
213 timer: Duration::default(),
214 movement_modifier: self.static_data.movement_modifier.recover,
215 ori_modifier: self.static_data.ori_modifier.recover,
216 ..*self
217 });
218 }
219 },
220 StageSection::Recover => {
221 if self.timer < self.static_data.recover_duration {
222 update.character = CharacterState::ChargedMelee(Data {
224 timer: tick_attack_or_default(
225 data,
226 self.timer,
227 Some(data.stats.recovery_speed_modifier),
228 ),
229 ..*self
230 });
231 } else {
232 end_melee_ability(data, &mut update);
234 }
235 },
236 _ => {
237 end_melee_ability(data, &mut update);
239 },
240 }
241
242 handle_interrupts(data, &mut update, output_events);
244
245 update
246 }
247}
248
249#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
251pub enum FrontendSpecifier {
252 GroundCleave,
253}