1use crate::{
2 Explosion, RadiusEffect, combat,
3 combat::{Attack, AttackDamage, Damage, DamageKind::Crushing, DamageSource, GroupTarget},
4 comp::{
5 CharacterState, MeleeConstructor, StateUpdate, character_state::OutputEvents,
6 item::Reagent, melee::CustomCombo, tool::Stats,
7 },
8 event::{ExplosionEvent, LocalEvent},
9 outcome::Outcome,
10 states::{
11 behavior::{CharacterBehavior, JoinData},
12 combo_melee2,
13 utils::*,
14 },
15};
16use serde::{Deserialize, Serialize};
17use std::time::Duration;
18use vek::*;
19
20#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
21#[serde(deny_unknown_fields)]
22pub struct Strike<T> {
23 pub melee_constructor: MeleeConstructor,
25 pub buildup_duration: T,
27 pub swing_duration: T,
30 pub hit_timing: f32,
32 pub recover_duration: T,
34 #[serde(default)]
36 pub movement: StrikeMovement,
37 #[serde(default)]
39 pub movement_modifier: MovementModifier,
40 #[serde(default)]
42 pub ori_modifier: OrientationModifier,
43 #[serde(default)]
44 pub custom_combo: CustomCombo,
45}
46
47impl Strike<f32> {
48 pub fn to_duration(self) -> Strike<Duration> {
49 Strike::<Duration> {
50 melee_constructor: self.melee_constructor,
51 buildup_duration: Duration::from_secs_f32(self.buildup_duration),
52 swing_duration: Duration::from_secs_f32(self.swing_duration),
53 hit_timing: self.hit_timing,
54 recover_duration: Duration::from_secs_f32(self.recover_duration),
55 movement: self.movement,
56 movement_modifier: self.movement_modifier,
57 ori_modifier: self.ori_modifier,
58 custom_combo: self.custom_combo,
59 }
60 }
61
62 #[must_use]
63 pub fn adjusted_by_stats(self, stats: Stats) -> Self {
64 Self {
65 melee_constructor: self.melee_constructor.adjusted_by_stats(stats),
66 buildup_duration: self.buildup_duration / stats.speed,
67 swing_duration: self.swing_duration / stats.speed,
68 hit_timing: self.hit_timing,
69 recover_duration: self.recover_duration / stats.speed,
70 movement: self.movement,
71 movement_modifier: self.movement_modifier,
72 ori_modifier: self.ori_modifier,
73 custom_combo: self.custom_combo,
74 }
75 }
76}
77
78#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Default)]
79#[serde(deny_unknown_fields)]
80pub struct StrikeMovement {
81 pub buildup: Option<ForcedMovement>,
82 pub swing: Option<ForcedMovement>,
83 pub recover: Option<ForcedMovement>,
84}
85
86#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
89pub struct StaticData {
91 pub strikes: Vec<Strike<Duration>>,
93 pub energy_cost_per_strike: f32,
95 pub specifier: Option<combo_melee2::FrontendSpecifier>,
97 pub auto_progress: bool,
100 pub ability_info: AbilityInfo,
101}
102#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
105pub struct Data {
106 pub static_data: StaticData,
109 pub exhausted: bool,
111 pub start_next_strike: bool,
113 pub timer: Duration,
115 pub stage_section: StageSection,
118 pub completed_strikes: usize,
121 pub movement_modifier: Option<f32>,
123 pub ori_modifier: Option<f32>,
125}
126
127impl CharacterBehavior for Data {
128 fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
129 let mut update = StateUpdate::from(data);
130
131 handle_orientation(data, &mut update, self.ori_modifier.unwrap_or(1.0), None);
132 handle_move(data, &mut update, self.movement_modifier.unwrap_or(0.7));
133 handle_interrupts(data, &mut update, output_events);
134
135 let strike_data = self.strike_data();
136
137 match self.stage_section {
138 StageSection::Buildup => {
139 if let Some(movement) = strike_data.movement.buildup {
140 handle_forced_movement(data, &mut update, movement);
141 }
142 if self.timer < strike_data.buildup_duration {
143 if let CharacterState::ComboMelee2(c) = &mut update.character {
145 c.timer = tick_attack_or_default(data, self.timer, None);
146 }
147 } else {
148 if let CharacterState::ComboMelee2(c) = &mut update.character {
150 c.timer = Duration::default();
151 c.stage_section = StageSection::Action;
152 c.movement_modifier = strike_data.movement_modifier.swing;
153 c.ori_modifier = strike_data.ori_modifier.swing;
154 }
155 }
156 if let Some(FrontendSpecifier::ClayGolemDash) = self.static_data.specifier {
157 output_events.emit_local(LocalEvent::CreateOutcome(Outcome::ClayGolemDash {
159 pos: data.pos.0,
160 }));
161 }
162 },
163 StageSection::Action => {
164 if let Some(movement) = strike_data.movement.swing {
165 handle_forced_movement(data, &mut update, movement);
166 }
167 if input_is_pressed(data, self.static_data.ability_info.input)
168 || self.static_data.auto_progress
169 {
170 if let CharacterState::ComboMelee2(c) = &mut update.character {
171 c.start_next_strike =
174 (c.completed_strikes + 1) < c.static_data.strikes.len();
175 }
176 }
177 if self.timer.as_secs_f32()
178 > strike_data.hit_timing * strike_data.swing_duration.as_secs_f32()
179 && !self.exhausted
180 {
181 if let CharacterState::ComboMelee2(c) = &mut update.character {
182 c.timer = tick_attack_or_default(data, self.timer, None);
183 c.exhausted = true;
184 }
185
186 let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
187 let tool_stats = get_tool_stats(data, self.static_data.ability_info);
188
189 data.updater.insert(
190 data.entity,
191 strike_data
192 .melee_constructor
193 .custom_combo(strike_data.custom_combo)
194 .create_melee(precision_mult, tool_stats),
195 );
196 } else if self.timer < strike_data.swing_duration {
197 if let CharacterState::ComboMelee2(c) = &mut update.character {
199 c.timer = tick_attack_or_default(data, self.timer, None);
200 }
201 if self.static_data.specifier == Some(FrontendSpecifier::IronGolemFist) {
202 let damage = AttackDamage::new(
203 Damage {
204 source: DamageSource::Explosion,
205 kind: Crushing,
206 value: 10.0,
207 },
208 Some(GroupTarget::OutOfGroup),
209 rand::random(),
210 );
211 let attack = Attack::default().with_damage(damage);
212 let explosion = Explosion {
213 effects: vec![RadiusEffect::Attack(attack)],
214 radius: data.body.max_radius() * 10.0,
215 reagent: Some(Reagent::Yellow),
216 min_falloff: 0.5,
217 };
218 let pos =
219 data.pos.0 + (*data.ori.look_dir() * (data.body.max_radius() * 3.0));
220 let explosition =
221 Vec3::new(pos.x, pos.y, pos.z + (data.body.height() / 2.0));
222 output_events.emit_server(ExplosionEvent {
223 pos: explosition,
224 explosion,
225 owner: Some(*data.uid),
226 });
227 }
228 } else if self.start_next_strike {
229 if let CharacterState::ComboMelee2(c) = &mut update.character {
230 c.completed_strikes += 1;
231 }
232 next_strike(data, &mut update, strike_data);
233 } else {
234 if let CharacterState::ComboMelee2(c) = &mut update.character {
236 c.timer = Duration::default();
237 c.stage_section = StageSection::Recover;
238 c.movement_modifier = strike_data.movement_modifier.recover;
239 c.ori_modifier = strike_data.ori_modifier.recover;
240 }
241 }
242 },
243 StageSection::Recover => {
244 if let Some(movement) = strike_data.movement.recover {
245 handle_forced_movement(data, &mut update, movement);
246 }
247 if self.timer < strike_data.recover_duration {
248 if let CharacterState::ComboMelee2(c) = &mut update.character {
250 c.timer = tick_attack_or_default(
251 data,
252 self.timer,
253 Some(data.stats.recovery_speed_modifier),
254 );
255 }
256 } else {
257 end_melee_ability(data, &mut update);
259 }
260 },
261 _ => {
262 end_melee_ability(data, &mut update);
264 },
265 }
266
267 update
268 }
269}
270
271impl Data {
272 pub fn strike_data(&self) -> &Strike<Duration> {
273 &self.static_data.strikes[self.completed_strikes % self.static_data.strikes.len()]
274 }
275}
276
277fn next_strike(data: &JoinData, update: &mut StateUpdate, strike_data: &Strike<Duration>) {
278 let revert_to_wield = if let CharacterState::ComboMelee2(c) = &mut update.character {
279 if update
280 .energy
281 .try_change_by(-c.static_data.energy_cost_per_strike)
282 .is_ok()
283 {
284 c.exhausted = false;
285 c.start_next_strike = false;
286 c.timer = Duration::default();
287 c.stage_section = StageSection::Buildup;
288 c.movement_modifier = strike_data.movement_modifier.buildup;
289 c.ori_modifier = strike_data.ori_modifier.buildup;
290 false
291 } else {
292 true
293 }
294 } else {
295 false
296 };
297 if revert_to_wield {
298 end_melee_ability(data, update)
299 }
300}
301
302#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
303pub enum FrontendSpecifier {
304 ClayGolemDash,
305 IronGolemFist,
306}