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