@opencharacter/5e-srd-5.1 0.1.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 (101) hide show
  1. package/README.md +58 -7
  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 -7
  14. package/content/classes/wizard.yaml +56 -0
  15. package/content/features/druid/natural-recovery.yaml +23 -45
  16. package/content/features/sorcerer/dragon-ancestor.yaml +1 -43
  17. package/content/features/sorcerer/elemental-affinity.yaml +29 -8
  18. package/content/features/sorcerer/metamagic.yaml +1 -93
  19. package/content/features/wizard/arcane-recovery.yaml +22 -67
  20. package/content/hunter-tactics/colossus-slayer.yaml +4 -0
  21. package/content/hunter-tactics/escape-the-horde.yaml +4 -0
  22. package/content/hunter-tactics/evasion.yaml +6 -0
  23. package/content/hunter-tactics/giant-killer.yaml +4 -0
  24. package/content/hunter-tactics/horde-breaker.yaml +4 -0
  25. package/content/hunter-tactics/multiattack-defense.yaml +4 -0
  26. package/content/hunter-tactics/stand-against-the-tide.yaml +4 -0
  27. package/content/hunter-tactics/steel-will.yaml +4 -0
  28. package/content/hunter-tactics/uncanny-dodge.yaml +6 -0
  29. package/content/hunter-tactics/volley.yaml +6 -0
  30. package/content/hunter-tactics/whirlwind-attack.yaml +6 -0
  31. package/content/invocations/bewitching-whispers.yaml +4 -2
  32. package/content/invocations/dreadful-word.yaml +4 -2
  33. package/content/invocations/eldritch-spear.yaml +3 -2
  34. package/content/invocations/minions-of-chaos.yaml +4 -2
  35. package/content/invocations/mire-the-mind.yaml +4 -2
  36. package/content/invocations/sculptor-of-flesh.yaml +4 -2
  37. package/content/invocations/sign-of-ill-omen.yaml +4 -2
  38. package/content/invocations/thief-of-five-fates.yaml +4 -2
  39. package/content/magic-items/arrow-of-slaying.yaml +1 -0
  40. package/content/magic-items/bag-of-holding.yaml +2 -0
  41. package/content/magic-items/efficient-quiver.yaml +2 -0
  42. package/content/magic-items/handy-haversack.yaml +2 -0
  43. package/content/magic-items/holy-avenger.yaml +9 -2
  44. package/content/magic-items/necklace-of-prayer-beads.yaml +1 -1
  45. package/content/magic-items/portable-hole.yaml +2 -0
  46. package/content/magic-items/ring-of-x-ray-vision.yaml +6 -0
  47. package/content/magic-items/robe-of-the-archmagi.yaml +1 -1
  48. package/content/magic-items/staff-of-charming.yaml +17 -12
  49. package/content/magic-items/staff-of-fire.yaml +9 -2
  50. package/content/magic-items/staff-of-frost.yaml +9 -2
  51. package/content/magic-items/staff-of-healing.yaml +9 -2
  52. package/content/magic-items/staff-of-power.yaml +2 -1
  53. package/content/magic-items/staff-of-swarming-insects.yaml +17 -12
  54. package/content/magic-items/staff-of-the-magi.yaml +2 -1
  55. package/content/magic-items/staff-of-the-python.yaml +7 -2
  56. package/content/magic-items/staff-of-the-woodlands.yaml +2 -1
  57. package/content/magic-items/staff-of-withering.yaml +9 -2
  58. package/content/metamagic/careful-spell.yaml +11 -0
  59. package/content/metamagic/distant-spell.yaml +11 -0
  60. package/content/metamagic/empowered-spell.yaml +22 -0
  61. package/content/metamagic/extended-spell.yaml +11 -0
  62. package/content/metamagic/heightened-spell.yaml +11 -0
  63. package/content/metamagic/quickened-spell.yaml +11 -0
  64. package/content/metamagic/subtle-spell.yaml +11 -0
  65. package/content/metamagic/twinned-spell.yaml +16 -0
  66. package/content/races/half-orc.yaml +0 -2
  67. package/content/races/tiefling.yaml +0 -3
  68. package/content/spells/death-ward.yaml +11 -0
  69. package/content/spells/eldritch-blast.yaml +1 -1
  70. package/content/subclasses/hunter.yaml +4 -29
  71. package/content/templates/eldritch-spear.yaml +4 -0
  72. package/content/traits/infernal-legacy.yaml +24 -2
  73. package/content/traits/relentless-endurance.yaml +11 -10
  74. package/content/weapons/javelin.yaml +1 -1
  75. package/dist/i18n/en.js +1 -1
  76. package/dist/i18n/en.json +301 -224
  77. package/dist/index.js +1 -1
  78. package/dist/release.json +6564 -7424
  79. package/i18n/en/monster-rules.yaml +1 -1
  80. package/i18n/en/type-fields.yaml +7 -0
  81. package/i18n/en.yaml +292 -246
  82. package/pack.yaml +2 -2
  83. package/package.json +2 -2
  84. package/rules/advancement.yaml +67 -38
  85. package/rules/checks.yaml +1 -1
  86. package/rules/classes.yaml +25 -4
  87. package/rules/combat.yaml +8 -2
  88. package/rules/conditions.yaml +1 -0
  89. package/rules/content-types.yaml +64 -4
  90. package/rules/core.yaml +6 -16
  91. package/rules/creation.yaml +26 -36
  92. package/rules/display.yaml +20 -11
  93. package/rules/equipment.yaml +13 -16
  94. package/rules/magic-items.yaml +19 -1
  95. package/rules/monsters.yaml +8 -2
  96. package/rules/rest.yaml +2 -73
  97. package/rules/spellcasting.yaml +1 -0
  98. package/rules/starting-equipment.yaml +17 -646
  99. package/rules/tags.yaml +12 -12
  100. package/upgrades/0.1.0-to-0.2.0.yaml +62 -0
  101. 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.1.0
3
- specVersion: 0.1.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.1.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.1.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 }
@@ -59,14 +56,38 @@ fields:
59
56
  type: integer,
60
57
  formula: 'level.total >= 17 ? 4 : level.total >= 11 ? 3 : level.total >= 5 ? 2 : 1',
61
58
  }
62
- eldritchBlast.range: { type: integer, formula: 120 }
63
59
  eldritchBlast.push: { type: integer, formula: 0 }
64
60
 
65
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
+
66
78
  invocation:
79
+ grantOnly: true
67
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 }]
68
88
 
69
89
  pactBoon:
90
+ grantOnly: true
70
91
  copies: { max: 0 }
71
92
  effects:
72
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:
@@ -754,6 +755,7 @@ actions:
754
755
  - id: breath-weapon
755
756
  tags: [breath-weapon]
756
757
  results: [breathed]
758
+ available: '!self.used'
757
759
  participants:
758
760
  target: {}
759
761
  steps:
@@ -788,6 +790,10 @@ actions:
788
790
  - save-half
789
791
  - { formula: "'save.' + item.save" }
790
792
  - { formula: "steps.saves.result == 'success' ? 'save-success' : 'save-failure'" }
793
+ - id: spend
794
+ kind: changes
795
+ changes:
796
+ - { type: setState, pick: self, key: used, value: true }
791
797
  - id: result
792
798
  kind: outcome
793
799
  result: "'breathed'"
@@ -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,18 +330,32 @@ 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 }
290
337
  area: { type: enum, values: [line, cone], required: true }
291
338
  save: { type: enum, values: [dex, con], required: true }
339
+ # Once until a short or long rest, on the ancestry's pick.
340
+ state:
341
+ used: { type: boolean, default: false }
292
342
  actions:
293
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 }]
294
350
 
295
351
  background:
296
352
  copies: { max: 1 }
353
+ fields:
354
+ startingKit: { type: boolean, default: false }
297
355
 
298
356
  backgroundFeature:
357
+ grantOnly: true
299
358
  copies: { max: 0 }
300
359
 
301
- 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: