@opencharacter/5e-srd-5.1 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/README.md +7 -6
  2. package/content/classes/warlock.yaml +0 -7
  3. package/content/features/druid/natural-recovery.yaml +23 -45
  4. package/content/features/wizard/arcane-recovery.yaml +22 -67
  5. package/content/invocations/bewitching-whispers.yaml +9 -2
  6. package/content/invocations/dreadful-word.yaml +9 -2
  7. package/content/invocations/eldritch-spear.yaml +3 -2
  8. package/content/invocations/minions-of-chaos.yaml +9 -2
  9. package/content/invocations/mire-the-mind.yaml +9 -2
  10. package/content/invocations/sculptor-of-flesh.yaml +9 -2
  11. package/content/invocations/sign-of-ill-omen.yaml +9 -2
  12. package/content/invocations/thief-of-five-fates.yaml +9 -2
  13. package/content/races/half-orc.yaml +0 -2
  14. package/content/races/tiefling.yaml +0 -3
  15. package/content/spells/death-ward.yaml +11 -0
  16. package/content/spells/eldritch-blast.yaml +1 -1
  17. package/content/templates/eldritch-spear.yaml +4 -0
  18. package/content/traits/infernal-legacy.yaml +11 -2
  19. package/content/traits/relentless-endurance.yaml +5 -10
  20. package/dist/i18n/en.js +1 -1
  21. package/dist/i18n/en.json +24 -23
  22. package/dist/index.js +1 -1
  23. package/dist/release.json +1341 -2227
  24. package/i18n/en.yaml +24 -23
  25. package/pack.yaml +2 -2
  26. package/package.json +2 -2
  27. package/rules/classes.yaml +0 -1
  28. package/rules/combat.yaml +5 -0
  29. package/rules/content-types.yaml +3 -0
  30. package/rules/creation.yaml +6 -2
  31. package/rules/display.yaml +0 -6
  32. package/rules/monsters.yaml +1 -1
  33. package/rules/rest.yaml +13 -50
  34. package/rules/spellcasting.yaml +5 -0
  35. package/upgrades/0.1.0-to-0.2.0.yaml +62 -0
package/README.md CHANGED
@@ -28,9 +28,9 @@ const { ruleset } = loaded;
28
28
 
29
29
  // A new character, made by the random creation start: every pick is drawn, so it finishes.
30
30
  const blank = {
31
- specVersion: '0.1.0',
31
+ specVersion: '0.2.0',
32
32
  id: 'new-character',
33
- packs: [{ id: '5e-srd-5.1', version: '0.1.0' }],
33
+ packs: [{ id: '5e-srd-5.1', version: '0.2.0' }],
34
34
  creating: true,
35
35
  };
36
36
  const created = startAction(ruleset, blank, {
@@ -57,7 +57,7 @@ the [guides](https://opencharacterformat.org/guides/) explain each call.
57
57
  - **Modeled:** abilities, skills and saves; creation (guided, random, point buy, standard
58
58
  array, rolled) and leveling to 20 with multiclassing; the nine races, the acolyte and the
59
59
  Grappler feat; all twelve classes and their SRD subclasses, feature by feature; all 319
60
- spells (slots, Pact Magic, damage, healing, saves, conditions, and 30 spells' effects on the
60
+ spells (slots, Pact Magic, damage, healing, saves, conditions, and 31 spells' effects on the
61
61
  creature they are cast on); weapons, armor, gear, tools, mounts and vehicles; the SRD magic
62
62
  items; conditions and diseases; combat, damage, resting and death saves; all 322 monsters,
63
63
  with their attacks, legendary actions, spellcasting and forms. Details in
@@ -250,12 +250,13 @@ Where the SRD is unclear or silent, the pack reads it this way:
250
250
  - **Races and backgrounds:** the nine SRD races, four subraces and ten draconic ancestries, the
251
251
  Acolyte and the Grappler feat.
252
252
  - **Classes:** all twelve classes and their twelve SRD subclasses, feature by feature. Uses
253
- until a rest are state on the class copy; per-roll bonuses are situational choices; lasting
253
+ until a rest are state on the class copy (an invocation's on its pick, the breath weapon's
254
+ `used` on the draconic ancestry pick, a racial trait's in its saved grant state); per-roll bonuses are situational choices; lasting
254
255
  effects are boolean inputs; once per turn is the app's.
255
256
  - **Spellcasting:** all 319 SRD spells as mechanics (level, school, class lists, casting time,
256
257
  range, components, duration, attack or save, damage and healing); class DCs and attack bonuses;
257
258
  spell slots by the multiclass table; Pact Magic; `cast-spell` with upcasting and cantrip
258
- scaling; concentration; 30 spells whose effects apply to their target's sheet; racial spells;
259
+ scaling; concentration; 31 spells whose effects apply to their target's sheet; racial spells;
259
260
  subclass spells; the wizard's spellbook (a spell only in the book is cast only as a ritual).
260
261
  - **Equipment:** every SRD weapon and armor, 135 gear items, 7 equipment packs, 35 tools, 8
261
262
  mounts and 11 vehicles, with cost (gp) and weight (lb) per unit. Extradimensional containers
@@ -271,7 +272,7 @@ Where the SRD is unclear or silent, the pack reads it this way:
271
272
  ## Limitations
272
273
 
273
274
  Of the 152 class and subclass features, 8 have no mechanics, and 63 of the 318 magic items are
274
- text. Of the 319 spells, 201 do nothing on the sheet beyond spending the slot: what they do is
275
+ text. Of the 319 spells, 200 do nothing on the sheet beyond spending the slot: what they do is
275
276
  their SRD text, for the app and the table (detection, illusions, summoning, movement, utility).
276
277
 
277
278
  - **The app's:** turns and action economy (Cunning Action, Fast Hands, Vanish, Persistent Rage,
@@ -10,13 +10,6 @@ state:
10
10
  eldritchMasterUsed: { type: boolean, default: false }
11
11
  darkOnesOwnLuckUsed: { type: boolean, default: false }
12
12
  hurlThroughHellUsed: { type: boolean, default: false }
13
- bewitchingWhispersUsed: { type: boolean, default: false }
14
- dreadfulWordUsed: { type: boolean, default: false }
15
- minionsOfChaosUsed: { type: boolean, default: false }
16
- mireTheMindUsed: { type: boolean, default: false }
17
- sculptorOfFleshUsed: { type: boolean, default: false }
18
- signOfIllOmenUsed: { type: boolean, default: false }
19
- thiefOfFiveFatesUsed: { type: boolean, default: false }
20
13
  prerequisites:
21
14
  - id: multiclass
22
15
  when: (self.startingClass && self.level == level.total) || item.id in multiclass.qualified
@@ -3,56 +3,34 @@ type: feature
3
3
  actions:
4
4
  - id: natural-recovery
5
5
  tags: [feature]
6
- results: [recovered, too-many]
7
- available: '!self.naturalRecoveryUsed'
8
- params:
9
- level1: { type: integer, optional: true, min: 0 }
10
- level2: { type: integer, optional: true, min: 0 }
11
- level3: { type: integer, optional: true, min: 0 }
12
- level4: { type: integer, optional: true, min: 0 }
13
- level5: { type: integer, optional: true, min: 0 }
6
+ results: [recovered]
7
+ available: >-
8
+ !self.naturalRecoveryUsed
9
+ && spellSlots.level1.used + spellSlots.level2.used + spellSlots.level3.used
10
+ + spellSlots.level4.used + spellSlots.level5.used > 0
14
11
  steps:
15
- - id: count
16
- kind: branch
17
- cases:
18
- - when: true
19
- flags:
20
- l1: 'has(params.level1) ? params.level1 : 0'
21
- l2: 'has(params.level2) ? params.level2 : 0'
22
- l3: 'has(params.level3) ? params.level3 : 0'
23
- l4: 'has(params.level4) ? params.level4 : 0'
24
- l5: 'has(params.level5) ? params.level5 : 0'
25
- - id: tooMany
26
- kind: outcome
27
- when: >-
28
- flags.l1 + 2 * flags.l2 + 3 * flags.l3 + 4 * flags.l4 + 5 * flags.l5 > ceil(self.level / 2)
29
- result: "'too-many'"
30
- data:
31
- limit: ceil(self.level / 2)
12
+ # One spent slot per pick, of 5th level or lower, while the levels fit under half the
13
+ # druid level.
14
+ - id: slots
15
+ kind: choice
16
+ options:
17
+ values: [1, 2, 3, 4, 5]
18
+ where: >-
19
+ picks < fieldOf('spellSlots.level{slot}.used', value)
20
+ && value + sumList(steps.slots.picked, indexOf(['1', '2', '3', '4', '5'], x) + 1)
21
+ <= ceil(self.level / 2)
22
+ repeat: { max: ceil(self.level / 2), min: 1 }
23
+ prefer: value
24
+ changes:
25
+ - type: adjust
26
+ field: { formula: "'spellSlots.level' + string(picked.value) + '.used'" }
27
+ by: -1
32
28
  - id: recover
33
29
  kind: changes
34
30
  changes:
35
- - type: set
36
- field: spellSlots.level1.used
37
- value: max(0, spellSlots.level1.used - flags.l1)
38
- when: flags.l1 > 0
39
- - type: set
40
- field: spellSlots.level2.used
41
- value: max(0, spellSlots.level2.used - flags.l2)
42
- when: flags.l2 > 0
43
- - type: set
44
- field: spellSlots.level3.used
45
- value: max(0, spellSlots.level3.used - flags.l3)
46
- when: flags.l3 > 0
47
- - type: set
48
- field: spellSlots.level4.used
49
- value: max(0, spellSlots.level4.used - flags.l4)
50
- when: flags.l4 > 0
51
- - type: set
52
- field: spellSlots.level5.used
53
- value: max(0, spellSlots.level5.used - flags.l5)
54
- when: flags.l5 > 0
55
31
  - { type: setState, entry: self, key: naturalRecoveryUsed, value: true }
56
32
  - id: result
57
33
  kind: outcome
58
34
  result: "'recovered'"
35
+ data:
36
+ levels: sumList(steps.slots.picked, indexOf(['1', '2', '3', '4', '5'], x) + 1)
@@ -3,79 +3,34 @@ type: feature
3
3
  actions:
4
4
  - id: arcane-recovery
5
5
  tags: [feature]
6
- results: [too-many, recovered]
7
- available: '!self.arcaneRecoveryUsed'
8
- params:
9
- level1: { type: integer, optional: true }
10
- level2: { type: integer, optional: true }
11
- level3: { type: integer, optional: true }
12
- level4: { type: integer, optional: true }
13
- level5: { type: integer, optional: true }
6
+ results: [recovered]
7
+ available: >-
8
+ !self.arcaneRecoveryUsed
9
+ && spellSlots.level1.used + spellSlots.level2.used + spellSlots.level3.used
10
+ + spellSlots.level4.used + spellSlots.level5.used > 0
14
11
  steps:
15
- - id: counts
16
- kind: branch
17
- cases:
18
- - when: true
19
- flags:
20
- level1: 'has(params.level1) ? params.level1 : 0'
21
- level2: 'has(params.level2) ? params.level2 : 0'
22
- level3: 'has(params.level3) ? params.level3 : 0'
23
- level4: 'has(params.level4) ? params.level4 : 0'
24
- level5: 'has(params.level5) ? params.level5 : 0'
25
- - id: total
26
- kind: branch
27
- cases:
28
- - when: true
29
- flags:
30
- levels: flags.level1 + 2 * flags.level2 + 3 * flags.level3 + 4 * flags.level4 + 5 * flags.level5
31
- - id: tooMany
32
- kind: outcome
33
- when: >-
34
- flags.levels == 0 || flags.levels > ceil(self.level / 2)
35
- || flags.level1 < 0 || flags.level1 > spellSlots.level1.used
36
- || flags.level2 < 0 || flags.level2 > spellSlots.level2.used
37
- || flags.level3 < 0 || flags.level3 > spellSlots.level3.used
38
- || flags.level4 < 0 || flags.level4 > spellSlots.level4.used
39
- || flags.level5 < 0 || flags.level5 > spellSlots.level5.used
40
- result: "'too-many'"
41
- data:
42
- maxLevels: ceil(self.level / 2)
12
+ # One spent slot per pick, of 5th level or lower, while the levels fit under half the
13
+ # wizard level.
14
+ - id: slots
15
+ kind: choice
16
+ options:
17
+ values: [1, 2, 3, 4, 5]
18
+ where: >-
19
+ picks < fieldOf('spellSlots.level{slot}.used', value)
20
+ && value + sumList(steps.slots.picked, indexOf(['1', '2', '3', '4', '5'], x) + 1)
21
+ <= ceil(self.level / 2)
22
+ repeat: { max: ceil(self.level / 2), min: 1 }
23
+ prefer: value
24
+ changes:
25
+ - type: adjust
26
+ field: { formula: "'spellSlots.level' + string(picked.value) + '.used'" }
27
+ by: -1
43
28
  - id: recover
44
29
  kind: changes
45
30
  changes:
46
31
  - { type: setState, entry: self, key: arcaneRecoveryUsed, value: true }
47
- - {
48
- type: adjust,
49
- field: spellSlots.level1.used,
50
- by: -flags.level1,
51
- when: flags.level1 > 0,
52
- }
53
- - {
54
- type: adjust,
55
- field: spellSlots.level2.used,
56
- by: -flags.level2,
57
- when: flags.level2 > 0,
58
- }
59
- - {
60
- type: adjust,
61
- field: spellSlots.level3.used,
62
- by: -flags.level3,
63
- when: flags.level3 > 0,
64
- }
65
- - {
66
- type: adjust,
67
- field: spellSlots.level4.used,
68
- by: -flags.level4,
69
- when: flags.level4 > 0,
70
- }
71
- - {
72
- type: adjust,
73
- field: spellSlots.level5.used,
74
- by: -flags.level5,
75
- when: flags.level5 > 0,
76
- }
77
32
  - id: result
78
33
  kind: outcome
79
34
  result: "'recovered'"
80
35
  data:
81
- levels: flags.levels
36
+ levels: sumList(steps.slots.picked, indexOf(['1', '2', '3', '4', '5'], x) + 1)
@@ -1,5 +1,7 @@
1
1
  id: invocations.bewitching-whispers
2
2
  type: invocation
3
+ state:
4
+ used: { type: boolean, default: false }
3
5
  prerequisites:
4
6
  - id: level
5
7
  when: self.level >= 7
@@ -12,9 +14,14 @@ effects:
12
14
  op: option
13
15
  when: >-
14
16
  action.item.id == @spells.compulsion && flags.slot == pactSlots.level
15
- && pactSlots.used < pactSlots.max && !self.bewitchingWhispersUsed
17
+ && pactSlots.used < pactSlots.max && !self.used
16
18
  value:
17
19
  id: invocation
18
20
  changes:
19
21
  - { type: adjust, field: pactSlots.used, by: 1 }
20
- - { type: setState, entry: self, key: bewitchingWhispersUsed, value: true }
22
+ - {
23
+ type: setState,
24
+ picks: { where: 'item.id == @invocations.bewitching-whispers' },
25
+ key: used,
26
+ value: true,
27
+ }
@@ -1,5 +1,7 @@
1
1
  id: invocations.dreadful-word
2
2
  type: invocation
3
+ state:
4
+ used: { type: boolean, default: false }
3
5
  prerequisites:
4
6
  - id: level
5
7
  when: self.level >= 7
@@ -12,9 +14,14 @@ effects:
12
14
  op: option
13
15
  when: >-
14
16
  action.item.id == @spells.confusion && flags.slot == pactSlots.level
15
- && pactSlots.used < pactSlots.max && !self.dreadfulWordUsed
17
+ && pactSlots.used < pactSlots.max && !self.used
16
18
  value:
17
19
  id: invocation
18
20
  changes:
19
21
  - { type: adjust, field: pactSlots.used, by: 1 }
20
- - { type: setState, entry: self, key: dreadfulWordUsed, value: true }
22
+ - {
23
+ type: setState,
24
+ picks: { where: 'item.id == @invocations.dreadful-word' },
25
+ key: used,
26
+ value: true,
27
+ }
@@ -3,5 +3,6 @@ type: invocation
3
3
  prerequisites:
4
4
  - id: spell
5
5
  when: '@spells.eldritch-blast in spells.warlock'
6
- effects:
7
- - { target: eldritchBlast.range, op: atLeast, value: 300 }
6
+ keepsTemplates:
7
+ - template: templates.eldritch-spear
8
+ content: { type: spell, where: 'item.id == @spells.eldritch-blast' }
@@ -1,5 +1,7 @@
1
1
  id: invocations.minions-of-chaos
2
2
  type: invocation
3
+ state:
4
+ used: { type: boolean, default: false }
3
5
  prerequisites:
4
6
  - id: level
5
7
  when: self.level >= 9
@@ -12,9 +14,14 @@ effects:
12
14
  op: option
13
15
  when: >-
14
16
  action.item.id == @spells.conjure-elemental && flags.slot == pactSlots.level
15
- && pactSlots.used < pactSlots.max && !self.minionsOfChaosUsed
17
+ && pactSlots.used < pactSlots.max && !self.used
16
18
  value:
17
19
  id: invocation
18
20
  changes:
19
21
  - { type: adjust, field: pactSlots.used, by: 1 }
20
- - { type: setState, entry: self, key: minionsOfChaosUsed, value: true }
22
+ - {
23
+ type: setState,
24
+ picks: { where: 'item.id == @invocations.minions-of-chaos' },
25
+ key: used,
26
+ value: true,
27
+ }
@@ -1,5 +1,7 @@
1
1
  id: invocations.mire-the-mind
2
2
  type: invocation
3
+ state:
4
+ used: { type: boolean, default: false }
3
5
  prerequisites:
4
6
  - id: level
5
7
  when: self.level >= 5
@@ -12,9 +14,14 @@ effects:
12
14
  op: option
13
15
  when: >-
14
16
  action.item.id == @spells.slow && flags.slot == pactSlots.level
15
- && pactSlots.used < pactSlots.max && !self.mireTheMindUsed
17
+ && pactSlots.used < pactSlots.max && !self.used
16
18
  value:
17
19
  id: invocation
18
20
  changes:
19
21
  - { type: adjust, field: pactSlots.used, by: 1 }
20
- - { type: setState, entry: self, key: mireTheMindUsed, value: true }
22
+ - {
23
+ type: setState,
24
+ picks: { where: 'item.id == @invocations.mire-the-mind' },
25
+ key: used,
26
+ value: true,
27
+ }
@@ -1,5 +1,7 @@
1
1
  id: invocations.sculptor-of-flesh
2
2
  type: invocation
3
+ state:
4
+ used: { type: boolean, default: false }
3
5
  prerequisites:
4
6
  - id: level
5
7
  when: self.level >= 7
@@ -12,9 +14,14 @@ effects:
12
14
  op: option
13
15
  when: >-
14
16
  action.item.id == @spells.polymorph && flags.slot == pactSlots.level
15
- && pactSlots.used < pactSlots.max && !self.sculptorOfFleshUsed
17
+ && pactSlots.used < pactSlots.max && !self.used
16
18
  value:
17
19
  id: invocation
18
20
  changes:
19
21
  - { type: adjust, field: pactSlots.used, by: 1 }
20
- - { type: setState, entry: self, key: sculptorOfFleshUsed, value: true }
22
+ - {
23
+ type: setState,
24
+ picks: { where: 'item.id == @invocations.sculptor-of-flesh' },
25
+ key: used,
26
+ value: true,
27
+ }
@@ -1,5 +1,7 @@
1
1
  id: invocations.sign-of-ill-omen
2
2
  type: invocation
3
+ state:
4
+ used: { type: boolean, default: false }
3
5
  prerequisites:
4
6
  - id: level
5
7
  when: self.level >= 5
@@ -12,9 +14,14 @@ effects:
12
14
  op: option
13
15
  when: >-
14
16
  action.item.id == @spells.bestow-curse && flags.slot == pactSlots.level
15
- && pactSlots.used < pactSlots.max && !self.signOfIllOmenUsed
17
+ && pactSlots.used < pactSlots.max && !self.used
16
18
  value:
17
19
  id: invocation
18
20
  changes:
19
21
  - { type: adjust, field: pactSlots.used, by: 1 }
20
- - { type: setState, entry: self, key: signOfIllOmenUsed, value: true }
22
+ - {
23
+ type: setState,
24
+ picks: { where: 'item.id == @invocations.sign-of-ill-omen' },
25
+ key: used,
26
+ value: true,
27
+ }
@@ -1,5 +1,7 @@
1
1
  id: invocations.thief-of-five-fates
2
2
  type: invocation
3
+ state:
4
+ used: { type: boolean, default: false }
3
5
  grants:
4
6
  - { item: spells.bane }
5
7
  effects:
@@ -9,9 +11,14 @@ effects:
9
11
  op: option
10
12
  when: >-
11
13
  action.item.id == @spells.bane && flags.slot == pactSlots.level
12
- && pactSlots.used < pactSlots.max && !self.thiefOfFiveFatesUsed
14
+ && pactSlots.used < pactSlots.max && !self.used
13
15
  value:
14
16
  id: invocation
15
17
  changes:
16
18
  - { type: adjust, field: pactSlots.used, by: 1 }
17
- - { type: setState, entry: self, key: thiefOfFiveFatesUsed, value: true }
19
+ - {
20
+ type: setState,
21
+ picks: { where: 'item.id == @invocations.thief-of-five-fates' },
22
+ key: used,
23
+ value: true,
24
+ }
@@ -3,8 +3,6 @@ type: race
3
3
  fields:
4
4
  speed: 30
5
5
  size: medium
6
- state:
7
- relentlessEnduranceUsed: { type: boolean, default: false }
8
6
  effects:
9
7
  - { target: abilities.str.base, op: add, value: 2 }
10
8
  - { target: abilities.con.base, op: add, value: 1 }
@@ -3,9 +3,6 @@ type: race
3
3
  fields:
4
4
  speed: 30
5
5
  size: medium
6
- state:
7
- hellishRebukeUsed: { type: boolean, default: false }
8
- darknessUsed: { type: boolean, default: false }
9
6
  effects:
10
7
  - { target: abilities.int.base, op: add, value: 1 }
11
8
  - { target: abilities.cha.base, op: add, value: 2 }
@@ -11,5 +11,16 @@ fields:
11
11
  material: false
12
12
  durationUnit: hour
13
13
  durationAmount: 8
14
+ applied: true
14
15
  actions:
15
16
  - use: cast-spell
17
+ effects:
18
+ - id: ward
19
+ target: { incoming: { fields: [hp.current], tags: [damage] } }
20
+ op: atLeast
21
+ value: -(hp.current + hp.temp - 1)
22
+ when: hp.current > 0 && hp.current + hp.temp + incoming.amount <= 0
23
+ scope: applied
24
+ duration: { amount: 8, unit: hour }
25
+ then:
26
+ - { type: removeEffect, entry: self }
@@ -65,5 +65,5 @@ actions:
65
65
  data:
66
66
  critical: flags.critical
67
67
  attack: steps.attack.total
68
- range: eldritchBlast.range
68
+ range: item.range
69
69
  push: 'has(steps.damage) ? eldritchBlast.push : 0'
@@ -0,0 +1,4 @@
1
+ id: templates.eldritch-spear
2
+ type: template
3
+ appliesTo: { type: spell }
4
+ fields: { range: 300 }
@@ -1,5 +1,8 @@
1
1
  id: traits.infernal-legacy
2
2
  type: trait
3
+ state:
4
+ hellishRebukeUsed: { type: boolean, default: false }
5
+ darknessUsed: { type: boolean, default: false }
3
6
  # The option casts at 2nd level with Charisma (`slot` and `castAbility`), whatever slot the app
4
7
  # passed and even when a class knows the spell. A spell no class knows is also in spells.limited, so
5
8
  # no slot is offered for it.
@@ -41,7 +44,10 @@ effects:
41
44
  id: hellish-rebuke
42
45
  flags: { slot: 2, castAbility: "'cha'" }
43
46
  changes:
44
- - { type: setState, entry: self, key: hellishRebukeUsed, value: true }
47
+ - type: setState
48
+ grants: { where: 'item.id == @traits.infernal-legacy' }
49
+ key: hellishRebukeUsed
50
+ value: true
45
51
  - target: { choices: [spell-cost] }
46
52
  op: option
47
53
  when: >-
@@ -50,7 +56,10 @@ effects:
50
56
  id: darkness
51
57
  flags: { slot: 2, castAbility: "'cha'" }
52
58
  changes:
53
- - { type: setState, entry: self, key: darknessUsed, value: true }
59
+ - type: setState
60
+ grants: { where: 'item.id == @traits.infernal-legacy' }
61
+ key: darknessUsed
62
+ value: true
54
63
  grants:
55
64
  - item: spells.thaumaturgy
56
65
  - item: spells.hellish-rebuke
@@ -1,28 +1,23 @@
1
1
  id: traits.relentless-endurance
2
2
  type: trait
3
+ state:
4
+ used: { type: boolean, default: false }
3
5
  followUps:
4
6
  - id: relentless-endurance
5
7
  becomes: hp.current == 0
6
- when: >-
7
- countCopies(has(copy.state.relentlessEnduranceUsed)
8
- && !copy.state.relentlessEnduranceUsed) > 0
8
+ when: '!self.used'
9
9
  action: relentless-endurance
10
10
  actions:
11
11
  - id: relentless-endurance
12
12
  tags: [feature]
13
13
  results: [endured]
14
- available: >-
15
- hp.current == 0 && countCopies(has(copy.state.relentlessEnduranceUsed)
16
- && !copy.state.relentlessEnduranceUsed) > 0
14
+ available: hp.current == 0 && !self.used
17
15
  steps:
18
16
  - id: endure
19
17
  kind: changes
20
18
  changes:
21
19
  - { type: set, field: hp.current, value: 1 }
22
- - type: setState
23
- copies: { type: race, where: has(self.relentlessEnduranceUsed) }
24
- key: relentlessEnduranceUsed
25
- value: true
20
+ - { type: setState, grant: self, key: used, value: true }
26
21
  - id: result
27
22
  kind: outcome
28
23
  result: "'endured'"