Add resistances immunities and weaknesses and refactor in preparation for traits (#7)
All checks were successful
MANDATORY: Build project (Pflib Net) TeamCity build finished

Reviewed-on: #7
Co-authored-by: Malachy Byrne <malachybyrne1@gmail.com>
Co-committed-by: Malachy Byrne <malachybyrne1@gmail.com>
This commit is contained in:
Malachy Byrne 2024-06-19 22:59:38 +02:00 committed by git
parent cfa39e2a4a
commit 02740cfdf4
Signed by: git
GPG Key ID: 78AD94148C3A0433
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.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, ProficiencyValue>
{
{ 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<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>
{
{ 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<string>();
resistances ??= new Dictionary<string, int>();
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]
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"));
}
}

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;
public interface ICharacter
@ -7,7 +9,7 @@ public interface ICharacter
/// </summary>
/// <param name="roll"></param>
/// <param name="damage"></param>
public void ResolveAttack(int roll, int damage);
public void ResolveAttack(int roll, Damage damage);
/// <summary>
/// Return the HP of the character

View File

@ -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<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;
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()

View File

@ -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<string> Traits { get; }
public Damage(int regular, string type, int? critical = null)
public Damage(int regular, ISet<string> 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<string> GetTraits()
{
return Type;
return Traits;
}
}