1use std::hash::Hash;
2
3use enum_map::EnumMap;
4use fastrand::Rng;
5
6use crate::{
7 command::AttributeType,
8 gamestate::{character::Class, items::*},
9 misc::EnumMapGet,
10 simulate::{damage::*, upgradeable::UpgradeableFighter, *},
11};
12
13#[derive(Debug, Clone)]
21pub struct Fighter {
22 pub ident: FighterIdent,
23 pub name: std::sync::Arc<str>,
26 pub class: Class,
28 pub level: u16,
30 pub attributes: EnumMap<AttributeType, u32>,
32 pub max_health: f64,
34 pub armor: u32,
36 pub first_weapon: Option<Weapon>,
38 pub second_weapon: Option<Weapon>,
41 pub has_reaction_enchant: bool,
43 pub crit_dmg_multi: f64,
45 pub resistances: EnumMap<Element, i32>,
47 pub portal_dmg_bonus: f64,
49 pub is_companion: bool,
51 pub gladiator_lvl: u32,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
56pub struct FighterIdent(u32);
57
58impl FighterIdent {
59 pub fn new() -> Self {
60 FighterIdent(fastrand::u32(..))
61 }
62}
63
64impl Default for FighterIdent {
65 fn default() -> Self {
66 Self::new()
67 }
68}
69
70impl From<&Monster> for Fighter {
71 fn from(monster: &Monster) -> Fighter {
72 let mut weapon = Weapon {
73 rune_value: 0,
74 rune_type: None,
75 damage: DamageRange {
76 min: f64::from(monster.min_dmg),
77 max: f64::from(monster.max_dmg),
78 },
79 };
80 let mut resistances = EnumMap::default();
81
82 if let Some(runes) = &monster.runes {
83 resistances = runes.resistances;
84 weapon.rune_value = runes.damage;
85 weapon.rune_type = Some(runes.damage_type);
86 }
87
88 let second_weapon =
90 (monster.class == Class::Assassin).then(|| weapon.clone());
91
92 Fighter {
93 ident: FighterIdent::new(),
94 name: std::sync::Arc::from(monster.name),
95 class: monster.class,
96 level: monster.level,
97 attributes: monster.attributes,
98 max_health: monster.hp as f64,
99 armor: monster.armor,
100 second_weapon,
101 first_weapon: Some(weapon),
102 has_reaction_enchant: false,
103 crit_dmg_multi: 2.0,
104 resistances,
105 portal_dmg_bonus: 0.0,
106 is_companion: false,
107 gladiator_lvl: 0,
108 }
109 }
110}
111
112impl From<&UpgradeableFighter> for Fighter {
113 fn from(char: &UpgradeableFighter) -> Self {
114 use RuneType as RT;
115
116 let attributes = char.attributes();
117 let health = char.hit_points(&attributes) as f64;
118
119 let mut resistances = EnumMap::default();
120 let mut has_reaction = false;
121 let mut extra_crit_dmg = 0.0;
122 let mut armor = 0;
123 let mut weapon = None;
124 let mut offhand = None;
125
126 for (slot, item) in &char.equipment.0 {
127 let Some(item) = item else {
128 continue;
129 };
130 armor += item.armor();
131 match item.enchantment {
132 Some(Enchantment::SwordOfVengeance) => {
133 extra_crit_dmg = 0.05;
134 }
135 Some(Enchantment::ShadowOfTheCowboy) => {
136 has_reaction = true;
137 }
138 _ => {}
139 }
140
141 if let Some(rune) = item.rune {
142 let mut apply = |element| {
143 *resistances.get_mut(element) += i32::from(rune.value);
144 };
145 match rune.typ {
146 RT::FireResistance => apply(Element::Fire),
147 RT::ColdResistence => apply(Element::Cold),
148 RT::LightningResistance => apply(Element::Lightning),
149 RT::TotalResistence => {
150 for val in &mut resistances.values_mut() {
151 *val += i32::from(rune.value);
152 }
153 }
154 _ => {}
155 }
156 }
157
158 match item.typ {
159 ItemType::Weapon { min_dmg, max_dmg } => {
160 let mut res = Weapon {
161 rune_value: 0,
162 rune_type: None,
163 damage: DamageRange {
164 min: f64::from(min_dmg),
165 max: f64::from(max_dmg),
166 },
167 };
168 if let Some(rune) = item.rune {
169 res.rune_type = match rune.typ {
170 RT::FireDamage => Some(Element::Fire),
171 RT::ColdDamage => Some(Element::Cold),
172 RT::LightningDamage => Some(Element::Lightning),
173 _ => None,
174 };
175 res.rune_value = rune.value.into();
176 }
177 match slot {
178 EquipmentSlot::Weapon => weapon = Some(res),
179 EquipmentSlot::Shield => offhand = Some(res),
180 _ => {}
181 }
182 }
183 ItemType::Shield { block_chance: _ } => {
184 }
187 _ => (),
188 }
189 }
190
191 let crit_multiplier =
192 2.0 + extra_crit_dmg + f64::from(char.gladiator) * 0.11;
193
194 Fighter {
195 ident: FighterIdent::new(),
196 name: char.name.clone(),
197 class: char.class,
198 level: char.level,
199 attributes,
200 max_health: health,
201 armor,
202 first_weapon: weapon,
203 second_weapon: offhand,
204 has_reaction_enchant: has_reaction,
205 crit_dmg_multi: crit_multiplier,
206 resistances,
207 portal_dmg_bonus: f64::from(char.portal_dmg_bonus),
208 is_companion: char.is_companion,
209 gladiator_lvl: char.gladiator,
210 }
211 }
212}
213
214#[derive(Debug, Clone)]
221pub(crate) struct InBattleFighter {
222 #[allow(unused)]
225 pub name: Arc<str>,
226 pub class: Class,
228 pub max_health: f64,
230 pub health: f64,
233 pub damage: DamageRange,
236 pub reaction: u8,
239 pub crit_chance: f64,
241 pub crit_dmg_multi: f64,
243 pub opponent_is_mage: bool,
247
248 pub class_data: ClassData,
251}
252
253#[derive(Debug, Clone)]
255pub(crate) enum ClassData {
256 Warrior {
257 block_chance: i32,
259 },
260 Mage,
261 Scout,
262 Assassin {
263 secondary_damage: DamageRange,
265 },
266 BattleMage {
267 fireball_dmg: f64,
269 },
270 Berserker {
271 frenzy_attacks: u32,
274 },
275 DemonHunter {
276 revive_count: u32,
278 },
279 Druid {
280 is_in_bear_form: bool,
282 rage_crit_chance: f64,
284 has_just_dodged: bool,
287 swoop_chance: f64,
289 swoop_dmg_multi: f64,
292 },
293 Bard {
294 melody_remaining_rounds: i32,
296 melody_cooldown_rounds: i32,
298 melody_dmg_multi: f64,
301 },
302 Necromancer {
303 damage_multi: f64,
305 minion: Option<Minion>,
307 minion_remaining_rounds: i32,
309 skeleton_revived: i32,
311 },
312 Paladin {
313 initial_armor_reduction: f64,
316 stance: Stance,
318 },
319 PlagueDoctor {
320 poison_remaining_round: usize,
322 poison_dmg_multis: [f64; 3],
325 },
326}
327
328#[derive(Debug, PartialEq, Eq, Clone, Copy)]
330pub(crate) enum Minion {
331 Skeleton,
332 Hound,
333 Golem,
334}
335
336#[derive(Debug, Clone, Copy, PartialEq, Eq)]
338pub(crate) enum Stance {
339 Regular,
340 Defensive,
341 Offensive,
342}
343
344impl Stance {
345 pub(crate) fn damage_multiplier(self) -> f64 {
346 match self {
347 Stance::Regular => 1.0,
348 Stance::Defensive => 1.0 / 0.833 * 0.568,
349 Stance::Offensive => 1.0 / 0.833 * 1.253,
350 }
351 }
352
353 pub(crate) fn block_chance(self) -> u8 {
354 match self {
355 Stance::Regular => 30,
356 Stance::Defensive => 50,
357 Stance::Offensive => 25,
358 }
359 }
360}
361pub(crate) fn calculate_crit_chance(
363 main: &Fighter,
364 opponent: &Fighter,
365 cap: f64,
366 crit_bonus: f64,
367) -> f64 {
368 let luck_factor = f64::from(main.attributes[AttributeType::Luck]) * 5.0;
369 let opponent_level_factor = f64::from(opponent.level) * 2.0;
370 let crit_chance = luck_factor / opponent_level_factor / 100.0 + crit_bonus;
371 crit_chance.min(cap)
372}
373
374impl InBattleFighter {
375 pub fn is_mage(&self) -> bool {
377 self.class == Class::Mage
378 }
379
380 pub fn update_opponent(
383 &mut self,
384 main: &Fighter,
385 opponent: &Fighter,
386 reduce_gladiator: bool,
387 ) {
388 self.damage = calculate_damage(main, opponent, false);
389
390 let mut crit_dmg_multi = main.crit_dmg_multi;
391 if reduce_gladiator {
392 let glad_lvl = main.gladiator_lvl.min(opponent.gladiator_lvl);
393 crit_dmg_multi -= f64::from(glad_lvl) * 0.11;
394 }
395 self.crit_dmg_multi = crit_dmg_multi;
396 self.crit_chance = calculate_crit_chance(main, opponent, 0.5, 0.0);
397
398 self.class_data.update_opponent(main, opponent);
399 self.opponent_is_mage = opponent.class == Class::Mage;
400 }
401
402 pub fn attack(
405 &mut self,
406 target: &mut InBattleFighter,
407 round: &mut u32,
408 rng: &mut Rng,
409 ) -> bool {
410 match &mut self.class_data {
411 ClassData::Assassin { secondary_damage } => {
412 let secondary_damage = *secondary_damage;
413
414 *round += 1;
416 if target.will_take_attack(rng) {
417 let first_weapon_damage =
418 self.calc_basic_hit_damage(*round, rng);
419 if target.take_attack_dmg(first_weapon_damage, round, rng) {
420 return true;
421 }
422 }
423
424 *round += 1;
426 if !target.will_take_attack(rng) {
427 return false;
428 }
429
430 let second_weapon_damage = calculate_hit_damage(
431 &secondary_damage,
432 *round,
433 self.crit_chance,
434 self.crit_dmg_multi,
435 rng,
436 );
437
438 target.take_attack_dmg(second_weapon_damage, round, rng)
439 }
440 ClassData::Druid {
441 has_just_dodged,
442 rage_crit_chance,
443 is_in_bear_form,
444 swoop_chance,
445 swoop_dmg_multi,
446 } => {
447 if target.is_mage() {
448 return self.attack_generic(target, round, rng);
449 }
450
451 if *has_just_dodged {
452 *is_in_bear_form = true;
454 *has_just_dodged = false;
455
456 *round += 1;
457
458 if !target.will_take_attack(rng) {
459 return false;
460 }
461
462 let rage_crit_multi = 6.0 * self.crit_dmg_multi / 2.0;
463 let dmg = calculate_hit_damage(
464 &self.damage,
465 *round,
466 *rage_crit_chance,
467 rage_crit_multi,
468 rng,
469 );
470 return target.take_attack_dmg(dmg, round, rng);
471 }
472
473 *is_in_bear_form = false;
474
475 let do_swoop_attack = rng.f64() < *swoop_chance;
478 if do_swoop_attack {
479 *round += 1;
480 *swoop_chance = (*swoop_chance + 0.05).min(0.5);
481
482 if target.will_take_attack(rng) {
483 let swoop_dmg_multi = *swoop_dmg_multi;
484 let swoop_dmg = self.calc_basic_hit_damage(*round, rng)
485 * swoop_dmg_multi;
486
487 if target.take_attack_dmg(swoop_dmg, round, rng) {
488 return true;
489 }
490 }
491 }
492
493 self.attack_generic(target, round, rng)
494 }
495 ClassData::Bard {
496 melody_remaining_rounds,
497 melody_cooldown_rounds,
498 melody_dmg_multi,
499 } => {
500 if target.is_mage() {
501 return self.attack_generic(target, round, rng);
502 }
503
504 if *melody_remaining_rounds <= 0 && *melody_cooldown_rounds <= 0
505 {
506 let (length, multi) = match rng.u32(0..4) {
508 0 | 1 => (3, 1.4),
509 2 => (3, 1.2),
510 _ => (4, 1.6),
511 };
512 *melody_remaining_rounds = length;
513 *melody_dmg_multi = multi;
514 *melody_cooldown_rounds = 4;
515 } else if *melody_remaining_rounds == 0 {
516 *melody_dmg_multi = 1.0;
518 }
519
520 *melody_remaining_rounds -= 1;
521 *melody_cooldown_rounds -= 1;
522
523 if !target.will_take_attack(rng) {
524 return false;
525 }
526
527 let dmg_multi = *melody_dmg_multi;
528 let dmg = self.calc_basic_hit_damage(*round, rng) * dmg_multi;
529 target.take_attack_dmg(dmg, round, rng)
530 }
531 ClassData::Necromancer {
532 minion,
533 minion_remaining_rounds: minion_rounds,
534 ..
535 } => {
536 if target.is_mage() {
537 return self.attack_generic(target, round, rng);
538 }
539 *round += 1;
540
541 if minion.is_none() && rng.bool() {
542 let (new_type, new_rounds) = match rng.u8(0..3) {
544 0 => (Minion::Skeleton, 3),
545 1 => (Minion::Hound, 2),
546 _ => (Minion::Golem, 4),
547 };
548
549 *minion = Some(new_type);
550 *minion_rounds = new_rounds;
551 return self.attack_with_minion(target, round, rng);
552 }
553
554 if target.will_take_attack(rng) {
555 let dmg = self.calc_basic_hit_damage(*round, rng);
557 if target.take_attack_dmg(dmg, round, rng) {
558 return true;
559 }
560 }
561
562 self.attack_with_minion(target, round, rng)
563 }
564 ClassData::Paladin { stance, .. } => {
565 if target.is_mage() {
566 return self.attack_generic(target, round, rng);
567 }
568
569 *round += 1;
570 if rng.bool() {
571 *stance = match stance {
573 Stance::Regular => Stance::Defensive,
574 Stance::Defensive => Stance::Offensive,
575 Stance::Offensive => Stance::Regular,
576 };
577 }
578
579 if !target.will_take_attack(rng) {
580 return false;
581 }
582
583 let dmg_multi = stance.damage_multiplier();
584 let dmg = self.calc_basic_hit_damage(*round, rng) * dmg_multi;
585 target.take_attack_dmg(dmg, round, rng)
586 }
587 ClassData::PlagueDoctor {
588 poison_remaining_round,
589 poison_dmg_multis,
590 } => {
591 if target.is_mage() {
592 return self.attack_generic(target, round, rng);
593 }
594
595 if *poison_remaining_round == 0 && rng.bool() {
596 *round += 1;
598 if !target.will_take_attack(rng) {
599 return false;
600 }
601
602 *poison_remaining_round = 3;
603
604 let dmg_multi = poison_dmg_multis[2];
605 let dmg =
606 self.calc_basic_hit_damage(*round, rng) * dmg_multi;
607 return target.take_attack_dmg(dmg, round, rng);
608 }
609
610 if *poison_remaining_round > 0 {
611 *round += 1;
614 *poison_remaining_round -= 1;
615
616 #[allow(clippy::indexing_slicing)]
617 let dmg_multi = poison_dmg_multis[*poison_remaining_round];
618 let dmg =
619 self.calc_basic_hit_damage(*round, rng) * dmg_multi;
620
621 if target.class == Class::Paladin {
622 target.health -= dmg;
624 if target.health <= 0.0 {
625 return true;
626 }
627 } else if target.take_attack_dmg(dmg, round, rng) {
628 return true;
629 }
630 }
631 self.attack_generic(target, round, rng)
632 }
633 ClassData::Mage => {
634 let dmg = self.calc_basic_hit_damage(*round, rng);
636 target.take_attack_dmg(dmg, round, rng)
637 }
638 _ => self.attack_generic(target, round, rng),
639 }
640 }
641
642 fn attack_generic(
645 &mut self,
646 target: &mut InBattleFighter,
647 round: &mut u32,
648 rng: &mut Rng,
649 ) -> bool {
650 *round += 1;
651
652 if !target.will_take_attack(rng) {
653 return false;
654 }
655
656 let dmg = self.calc_basic_hit_damage(*round, rng);
657 target.take_attack_dmg(dmg, round, rng)
658 }
659
660 pub fn attack_before_fight(
662 &mut self,
663 target: &mut InBattleFighter,
664 round: &mut u32,
665 rng: &mut Rng,
666 ) -> bool {
667 match &mut self.class_data {
668 ClassData::BattleMage { fireball_dmg } => {
669 *round += 1;
670 target.take_attack_dmg(*fireball_dmg, round, rng)
671 }
672 _ => false,
673 }
674 }
675
676 pub fn will_skips_opponent_round(
678 &mut self,
679 target: &mut InBattleFighter,
680 _round: &mut u32,
681 rng: &mut Rng,
682 ) -> bool {
683 match &mut self.class_data {
684 ClassData::Berserker { frenzy_attacks } => {
685 if target.class == Class::Mage {
686 return false;
687 }
688
689 if *frenzy_attacks < 14 && rng.bool() {
690 *frenzy_attacks += 1;
691 return true;
692 }
693
694 *frenzy_attacks = 0;
695 false
696 }
697 _ => false,
698 }
699 }
700
701 pub fn take_attack_dmg(
705 &mut self,
706 damage: f64,
707 round: &mut u32,
708 rng: &mut Rng,
709 ) -> bool {
710 match &mut self.class_data {
711 ClassData::DemonHunter { revive_count } => {
712 let health = &mut self.health;
713 *health -= damage;
714 if *health > 0.0 {
715 return false;
716 }
717 if self.opponent_is_mage {
718 return true;
719 }
720
721 let revive_chance = 0.44 - (f64::from(*revive_count) * 0.11);
723 if revive_chance <= 0.0 || rng.f64() >= revive_chance {
724 return true;
725 }
726
727 *round += 1;
728 *revive_count += 1;
729
730 true
731 }
732 ClassData::Paladin {
733 stance,
734 initial_armor_reduction,
735 } => {
736 let current_armor_reduction = match stance {
737 Stance::Regular | Stance::Defensive => 1.0,
738 Stance::Offensive => {
739 1.0 / (1.0 - *initial_armor_reduction)
740 * (1.0 - initial_armor_reduction.min(0.20))
741 }
742 };
743 let actual_damage = damage * current_armor_reduction;
744 let health = &mut self.health;
745
746 if self.opponent_is_mage {
747 *health -= actual_damage;
748 return *health <= 0.0;
749 }
750
751 if *stance == Stance::Defensive
752 && rng.u8(1..=100) <= stance.block_chance()
753 {
754 let heal_cap = actual_damage * 0.3;
755 *health += (self.max_health - *health).clamp(0.0, heal_cap);
756 return false;
757 }
758
759 *health -= actual_damage;
760 *health <= 0.0
761 }
762 _ => {
763 let health = &mut self.health;
764 *health -= damage;
765 *health <= 0.0
766 }
767 }
768 }
769
770 pub fn will_take_attack(&mut self, rng: &mut Rng) -> bool {
772 match &mut self.class_data {
773 ClassData::Warrior { block_chance } => {
774 rng.i32(1..=100) > *block_chance
775 }
776 ClassData::Assassin { .. } | ClassData::Scout => rng.bool(),
777 ClassData::Druid {
778 is_in_bear_form,
779 has_just_dodged,
780 ..
781 } => {
782 if !*is_in_bear_form && rng.u8(1..=100) <= 35 {
783 *has_just_dodged = true;
785 return false;
786 }
787 true
788 }
789 ClassData::Necromancer { minion, .. } => {
790 if self.opponent_is_mage {
791 return true;
792 }
793 if *minion != Some(Minion::Golem) {
794 return true;
795 }
796 rng.u8(1..=100) > 25
797 }
798 ClassData::Paladin { stance, .. } => {
799 *stance == Stance::Defensive
800 || rng.u8(1..=100) > stance.block_chance()
801 }
802 ClassData::PlagueDoctor {
803 poison_remaining_round,
804 ..
805 } => {
806 let chance = match poison_remaining_round {
807 3 => 65,
808 2 => 50,
809 1 => 35,
810 _ => 20,
811 };
812 rng.u8(1..=100) > chance
813 }
814 _ => true,
815 }
816 }
817
818 fn calc_basic_hit_damage(&self, round: u32, rng: &mut Rng) -> f64 {
819 calculate_hit_damage(
820 &self.damage,
821 round,
822 self.crit_chance,
823 self.crit_dmg_multi,
824 rng,
825 )
826 }
827
828 fn attack_with_minion(
829 &mut self,
830 target: &mut InBattleFighter,
831 round: &mut u32,
832 rng: &mut Rng,
833 ) -> bool {
834 let ClassData::Necromancer {
835 minion,
836 minion_remaining_rounds,
837 skeleton_revived,
838 damage_multi,
839 } = &mut self.class_data
840 else {
841 return false;
843 };
844
845 if minion.is_none() {
846 return false;
847 }
848
849 *round += 1;
850
851 *minion_remaining_rounds -= 1;
852
853 if *minion_remaining_rounds == 0
856 && *minion == Some(Minion::Skeleton)
857 && *skeleton_revived < 1
858 && rng.bool()
859 {
860 *minion_remaining_rounds = 1;
861 *skeleton_revived += 1;
862 } else if *minion_remaining_rounds == 0 {
863 *minion = None;
864 *skeleton_revived = 0;
865 }
866
867 if !target.will_take_attack(rng) {
868 return false;
869 }
870
871 let mut crit_chance = self.crit_chance;
872 let mut crit_multi = self.crit_dmg_multi;
873 if *minion == Some(Minion::Hound) {
874 crit_chance = (crit_chance + 0.1).min(0.6);
875 crit_multi = 2.5 * (crit_multi / 2.0);
876 }
877
878 let mut dmg = calculate_hit_damage(
879 &self.damage,
880 *round,
881 crit_chance,
882 crit_multi,
883 rng,
884 );
885
886 let base_multi = *damage_multi;
887 let minion_dmg_multiplier = match minion {
888 Some(Minion::Skeleton) => (base_multi + 0.25) / base_multi,
889 Some(Minion::Hound) => (base_multi + 1.0) / base_multi,
890 Some(Minion::Golem) => 1.0,
891 None => 0.0,
892 };
893 dmg *= minion_dmg_multiplier;
894
895 target.take_attack_dmg(dmg, round, rng)
896 }
897}
898
899impl InBattleFighter {
900 pub(crate) fn new(
901 main: &Fighter,
902 opponent: &Fighter,
903 reduce_gladiator: bool,
904 ) -> InBattleFighter {
905 let class_data = ClassData::new(main, opponent);
906
907 let mut res = InBattleFighter {
908 name: main.name.clone(),
909 class: main.class,
910 health: main.max_health,
911 max_health: main.max_health,
912 reaction: u8::from(main.has_reaction_enchant),
913 damage: DamageRange::default(),
914 crit_chance: 0.0,
915 crit_dmg_multi: 0.0,
916 opponent_is_mage: false,
917 class_data,
918 };
919 res.update_opponent(main, opponent, reduce_gladiator);
920 res
921 }
922}
923
924impl ClassData {
925 pub(crate) fn update_opponent(
926 &mut self,
927 main: &Fighter,
928 opponent: &Fighter,
929 ) {
930 match self {
933 ClassData::Bard { .. }
934 | ClassData::DemonHunter { .. }
935 | ClassData::Mage
936 | ClassData::Scout
937 | ClassData::Warrior { .. } => {}
938 ClassData::Assassin { secondary_damage } => {
939 let range = calculate_damage(main, opponent, true);
940 *secondary_damage = range;
941 }
942 ClassData::BattleMage { fireball_dmg, .. } => {
943 *fireball_dmg = calculate_fire_ball_damage(main, opponent);
944 }
945 ClassData::Berserker {
946 frenzy_attacks: chain_attack_counter,
947 } => *chain_attack_counter = 0,
948 ClassData::Druid {
949 rage_crit_chance,
950 swoop_dmg_multi,
951 ..
952 } => {
953 *rage_crit_chance =
954 calculate_crit_chance(main, opponent, 0.75, 0.1);
955 *swoop_dmg_multi = calculate_swoop_damage(main, opponent);
956 }
957
958 ClassData::Necromancer { damage_multi, .. } => {
959 *damage_multi = calculate_damage_multiplier(main, opponent);
960 }
961 ClassData::Paladin {
962 initial_armor_reduction,
963 ..
964 } => {
965 *initial_armor_reduction =
966 calculate_damage_reduction(opponent, main);
967 }
968 ClassData::PlagueDoctor {
969 poison_dmg_multis, ..
970 } => {
971 let base_dmg_multi =
972 calculate_damage_multiplier(main, opponent);
973
974 let dmg_multiplier = Class::PlagueDoctor.damage_multiplier();
975 let class_dmg_multi = base_dmg_multi / dmg_multiplier;
976
977 *poison_dmg_multis = [
978 (base_dmg_multi - 0.9 * class_dmg_multi) / base_dmg_multi,
979 (base_dmg_multi - 0.55 * class_dmg_multi) / base_dmg_multi,
980 (base_dmg_multi - 0.2 * class_dmg_multi) / base_dmg_multi,
981 ];
982 }
984 }
985 }
986
987 pub(crate) fn new(main: &Fighter, opponent: &Fighter) -> ClassData {
988 let mut res = match main.class {
989 Class::Warrior if main.is_companion => {
990 ClassData::Warrior { block_chance: 0 }
991 }
992 Class::Warrior => ClassData::Warrior { block_chance: 25 },
993 Class::Mage => ClassData::Mage,
994 Class::Scout => ClassData::Scout,
995 Class::Assassin => ClassData::Assassin {
996 secondary_damage: DamageRange::default(),
997 },
998 Class::BattleMage => ClassData::BattleMage { fireball_dmg: 0.0 },
999 Class::Berserker => ClassData::Berserker { frenzy_attacks: 0 },
1000 Class::DemonHunter => ClassData::DemonHunter { revive_count: 0 },
1001 Class::Druid => ClassData::Druid {
1002 rage_crit_chance: 0.0,
1003 is_in_bear_form: false,
1004 has_just_dodged: false,
1005 swoop_chance: 0.15,
1006 swoop_dmg_multi: 0.0,
1007 },
1008 Class::Bard => ClassData::Bard {
1009 melody_remaining_rounds: -1,
1010 melody_cooldown_rounds: 0,
1011 melody_dmg_multi: 1.0,
1012 },
1013 Class::Necromancer => ClassData::Necromancer {
1014 damage_multi: 0.0,
1015 minion: None,
1016 minion_remaining_rounds: 0,
1017 skeleton_revived: 0,
1018 },
1019 Class::Paladin => ClassData::Paladin {
1020 initial_armor_reduction: 0.0,
1021 stance: Stance::Regular,
1022 },
1023 Class::PlagueDoctor => ClassData::PlagueDoctor {
1024 poison_remaining_round: 0,
1025 poison_dmg_multis: [0.0, 0.0, 0.0],
1026 },
1027 };
1028 res.update_opponent(main, opponent);
1029 res
1030 }
1031}