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 if self.scaled.is_some() {
104 dev_panic!(
105 "Attempted to create a melee attack that had a provided scaled value without \
106 scaling the melee attack."
107 )
108 }
109
110 let instance = rand::random();
111 let attack = match self.kind {
112 MeleeConstructorKind::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_effect(energy)
157 .with_effect(poise)
158 .with_effect(knockback)
159 },
160 MeleeConstructorKind::Stab {
161 damage,
162 poise,
163 knockback,
164 energy_regen,
165 } => {
166 let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen))
167 .with_requirement(CombatRequirement::AnyDamage);
168 let buff = CombatEffect::Buff(CombatBuff {
169 kind: BuffKind::Bleeding,
170 dur_secs: 5.0,
171 strength: CombatBuffStrength::DamageFraction(0.05),
172 chance: 0.1,
173 })
174 .adjusted_by_stats(tool_stats);
175 let mut damage = AttackDamage::new(
176 Damage {
177 source: DamageSource::Melee,
178 kind: DamageKind::Piercing,
179 value: damage,
180 },
181 Some(GroupTarget::OutOfGroup),
182 instance,
183 )
184 .with_effect(buff);
185
186 if let Some(damage_effect) = self.damage_effect {
187 damage = damage.with_effect(damage_effect);
188 }
189
190 let poise =
191 AttackEffect::new(Some(GroupTarget::OutOfGroup), CombatEffect::Poise(poise))
192 .with_requirement(CombatRequirement::AnyDamage);
193 let knockback = AttackEffect::new(
194 Some(GroupTarget::OutOfGroup),
195 CombatEffect::Knockback(Knockback {
196 strength: knockback,
197 direction: KnockbackDir::Away,
198 }),
199 )
200 .with_requirement(CombatRequirement::AnyDamage);
201
202 Attack::default()
203 .with_damage(damage)
204 .with_effect(energy)
205 .with_effect(poise)
206 .with_effect(knockback)
207 },
208 MeleeConstructorKind::Bash {
209 damage,
210 poise,
211 knockback,
212 energy_regen,
213 } => {
214 let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen))
215 .with_requirement(CombatRequirement::AnyDamage);
216 let mut damage = AttackDamage::new(
217 Damage {
218 source: DamageSource::Melee,
219 kind: DamageKind::Crushing,
220 value: damage,
221 },
222 Some(GroupTarget::OutOfGroup),
223 instance,
224 );
225
226 if let Some(damage_effect) = self.damage_effect {
227 damage = damage.with_effect(damage_effect);
228 }
229
230 let poise =
231 AttackEffect::new(Some(GroupTarget::OutOfGroup), CombatEffect::Poise(poise))
232 .with_requirement(CombatRequirement::AnyDamage);
233 let knockback = AttackEffect::new(
234 Some(GroupTarget::OutOfGroup),
235 CombatEffect::Knockback(Knockback {
236 strength: knockback,
237 direction: KnockbackDir::Away,
238 }),
239 )
240 .with_requirement(CombatRequirement::AnyDamage);
241
242 Attack::default()
243 .with_damage(damage)
244 .with_effect(energy)
245 .with_effect(poise)
246 .with_effect(knockback)
247 },
248 MeleeConstructorKind::Hook {
249 damage,
250 poise,
251 pull,
252 } => {
253 let buff = CombatEffect::Buff(CombatBuff {
254 kind: BuffKind::Bleeding,
255 dur_secs: 5.0,
256 strength: CombatBuffStrength::DamageFraction(0.2),
257 chance: 0.1,
258 })
259 .adjusted_by_stats(tool_stats);
260 let mut damage = AttackDamage::new(
261 Damage {
262 source: DamageSource::Melee,
263 kind: DamageKind::Piercing,
264 value: damage,
265 },
266 Some(GroupTarget::OutOfGroup),
267 instance,
268 )
269 .with_effect(buff);
270
271 if let Some(damage_effect) = self.damage_effect {
272 damage = damage.with_effect(damage_effect);
273 }
274
275 let poise =
276 AttackEffect::new(Some(GroupTarget::OutOfGroup), CombatEffect::Poise(poise))
277 .with_requirement(CombatRequirement::AnyDamage);
278 let knockback = AttackEffect::new(
279 Some(GroupTarget::OutOfGroup),
280 CombatEffect::Knockback(Knockback {
281 strength: pull,
282 direction: KnockbackDir::Towards,
283 }),
284 )
285 .with_requirement(CombatRequirement::AnyDamage);
286
287 Attack::default()
288 .with_damage(damage)
289 .with_effect(poise)
290 .with_effect(knockback)
291 },
292 MeleeConstructorKind::NecroticVortex {
293 damage,
294 pull,
295 lifesteal,
296 energy_regen,
297 } => {
298 let energy = AttackEffect::new(None, CombatEffect::EnergyReward(energy_regen))
299 .with_requirement(CombatRequirement::AnyDamage);
300 let lifesteal = CombatEffect::Lifesteal(lifesteal);
301
302 let mut damage = AttackDamage::new(
303 Damage {
304 source: DamageSource::Melee,
305 kind: DamageKind::Energy,
306 value: damage,
307 },
308 None,
309 instance,
310 )
311 .with_effect(lifesteal);
312
313 if let Some(damage_effect) = self.damage_effect {
314 damage = damage.with_effect(damage_effect);
315 }
316
317 let knockback = AttackEffect::new(
318 Some(GroupTarget::OutOfGroup),
319 CombatEffect::Knockback(Knockback {
320 strength: pull,
321 direction: KnockbackDir::Towards,
322 }),
323 )
324 .with_requirement(CombatRequirement::AnyDamage);
325
326 Attack::default()
327 .with_damage(damage)
328 .with_effect(energy)
329 .with_effect(knockback)
330 },
331 MeleeConstructorKind::SonicWave {
332 damage,
333 poise,
334 knockback,
335 } => {
336 let mut damage = AttackDamage::new(
337 Damage {
338 source: DamageSource::Melee,
339 kind: DamageKind::Energy,
340 value: damage,
341 },
342 Some(GroupTarget::OutOfGroup),
343 instance,
344 );
345
346 if let Some(damage_effect) = self.damage_effect {
347 damage = damage.with_effect(damage_effect);
348 }
349
350 let poise =
351 AttackEffect::new(Some(GroupTarget::OutOfGroup), CombatEffect::Poise(poise))
352 .with_requirement(CombatRequirement::AnyDamage);
353 let knockback = AttackEffect::new(
354 Some(GroupTarget::OutOfGroup),
355 CombatEffect::Knockback(Knockback {
356 strength: knockback,
357 direction: KnockbackDir::Away,
358 }),
359 )
360 .with_requirement(CombatRequirement::AnyDamage);
361
362 Attack::default()
363 .with_damage(damage)
364 .with_effect(poise)
365 .with_effect(knockback)
366 },
367 }
368 .with_precision(precision_mult);
369
370 let attack = if let Some((effect, requirement)) = self.attack_effect {
371 let effect = AttackEffect::new(Some(GroupTarget::OutOfGroup), effect)
372 .with_requirement(requirement);
373 attack.with_effect(effect)
374 } else {
375 attack
376 };
377
378 let attack = if let Some(combo) = self.custom_combo.base {
379 attack.with_combo(combo)
380 } else {
381 attack.with_combo_increment()
382 };
383
384 let attack = if let Some((additional, req)) = self.custom_combo.conditional {
385 attack.with_combo_requirement(additional, req)
386 } else {
387 attack
388 };
389
390 Melee {
391 attack,
392 range: self.range,
393 max_angle: self.angle.to_radians(),
394 applied: false,
395 hit_count: 0,
396 multi_target: self.multi_target,
397 break_block: None,
398 simultaneous_hits: self.simultaneous_hits,
399 precision_flank_multipliers: self.precision_flank_multipliers,
400 precision_flank_invert: self.precision_flank_invert,
401 dodgeable: self.dodgeable,
402 }
403 }
404
405 #[must_use]
406 pub fn handle_scaling(mut self, scaling: f32) -> Self {
407 let scale_values = |a, b| a + b * scaling;
408
409 if let Some(max_scale) = self.scaled {
410 use MeleeConstructorKind::*;
411 let scaled = match (self.kind, max_scale.kind) {
412 (
413 Slash {
414 damage: a_damage,
415 poise: a_poise,
416 knockback: a_knockback,
417 energy_regen: a_energy_regen,
418 },
419 Slash {
420 damage: b_damage,
421 poise: b_poise,
422 knockback: b_knockback,
423 energy_regen: b_energy_regen,
424 },
425 ) => Slash {
426 damage: scale_values(a_damage, b_damage),
427 poise: scale_values(a_poise, b_poise),
428 knockback: scale_values(a_knockback, b_knockback),
429 energy_regen: scale_values(a_energy_regen, b_energy_regen),
430 },
431 (
432 Stab {
433 damage: a_damage,
434 poise: a_poise,
435 knockback: a_knockback,
436 energy_regen: a_energy_regen,
437 },
438 Stab {
439 damage: b_damage,
440 poise: b_poise,
441 knockback: b_knockback,
442 energy_regen: b_energy_regen,
443 },
444 ) => Stab {
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 Bash {
452 damage: a_damage,
453 poise: a_poise,
454 knockback: a_knockback,
455 energy_regen: a_energy_regen,
456 },
457 Bash {
458 damage: b_damage,
459 poise: b_poise,
460 knockback: b_knockback,
461 energy_regen: b_energy_regen,
462 },
463 ) => Bash {
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 NecroticVortex {
471 damage: a_damage,
472 pull: a_pull,
473 lifesteal: a_lifesteal,
474 energy_regen: a_energy_regen,
475 },
476 NecroticVortex {
477 damage: b_damage,
478 pull: b_pull,
479 lifesteal: b_lifesteal,
480 energy_regen: b_energy_regen,
481 },
482 ) => NecroticVortex {
483 damage: scale_values(a_damage, b_damage),
484 pull: scale_values(a_pull, b_pull),
485 lifesteal: scale_values(a_lifesteal, b_lifesteal),
486 energy_regen: scale_values(a_energy_regen, b_energy_regen),
487 },
488 (
489 Hook {
490 damage: a_damage,
491 poise: a_poise,
492 pull: a_pull,
493 },
494 Hook {
495 damage: b_damage,
496 poise: b_poise,
497 pull: b_pull,
498 },
499 ) => Hook {
500 damage: scale_values(a_damage, b_damage),
501 poise: scale_values(a_poise, b_poise),
502 pull: scale_values(a_pull, b_pull),
503 },
504 (
505 SonicWave {
506 damage: a_damage,
507 poise: a_poise,
508 knockback: a_knockback,
509 },
510 SonicWave {
511 damage: b_damage,
512 poise: b_poise,
513 knockback: b_knockback,
514 },
515 ) => SonicWave {
516 damage: scale_values(a_damage, b_damage),
517 poise: scale_values(a_poise, b_poise),
518 knockback: scale_values(a_knockback, b_knockback),
519 },
520 _ => {
521 dev_panic!(
522 "Attempted to scale on a melee attack between two different kinds of \
523 melee constructors."
524 );
525 self.kind
526 },
527 };
528 self.kind = scaled;
529 self.range = scale_values(self.range, max_scale.range);
530 self.angle = scale_values(self.angle, max_scale.angle);
531 self.scaled = None;
532 } else {
533 dev_panic!("Attempted to scale on a melee attack that had no provided scaling value.")
534 }
535 self
536 }
537
538 #[must_use]
539 pub fn adjusted_by_stats(mut self, stats: Stats) -> Self {
540 self.range *= stats.range;
541 self.kind = self.kind.adjusted_by_stats(stats);
542 if let Some(scaled) = &mut self.scaled {
543 scaled.kind = scaled.kind.adjusted_by_stats(stats);
544 scaled.range *= stats.range;
545 }
546 self.damage_effect = self.damage_effect.map(|de| de.adjusted_by_stats(stats));
547 self
548 }
549
550 #[must_use]
551 pub fn custom_combo(mut self, custom: CustomCombo) -> Self {
552 self.custom_combo = custom;
553 self
554 }
555}
556
557#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
558pub enum MeleeConstructorKind {
560 Slash {
561 damage: f32,
562 poise: f32,
563 knockback: f32,
564 energy_regen: f32,
565 },
566 Stab {
567 damage: f32,
568 poise: f32,
569 knockback: f32,
570 energy_regen: f32,
571 },
572 Bash {
573 damage: f32,
574 poise: f32,
575 knockback: f32,
576 energy_regen: f32,
577 },
578 Hook {
579 damage: f32,
580 poise: f32,
581 pull: f32,
582 },
583 NecroticVortex {
584 damage: f32,
585 pull: f32,
586 lifesteal: f32,
587 energy_regen: f32,
588 },
589 SonicWave {
590 damage: f32,
591 poise: f32,
592 knockback: f32,
593 },
594}
595
596impl MeleeConstructorKind {
597 #[must_use]
598 pub fn adjusted_by_stats(mut self, stats: Stats) -> Self {
599 use MeleeConstructorKind::*;
600 match self {
601 Slash {
602 ref mut damage,
603 ref mut poise,
604 ref mut knockback,
605 energy_regen: _,
606 } => {
607 *damage *= stats.power;
608 *poise *= stats.effect_power;
609 *knockback *= stats.effect_power;
610 },
611 Stab {
612 ref mut damage,
613 ref mut poise,
614 ref mut knockback,
615 energy_regen: _,
616 } => {
617 *damage *= stats.power;
618 *poise *= stats.effect_power;
619 *knockback *= stats.effect_power;
620 },
621 Bash {
622 ref mut damage,
623 ref mut poise,
624 ref mut knockback,
625 energy_regen: _,
626 } => {
627 *damage *= stats.power;
628 *poise *= stats.effect_power;
629 *knockback *= stats.effect_power;
630 },
631 Hook {
632 ref mut damage,
633 ref mut poise,
634 ref mut pull,
635 } => {
636 *damage *= stats.power;
637 *poise *= stats.effect_power;
638 *pull *= stats.effect_power;
639 },
640 NecroticVortex {
641 ref mut damage,
642 ref mut pull,
643 ref mut lifesteal,
644 energy_regen: _,
645 } => {
646 *damage *= stats.power;
647 *pull *= stats.effect_power;
648 *lifesteal *= stats.effect_power;
649 },
650 SonicWave {
651 ref mut damage,
652 ref mut poise,
653 ref mut knockback,
654 } => {
655 *damage *= stats.power;
656 *poise *= stats.effect_power;
657 *knockback *= stats.effect_power;
658 },
659 }
660 self
661 }
662}