From d7eaf4f8488af2354ad4fedbf5152c02eb49248e Mon Sep 17 00:00:00 2001 From: Malachy Byrne Date: Wed, 19 Jun 2024 21:46:36 +0100 Subject: [PATCH] Add resistances immunities and weaknesses and refactor in preparation for traits --- .../characters/PlayerCharacterTest.cs | 104 +++++++++++++++--- pflib-net.Tests/damage/DamageTest.cs | 16 +-- pflib-net.Tests/damage/DamageUtils.cs | 20 ++++ pflib-net/characters/ICharacter.cs | 4 +- pflib-net/characters/PlayerCharacter.cs | 45 ++++++-- pflib-net/damage/Damage.cs | 14 +-- 6 files changed, 164 insertions(+), 39 deletions(-) create mode 100644 pflib-net.Tests/damage/DamageUtils.cs diff --git a/pflib-net.Tests/characters/PlayerCharacterTest.cs b/pflib-net.Tests/characters/PlayerCharacterTest.cs index 0809a84..1e62489 100644 --- a/pflib-net.Tests/characters/PlayerCharacterTest.cs +++ b/pflib-net.Tests/characters/PlayerCharacterTest.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using pflib_net.characters; using pflib_net.characters.internals.proficiencies; using pflib_net.characters.internals.stats; +using pflib_net.Tests.damage; namespace pflib_net.Tests.characters; @@ -14,24 +15,85 @@ public class PlayerCharacterTest [Test] public void DamageReducesHp() { - var stats = new Stats(0, 0, 0, 0, 0, 0); - var armorValues = new Dictionary - { - { ProficiencyType.Unarmored, ProficiencyValue.Expert }, - { ProficiencyType.LightArmor, ProficiencyValue.None }, - { ProficiencyType.MediumArmor, ProficiencyValue.None }, - { ProficiencyType.HeavyArmor, ProficiencyValue.None } - }; - var armorProficiency = new ArmorProficiency(armorValues); - var creature = new PlayerCharacter(50, stats, 5, armorProficiency); - creature.ResolveAttack(20, 20); + var creature = CreateCharacter(); + var damage = DamageUtils.CreateDamage(); + creature.ResolveAttack(20, damage); Assert.That(creature.GetHp(), Is.LessThan(50)); } [Test] public void AttackFailsOnMiss() { - var stats = new Stats(0, 0, 0, 0, 0, 0); + var creature = CreateCharacter(); + var damage = DamageUtils.CreateDamage(); + creature.ResolveAttack(0, damage); + Assert.That(creature.GetHp(), Is.EqualTo(50)); + } + + [Test] + public void ImmunitiesApplyNoDamage() + { + var immunities = new HashSet + { + "fire" + }; + var creature = CreateCharacter(immunities: immunities); + creature.ResolveAttack(1000, DamageUtils.CreateDamage()); + Assert.That(creature.GetHp(), Is.Positive); + } + + [Test] + public void ResistancesReduceDamage() + { + var resistances = new Dictionary + { + { "fire", 5 } + }; + var creature1 = CreateCharacter(); + creature1.ResolveAttack(20, DamageUtils.CreateDamage()); + var creature2 = CreateCharacter(resistances: resistances); + creature2.ResolveAttack(20, DamageUtils.CreateDamage()); + Assert.That(creature2.GetHp() - creature1.GetHp(), Is.EqualTo(5)); + } + + [Test] + public void WeaknessIncreaseDamage() + { + var weaknesses = new Dictionary + { + { "fire", 5 } + }; + var creature1 = CreateCharacter(); + creature1.ResolveAttack(20, DamageUtils.CreateDamage()); + var creature2 = CreateCharacter(weaknesses: weaknesses); + creature2.ResolveAttack(20, DamageUtils.CreateDamage()); + Assert.That(creature1.GetHp() - creature2.GetHp(), Is.EqualTo(5)); + } + + [Test] + public void CannotTakeNegativeAmountsOfDamage() + { + var resistances = new Dictionary + { + { "fire", 45 } + }; + var creature = CreateCharacter(hp: 20, resistances: resistances); + var damage = DamageUtils.CreateDamage(); + creature.ResolveAttack(20, damage); + Assert.That(creature.GetHp(), Is.EqualTo(20)); + } + + private ICharacter CreateCharacter( + int hp = 50, + Stats stats = null, + int level = 5, + ArmorProficiency armorProficiency = null, + ISet immunities = null, + Dictionary resistances = null, + Dictionary weaknesses = null + ) + { + stats ??= new Stats(0, 0, 0, 0, 0, 0); var armorValues = new Dictionary { { ProficiencyType.Unarmored, ProficiencyValue.Expert }, @@ -39,9 +101,19 @@ public class PlayerCharacterTest { ProficiencyType.MediumArmor, ProficiencyValue.None }, { ProficiencyType.HeavyArmor, ProficiencyValue.None } }; - var armorProficiency = new ArmorProficiency(armorValues); - var creature = new PlayerCharacter(50, stats, 5, armorProficiency); - creature.ResolveAttack(0, 50); - Assert.That(creature.GetHp(), Is.EqualTo(50)); + armorProficiency ??= new ArmorProficiency(armorValues); + immunities ??= new HashSet(); + resistances ??= new Dictionary(); + weaknesses ??= new Dictionary(); + + return new PlayerCharacter( + hp, + stats, + level, + armorProficiency, + immunities, + resistances, + weaknesses + ); } } \ No newline at end of file diff --git a/pflib-net.Tests/damage/DamageTest.cs b/pflib-net.Tests/damage/DamageTest.cs index 60c96ce..8be393f 100644 --- a/pflib-net.Tests/damage/DamageTest.cs +++ b/pflib-net.Tests/damage/DamageTest.cs @@ -11,28 +11,28 @@ public class DamageTest [Test] public void GetDamageReturnsCorrectRegularAmount() { - var damage = new Damage(10, "fire"); - Assert.That(damage.GetDamage(false), Is.EqualTo(10)); + var damage = DamageUtils.CreateDamage(); + Assert.That(damage.GetDamage(false), Is.EqualTo(20)); } [Test] public void GetDamageReturnsCriticalAmount() { - var damage = new Damage(10, "fire", 30); + var damage = DamageUtils.CreateDamage(crit: 30); Assert.That(damage.GetDamage(true), Is.EqualTo(30)); } [Test] public void CriticalDamageIsDoubleRegular() { - var damage = new Damage(10, "fire"); - Assert.That(damage.GetDamage(true), Is.EqualTo(20)); + var damage = DamageUtils.CreateDamage(); + Assert.That(damage.GetDamage(true), Is.EqualTo(40)); } [Test] - public void GetDamageTypeReturnsCorrectType() + public void GetDamageTypeReturnsCorrectTraits() { - var damage = new Damage(0, "fire"); - Assert.That(damage.GetDamageType(), Is.EqualTo("fire")); + var damage = DamageUtils.CreateDamage(); + Assert.That(damage.GetTraits(), Does.Contain("fire")); } } \ No newline at end of file diff --git a/pflib-net.Tests/damage/DamageUtils.cs b/pflib-net.Tests/damage/DamageUtils.cs new file mode 100644 index 0000000..795f1fa --- /dev/null +++ b/pflib-net.Tests/damage/DamageUtils.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using pflib_net.damage; + +namespace pflib_net.Tests.damage; + +public static class DamageUtils +{ + public static Damage CreateDamage( + int damage = 20, + ISet traits = null, + int? crit = null + ) + { + traits ??= new HashSet + { + "fire" + }; + return new Damage(damage, traits, crit); + } +} \ No newline at end of file diff --git a/pflib-net/characters/ICharacter.cs b/pflib-net/characters/ICharacter.cs index a8fbaf0..a0522ec 100644 --- a/pflib-net/characters/ICharacter.cs +++ b/pflib-net/characters/ICharacter.cs @@ -1,3 +1,5 @@ +using pflib_net.damage; + namespace pflib_net.characters; public interface ICharacter @@ -7,7 +9,7 @@ public interface ICharacter /// /// /// - public void ResolveAttack(int roll, int damage); + public void ResolveAttack(int roll, Damage damage); /// /// Return the HP of the character diff --git a/pflib-net/characters/PlayerCharacter.cs b/pflib-net/characters/PlayerCharacter.cs index 0c169bf..308c38c 100644 --- a/pflib-net/characters/PlayerCharacter.cs +++ b/pflib-net/characters/PlayerCharacter.cs @@ -1,33 +1,64 @@ using System.Collections.Generic; using pflib_net.characters.internals.proficiencies; using pflib_net.characters.internals.stats; +using pflib_net.damage; namespace pflib_net.characters; public class PlayerCharacter : ICharacter { private int Hp { get; set; } - private Stats Stats { get; set; } - private int Level { get; set; } + private Stats Stats { get; } + private int Level { get; } private ArmorProficiency ArmorProficiency { get; } + private ISet Immunities { get; } + private Dictionary Resistances { get; } + private Dictionary Weaknesses { get; } - public PlayerCharacter(int hp, Stats stats, int level, ArmorProficiency armorProficiency) + public PlayerCharacter( + int hp, + Stats stats, + int level, + ArmorProficiency armorProficiency, + ISet immunities, + Dictionary resistances, + Dictionary weaknesses + ) { Hp = hp; Stats = stats; Level = level; ArmorProficiency = armorProficiency; + Immunities = immunities; + Resistances = resistances; + Weaknesses = weaknesses; } - - public void ResolveAttack(int roll, int damage) + public void ResolveAttack(int roll, Damage damage) { if (roll < GetAc()) return; - Hp -= damage; + ApplyDamage(damage, (roll >= GetAc() + 10)); } private int GetAc() { - return 10 + Stats.GetStat(StatType.Dexterity) + ArmorProficiency.GetValue(ProficiencyType.Unarmored, Level); + return 10 + Stats.GetStat(StatType.Dexterity) + + ArmorProficiency.GetValue(ProficiencyType.Unarmored, Level); + } + + private void ApplyDamage(Damage damage, bool crit) + { + var value = damage.GetDamage(crit); + if (Immunities.Intersect(damage.GetTraits()).Any()) + return; + foreach (var trait in damage.GetTraits()) + { + if (Weaknesses.TryGetValue(trait, out var weakness)) + value += weakness; + if (Resistances.TryGetValue(trait, out var resistance)) + value -= resistance; + } + if (value > 0) + Hp -= value; } public int GetHp() diff --git a/pflib-net/damage/Damage.cs b/pflib-net/damage/Damage.cs index 7a85049..0c9d59b 100644 --- a/pflib-net/damage/Damage.cs +++ b/pflib-net/damage/Damage.cs @@ -2,14 +2,14 @@ namespace pflib_net.damage; public class Damage { - private int Regular { get; set; } - private int Critical { get; set; } - private string Type { get; set; } + private int Regular { get; } + private int Critical { get; } + private ISet Traits { get; } - public Damage(int regular, string type, int? critical = null) + public Damage(int regular, ISet traits, int? critical = null) { Regular = regular; - Type = type; + Traits = traits; if (critical.HasValue) Critical = critical.Value; else @@ -21,8 +21,8 @@ public class Damage return crit ? Critical : Regular; } - public string GetDamageType() + public ISet GetTraits() { - return Type; + return Traits; } } \ No newline at end of file