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}
46
47#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
48pub struct Data {
49 pub static_data: StaticData,
52 pub stage_section: StageSection,
54 pub timer: Duration,
56 pub exhausted: bool,
58 pub charge_amount: f32,
60}
61
62impl Data {
63 pub fn charge_frac(&self) -> f32 {
65 if let StageSection::Charge = self.stage_section {
66 (self.timer.as_secs_f32() / self.static_data.charge_duration.as_secs_f32()).min(1.0)
67 } else {
68 0.0
69 }
70 }
71}
72
73impl CharacterBehavior for Data {
74 fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
75 let mut update = StateUpdate::from(data);
76
77 handle_orientation(data, &mut update, 1.0, None);
78 handle_move(data, &mut update, 0.7);
79 handle_jump(data, output_events, &mut update, 1.0);
80
81 match self.stage_section {
82 StageSection::Buildup => {
83 if let Some((buildup, strike)) = self.static_data.buildup_strike {
84 if self.timer < buildup {
85 if let CharacterState::ChargedMelee(c) = &mut update.character {
86 c.timer = tick_attack_or_default(data, self.timer, None);
87 }
88 } else {
89 let precision_mult =
90 combat::compute_precision_mult(data.inventory, data.msm);
91 let tool_stats = get_tool_stats(data, self.static_data.ability_info);
92 data.updater
93 .insert(data.entity, strike.create_melee(precision_mult, tool_stats));
94
95 if let CharacterState::ChargedMelee(c) = &mut update.character {
96 c.stage_section = StageSection::Charge;
97 c.timer = Duration::default();
98 }
99 }
100 } else if let CharacterState::ChargedMelee(c) = &mut update.character {
101 c.stage_section = StageSection::Charge;
102 c.timer = Duration::default();
103 }
104 },
105 StageSection::Charge => {
106 if input_is_pressed(data, self.static_data.ability_info.input)
107 && update.energy.current() >= self.static_data.energy_cost
108 && self.timer < self.static_data.charge_duration
109 {
110 let charge = (self.timer.as_secs_f32()
111 / self.static_data.charge_duration.as_secs_f32())
112 .min(1.0);
113
114 update.character = CharacterState::ChargedMelee(Data {
116 timer: tick_attack_or_default(data, self.timer, None),
117 charge_amount: charge,
118 ..*self
119 });
120
121 update
123 .energy
124 .change_by(-self.static_data.energy_drain * data.dt.0);
125 } else if input_is_pressed(data, self.static_data.ability_info.input)
126 && update.energy.current() >= self.static_data.energy_cost
127 {
128 update.character = CharacterState::ChargedMelee(Data {
130 timer: tick_attack_or_default(data, self.timer, None),
131 ..*self
132 });
133
134 update
136 .energy
137 .change_by(-self.static_data.energy_drain * data.dt.0 / 5.0);
138 } else {
139 update.character = CharacterState::ChargedMelee(Data {
141 stage_section: StageSection::Action,
142 timer: Duration::default(),
143 ..*self
144 });
145 }
146 },
147 StageSection::Action => {
148 if self.timer.as_millis() as f32
149 > self.static_data.hit_timing
150 * self.static_data.swing_duration.as_millis() as f32
151 && !self.exhausted
152 {
153 update.character = CharacterState::ChargedMelee(Data {
155 timer: tick_attack_or_default(data, self.timer, None),
156 exhausted: true,
157 ..*self
158 });
159
160 let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
161 let tool_stats = get_tool_stats(data, self.static_data.ability_info);
162 let custom_combo = CustomCombo {
163 base: self
164 .static_data
165 .custom_combo
166 .base
167 .map(|b| (self.charge_amount * b as f32).round() as i32),
168 conditional: self
169 .static_data
170 .custom_combo
171 .conditional
172 .map(|c| ((self.charge_amount * c.0 as f32).round() as i32, c.1)),
173 };
174
175 data.updater.insert(
176 data.entity,
177 self.static_data
178 .melee_constructor
179 .custom_combo(custom_combo)
180 .handle_scaling(self.charge_amount)
181 .create_melee(precision_mult, tool_stats),
182 );
183
184 if let Some(FrontendSpecifier::GroundCleave) = self.static_data.specifier {
185 output_events.emit_local(LocalEvent::CreateOutcome(Outcome::GroundSlam {
187 pos: data.pos.0
188 + *data.ori.look_dir()
189 * (data.body.max_radius()
190 + self.static_data.melee_constructor.range),
191 }));
192 }
193 } else if self.timer < self.static_data.swing_duration {
194 update.character = CharacterState::ChargedMelee(Data {
196 timer: tick_attack_or_default(data, self.timer, None),
197 ..*self
198 });
199 } else {
200 update.character = CharacterState::ChargedMelee(Data {
202 stage_section: StageSection::Recover,
203 timer: Duration::default(),
204 ..*self
205 });
206 }
207 },
208 StageSection::Recover => {
209 if self.timer < self.static_data.recover_duration {
210 update.character = CharacterState::ChargedMelee(Data {
212 timer: tick_attack_or_default(
213 data,
214 self.timer,
215 Some(data.stats.recovery_speed_modifier),
216 ),
217 ..*self
218 });
219 } else {
220 end_melee_ability(data, &mut update);
222 }
223 },
224 _ => {
225 end_melee_ability(data, &mut update);
227 },
228 }
229
230 handle_interrupts(data, &mut update, output_events);
232
233 update
234 }
235}
236
237#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
239pub enum FrontendSpecifier {
240 GroundCleave,
241}