feat(rules): conform bulls strength, bears endurance and shield to the SRD
Bull's Strength now grants the SRD +4 enhancement bonus to Strength as +2 melee attack, +2 melee damage and +2 CMB (three modifiers instead of the previous +1/+1 approximation). Shield now uses a real shield bonus type (+4 AC) instead of approximating it as deflection: the engine's BonusType literal and stacking docs gain the shield type, which stays out of the CMD bonus list (SRD Combat Maneuver Defense). Bear's Endurance keeps its +2 Fortitude / +2 hp per HD and its description now cites the SRD and flags the hp modifier as informational. Golden transcripts updated: the buff log reports 3 mods and Tammara's pistol attacks gain the extra +1 enhancement (d20=10+9-6=15). Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -16,5 +16,5 @@
|
|||||||
"duration_rounds": 60
|
"duration_rounds": 60
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "The subject gains a +4 enhancement bonus to Constitution, granting +2 Fortitude and +2 hit points (approximated as +2/+2). Fortitude negates (harmless). Duration 10 min/level approximated as 60 rounds."
|
"description": "The subject gains a +4 enhancement bonus to Constitution, granting +2 to Fortitude saves and +2 hit points per Hit Die (SRD bear's endurance; the hp modifier is informational — the engine has no per-HD hp pool). Will negates (harmless). Duration 10 min/level approximated as 60 rounds."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,11 +10,12 @@
|
|||||||
{
|
{
|
||||||
"type": "buff",
|
"type": "buff",
|
||||||
"modifiers": [
|
"modifiers": [
|
||||||
{ "target": "attack", "value": 1, "bonus_type": "enhancement" },
|
{ "target": "attack", "value": 2, "bonus_type": "enhancement" },
|
||||||
{ "target": "damage", "value": 1, "bonus_type": "enhancement" }
|
{ "target": "damage", "value": 2, "bonus_type": "enhancement" },
|
||||||
|
{ "target": "cmb", "value": 2, "bonus_type": "enhancement" }
|
||||||
],
|
],
|
||||||
"duration_rounds": 60
|
"duration_rounds": 60
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "The subject gains a +4 enhancement bonus to Strength, granting +2 attack and +2 damage (approximated as +1/+1). Fortitude negates (harmless)."
|
"description": "The subject gains a +4 enhancement bonus to Strength, granting +2 to melee attack rolls, melee damage rolls, and combat maneuver checks (SRD bull's strength). Will negates (harmless)."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,10 @@
|
|||||||
{
|
{
|
||||||
"type": "buff",
|
"type": "buff",
|
||||||
"modifiers": [
|
"modifiers": [
|
||||||
{ "target": "ac", "value": 4, "bonus_type": "deflection" }
|
{ "target": "ac", "value": 4, "bonus_type": "shield" }
|
||||||
],
|
],
|
||||||
"duration_rounds": 10
|
"duration_rounds": 10
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"description": "An invisible shield of force hovers in front of you, granting a +4 shield bonus to AC for 1 min./level (modeled as 10 rounds; the shield bonus type is approximated as deflection since the engine has no shield bonus type). Negating magic missile and the ranged-attack deflection are not modeled."
|
"description": "An invisible shield of force hovers in front of you, granting a +4 shield bonus to AC for 1 min./level (modeled as 10 rounds). Negating magic missile and the ranged-attack deflection are not modeled."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
PF1e bonus types determine whether multiple bonuses to the same stat stack:
|
PF1e bonus types determine whether multiple bonuses to the same stat stack:
|
||||||
- Stacking types (dodge, racial, trait, untyped/"") all sum together.
|
- Stacking types (dodge, racial, trait, untyped/"") all sum together.
|
||||||
- Non-stacking types (morale, sacred, profane, competence, insight, luck,
|
- Non-stacking types (morale, sacred, profane, competence, insight, luck,
|
||||||
enhancement, deflection, natural) keep only the highest value per type.
|
enhancement, deflection, natural, shield) keep only the highest value per
|
||||||
|
type.
|
||||||
- Penalties follow the same rules: same-type penalties don't stack (the
|
- Penalties follow the same rules: same-type penalties don't stack (the
|
||||||
least severe — i.e. the max value — is kept).
|
least severe — i.e. the max value — is kept).
|
||||||
|
|
||||||
@@ -36,6 +37,7 @@ BonusType = Literal[
|
|||||||
"enhancement", # no stack
|
"enhancement", # no stack
|
||||||
"deflection", # no stack
|
"deflection", # no stack
|
||||||
"natural", # no stack
|
"natural", # no stack
|
||||||
|
"shield", # no stack
|
||||||
"circumstance", # no stack (simulator can't distinguish circumstances)
|
"circumstance", # no stack (simulator can't distinguish circumstances)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -64,28 +64,28 @@ class TestGoldenCombats:
|
|||||||
assert result.transcript == (
|
assert result.transcript == (
|
||||||
"initiative: tammara d20=15+7=22",
|
"initiative: tammara d20=15+7=22",
|
||||||
"initiative: goblin d20=10+6=16",
|
"initiative: goblin d20=10+6=16",
|
||||||
("round 1 tammara: cast Bull's Strength vs tammara DC15 SAVED -> buff 2 mods (60r) "
|
("round 1 tammara: cast Bull's Strength vs tammara DC15 SAVED -> buff 3 mods (60r) "
|
||||||
"(36->36)"),
|
"(36->36)"),
|
||||||
"round 1 goblin: short bow vs tammara d20=15+4-2=17 AC 20 -> MISS",
|
"round 1 goblin: short bow vs tammara d20=15+4-2=17 AC 20 -> MISS",
|
||||||
("round 2 tammara: cast Bull's Strength vs tammara DC15 SAVED -> buff 2 mods (60r) "
|
("round 2 tammara: cast Bull's Strength vs tammara DC15 SAVED -> buff 3 mods (60r) "
|
||||||
"(36->36)"),
|
"(36->36)"),
|
||||||
"round 2 goblin: short bow vs tammara d20=15+4-2=17 AC 20 -> MISS",
|
"round 2 goblin: short bow vs tammara d20=15+4-2=17 AC 20 -> MISS",
|
||||||
("round 3 tammara: cast Bull's Strength vs tammara DC15 SAVED -> buff 2 mods (60r) "
|
("round 3 tammara: cast Bull's Strength vs tammara DC15 SAVED -> buff 3 mods (60r) "
|
||||||
"(36->36)"),
|
"(36->36)"),
|
||||||
"round 3 goblin: short bow vs tammara d20=15+4-2=17 AC 20 -> MISS",
|
"round 3 goblin: short bow vs tammara d20=15+4-2=17 AC 20 -> MISS",
|
||||||
"round 4 tammara: Braises d'Automne (pistolet ?) vs goblin d20=10+9-6=14 AC 16 -> MISS",
|
"round 4 tammara: Braises d'Automne (pistolet ?) vs goblin d20=10+9-6=15 AC 16 -> MISS",
|
||||||
"round 4 goblin: short bow vs tammara d20=15+4-2=17 AC 20 -> MISS",
|
"round 4 goblin: short bow vs tammara d20=15+4-2=17 AC 20 -> MISS",
|
||||||
"round 5 tammara: Braises d'Automne (pistolet ?) vs goblin d20=10+9-6=14 AC 16 -> MISS",
|
"round 5 tammara: Braises d'Automne (pistolet ?) vs goblin d20=10+9-6=15 AC 16 -> MISS",
|
||||||
"round 5 goblin: short bow vs tammara d20=15+4-2=17 AC 20 -> MISS",
|
"round 5 goblin: short bow vs tammara d20=15+4-2=17 AC 20 -> MISS",
|
||||||
"round 6 tammara: Braises d'Automne (pistolet ?) vs goblin d20=10+9-6=14 AC 16 -> MISS",
|
"round 6 tammara: Braises d'Automne (pistolet ?) vs goblin d20=10+9-6=15 AC 16 -> MISS",
|
||||||
"round 6 goblin: short bow vs tammara d20=15+4-2=17 AC 20 -> MISS",
|
"round 6 goblin: short bow vs tammara d20=15+4-2=17 AC 20 -> MISS",
|
||||||
"round 7 tammara: Braises d'Automne (pistolet ?) vs goblin d20=10+9-6=14 AC 16 -> MISS",
|
"round 7 tammara: Braises d'Automne (pistolet ?) vs goblin d20=10+9-6=15 AC 16 -> MISS",
|
||||||
"round 7 goblin: short bow vs tammara d20=15+4-2=17 AC 20 -> MISS",
|
"round 7 goblin: short bow vs tammara d20=15+4-2=17 AC 20 -> MISS",
|
||||||
"round 8 tammara: Braises d'Automne (pistolet ?) vs goblin d20=10+9-6=14 AC 16 -> MISS",
|
"round 8 tammara: Braises d'Automne (pistolet ?) vs goblin d20=10+9-6=15 AC 16 -> MISS",
|
||||||
"round 8 goblin: short bow vs tammara d20=15+4-2=17 AC 20 -> MISS",
|
"round 8 goblin: short bow vs tammara d20=15+4-2=17 AC 20 -> MISS",
|
||||||
"round 9 tammara: Braises d'Automne (pistolet ?) vs goblin d20=10+9-6=14 AC 16 -> MISS",
|
"round 9 tammara: Braises d'Automne (pistolet ?) vs goblin d20=10+9-6=15 AC 16 -> MISS",
|
||||||
"round 9 goblin: short bow vs tammara d20=15+4-2=17 AC 20 -> MISS",
|
"round 9 goblin: short bow vs tammara d20=15+4-2=17 AC 20 -> MISS",
|
||||||
("round 10 tammara: Braises d'Automne (pistolet ?) vs goblin d20=10+9-6=14 AC 16 "
|
("round 10 tammara: Braises d'Automne (pistolet ?) vs goblin d20=10+9-6=15 AC 16 "
|
||||||
"-> MISS"),
|
"-> MISS"),
|
||||||
"round 10 goblin: short bow vs tammara d20=15+4-2=17 AC 20 -> MISS",
|
"round 10 goblin: short bow vs tammara d20=15+4-2=17 AC 20 -> MISS",
|
||||||
"battle over: draw after 10 rounds",
|
"battle over: draw after 10 rounds",
|
||||||
@@ -106,7 +106,7 @@ class TestGoldenCombats:
|
|||||||
assert result.transcript == (
|
assert result.transcript == (
|
||||||
"initiative: tammara d20=15+7=22",
|
"initiative: tammara d20=15+7=22",
|
||||||
"initiative: lich d20=10+6=16",
|
"initiative: lich d20=10+6=16",
|
||||||
("round 1 tammara: cast Bull's Strength vs tammara DC15 SAVED -> buff 2 mods (60r) "
|
("round 1 tammara: cast Bull's Strength vs tammara DC15 SAVED -> buff 3 mods (60r) "
|
||||||
"(36->36)"),
|
"(36->36)"),
|
||||||
"round 1 lich: cast Magic Missile vs tammara DC16 NO SAVE -> 43 damage (36->-7)",
|
"round 1 lich: cast Magic Missile vs tammara DC16 NO SAVE -> 43 damage (36->-7)",
|
||||||
"battle over: monsters win in 1 rounds",
|
"battle over: monsters win in 1 rounds",
|
||||||
|
|||||||
@@ -193,7 +193,9 @@ class TestSpellLoading:
|
|||||||
spell = load_spell(_SPELLS_DIR / "bulls_strength.json")
|
spell = load_spell(_SPELLS_DIR / "bulls_strength.json")
|
||||||
eff = spell.effects[0]
|
eff = spell.effects[0]
|
||||||
assert isinstance(eff, BuffEffect)
|
assert isinstance(eff, BuffEffect)
|
||||||
assert len(eff.modifiers) == 2
|
# SRD bull's strength: +4 enhancement Str -> +2 melee attack, +2 melee
|
||||||
|
# damage, +2 CMB (three modifiers).
|
||||||
|
assert len(eff.modifiers) == 3
|
||||||
|
|
||||||
def test_summon_monster_casts_for_one_round(self) -> None:
|
def test_summon_monster_casts_for_one_round(self) -> None:
|
||||||
# PF1e: Summon Monster I/II have a 1-round casting time.
|
# PF1e: Summon Monster I/II have a 1-round casting time.
|
||||||
@@ -616,10 +618,13 @@ class TestBullsStrength:
|
|||||||
buff_mods = [m for b in t.active_buffs for m in b.modifiers]
|
buff_mods = [m for b in t.active_buffs for m in b.modifiers]
|
||||||
atk_mods = [e for e in buff_mods if e.target == "attack"]
|
atk_mods = [e for e in buff_mods if e.target == "attack"]
|
||||||
dmg_mods = [e for e in buff_mods if e.target == "damage"]
|
dmg_mods = [e for e in buff_mods if e.target == "damage"]
|
||||||
|
cmb_mods = [e for e in buff_mods if e.target == "cmb"]
|
||||||
assert len(atk_mods) == 1
|
assert len(atk_mods) == 1
|
||||||
assert len(dmg_mods) == 1
|
assert len(dmg_mods) == 1
|
||||||
assert atk_mods[0].value == 1
|
assert len(cmb_mods) == 1
|
||||||
assert dmg_mods[0].value == 1
|
assert atk_mods[0].value == 2
|
||||||
|
assert dmg_mods[0].value == 2
|
||||||
|
assert cmb_mods[0].value == 2
|
||||||
|
|
||||||
def test_transcript_shows_buff(self) -> None:
|
def test_transcript_shows_buff(self) -> None:
|
||||||
caster = _make_caster()
|
caster = _make_caster()
|
||||||
|
|||||||
Reference in New Issue
Block a user