@opencharacter/awn-core 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 (123) hide show
  1. package/README.md +48 -3
  2. package/content/edges/educated.yaml +11 -0
  3. package/content/edges/focused.yaml +1 -0
  4. package/content/edges/forged-by-fire.yaml +1 -0
  5. package/content/edges/hardened-genetics.yaml +1 -0
  6. package/content/edges/mutant.yaml +17 -0
  7. package/content/edges/mutation-acceleration.yaml +24 -0
  8. package/content/edges/prodigy.yaml +1 -0
  9. package/content/foci/unstable-mutant.yaml +14 -0
  10. package/content/mutation-categories/cognition.yaml +2 -0
  11. package/content/mutation-categories/exotic.yaml +2 -0
  12. package/content/mutation-categories/hybrid.yaml +2 -0
  13. package/content/mutation-categories/pseudo-psychic.yaml +2 -0
  14. package/content/mutation-categories/sense.yaml +2 -0
  15. package/content/mutation-categories/structure.yaml +2 -0
  16. package/content/mutations/cognition/augmented-cognition.yaml +1 -1
  17. package/content/mutations/cognition/ballistic-calculator.yaml +1 -1
  18. package/content/mutations/cognition/eidetic-memory.yaml +1 -1
  19. package/content/mutations/cognition/internal-map.yaml +1 -1
  20. package/content/mutations/cognition/intuitive-leap.yaml +1 -1
  21. package/content/mutations/cognition/memory-eater.yaml +1 -1
  22. package/content/mutations/cognition/mental-bifurcation.yaml +1 -1
  23. package/content/mutations/cognition/mind-over-body.yaml +1 -1
  24. package/content/mutations/cognition/omniglot.yaml +1 -1
  25. package/content/mutations/cognition/predictive-analysis.yaml +1 -1
  26. package/content/mutations/exotic/chameleon-field.yaml +1 -1
  27. package/content/mutations/exotic/electrical-generation.yaml +1 -1
  28. package/content/mutations/exotic/hemovore.yaml +1 -1
  29. package/content/mutations/exotic/image-projection.yaml +1 -1
  30. package/content/mutations/exotic/metamorph.yaml +1 -1
  31. package/content/mutations/exotic/molecular-phasing.yaml +1 -1
  32. package/content/mutations/exotic/natural-projectiles.yaml +1 -1
  33. package/content/mutations/exotic/pheromone-cloud.yaml +1 -1
  34. package/content/mutations/exotic/photon-manipulation.yaml +1 -1
  35. package/content/mutations/exotic/sound-generation.yaml +1 -1
  36. package/content/mutations/hybrid/aquatic-adaptation.yaml +1 -1
  37. package/content/mutations/hybrid/arboreal-aptitude.yaml +1 -1
  38. package/content/mutations/hybrid/centauroid.yaml +1 -1
  39. package/content/mutations/hybrid/crushing-jaws.yaml +1 -1
  40. package/content/mutations/hybrid/digitigrade-legs.yaml +1 -1
  41. package/content/mutations/hybrid/functional-wings.yaml +1 -1
  42. package/content/mutations/hybrid/prehensile-limb.yaml +1 -1
  43. package/content/mutations/hybrid/savage-claws.yaml +1 -1
  44. package/content/mutations/hybrid/thick-hide.yaml +1 -1
  45. package/content/mutations/hybrid/venom-glands.yaml +1 -1
  46. package/content/mutations/pseudo-psychic/force-field.yaml +1 -1
  47. package/content/mutations/pseudo-psychic/induce-confusion.yaml +1 -1
  48. package/content/mutations/pseudo-psychic/limited-precognition.yaml +1 -1
  49. package/content/mutations/pseudo-psychic/mental-domination.yaml +1 -1
  50. package/content/mutations/pseudo-psychic/mind-reader.yaml +1 -1
  51. package/content/mutations/pseudo-psychic/neural-lockdown.yaml +1 -1
  52. package/content/mutations/pseudo-psychic/panic-wave.yaml +1 -1
  53. package/content/mutations/pseudo-psychic/spatial-displacement.yaml +1 -1
  54. package/content/mutations/pseudo-psychic/telekinetic-reach.yaml +1 -1
  55. package/content/mutations/pseudo-psychic/thermokinesis.yaml +1 -1
  56. package/content/mutations/sense/accelerated-perception.yaml +1 -1
  57. package/content/mutations/sense/acute-hearing.yaml +1 -1
  58. package/content/mutations/sense/acute-olfaction.yaml +1 -1
  59. package/content/mutations/sense/echolocation.yaml +1 -1
  60. package/content/mutations/sense/empathic-sensitivity.yaml +1 -1
  61. package/content/mutations/sense/night-vision.yaml +1 -1
  62. package/content/mutations/sense/panoptic-vision.yaml +1 -1
  63. package/content/mutations/sense/tactile-analysis.yaml +1 -1
  64. package/content/mutations/sense/telescopic-sight.yaml +1 -1
  65. package/content/mutations/sense/thermal-vision.yaml +1 -1
  66. package/content/mutations/structure/augmented-muscle.yaml +1 -1
  67. package/content/mutations/structure/explosive-exertion.yaml +1 -1
  68. package/content/mutations/structure/hypercontortion.yaml +1 -1
  69. package/content/mutations/structure/improved-joints.yaml +1 -1
  70. package/content/mutations/structure/leaper.yaml +1 -1
  71. package/content/mutations/structure/redundant-organs.yaml +1 -1
  72. package/content/mutations/structure/regenerator.yaml +1 -1
  73. package/content/mutations/structure/reinforced-tissues.yaml +1 -1
  74. package/content/mutations/structure/sealed-systems.yaml +1 -1
  75. package/content/mutations/structure/stamina-reserves.yaml +1 -1
  76. package/content/prepared/backpack.yaml +5 -0
  77. package/content/prepared/binoculars.yaml +4 -0
  78. package/content/prepared/camping-kit.yaml +4 -0
  79. package/content/prepared/cash.yaml +4 -0
  80. package/content/prepared/cold-weather-clothes.yaml +5 -0
  81. package/content/prepared/criminal-firearm.yaml +7 -0
  82. package/content/prepared/first-aid-kit.yaml +4 -0
  83. package/content/prepared/flashlight.yaml +4 -0
  84. package/content/prepared/heavy-pistol.yaml +7 -0
  85. package/content/prepared/knife.yaml +5 -0
  86. package/content/prepared/laptop.yaml +4 -0
  87. package/content/prepared/light-pistol.yaml +7 -0
  88. package/content/prepared/lighters.yaml +4 -0
  89. package/content/prepared/lockpick-kit.yaml +4 -0
  90. package/content/prepared/preserved-food.yaml +4 -0
  91. package/content/prepared/profession-item.yaml +4 -0
  92. package/content/prepared/purification-tablets.yaml +4 -0
  93. package/content/prepared/radio.yaml +4 -0
  94. package/content/prepared/rifle.yaml +7 -0
  95. package/content/prepared/shotgun.yaml +7 -0
  96. package/content/prepared/sleeping-bag.yaml +4 -0
  97. package/content/prepared/smartphone.yaml +4 -0
  98. package/content/prepared/solar-charger.yaml +4 -0
  99. package/content/prepared/tent.yaml +4 -0
  100. package/content/prepared/toolkit.yaml +4 -0
  101. package/content/prepared/walkie-talkies.yaml +5 -0
  102. package/content/prepared/water-purifier.yaml +4 -0
  103. package/content/weapons/primitive-bow.yaml +1 -0
  104. package/dist/i18n/en.js +1 -1
  105. package/dist/i18n/en.json +101 -34
  106. package/dist/index.js +1 -1
  107. package/dist/release.json +2008 -1434
  108. package/i18n/en/content.yaml +49 -0
  109. package/i18n/en/core.yaml +52 -34
  110. package/pack.yaml +2 -2
  111. package/package.json +2 -2
  112. package/rules/advancement.yaml +41 -42
  113. package/rules/backgrounds.yaml +17 -16
  114. package/rules/checks.yaml +5 -1
  115. package/rules/combat.yaml +1 -1
  116. package/rules/creation.yaml +71 -25
  117. package/rules/features.yaml +9 -4
  118. package/rules/gear.yaml +20 -1
  119. package/rules/layout.yaml +2 -0
  120. package/rules/mutations.yaml +50 -26
  121. package/rules/skills.yaml +8 -4
  122. package/rules/starting-equipment.yaml +24 -101
  123. package/rules/tags.yaml +13 -0
@@ -290,96 +290,112 @@ actions:
290
290
  kind: action
291
291
  use: starting-equipment
292
292
  params: { random: params.random }
293
- # A start may bring more than the survivor can haul. Readied items past the push limit are
294
- # stowed, then Stowed ones past it left behind (gear, then weapons, then armor, the heaviest
295
- # first), so play starts within the limits.
293
+ # A start may bring more than the survivor can haul. Readied items past the limit are
294
+ # stowed; Stowed gear past it is readied where it fits, then left behind (gear, then weapons,
295
+ # then armor, the heaviest first), so play starts unencumbered.
296
296
  - id: stowGear
297
297
  kind: choice
298
298
  sheet: proposed
299
- when: encumbrance.overload > 2
300
- repeat: { max: 20 }
299
+ when: encumbrance.overload > 0
300
+ repeat: { max: 20, min: 20 }
301
301
  options:
302
302
  copies:
303
303
  type: gear
304
304
  where: >-
305
305
  self.equipped && has(item.enc) && item.enc > 0
306
306
  && !(has(item.wornFree) && item.wornFree)
307
- && encumbrance.readied > encumbrance.readied.max + 4
307
+ && encumbrance.readied > encumbrance.readied.max
308
308
  prefer: item.enc
309
309
  changes:
310
310
  - { type: equip, entry: picked, equipped: false }
311
311
  - id: stowWeapon
312
312
  kind: choice
313
313
  sheet: proposed
314
- when: encumbrance.overload > 2
315
- repeat: { max: 20 }
314
+ when: encumbrance.overload > 0
315
+ repeat: { max: 20, min: 20 }
316
316
  options:
317
317
  copies:
318
318
  type: weapon
319
319
  where: >-
320
320
  self.equipped && item.enc > 0
321
- && encumbrance.readied > encumbrance.readied.max + 4
321
+ && encumbrance.readied > encumbrance.readied.max
322
322
  prefer: item.enc
323
323
  changes:
324
324
  - { type: equip, entry: picked, equipped: false }
325
325
  - id: stowArmor
326
326
  kind: choice
327
327
  sheet: proposed
328
- when: encumbrance.overload > 2
329
- repeat: { max: 20 }
328
+ when: encumbrance.overload > 0
329
+ repeat: { max: 20, min: 20 }
330
330
  options:
331
331
  copies:
332
332
  type: armor
333
333
  where: >-
334
334
  self.equipped && item.enc > 0
335
- && encumbrance.readied > encumbrance.readied.max + 4
335
+ && encumbrance.readied > encumbrance.readied.max
336
336
  prefer: item.enc
337
337
  changes:
338
338
  - { type: equip, entry: picked, equipped: false }
339
- - id: dropGear
339
+ - id: readyGear
340
340
  kind: choice
341
341
  sheet: proposed
342
- when: encumbrance.overload > 2
343
- repeat: { max: 20 }
342
+ when: encumbrance.overload > 0
343
+ repeat: { max: 20, min: 20 }
344
344
  options:
345
345
  copies:
346
346
  type: gear
347
347
  where: >-
348
348
  !self.equipped && has(item.enc) && item.enc > 0
349
- && encumbrance.stowed > encumbrance.stowed.max + 8
349
+ && encumbrance.stowed > encumbrance.stowed.max
350
+ && encumbrance.readied + item.enc * self.quantity <= encumbrance.readied.max
350
351
  prefer: item.enc
352
+ changes:
353
+ - { type: equip, entry: picked, equipped: true }
354
+ - id: dropGear
355
+ kind: choice
356
+ sheet: proposed
357
+ when: encumbrance.overload > 0
358
+ repeat: { max: 20, min: 20 }
359
+ options:
360
+ copies:
361
+ type: gear
362
+ where: >-
363
+ !self.equipped && has(item.enc) && item.enc > 0
364
+ && encumbrance.stowed > encumbrance.stowed.max
365
+ prefer: "item.enc * ('bundleable' in item.tags ? floor((self.quantity + 2) / 3) : self.quantity)"
351
366
  changes:
352
367
  - { type: removeItem, entry: picked }
353
368
  - id: dropWeapon
354
369
  kind: choice
355
370
  sheet: proposed
356
- when: encumbrance.overload > 2
357
- repeat: { max: 20 }
371
+ when: encumbrance.overload > 0
372
+ repeat: { max: 20, min: 20 }
358
373
  options:
359
374
  copies:
360
375
  type: weapon
361
376
  where: >-
362
377
  !self.equipped && item.enc > 0
363
- && encumbrance.stowed > encumbrance.stowed.max + 8
378
+ && encumbrance.stowed > encumbrance.stowed.max
364
379
  prefer: item.enc
365
380
  changes:
366
381
  - { type: removeItem, entry: picked }
367
382
  - id: dropArmor
368
383
  kind: choice
369
384
  sheet: proposed
370
- when: encumbrance.overload > 2
371
- repeat: { max: 20 }
385
+ when: encumbrance.overload > 0
386
+ repeat: { max: 20, min: 20 }
372
387
  options:
373
388
  copies:
374
389
  type: armor
375
390
  where: >-
376
391
  !self.equipped && item.enc > 0
377
- && encumbrance.stowed > encumbrance.stowed.max + 8
392
+ && encumbrance.stowed > encumbrance.stowed.max
378
393
  prefer: item.enc
379
394
  changes:
380
395
  - { type: removeItem, entry: picked }
381
396
 
382
397
  - id: finish
398
+ tags: [creation.finish]
383
399
  kind: changes
384
400
  sheet: proposed
385
401
  changes:
@@ -391,11 +407,41 @@ actions:
391
407
  # One entry of a background's tables: a skill (`any-combat`, `any-skill` or a skill key), or
392
408
  # an attribute bonus, which the highest attribute that can take it all is recommended.
393
409
  - id: gain-benefit
410
+ available: character.creating
394
411
  params:
395
- entry: { type: string }
412
+ entry:
413
+ type: enum
414
+ open: true
415
+ values:
416
+ [
417
+ administer,
418
+ connect,
419
+ drive,
420
+ exert,
421
+ fix,
422
+ heal,
423
+ know,
424
+ lead,
425
+ notice,
426
+ perform,
427
+ program,
428
+ punch,
429
+ shoot,
430
+ sneak,
431
+ stab,
432
+ survive,
433
+ talk,
434
+ trade,
435
+ work,
436
+ any-combat,
437
+ any-skill,
438
+ any-stat-1,
439
+ physical2,
440
+ mental2,
441
+ ]
396
442
  prefer: { type: set, of: string, default: [] }
397
443
  random: { type: boolean, default: false }
398
- results: [gained]
444
+ results: [gained, none]
399
445
  steps:
400
446
  - id: skill
401
447
  kind: action
@@ -446,4 +492,4 @@ actions:
446
492
  by: 1
447
493
  - id: result
448
494
  kind: outcome
449
- result: "'gained'"
495
+ result: "has(steps.skill) && steps.skill.result == 'none' ? 'none' : 'gained'"
@@ -4,6 +4,8 @@ contentTypes:
4
4
  edge:
5
5
  fields:
6
6
  bonusSkill: { type: set, of: string, default: [] }
7
+ # It has a `gain-part` action, run when the edge is taken.
8
+ gainPart: { type: boolean, default: false }
7
9
  effects:
8
10
  - { target: edges.held, op: grant, value: { formula: item.id } }
9
11
 
@@ -18,6 +20,8 @@ contentTypes:
18
20
  specialty: { type: boolean, default: false }
19
21
  bonusSkill: { type: set, of: string, default: [] }
20
22
  bonusSkill2: { type: set, of: string, default: [] }
23
+ # It has a `gain-part` action, run when the focus is taken.
24
+ gainPart: { type: boolean, default: false }
21
25
  state:
22
26
  level: { type: integer, default: 1, min: 1, max: item.maxLevel }
23
27
  effects:
@@ -35,7 +39,9 @@ fields:
35
39
 
36
40
  # Two edges at 1st level and one at 5th; Forged By Fire takes the place of the first two and
37
41
  # gives one at each of 2nd, 3rd and 4th instead.
38
- edges.forgedByFire: { type: boolean, formula: '@edges.forged-by-fire in edges.held' }
42
+ edges.forgedByFire:
43
+ type: boolean
44
+ formula: "countCopies(copy.type == 'edge' && 'first-edge' in copy.tags) > 0"
39
45
  edges.allowed:
40
46
  type: integer
41
47
  formula: >-
@@ -61,9 +67,8 @@ constraints:
61
67
  check: '!character.creating || !edges.underdog || attributes.baseTotal <= -1'
62
68
  # Each edge once, but Focused; each focus once, but Specialist.
63
69
  - id: edge-once
64
- check: >-
65
- countCopies(copy.type == 'edge' && copy.item != @edges.focused) == size(edges.held)
66
- - (@edges.focused in edges.held ? 1 : 0)
70
+ copies: { type: edge, where: "!('repeatable' in item.tags)" }
71
+ check: countCopies(copy.item == item.id) == 1
67
72
  - id: focus-once
68
73
  copies: { type: focus, where: '!item.multiple' }
69
74
  check: countCopies(copy.item == item.id) == 1
package/rules/gear.yaml CHANGED
@@ -20,6 +20,8 @@ contentTypes:
20
20
  techLevel: { type: integer, min: 0, max: 5 }
21
21
  traumaDie: { type: dice }
22
22
  traumaRating: { type: integer, min: 1 }
23
+ # The ammunition a primitive missile weapon starts with in a wasteland warrior's kit.
24
+ kitAmmo: { type: id, of: gear }
23
25
  state:
24
26
  shots: { type: integer, min: 0, default: 'has(item.magazine) ? item.magazine : 0' }
25
27
  computed:
@@ -101,6 +103,19 @@ contentTypes:
101
103
  # Drugs and stims: the Heal level applying it needs.
102
104
  healSkill: { type: integer, min: 0 }
103
105
 
106
+ # A pick on the prepared survivors' list: the item (`gear`, a weapon, armor or gear ID; another
107
+ # pack's qualified), how many, readied or not, and what comes with it (a firearm's magazines).
108
+ prepared:
109
+ grantOnly: true
110
+ carried: false
111
+ copies: { max: 0 }
112
+ fields:
113
+ gear: { type: string, required: true }
114
+ quantity: { type: integer, default: 1, min: 1 }
115
+ equipped: { type: boolean, default: false }
116
+ extra: { type: string }
117
+ extraQuantity: { type: integer, default: 1, min: 1 }
118
+
104
119
  # Everyone has fists.
105
120
  standardCopies:
106
121
  - { type: weapon, where: 'item.id == @weapons.unarmed' }
@@ -154,7 +169,11 @@ situational:
154
169
 
155
170
  resultBands:
156
171
  attack:
157
- - { id: hit, when: 'roll.kept[0] == 20 || (roll.kept[0] != 1 && roll.total >= flags.ac)' }
172
+ - {
173
+ id: hit,
174
+ when: 'roll.kept[0] == 20 || (roll.kept[0] != 1 && roll.total >= flags.ac)',
175
+ success: true,
176
+ }
158
177
  - { id: miss, when: true }
159
178
 
160
179
  actions:
package/rules/layout.yaml CHANGED
@@ -118,6 +118,8 @@ display:
118
118
  foci.bonus: { hidden: true }
119
119
  encumbrance.armorFactor: { hidden: true }
120
120
  mutations.mutant: { hidden: true }
121
+ mutations.barred: { hidden: true }
122
+ mutations.points.edges: { hidden: true }
121
123
  mutations.noVision: { hidden: true }
122
124
  mutations.noHearing: { hidden: true }
123
125
  mutations.sensitiveSkin: { hidden: true }
@@ -1,15 +1,16 @@
1
1
  contentTypes:
2
+ # The positive mutations' categories: the book's six, in its order on the d6 table.
3
+ mutationCategory:
4
+ grantOnly: true
5
+ carried: false
6
+ copies: { max: 0 }
7
+
2
8
  # Positive mutations: their category and their d10 roll within it (the d6 picks the category).
3
9
  # `origin` is how it was bought: one point rolled, three for a pair rolled in one category,
4
10
  # three chosen, or free (a negative mutation's compensation, the Unstable Mutant focus).
5
11
  mutation:
6
12
  fields:
7
- category:
8
- {
9
- type: enum,
10
- values: [structure, sense, hybrid, cognition, pseudo-psychic, exotic],
11
- required: true,
12
- }
13
+ category: { type: id, of: mutationCategory, required: true }
13
14
  roll: { type: integer, required: true, min: 1, max: 10 }
14
15
  bonusSkill: { type: set, of: string, default: [] }
15
16
  state:
@@ -23,14 +24,20 @@ contentTypes:
23
24
  light: { type: boolean, default: false }
24
25
 
25
26
  fields:
26
- mutations.mutant: { type: boolean, formula: '@edges.mutant in edges.held' }
27
+ mutations.mutant:
28
+ type: boolean
29
+ formula: "countCopies(copy.type == 'edge' && 'makes-mutant' in copy.tags) > 0"
30
+ # An edge that bars mutation is held (Hardened Genetics).
31
+ mutations.barred:
32
+ type: boolean
33
+ formula: "countCopies(copy.type == 'edge' && 'not-mutant' in copy.tags) > 0"
34
+ # Mutation points edges give: 2 for Mutant, 2 for Mutation Acceleration.
35
+ mutations.points.edges: { type: integer, formula: 0 }
27
36
  mutations.negatives: { type: integer, formula: "countCopies(copy.type == 'negative')" }
28
- # Mutation points: 2 for Mutant, 2 for Mutation Acceleration, 2 per negative mutation.
37
+ # Mutation points: the edges', and 2 per negative mutation.
29
38
  mutations.points.total:
30
39
  type: integer
31
- formula: >-
32
- (mutations.mutant ? 2 : 0) + (@edges.mutation-acceleration in edges.held ? 2 : 0)
33
- + 2 * mutations.negatives
40
+ formula: mutations.points.edges + 2 * mutations.negatives
34
41
  # A stigma that normal clothing hides costs a point.
35
42
  mutations.stigmaConcealed: { type: boolean, input: true, default: false }
36
43
  mutations.points.spent:
@@ -50,17 +57,31 @@ fields:
50
57
  # The stigma, from the suggestion tables: a body part (a second one when the first roll says
51
58
  # so), the nature of the change, and a detail.
52
59
  stigma.part:
53
- { type: enum, input: true, default: none, values: [none, head, arms, legs, torso, senses] }
60
+ {
61
+ type: enum,
62
+ input: true,
63
+ open: true,
64
+ default: none,
65
+ values: [none, head, arms, legs, torso, senses],
66
+ }
54
67
  stigma.part2:
55
- { type: enum, input: true, default: none, values: [none, head, arms, legs, torso, senses] }
68
+ {
69
+ type: enum,
70
+ input: true,
71
+ open: true,
72
+ default: none,
73
+ values: [none, head, arms, legs, torso, senses],
74
+ }
56
75
  stigma.nature:
57
76
  type: enum
58
77
  input: true
78
+ open: true
59
79
  default: none
60
80
  values: [none, appearance, animal, size, plant, amorphous, mineral]
61
81
  stigma.detail:
62
82
  type: enum
63
83
  input: true
84
+ open: true
64
85
  default: none
65
86
  values:
66
87
  [
@@ -137,6 +158,7 @@ actions:
137
158
  # The random start rolls a d4 for 0-3 negative mutations and spends every point on single
138
159
  # random rolls; the guided one recommends no negative mutation and the same rolls.
139
160
  - id: mutate
161
+ available: character.creating
140
162
  tags: [creation]
141
163
  params:
142
164
  random: { type: boolean, default: false }
@@ -275,6 +297,7 @@ actions:
275
297
  # The stigma suggestions: a d6 body part (on a 6, two parts), a d6 nature, and a d12 detail
276
298
  # from the column that matches the nature (texture for size, amorphous and mineral changes).
277
299
  - id: roll-stigma
300
+ available: character.creating
278
301
  tags: [creation]
279
302
  results: [rolled]
280
303
  steps:
@@ -333,6 +356,7 @@ actions:
333
356
  # (or a second lightly touched result) is rolled again, twice; after that a uniform draw among
334
357
  # the ones left stands in for rolling until something new comes up.
335
358
  - id: roll-negative-mutation
359
+ available: character.creating
336
360
  tags: [creation]
337
361
  params:
338
362
  random: { type: boolean, default: false }
@@ -490,7 +514,7 @@ actions:
490
514
  content:
491
515
  type: mutation
492
516
  where: >-
493
- item.category == 'sense' && !('vision' in item.tags)
517
+ item.category == @mutation-categories.sense && !('vision' in item.tags)
494
518
  && countCopies(copy.item == item.id) == 0
495
519
  random: params.random
496
520
  recommend: options[0]
@@ -504,12 +528,14 @@ actions:
504
528
  # Spending mutation points: one to roll a positive mutation, three to roll two in a category,
505
529
  # three to choose one.
506
530
  - id: buy-mutation
531
+ available: character.creating
507
532
  tags: [creation]
508
533
  params:
509
534
  random: { type: boolean, default: false }
510
535
  results: [bought]
511
536
  steps:
512
537
  - id: how
538
+ tags: [creation.mutation]
513
539
  kind: choice
514
540
  sheet: proposed
515
541
  options:
@@ -525,15 +551,9 @@ actions:
525
551
  - id: category
526
552
  kind: choice
527
553
  when: "steps.how.picked == 'category'"
528
- options:
529
- - { id: structure }
530
- - { id: sense }
531
- - { id: hybrid }
532
- - { id: cognition }
533
- - { id: pseudo-psychic }
534
- - { id: exotic }
554
+ options: { content: { type: mutationCategory } }
535
555
  random: params.random
536
- recommend: options[0]
556
+ recommend: 'filterList([@mutation-categories.structure, options[0]], x in options)[0]'
537
557
  - id: pair1
538
558
  kind: action
539
559
  when: "steps.how.picked == 'category'"
@@ -574,10 +594,11 @@ actions:
574
594
  # it. A result the character can't take (held already, or barred by a negative mutation) is
575
595
  # rolled again; a uniform draw among those it can take stands in for rolling until one fits.
576
596
  - id: roll-mutation
597
+ available: character.creating
577
598
  tags: [creation]
578
599
  params:
579
600
  origin: { type: enum, values: [random, category, chosen, free, unstable], default: random }
580
- category: { type: string, optional: true }
601
+ category: { type: id, of: mutationCategory, optional: true }
581
602
  random: { type: boolean, default: false }
582
603
  results: [rolled]
583
604
  steps:
@@ -595,8 +616,10 @@ actions:
595
616
  flags:
596
617
  category: >-
597
618
  has(params.category) ? params.category
598
- : ['structure', 'sense', 'hybrid', 'cognition', 'pseudo-psychic',
599
- 'exotic'][steps.categoryRoll.total - 1]
619
+ : [@mutation-categories.structure, @mutation-categories.sense,
620
+ @mutation-categories.hybrid, @mutation-categories.cognition,
621
+ @mutation-categories.pseudo-psychic,
622
+ @mutation-categories.exotic][steps.categoryRoll.total - 1]
600
623
  - { id: roll, kind: roll, tags: [creation], parts: [{ dice: 1d10 }] }
601
624
  - id: exact
602
625
  kind: choice
@@ -645,6 +668,7 @@ actions:
645
668
  # Levels past a skill's cap (a skill a negative mutation forbids, or Slow Development's lower
646
669
  # maximum) go to other skills, a pick per level: the skill drops to its cap.
647
670
  - id: reassign-barred-skills
671
+ available: character.creating
648
672
  tags: [creation]
649
673
  params:
650
674
  random: { type: boolean, default: false }
@@ -669,7 +693,7 @@ actions:
669
693
  changes:
670
694
  - type: set
671
695
  forEach: flags.barred
672
- field: { formula: "'skills.' + x + '.level'" }
696
+ field: { formula: "fieldId('skills.{skill}.level', x)" }
673
697
  value: "max(-1, fieldOf('skills.{skill}.cap', x))"
674
698
  - {
675
699
  id: relearn1,
package/rules/skills.yaml CHANGED
@@ -59,11 +59,12 @@ actions:
59
59
  # skill when none of the offered ones can take it). `skills` lists the skill keys offered (all
60
60
  # when empty); `prefer` the ones to recommend, in order.
61
61
  - id: gain-skill
62
+ listed: false
62
63
  params:
63
64
  skills: { type: set, of: string, default: [] }
64
65
  prefer: { type: set, of: string, default: [] }
65
66
  random: { type: boolean, default: false }
66
- results: [gained]
67
+ results: [gained, none]
67
68
  steps:
68
69
  - id: open
69
70
  kind: branch
@@ -91,16 +92,19 @@ actions:
91
92
  flags:
92
93
  suggest: >-
93
94
  size(filterList(params.prefer, x in flags.offered)) > 0
94
- ? filterList(params.prefer, x in flags.offered)[0] : flags.offered[0]
95
+ ? filterList(params.prefer, x in flags.offered)[0]
96
+ : size(flags.offered) > 0 ? flags.offered[0] : ''
97
+ # Every skill at its cap: nothing to gain.
95
98
  - id: skill
96
99
  kind: choice
100
+ when: size(flags.offered) > 0
97
101
  options: { values: { formula: flags.offered } }
98
102
  random: params.random
99
103
  recommend: flags.suggest
100
104
  changes:
101
105
  - type: adjust
102
- field: { formula: "'skills.' + picked.value + '.level'" }
106
+ field: { formula: "fieldId('skills.{skill}.level', picked.value)" }
103
107
  by: 1
104
108
  - id: result
105
109
  kind: outcome
106
- result: "'gained'"
110
+ result: "has(steps.skill) ? 'gained' : 'none'"
@@ -4,121 +4,43 @@ actions:
4
4
  # wasteland warriors get a kit, then one pick or three rolls on a d20 table: the near-future
5
5
  # one or the wasteland one. The book's default setting is a wasteland, so that is recommended.
6
6
  - id: starting-equipment
7
+ available: character.creating
7
8
  tags: [creation]
8
9
  params:
9
10
  random: { type: boolean, default: false }
10
11
  results: [equipped]
11
12
  steps:
12
13
  - id: kit
14
+ tags: [creation.kit]
13
15
  kind: choice
14
16
  options: [{ id: innocents }, { id: prepared }, { id: near-future }, { id: wasteland }]
15
17
  recommend: "'wasteland'"
18
+ # Four different picks from the list (`prepared` content, an add-on pack's too).
16
19
  - id: prepared
17
20
  kind: choice
18
21
  when: "steps.kit.picked == 'prepared'"
19
22
  repeat: { max: 4, min: 4 }
20
23
  random: params.random
21
24
  recommend: >-
22
- size(filterList(['knife', 'first-aid-kit', 'preserved-food', 'backpack'], x in options)) > 0
23
- ? filterList(['knife', 'first-aid-kit', 'preserved-food', 'backpack'], x in options)[0]
25
+ size(filterList([@prepared.knife, @prepared.first-aid-kit, @prepared.preserved-food,
26
+ @prepared.backpack], x in options)) > 0
27
+ ? filterList([@prepared.knife, @prepared.first-aid-kit, @prepared.preserved-food,
28
+ @prepared.backpack], x in options)[0]
24
29
  : options[0]
25
- options:
26
- - id: light-pistol
27
- when: picks == 0
28
- changes:
29
- - { type: addItem, entry: { item: "'weapons.light-pistol'", equipped: true } }
30
- - { type: addItem, entry: { item: "'gear.magazine'", quantity: 2 } }
31
- - id: heavy-pistol
32
- when: picks == 0
33
- changes:
34
- - { type: addItem, entry: { item: "'weapons.heavy-pistol'", equipped: true } }
35
- - { type: addItem, entry: { item: "'gear.magazine'", quantity: 2 } }
36
- - id: rifle
37
- when: picks == 0
38
- changes:
39
- - { type: addItem, entry: { item: "'weapons.rifle'", equipped: true } }
40
- - { type: addItem, entry: { item: "'gear.magazine'", quantity: 2 } }
41
- - id: shotgun
42
- when: picks == 0
43
- changes:
44
- - { type: addItem, entry: { item: "'weapons.shotgun'", equipped: true } }
45
- - { type: addItem, entry: { item: "'gear.ammo'", quantity: 2 } }
46
- # A criminal's non-military firearm: the semi-auto shotgun stands for any of them.
47
- - id: criminal-firearm
48
- when: picks == 0
49
- changes:
50
- - { type: addItem, entry: { item: "'weapons.semi-auto-shotgun'", equipped: true } }
51
- - { type: addItem, entry: { item: "'gear.magazine'", quantity: 2 } }
52
- - id: knife
53
- when: picks == 0
54
- changes: [{ type: addItem, entry: { item: "'weapons.knife'", equipped: true } }]
55
- - id: smartphone
56
- when: picks == 0
57
- changes: [{ type: addItem, entry: { item: "'gear.smartphone'" } }]
58
- - id: preserved-food
59
- when: picks == 0
60
- changes: [{ type: addItem, entry: { item: "'gear.preserved-food'" } }]
61
- - id: camping-kit
62
- when: picks == 0
63
- changes: [{ type: addItem, entry: { item: "'gear.camping-kit'" } }]
64
- - id: first-aid-kit
65
- when: picks == 0
66
- changes: [{ type: addItem, entry: { item: "'gear.first-aid-kit'" } }]
67
- - id: tent
68
- when: picks == 0
69
- changes: [{ type: addItem, entry: { item: "'gear.tent'" } }]
70
- - id: toolkit
71
- when: picks == 0
72
- changes: [{ type: addItem, entry: { item: "'gear.toolkit'" } }]
73
- - id: flashlight
74
- when: picks == 0
75
- changes: [{ type: addItem, entry: { item: "'gear.flashlight'" } }]
76
- - id: cold-weather-clothes
77
- when: picks == 0
78
- changes:
79
- [{ type: addItem, entry: { item: "'gear.clothes-cold-weather'", equipped: true } }]
80
- - id: sleeping-bag
81
- when: picks == 0
82
- changes: [{ type: addItem, entry: { item: "'gear.sleeping-bag'" } }]
83
- - id: cash
84
- when: picks == 0
85
- changes: [{ type: addItem, entry: { item: "'gear.cash'" } }]
86
- - id: laptop
87
- when: picks == 0
88
- changes: [{ type: addItem, entry: { item: "'gear.laptop'" } }]
89
- - id: walkie-talkies
90
- when: picks == 0
91
- changes: [{ type: addItem, entry: { item: "'gear.walkie-talkie'", quantity: 2 } }]
92
- - id: solar-charger
93
- when: picks == 0
94
- changes: [{ type: addItem, entry: { item: "'gear.solar-charger'" } }]
95
- - id: radio
96
- when: picks == 0
97
- changes: [{ type: addItem, entry: { item: "'gear.radio'" } }]
98
- - id: lockpick-kit
99
- when: picks == 0
100
- changes: [{ type: addItem, entry: { item: "'gear.lockpick-kit'" } }]
101
- - id: lighters
102
- when: picks == 0
103
- changes: [{ type: addItem, entry: { item: "'gear.lighters'" } }]
104
- - id: purification-tablets
105
- when: picks == 0
106
- changes: [{ type: addItem, entry: { item: "'gear.purification-tablets'" } }]
107
- - id: profession-item
108
- when: picks == 0
109
- changes: [{ type: addItem, entry: { item: "'gear.profession-item'" } }]
110
- - id: backpack
111
- when: picks == 0
112
- changes: [{ type: addItem, entry: { item: "'gear.backpack'", equipped: true } }]
113
- - id: binoculars
114
- when: picks == 0
115
- changes: [{ type: addItem, entry: { item: "'gear.binoculars'" } }]
116
- - id: water-purifier
117
- when: picks == 0
118
- changes: [{ type: addItem, entry: { item: "'gear.water-purifier'" } }]
30
+ options: { content: { type: prepared, where: picks == 0 } }
31
+ changes:
32
+ - type: addItem
33
+ entry:
34
+ item: picked.item.gear
35
+ quantity: picked.item.quantity
36
+ equipped: picked.item.equipped
37
+ - type: addItem
38
+ when: has(picked.item.extra)
39
+ entry: { item: picked.item.extra, quantity: picked.item.extraQuantity }
119
40
 
120
- # The warrior's kit: a primitive melee weapon or a primitive bow with 20 arrows, a knife,
121
- # backpack, sleeping bag, tinderbox, three torches, clothes and 1d4 days of food.
41
+ # The warrior's kit: a primitive melee weapon or a primitive bow with 20 arrows (a primitive
42
+ # missile weapon with its `kitAmmo`), a knife, backpack, sleeping bag, tinderbox, three
43
+ # torches, clothes and 1d4 days of food.
122
44
  - id: weapon
123
45
  kind: choice
124
46
  when: "steps.kit.picked == 'near-future' || steps.kit.picked == 'wasteland'"
@@ -127,14 +49,14 @@ actions:
127
49
  type: weapon
128
50
  where: >-
129
51
  has(item.techLevel) && item.techLevel <= 1 && !('grenade' in item.tags)
130
- && ('stab' in item.skills || item.id == @weapons.primitive-bow)
52
+ && ('stab' in item.skills || has(item.kitAmmo))
131
53
  random: params.random
132
54
  recommend: options[0]
133
55
  changes:
134
56
  - { type: addItem, entry: { item: picked.id, equipped: true } }
135
57
  - type: addItem
136
- when: picked.id == @weapons.primitive-bow
137
- entry: { item: "'gear.arrows'" }
58
+ when: has(picked.item.kitAmmo)
59
+ entry: { item: picked.item.kitAmmo }
138
60
  - id: food
139
61
  kind: roll
140
62
  tags: [creation]
@@ -207,6 +129,7 @@ actions:
207
129
  # One entry of a warrior's d20 table. The wasteland table's three high-tech items stand for
208
130
  # rolls on the GM's loot tables, which this pack doesn't model.
209
131
  - id: kit-entry
132
+ available: character.creating
210
133
  tags: [creation]
211
134
  params:
212
135
  table: { type: string }