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