1use crate::{
2 Explosion, RadiusEffect,
3 combat::{self, Attack, AttackDamage, Damage, DamageKind::Crushing, GroupTarget},
4 comp::{
5 CharacterState, MeleeConstructor, StateUpdate, ability::Dodgeable,
6 character_state::OutputEvents, 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, 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.action;
153 c.ori_modifier = strike_data.ori_modifier.action;
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 && let CharacterState::ComboMelee2(c) = &mut update.character
170 {
171 c.start_next_strike = (c.completed_strikes + 1) < c.static_data.strikes.len();
174 }
175 if self.timer.as_secs_f32()
176 > strike_data.hit_timing * strike_data.swing_duration.as_secs_f32()
177 && !self.exhausted
178 {
179 if let CharacterState::ComboMelee2(c) = &mut update.character {
180 c.timer = tick_attack_or_default(data, self.timer, None);
181 c.exhausted = true;
182 }
183
184 let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
185
186 data.updater.insert(
187 data.entity,
188 strike_data
189 .melee_constructor
190 .clone()
191 .custom_combo(strike_data.custom_combo)
192 .create_melee(precision_mult, self.static_data.ability_info),
193 );
194 } else if self.timer < strike_data.swing_duration {
195 if let CharacterState::ComboMelee2(c) = &mut update.character {
197 c.timer = tick_attack_or_default(data, self.timer, None);
198 }
199 if self.static_data.specifier == Some(FrontendSpecifier::IronGolemFist) {
201 let damage = AttackDamage::new(
202 Damage {
203 kind: Crushing,
204 value: 10.0,
205 },
206 Some(GroupTarget::OutOfGroup),
207 rand::random(),
208 );
209 let attack =
210 Attack::new(Some(self.static_data.ability_info)).with_damage(damage);
211 let explosion = Explosion {
212 effects: vec![RadiusEffect::Attack {
213 attack,
214 dodgeable: Dodgeable::Roll,
215 }],
216 radius: data.body.max_radius() * 10.0,
217 reagent: Some(Reagent::Yellow),
218 min_falloff: 0.5,
219 };
220 let pos =
221 data.pos.0 + (*data.ori.look_dir() * (data.body.max_radius() * 3.0));
222 let explosition =
223 Vec3::new(pos.x, pos.y, pos.z + (data.body.height() / 2.0));
224 output_events.emit_server(ExplosionEvent {
225 pos: explosition,
226 explosion,
227 owner: Some(*data.uid),
228 });
229 }
230 } else if self.start_next_strike {
231 if let CharacterState::ComboMelee2(c) = &mut update.character {
232 c.completed_strikes += 1;
233 }
234 next_strike(data, &mut update, strike_data);
235 } else {
236 if let CharacterState::ComboMelee2(c) = &mut update.character {
238 c.timer = Duration::default();
239 c.stage_section = StageSection::Recover;
240 c.movement_modifier = strike_data.movement_modifier.recover;
241 c.ori_modifier = strike_data.ori_modifier.recover;
242 }
243 }
244 },
245 StageSection::Recover => {
246 if let Some(movement) = strike_data.movement.recover {
247 handle_forced_movement(data, &mut update, movement);
248 }
249 if self.timer < strike_data.recover_duration {
250 if let CharacterState::ComboMelee2(c) = &mut update.character {
252 c.timer = tick_attack_or_default(data, self.timer, None);
253 }
254 } else {
255 end_melee_ability(data, &mut update);
257 }
258 },
259 _ => {
260 end_melee_ability(data, &mut update);
262 },
263 }
264
265 update
266 }
267}
268
269impl Data {
270 pub fn strike_data(&self) -> &Strike<Duration> {
271 &self.static_data.strikes[self.completed_strikes % self.static_data.strikes.len()]
272 }
273}
274
275fn next_strike(data: &JoinData, update: &mut StateUpdate, strike_data: &Strike<Duration>) {
276 let revert_to_wield = if let CharacterState::ComboMelee2(c) = &mut update.character {
277 if update
278 .energy
279 .try_change_by(-c.static_data.energy_cost_per_strike)
280 .is_ok()
281 {
282 c.exhausted = false;
283 c.start_next_strike = false;
284 c.timer = Duration::default();
285 c.stage_section = StageSection::Buildup;
286 c.movement_modifier = strike_data.movement_modifier.buildup;
287 c.ori_modifier = strike_data.ori_modifier.buildup;
288 false
289 } else {
290 true
291 }
292 } else {
293 false
294 };
295 if revert_to_wield {
296 end_melee_ability(data, update)
297 }
298}
299
300#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
301pub enum FrontendSpecifier {
302 ClayGolemDash,
303 IronGolemFist,
304}