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