Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/5e-srd-5-2-cunning-strike-cost.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@opencharacter/5e-srd-5.2': minor
---

Cunning Strike works out its Sneak Attack dice once, as the attack's derived parameter `cunningStrikeCost`, which the Sneak Attack and the strike effects read; its save DC is the feature's `strikeDc` computed value, read by the strike effects. The `rogue.strikeDc` field is removed (breaking: read the feature's `strikeDc` instead). The rules are unchanged.
10 changes: 5 additions & 5 deletions packs/5e/srd-5.2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,8 @@ automatic run finishes too; a `level` parameter starts higher. A monster is made
Metamagic options, Pact Magic and the 28 invocations, the spellbook, Arcane Recovery. Uses until
a rest are state on the class copy, an invocation's on its pick (Gift of the Depths' `used`).
Features carry the numbers their card needs as computed values (in computeSheet's
`activationValues`), which their actions read: Cunning Strike's `strikeDc` (its saves' DC; the
strike effects read it from the `rogue.strikeDc` field, since effects can't read computed values
yet ([#251](https://github.com/pixelqueue/opencharacter/issues/251))), Intimidating
`activationValues`), which their actions and action-time effects read: Cunning Strike's
`strikeDc` (its saves' DC, read by the strike effects), Intimidating
Presence's `presenceDc`, Relentless Rage's `rageDc`, Sacred Weapon's `weaponBonus`, Preserve
Life's `healingPool`, and Breath Weapon's `breathDc` and `breathDice`.
A species trait keeps its own uses as its grant's saved state (Breath Weapon, Draconic Flight,
Expand Down Expand Up @@ -337,8 +336,9 @@ Where the SRD is unclear or silent, the pack reads it this way:
of the attack (an `action` effect on its `attack-hit` step), Poisoned, Prone, Unconscious or
Blinded on a failure. An effect the rogue can't use (`rogue.strikeEffects`: not known yet, or
Poison without a Poisoner's Kit), or effects together costing more dice than the Sneak Attack
has, do nothing and cost nothing, as does a Trip on a Huge or larger target. Withdraw,
Daze's limit and Stealth Attack are the player's.
has, do nothing and cost nothing, as does a Trip on a Huge or larger target. The attack works
out the dice spent once, as the derived parameter `cunningStrikeCost`, from its first target
(an attack has one). Withdraw, Daze's limit and Stealth Attack are the player's.
- Stunning Strike (`stunningStrike`, 1 Focus Point, Constitution or Stunned), Open Hand
Technique (`openHand`, on an Unarmed Strike: Push's Strength save, Topple's Dexterity save or
Prone; it is offered on every Unarmed Strike, and whether the strike is one of Flurry of
Expand Down
65 changes: 42 additions & 23 deletions packs/5e/srd-5.2/content/features/rogue/cunning-strike.yaml
Original file line number Diff line number Diff line change
@@ -1,25 +1,56 @@
id: features.rogue.cunning-strike
type: feature
# The save DC (`rogue.strikeDc`): 8 + Dexterity modifier + Proficiency Bonus, on the card too.
# The save DC: 8 + Dexterity modifier + Proficiency Bonus, on the card too.
computed:
strikeDc: { type: integer, formula: rogue.strikeDc }
strikeDc: { type: integer, formula: 8 + abilities.dex.mod + proficiency.bonus }
# The effect is a parameter of each attack (`cunningStrike`; Improved Cunning Strike adds
# `cunningStrike2`). Its dice come off the Sneak Attack (the rules' `sneakAttack` situational
# choice), and on a hit that deals Sneak Attack damage the target saves
# against it. An effect the rogue can't use (not in `rogue.strikeEffects`: one it doesn't know yet, or
# Poison without a Poisoner's Kit), or effects together costing more dice than the Sneak Attack
# has, do nothing and cost nothing; so does a Trip against a Huge or larger target. Withdraw's
# choice reads `cunningStrikeCost`), and on a hit that deals Sneak Attack damage the target saves
# against it. An effect the rogue can't use (not in `rogue.strikeEffects`: one it doesn't know yet,
# or Poison without a Poisoner's Kit), or effects together costing more dice than the Sneak Attack
# has, do nothing and cost nothing; so does a Trip against a Huge or larger target (the cost reads
# the first target's size: one value per attack, and an attack has one target). Withdraw's
# movement, Daze's limit and Stealth Attack are the player's.
effects:
- { target: rogue.cunningStrikes, op: atLeast, value: 1 }
- { target: rogue.strikeDc, op: set, value: 8 + abilities.dex.mod + proficiency.bonus }
- target: { actions: [attack-hit] }
op: params
value:
cunningStrike: &effect
type: enum
values: [poison, trip, withdraw, daze, knock-out, obscure, stealth-attack]
optional: true
# The Sneak Attack dice the effects spend: those the rogue can use on this target, if
# together they fit in the Sneak Attack, else none.
cunningStrikeCost:
type: integer
formula: >-
(has(params.cunningStrike) && params.cunningStrike in rogue.strikeEffects
&& !(params.cunningStrike == 'trip' && has(participants.target)
&& size(participants.target) > 0 && has(participants.target[0].creature.size)
&& participants.target[0].creature.size in ['huge', 'gargantuan'])
? (params.cunningStrike == 'knock-out' ? 6 : params.cunningStrike == 'obscure' ? 3
: params.cunningStrike == 'daze' ? 2 : 1) : 0)
+ (has(params.cunningStrike2) && params.cunningStrike2 in rogue.strikeEffects
&& !(params.cunningStrike2 == 'trip' && has(participants.target)
&& size(participants.target) > 0 && has(participants.target[0].creature.size)
&& participants.target[0].creature.size in ['huge', 'gargantuan'])
? (params.cunningStrike2 == 'knock-out' ? 6 : params.cunningStrike2 == 'obscure' ? 3
: params.cunningStrike2 == 'daze' ? 2 : 1) : 0)
<= rogue.sneakAttack
? (has(params.cunningStrike) && params.cunningStrike in rogue.strikeEffects
&& !(params.cunningStrike == 'trip' && has(participants.target)
&& size(participants.target) > 0 && has(participants.target[0].creature.size)
&& participants.target[0].creature.size in ['huge', 'gargantuan'])
? (params.cunningStrike == 'knock-out' ? 6 : params.cunningStrike == 'obscure' ? 3
: params.cunningStrike == 'daze' ? 2 : 1) : 0)
+ (has(params.cunningStrike2) && params.cunningStrike2 in rogue.strikeEffects
&& !(params.cunningStrike2 == 'trip' && has(participants.target)
&& size(participants.target) > 0 && has(participants.target[0].creature.size)
&& participants.target[0].creature.size in ['huge', 'gargantuan'])
? (params.cunningStrike2 == 'knock-out' ? 6 : params.cunningStrike2 == 'obscure' ? 3
: params.cunningStrike2 == 'daze' ? 2 : 1) : 0)
: 0
- target: { actions: [attack-hit] }
op: params
when: rogue.cunningStrikes >= 2
Expand All @@ -31,13 +62,7 @@ effects:
has(params.cunningStrike)
&& params.cunningStrike in ['poison', 'trip', 'knock-out', 'obscure', 'daze']
&& params.cunningStrike in rogue.strikeEffects
&& (params.cunningStrike == 'knock-out' ? 6 : params.cunningStrike == 'obscure' ? 3
: params.cunningStrike == 'daze' ? 2 : 1)
+ (has(params.cunningStrike2) && params.cunningStrike2 in rogue.strikeEffects
&& !(params.cunningStrike2 == 'trip' && participant.creature.size in ['huge', 'gargantuan'])
? (params.cunningStrike2 == 'knock-out' ? 6 : params.cunningStrike2 == 'obscure' ? 3
: params.cunningStrike2 == 'daze' ? 2 : 1) : 0)
<= rogue.sneakAttack
&& params.cunningStrikeCost > 0
&& situational.sneakAttack && has(action.item.damageType)
&& (action.item.range == 'ranged' || 'finesse' in action.item.tags)
&& (params.cunningStrike != 'trip'
Expand All @@ -49,7 +74,7 @@ effects:
as: participant
params:
ability: "params.cunningStrike in ['trip', 'obscure'] ? 'dex' : 'con'"
dc: rogue.strikeDc
dc: self.strikeDc
changes:
- type: applyEffect
when: run.result == 'failure' && params.cunningStrike != 'daze'
Expand All @@ -64,13 +89,7 @@ effects:
has(params.cunningStrike2)
&& params.cunningStrike2 in ['poison', 'trip', 'knock-out', 'obscure', 'daze']
&& params.cunningStrike2 in rogue.strikeEffects
&& (params.cunningStrike2 == 'knock-out' ? 6 : params.cunningStrike2 == 'obscure' ? 3
: params.cunningStrike2 == 'daze' ? 2 : 1)
+ (has(params.cunningStrike) && params.cunningStrike in rogue.strikeEffects
&& !(params.cunningStrike == 'trip' && participant.creature.size in ['huge', 'gargantuan'])
? (params.cunningStrike == 'knock-out' ? 6 : params.cunningStrike == 'obscure' ? 3
: params.cunningStrike == 'daze' ? 2 : 1) : 0)
<= rogue.sneakAttack
&& params.cunningStrikeCost > 0
&& situational.sneakAttack && has(action.item.damageType)
&& (action.item.range == 'ranged' || 'finesse' in action.item.tags)
&& (params.cunningStrike2 != 'trip'
Expand All @@ -82,7 +101,7 @@ effects:
as: participant
params:
ability: "params.cunningStrike2 in ['trip', 'obscure'] ? 'dex' : 'con'"
dc: rogue.strikeDc
dc: self.strikeDc
changes:
- type: applyEffect
when: run.result == 'failure' && params.cunningStrike2 != 'daze'
Expand Down
1 change: 0 additions & 1 deletion packs/5e/srd-5.2/i18n/en/class-rogue.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ classes.rogue.state.strokeOfLuckUsed.name: 'Stroke of Luck used'

fields.rogue.sneakAttack.name: 'Sneak Attack dice (d6)'
fields.rogue.cunningStrikes.name: 'Cunning Strike effects per Sneak Attack'
fields.rogue.strikeDc.name: 'Cunning Strike save DC'
situational.sneakAttack.name: 'Sneak Attack'
situational.uncannyDodge.name: 'Uncanny Dodge (halve this attack’s damage)'

Expand Down
48 changes: 6 additions & 42 deletions packs/5e/srd-5.2/rules/class-rogue.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ fields:
rogue.cunningStrikes: { type: integer, formula: 0 }
rogue.deviousStrikes: { type: boolean, formula: false }
rogue.stealthAttack: { type: boolean, formula: false }
# Cunning Strike: the save DC, and the effects the rogue can use now (Poison needs a Poisoner's
# Kit; Devious Strikes adds Daze, Knock Out and Obscure; Supreme Sneak, Stealth Attack).
rogue.strikeDc: { type: integer, formula: 0 }
# Cunning Strike: the effects the rogue can use now (Poison needs a Poisoner's Kit; Devious
# Strikes adds Daze, Knock Out and Obscure; Supreme Sneak, Stealth Attack).
rogue.strikeEffects:
type: set
of: string
Expand All @@ -18,8 +17,7 @@ fields:
rogue.uncannyDodge: { type: boolean, formula: false }

# Cunning Strike's effects (the attack's `cunningStrike` and `cunningStrike2` parameters) spend
# Sneak Attack dice: the ones the rogue can use on this target (Trip needs a Large or smaller one),
# if together they fit in the Sneak Attack (else none is used, and nothing is spent).
# Sneak Attack dice: the attack's `cunningStrikeCost`, worked out by the feature.
situational:
- id: sneakAttack
type: boolean
Expand All @@ -30,30 +28,7 @@ situational:
- target: { rolls: [damage.weapon] }
op: add
value: >-
(rogue.sneakAttack - ((has(params.cunningStrike) && params.cunningStrike in rogue.strikeEffects
&& !(params.cunningStrike == 'trip' && has(participants.target) && size(participants.target) > 0
&& has(participants.target[0].creature.size)
&& participants.target[0].creature.size in ['huge', 'gargantuan'])
? (params.cunningStrike == 'knock-out' ? 6 : params.cunningStrike == 'obscure' ? 3
: params.cunningStrike == 'daze' ? 2 : 1) : 0)
+ (has(params.cunningStrike2) && params.cunningStrike2 in rogue.strikeEffects
&& !(params.cunningStrike2 == 'trip' && has(participants.target) && size(participants.target) > 0
&& has(participants.target[0].creature.size)
&& participants.target[0].creature.size in ['huge', 'gargantuan'])
? (params.cunningStrike2 == 'knock-out' ? 6 : params.cunningStrike2 == 'obscure' ? 3
: params.cunningStrike2 == 'daze' ? 2 : 1) : 0) <= rogue.sneakAttack
? (has(params.cunningStrike) && params.cunningStrike in rogue.strikeEffects
&& !(params.cunningStrike == 'trip' && has(participants.target) && size(participants.target) > 0
&& has(participants.target[0].creature.size)
&& participants.target[0].creature.size in ['huge', 'gargantuan'])
? (params.cunningStrike == 'knock-out' ? 6 : params.cunningStrike == 'obscure' ? 3
: params.cunningStrike == 'daze' ? 2 : 1) : 0)
+ (has(params.cunningStrike2) && params.cunningStrike2 in rogue.strikeEffects
&& !(params.cunningStrike2 == 'trip' && has(participants.target) && size(participants.target) > 0
&& has(participants.target[0].creature.size)
&& participants.target[0].creature.size in ['huge', 'gargantuan'])
? (params.cunningStrike2 == 'knock-out' ? 6 : params.cunningStrike2 == 'obscure' ? 3
: params.cunningStrike2 == 'daze' ? 2 : 1) : 0) : 0))d6
(rogue.sneakAttack - (has(params.cunningStrikeCost) ? params.cunningStrikeCost : 0))d6
tags:
[
{
Expand All @@ -62,18 +37,8 @@ situational:
]
when: >-
situational.sneakAttack
&& rogue.sneakAttack != (has(params.cunningStrike) && params.cunningStrike in rogue.strikeEffects
&& !(params.cunningStrike == 'trip' && has(participants.target) && size(participants.target) > 0
&& has(participants.target[0].creature.size)
&& participants.target[0].creature.size in ['huge', 'gargantuan'])
? (params.cunningStrike == 'knock-out' ? 6 : params.cunningStrike == 'obscure' ? 3
: params.cunningStrike == 'daze' ? 2 : 1) : 0)
+ (has(params.cunningStrike2) && params.cunningStrike2 in rogue.strikeEffects
&& !(params.cunningStrike2 == 'trip' && has(participants.target) && size(participants.target) > 0
&& has(participants.target[0].creature.size)
&& participants.target[0].creature.size in ['huge', 'gargantuan'])
? (params.cunningStrike2 == 'knock-out' ? 6 : params.cunningStrike2 == 'obscure' ? 3
: params.cunningStrike2 == 'daze' ? 2 : 1) : 0)
&& rogue.sneakAttack
!= (has(params.cunningStrikeCost) ? params.cunningStrikeCost : 0)
&& has(roll.item) && has(roll.item.damageType)
&& (roll.item.range == 'ranged' || 'finesse' in roll.item.tags)
- id: uncannyDodge
Expand All @@ -93,6 +58,5 @@ display:
{ groups: [class-features], priority: 2, showWhen: rogue.cunningStrikes > 0 }
rogue.deviousStrikes: { hidden: true }
rogue.stealthAttack: { hidden: true }
rogue.strikeDc: { groups: [class-features], priority: 2, showWhen: rogue.cunningStrikes > 0 }
rogue.strikeEffects: { hidden: true }
rogue.uncannyDodge: { hidden: true }
6 changes: 3 additions & 3 deletions packs/draw-steel/heroes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,9 @@ computeSheet(ruleset, made.character).values['draw-steel-heroes:stamina.max']?.v
Visceral Roar and Deluge take the stormwight kit's primordial type (`fury.primordialType`),
and Deluge's ignores immunity.
- Healing Grace's Spend enhancements: `graceRecoveries`, `graceStand` and `graceEnd`, which
must fit in `spend`. `graceEndEntry` is the ID of the applied entry to end on the target;
without it, the first that a saving throw or the end of a turn ends. An overspend is
reported on `graceRecoveries` ([#252](https://github.com/pixelqueue/opencharacter/issues/252)).
must fit in `spend` (they bound it: an overspend is `out-of-range` on `spend`).
`graceEndEntry` is the ID of the applied entry to end on the target; without it, the first
that a saving throw or the end of a turn ends.
- No Dying on My Watch's `adjacentEnemies`: the ally gains 5 temporary Stamina for each.

An `ally` participant is the ally a triggered ability answers or an ability helps: No Dying on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,14 @@ fields:
effects:
# Spend 1+ piety, one enhancement each: more targets (the participants), a Recovery more for a
# target, a prone target standing up, or an effect a saving throw or the end of their turn ends
# (the first such). Pass `spend` for the total: the extra Recoveries, standing and ending fit
# in it.
# (the first such). Pass `spend` for the total: the extra Recoveries, standing and ending
# bound it from below.
- target: { actions: [ability], from: self }
op: params
value:
graceRecoveries:
type: integer
default: 0
min: 0
max: 'params.spend - (params.graceStand ? 1 : 0) - (params.graceEnd ? 1 : 0)'
spend:
{ min: 'params.graceRecoveries + (params.graceStand ? 1 : 0) + (params.graceEnd ? 1 : 0)' }
graceRecoveries: { type: integer, default: 0, min: 0 }
graceStand: { type: boolean, default: false }
graceEnd: { type: boolean, default: false }
# The applied entry to end (its ID on the target); without it, the first that qualifies.
Expand Down
Loading