optolith-database-schema 0.15.2 → 0.15.4

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.
@@ -0,0 +1,924 @@
1
+ import { isNotNullish } from "../helpers/nullable.js";
2
+ import { mapObject } from "../helpers/object.js";
3
+ import { assertExhaustive } from "../helpers/typeSafety.js";
4
+ const PRINCIPLES_ID = 31;
5
+ const PROPERTY_KNOWLEDGE_ID = 3;
6
+ const ASPECT_KNOWLEDGE_ID = 1;
7
+ const matchesSpecificSkillishIdList = (id, config, equalsId) => {
8
+ switch (config.operation) {
9
+ case "Intersection":
10
+ return config.list.some(ref => equalsId(ref.id, id));
11
+ case "Difference":
12
+ return !config.list.some(ref => equalsId(ref.id, id));
13
+ default:
14
+ return assertExhaustive(config.operation);
15
+ }
16
+ };
17
+ const getSkillishPrerequisites = (ps, id) => {
18
+ if (ps === undefined) {
19
+ return undefined;
20
+ }
21
+ return ps.map(p => {
22
+ switch (p.tag) {
23
+ case "Self":
24
+ return {
25
+ level: 1,
26
+ prerequisite: {
27
+ tag: "Single",
28
+ single: {
29
+ tag: "Rated",
30
+ rated: {
31
+ id,
32
+ value: p.self.value,
33
+ },
34
+ },
35
+ },
36
+ };
37
+ case "SelectOption":
38
+ return {
39
+ level: 1,
40
+ prerequisite: {
41
+ tag: "Single",
42
+ single: {
43
+ tag: "Activatable",
44
+ activatable: {
45
+ id: p.select_option.id,
46
+ active: p.select_option.active,
47
+ level: p.select_option.level,
48
+ options: [id],
49
+ },
50
+ },
51
+ },
52
+ };
53
+ default:
54
+ return assertExhaustive(p);
55
+ }
56
+ });
57
+ };
58
+ const equalsSkillishIdGroup = (a, b) => {
59
+ switch (a.tag) {
60
+ case "Skill":
61
+ return b.tag === "Skill" && a.skill === b.skill;
62
+ case "Spell":
63
+ return b.tag === "Spell" && a.spell === b.spell;
64
+ case "Ritual":
65
+ return b.tag === "Ritual" && a.ritual === b.ritual;
66
+ case "LiturgicalChant":
67
+ return b.tag === "LiturgicalChant" && a.liturgical_chant === b.liturgical_chant;
68
+ case "Ceremony":
69
+ return b.tag === "Ceremony" && a.ceremony === b.ceremony;
70
+ case "CloseCombatTechnique":
71
+ return (b.tag === "CloseCombatTechnique" && a.close_combat_technique === b.close_combat_technique);
72
+ case "RangedCombatTechnique":
73
+ return (b.tag === "RangedCombatTechnique" && a.ranged_combat_technique === b.ranged_combat_technique);
74
+ default:
75
+ return assertExhaustive(a);
76
+ }
77
+ };
78
+ const getApValueForSkillish = (config, id, ic) => {
79
+ if (config === undefined) {
80
+ return undefined;
81
+ }
82
+ switch (config.tag) {
83
+ case "DerivedFromImprovementCost":
84
+ return ((() => {
85
+ switch (ic) {
86
+ case "A":
87
+ return 1;
88
+ case "B":
89
+ return 2;
90
+ case "C":
91
+ return 3;
92
+ case "D":
93
+ return 4;
94
+ default:
95
+ assertExhaustive(ic);
96
+ }
97
+ })() *
98
+ (config.derived_from_improvement_cost.multiplier ?? 1) +
99
+ (config.derived_from_improvement_cost.offset ?? 0));
100
+ case "Fixed":
101
+ return (config.fixed.map.find(mapping => equalsSkillishIdGroup(mapping.id, id))?.ap_value ??
102
+ config.fixed.default);
103
+ default:
104
+ return assertExhaustive(config);
105
+ }
106
+ };
107
+ const getDerivedSelectOptions = (selectOptionCategory, entryId, database) => {
108
+ switch (selectOptionCategory.tag) {
109
+ case "Blessings":
110
+ return database.blessings.map(([_, blessing]) => ({
111
+ id: { tag: "Blessing", blessing: blessing.id },
112
+ src: blessing.src,
113
+ translations: mapObject(blessing.translations, t10n => ({ name: t10n.name })),
114
+ }));
115
+ case "Cantrips":
116
+ return database.cantrips.map(([_, cantrip]) => ({
117
+ id: { tag: "Cantrip", cantrip: cantrip.id },
118
+ src: cantrip.src,
119
+ translations: mapObject(cantrip.translations, t10n => ({ name: t10n.name })),
120
+ }));
121
+ case "TradeSecrets":
122
+ return database.tradeSecrets.map(([_, tradeSecret]) => ({
123
+ id: { tag: "TradeSecret", trade_secret: tradeSecret.id },
124
+ prerequisites: tradeSecret.prerequisites,
125
+ ap_value: tradeSecret.ap_value,
126
+ src: tradeSecret.src,
127
+ translations: mapObject(tradeSecret.translations, t10n => ({
128
+ name: t10n.name,
129
+ errata: t10n.errata,
130
+ })),
131
+ }));
132
+ case "Scripts":
133
+ return database.scripts.map(([_, script]) => ({
134
+ id: { tag: "Script", script: script.id },
135
+ ap_value: script.ap_value,
136
+ src: script.src,
137
+ translations: mapObject(script.translations, t10n => ({
138
+ name: t10n.name,
139
+ errata: t10n.errata,
140
+ })),
141
+ }));
142
+ case "AnimalShapes": {
143
+ const pathsWithOrderedIds = database.animalShapePaths.reduce((acc, [id, _path]) => ({
144
+ ...acc,
145
+ [id]: database.animalShapes
146
+ .toSorted(([_1, a], [_2, b]) => a.size.id - b.size.id)
147
+ .map(([id]) => id),
148
+ }), {});
149
+ return database.animalShapes.map(([_, animalShape]) => {
150
+ const path = database.animalShapePaths.find(([id]) => id === animalShape.path.id)?.[1];
151
+ const size = database.animalShapeSizes.find(([id]) => id === animalShape.size.id)?.[1];
152
+ const pathIndex = path !== undefined ? pathsWithOrderedIds[path.id]?.indexOf(animalShape.id) ?? -1 : -1;
153
+ return {
154
+ id: { tag: "AnimalShape", animal_shape: animalShape.id },
155
+ prerequisites: pathIndex >= 0
156
+ ? pathIndex === 0
157
+ ? database.animalShapePaths
158
+ .filter(([id]) => id !== animalShape.path.id && pathsWithOrderedIds[id]?.[0] !== undefined)
159
+ .map(([id]) => ({
160
+ level: 1,
161
+ prerequisite: {
162
+ tag: "Single",
163
+ single: {
164
+ tag: "Activatable",
165
+ activatable: {
166
+ id: entryId,
167
+ active: false,
168
+ options: [
169
+ { tag: "AnimalShape", animal_shape: pathsWithOrderedIds[id][0] },
170
+ ],
171
+ },
172
+ },
173
+ },
174
+ }))
175
+ : [
176
+ {
177
+ level: 1,
178
+ prerequisite: {
179
+ tag: "Single",
180
+ single: {
181
+ tag: "Activatable",
182
+ activatable: {
183
+ id: entryId,
184
+ active: true,
185
+ options: [
186
+ {
187
+ tag: "AnimalShape",
188
+ animal_shape: pathsWithOrderedIds[path.id][pathIndex - 1],
189
+ },
190
+ ],
191
+ },
192
+ },
193
+ },
194
+ },
195
+ ]
196
+ : undefined,
197
+ volume: size?.volume,
198
+ ap_value: size?.ap_value,
199
+ translations: mapObject(animalShape.translations, (t10n, lang) => ({
200
+ name: path?.translations[lang] !== undefined
201
+ ? `${t10n.name} (${path.translations[lang].name})`
202
+ : t10n.name,
203
+ })),
204
+ };
205
+ });
206
+ }
207
+ case "ArcaneBardTraditions":
208
+ return database.arcaneBardTraditions.map(([_, arcaneBardTradition]) => ({
209
+ id: { tag: "ArcaneBardTradition", arcane_bard_tradition: arcaneBardTradition.id },
210
+ prerequisites: arcaneBardTradition.prerequisites.map(p => ({
211
+ level: 1,
212
+ prerequisite: p,
213
+ })),
214
+ translations: mapObject(arcaneBardTradition.translations, t10n => ({
215
+ name: t10n.name,
216
+ })),
217
+ }));
218
+ case "ArcaneDancerTraditions":
219
+ return database.arcaneDancerTraditions.map(([_, arcaneDancerTradition]) => ({
220
+ id: { tag: "ArcaneDancerTradition", arcane_dancer_tradition: arcaneDancerTradition.id },
221
+ prerequisites: arcaneDancerTradition.prerequisites.map(p => ({
222
+ level: 1,
223
+ prerequisite: p,
224
+ })),
225
+ translations: mapObject(arcaneDancerTradition.translations, t10n => ({
226
+ name: t10n.name,
227
+ })),
228
+ }));
229
+ case "SexPractices":
230
+ return database.sexPractices.map(([_, sexPractice]) => ({
231
+ id: { tag: "SexPractice", sex_practice: sexPractice.id },
232
+ src: sexPractice.src,
233
+ translations: mapObject(sexPractice.translations, t10n => ({
234
+ name: t10n.name,
235
+ })),
236
+ }));
237
+ case "Races":
238
+ return database.races.map(([_, race]) => ({
239
+ id: { tag: "Race", race: race.id },
240
+ src: race.src,
241
+ translations: mapObject(race.translations, t10n => ({
242
+ name: t10n.name,
243
+ })),
244
+ }));
245
+ case "Cultures":
246
+ return database.cultures.map(([_, culture]) => ({
247
+ id: { tag: "Culture", culture: culture.id },
248
+ src: culture.src,
249
+ translations: mapObject(culture.translations, t10n => ({
250
+ name: t10n.name,
251
+ })),
252
+ }));
253
+ case "RacesAndCultures":
254
+ return [
255
+ ...database.races.map(([_, race]) => ({
256
+ id: { tag: "Race", race: race.id },
257
+ src: race.src,
258
+ translations: mapObject(race.translations, t10n => ({
259
+ name: t10n.name,
260
+ })),
261
+ })),
262
+ ...database.cultures.map(([_, culture]) => ({
263
+ id: { tag: "Culture", culture: culture.id },
264
+ src: culture.src,
265
+ translations: mapObject(culture.translations, t10n => ({
266
+ name: t10n.name,
267
+ })),
268
+ })),
269
+ ];
270
+ case "BlessedTraditions": {
271
+ const getPrerequisites = (blessedTradition) => {
272
+ if (selectOptionCategory.blessed_traditions.require_principles &&
273
+ blessedTradition.associated_principles_id !== undefined) {
274
+ return [
275
+ {
276
+ level: 1,
277
+ prerequisite: {
278
+ tag: "Single",
279
+ single: {
280
+ tag: "Activatable",
281
+ activatable: {
282
+ id: { tag: "Disadvantage", disadvantage: PRINCIPLES_ID },
283
+ active: true,
284
+ options: [
285
+ { tag: "General", general: blessedTradition.associated_principles_id },
286
+ ],
287
+ },
288
+ },
289
+ },
290
+ },
291
+ ];
292
+ }
293
+ };
294
+ return database.blessedTraditions.map(([_, blessedTradition]) => ({
295
+ id: { tag: "BlessedTradition", blessed_tradition: blessedTradition.id },
296
+ prerequisites: getPrerequisites(blessedTradition),
297
+ src: blessedTradition.src,
298
+ translations: mapObject(blessedTradition.translations, t10n => ({
299
+ name: t10n.name,
300
+ })),
301
+ }));
302
+ }
303
+ case "Elements": {
304
+ const mapToResolvedSelectOption = ([_, element]) => ({
305
+ id: { tag: "Element", element: element.id },
306
+ translations: mapObject(element.translations, t10n => ({
307
+ name: t10n.name,
308
+ })),
309
+ });
310
+ if (selectOptionCategory.elements.specific) {
311
+ return database.elements
312
+ .filter(([id]) => selectOptionCategory.elements.specific.some(ref => ref.id.element === id))
313
+ .map(mapToResolvedSelectOption);
314
+ }
315
+ return database.elements.map(mapToResolvedSelectOption);
316
+ }
317
+ case "Properties": {
318
+ const getPrerequisites = (property) => {
319
+ if (selectOptionCategory.properties.require_knowledge !== undefined ||
320
+ selectOptionCategory.properties.require_minimum_spellworks_on !== undefined) {
321
+ const knowledgePrerequisite = selectOptionCategory.properties.require_knowledge !== undefined
322
+ ? {
323
+ level: 1,
324
+ prerequisite: {
325
+ tag: "Single",
326
+ single: {
327
+ tag: "Activatable",
328
+ activatable: {
329
+ id: {
330
+ tag: "MagicalSpecialAbility",
331
+ magical_special_ability: PROPERTY_KNOWLEDGE_ID,
332
+ },
333
+ active: true,
334
+ options: [{ tag: "Property", property: property.id }],
335
+ },
336
+ },
337
+ },
338
+ }
339
+ : undefined;
340
+ const minimumSpellworksPrerequisite = selectOptionCategory.properties.require_minimum_spellworks_on !== undefined
341
+ ? {
342
+ level: 1,
343
+ prerequisite: {
344
+ tag: "Single",
345
+ single: {
346
+ tag: "RatedMinimumNumber",
347
+ rated_minimum_number: {
348
+ number: selectOptionCategory.properties.require_minimum_spellworks_on.number,
349
+ value: selectOptionCategory.properties.require_minimum_spellworks_on.rating,
350
+ targets: {
351
+ tag: "Spellworks",
352
+ spellworks: {
353
+ id: { tag: "Property", property: property.id },
354
+ },
355
+ },
356
+ },
357
+ },
358
+ },
359
+ }
360
+ : undefined;
361
+ return [knowledgePrerequisite, minimumSpellworksPrerequisite].filter(isNotNullish);
362
+ }
363
+ };
364
+ return database.properties.map(([_, property]) => ({
365
+ id: { tag: "Property", property: property.id },
366
+ prerequisites: getPrerequisites(property),
367
+ translations: mapObject(property.translations, t10n => ({
368
+ name: t10n.name,
369
+ })),
370
+ }));
371
+ }
372
+ case "Aspects": {
373
+ const getPrerequisites = (aspect) => {
374
+ if (selectOptionCategory.aspects.require_knowledge !== undefined ||
375
+ selectOptionCategory.aspects.require_minimum_liturgies_on !== undefined) {
376
+ const knowledgePrerequisite = selectOptionCategory.aspects.require_knowledge !== undefined
377
+ ? {
378
+ level: 1,
379
+ prerequisite: {
380
+ tag: "Single",
381
+ single: {
382
+ tag: "Activatable",
383
+ activatable: {
384
+ id: {
385
+ tag: "KarmaSpecialAbility",
386
+ karma_special_ability: ASPECT_KNOWLEDGE_ID,
387
+ },
388
+ active: true,
389
+ options: [{ tag: "Property", property: aspect.id }],
390
+ },
391
+ },
392
+ },
393
+ }
394
+ : undefined;
395
+ const minimumSpellworksPrerequisite = selectOptionCategory.aspects.require_minimum_liturgies_on !== undefined
396
+ ? {
397
+ level: 1,
398
+ prerequisite: {
399
+ tag: "Single",
400
+ single: {
401
+ tag: "RatedMinimumNumber",
402
+ rated_minimum_number: {
403
+ number: selectOptionCategory.aspects.require_minimum_liturgies_on.number,
404
+ value: selectOptionCategory.aspects.require_minimum_liturgies_on.rating,
405
+ targets: {
406
+ tag: "Liturgies",
407
+ liturgies: {
408
+ id: { tag: "Aspect", aspect: aspect.id },
409
+ },
410
+ },
411
+ },
412
+ },
413
+ },
414
+ }
415
+ : undefined;
416
+ return [knowledgePrerequisite, minimumSpellworksPrerequisite].filter(isNotNullish);
417
+ }
418
+ };
419
+ if (selectOptionCategory.aspects.use_master_of_suffix_as_name === true) {
420
+ return database.aspects
421
+ .map(([_, aspect]) => ({
422
+ id: { tag: "Aspect", aspect: aspect.id },
423
+ prerequisites: getPrerequisites(aspect),
424
+ translations: mapObject(aspect.translations, t10n => t10n.master_of_aspect_suffix === undefined
425
+ ? undefined
426
+ : {
427
+ name: t10n.master_of_aspect_suffix,
428
+ }),
429
+ }))
430
+ .filter(value => Object.keys(value.translations).length > 0);
431
+ }
432
+ return database.aspects.map(([_, aspect]) => ({
433
+ id: { tag: "Aspect", aspect: aspect.id },
434
+ prerequisites: getPrerequisites(aspect),
435
+ translations: mapObject(aspect.translations, t10n => ({
436
+ name: t10n.name,
437
+ })),
438
+ }));
439
+ }
440
+ case "Diseases":
441
+ return database.diseases.map(([_, disease]) => ({
442
+ id: { tag: "Disease", disease: disease.id },
443
+ ap_value: selectOptionCategory.diseases.use_half_level_as_ap_value === true
444
+ ? Math.round(disease.level / 3)
445
+ : disease.level,
446
+ src: disease.src,
447
+ translations: mapObject(disease.translations, t10n => ({
448
+ name: t10n.name,
449
+ })),
450
+ }));
451
+ case "Poisons": {
452
+ const getLevel = (poison) => {
453
+ switch (poison.source_type.tag) {
454
+ case "AnimalVenom":
455
+ return poison.source_type.animal_venom.level;
456
+ case "AlchemicalPoison":
457
+ return 6;
458
+ case "MineralPoison":
459
+ return poison.source_type.mineral_poison.level;
460
+ case "PlantPoison":
461
+ return poison.source_type.plant_poison.level;
462
+ case "DemonicPoison":
463
+ switch (poison.source_type.demonic_poison.level.tag) {
464
+ case "Constant":
465
+ return poison.source_type.demonic_poison.level.constant.value;
466
+ case "QualityLevel":
467
+ return 6;
468
+ default:
469
+ return assertExhaustive(poison.source_type.demonic_poison.level);
470
+ }
471
+ default:
472
+ return assertExhaustive(poison.source_type);
473
+ }
474
+ };
475
+ return database.poisons.map(([_, poison]) => ({
476
+ id: { tag: "Poison", poison: poison.id },
477
+ ap_value: selectOptionCategory.poisons.use_half_level_as_ap_value === true
478
+ ? Math.round(getLevel(poison) / 3)
479
+ : getLevel(poison),
480
+ src: poison.src,
481
+ translations: mapObject(poison.translations, t10n => ({
482
+ name: t10n.name,
483
+ })),
484
+ }));
485
+ }
486
+ case "Languages": {
487
+ const getPrerequisites = (language) => {
488
+ if (selectOptionCategory.languages.prerequisites !== undefined) {
489
+ return selectOptionCategory.languages.prerequisites.map(config => ({
490
+ level: 1,
491
+ prerequisite: {
492
+ tag: "Single",
493
+ single: {
494
+ tag: "Activatable",
495
+ activatable: {
496
+ id: config.select_option.id,
497
+ active: config.select_option.active,
498
+ level: config.select_option.level,
499
+ options: [{ tag: "Language", language: language.id }],
500
+ },
501
+ },
502
+ },
503
+ }));
504
+ }
505
+ };
506
+ return database.languages.map(([_, language]) => ({
507
+ id: { tag: "Language", language: language.id },
508
+ prerequisites: getPrerequisites(language),
509
+ src: language.src,
510
+ translations: mapObject(language.translations, t10n => ({
511
+ name: t10n.name,
512
+ })),
513
+ }));
514
+ }
515
+ case "Skills": {
516
+ const apValueGen = selectOptionCategory.skills.ap_value;
517
+ return selectOptionCategory.skills.categories.flatMap(category => {
518
+ switch (category.tag) {
519
+ case "Skills":
520
+ return database.skills
521
+ .filter(([_, skill]) => {
522
+ const matchesGroupRequirement = category.skills.groups === undefined ||
523
+ category.skills.groups.some(ref => ref.id.skill_group === skill.group.id.skill_group);
524
+ const matchesIdRequirement = category.skills.specific === undefined ||
525
+ matchesSpecificSkillishIdList({ tag: "Skill", skill: skill.id }, category.skills.specific, equalsSkillishIdGroup);
526
+ return matchesGroupRequirement && matchesIdRequirement;
527
+ })
528
+ .map(([_, skill]) => {
529
+ const id = { tag: "Skill", skill: skill.id };
530
+ return {
531
+ id,
532
+ skill_uses: category.skills.skill_uses?.map(use => ({
533
+ id: use.id,
534
+ skill: { tag: "Single", single: { id } },
535
+ translations: use.translations,
536
+ })),
537
+ skill_applications: category.skills.skill_applications?.map(use => ({
538
+ id: use.id,
539
+ skill: { tag: "Single", single: { id } },
540
+ translations: use.translations,
541
+ })),
542
+ prerequisites: getSkillishPrerequisites(category.skills.prerequisites, id),
543
+ ap_value: getApValueForSkillish(category.skills.ap_value ?? apValueGen, id, skill.improvement_cost),
544
+ src: skill.src,
545
+ translations: mapObject(skill.translations, t10n => ({
546
+ name: t10n.name,
547
+ })),
548
+ };
549
+ });
550
+ case "Spells":
551
+ return database.spells
552
+ .filter(([_, spell]) => category.spells.specific === undefined ||
553
+ matchesSpecificSkillishIdList({ tag: "Spell", spell: spell.id }, category.spells.specific, equalsSkillishIdGroup))
554
+ .map(([_, spell]) => {
555
+ const id = { tag: "Spell", spell: spell.id };
556
+ return {
557
+ id,
558
+ prerequisites: getSkillishPrerequisites(category.spells.prerequisites, id),
559
+ ap_value: getApValueForSkillish(apValueGen, id, spell.improvement_cost),
560
+ src: spell.src,
561
+ translations: mapObject(spell.translations, t10n => ({
562
+ name: t10n.name,
563
+ })),
564
+ };
565
+ });
566
+ case "Rituals":
567
+ return database.rituals
568
+ .filter(([_, ritual]) => category.rituals.specific === undefined ||
569
+ matchesSpecificSkillishIdList({ tag: "Ritual", ritual: ritual.id }, category.rituals.specific, equalsSkillishIdGroup))
570
+ .map(([_, ritual]) => {
571
+ const id = { tag: "Ritual", ritual: ritual.id };
572
+ return {
573
+ id,
574
+ prerequisites: getSkillishPrerequisites(category.rituals.prerequisites, id),
575
+ ap_value: getApValueForSkillish(apValueGen, id, ritual.improvement_cost),
576
+ src: ritual.src,
577
+ translations: mapObject(ritual.translations, t10n => ({
578
+ name: t10n.name,
579
+ })),
580
+ };
581
+ });
582
+ case "LiturgicalChants":
583
+ return database.liturgicalChants
584
+ .filter(([_, liturgicalChant]) => category.liturgical_chants.specific === undefined ||
585
+ matchesSpecificSkillishIdList({ tag: "LiturgicalChant", liturgical_chant: liturgicalChant.id }, category.liturgical_chants.specific, equalsSkillishIdGroup))
586
+ .map(([_, liturgicalChant]) => {
587
+ const id = {
588
+ tag: "LiturgicalChant",
589
+ liturgical_chant: liturgicalChant.id,
590
+ };
591
+ return {
592
+ id,
593
+ prerequisites: getSkillishPrerequisites(category.liturgical_chants.prerequisites, id),
594
+ ap_value: getApValueForSkillish(apValueGen, id, liturgicalChant.improvement_cost),
595
+ src: liturgicalChant.src,
596
+ translations: mapObject(liturgicalChant.translations, t10n => ({
597
+ name: t10n.name,
598
+ })),
599
+ };
600
+ });
601
+ case "Ceremonies":
602
+ return database.ceremonies
603
+ .filter(([_, ceremony]) => category.ceremonies.specific === undefined ||
604
+ matchesSpecificSkillishIdList({ tag: "Ceremony", ceremony: ceremony.id }, category.ceremonies.specific, equalsSkillishIdGroup))
605
+ .map(([_, ceremony]) => {
606
+ const id = { tag: "Ceremony", ceremony: ceremony.id };
607
+ return {
608
+ id,
609
+ prerequisites: getSkillishPrerequisites(category.ceremonies.prerequisites, id),
610
+ ap_value: getApValueForSkillish(apValueGen, id, ceremony.improvement_cost),
611
+ src: ceremony.src,
612
+ translations: mapObject(ceremony.translations, t10n => ({
613
+ name: t10n.name,
614
+ })),
615
+ };
616
+ });
617
+ default:
618
+ return assertExhaustive(category);
619
+ }
620
+ });
621
+ }
622
+ case "CombatTechniques": {
623
+ const apValueGen = selectOptionCategory.combat_techniques.ap_value;
624
+ return selectOptionCategory.combat_techniques.categories.flatMap(category => {
625
+ switch (category.tag) {
626
+ case "CloseCombatTechniques":
627
+ return database.closeCombatTechniques
628
+ .filter(([_, closeCombatTechnique]) => category.close_combat_techniques.specific === undefined ||
629
+ matchesSpecificSkillishIdList({
630
+ tag: "CloseCombatTechnique",
631
+ close_combat_technique: closeCombatTechnique.id,
632
+ }, category.close_combat_techniques.specific, equalsSkillishIdGroup))
633
+ .map(([_, closeCombatTechnique]) => {
634
+ const id = {
635
+ tag: "CloseCombatTechnique",
636
+ close_combat_technique: closeCombatTechnique.id,
637
+ };
638
+ return {
639
+ id,
640
+ prerequisites: getSkillishPrerequisites(category.close_combat_techniques.prerequisites, id),
641
+ ap_value: getApValueForSkillish(apValueGen, id, closeCombatTechnique.improvement_cost),
642
+ src: closeCombatTechnique.src,
643
+ translations: mapObject(closeCombatTechnique.translations, t10n => ({
644
+ name: t10n.name,
645
+ })),
646
+ };
647
+ });
648
+ case "RangedCombatTechniques":
649
+ return database.rangedCombatTechniques
650
+ .filter(([_, rangedCombatTechnique]) => category.ranged_combat_techniques.specific === undefined ||
651
+ matchesSpecificSkillishIdList({
652
+ tag: "RangedCombatTechnique",
653
+ ranged_combat_technique: rangedCombatTechnique.id,
654
+ }, category.ranged_combat_techniques.specific, equalsSkillishIdGroup))
655
+ .map(([_, rangedCombatTechnique]) => {
656
+ const id = {
657
+ tag: "RangedCombatTechnique",
658
+ ranged_combat_technique: rangedCombatTechnique.id,
659
+ };
660
+ return {
661
+ id,
662
+ prerequisites: getSkillishPrerequisites(category.ranged_combat_techniques.prerequisites, id),
663
+ ap_value: getApValueForSkillish(apValueGen, id, rangedCombatTechnique.improvement_cost),
664
+ src: rangedCombatTechnique.src,
665
+ translations: mapObject(rangedCombatTechnique.translations, t10n => ({
666
+ name: t10n.name,
667
+ })),
668
+ };
669
+ });
670
+ default:
671
+ return assertExhaustive(category);
672
+ }
673
+ });
674
+ }
675
+ case "TargetCategories": {
676
+ const mapToResolvedSelectOption = (targetCategory, specificTargetCategory) => ({
677
+ id: { tag: "TargetCategory", target_category: targetCategory.id },
678
+ volume: specificTargetCategory?.volume,
679
+ translations: mapObject(targetCategory.translations, t10n => ({
680
+ name: t10n.name,
681
+ })),
682
+ });
683
+ if (selectOptionCategory.target_categories.list) {
684
+ return database.targetCategories
685
+ .filter(([id]) => selectOptionCategory.target_categories.list.some(ref => ref.id.target_category === id))
686
+ .map(([id, targetCategory]) => mapToResolvedSelectOption(targetCategory, selectOptionCategory.target_categories.list.find(ref => ref.id.target_category === id)));
687
+ }
688
+ return database.targetCategories.map(p => mapToResolvedSelectOption(p[1]));
689
+ }
690
+ default:
691
+ return assertExhaustive(selectOptionCategory);
692
+ }
693
+ };
694
+ const getExplicitSelectOptions = (explicitSelectOptions, database) => explicitSelectOptions
695
+ .map((explicitSelectOption) => {
696
+ switch (explicitSelectOption.tag) {
697
+ case "General":
698
+ return {
699
+ id: { tag: "Generic", generic: explicitSelectOption.general.id },
700
+ translations: explicitSelectOption.general.translations,
701
+ };
702
+ case "Skill": {
703
+ const skill = database.skills.find(p => p[0] === explicitSelectOption.skill.id.skill)?.[1];
704
+ if (skill === undefined) {
705
+ return undefined;
706
+ }
707
+ return {
708
+ id: { tag: "Skill", skill: explicitSelectOption.skill.id.skill },
709
+ translations: mapObject(skill.translations, t10n => ({ name: t10n.name })),
710
+ };
711
+ }
712
+ case "CombatTechnique":
713
+ switch (explicitSelectOption.combat_technique.id.tag) {
714
+ case "CloseCombatTechnique": {
715
+ const id = explicitSelectOption.combat_technique.id.close_combat_technique;
716
+ const closeCombatTechnique = database.closeCombatTechniques.find(p => p[0] === id)?.[1];
717
+ if (closeCombatTechnique === undefined) {
718
+ return undefined;
719
+ }
720
+ return {
721
+ id: { tag: "CloseCombatTechnique", close_combat_technique: id },
722
+ translations: mapObject(closeCombatTechnique.translations, t10n => ({
723
+ name: t10n.name,
724
+ })),
725
+ };
726
+ }
727
+ case "RangedCombatTechnique": {
728
+ const id = explicitSelectOption.combat_technique.id.ranged_combat_technique;
729
+ const rangedCombatTechnique = database.rangedCombatTechniques.find(p => p[0] === id)?.[1];
730
+ if (rangedCombatTechnique === undefined) {
731
+ return undefined;
732
+ }
733
+ return {
734
+ id: { tag: "RangedCombatTechnique", ranged_combat_technique: id },
735
+ translations: mapObject(rangedCombatTechnique.translations, t10n => ({
736
+ name: t10n.name,
737
+ })),
738
+ };
739
+ }
740
+ default:
741
+ return assertExhaustive(explicitSelectOption.combat_technique.id);
742
+ }
743
+ default:
744
+ return assertExhaustive(explicitSelectOption);
745
+ }
746
+ })
747
+ .filter(isNotNullish);
748
+ const getSelectOptions = (selectOptions, id, database) => [
749
+ ...(selectOptions.derived === undefined
750
+ ? []
751
+ : getDerivedSelectOptions(selectOptions.derived, id, database)),
752
+ ...(selectOptions.explicit === undefined
753
+ ? []
754
+ : getExplicitSelectOptions(selectOptions.explicit, database)),
755
+ ];
756
+ const getSelectOptionsForResults = (database, idTag, results) => results.reduce((acc, [id, data]) => {
757
+ const options = getSelectOptions(data.select_options ?? {}, (() => {
758
+ switch (idTag) {
759
+ case "Advantage":
760
+ return { tag: "Advantage", advantage: id };
761
+ case "Disadvantage":
762
+ return { tag: "Disadvantage", disadvantage: id };
763
+ case "GeneralSpecialAbility":
764
+ return { tag: "GeneralSpecialAbility", general_special_ability: id };
765
+ case "FatePointSpecialAbility":
766
+ return { tag: "FatePointSpecialAbility", fate_point_special_ability: id };
767
+ case "CombatSpecialAbility":
768
+ return { tag: "CombatSpecialAbility", combat_special_ability: id };
769
+ case "MagicalSpecialAbility":
770
+ return { tag: "MagicalSpecialAbility", magical_special_ability: id };
771
+ case "StaffEnchantment":
772
+ return { tag: "StaffEnchantment", staff_enchantment: id };
773
+ case "FamiliarSpecialAbility":
774
+ return { tag: "FamiliarSpecialAbility", familiar_special_ability: id };
775
+ case "KarmaSpecialAbility":
776
+ return { tag: "KarmaSpecialAbility", karma_special_ability: id };
777
+ case "ProtectiveWardingCircleSpecialAbility":
778
+ return {
779
+ tag: "ProtectiveWardingCircleSpecialAbility",
780
+ protective_warding_circle_special_ability: id,
781
+ };
782
+ case "CombatStyleSpecialAbility":
783
+ return { tag: "CombatStyleSpecialAbility", combat_style_special_ability: id };
784
+ case "AdvancedCombatSpecialAbility":
785
+ return { tag: "AdvancedCombatSpecialAbility", advanced_combat_special_ability: id };
786
+ case "CommandSpecialAbility":
787
+ return { tag: "CommandSpecialAbility", command_special_ability: id };
788
+ case "MagicStyleSpecialAbility":
789
+ return { tag: "MagicStyleSpecialAbility", magic_style_special_ability: id };
790
+ case "AdvancedMagicalSpecialAbility":
791
+ return { tag: "AdvancedMagicalSpecialAbility", advanced_magical_special_ability: id };
792
+ case "SpellSwordEnchantment":
793
+ return { tag: "SpellSwordEnchantment", spell_sword_enchantment: id };
794
+ case "DaggerRitual":
795
+ return { tag: "DaggerRitual", dagger_ritual: id };
796
+ case "InstrumentEnchantment":
797
+ return { tag: "InstrumentEnchantment", instrument_enchantment: id };
798
+ case "AttireEnchantment":
799
+ return { tag: "AttireEnchantment", attire_enchantment: id };
800
+ case "OrbEnchantment":
801
+ return { tag: "OrbEnchantment", orb_enchantment: id };
802
+ case "WandEnchantment":
803
+ return { tag: "WandEnchantment", wand_enchantment: id };
804
+ case "BrawlingSpecialAbility":
805
+ return { tag: "BrawlingSpecialAbility", brawling_special_ability: id };
806
+ case "AncestorGlyph":
807
+ return { tag: "AncestorGlyph", ancestor_glyph: id };
808
+ case "CeremonialItemSpecialAbility":
809
+ return { tag: "CeremonialItemSpecialAbility", ceremonial_item_special_ability: id };
810
+ case "Sermon":
811
+ return { tag: "Sermon", sermon: id };
812
+ case "LiturgicalStyleSpecialAbility":
813
+ return { tag: "LiturgicalStyleSpecialAbility", liturgical_style_special_ability: id };
814
+ case "AdvancedKarmaSpecialAbility":
815
+ return { tag: "AdvancedKarmaSpecialAbility", advanced_karma_special_ability: id };
816
+ case "Vision":
817
+ return { tag: "Vision", vision: id };
818
+ case "MagicalTradition":
819
+ return { tag: "MagicalTradition", magical_tradition: id };
820
+ case "BlessedTradition":
821
+ return { tag: "BlessedTradition", blessed_tradition: id };
822
+ case "PactGift":
823
+ return { tag: "PactGift", pact_gift: id };
824
+ case "SikaryanDrainSpecialAbility":
825
+ return { tag: "SikaryanDrainSpecialAbility", sikaryan_drain_special_ability: id };
826
+ case "VampiricGift":
827
+ return { tag: "VampiricGift", vampiric_gift: id };
828
+ case "LycantropicGift":
829
+ return { tag: "LycantropicGift", lycantropic_gift: id };
830
+ case "SkillStyleSpecialAbility":
831
+ return { tag: "SkillStyleSpecialAbility", skill_style_special_ability: id };
832
+ case "AdvancedSkillSpecialAbility":
833
+ return { tag: "AdvancedSkillSpecialAbility", advanced_skill_special_ability: id };
834
+ case "ArcaneOrbEnchantment":
835
+ return { tag: "ArcaneOrbEnchantment", arcane_orb_enchantment: id };
836
+ case "CauldronEnchantment":
837
+ return { tag: "CauldronEnchantment", cauldron_enchantment: id };
838
+ case "FoolsHatEnchantment":
839
+ return { tag: "FoolsHatEnchantment", fools_hat_enchantment: id };
840
+ case "ToyEnchantment":
841
+ return { tag: "ToyEnchantment", toy_enchantment: id };
842
+ case "BowlEnchantment":
843
+ return { tag: "BowlEnchantment", bowl_enchantment: id };
844
+ case "FatePointSexSpecialAbility":
845
+ return { tag: "FatePointSexSpecialAbility", fate_point_sex_special_ability: id };
846
+ case "SexSpecialAbility":
847
+ return { tag: "SexSpecialAbility", sex_special_ability: id };
848
+ case "WeaponEnchantment":
849
+ return { tag: "WeaponEnchantment", weapon_enchantment: id };
850
+ case "SickleRitual":
851
+ return { tag: "SickleRitual", sickle_ritual: id };
852
+ case "RingEnchantment":
853
+ return { tag: "RingEnchantment", ring_enchantment: id };
854
+ case "ChronicleEnchantment":
855
+ return { tag: "ChronicleEnchantment", chronicle_enchantment: id };
856
+ case "Krallenkettenzauber":
857
+ return { tag: "Krallenkettenzauber", krallenkettenzauber: id };
858
+ case "Trinkhornzauber":
859
+ return { tag: "Trinkhornzauber", trinkhornzauber: id };
860
+ default:
861
+ return assertExhaustive(idTag);
862
+ }
863
+ })(), database);
864
+ if (options.length > 0) {
865
+ acc[id] = options;
866
+ }
867
+ return acc;
868
+ }, {});
869
+ // prettier-ignore
870
+ export const config = {
871
+ builder(database) {
872
+ return {
873
+ advancedCombatSpecialAbilities: getSelectOptionsForResults(database, "AdvancedCombatSpecialAbility", database.advancedCombatSpecialAbilities),
874
+ advancedKarmaSpecialAbilities: getSelectOptionsForResults(database, "AdvancedKarmaSpecialAbility", database.advancedKarmaSpecialAbilities),
875
+ advancedMagicalSpecialAbilities: getSelectOptionsForResults(database, "AdvancedMagicalSpecialAbility", database.advancedMagicalSpecialAbilities),
876
+ advancedSkillSpecialAbilities: getSelectOptionsForResults(database, "AdvancedSkillSpecialAbility", database.advancedSkillSpecialAbilities),
877
+ advantages: getSelectOptionsForResults(database, "Advantage", database.advantages),
878
+ ancestorGlyphs: getSelectOptionsForResults(database, "AncestorGlyph", database.ancestorGlyphs),
879
+ arcaneOrbEnchantments: getSelectOptionsForResults(database, "ArcaneOrbEnchantment", database.arcaneOrbEnchantments),
880
+ attireEnchantments: getSelectOptionsForResults(database, "AttireEnchantment", database.attireEnchantments),
881
+ blessedTraditions: getSelectOptionsForResults(database, "BlessedTradition", database.blessedTraditions),
882
+ bowlEnchantments: getSelectOptionsForResults(database, "BowlEnchantment", database.bowlEnchantments),
883
+ brawlingSpecialAbilities: getSelectOptionsForResults(database, "BrawlingSpecialAbility", database.brawlingSpecialAbilities),
884
+ cauldronEnchantments: getSelectOptionsForResults(database, "CauldronEnchantment", database.cauldronEnchantments),
885
+ ceremonialItemSpecialAbilities: getSelectOptionsForResults(database, "CeremonialItemSpecialAbility", database.ceremonialItemSpecialAbilities),
886
+ chronicleEnchantments: getSelectOptionsForResults(database, "ChronicleEnchantment", database.chronicleEnchantments),
887
+ combatSpecialAbilities: getSelectOptionsForResults(database, "CombatSpecialAbility", database.combatSpecialAbilities),
888
+ combatStyleSpecialAbilities: getSelectOptionsForResults(database, "CombatStyleSpecialAbility", database.combatStyleSpecialAbilities),
889
+ commandSpecialAbilities: getSelectOptionsForResults(database, "CommandSpecialAbility", database.commandSpecialAbilities),
890
+ daggerRituals: getSelectOptionsForResults(database, "DaggerRitual", database.daggerRituals),
891
+ disadvantages: getSelectOptionsForResults(database, "Disadvantage", database.disadvantages),
892
+ familiarSpecialAbilities: getSelectOptionsForResults(database, "FamiliarSpecialAbility", database.familiarSpecialAbilities),
893
+ fatePointSexSpecialAbilities: getSelectOptionsForResults(database, "FatePointSexSpecialAbility", database.fatePointSexSpecialAbilities),
894
+ fatePointSpecialAbilities: getSelectOptionsForResults(database, "FatePointSpecialAbility", database.fatePointSpecialAbilities),
895
+ foolsHatEnchantments: getSelectOptionsForResults(database, "FoolsHatEnchantment", database.foolsHatEnchantments),
896
+ generalSpecialAbilities: getSelectOptionsForResults(database, "GeneralSpecialAbility", database.generalSpecialAbilities),
897
+ instrumentEnchantments: getSelectOptionsForResults(database, "InstrumentEnchantment", database.instrumentEnchantments),
898
+ karmaSpecialAbilities: getSelectOptionsForResults(database, "KarmaSpecialAbility", database.karmaSpecialAbilities),
899
+ krallenkettenzauber: getSelectOptionsForResults(database, "Krallenkettenzauber", database.krallenkettenzauber),
900
+ liturgicalStyleSpecialAbilities: getSelectOptionsForResults(database, "LiturgicalStyleSpecialAbility", database.liturgicalStyleSpecialAbilities),
901
+ lycantropicGifts: getSelectOptionsForResults(database, "LycantropicGift", database.lycantropicGifts),
902
+ magicalSpecialAbilities: getSelectOptionsForResults(database, "MagicalSpecialAbility", database.magicalSpecialAbilities),
903
+ magicalTraditions: getSelectOptionsForResults(database, "MagicalTradition", database.magicalTraditions),
904
+ magicStyleSpecialAbilities: getSelectOptionsForResults(database, "MagicStyleSpecialAbility", database.magicStyleSpecialAbilities),
905
+ orbEnchantments: getSelectOptionsForResults(database, "OrbEnchantment", database.orbEnchantments),
906
+ pactGifts: getSelectOptionsForResults(database, "PactGift", database.pactGifts),
907
+ protectiveWardingCircleSpecialAbilities: getSelectOptionsForResults(database, "ProtectiveWardingCircleSpecialAbility", database.protectiveWardingCircleSpecialAbilities),
908
+ ringEnchantments: getSelectOptionsForResults(database, "RingEnchantment", database.ringEnchantments),
909
+ sermons: getSelectOptionsForResults(database, "Sermon", database.sermons),
910
+ sexSpecialAbilities: getSelectOptionsForResults(database, "SexSpecialAbility", database.sexSpecialAbilities),
911
+ sickleRituals: getSelectOptionsForResults(database, "SickleRitual", database.sickleRituals),
912
+ sikaryanDrainSpecialAbilities: getSelectOptionsForResults(database, "SikaryanDrainSpecialAbility", database.sikaryanDrainSpecialAbilities),
913
+ staffEnchantments: getSelectOptionsForResults(database, "StaffEnchantment", database.staffEnchantments),
914
+ skillStyleSpecialAbilities: getSelectOptionsForResults(database, "SkillStyleSpecialAbility", database.skillStyleSpecialAbilities),
915
+ spellSwordEnchantments: getSelectOptionsForResults(database, "SpellSwordEnchantment", database.spellSwordEnchantments),
916
+ toyEnchantments: getSelectOptionsForResults(database, "ToyEnchantment", database.toyEnchantments),
917
+ trinkhornzauber: getSelectOptionsForResults(database, "Trinkhornzauber", database.trinkhornzauber),
918
+ vampiricGifts: getSelectOptionsForResults(database, "VampiricGift", database.vampiricGifts),
919
+ visions: getSelectOptionsForResults(database, "Vision", database.visions),
920
+ wandEnchantments: getSelectOptionsForResults(database, "WandEnchantment", database.wandEnchantments),
921
+ weaponEnchantments: getSelectOptionsForResults(database, "WeaponEnchantment", database.weaponEnchantments),
922
+ };
923
+ },
924
+ };