Add resistances immunities and weaknesses and refactor in preparation for traits #7

Merged
malmal200 merged 1 commits from PF-2 into master 2024-06-19 22:59:38 +02:00
6 changed files with 164 additions and 39 deletions

View File

@ -3,6 +3,7 @@ using NUnit.Framework;
using pflib_net.characters; using pflib_net.characters;
using pflib_net.characters.internals.proficiencies; using pflib_net.characters.internals.proficiencies;
using pflib_net.characters.internals.stats; using pflib_net.characters.internals.stats;
using pflib_net.Tests.damage;
namespace pflib_net.Tests.characters; namespace pflib_net.Tests.characters;
@ -14,24 +15,85 @@ public class PlayerCharacterTest
[Test] [Test]
public void DamageReducesHp() public void DamageReducesHp()
{ {
var stats = new Stats(0, 0, 0, 0, 0, 0); var creature = CreateCharacter();
var armorValues = new Dictionary<ProficiencyType, ProficiencyValue> var damage = DamageUtils.CreateDamage();
{ creature.ResolveAttack(20, damage);
{ 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);
Assert.That(creature.GetHp(), Is.LessThan(50)); Assert.That(creature.GetHp(), Is.LessThan(50));
} }
[Test] [Test]
public void AttackFailsOnMiss() 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<string>
{
"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<string, int>
{
{ "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<string, int>
{
{ "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<string, int>
{
{ "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<string> immunities = null,
Dictionary<string, int> resistances = null,
Dictionary<string, int> weaknesses = null
)
{
stats ??= new Stats(0, 0, 0, 0, 0, 0);
var armorValues = new Dictionary<ProficiencyType, ProficiencyValue> var armorValues = new Dictionary<ProficiencyType, ProficiencyValue>
{ {
{ ProficiencyType.Unarmored, ProficiencyValue.Expert }, { ProficiencyType.Unarmored, ProficiencyValue.Expert },
@ -39,9 +101,19 @@ public class PlayerCharacterTest
{ ProficiencyType.MediumArmor, ProficiencyValue.None }, { ProficiencyType.MediumArmor, ProficiencyValue.None },
{ ProficiencyType.HeavyArmor, ProficiencyValue.None } { ProficiencyType.HeavyArmor, ProficiencyValue.None }
}; };
var armorProficiency = new ArmorProficiency(armorValues); armorProficiency ??= new ArmorProficiency(armorValues);
var creature = new PlayerCharacter(50, stats, 5, armorProficiency); immunities ??= new HashSet<string>();
creature.ResolveAttack(0, 50); resistances ??= new Dictionary<string, int>();
Assert.That(creature.GetHp(), Is.EqualTo(50)); weaknesses ??= new Dictionary<string, int>();
return new PlayerCharacter(
hp,
stats,
level,
armorProficiency,
immunities,
resistances,
weaknesses
);
} }
} }

View File

@ -11,28 +11,28 @@ public class DamageTest
[Test] [Test]
public void GetDamageReturnsCorrectRegularAmount() public void GetDamageReturnsCorrectRegularAmount()
{ {
var damage = new Damage(10, "fire"); var damage = DamageUtils.CreateDamage();
Assert.That(damage.GetDamage(false), Is.EqualTo(10)); Assert.That(damage.GetDamage(false), Is.EqualTo(20));
} }
[Test] [Test]
public void GetDamageReturnsCriticalAmount() public void GetDamageReturnsCriticalAmount()
{ {
var damage = new Damage(10, "fire", 30); var damage = DamageUtils.CreateDamage(crit: 30);
Assert.That(damage.GetDamage(true), Is.EqualTo(30)); Assert.That(damage.GetDamage(true), Is.EqualTo(30));
} }
[Test] [Test]
public void CriticalDamageIsDoubleRegular() public void CriticalDamageIsDoubleRegular()
{ {
var damage = new Damage(10, "fire"); var damage = DamageUtils.CreateDamage();
Assert.That(damage.GetDamage(true), Is.EqualTo(20)); Assert.That(damage.GetDamage(true), Is.EqualTo(40));
} }
[Test] [Test]
public void GetDamageTypeReturnsCorrectType() public void GetDamageTypeReturnsCorrectTraits()
{ {
var damage = new Damage(0, "fire"); var damage = DamageUtils.CreateDamage();
Assert.That(damage.GetDamageType(), Is.EqualTo("fire")); Assert.That(damage.GetTraits(), Does.Contain("fire"));
} }
} }

View File

@ -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<string> traits = null,
int? crit = null
)
{
traits ??= new HashSet<string>
{
"fire"
};
return new Damage(damage, traits, crit);
}
}

View File

@ -1,3 +1,5 @@
using pflib_net.damage;
namespace pflib_net.characters; namespace pflib_net.characters;
public interface ICharacter public interface ICharacter
@ -7,7 +9,7 @@ public interface ICharacter
/// </summary> /// </summary>
/// <param name="roll"></param> /// <param name="roll"></param>
/// <param name="damage"></param> /// <param name="damage"></param>
public void ResolveAttack(int roll, int damage); public void ResolveAttack(int roll, Damage damage);
/// <summary> /// <summary>
/// Return the HP of the character /// Return the HP of the character

View File

@ -1,33 +1,64 @@
using System.Collections.Generic; using System.Collections.Generic;
using pflib_net.characters.internals.proficiencies; using pflib_net.characters.internals.proficiencies;
using pflib_net.characters.internals.stats; using pflib_net.characters.internals.stats;
using pflib_net.damage;
namespace pflib_net.characters; namespace pflib_net.characters;
public class PlayerCharacter : ICharacter public class PlayerCharacter : ICharacter
{ {
private int Hp { get; set; } private int Hp { get; set; }
private Stats Stats { get; set; } private Stats Stats { get; }
private int Level { get; set; } private int Level { get; }
private ArmorProficiency ArmorProficiency { get; } private ArmorProficiency ArmorProficiency { get; }
private ISet<string> Immunities { get; }
private Dictionary<string, int> Resistances { get; }
private Dictionary<string, int> Weaknesses { get; }
public PlayerCharacter(int hp, Stats stats, int level, ArmorProficiency armorProficiency) public PlayerCharacter(
int hp,
Stats stats,
int level,
ArmorProficiency armorProficiency,
ISet<string> immunities,
Dictionary<string, int> resistances,
Dictionary<string, int> weaknesses
)
{ {
Hp = hp; Hp = hp;
Stats = stats; Stats = stats;
Level = level; Level = level;
ArmorProficiency = armorProficiency; ArmorProficiency = armorProficiency;
Immunities = immunities;
Resistances = resistances;
Weaknesses = weaknesses;
} }
public void ResolveAttack(int roll, Damage damage)
public void ResolveAttack(int roll, int damage)
{ {
if (roll < GetAc()) return; if (roll < GetAc()) return;
Hp -= damage; ApplyDamage(damage, (roll >= GetAc() + 10));
} }
private int GetAc() 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() public int GetHp()

View File

@ -2,14 +2,14 @@ namespace pflib_net.damage;
public class Damage public class Damage
{ {
private int Regular { get; set; } private int Regular { get; }
private int Critical { get; set; } private int Critical { get; }
private string Type { get; set; } private ISet<string> Traits { get; }
public Damage(int regular, string type, int? critical = null) public Damage(int regular, ISet<string> traits, int? critical = null)
{ {
Regular = regular; Regular = regular;
Type = type; Traits = traits;
if (critical.HasValue) if (critical.HasValue)
Critical = critical.Value; Critical = critical.Value;
else else
@ -21,8 +21,8 @@ public class Damage
return crit ? Critical : Regular; return crit ? Critical : Regular;
} }
public string GetDamageType() public ISet<string> GetTraits()
{ {
return Type; return Traits;
} }
} }