@maka/maka-cli 5.10.0 → 5.11.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 (65) hide show
  1. package/bundle/typescript/package.json +1 -1
  2. package/bundle/typescript/src/commands/game/sideQuest/archetypes.js +3 -1
  3. package/bundle/typescript/src/commands/game/sideQuest/archetypes.js.map +1 -1
  4. package/bundle/typescript/src/commands/game/sideQuest/commands/ask.js +1 -1
  5. package/bundle/typescript/src/commands/game/sideQuest/commands/ask.js.map +1 -1
  6. package/bundle/typescript/src/commands/game/sideQuest/commands/call.js +10 -2
  7. package/bundle/typescript/src/commands/game/sideQuest/commands/call.js.map +1 -1
  8. package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js +2 -0
  9. package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js.map +1 -1
  10. package/bundle/typescript/src/commands/game/sideQuest/commands/contacts.js +8 -2
  11. package/bundle/typescript/src/commands/game/sideQuest/commands/contacts.js.map +1 -1
  12. package/bundle/typescript/src/commands/game/sideQuest/commands/docwagon.d.ts +14 -0
  13. package/bundle/typescript/src/commands/game/sideQuest/commands/docwagon.js +86 -0
  14. package/bundle/typescript/src/commands/game/sideQuest/commands/docwagon.js.map +1 -0
  15. package/bundle/typescript/src/commands/game/sideQuest/commands/grapple.js +8 -2
  16. package/bundle/typescript/src/commands/game/sideQuest/commands/grapple.js.map +1 -1
  17. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +6 -4
  18. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js.map +1 -1
  19. package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.js +5 -0
  20. package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.js.map +1 -1
  21. package/bundle/typescript/src/commands/game/sideQuest/commands/job-offer.d.ts +32 -0
  22. package/bundle/typescript/src/commands/game/sideQuest/commands/job-offer.js +94 -0
  23. package/bundle/typescript/src/commands/game/sideQuest/commands/job-offer.js.map +1 -0
  24. package/bundle/typescript/src/commands/game/sideQuest/commands/lead.d.ts +39 -0
  25. package/bundle/typescript/src/commands/game/sideQuest/commands/lead.js +187 -0
  26. package/bundle/typescript/src/commands/game/sideQuest/commands/lead.js.map +1 -0
  27. package/bundle/typescript/src/commands/game/sideQuest/commands/palm.d.ts +16 -0
  28. package/bundle/typescript/src/commands/game/sideQuest/commands/palm.js +86 -0
  29. package/bundle/typescript/src/commands/game/sideQuest/commands/palm.js.map +1 -0
  30. package/bundle/typescript/src/commands/game/sideQuest/commands/perform.d.ts +13 -0
  31. package/bundle/typescript/src/commands/game/sideQuest/commands/perform.js +48 -0
  32. package/bundle/typescript/src/commands/game/sideQuest/commands/perform.js.map +1 -0
  33. package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js +8 -3
  34. package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js.map +1 -1
  35. package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +7 -2
  36. package/bundle/typescript/src/commands/game/sideQuest/commands/search.js.map +1 -1
  37. package/bundle/typescript/src/commands/game/sideQuest/commands/source.d.ts +9 -0
  38. package/bundle/typescript/src/commands/game/sideQuest/commands/source.js +159 -0
  39. package/bundle/typescript/src/commands/game/sideQuest/commands/source.js.map +1 -0
  40. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js +6 -3
  41. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js.map +1 -1
  42. package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +57 -1
  43. package/bundle/typescript/src/commands/game/sideQuest/game.js +326 -13
  44. package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
  45. package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +4 -1
  46. package/bundle/typescript/src/commands/game/sideQuest/models/npc.js.map +1 -1
  47. package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +19 -1
  48. package/bundle/typescript/src/commands/game/sideQuest/models/player.js +48 -9
  49. package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
  50. package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +7 -1
  51. package/bundle/typescript/src/commands/game/sideQuest/models/scene.js.map +1 -1
  52. package/bundle/typescript/src/commands/game/sideQuest/scenes/scene2.json +299 -60
  53. package/bundle/typescript/src/commands/game/sideQuest/types/save-file.d.ts +22 -0
  54. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.d.ts +3 -0
  55. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +41 -9
  56. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js.map +1 -1
  57. package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.d.ts +19 -0
  58. package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.js +13 -0
  59. package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.js.map +1 -1
  60. package/bundle/typescript/src/commands/game/sideQuest/utilities/docwagon.d.ts +33 -0
  61. package/bundle/typescript/src/commands/game/sideQuest/utilities/docwagon.js +44 -0
  62. package/bundle/typescript/src/commands/game/sideQuest/utilities/docwagon.js.map +1 -0
  63. package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js +7 -16
  64. package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js.map +1 -1
  65. package/package.json +1 -1
@@ -12,7 +12,9 @@
12
12
  ],
13
13
  "currency": {
14
14
  "type": "nuyen",
15
- "authorityRoomTags": ["Bank"]
15
+ "authorityRoomTags": [
16
+ "Bank"
17
+ ]
16
18
  },
17
19
  "rumors": [
18
20
  "The Haze Dragons doubled their watch on the Smuggler's Tunnel -- something's moving under the district that even Brick won't name.",
@@ -143,7 +145,7 @@
143
145
  "color": "Black",
144
146
  "texture": "Rugged",
145
147
  "rating": "Common",
146
- "weight": 2.0
148
+ "weight": 2
147
149
  },
148
150
  {
149
151
  "name": "Knockoff Smart Glasses",
@@ -158,22 +160,38 @@
158
160
  }
159
161
  ]
160
162
  },
161
-
162
163
  "rooms": [
163
164
  {
164
165
  "name": "Neon Strip",
165
166
  "description": "The spine of the Purple Haze district: rain-slick pavement under a canopy of violet neon, food smoke, and AR graffiti three layers deep. Everyone you need is a block away in some direction.",
166
167
  "roomType": "Street",
167
- "tags": ["residence"],
168
+ "tags": [
169
+ "residence"
170
+ ],
168
171
  "supportedLifestyles": {
169
- "residenceTypes": ["Street", "Squatter"]
172
+ "residenceTypes": [
173
+ "Street",
174
+ "Squatter"
175
+ ]
170
176
  },
171
177
  "isStartRoom": true,
172
178
  "exits": [
173
- { "direction": "north", "targetRoom": "Purple Haze Bar" },
174
- { "direction": "east", "targetRoom": "Night Market" },
175
- { "direction": "south", "targetRoom": "Rain-Chewed Alley" },
176
- { "direction": "west", "targetRoom": "Mama Wu's Noodles" }
179
+ {
180
+ "direction": "north",
181
+ "targetRoom": "Purple Haze Bar"
182
+ },
183
+ {
184
+ "direction": "east",
185
+ "targetRoom": "Night Market"
186
+ },
187
+ {
188
+ "direction": "south",
189
+ "targetRoom": "Rain-Chewed Alley"
190
+ },
191
+ {
192
+ "direction": "west",
193
+ "targetRoom": "Mama Wu's Noodles"
194
+ }
177
195
  ]
178
196
  },
179
197
  {
@@ -182,13 +200,32 @@
182
200
  "roomType": "Bar",
183
201
  "size": "large",
184
202
  "spots": [
185
- { "name": "door", "description": "The street door, where the neon bleeds in." },
186
- { "name": "bar", "description": "The long counter, sticky with decades." },
187
- { "name": "booths", "description": "High-backed booths built for quiet deals.", "tags": ["cover-partial"] },
188
- { "name": "back corner", "description": "The dim table nobody chooses by accident." }
203
+ {
204
+ "name": "door",
205
+ "description": "The street door, where the neon bleeds in."
206
+ },
207
+ {
208
+ "name": "bar",
209
+ "description": "The long counter, sticky with decades."
210
+ },
211
+ {
212
+ "name": "booths",
213
+ "description": "High-backed booths built for quiet deals.",
214
+ "tags": [
215
+ "cover-partial"
216
+ ]
217
+ },
218
+ {
219
+ "name": "back corner",
220
+ "description": "The dim table nobody chooses by accident."
221
+ }
189
222
  ],
190
223
  "exits": [
191
- { "direction": "north", "targetRoom": "Bar Backroom", "atSpot": "back corner" }
224
+ {
225
+ "direction": "north",
226
+ "targetRoom": "Bar Backroom",
227
+ "atSpot": "back corner"
228
+ }
192
229
  ]
193
230
  },
194
231
  {
@@ -197,9 +234,15 @@
197
234
  "roomType": "Bar",
198
235
  "size": "small",
199
236
  "spots": [
200
- { "name": "door" },
201
- { "name": "card table" },
202
- { "name": "crates" }
237
+ {
238
+ "name": "door"
239
+ },
240
+ {
241
+ "name": "card table"
242
+ },
243
+ {
244
+ "name": "crates"
245
+ }
203
246
  ]
204
247
  },
205
248
  {
@@ -208,8 +251,14 @@
208
251
  "roomType": "Restaurant",
209
252
  "size": "small",
210
253
  "spots": [
211
- { "name": "door", "description": "A bead curtain and a blast of chili-scented steam." },
212
- { "name": "counter", "description": "Six stools and the pot that never empties." }
254
+ {
255
+ "name": "door",
256
+ "description": "A bead curtain and a blast of chili-scented steam."
257
+ },
258
+ {
259
+ "name": "counter",
260
+ "description": "Six stools and the pot that never empties."
261
+ }
213
262
  ]
214
263
  },
215
264
  {
@@ -218,14 +267,35 @@
218
267
  "roomType": "Market",
219
268
  "size": "large",
220
269
  "spots": [
221
- { "name": "entrance" },
222
- { "name": "stalls" },
223
- { "name": "shadowed edge", "tags": ["cover-partial"] }
270
+ {
271
+ "name": "entrance"
272
+ },
273
+ {
274
+ "name": "stalls"
275
+ },
276
+ {
277
+ "name": "shadowed edge",
278
+ "tags": [
279
+ "cover-partial"
280
+ ]
281
+ }
224
282
  ],
225
283
  "exits": [
226
- { "direction": "north", "targetRoom": "Ironmonger's Arms", "atSpot": "stalls" },
227
- { "direction": "east", "targetRoom": "Circuit Bazaar", "atSpot": "stalls" },
228
- { "direction": "south", "targetRoom": "Doc Halloran's Clinic", "atSpot": "stalls" }
284
+ {
285
+ "direction": "north",
286
+ "targetRoom": "Ironmonger's Arms",
287
+ "atSpot": "stalls"
288
+ },
289
+ {
290
+ "direction": "east",
291
+ "targetRoom": "Circuit Bazaar",
292
+ "atSpot": "stalls"
293
+ },
294
+ {
295
+ "direction": "south",
296
+ "targetRoom": "Doc Halloran's Clinic",
297
+ "atSpot": "stalls"
298
+ }
229
299
  ]
230
300
  },
231
301
  {
@@ -235,9 +305,15 @@
235
305
  "owner": "Torque",
236
306
  "size": "small",
237
307
  "spots": [
238
- { "name": "door" },
239
- { "name": "counter" },
240
- { "name": "racks" }
308
+ {
309
+ "name": "door"
310
+ },
311
+ {
312
+ "name": "counter"
313
+ },
314
+ {
315
+ "name": "racks"
316
+ }
241
317
  ]
242
318
  },
243
319
  {
@@ -248,12 +324,21 @@
248
324
  "hasNode": true,
249
325
  "size": "small",
250
326
  "spots": [
251
- { "name": "door" },
252
- { "name": "counter" },
253
- { "name": "workbenches" }
327
+ {
328
+ "name": "door"
329
+ },
330
+ {
331
+ "name": "counter"
332
+ },
333
+ {
334
+ "name": "workbenches"
335
+ }
254
336
  ],
255
337
  "exits": [
256
- { "direction": "east", "targetRoom": "Talismonger's Hut" }
338
+ {
339
+ "direction": "east",
340
+ "targetRoom": "Talismonger's Hut"
341
+ }
257
342
  ]
258
343
  },
259
344
  {
@@ -264,8 +349,12 @@
264
349
  "warded": true,
265
350
  "size": "small",
266
351
  "spots": [
267
- { "name": "doorway" },
268
- { "name": "altar table" }
352
+ {
353
+ "name": "doorway"
354
+ },
355
+ {
356
+ "name": "altar table"
357
+ }
269
358
  ]
270
359
  },
271
360
  {
@@ -275,9 +364,15 @@
275
364
  "owner": "Doc Halloran",
276
365
  "size": "small",
277
366
  "spots": [
278
- { "name": "door" },
279
- { "name": "cots" },
280
- { "name": "supply shelf" }
367
+ {
368
+ "name": "door"
369
+ },
370
+ {
371
+ "name": "cots"
372
+ },
373
+ {
374
+ "name": "supply shelf"
375
+ }
281
376
  ]
282
377
  },
283
378
  {
@@ -285,8 +380,14 @@
285
380
  "description": "The way down: an alley chewed ragged by decades of acid rain, its storm drains breathing cold air up from the undermarket below.",
286
381
  "roomType": "Street",
287
382
  "exits": [
288
- { "direction": "south", "targetRoom": "Flooded Undermarket" },
289
- { "direction": "east", "targetRoom": "Union Pawnshop" }
383
+ {
384
+ "direction": "south",
385
+ "targetRoom": "Flooded Undermarket"
386
+ },
387
+ {
388
+ "direction": "east",
389
+ "targetRoom": "Union Pawnshop"
390
+ }
290
391
  ]
291
392
  },
292
393
  {
@@ -296,9 +397,15 @@
296
397
  "owner": "Whisper",
297
398
  "size": "small",
298
399
  "spots": [
299
- { "name": "door" },
300
- { "name": "counter" },
301
- { "name": "back shelves" }
400
+ {
401
+ "name": "door"
402
+ },
403
+ {
404
+ "name": "counter"
405
+ },
406
+ {
407
+ "name": "back shelves"
408
+ }
302
409
  ]
303
410
  },
304
411
  {
@@ -307,13 +414,27 @@
307
414
  "roomType": "Market",
308
415
  "size": "large",
309
416
  "spots": [
310
- { "name": "entrance stair" },
311
- { "name": "stilted stalls" },
312
- { "name": "dry platforms" }
417
+ {
418
+ "name": "entrance stair"
419
+ },
420
+ {
421
+ "name": "stilted stalls"
422
+ },
423
+ {
424
+ "name": "dry platforms"
425
+ }
313
426
  ],
314
427
  "exits": [
315
- { "direction": "south", "targetRoom": "Smuggler's Tunnel", "atSpot": "stilted stalls" },
316
- { "direction": "west", "targetRoom": "Condemned Chapel", "atSpot": "dry platforms" }
428
+ {
429
+ "direction": "south",
430
+ "targetRoom": "Smuggler's Tunnel",
431
+ "atSpot": "stilted stalls"
432
+ },
433
+ {
434
+ "direction": "west",
435
+ "targetRoom": "Condemned Chapel",
436
+ "atSpot": "dry platforms"
437
+ }
317
438
  ]
318
439
  },
319
440
  {
@@ -321,8 +442,14 @@
321
442
  "description": "A bore of cracked ferrocrete running under the district, lit by chemlights and lined with cargo that never saw customs.",
322
443
  "roomType": "Tunnel",
323
444
  "exits": [
324
- { "direction": "east", "targetRoom": "Haze Dragons Turf" },
325
- { "direction": "south", "targetRoom": "Sim Den" }
445
+ {
446
+ "direction": "east",
447
+ "targetRoom": "Haze Dragons Turf"
448
+ },
449
+ {
450
+ "direction": "south",
451
+ "targetRoom": "Sim Den"
452
+ }
326
453
  ]
327
454
  },
328
455
  {
@@ -332,9 +459,18 @@
332
459
  "owner": "Brick",
333
460
  "size": "medium",
334
461
  "spots": [
335
- { "name": "gate" },
336
- { "name": "fire barrel" },
337
- { "name": "stacked crates", "tags": ["cover-partial"] }
462
+ {
463
+ "name": "gate"
464
+ },
465
+ {
466
+ "name": "fire barrel"
467
+ },
468
+ {
469
+ "name": "stacked crates",
470
+ "tags": [
471
+ "cover-partial"
472
+ ]
473
+ }
338
474
  ]
339
475
  },
340
476
  {
@@ -343,12 +479,23 @@
343
479
  "roomType": "Chapel",
344
480
  "size": "medium",
345
481
  "spots": [
346
- { "name": "doorway" },
347
- { "name": "nave" },
348
- { "name": "altar" }
482
+ {
483
+ "name": "doorway"
484
+ },
485
+ {
486
+ "name": "nave"
487
+ },
488
+ {
489
+ "name": "altar"
490
+ }
349
491
  ],
350
492
  "exits": [
351
- { "direction": "down", "targetRoom": "Chapel Crypt", "puzzle": "Crypt Grate", "atSpot": "altar" }
493
+ {
494
+ "direction": "down",
495
+ "targetRoom": "Chapel Crypt",
496
+ "puzzle": "Crypt Grate",
497
+ "atSpot": "altar"
498
+ }
352
499
  ]
353
500
  },
354
501
  {
@@ -357,8 +504,12 @@
357
504
  "roomType": "Crypt",
358
505
  "size": "small",
359
506
  "spots": [
360
- { "name": "entry stairs" },
361
- { "name": "reliquary niches" }
507
+ {
508
+ "name": "entry stairs"
509
+ },
510
+ {
511
+ "name": "reliquary niches"
512
+ }
362
513
  ]
363
514
  },
364
515
  {
@@ -369,8 +520,12 @@
369
520
  "nodeAccess": "wired",
370
521
  "size": "small",
371
522
  "spots": [
372
- { "name": "door" },
373
- { "name": "couch rows" }
523
+ {
524
+ "name": "door"
525
+ },
526
+ {
527
+ "name": "couch rows"
528
+ }
374
529
  ]
375
530
  },
376
531
  {
@@ -1208,6 +1363,90 @@
1208
1363
  "currencyAmount": 300,
1209
1364
  "room": "Chapel Crypt",
1210
1365
  "atSpot": "reliquary niches"
1366
+ },
1367
+ {
1368
+ "name": "Torque's Commlink",
1369
+ "description": "A ruggedized shop commlink, speed-dial worn down to the metal -- Torque answers for paying customers.",
1370
+ "size": "Small",
1371
+ "category": "Commlink",
1372
+ "shape": "Slab",
1373
+ "color": "Gunmetal",
1374
+ "texture": "Scarred Polymer",
1375
+ "rating": "Standard",
1376
+ "weight": 0.2,
1377
+ "heldBy": "Torque",
1378
+ "owner": "Torque",
1379
+ "transferable": false
1380
+ },
1381
+ {
1382
+ "name": "Ratchet's Commlink",
1383
+ "description": "A franken-link of three generations of parts that somehow outperforms all of them.",
1384
+ "size": "Small",
1385
+ "category": "Commlink",
1386
+ "shape": "Slab",
1387
+ "color": "Circuit Green",
1388
+ "texture": "Taped Seams",
1389
+ "rating": "Rare",
1390
+ "weight": 0.2,
1391
+ "heldBy": "Ratchet",
1392
+ "owner": "Ratchet",
1393
+ "transferable": false
1394
+ },
1395
+ {
1396
+ "name": "Halloran's Commlink",
1397
+ "description": "A clinic-grade commlink with a triage tone nobody in the district ignores.",
1398
+ "size": "Small",
1399
+ "category": "Commlink",
1400
+ "shape": "Slab",
1401
+ "color": "Bone White",
1402
+ "texture": "Antiseptic Plastic",
1403
+ "rating": "Standard",
1404
+ "weight": 0.2,
1405
+ "heldBy": "Doc Halloran",
1406
+ "owner": "Doc Halloran",
1407
+ "transferable": false
1408
+ },
1409
+ {
1410
+ "name": "Cray's Commlink",
1411
+ "description": "An ancient rigger deck stripped down to a talk-box -- the dead datajack port still blinks.",
1412
+ "size": "Small",
1413
+ "category": "Commlink",
1414
+ "shape": "Slab",
1415
+ "color": "Faded Orange",
1416
+ "texture": "Worn Rubber",
1417
+ "rating": "Common",
1418
+ "weight": 0.2,
1419
+ "heldBy": "Old Cray",
1420
+ "owner": "Old Cray",
1421
+ "transferable": false
1422
+ },
1423
+ {
1424
+ "name": "Skitter's Commlink",
1425
+ "description": "A knockoff commlink under three cracked screen protectors, ringtone set to a jingle.",
1426
+ "size": "Small",
1427
+ "category": "Commlink",
1428
+ "shape": "Slab",
1429
+ "color": "Neon Pink",
1430
+ "texture": "Sticker-Covered",
1431
+ "rating": "Common",
1432
+ "weight": 0.2,
1433
+ "heldBy": "Skitter",
1434
+ "owner": "Skitter",
1435
+ "transferable": false
1436
+ },
1437
+ {
1438
+ "name": "Whisper's Commlink",
1439
+ "description": "A pawned-and-reclaimed executive commlink, call log encrypted twice.",
1440
+ "size": "Small",
1441
+ "category": "Commlink",
1442
+ "shape": "Slab",
1443
+ "color": "Silver",
1444
+ "texture": "Polished Glass",
1445
+ "rating": "Standard",
1446
+ "weight": 0.2,
1447
+ "heldBy": "Whisper",
1448
+ "owner": "Whisper",
1449
+ "transferable": false
1211
1450
  }
1212
1451
  ],
1213
1452
  "puzzles": [
@@ -155,7 +155,29 @@ export interface ISavedGameState {
155
155
  spent: number;
156
156
  gigs: number;
157
157
  asked: boolean;
158
+ role?: string;
158
159
  }][];
160
+ /** SWAG orders in flight (commands/source.ts): paid, awaiting the
161
+ * next homecoming. Optional -- absent in pre-swag v1 saves. */
162
+ procurements?: {
163
+ contact: string;
164
+ itemConfig: ISavedItem;
165
+ price: number;
166
+ }[];
167
+ /** DocWagon contract (utilities/docwagon.ts). Optional -- uninsured. */
168
+ docwagon?: {
169
+ tier: string;
170
+ resuscsLeft: number;
171
+ homecomingsLeft: number;
172
+ };
173
+ /** Leadership's Command action (commands/lead.ts): the recruit riding
174
+ * the next run, and who refused you at what loyalty. */
175
+ enlistedAlly?: {
176
+ name: string;
177
+ description: string;
178
+ combat: Record<string, unknown>;
179
+ };
180
+ commandRefusals?: Record<string, number>;
159
181
  fencedCategories: string[];
160
182
  /** Rumor pool stays seed-sourced; only the cursor + active persist. */
161
183
  rumorIndex: number;
@@ -21,6 +21,9 @@ export interface IStrikeProfile {
21
21
  * physical -- knockouts, not kills (though stun overflow still bleeds
22
22
  * into the body). */
23
23
  dealStun?: boolean;
24
+ /** A SPELL strike (cast.ts): an Awakened defender's COUNTERSPELLING
25
+ * (canon: its own Magic skill) rides their defense against it. */
26
+ isSpell?: boolean;
24
27
  }
25
28
  /**
26
29
  * The shared combat-exchange engine, extracted from AttackCommand so spell
@@ -162,14 +162,26 @@ export class CombatExchange {
162
162
  freezeLines.push(` ${c.name} FREEZES as it starts -- initiative halved this exchange.`);
163
163
  return Math.ceil(pool / 2);
164
164
  };
165
+ // RALLY (SR5e p.141-142, commands/lead.ts): a banked +score bonus,
166
+ // consumed on this contest -- initiative SCORE maps to hits here.
167
+ const rallyOf = (c) => {
168
+ const bonus = c.rallyInitBonus ?? 0;
169
+ if (bonus > 0)
170
+ c.rallyInitBonus = 0;
171
+ return bonus;
172
+ };
165
173
  const aggRoll = rollPool(initiativePool(aggressor));
166
174
  const defRoll = rollPool(initiativePool(defender));
167
- const aggressorFirst = aggRoll.hits >= defRoll.hits;
175
+ const aggRally = rallyOf(aggressor);
176
+ const defRally = rallyOf(defender);
177
+ const aggHits = aggRoll.hits + aggRally;
178
+ const defHits = defRoll.hits + defRally;
179
+ const aggressorFirst = aggHits >= defHits;
168
180
  const first = aggressorFirst ? aggressor : defender;
169
181
  const second = aggressorFirst ? defender : aggressor;
170
- const net = Math.abs(aggRoll.hits - defRoll.hits);
182
+ const net = Math.abs(aggHits - defHits);
171
183
  const extraStriker = net >= 3 ? first : null;
172
- this.logger.write(`Initiative: ${aggressor.name} ${aggRoll.hits} vs ${defender.name} ${defRoll.hits} -- ${first.name} first${extraStriker ? ' with an extra strike' : ''}.`);
184
+ this.logger.write(`Initiative: ${aggressor.name} ${aggHits} vs ${defender.name} ${defHits} -- ${first.name} first${extraStriker ? ' with an extra strike' : ''}.`);
173
185
  // Readability split: the dice anatomy is Mechanics-ticker material
174
186
  // (metaLines); the world log keeps only what the player FEELS --
175
187
  // who froze, who moves first (lines). Callers rebuild AI-history
@@ -178,8 +190,8 @@ export class CombatExchange {
178
190
  // hard to read) -- each carries its own right-justified wave.
179
191
  const metaLines = [
180
192
  `Initiative:`,
181
- ` ${aggressor.name}: ${formatRoll(aggRoll)}`,
182
- ` ${defender.name}: ${formatRoll(defRoll)}`,
193
+ ` ${aggressor.name}: ${formatRoll(aggRoll)}${aggRally > 0 ? ` +${aggRally} rally` : ''}`,
194
+ ` ${defender.name}: ${formatRoll(defRoll)}${defRally > 0 ? ` +${defRally} rally` : ''}`,
183
195
  ];
184
196
  const lines = [
185
197
  ...freezeLines,
@@ -298,12 +310,25 @@ export class CombatExchange {
298
310
  strike.weaponItem.fireRound();
299
311
  this.logger.write(`${attacker.name} fired ${strike.weaponItem.name} (${strike.weaponItem.ammo} rounds left).`);
300
312
  }
301
- const attack = rollPool(strike.pool);
302
- const defense = rollPool(defender.getDefensePool());
313
+ // DIRECT / INSPIRE (SR5e p.141-142, commands/lead.ts): banked
314
+ // teamwork dice, consumed on this roll.
315
+ const directed = attacker.directedDice ?? 0;
316
+ if (directed > 0)
317
+ attacker.directedDice = 0;
318
+ const inspired = defender.inspiredGuardDice ?? 0;
319
+ if (inspired > 0)
320
+ defender.inspiredGuardDice = 0;
321
+ // COUNTERSPELLING (canon: the Awakened parry): only against spell
322
+ // strikes, only for a caster defender.
323
+ const counterspell = strike.isSpell && typeof defender.isCaster === 'function' && defender.isCaster()
324
+ ? defender.skillRating('counterspelling')
325
+ : 0;
326
+ const attack = rollPool(strike.pool + directed);
327
+ const defense = rollPool(defender.getDefensePool() + inspired + counterspell);
303
328
  const header = `${attacker.name} attacks ${defender.name} ${strike.label}`;
304
329
  meta.push(`${header}:`);
305
- meta.push(` Attack: ${formatRoll(attack)}`);
306
- meta.push(` Defense: ${formatRoll(defense)}`);
330
+ meta.push(` Attack: ${formatRoll(attack)}${directed > 0 ? ` (+${directed} directed)` : ''}`);
331
+ meta.push(` Defense: ${formatRoll(defense)}${inspired > 0 ? ` (+${inspired} inspired)` : ''}${counterspell > 0 ? ` (+${counterspell} counterspell)` : ''}`);
307
332
  const dryWarning = strike.weaponItem?.isFirearm() && strike.weaponItem.ammo === 0
308
333
  ? [` ${attacker.name}'s ${strike.weaponItem.name} clicks empty -- that was the last round.`]
309
334
  : [];
@@ -480,6 +505,13 @@ export class CombatExchange {
480
505
  if (isPlayer) {
481
506
  const lines = [opts?.fallLine ?? `You collapse. The last thing you see is ${killer.name} standing over you.`];
482
507
  await this.announce(lines);
508
+ // DOCWAGON RESUSCITATION (SR5e p.450, Game.tryDocwagonRescue): the
509
+ // sanctioned carve-out to permadeath (player ruling) -- a contract
510
+ // holder's death is a billing event, not a game over. The rescue
511
+ // narrates, bills, and medevacs; we just don't end the game.
512
+ if ((opts?.fatal ?? true) && this.scene.ownerGame?.tryDocwagonRescue('resusc')) {
513
+ return ['', ...lines];
514
+ }
483
515
  // Same flow as kill.ts and a fatal bypass: freeze input, self-announce
484
516
  // so the reason stays on screen, and wait for ctrl-c ("You died!").
485
517
  const gameOverLine = opts?.gameOverLine ?? `💀 GAME OVER: ${fallen.name} was gunned down by ${killer.name}.`;