fix(rules): correct the inspire courage bonus type and saves

PF1e CRB (Bardic Performance - Inspire Courage): the attack/weapon-damage
bonus is a COMPETENCE bonus (not morale), so it stacks with morale sources
like Heroism; the performance also grants a +1 MORALE bonus on saving throws
against charm and fear effects, implemented as two descriptor-conditional
will modifiers (fear / mind_affecting) that dedupe via morale stacking.
Simplifications (documented): flat +1 (no scaling per four bard levels) and
mind_affecting stands in for the missing charm descriptor.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-09-11 20:35:01 +02:00
parent 0fefb9cdd2
commit 529999da69
3 changed files with 93 additions and 6 deletions
+25 -5
View File
@@ -4454,7 +4454,18 @@ class CombatEngine:
def _execute_inspire_courage(
self, state: CombatantState, target: CombatantState | None
) -> None:
"""Execute Inspire Courage (morale +1 attack/damage, 30ft radius, costs bardic_rounds)."""
"""Execute Inspire Courage (30ft radius, costs bardic_rounds).
PF1e (CRB, Bardic Performance — Inspire Courage): +1 competence bonus
on attack rolls and weapon damage rolls, and a +1 morale bonus on
saving throws against charm and fear effects. Simplifications: the
scaling (+1 per four bard levels) is not modeled (flat +1), and the
engine has no "charm" descriptor, so mind_affecting stands in for it
(documented over-coverage: compulsion effects also count). The save
bonus is carried by two conditional modifiers (fear /
mind_affecting); a save with both descriptors still totals +1 because
morale does not stack with itself.
"""
if not state.spend_resource("bardic_rounds", 1):
self._log(
f"round {self._current_round} {state.combatant.id}: inspire courage "
@@ -4481,13 +4492,21 @@ class CombatEngine:
)
return
# Apply +1 morale bonus to attack and damage
# Apply +1 competence attack/damage and +1 morale vs charm/fear saves
for ally in affected:
ally.active_buffs.append(
ActiveBuff(
modifiers=(
StatModifier(target="attack", value=1, bonus_type="morale"),
StatModifier(target="damage", value=1, bonus_type="morale"),
StatModifier(target="attack", value=1, bonus_type="competence"),
StatModifier(target="damage", value=1, bonus_type="competence"),
StatModifier(
target="will", value=1, bonus_type="morale",
condition="fear",
),
StatModifier(
target="will", value=1, bonus_type="morale",
condition="mind_affecting",
),
),
rounds_left=1,
source=f"inspire courage by {state.combatant.id}",
@@ -4495,7 +4514,8 @@ class CombatEngine:
)
self._log(
f"round {self._current_round} {state.combatant.id}: "
f"inspire courage +1 morale attack/damage to {len(affected)} allies"
f"inspire courage +1 competence attack/damage, +1 morale vs "
f"charm/fear saves to {len(affected)} allies"
)
def _execute_attack(
self,