Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import Monsters H-Z #30

Merged
merged 39 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
b272a5d
Import Monsters H
Jun 16, 2023
a7fccdb
Import Monsters H
Jun 16, 2023
0c0b7cb
Import Monsters H
Jun 16, 2023
57920e5
Import Monsters H
Jun 16, 2023
acbc458
Import Monsters I
Jun 16, 2023
b1dcded
Import Monsters I
Jun 16, 2023
77fb3c7
Import Monsters K
Jun 16, 2023
6aea4ae
Import Monsters K
Jun 16, 2023
a3f2978
Import Monsters L
Jun 20, 2023
7f6f591
Import Monsters M
Jun 20, 2023
659aac4
Import Monsters N
kupka Aug 13, 2023
0b6b58b
Monsters O
kupka Sep 18, 2023
d1f79ab
Tests
kupka Sep 18, 2023
ad87e00
Add codecov secret
kupka Sep 18, 2023
c4e79d9
Monsters_P
kupka Sep 26, 2023
94ef9ce
Monsters Q
kupka Sep 28, 2023
2636718
Monsters S
kupka Sep 23, 2024
f505d49
Monsters R
kupka Sep 23, 2024
2bedb75
Monsters T
kupka Sep 23, 2024
ab64f25
Monsters U
kupka Sep 23, 2024
5282f6f
Monsters X-Z
kupka Sep 25, 2024
678b92c
Monsters V-W
kupka Sep 26, 2024
adb3b24
Added default attack tests
kupka Sep 26, 2024
285fe89
Fixed mutable members
kupka Sep 26, 2024
7e3526c
Fix missing coverage
kupka Sep 26, 2024
c48f4fd
Fix missing attack assignment to monsters
kupka Sep 27, 2024
f75b9bd
Fix test
kupka Sep 27, 2024
272fb9c
Effect enumeration test
kupka Sep 27, 2024
ebbc660
More coverage
kupka Sep 30, 2024
ef14ea3
More coverage
kupka Sep 30, 2024
026f707
More coverage
kupka Sep 30, 2024
2435d7b
More coverage
kupka Sep 30, 2024
c162a64
More coverage
kupka Sep 30, 2024
e5fafd4
More coverage
kupka Sep 30, 2024
c9d4f6a
More coverage
kupka Sep 30, 2024
b2c8894
Updated README
kupka Sep 30, 2024
62d9ea6
Optimize
kupka Sep 30, 2024
be3cd69
Quasit Poison
kupka Sep 30, 2024
2a87e9d
Sign off final commit to branch
kupka Sep 30, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jobs:
- name: Publish coverage report to codecov
uses: codecov/codecov-action@main
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ${{ steps.get_lcov_path.outputs.lcov_path }}
fail_ci_if_error: true
# - name: Publish coverage report to coveralls.io
Expand Down
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,19 @@ Add the nuget package to your .NET project and start coding!

```dotnet add <yourproject>.csproj package libsrd5```

You can find examples how to integrate libsrd5 in a Blazor and Unity3D project [here](https://github.com/kupka/libsrd5-examples).
You can find examples how to integrate libsrd5 in a Blazor and Unity3D project [here](https://github.com/kupka/libsrd5-examples).
This example uses a very outdated version of libsrd5, an update will probably arrive end 2024.

## Copyright

libsrd5 Copyright 2021-2023, Thomas Kupka
libsrd5 Copyright 2021-2024, Thomas Kupka

## Current Status

The latest milestone finally contains **all** the monsters and their attacks from the SRD, though not all attack effects are fully implemented.
However, a ton already works, such as poison and grappling effects.

Next milestone will concentrate on implementing the spell effects, then I will work on the character classes.

## Acknowledgements

Expand Down
289 changes: 285 additions & 4 deletions libsrd5.tests/AttackEffectTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,22 @@ private static Monster createPansyMonster() {
return pansyMonster;
}

private Monster pansyMonsterThatDies = createPansyMonsterThatDies();

private static Monster createPansyMonsterThatDies() {
Monster pansyMonster = new Monster(
Monsters.Type.BEAST, Monsters.ID.GOAT, Alignment.LAWFUL_EVIL, 2, 1, 1, 1, 1, 1, 1, "1d1", 40, 16,
new Attack[] { }, new Attack[] { }, Size.MEDIUM
);
pansyMonster.AddEffects(Effect.FAIL_STRENGTH_CHECK, Effect.FAIL_DEXERITY_CHECK, Effect.FAIL_CONSTITUTION_CHECK);
return pansyMonster;
}

private void restoreMonsters() {
uberMonster = createUberMonster();
averageMonster = createAverageMonster();
pansyMonster = createPansyMonster();
pansyMonsterThatDies = createPansyMonsterThatDies();
}

private readonly Dices dices = new Dices("1d12-1");
Expand All @@ -43,13 +55,16 @@ private DamageType randomDamageType() {
return (DamageType)value;
}

private void attackEffectTest(AttackEffect effect) {
private void attackEffectTest(AttackEffect effect, bool guaranteedLethal = false) {
for (int i = 0; i < 10; i++) {
restoreMonsters();
effect.Invoke(Monsters.Aboleth, uberMonster);
effect.Invoke(Monsters.Aboleth, averageMonster);
effect.Invoke(Monsters.Aboleth, pansyMonster);

if (guaranteedLethal) {
effect.Invoke(Monsters.Aboleth, pansyMonsterThatDies);
Assert.True(pansyMonsterThatDies.Dead);
}
for (int j = 0; j < 20; j++) {
uberMonster.TakeDamage(randomDamageType(), Dice.D4.Value);
uberMonster.EscapeFromGrapple();
Expand Down Expand Up @@ -191,6 +206,150 @@ public void TestAttackEffects_G() {
attackEffectTest(Attacks.GuardianNagaSpitPoisonEffect);
}

[Fact]
public void TestAttackEffects_H() {
attackEffectTest(Attacks.HalfRedDragonVeteranLongswordEffect);
attackEffectTest(Attacks.HomunculusBiteEffect);
attackEffectTest(Attacks.HornedDevilHurlFlameEffect);
attackEffectTest(Attacks.HornedDevilTailEffect);
}

[Fact]
public void TestAttackEffects_I() {
attackEffectTest(Attacks.ImpStingEffect);
}

[Fact]
public void TestAttackEffects_K() {
attackEffectTest(Attacks.KrakenBiteEffect);
attackEffectTest(Attacks.KrakenTentacleEffect);
}

[Fact]
public void TestAttackEffects_L() {
attackEffectTest(Attacks.LichParalyzingTouchEffect);
}

[Fact]
public void TestAttackEffects_M() {
attackEffectTest(Attacks.MagminTouchEffect);
attackEffectTest(Attacks.MammothStompEffect);
attackEffectTest(Attacks.MarilithTailEffect);
attackEffectTest(Attacks.MastiffBiteEffect);
attackEffectTest(Attacks.MerrowHarpoonEffect);
attackEffectTest(Attacks.MimicPseudopodEffect);
attackEffectTest(Attacks.MummyLordRottingFistEffect);
attackEffectTest(Attacks.MummyRottingFistEffect);
}

[Fact]
public void TestAttackEffects_O() {
attackEffectTest(Attacks.OctopusInkCloudEffect);
attackEffectTest(Attacks.OctopusTentaclesEffect);
attackEffectTest(Attacks.OtyughBiteEffect);
attackEffectTest(Attacks.OtyughTentacleEffect);
}

[Fact]
public void TestAttackEffects_P() {
attackEffectTest(Attacks.PhaseSpiderBiteEffect);
attackEffectTest(Attacks.PitFiendBiteEffect);
attackEffectTest(Attacks.PoisonousSnakeBiteEffect);
attackEffectTest(Attacks.PseudodragonStingEffect);
attackEffectTest(Attacks.PurpleWormTailStingerEffect);
}

[Fact]
public void TestAttackEffects_Q() {
attackEffectTest(Attacks.QuasitClawEffect);
}

[Fact]
public void TestAttackEffects_R() {
attackEffectTest(Attacks.RakshasaClawEffect);
attackEffectTest(Attacks.RemorhazBiteEffect);
attackEffectTest(Attacks.RocTalonsEffect);
attackEffectTest(Attacks.RoperTendrilEffect);
attackEffectTest(Attacks.RugOfSmotheringSmotherEffect);
}

[Fact]
public void TestAttackEffects_S() {
attackEffectTest(Attacks.SalamanderTailEffect);
attackEffectTest(Attacks.ScorpionStingEffect);
attackEffectTest(Attacks.ScoutLongbowEffect);
attackEffectTest(Attacks.ShadowStrengthDrainEffect);
attackEffectTest(Attacks.SolarSlayingLongbowEffect, true);
attackEffectTest(Attacks.SpecterLifeDrainEffect, true);
attackEffectTest(Attacks.SpiderBiteEffect);
attackEffectTest(Attacks.SpiritNagaBiteEffect);
attackEffectTest(Attacks.SpriteShortbowEffect);
attackEffectTest(Attacks.StirgeBloodDrainEffect);
attackEffectTest(Attacks.StoneGiantRockEffect);
attackEffectTest(Attacks.SuccubusDrainingKissEffect, true);
attackEffectTest(Attacks.SwarmOfBatsBitesEffect);
attackEffectTest(Attacks.SwarmOfBeetlesBitesEffect);
attackEffectTest(Attacks.SwarmOfCentipedesBitesEffect);
attackEffectTest(Attacks.SwarmOfInsectsBitesEffect);
attackEffectTest(Attacks.SwarmOfPoisonousSnakesBitesEffect);
attackEffectTest(Attacks.SwarmOfQuippersBitesEffect);
attackEffectTest(Attacks.SwarmOfRatsBitesEffect);
attackEffectTest(Attacks.SwarmOfRavensBeaksEffect);
attackEffectTest(Attacks.SwarmOfSpidersBitesEffect);
attackEffectTest(Attacks.SwarmOfWaspsBitesEffect);
}

[Fact]
public void TestAttackEffects_T() {
attackEffectTest(Attacks.TarrasqueBiteEffect);
attackEffectTest(Attacks.TarrasqueTailEffect);
attackEffectTest(Attacks.TriceratopsStompEffect);
attackEffectTest(Attacks.TyrannosaurusRexBiteEffect);
}

[Fact]
public void TestAttackEffects_U() {
// None here
}

[Fact]
public void TestAttackEffects_V() {
attackEffectTest(Attacks.VampireBiteEffect);
attackEffectTest(Attacks.VampireSpawnBiteEffect);
attackEffectTest(Attacks.VampireSpawnClawsEffect);
attackEffectTest(Attacks.VampireUnarmedStrikeEffect);
}

[Fact]
public void TestAttackEffects_W() {
attackEffectTest(Attacks.WerebearBiteEffect);
attackEffectTest(Attacks.WereboarTusksEffect);
attackEffectTest(Attacks.WereratBiteEffect);
attackEffectTest(Attacks.WeretigerBiteEffect);
attackEffectTest(Attacks.WerewolfBiteEffect);
attackEffectTest(Attacks.WightLifeDrainEffect, true);
attackEffectTest(Attacks.WinterWolfBiteEffect);
attackEffectTest(Attacks.WolfBiteEffect);
attackEffectTest(Attacks.WorgBiteEffect);
attackEffectTest(Attacks.WraithLifeDrainEffect, true);
attackEffectTest(Attacks.WyvernStingerEffect);
}

[Fact]
public void TestAttackEffects_X() {
// None here
}

[Fact]
public void TestAttackEffects_Y() {
// None here
}

[Fact]
public void TestAttackEffects_Z() {
// None here
}

[Fact]
public void AssassinShortswordTest() {
Monster undead = Monsters.Ghost; // immune to poison
Expand All @@ -213,9 +372,19 @@ public void BeardedDevilGlaiveEffect() {
Monster golem = Monsters.ClayGolem;
Monster shadow = Monsters.Shadow;
Attacks.BeardedDevilGlaiveEffect.Invoke(Monsters.Aboleth, golem); // don't affect construct
Assert.False(golem.HasEffect(Effect.INFERNAL_WOUND));
Assert.False(golem.HasEffect(Effect.INFERNAL_WOUND_BEARDED_DEVIL));
Attacks.BeardedDevilGlaiveEffect.Invoke(Monsters.Aboleth, shadow); // don't affect undead
Assert.False(shadow.HasEffect(Effect.INFERNAL_WOUND));
Assert.False(shadow.HasEffect(Effect.INFERNAL_WOUND_BEARDED_DEVIL));
}

[Fact]
public void HornedDeveilTailEffect() {
Monster golem = Monsters.ClayGolem;
Monster shadow = Monsters.Shadow;
Attacks.HornedDevilTailEffect.Invoke(Monsters.Aboleth, golem); // don't affect construct
Assert.False(golem.HasEffect(Effect.INFERNAL_WOUND_BEARDED_DEVIL));
Attacks.HornedDevilTailEffect.Invoke(Monsters.Aboleth, shadow); // don't affect undead
Assert.False(shadow.HasEffect(Effect.INFERNAL_WOUND_BEARDED_DEVIL));
}

[Fact]
Expand Down Expand Up @@ -375,6 +544,12 @@ public void ProneEffectTest() {
hp = target1.HitPoints;
Attacks.GiantElkHoovesEffect.Invoke(Monsters.GiantElk, target1);
Assert.True(target1.HitPoints < hp);
hp = target1.HitPoints;
Attacks.MammothStompEffect.Invoke(Monsters.Mammoth, target1);
Assert.True(target1.HitPoints < hp);
hp = target1.HitPoints;
Attacks.TriceratopsStompEffect.Invoke(Monsters.Triceratops, target1);
Assert.True(target1.HitPoints < hp);
}

[Fact]
Expand Down Expand Up @@ -414,5 +589,111 @@ public void GiantCrabClawTest() {
Assert.True(n00b2.HasCondition(ConditionType.GRAPPLED_DC11));
Assert.False(n00b3.HasCondition(ConditionType.GRAPPLED_DC11));
}

[Fact]
public void GrapplingEffectTest() {
// Water Elemental cannot be grappled
Monster waterElemental = Monsters.WaterElemental;
for (int i = 0; i < 100; i++) {
AttackEffects.GrapplingEffect(Monsters.AdultBlackDragon, waterElemental, 20, Size.GARGANTUAN);
}
Assert.False(waterElemental.HasCondition(ConditionType.GRAPPLED_DC20));
}

[Fact]
public void HomunculusBiteEffectTest() {
Monster bandit = Monsters.Bandit;
while (!bandit.HasEffect(Effect.HOMUNCULUS_POISON_UNCONCIOUSNESS)) {
Attacks.HomunculusBiteEffect(Monsters.Homunculus, bandit);
}
Assert.True(bandit.HasEffect(Effect.HOMUNCULUS_POISON_UNCONCIOUSNESS));
for (int i = 0; i < 101; i++) {
bandit.OnEndOfTurn();
}
Assert.False(bandit.HasEffect(Effect.HOMUNCULUS_POISON_UNCONCIOUSNESS));
}

[Fact]
public void KrakenTest() {
Monster pansyMonster = createPansyMonster();
Monster uberMonster = createUberMonster();
Monster luckyMonster = createPansyMonster();
Monster kraken = Monsters.Kraken;
// Grab the monsters
for (int i = 0; i < 5; i++) {
Attacks.KrakenTentacleEffect.Invoke(kraken, pansyMonster);
Attacks.KrakenTentacleEffect.Invoke(kraken, uberMonster);
}
Assert.True(pansyMonster.HasCondition(ConditionType.GRAPPLED_DC18));
Assert.True(pansyMonster.HasCondition(ConditionType.GRAPPLED_DC18));
Assert.False(luckyMonster.HasCondition(ConditionType.GRAPPLED_DC18));
// Try to swallow the monsters
Attacks.KrakenBiteEffect.Invoke(kraken, pansyMonster);
Assert.True(pansyMonster.HasEffect(Effect.KRAKEN_SWALLOW));
Attacks.KrakenBiteEffect(kraken, uberMonster); // cannot swallow huge monsters
Assert.False(uberMonster.HasEffect(Effect.KRAKEN_SWALLOW));
Attacks.KrakenBiteEffect(kraken, luckyMonster); // cannot swallow monsters that are not grappled
Assert.False(luckyMonster.HasEffect(Effect.KRAKEN_SWALLOW));
int hitpoints = pansyMonster.HitPoints;
// digest the monster
kraken.OnStartOfTurn();
Assert.True(pansyMonster.HitPoints < hitpoints);
hitpoints = pansyMonster.HitPoints;
pansyMonster.RemoveEffect(Effect.KRAKEN_SWALLOW);
kraken.OnStartOfTurn();
Assert.Equal(hitpoints, pansyMonster.HitPoints);
}

[Fact]
public void PhaseSpiderBiteEffect() {
Monster pansy = createPansyMonsterThatDies();
Attacks.PhaseSpiderBiteEffect(Monsters.PhaseSpider, pansy);
Assert.True(pansy.HasEffect(Effect.PHASE_SPIDER_POISON));
}

[Fact]
public void SolarTest() {
bool died = false;
bool survived = false;
for (int i = 0; i < 100; i++) {
Monster bandit = Monsters.Bandit;
Attacks.SolarSlayingLongbowEffect(Monsters.Solar, bandit);
if (bandit.Dead) {
died = true;
} else {
survived = true;
}
}
Assert.True(died && survived);
}

[Fact]
public void StirgeBloodDrainEffectTest() {
Monster stirge1 = Monsters.Stirge;
Monster stirge2 = Monsters.Stirge;
Monster stirge3 = Monsters.Stirge;
Monster ogre = Monsters.Ogre;
int hpOgre = ogre.HitPoints;
// Undead and Constructs are immune
Monster zombie = Monsters.OgreZombie;
int hpZombie = zombie.HitPoints;
Monster golem = Monsters.IronGolem;
int hpGolem = golem.HitPoints;
Attacks.StirgeBloodDrainEffect(stirge1, ogre);
ogre.OnStartOfTurn();
ogre.RemoveEffect(Effect.STIRGE_BLOOD_DRAIN_EFFECT);
ogre.OnStartOfTurn();
Assert.Null(ogre.StartOfTurnEvents[0]);
Assert.True(hpOgre > ogre.HitPoints);
Assert.True(stirge1.HasEffect(Effect.STIRGE_BLOOD_DRAINING));
Attacks.StirgeBloodDrainEffect(stirge2, zombie);
zombie.OnStartOfTurn();
Assert.Equal(hpZombie, zombie.HitPoints);
Assert.False(stirge2.HasEffect(Effect.STIRGE_BLOOD_DRAINING));
Attacks.StirgeBloodDrainEffect(stirge3, golem);
golem.OnStartOfTurn();
Assert.Equal(hpGolem, golem.HitPoints);
Assert.False(stirge3.HasEffect(Effect.STIRGE_BLOOD_DRAINING));
}
}
}
Loading
Loading