1use crate::{
2 combat::{
3 Attack, AttackDamage, AttackEffect, CombatBuff, CombatBuffStrength, CombatEffect,
4 CombatRequirement, Damage, DamageKind, FlankMults, GroupTarget, Knockback, KnockbackDir,
5 },
6 comp::{
7 buff::BuffKind,
8 tool::{Stats, ToolKind},
9 },
10 resources::Secs,
11 states::utils::AbilityInfo,
12};
13use common_base::dev_panic;
14use serde::{Deserialize, Serialize};
15use specs::{Component, VecStorage};
16use vek::*;
17
18use super::ability::Dodgeable;
19
20#[derive(Debug, Serialize, Deserialize)]
21pub struct Melee {
22 pub attack: Attack,
23 pub range: f32,
24 pub max_angle: f32,
25 pub applied: bool,
26 pub hit_count: u32,
27 pub multi_target: Option<MultiTarget>,
28 pub break_block: Option<(Vec3<i32>, Option<ToolKind>)>,
29 pub simultaneous_hits: u32,
30 pub precision_flank_multipliers: FlankMults,
31 pub precision_flank_invert: bool,
32 pub dodgeable: Dodgeable,
33}
34
35#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
36pub enum MultiTarget {
37 Normal,
38 Scaling(f32),
43}
44
45impl Melee {
46 #[must_use]
47 pub fn with_block_breaking(
48 mut self,
49 break_block: Option<(Vec3<i32>, Option<ToolKind>)>,
50 ) -> Self {
51 self.break_block = break_block;
52 self
53 }
54}
55
56impl Component for Melee {
57 type Storage = VecStorage<Self>;
58}
59
60fn default_true() -> bool { true }
61fn default_simultaneous_hits() -> u32 { 1 }
62
63#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
64pub struct Scaled {
65 pub kind: MeleeConstructorKind,
66 #[serde(default)]
67 pub range: f32,
68 #[serde(default)]
69 pub angle: f32,
70}
71
72#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
73#[serde(deny_unknown_fields)]
74pub struct MeleeConstructor {
75 pub kind: MeleeConstructorKind,
76 pub scaled: Option<Scaled>,
80 pub range: f32,
81 pub angle: f32,
82 pub multi_target: Option<MultiTarget>,
83 pub damage_effect: Option<CombatEffect>,
84 pub attack_effect: Option<(CombatEffect, CombatRequirement)>,
85 #[serde(default)]
86 pub dodgeable: Dodgeable,
87 #[serde(default = "default_true")]
88 pub blockable: bool,
89 #[serde(default = "default_simultaneous_hits")]
90 pub simultaneous_hits: u32,
91 #[serde(default)]
92 pub custom_combo: CustomCombo,
93 #[serde(default)]
94 pub precision_flank_multipliers: FlankMults,
95 #[serde(default)]
96 pub precision_flank_invert: bool,
97}
98
99#[derive(Copy, Clone, Debug, PartialEq, Serialize, Default, Deserialize)]
100pub struct CustomCombo {
101 pub base: Option<i32>,
102 pub conditional: Option<(i32, CombatRequirement)>,
103}
104
105impl MeleeConstructor {
106 pub fn create_melee(
107 self,
108 precision_mult: f32,
109 tool_stats: Stats,
110 ability_info: AbilityInfo,
111 ) -> Melee {
112 if self.scaled.is_some() {
113 dev_panic!(
114 "Attempted to create a melee attack that had a provided scaled value without \
115 scaling the melee attack."
116 )
117 }
118
119 let instance = rand::random();
120 let attack = match self.kind {
121 MeleeConstructorKind::Slash {
122 damage,
123 poise,
124 knockback,
125 energy_regen,
126 } => {
127 let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen))
128 .with_requirement(CombatRequirement::AnyDamage);
129 let buff = CombatEffect::Buff(CombatBuff {
130 kind: BuffKind::Bleeding,
131 dur_secs: Secs(10.0),
132 strength: CombatBuffStrength::DamageFraction(0.1),
133 chance: 0.1,
134 })
135 .adjusted_by_stats(tool_stats);
136 let mut damage = AttackDamage::new(
137 Damage {
138 kind: DamageKind::Slashing,
139 value: damage,
140 },
141 Some(GroupTarget::OutOfGroup),
142 instance,
143 )
144 .with_effect(buff);
145
146 if let Some(damage_effect) = self.damage_effect {
147 damage = damage.with_effect(damage_effect);
148 }
149
150 let poise =
151 AttackEffect::new(Some(GroupTarget::OutOfGroup), CombatEffect::Poise(poise))
152 .with_requirement(CombatRequirement::AnyDamage);
153 let knockback = AttackEffect::new(
154 Some(GroupTarget::OutOfGroup),
155 CombatEffect::Knockback(Knockback {
156 strength: knockback,
157 direction: KnockbackDir::Away,
158 }),
159 )
160 .with_requirement(CombatRequirement::AnyDamage);
161
162 Attack::new(Some(ability_info))
163 .with_damage(damage)
164 .with_effect(energy)
165 .with_effect(poise)
166 .with_effect(knockback)
167 },
168 MeleeConstructorKind::Stab {
169 damage,
170 poise,
171 knockback,
172 energy_regen,
173 } => {
174 let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen))
175 .with_requirement(CombatRequirement::AnyDamage);
176 let buff = CombatEffect::Buff(CombatBuff {
177 kind: BuffKind::Bleeding,
178 dur_secs: Secs(5.0),
179 strength: CombatBuffStrength::DamageFraction(0.05),
180 chance: 0.1,
181 })
182 .adjusted_by_stats(tool_stats);
183 let mut damage = AttackDamage::new(
184 Damage {
185 kind: DamageKind::Piercing,
186 value: damage,
187 },
188 Some(GroupTarget::OutOfGroup),
189 instance,
190 )
191 .with_effect(buff);
192
193 if let Some(damage_effect) = self.damage_effect {
194 damage = damage.with_effect(damage_effect);
195 }
196
197 let poise =
198 AttackEffect::new(Some(GroupTarget::OutOfGroup), CombatEffect::Poise(poise))
199 .with_requirement(CombatRequirement::AnyDamage);
200 let knockback = AttackEffect::new(
201 Some(GroupTarget::OutOfGroup),
202 CombatEffect::Knockback(Knockback {
203 strength: knockback,
204 direction: KnockbackDir::Away,
205 }),
206 )
207 .with_requirement(CombatRequirement::AnyDamage);
208
209 Attack::new(Some(ability_info))
210 .with_damage(damage)
211 .with_effect(energy)
212 .with_effect(poise)
213 .with_effect(knockback)
214 },
215 MeleeConstructorKind::Bash {
216 damage,
217 poise,
218 knockback,
219 energy_regen,
220 } => {
221 let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen))
222 .with_requirement(CombatRequirement::AnyDamage);
223 let mut damage = AttackDamage::new(
224 Damage {
225 kind: DamageKind::Crushing,
226 value: damage,
227 },
228 Some(GroupTarget::OutOfGroup),
229 instance,
230 );
231
232 if let Some(damage_effect) = self.damage_effect {
233 damage = damage.with_effect(damage_effect);
234 }
235
236 let poise =
237 AttackEffect::new(Some(GroupTarget::OutOfGroup), CombatEffect::Poise(poise))
238 .with_requirement(CombatRequirement::AnyDamage);
239 let knockback = AttackEffect::new(
240 Some(GroupTarget::OutOfGroup),
241 CombatEffect::Knockback(Knockback {
242 strength: knockback,
243 direction: KnockbackDir::Away,
244 }),
245 )
246 .with_requirement(CombatRequirement::AnyDamage);
247
248 Attack::new(Some(ability_info))
249 .with_damage(damage)
250 .with_effect(energy)
251 .with_effect(poise)
252 .with_effect(knockback)
253 },
254 MeleeConstructorKind::Hook {
255 damage,
256 poise,
257 pull,
258 } => {
259 let buff = CombatEffect::Buff(CombatBuff {
260 kind: BuffKind::Bleeding,
261 dur_secs: Secs(5.0),
262 strength: CombatBuffStrength::DamageFraction(0.2),
263 chance: 0.1,
264 })
265 .adjusted_by_stats(tool_stats);
266 let mut damage = AttackDamage::new(
267 Damage {
268 kind: DamageKind::Piercing,
269 value: damage,
270 },
271 Some(GroupTarget::OutOfGroup),
272 instance,
273 )
274 .with_effect(buff);
275
276 if let Some(damage_effect) = self.damage_effect {
277 damage = damage.with_effect(damage_effect);
278 }
279
280 let poise =
281 AttackEffect::new(Some(GroupTarget::OutOfGroup), CombatEffect::Poise(poise))
282 .with_requirement(CombatRequirement::AnyDamage);
283 let knockback = AttackEffect::new(
284 Some(GroupTarget::OutOfGroup),
285 CombatEffect::Knockback(Knockback {
286 strength: pull,
287 direction: KnockbackDir::Towards,
288 }),
289 )
290 .with_requirement(CombatRequirement::AnyDamage);
291
292 Attack::new(Some(ability_info))
293 .with_damage(damage)
294 .with_effect(poise)
295 .with_effect(knockback)
296 },
297 MeleeConstructorKind::NecroticVortex {
298 damage,
299 pull,
300 lifesteal,
301 energy_regen,
302 } => {
303 let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen))
304 .with_requirement(CombatRequirement::AnyDamage);
305 let lifesteal = CombatEffect::Lifesteal(lifesteal);
306
307 let mut damage = AttackDamage::new(
308 Damage {
309 kind: DamageKind::Energy,
310 value: damage,
311 },
312 None,
313 instance,
314 )
315 .with_effect(lifesteal);
316
317 if let Some(damage_effect) = self.damage_effect {
318 damage = damage.with_effect(damage_effect);
319 }
320
321 let knockback = AttackEffect::new(
322 Some(GroupTarget::OutOfGroup),
323 CombatEffect::Knockback(Knockback {
324 strength: pull,
325 direction: KnockbackDir::Towards,
326 }),
327 )
328 .with_requirement(CombatRequirement::AnyDamage);
329
330 Attack::new(Some(ability_info))
331 .with_damage(damage)
332 .with_effect(energy)
333 .with_effect(knockback)
334 },
335 MeleeConstructorKind::SonicWave {
336 damage,
337 poise,
338 knockback,
339 } => {
340 let mut damage = AttackDamage::new(
341 Damage {
342 kind: DamageKind::Energy,
343 value: damage,
344 },
345 Some(GroupTarget::OutOfGroup),
346 instance,
347 );
348
349 if let Some(damage_effect) = self.damage_effect {
350 damage = damage.with_effect(damage_effect);
351 }
352
353 let poise =
354 AttackEffect::new(Some(GroupTarget::OutOfGroup), CombatEffect::Poise(poise))
355 .with_requirement(CombatRequirement::AnyDamage);
356 let knockback = AttackEffect::new(
357 Some(GroupTarget::OutOfGroup),
358 CombatEffect::Knockback(Knockback {
359 strength: knockback,
360 direction: KnockbackDir::Away,
361 }),
362 )
363 .with_requirement(CombatRequirement::AnyDamage);
364
365 Attack::new(Some(ability_info))
366 .with_damage(damage)
367 .with_effect(poise)
368 .with_effect(knockback)
369 },
370 }
371 .with_precision(
372 precision_mult
373 * ability_info
374 .ability_meta
375 .precision_power_mult
376 .unwrap_or(1.0),
377 )
378 .with_blockable(self.blockable);
379
380 let attack = if let Some((effect, requirement)) = self.attack_effect {
381 let effect = AttackEffect::new(Some(GroupTarget::OutOfGroup), effect)
382 .with_requirement(requirement);
383 attack.with_effect(effect)
384 } else {
385 attack
386 };
387
388 let attack = if let Some(combo) = self.custom_combo.base {
389 attack.with_combo(combo)
390 } else {
391 attack.with_combo_increment()
392 };
393
394 let attack = if let Some((additional, req)) = self.custom_combo.conditional {
395 attack.with_combo_requirement(additional, req)
396 } else {
397 attack
398 };
399
400 Melee {
401 attack,
402 range: self.range,
403 max_angle: self.angle.to_radians(),
404 applied: false,
405 hit_count: 0,
406 multi_target: self.multi_target,
407 break_block: None,
408 simultaneous_hits: self.simultaneous_hits,
409 precision_flank_multipliers: self.precision_flank_multipliers,
410 precision_flank_invert: self.precision_flank_invert,
411 dodgeable: self.dodgeable,
412 }
413 }
414
415 #[must_use]
416 pub fn handle_scaling(mut self, scaling: f32) -> Self {
417 let scale_values = |a, b| a + b * scaling;
418
419 if let Some(max_scale) = self.scaled {
420 use MeleeConstructorKind::*;
421 let scaled = match (self.kind, max_scale.kind) {
422 (
423 Slash {
424 damage: a_damage,
425 poise: a_poise,
426 knockback: a_knockback,
427 energy_regen: a_energy_regen,
428 },
429 Slash {
430 damage: b_damage,
431 poise: b_poise,
432 knockback: b_knockback,
433 energy_regen: b_energy_regen,
434 },
435 ) => Slash {
436 damage: scale_values(a_damage, b_damage),
437 poise: scale_values(a_poise, b_poise),
438 knockback: scale_values(a_knockback, b_knockback),
439 energy_regen: scale_values(a_energy_regen, b_energy_regen),
440 },
441 (
442 Stab {
443 damage: a_damage,
444 poise: a_poise,
445 knockback: a_knockback,
446 energy_regen: a_energy_regen,
447 },
448 Stab {
449 damage: b_damage,
450 poise: b_poise,
451 knockback: b_knockback,
452 energy_regen: b_energy_regen,
453 },
454 ) => Stab {
455 damage: scale_values(a_damage, b_damage),
456 poise: scale_values(a_poise, b_poise),
457 knockback: scale_values(a_knockback, b_knockback),
458 energy_regen: scale_values(a_energy_regen, b_energy_regen),
459 },
460 (
461 Bash {
462 damage: a_damage,
463 poise: a_poise,
464 knockback: a_knockback,
465 energy_regen: a_energy_regen,
466 },
467 Bash {
468 damage: b_damage,
469 poise: b_poise,
470 knockback: b_knockback,
471 energy_regen: b_energy_regen,
472 },
473 ) => Bash {
474 damage: scale_values(a_damage, b_damage),
475 poise: scale_values(a_poise, b_poise),
476 knockback: scale_values(a_knockback, b_knockback),
477 energy_regen: scale_values(a_energy_regen, b_energy_regen),
478 },
479 (
480 NecroticVortex {
481 damage: a_damage,
482 pull: a_pull,
483 lifesteal: a_lifesteal,
484 energy_regen: a_energy_regen,
485 },
486 NecroticVortex {
487 damage: b_damage,
488 pull: b_pull,
489 lifesteal: b_lifesteal,
490 energy_regen: b_energy_regen,
491 },
492 ) => NecroticVortex {
493 damage: scale_values(a_damage, b_damage),
494 pull: scale_values(a_pull, b_pull),
495 lifesteal: scale_values(a_lifesteal, b_lifesteal),
496 energy_regen: scale_values(a_energy_regen, b_energy_regen),
497 },
498 (
499 Hook {
500 damage: a_damage,
501 poise: a_poise,
502 pull: a_pull,
503 },
504 Hook {
505 damage: b_damage,
506 poise: b_poise,
507 pull: b_pull,
508 },
509 ) => Hook {
510 damage: scale_values(a_damage, b_damage),
511 poise: scale_values(a_poise, b_poise),
512 pull: scale_values(a_pull, b_pull),
513 },
514 (
515 SonicWave {
516 damage: a_damage,
517 poise: a_poise,
518 knockback: a_knockback,
519 },
520 SonicWave {
521 damage: b_damage,
522 poise: b_poise,
523 knockback: b_knockback,
524 },
525 ) => SonicWave {
526 damage: scale_values(a_damage, b_damage),
527 poise: scale_values(a_poise, b_poise),
528 knockback: scale_values(a_knockback, b_knockback),
529 },
530 _ => {
531 dev_panic!(
532 "Attempted to scale on a melee attack between two different kinds of \
533 melee constructors."
534 );
535 self.kind
536 },
537 };
538 self.kind = scaled;
539 self.range = scale_values(self.range, max_scale.range);
540 self.angle = scale_values(self.angle, max_scale.angle);
541 self.scaled = None;
542 } else {
543 dev_panic!("Attempted to scale on a melee attack that had no provided scaling value.")
544 }
545 self
546 }
547
548 #[must_use]
549 pub fn adjusted_by_stats(mut self, stats: Stats) -> Self {
550 self.range *= stats.range;
551 self.kind = self.kind.adjusted_by_stats(stats);
552 if let Some(scaled) = &mut self.scaled {
553 scaled.kind = scaled.kind.adjusted_by_stats(stats);
554 scaled.range *= stats.range;
555 }
556 self.damage_effect = self.damage_effect.map(|de| de.adjusted_by_stats(stats));
557 self.attack_effect = self
558 .attack_effect
559 .map(|(e, r)| (e.adjusted_by_stats(stats), r));
560 self
561 }
562
563 #[must_use]
564 pub fn custom_combo(mut self, custom: CustomCombo) -> Self {
565 self.custom_combo = custom;
566 self
567 }
568}
569
570#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
571pub enum MeleeConstructorKind {
573 Slash {
574 damage: f32,
575 poise: f32,
576 knockback: f32,
577 energy_regen: f32,
578 },
579 Stab {
580 damage: f32,
581 poise: f32,
582 knockback: f32,
583 energy_regen: f32,
584 },
585 Bash {
586 damage: f32,
587 poise: f32,
588 knockback: f32,
589 energy_regen: f32,
590 },
591 Hook {
592 damage: f32,
593 poise: f32,
594 pull: f32,
595 },
596 NecroticVortex {
597 damage: f32,
598 pull: f32,
599 lifesteal: f32,
600 energy_regen: f32,
601 },
602 SonicWave {
603 damage: f32,
604 poise: f32,
605 knockback: f32,
606 },
607}
608
609impl MeleeConstructorKind {
610 #[must_use]
611 pub fn adjusted_by_stats(mut self, stats: Stats) -> Self {
612 use MeleeConstructorKind::*;
613 match self {
614 Slash {
615 ref mut damage,
616 ref mut poise,
617 ref mut knockback,
618 energy_regen: _,
619 } => {
620 *damage *= stats.power;
621 *poise *= stats.effect_power;
622 *knockback *= stats.effect_power;
623 },
624 Stab {
625 ref mut damage,
626 ref mut poise,
627 ref mut knockback,
628 energy_regen: _,
629 } => {
630 *damage *= stats.power;
631 *poise *= stats.effect_power;
632 *knockback *= stats.effect_power;
633 },
634 Bash {
635 ref mut damage,
636 ref mut poise,
637 ref mut knockback,
638 energy_regen: _,
639 } => {
640 *damage *= stats.power;
641 *poise *= stats.effect_power;
642 *knockback *= stats.effect_power;
643 },
644 Hook {
645 ref mut damage,
646 ref mut poise,
647 ref mut pull,
648 } => {
649 *damage *= stats.power;
650 *poise *= stats.effect_power;
651 *pull *= stats.effect_power;
652 },
653 NecroticVortex {
654 ref mut damage,
655 ref mut pull,
656 ref mut lifesteal,
657 energy_regen: _,
658 } => {
659 *damage *= stats.power;
660 *pull *= stats.effect_power;
661 *lifesteal *= stats.effect_power;
662 },
663 SonicWave {
664 ref mut damage,
665 ref mut poise,
666 ref mut knockback,
667 } => {
668 *damage *= stats.power;
669 *poise *= stats.effect_power;
670 *knockback *= stats.effect_power;
671 },
672 }
673 self
674 }
675}