@thijshakkenberg/valkyrie-mom-mcp 2.3.1 → 2.3.2

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.
@@ -324,6 +324,246 @@ export const MONSTERS: CatalogEntry[] = [
324
324
  "awareness": 2,
325
325
  "horror": 6,
326
326
  "brawn": 1
327
+ },
328
+ {
329
+ "id": "MonsterZombie",
330
+ "type": "monster",
331
+ "name": "{ffg:MONSTER_ZOMBIE}",
332
+ "pack": "mom1e-monsters",
333
+ "traits": [
334
+ "undead",
335
+ "small",
336
+ "humanoid"
337
+ ],
338
+ "health": 2,
339
+ "healthperhero": 1,
340
+ "awareness": 2,
341
+ "horror": 4,
342
+ "brawn": 2
343
+ },
344
+ {
345
+ "id": "MonsterCthonian",
346
+ "type": "monster",
347
+ "name": "{ffg:MONSTER_CTHONIAN}",
348
+ "pack": "mom1e-monsters",
349
+ "traits": [
350
+ "beast"
351
+ ],
352
+ "health": 3,
353
+ "healthperhero": 2,
354
+ "awareness": 4,
355
+ "horror": 7,
356
+ "brawn": 5
357
+ },
358
+ {
359
+ "id": "MonsterHoundOfTindalos",
360
+ "type": "monster",
361
+ "name": "{ffg:MONSTER_HOUND_OF_TINDALOS}",
362
+ "pack": "mom1e-monsters",
363
+ "traits": [
364
+ "beast"
365
+ ],
366
+ "health": 1,
367
+ "healthperhero": 2,
368
+ "awareness": 6,
369
+ "horror": 5,
370
+ "brawn": 5
371
+ },
372
+ {
373
+ "id": "MonsterManiac",
374
+ "type": "monster",
375
+ "name": "{ffg:MONSTER_MANIAC}",
376
+ "pack": "mom1e-monsters",
377
+ "traits": [
378
+ "humanoid"
379
+ ],
380
+ "health": 2,
381
+ "healthperhero": 1,
382
+ "awareness": 5,
383
+ "horror": 1,
384
+ "brawn": 2
385
+ },
386
+ {
387
+ "id": "MonsterMiGo",
388
+ "type": "monster",
389
+ "name": "{ffg:MONSTER_MI_GO}",
390
+ "pack": "mom1e-monsters",
391
+ "traits": [
392
+ "beast"
393
+ ],
394
+ "health": 3,
395
+ "healthperhero": 1,
396
+ "awareness": 6,
397
+ "horror": 4,
398
+ "brawn": 3
399
+ },
400
+ {
401
+ "id": "MonsterShoggoth",
402
+ "type": "monster",
403
+ "name": "{ffg:MONSTER_SHOGGOTH}",
404
+ "pack": "mom1e-monsters",
405
+ "traits": [
406
+ "beast"
407
+ ],
408
+ "health": 4,
409
+ "healthperhero": 2,
410
+ "awareness": 4,
411
+ "horror": 6,
412
+ "brawn": 4
413
+ },
414
+ {
415
+ "id": "MonsterWitch",
416
+ "type": "monster",
417
+ "name": "{ffg:MONSTER_WITCH}",
418
+ "pack": "mom1e-monsters",
419
+ "traits": [
420
+ "humanoid"
421
+ ],
422
+ "health": 2,
423
+ "healthperhero": 1,
424
+ "awareness": 3,
425
+ "horror": 2,
426
+ "brawn": 2
427
+ },
428
+ {
429
+ "id": "MonsterCultLeader",
430
+ "type": "monster",
431
+ "name": "{ffg:UNIQUE_MONSTER_CULT_LEADER}",
432
+ "pack": "mom1e-monsters",
433
+ "traits": [
434
+ "humanoid"
435
+ ],
436
+ "health": 8,
437
+ "healthperhero": 4,
438
+ "awareness": 4,
439
+ "horror": 3,
440
+ "brawn": 2
441
+ },
442
+ {
443
+ "id": "MonsterChildOfTheGoat",
444
+ "type": "monster",
445
+ "name": "{ffg:MONSTER_CHILD_OF_THE_GOAT}",
446
+ "pack": "cotw-monsters",
447
+ "traits": [
448
+ "humanoid"
449
+ ],
450
+ "health": 1,
451
+ "healthperhero": 1,
452
+ "awareness": 3,
453
+ "horror": 1,
454
+ "brawn": 2
455
+ },
456
+ {
457
+ "id": "MonsterDarkDruid",
458
+ "type": "monster",
459
+ "name": "{ffg:MONSTER_DARK_DRUID}",
460
+ "pack": "cotw-monsters",
461
+ "traits": [
462
+ "humanoid"
463
+ ],
464
+ "health": 2,
465
+ "healthperhero": 1,
466
+ "awareness": 3,
467
+ "horror": 2,
468
+ "brawn": 2
469
+ },
470
+ {
471
+ "id": "MonsterDarkYoung",
472
+ "type": "monster",
473
+ "name": "{ffg:MONSTER_DARK_YOUNG}",
474
+ "pack": "cotw-monsters",
475
+ "traits": [
476
+ "beast"
477
+ ],
478
+ "health": 4,
479
+ "healthperhero": 2,
480
+ "awareness": 4,
481
+ "horror": 7,
482
+ "brawn": 5
483
+ },
484
+ {
485
+ "id": "MonsterGoatSpawn",
486
+ "type": "monster",
487
+ "name": "{ffg:MONSTER_GOAT_SPAWN}",
488
+ "pack": "cotw-monsters",
489
+ "traits": [
490
+ "humanoid"
491
+ ],
492
+ "health": 1,
493
+ "healthperhero": 2,
494
+ "awareness": 4,
495
+ "horror": 3,
496
+ "brawn": 2
497
+ },
498
+ {
499
+ "id": "MonsterNightgaunt",
500
+ "type": "monster",
501
+ "name": "{ffg:MONSTER_NIGHTGAUNT}",
502
+ "pack": "cotw-monsters",
503
+ "traits": [
504
+ "beast"
505
+ ],
506
+ "health": 1,
507
+ "healthperhero": 2,
508
+ "awareness": 6,
509
+ "horror": 5,
510
+ "brawn": 3
511
+ },
512
+ {
513
+ "id": "MonsterDunwichHorror",
514
+ "type": "monster",
515
+ "name": "{ffg:UNIQUE_MONSTER_DUNWICH_HORROR}",
516
+ "pack": "cotw-monsters",
517
+ "traits": [
518
+ "horror"
519
+ ],
520
+ "health": 2,
521
+ "healthperhero": 3,
522
+ "awareness": 7,
523
+ "horror": 9,
524
+ "brawn": 5
525
+ },
526
+ {
527
+ "id": "MonsterWizard",
528
+ "type": "monster",
529
+ "name": "{ffg:UNIQUE_MONSTER_WIZARD}",
530
+ "pack": "cotw-monsters",
531
+ "traits": [
532
+ "humanoid"
533
+ ],
534
+ "health": 2,
535
+ "healthperhero": 1,
536
+ "awareness": 4,
537
+ "horror": 3,
538
+ "brawn": 2
539
+ },
540
+ {
541
+ "id": "MonsterByakhee",
542
+ "type": "monster",
543
+ "name": "{ffg:MONSTER_BYAKHEE}",
544
+ "pack": "fa-monsters",
545
+ "traits": [
546
+ "beast"
547
+ ],
548
+ "health": 1,
549
+ "healthperhero": 2,
550
+ "awareness": 4,
551
+ "horror": 3,
552
+ "brawn": 2
553
+ },
554
+ {
555
+ "id": "MonsterCrawlingOne",
556
+ "type": "monster",
557
+ "name": "{ffg:MONSTER_CRAWLING_ONE}",
558
+ "pack": "fa-monsters",
559
+ "traits": [
560
+ "beast"
561
+ ],
562
+ "health": 3,
563
+ "healthperhero": 1,
564
+ "awareness": 2,
565
+ "horror": 4,
566
+ "brawn": 4
327
567
  }
328
568
  ];
329
569
 
@@ -5431,6 +5671,26 @@ export const ITEMS: CatalogEntry[] = [
5431
5671
  "spell",
5432
5672
  "spellattack"
5433
5673
  ]
5674
+ },
5675
+ {
5676
+ "id": "ItemUniqueDukeTheDog",
5677
+ "type": "item",
5678
+ "name": "{ffg:UNIQUE_ITEM_DUKE_THE_DOG}",
5679
+ "pack": "ck-tokensandcards",
5680
+ "traits": [
5681
+ "ally",
5682
+ "unique"
5683
+ ]
5684
+ },
5685
+ {
5686
+ "id": "ItemUniqueFluxStabilizer",
5687
+ "type": "item",
5688
+ "name": "{ffg:UNIQUE_ITEM_FLUX_STABILIZER}",
5689
+ "pack": "ck-tokensandcards",
5690
+ "traits": [
5691
+ "equipment",
5692
+ "unique"
5693
+ ]
5434
5694
  }
5435
5695
  ];
5436
5696
 
@@ -5650,6 +5910,150 @@ export const INVESTIGATORS: CatalogEntry[] = [
5650
5910
  "traits": [
5651
5911
  "female"
5652
5912
  ]
5913
+ },
5914
+ {
5915
+ "id": "HeroMichaelMcGlen",
5916
+ "type": "investigator",
5917
+ "name": "{ffg:INVESTIGATOR_MICHAEL_MCGLEN}",
5918
+ "pack": "mom1e-investigators",
5919
+ "traits": [
5920
+ "male"
5921
+ ]
5922
+ },
5923
+ {
5924
+ "id": "HeroAshcanPete",
5925
+ "type": "investigator",
5926
+ "name": "{ffg:INVESTIGATOR_ASHCAN_PETE}",
5927
+ "pack": "mom1e-investigators",
5928
+ "traits": [
5929
+ "male"
5930
+ ]
5931
+ },
5932
+ {
5933
+ "id": "HeroGloriaGoldberg",
5934
+ "type": "investigator",
5935
+ "name": "{ffg:INVESTIGATOR_GLORIA_GOLDBERG}",
5936
+ "pack": "mom1e-investigators",
5937
+ "traits": [
5938
+ "female"
5939
+ ]
5940
+ },
5941
+ {
5942
+ "id": "HeroHarveyWalters",
5943
+ "type": "investigator",
5944
+ "name": "{ffg:INVESTIGATOR_HARVEY_WALTERS}",
5945
+ "pack": "mom1e-investigators",
5946
+ "traits": [
5947
+ "male"
5948
+ ]
5949
+ },
5950
+ {
5951
+ "id": "HeroJennyBarnes",
5952
+ "type": "investigator",
5953
+ "name": "{ffg:INVESTIGATOR_JENNY_BARNES}",
5954
+ "pack": "mom1e-investigators",
5955
+ "traits": [
5956
+ "female"
5957
+ ]
5958
+ },
5959
+ {
5960
+ "id": "HeroJoeDiamond",
5961
+ "type": "investigator",
5962
+ "name": "{ffg:INVESTIGATOR_JOE_DIAMOND}",
5963
+ "pack": "mom1e-investigators",
5964
+ "traits": [
5965
+ "male"
5966
+ ]
5967
+ },
5968
+ {
5969
+ "id": "HeroKateWinthrop",
5970
+ "type": "investigator",
5971
+ "name": "{ffg:INVESTIGATOR_KATE_WINTHROP}",
5972
+ "pack": "mom1e-investigators",
5973
+ "traits": [
5974
+ "female"
5975
+ ]
5976
+ },
5977
+ {
5978
+ "id": "HeroSisterMary",
5979
+ "type": "investigator",
5980
+ "name": "{ffg:INVESTIGATOR_SISTER_MARY}",
5981
+ "pack": "mom1e-investigators",
5982
+ "traits": [
5983
+ "female"
5984
+ ]
5985
+ },
5986
+ {
5987
+ "id": "HeroAmandaSharpe",
5988
+ "type": "investigator",
5989
+ "name": "{ffg:INVESTIGATOR_AMANDA_SHARPE}",
5990
+ "pack": "cotw-investigators",
5991
+ "traits": [
5992
+ "female"
5993
+ ]
5994
+ },
5995
+ {
5996
+ "id": "HeroBobJenkins",
5997
+ "type": "investigator",
5998
+ "name": "{ffg:INVESTIGATOR_BOB_JENKINS}",
5999
+ "pack": "cotw-investigators",
6000
+ "traits": [
6001
+ "male"
6002
+ ]
6003
+ },
6004
+ {
6005
+ "id": "HeroMandyThompson",
6006
+ "type": "investigator",
6007
+ "name": "{ffg:INVESTIGATOR_MANDY_THOMPSON}",
6008
+ "pack": "cotw-investigators",
6009
+ "traits": [
6010
+ "female"
6011
+ ]
6012
+ },
6013
+ {
6014
+ "id": "HeroMontereyJack",
6015
+ "type": "investigator",
6016
+ "name": "{ffg:INVESTIGATOR_MONTEREY_JACK}",
6017
+ "pack": "cotw-investigators",
6018
+ "traits": [
6019
+ "male"
6020
+ ]
6021
+ },
6022
+ {
6023
+ "id": "HeroCarolynFern",
6024
+ "type": "investigator",
6025
+ "name": "{ffg:INVESTIGATOR_CAROLYN_FERN}",
6026
+ "pack": "fa-investigators",
6027
+ "traits": [
6028
+ "female"
6029
+ ]
6030
+ },
6031
+ {
6032
+ "id": "HeroDarrellSimmons",
6033
+ "type": "investigator",
6034
+ "name": "{ffg:INVESTIGATOR_DARRELL_SIMMONS}",
6035
+ "pack": "fa-investigators",
6036
+ "traits": [
6037
+ "male"
6038
+ ]
6039
+ },
6040
+ {
6041
+ "id": "HeroDexterDrake",
6042
+ "type": "investigator",
6043
+ "name": "{ffg:INVESTIGATOR_DEXTER_DRAKE}",
6044
+ "pack": "fa-investigators",
6045
+ "traits": [
6046
+ "male"
6047
+ ]
6048
+ },
6049
+ {
6050
+ "id": "HeroVincentLee",
6051
+ "type": "investigator",
6052
+ "name": "{ffg:INVESTIGATOR_VINCENT_LEE}",
6053
+ "pack": "fa-investigators",
6054
+ "traits": [
6055
+ "male"
6056
+ ]
5653
6057
  }
5654
6058
  ];
5655
6059
 
@@ -8387,5 +8791,33 @@ export const TOKENS: CatalogEntry[] = [
8387
8791
  "spell",
8388
8792
  "spellattack"
8389
8793
  ]
8794
+ },
8795
+ {
8796
+ "id": "TokenAmmiPierce",
8797
+ "type": "token",
8798
+ "name": "",
8799
+ "pack": "ck-tokensandcards",
8800
+ "traits": []
8801
+ },
8802
+ {
8803
+ "id": "TokenCorrinaJones",
8804
+ "type": "token",
8805
+ "name": "",
8806
+ "pack": "ck-tokensandcards",
8807
+ "traits": []
8808
+ },
8809
+ {
8810
+ "id": "TokenEricColt",
8811
+ "type": "token",
8812
+ "name": "",
8813
+ "pack": "ck-tokensandcards",
8814
+ "traits": []
8815
+ },
8816
+ {
8817
+ "id": "TokenZebulonWhateley",
8818
+ "type": "token",
8819
+ "name": "",
8820
+ "pack": "ck-tokensandcards",
8821
+ "traits": []
8390
8822
  }
8391
8823
  ];
@@ -70,7 +70,17 @@ const PACK_FILE_MAPPINGS: FileMapping[] = [
70
70
  { file: 'tokens_monsters.ini', type: 'token' },
71
71
  ];
72
72
 
73
- const CONTENT_PACKS = ['base', 'btt', 'hj', 'pots', 'soa', 'sot'];
73
+ /**
74
+ * Content folders to read, and the catalog pack each one files its entries under.
75
+ * The ck/ folders are the first-edition conversion kit. Recurring Nightmares (RN) and
76
+ * Suppressed Memories (SM) clone them, so owners of those figure packs have these
77
+ * monsters and items. Their tiles are left out: tile-geometry.ts has no data for them.
78
+ */
79
+ const CONTENT_PACKS: Array<{ dir: string; pack: string }> = [
80
+ ...['base', 'btt', 'hj', 'pots', 'soa', 'sot'].map(p => ({ dir: p, pack: p })),
81
+ ...['mom1e-monsters', 'mom1e-investigators', 'ck-tokensandcards', 'cotw-monsters', 'cotw-investigators', 'fa-monsters', 'fa-investigators']
82
+ .map(p => ({ dir: path.join('ck', p), pack: p })),
83
+ ];
74
84
 
75
85
  /**
76
86
  * Extract all catalogs from Valkyrie content directory.
@@ -89,9 +99,9 @@ export function extractAllCatalogs(
89
99
  token: [],
90
100
  };
91
101
 
92
- for (const pack of CONTENT_PACKS) {
102
+ for (const { dir, pack } of CONTENT_PACKS) {
93
103
  for (const mapping of PACK_FILE_MAPPINGS) {
94
- const filePath = path.join(contentDir, pack, mapping.file);
104
+ const filePath = path.join(contentDir, dir, mapping.file);
95
105
  if (!fs.existsSync(filePath)) continue;
96
106
 
97
107
  const content = fs.readFileSync(filePath, 'utf-8');
@@ -171,25 +171,7 @@ export async function saveScenario(model: ScenarioModel): Promise<void> {
171
171
  }
172
172
  }
173
173
 
174
- // Auto-compute required packs from tile sides and monsters
175
- const catalog = getSharedCatalog();
176
- const requiredPacks = new Set<string>();
177
-
178
- for (const comp of model.getByType('Tile')) {
179
- const side = comp.data.side;
180
- if (!side) continue;
181
- const pack = catalog.getPackForTileSide(side);
182
- if (pack && pack !== 'MoMBase') requiredPacks.add(pack);
183
- }
184
-
185
- for (const comp of model.getByType('Spawn')) {
186
- const monsterField = comp.data.monster;
187
- if (!monsterField) continue;
188
- for (const name of parseRefList(monsterField)) {
189
- const pack = catalog.getPackForMonster(name);
190
- if (pack && pack !== 'MoMBase') requiredPacks.add(pack);
191
- }
192
- }
174
+ const requiredPacks = computeRequiredPacks(model);
193
175
 
194
176
  if (requiredPacks.size > 0) {
195
177
  model.questConfig.packs = [...requiredPacks].sort().join(' ');
@@ -214,6 +196,44 @@ export async function saveScenario(model: ScenarioModel): Promise<void> {
214
196
  fs.writeFileSync(path.join(dir, 'Localization.English.txt'), locContent);
215
197
  }
216
198
 
199
+ /**
200
+ * The expansions a scenario needs, from everything it puts on the table:
201
+ * tile sides, spawned monsters (a custom monster counts through its base),
202
+ * catalog monsters shown as tokens, and quest items given by name.
203
+ */
204
+ export function computeRequiredPacks(model: ScenarioModel): Set<string> {
205
+ const catalog = getSharedCatalog();
206
+ const requiredPacks = new Set<string>();
207
+ const add = (pack: string | undefined) => {
208
+ if (pack && pack !== 'MoMBase') requiredPacks.add(pack);
209
+ };
210
+ const addMonster = (name: string) => {
211
+ const custom = name.startsWith('CustomMonster') ? model.get(name) : undefined;
212
+ add(catalog.getPackForMonster(custom?.data.base ?? name));
213
+ };
214
+
215
+ for (const comp of model.getByType('Tile')) {
216
+ if (comp.data.side) add(catalog.getPackForTileSide(comp.data.side));
217
+ }
218
+ for (const comp of model.getByType('Spawn')) {
219
+ for (const name of parseRefList(comp.data.monster ?? '')) addMonster(name);
220
+ }
221
+ for (const comp of model.getByType('CustomMonster')) {
222
+ if (comp.data.base) add(catalog.getPackForMonster(comp.data.base));
223
+ }
224
+ for (const comp of model.getByType('Token')) {
225
+ if (comp.data.type && !comp.data.customImage) add(catalog.getPackForMonster(comp.data.type));
226
+ }
227
+ for (const comp of model.getByType('QItem')) {
228
+ // With traits, itemname lists items to exclude from the draw, so they need not be owned
229
+ if (comp.data.traits?.trim()) continue;
230
+ // Several names: Valkyrie picks one it can find, so only a single name is a hard requirement
231
+ const names = parseRefList(comp.data.itemname ?? '');
232
+ if (names.length === 1) add(catalog.getPackForItem(names[0]));
233
+ }
234
+ return requiredPacks;
235
+ }
236
+
217
237
  /** Builds .valkyrie package */
218
238
  export async function buildScenario(model: ScenarioModel, outputPath: string): Promise<void> {
219
239
  const dir = model.scenarioDir;