@opencharacter/5e-srd-5.1 0.2.0 → 0.3.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 (93) hide show
  1. package/README.md +53 -3
  2. package/content/backgrounds/acolyte.yaml +22 -0
  3. package/content/classes/barbarian.yaml +57 -0
  4. package/content/classes/bard.yaml +68 -0
  5. package/content/classes/cleric.yaml +83 -0
  6. package/content/classes/druid.yaml +61 -0
  7. package/content/classes/fighter.yaml +75 -0
  8. package/content/classes/monk.yaml +47 -0
  9. package/content/classes/paladin.yaml +76 -0
  10. package/content/classes/ranger.yaml +69 -0
  11. package/content/classes/rogue.yaml +59 -0
  12. package/content/classes/sorcerer.yaml +64 -0
  13. package/content/classes/warlock.yaml +71 -0
  14. package/content/classes/wizard.yaml +56 -0
  15. package/content/features/sorcerer/dragon-ancestor.yaml +1 -43
  16. package/content/features/sorcerer/elemental-affinity.yaml +29 -8
  17. package/content/features/sorcerer/metamagic.yaml +1 -93
  18. package/content/features/wizard/arcane-recovery.yaml +4 -4
  19. package/content/hunter-tactics/colossus-slayer.yaml +4 -0
  20. package/content/hunter-tactics/escape-the-horde.yaml +4 -0
  21. package/content/hunter-tactics/evasion.yaml +6 -0
  22. package/content/hunter-tactics/giant-killer.yaml +4 -0
  23. package/content/hunter-tactics/horde-breaker.yaml +4 -0
  24. package/content/hunter-tactics/multiattack-defense.yaml +4 -0
  25. package/content/hunter-tactics/stand-against-the-tide.yaml +4 -0
  26. package/content/hunter-tactics/steel-will.yaml +4 -0
  27. package/content/hunter-tactics/uncanny-dodge.yaml +6 -0
  28. package/content/hunter-tactics/volley.yaml +6 -0
  29. package/content/hunter-tactics/whirlwind-attack.yaml +6 -0
  30. package/content/invocations/bewitching-whispers.yaml +1 -6
  31. package/content/invocations/dreadful-word.yaml +1 -6
  32. package/content/invocations/minions-of-chaos.yaml +1 -6
  33. package/content/invocations/mire-the-mind.yaml +1 -6
  34. package/content/invocations/sculptor-of-flesh.yaml +1 -6
  35. package/content/invocations/sign-of-ill-omen.yaml +1 -6
  36. package/content/invocations/thief-of-five-fates.yaml +1 -6
  37. package/content/magic-items/arrow-of-slaying.yaml +1 -0
  38. package/content/magic-items/bag-of-holding.yaml +2 -0
  39. package/content/magic-items/efficient-quiver.yaml +2 -0
  40. package/content/magic-items/handy-haversack.yaml +2 -0
  41. package/content/magic-items/holy-avenger.yaml +9 -2
  42. package/content/magic-items/necklace-of-prayer-beads.yaml +1 -1
  43. package/content/magic-items/portable-hole.yaml +2 -0
  44. package/content/magic-items/ring-of-x-ray-vision.yaml +6 -0
  45. package/content/magic-items/robe-of-the-archmagi.yaml +1 -1
  46. package/content/magic-items/staff-of-charming.yaml +17 -12
  47. package/content/magic-items/staff-of-fire.yaml +9 -2
  48. package/content/magic-items/staff-of-frost.yaml +9 -2
  49. package/content/magic-items/staff-of-healing.yaml +9 -2
  50. package/content/magic-items/staff-of-power.yaml +2 -1
  51. package/content/magic-items/staff-of-swarming-insects.yaml +17 -12
  52. package/content/magic-items/staff-of-the-magi.yaml +2 -1
  53. package/content/magic-items/staff-of-the-python.yaml +7 -2
  54. package/content/magic-items/staff-of-the-woodlands.yaml +2 -1
  55. package/content/magic-items/staff-of-withering.yaml +9 -2
  56. package/content/metamagic/careful-spell.yaml +11 -0
  57. package/content/metamagic/distant-spell.yaml +11 -0
  58. package/content/metamagic/empowered-spell.yaml +22 -0
  59. package/content/metamagic/extended-spell.yaml +11 -0
  60. package/content/metamagic/heightened-spell.yaml +11 -0
  61. package/content/metamagic/quickened-spell.yaml +11 -0
  62. package/content/metamagic/subtle-spell.yaml +11 -0
  63. package/content/metamagic/twinned-spell.yaml +16 -0
  64. package/content/subclasses/hunter.yaml +4 -29
  65. package/content/traits/infernal-legacy.yaml +15 -2
  66. package/content/traits/relentless-endurance.yaml +6 -0
  67. package/content/weapons/javelin.yaml +1 -1
  68. package/dist/i18n/en.js +1 -1
  69. package/dist/i18n/en.json +277 -201
  70. package/dist/index.js +1 -1
  71. package/dist/release.json +5795 -5769
  72. package/i18n/en/monster-rules.yaml +1 -1
  73. package/i18n/en/type-fields.yaml +7 -0
  74. package/i18n/en.yaml +268 -223
  75. package/pack.yaml +2 -2
  76. package/package.json +2 -2
  77. package/rules/advancement.yaml +67 -38
  78. package/rules/checks.yaml +1 -1
  79. package/rules/classes.yaml +25 -3
  80. package/rules/combat.yaml +3 -2
  81. package/rules/conditions.yaml +1 -0
  82. package/rules/content-types.yaml +61 -4
  83. package/rules/core.yaml +6 -16
  84. package/rules/creation.yaml +20 -34
  85. package/rules/display.yaml +20 -5
  86. package/rules/equipment.yaml +13 -16
  87. package/rules/magic-items.yaml +19 -1
  88. package/rules/monsters.yaml +7 -1
  89. package/rules/rest.yaml +2 -36
  90. package/rules/spellcasting.yaml +1 -5
  91. package/rules/starting-equipment.yaml +17 -646
  92. package/rules/tags.yaml +12 -12
  93. package/upgrades/0.2.0-to-0.3.0.yaml +152 -0
package/pack.yaml CHANGED
@@ -1,6 +1,6 @@
1
1
  id: 5e-srd-5.1
2
- version: 0.2.0
3
- specVersion: 0.2.0
2
+ version: 0.3.0
3
+ specVersion: 0.3.0
4
4
  license: CC-BY-4.0
5
5
  credit: >-
6
6
  This work includes material taken from the System Reference Document 5.1 (“SRD 5.1”) by
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencharacter/5e-srd-5.1",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Open Character Format pack: the 5E System Reference Document 5.1.",
5
5
  "keywords": [
6
6
  "5e",
@@ -52,7 +52,7 @@
52
52
  "access": "public"
53
53
  },
54
54
  "devDependencies": {
55
- "@opencharacter/engine": "0.2.0"
55
+ "@opencharacter/engine": "0.3.0"
56
56
  },
57
57
  "engines": {
58
58
  "node": ">=22"
@@ -7,7 +7,30 @@ fields:
7
7
  : [0, 300, 900, 2700, 6500, 14000, 23000, 34000, 48000, 64000, 85000, 100000, 120000,
8
8
  140000, 165000, 195000, 225000, 265000, 305000, 355000][level.total]
9
9
  xp.levelUp: { type: boolean, formula: 'xp.nextLevel > 0 && xp.current >= xp.nextLevel' }
10
- multiclass.qualified: { type: set, of: class }
10
+ # Multiclassing into a class needs 13 in each of its multiclassAll abilities and in one of its
11
+ # multiclassAny ones, when it lists any.
12
+ multiclass.qualified:
13
+ type: set
14
+ of: class
15
+ formula: >-
16
+ filterList(multiclass.classes,
17
+ !(x in multiclass.needs.str && abilities.str.score < 13)
18
+ && !(x in multiclass.needs.dex && abilities.dex.score < 13)
19
+ && !(x in multiclass.needs.con && abilities.con.score < 13)
20
+ && !(x in multiclass.needs.int && abilities.int.score < 13)
21
+ && !(x in multiclass.needs.wis && abilities.wis.score < 13)
22
+ && !(x in multiclass.needs.cha && abilities.cha.score < 13)
23
+ && (!(x in multiclass.oneOfListed)
24
+ || (x in multiclass.oneOf.str && abilities.str.score >= 13)
25
+ || (x in multiclass.oneOf.dex && abilities.dex.score >= 13)
26
+ || (x in multiclass.oneOf.con && abilities.con.score >= 13)
27
+ || (x in multiclass.oneOf.int && abilities.int.score >= 13)
28
+ || (x in multiclass.oneOf.wis && abilities.wis.score >= 13)
29
+ || (x in multiclass.oneOf.cha && abilities.cha.score >= 13)))
30
+ multiclass.classes: { type: set, of: class }
31
+ multiclass.oneOfListed: { type: set, of: class }
32
+ 'multiclass.needs.{ability}': { type: set, of: class }
33
+ 'multiclass.oneOf.{ability}': { type: set, of: class }
11
34
 
12
35
  display:
13
36
  fields:
@@ -16,62 +39,65 @@ display:
16
39
  groups: [summary],
17
40
  priority: 1,
18
41
  visibility: secondary,
42
+ showWhen: '!sheet.monster',
19
43
  with: [{ field: xp.nextLevel, as: max }],
20
44
  }
21
45
  xp.nextLevel:
22
- { groups: [summary], priority: 2, visibility: secondary, showWhen: xp.nextLevel > 0 }
46
+ {
47
+ groups: [summary],
48
+ priority: 2,
49
+ visibility: secondary,
50
+ showWhen: '!sheet.monster && xp.nextLevel > 0',
51
+ }
23
52
  xp.levelUp: { hidden: true }
24
53
  multiclass.qualified: { hidden: true }
54
+ multiclass.classes: { hidden: true }
55
+ multiclass.oneOfListed: { hidden: true }
56
+ 'multiclass.needs.{ability}': { hidden: true }
57
+ 'multiclass.oneOf.{ability}': { hidden: true }
25
58
 
26
59
  effects:
27
- - target: multiclass.qualified
60
+ # The families the qualification reads, from each class's multiclassAll and multiclassAny.
61
+ - { target: multiclass.classes, op: grant, value: { query: { type: class } } }
62
+ - target: multiclass.oneOfListed
63
+ op: grant
64
+ value: { query: { type: class, where: size(item.multiclassAny) > 0 } }
65
+ - target: multiclass.needs.str
28
66
  op: grant
29
- value: { formula: '@classes.barbarian' }
30
- when: abilities.str.score >= 13
31
- - target: multiclass.qualified
67
+ value: { query: { type: class, where: "'str' in item.multiclassAll" } }
68
+ - target: multiclass.needs.dex
32
69
  op: grant
33
- value: { formula: '@classes.bard' }
34
- when: abilities.cha.score >= 13
35
- - target: multiclass.qualified
70
+ value: { query: { type: class, where: "'dex' in item.multiclassAll" } }
71
+ - target: multiclass.needs.con
36
72
  op: grant
37
- value: { formula: '@classes.cleric' }
38
- when: abilities.wis.score >= 13
39
- - target: multiclass.qualified
73
+ value: { query: { type: class, where: "'con' in item.multiclassAll" } }
74
+ - target: multiclass.needs.int
40
75
  op: grant
41
- value: { formula: '@classes.druid' }
42
- when: abilities.wis.score >= 13
43
- - target: multiclass.qualified
76
+ value: { query: { type: class, where: "'int' in item.multiclassAll" } }
77
+ - target: multiclass.needs.wis
44
78
  op: grant
45
- value: { formula: '@classes.fighter' }
46
- when: abilities.str.score >= 13 || abilities.dex.score >= 13
47
- - target: multiclass.qualified
79
+ value: { query: { type: class, where: "'wis' in item.multiclassAll" } }
80
+ - target: multiclass.needs.cha
48
81
  op: grant
49
- value: { formula: '@classes.monk' }
50
- when: abilities.dex.score >= 13 && abilities.wis.score >= 13
51
- - target: multiclass.qualified
82
+ value: { query: { type: class, where: "'cha' in item.multiclassAll" } }
83
+ - target: multiclass.oneOf.str
52
84
  op: grant
53
- value: { formula: '@classes.paladin' }
54
- when: abilities.str.score >= 13 && abilities.cha.score >= 13
55
- - target: multiclass.qualified
85
+ value: { query: { type: class, where: "'str' in item.multiclassAny" } }
86
+ - target: multiclass.oneOf.dex
56
87
  op: grant
57
- value: { formula: '@classes.ranger' }
58
- when: abilities.dex.score >= 13 && abilities.wis.score >= 13
59
- - target: multiclass.qualified
88
+ value: { query: { type: class, where: "'dex' in item.multiclassAny" } }
89
+ - target: multiclass.oneOf.con
60
90
  op: grant
61
- value: { formula: '@classes.rogue' }
62
- when: abilities.dex.score >= 13
63
- - target: multiclass.qualified
91
+ value: { query: { type: class, where: "'con' in item.multiclassAny" } }
92
+ - target: multiclass.oneOf.int
64
93
  op: grant
65
- value: { formula: '@classes.sorcerer' }
66
- when: abilities.cha.score >= 13
67
- - target: multiclass.qualified
94
+ value: { query: { type: class, where: "'int' in item.multiclassAny" } }
95
+ - target: multiclass.oneOf.wis
68
96
  op: grant
69
- value: { formula: '@classes.warlock' }
70
- when: abilities.cha.score >= 13
71
- - target: multiclass.qualified
97
+ value: { query: { type: class, where: "'wis' in item.multiclassAny" } }
98
+ - target: multiclass.oneOf.cha
72
99
  op: grant
73
- value: { formula: '@classes.wizard' }
74
- when: abilities.int.score >= 13
100
+ value: { query: { type: class, where: "'cha' in item.multiclassAny" } }
75
101
 
76
102
  followUps:
77
103
  - id: level-up
@@ -89,6 +115,7 @@ actions:
89
115
  - when: true
90
116
  flags: { maxBefore: hp.max }
91
117
  - id: class
118
+ tags: [level-up.class]
92
119
  kind: choice
93
120
  options:
94
121
  content:
@@ -98,6 +125,7 @@ actions:
98
125
  && countCopies(copy.type == 'class' && !(copy.item in multiclass.qualified)) == 0)
99
126
  prefer: sumCopies(copy.state.level, copy.type == 'class' && copy.item == item.id)
100
127
  - id: gain
128
+ tags: [level-up.gain]
101
129
  kind: changes
102
130
  changes:
103
131
  - type: adjustState
@@ -110,6 +138,7 @@ actions:
110
138
  when: '!(steps.class.picked in classes.held)'
111
139
  entry: { item: steps.class.picked, state: { level: 1 } }
112
140
  - id: hitPoints
141
+ tags: [level-up.hit-points]
113
142
  kind: choice
114
143
  options: [{ id: roll }, { id: fixed }]
115
144
  recommend: "'fixed'"
package/rules/checks.yaml CHANGED
@@ -177,7 +177,7 @@ situational:
177
177
  resultBands:
178
178
  dc:
179
179
  - { id: rolled, when: flags.dc < 0 }
180
- - { id: success, when: roll.total >= flags.dc }
180
+ - { id: success, when: roll.total >= flags.dc, success: true }
181
181
  - { id: failure, when: true }
182
182
 
183
183
  actions:
@@ -39,7 +39,6 @@ fields:
39
39
  sacredWeapon.active: { type: boolean, input: true, default: false }
40
40
  hideInPlainSight.active: { type: boolean, input: true, default: false }
41
41
  foeSlayer.bonus: { type: integer, formula: 0 }
42
- hunter.colossusSlayer: { type: boolean, formula: false }
43
42
  sneakAttack.dice: { type: integer, formula: 0 }
44
43
  sorcery.points: { type: integer, formula: 0 }
45
44
  sorcery.left:
@@ -49,8 +48,6 @@ fields:
49
48
  copy.type == 'class' && has(copy.state.sorceryPointsSpent))
50
49
  sorcery.shortRestRegain: { type: integer, formula: 0 }
51
50
  sorcery.metamagic: { type: boolean, formula: false }
52
- draconic.damageType:
53
- { type: enum, values: [none, acid, cold, fire, lightning, poison], formula: "'none'" }
54
51
  elementalAffinity.resistant: { type: boolean, input: true, default: false }
55
52
  dragonWings.active: { type: boolean, input: true, default: false }
56
53
  warlock.boons: { type: set, of: pactBoon }
@@ -62,10 +59,35 @@ fields:
62
59
  eldritchBlast.push: { type: integer, formula: 0 }
63
60
 
64
61
  contentTypes:
62
+ # The Hunter's options, by the feature that offers them.
63
+ hunterTactic:
64
+ grantOnly: true
65
+ copies: { max: 0 }
66
+ fields:
67
+ feature:
68
+ type: enum
69
+ values: [hunters-prey, defensive-tactics, multiattack, superior-hunters-defense]
70
+ required: true
71
+
72
+ metamagic:
73
+ grantOnly: true
74
+ copies: { max: 0 }
75
+ effects:
76
+ - { target: sorcery.metamagic, op: set, value: sorcery.left > 0 }
77
+
65
78
  invocation:
79
+ grantOnly: true
66
80
  copies: { max: 0 }
81
+ # An invocation usable once per rest keeps `used` on its pick.
82
+ effects:
83
+ - target: { steps: [rest.long] }
84
+ op: changes
85
+ when: has(self.used) && self.used
86
+ value:
87
+ changes: [{ type: setState, pick: owner, key: used, value: false }]
67
88
 
68
89
  pactBoon:
90
+ grantOnly: true
69
91
  copies: { max: 0 }
70
92
  effects:
71
93
  - target: warlock.boons
package/rules/combat.yaml CHANGED
@@ -15,6 +15,7 @@ viewFields:
15
15
  combat.spellAttackersDisadvantage: { type: boolean, default: false }
16
16
  creature.type:
17
17
  type: enum
18
+ open: true
18
19
  values:
19
20
  [
20
21
  aberration,
@@ -139,7 +140,7 @@ situational:
139
140
  - id: colossusSlayer
140
141
  type: boolean
141
142
  default: false
142
- available: hunter.colossusSlayer
143
+ available: "@hunter-tactics.colossus-slayer in picksOf('subclasses.hunter', 'hunters-prey')"
143
144
  rolls: [damage.weapon]
144
145
  effects:
145
146
  - target: { rolls: [damage.weapon] }
@@ -293,7 +294,7 @@ resultBands:
293
294
  - { id: critical, when: 'roll.kept[0] >= flags.crit' }
294
295
  - { id: natural-miss, when: 'roll.kept[0] == 1' }
295
296
  - { id: rolled, when: flags.ac < 0 }
296
- - { id: hit, when: roll.total >= flags.ac }
297
+ - { id: hit, when: roll.total >= flags.ac, success: true }
297
298
  - { id: miss, when: true }
298
299
 
299
300
  actions:
@@ -7,6 +7,7 @@ contentTypes:
7
7
  by:
8
8
  {
9
9
  type: enum,
10
+ open: true,
10
11
  values:
11
12
  [
12
13
  aberration,
@@ -26,11 +26,13 @@ contentTypes:
26
26
  thunder,
27
27
  ],
28
28
  required: true,
29
+ open: true,
29
30
  }
30
31
  versatileDamage: { type: dice }
31
32
  normalRange: { type: integer }
32
33
  longRange: { type: integer }
33
- ammunition: { type: enum, values: [arrow, blowgun-needle, crossbow-bolt, sling-bullet] }
34
+ ammunition:
35
+ { type: enum, values: [arrow, blowgun-needle, crossbow-bolt, sling-bullet], open: true }
34
36
  weight: { type: decimal } # pounds
35
37
  cost: { type: decimal } # gold pieces
36
38
  state:
@@ -38,9 +40,17 @@ contentTypes:
38
40
  attuned: { type: boolean, default: false }
39
41
  stowed: { type: boolean, default: false } # in an extradimensional container (equipment.yaml)
40
42
  quivered: { type: boolean, default: false } # in an Efficient Quiver (equipment.yaml)
43
+ usedToday: { type: set, of: string, default: [] }
41
44
  actions:
42
45
  - use: weapon-attack
43
46
  effects:
47
+ # An attuned item for other classes than the character's (attunementClasses).
48
+ - target: attunement.classUnmet
49
+ op: add
50
+ value: 1
51
+ when: >-
52
+ self.attuned && has(item.attunementClasses) && size(item.attunementClasses) > 0
53
+ && countList(item.attunementClasses, x in classes.held) == 0
44
54
  - id: magic-attack
45
55
  target: { rolls: [attack.melee, attack.ranged], from: self }
46
56
  op: add
@@ -77,8 +87,16 @@ contentTypes:
77
87
  state:
78
88
  attuned: { type: boolean, default: false }
79
89
  stowed: { type: boolean, default: false } # in an extradimensional container (equipment.yaml)
90
+ usedToday: { type: set, of: string, default: [] }
80
91
  # Every armor item has these effects; the shield switches off the first three (`suppress`).
81
92
  effects:
93
+ # An attuned item for other classes than the character's (attunementClasses).
94
+ - target: attunement.classUnmet
95
+ op: add
96
+ value: 1
97
+ when: >-
98
+ self.attuned && has(item.attunementClasses) && size(item.attunementClasses) > 0
99
+ && countList(item.attunementClasses, x in classes.held) == 0
82
100
  - id: base-ac
83
101
  target: combat.ac
84
102
  op: set
@@ -121,6 +139,25 @@ contentTypes:
121
139
  copies: { perItem: 1 }
122
140
  fields:
123
141
  hitDie: { type: dice, required: true }
142
+ # Its own starting-equipment action gives its starting kit.
143
+ startingKit: { type: boolean, default: false }
144
+ # The abilities multiclassing into it needs 13 in: all of these, and one of those.
145
+ multiclassAll: { type: set, of: string, default: [] }
146
+ multiclassAny: { type: set, of: string, default: [] }
147
+ # The abilities a random start raises first, in order.
148
+ abilityPriority:
149
+ type: list
150
+ of: field
151
+ unique: true
152
+ default:
153
+ [
154
+ abilities.str.score,
155
+ abilities.con.score,
156
+ abilities.dex.score,
157
+ abilities.wis.score,
158
+ abilities.cha.score,
159
+ abilities.int.score,
160
+ ]
124
161
  state:
125
162
  level: { type: integer, default: 1, min: 1, max: 20 }
126
163
  startingClass: { type: boolean, default: false }
@@ -131,16 +168,20 @@ contentTypes:
131
168
  - use: spend-hit-die
132
169
 
133
170
  subclass:
171
+ grantOnly: true
134
172
  copies: { max: 0 }
135
173
  fields:
136
174
  class: { type: id, of: class, required: true }
137
175
 
138
176
  fightingStyle:
177
+ grantOnly: true
139
178
  copies: { max: 0 }
140
179
 
141
- feature: {}
180
+ feature:
181
+ grantOnly: true
142
182
 
143
183
  improvement:
184
+ grantOnly: true
144
185
  copies: { max: 0 }
145
186
 
146
187
  spell:
@@ -165,6 +206,7 @@ contentTypes:
165
206
  transmutation,
166
207
  ],
167
208
  required: true,
209
+ open: true,
168
210
  }
169
211
  classes: { type: set, of: class }
170
212
  ritual: { type: boolean }
@@ -221,6 +263,7 @@ contentTypes:
221
263
  slashing,
222
264
  thunder,
223
265
  ],
266
+ open: true,
224
267
  }
225
268
  damage: { type: dice }
226
269
  damageBonus: { type: integer }
@@ -248,6 +291,7 @@ contentTypes:
248
291
  slashing,
249
292
  thunder,
250
293
  ],
294
+ open: true,
251
295
  }
252
296
  damage2: { type: dice }
253
297
  upcast2Dice: { type: integer }
@@ -257,7 +301,8 @@ contentTypes:
257
301
  upcastHealDice: { type: integer }
258
302
  upcastHealBonus: { type: integer }
259
303
 
260
- trait: {}
304
+ trait:
305
+ grantOnly: true
261
306
 
262
307
  race:
263
308
  copies: { max: 1 }
@@ -270,6 +315,7 @@ contentTypes:
270
315
  - { id: race, target: creature.race, op: set, value: item.id }
271
316
 
272
317
  subrace:
318
+ grantOnly: true
273
319
  copies: { max: 0 }
274
320
  fields:
275
321
  race: { type: id, of: race, required: true }
@@ -284,6 +330,7 @@ contentTypes:
284
330
 
285
331
  # Each ancestry item carries its resistance, since incoming-change tags can't be computed.
286
332
  draconicAncestry:
333
+ grantOnly: true
287
334
  copies: { max: 0 }
288
335
  fields:
289
336
  damageType: { type: enum, values: [acid, cold, fire, lightning, poison], required: true }
@@ -294,11 +341,21 @@ contentTypes:
294
341
  used: { type: boolean, default: false }
295
342
  actions:
296
343
  - use: breath-weapon
344
+ effects:
345
+ - target: { steps: [rest.short, rest.long] }
346
+ op: changes
347
+ when: self.used
348
+ value:
349
+ changes: [{ type: setState, pick: owner, key: used, value: false }]
297
350
 
298
351
  background:
299
352
  copies: { max: 1 }
353
+ fields:
354
+ startingKit: { type: boolean, default: false }
300
355
 
301
356
  backgroundFeature:
357
+ grantOnly: true
302
358
  copies: { max: 0 }
303
359
 
304
- feat: {}
360
+ feat:
361
+ grantOnly: true
package/rules/core.yaml CHANGED
@@ -49,6 +49,7 @@ fields:
49
49
  creature.type:
50
50
  {
51
51
  type: enum,
52
+ open: true,
52
53
  values:
53
54
  [
54
55
  aberration,
@@ -110,6 +111,7 @@ fields:
110
111
  : creature.size == 'huge' ? 4 : creature.size == 'gargantuan' ? 8 : 1)
111
112
 
112
113
  classes.held: { type: set, of: class }
114
+ attunement.classUnmet: { type: integer, formula: 0 }
113
115
  creature.race: { type: string, formula: "''" }
114
116
  magicItems.ignoreRequirements: { type: boolean, formula: false }
115
117
 
@@ -118,22 +120,10 @@ fields:
118
120
  constraints:
119
121
  - id: attunement-requirement
120
122
  check: >-
121
- magicItems.ignoreRequirements || countCopies(has(copy.state.attuned) && copy.state.attuned
122
- && (('attunement.spellcaster' in copy.tags && spellcasting.casterLevel == 0 && pactSlots.max == 0)
123
- || ('attunement.dwarf' in copy.tags && creature.race != @races.dwarf)
124
- || (('attunement.barbarian' in copy.tags || 'attunement.bard' in copy.tags || 'attunement.cleric' in copy.tags || 'attunement.druid' in copy.tags || 'attunement.fighter' in copy.tags || 'attunement.monk' in copy.tags || 'attunement.paladin' in copy.tags || 'attunement.ranger' in copy.tags || 'attunement.rogue' in copy.tags || 'attunement.sorcerer' in copy.tags || 'attunement.warlock' in copy.tags || 'attunement.wizard' in copy.tags)
125
- && !(('attunement.barbarian' in copy.tags && @classes.barbarian in classes.held)
126
- || ('attunement.bard' in copy.tags && @classes.bard in classes.held)
127
- || ('attunement.cleric' in copy.tags && @classes.cleric in classes.held)
128
- || ('attunement.druid' in copy.tags && @classes.druid in classes.held)
129
- || ('attunement.fighter' in copy.tags && @classes.fighter in classes.held)
130
- || ('attunement.monk' in copy.tags && @classes.monk in classes.held)
131
- || ('attunement.paladin' in copy.tags && @classes.paladin in classes.held)
132
- || ('attunement.ranger' in copy.tags && @classes.ranger in classes.held)
133
- || ('attunement.rogue' in copy.tags && @classes.rogue in classes.held)
134
- || ('attunement.sorcerer' in copy.tags && @classes.sorcerer in classes.held)
135
- || ('attunement.warlock' in copy.tags && @classes.warlock in classes.held)
136
- || ('attunement.wizard' in copy.tags && @classes.wizard in classes.held))))) == 0
123
+ magicItems.ignoreRequirements || (attunement.classUnmet == 0
124
+ && countCopies(has(copy.state.attuned) && copy.state.attuned
125
+ && (('attunement.spellcaster' in copy.tags && spellcasting.casterLevel == 0 && pactSlots.max == 0)
126
+ || ('attunement.dwarf' in copy.tags && creature.race != @races.dwarf))) == 0)
137
127
  - id: attunement-limit
138
128
  check: countCopies(has(copy.state.attuned) && copy.state.attuned) <= 3
139
129
  data:
@@ -14,12 +14,14 @@ actions:
14
14
  available: character.creating
15
15
  steps:
16
16
  - id: race
17
+ tags: [creation.race]
17
18
  kind: choice
18
19
  options: { content: { type: race } }
19
20
  changes:
20
21
  - type: addItem
21
22
  entry: { id: race, item: picked.id }
22
23
  - id: subrace
24
+ tags: [creation.subrace]
23
25
  kind: choice
24
26
  options: { content: { type: subrace, where: item.race == steps.race.picked } }
25
27
  changes:
@@ -27,18 +29,21 @@ actions:
27
29
  path: { entry: { formula: 'steps.race.added[0]' }, choice: [subrace] }
28
30
  option: picked.id
29
31
  - id: class
32
+ tags: [creation.class]
30
33
  kind: choice
31
34
  options: { content: { type: class } }
32
35
  changes:
33
36
  - type: addItem
34
37
  entry: { id: class, item: picked.id, state: { level: 1, startingClass: true } }
35
38
  - id: background
39
+ tags: [creation.background]
36
40
  kind: choice
37
41
  options: { content: { type: background } }
38
42
  changes:
39
43
  - type: addItem
40
44
  entry: { id: background, item: picked.id }
41
45
  - id: method
46
+ tags: [creation.method]
42
47
  kind: choice
43
48
  options: [{ id: roll }, { id: point-buy }, { id: standard-array }]
44
49
  recommend: "'standard-array'"
@@ -95,12 +100,12 @@ actions:
95
100
  - id: equipment
96
101
  kind: action
97
102
  use: starting-equipment
98
- params: { class: steps.class.picked, background: steps.background.picked }
99
103
  - id: hitDie
100
104
  kind: changes
101
105
  changes:
102
106
  - { type: set, field: hp.base, value: maxOf(steps.class.item.hitDie) }
103
107
  - id: finish
108
+ tags: [creation.finish]
104
109
  kind: changes
105
110
  sheet: proposed
106
111
  changes:
@@ -120,6 +125,7 @@ actions:
120
125
  - { id: roll5, kind: roll, tags: [creation], parts: [{ dice: 4d6dl1 }] }
121
126
  - { id: roll6, kind: roll, tags: [creation], parts: [{ dice: 4d6dl1 }] }
122
127
  - id: race
128
+ tags: [creation.race]
123
129
  kind: choice
124
130
  options: { content: { type: race } }
125
131
  random: true
@@ -127,6 +133,7 @@ actions:
127
133
  - type: addItem
128
134
  entry: { id: race, item: picked.id }
129
135
  - id: subrace
136
+ tags: [creation.subrace]
130
137
  kind: choice
131
138
  options: { content: { type: subrace, where: item.race == steps.race.picked } }
132
139
  random: true
@@ -135,6 +142,7 @@ actions:
135
142
  path: { entry: { formula: 'steps.race.added[0]' }, choice: [subrace] }
136
143
  option: picked.id
137
144
  - id: class
145
+ tags: [creation.class]
138
146
  kind: choice
139
147
  options: { content: { type: class } }
140
148
  random: true
@@ -142,14 +150,15 @@ actions:
142
150
  - type: addItem
143
151
  entry: { id: class, item: picked.id, state: { level: 1, startingClass: true } }
144
152
  - id: background
153
+ tags: [creation.background]
145
154
  kind: choice
146
155
  options: { content: { type: background } }
147
156
  random: true
148
157
  changes:
149
158
  - type: addItem
150
159
  entry: { id: background, item: picked.id }
151
- # The class's ability priority: the highest roll goes to its first ability, the next to its
152
- # second, and so on. A class this pack doesn't know takes the barbarian's (the first).
160
+ # The class's ability priority (its abilityPriority): the highest roll goes to its
161
+ # first ability, the next to its second, and so on.
153
162
  - id: priority
154
163
  kind: branch
155
164
  cases:
@@ -158,34 +167,7 @@ actions:
158
167
  sorted: >-
159
168
  sortList([steps.roll1.total, steps.roll2.total, steps.roll3.total,
160
169
  steps.roll4.total, steps.roll5.total, steps.roll6.total], -x)
161
- priority: >-
162
- [
163
- ['str', 'con', 'dex', 'wis', 'cha', 'int'],
164
- ['cha', 'dex', 'con', 'wis', 'int', 'str'],
165
- ['wis', 'con', 'str', 'dex', 'cha', 'int'],
166
- ['wis', 'con', 'dex', 'int', 'cha', 'str'],
167
- ['str', 'con', 'dex', 'wis', 'cha', 'int'],
168
- ['dex', 'wis', 'con', 'str', 'int', 'cha'],
169
- ['str', 'cha', 'con', 'wis', 'dex', 'int'],
170
- ['dex', 'wis', 'con', 'str', 'int', 'cha'],
171
- ['dex', 'con', 'wis', 'int', 'cha', 'str'],
172
- ['cha', 'con', 'dex', 'wis', 'int', 'str'],
173
- ['cha', 'con', 'dex', 'wis', 'int', 'str'],
174
- ['int', 'con', 'dex', 'wis', 'cha', 'str']
175
- ][max(0, indexOf([
176
- @classes.barbarian,
177
- @classes.bard,
178
- @classes.cleric,
179
- @classes.druid,
180
- @classes.fighter,
181
- @classes.monk,
182
- @classes.paladin,
183
- @classes.ranger,
184
- @classes.rogue,
185
- @classes.sorcerer,
186
- @classes.warlock,
187
- @classes.wizard
188
- ], steps.class.picked))]
170
+ priority: steps.class.item.abilityPriority
189
171
  - id: scores
190
172
  kind: allocate
191
173
  fields: { type: field, where: "'ability-base' in item.tags" }
@@ -197,21 +179,25 @@ actions:
197
179
  - steps.roll5.total
198
180
  - steps.roll6.total
199
181
  recommend:
200
- 'abilities.{ability}.base': flags.sorted[indexOf(flags.priority, '{ability}')]
182
+ 'abilities.{ability}.base': flags.sorted[indexOf(flags.priority, @abilities.{ability}.score)]
201
183
  - id: picks
202
184
  kind: choice
203
185
  options: { pending: {} }
204
186
  random: "!(choice.owner == @races.half-elf && choice.id == 'ability-scores')"
205
- recommend: flags.priority[minList(options, indexOf(flags.priority, x))]
187
+ recommend: >-
188
+ sortList(options, indexOf(flags.priority, [@abilities.str.score, @abilities.dex.score,
189
+ @abilities.con.score, @abilities.int.score, @abilities.wis.score,
190
+ @abilities.cha.score][indexOf(['str', 'dex', 'con', 'int', 'wis', 'cha'], x)]))[0]
206
191
  - id: equipment
207
192
  kind: action
208
193
  use: starting-equipment
209
- params: { class: steps.class.picked, background: steps.background.picked, random: true }
194
+ params: { random: true }
210
195
  - id: hitDie
211
196
  kind: changes
212
197
  changes:
213
198
  - { type: set, field: hp.base, value: maxOf(steps.class.item.hitDie) }
214
199
  - id: finish
200
+ tags: [creation.finish]
215
201
  kind: changes
216
202
  sheet: proposed
217
203
  changes: