@maka/maka-cli 5.3.16 → 5.4.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 (220) hide show
  1. package/bundle/typescript/package.json +1 -1
  2. package/bundle/typescript/src/commands/create.js +1 -1
  3. package/bundle/typescript/src/commands/create.js.map +1 -1
  4. package/bundle/typescript/src/commands/game/sideQuest/archetypes.d.ts +43 -0
  5. package/bundle/typescript/src/commands/game/sideQuest/archetypes.js +131 -0
  6. package/bundle/typescript/src/commands/game/sideQuest/archetypes.js.map +1 -0
  7. package/bundle/typescript/src/commands/game/sideQuest/commands/assense.d.ts +14 -0
  8. package/bundle/typescript/src/commands/game/sideQuest/commands/assense.js +79 -0
  9. package/bundle/typescript/src/commands/game/sideQuest/commands/assense.js.map +1 -0
  10. package/bundle/typescript/src/commands/game/sideQuest/commands/attack.d.ts +42 -0
  11. package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js +224 -0
  12. package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js.map +1 -0
  13. package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.d.ts +28 -0
  14. package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js +101 -0
  15. package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js.map +1 -0
  16. package/bundle/typescript/src/commands/game/sideQuest/commands/call.d.ts +19 -0
  17. package/bundle/typescript/src/commands/game/sideQuest/commands/call.js +149 -0
  18. package/bundle/typescript/src/commands/game/sideQuest/commands/call.js.map +1 -0
  19. package/bundle/typescript/src/commands/game/sideQuest/commands/cast.d.ts +58 -0
  20. package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js +378 -0
  21. package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js.map +1 -0
  22. package/bundle/typescript/src/commands/game/sideQuest/commands/command-registry.d.ts +11 -1
  23. package/bundle/typescript/src/commands/game/sideQuest/commands/command-registry.js +8 -3
  24. package/bundle/typescript/src/commands/game/sideQuest/commands/command-registry.js.map +1 -1
  25. package/bundle/typescript/src/commands/game/sideQuest/commands/command.d.ts +6 -0
  26. package/bundle/typescript/src/commands/game/sideQuest/commands/command.js +4 -0
  27. package/bundle/typescript/src/commands/game/sideQuest/commands/command.js.map +1 -1
  28. package/bundle/typescript/src/commands/game/sideQuest/commands/drone.d.ts +15 -0
  29. package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js +60 -0
  30. package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js.map +1 -0
  31. package/bundle/typescript/src/commands/game/sideQuest/commands/edge.d.ts +13 -0
  32. package/bundle/typescript/src/commands/game/sideQuest/commands/edge.js +31 -0
  33. package/bundle/typescript/src/commands/game/sideQuest/commands/edge.js.map +1 -0
  34. package/bundle/typescript/src/commands/game/sideQuest/commands/end-call.d.ts +6 -0
  35. package/bundle/typescript/src/commands/game/sideQuest/commands/end-call.js +27 -0
  36. package/bundle/typescript/src/commands/game/sideQuest/commands/end-call.js.map +1 -0
  37. package/bundle/typescript/src/commands/game/sideQuest/commands/equip.d.ts +4 -0
  38. package/bundle/typescript/src/commands/game/sideQuest/commands/equip.js +40 -0
  39. package/bundle/typescript/src/commands/game/sideQuest/commands/equip.js.map +1 -1
  40. package/bundle/typescript/src/commands/game/sideQuest/commands/equipment.d.ts +13 -0
  41. package/bundle/typescript/src/commands/game/sideQuest/commands/equipment.js +43 -0
  42. package/bundle/typescript/src/commands/game/sideQuest/commands/equipment.js.map +1 -0
  43. package/bundle/typescript/src/commands/game/sideQuest/commands/give.js +7 -1
  44. package/bundle/typescript/src/commands/game/sideQuest/commands/give.js.map +1 -1
  45. package/bundle/typescript/src/commands/game/sideQuest/commands/go.d.ts +11 -0
  46. package/bundle/typescript/src/commands/game/sideQuest/commands/go.js +115 -4
  47. package/bundle/typescript/src/commands/game/sideQuest/commands/go.js.map +1 -1
  48. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.d.ts +25 -0
  49. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +176 -0
  50. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js.map +1 -0
  51. package/bundle/typescript/src/commands/game/sideQuest/commands/inv.d.ts +12 -0
  52. package/bundle/typescript/src/commands/game/sideQuest/commands/inv.js +39 -0
  53. package/bundle/typescript/src/commands/game/sideQuest/commands/inv.js.map +1 -0
  54. package/bundle/typescript/src/commands/game/sideQuest/commands/jack.d.ts +17 -0
  55. package/bundle/typescript/src/commands/game/sideQuest/commands/jack.js +64 -0
  56. package/bundle/typescript/src/commands/game/sideQuest/commands/jack.js.map +1 -0
  57. package/bundle/typescript/src/commands/game/sideQuest/commands/kill.d.ts +10 -3
  58. package/bundle/typescript/src/commands/game/sideQuest/commands/kill.js +11 -33
  59. package/bundle/typescript/src/commands/game/sideQuest/commands/kill.js.map +1 -1
  60. package/bundle/typescript/src/commands/game/sideQuest/commands/look.js +35 -4
  61. package/bundle/typescript/src/commands/game/sideQuest/commands/look.js.map +1 -1
  62. package/bundle/typescript/src/commands/game/sideQuest/commands/map.d.ts +12 -0
  63. package/bundle/typescript/src/commands/game/sideQuest/commands/map.js +46 -0
  64. package/bundle/typescript/src/commands/game/sideQuest/commands/map.js.map +1 -0
  65. package/bundle/typescript/src/commands/game/sideQuest/commands/pan.d.ts +12 -0
  66. package/bundle/typescript/src/commands/game/sideQuest/commands/pan.js +55 -0
  67. package/bundle/typescript/src/commands/game/sideQuest/commands/pan.js.map +1 -0
  68. package/bundle/typescript/src/commands/game/sideQuest/commands/pick.d.ts +9 -0
  69. package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js +18 -0
  70. package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js.map +1 -0
  71. package/bundle/typescript/src/commands/game/sideQuest/commands/project.d.ts +19 -0
  72. package/bundle/typescript/src/commands/game/sideQuest/commands/project.js +54 -0
  73. package/bundle/typescript/src/commands/game/sideQuest/commands/project.js.map +1 -0
  74. package/bundle/typescript/src/commands/game/sideQuest/commands/read.js +6 -5
  75. package/bundle/typescript/src/commands/game/sideQuest/commands/read.js.map +1 -1
  76. package/bundle/typescript/src/commands/game/sideQuest/commands/reboot.d.ts +12 -0
  77. package/bundle/typescript/src/commands/game/sideQuest/commands/reboot.js +52 -0
  78. package/bundle/typescript/src/commands/game/sideQuest/commands/reboot.js.map +1 -0
  79. package/bundle/typescript/src/commands/game/sideQuest/commands/reload.d.ts +14 -0
  80. package/bundle/typescript/src/commands/game/sideQuest/commands/reload.js +40 -0
  81. package/bundle/typescript/src/commands/game/sideQuest/commands/reload.js.map +1 -0
  82. package/bundle/typescript/src/commands/game/sideQuest/commands/rest.d.ts +15 -0
  83. package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js +59 -0
  84. package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js.map +1 -0
  85. package/bundle/typescript/src/commands/game/sideQuest/commands/say.js +23 -2
  86. package/bundle/typescript/src/commands/game/sideQuest/commands/say.js.map +1 -1
  87. package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +20 -3
  88. package/bundle/typescript/src/commands/game/sideQuest/commands/search.js.map +1 -1
  89. package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.d.ts +12 -0
  90. package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js +49 -0
  91. package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js.map +1 -0
  92. package/bundle/typescript/src/commands/game/sideQuest/commands/silent.d.ts +13 -0
  93. package/bundle/typescript/src/commands/game/sideQuest/commands/silent.js +50 -0
  94. package/bundle/typescript/src/commands/game/sideQuest/commands/silent.js.map +1 -0
  95. package/bundle/typescript/src/commands/game/sideQuest/commands/solve.js +32 -11
  96. package/bundle/typescript/src/commands/game/sideQuest/commands/solve.js.map +1 -1
  97. package/bundle/typescript/src/commands/game/sideQuest/commands/spells.d.ts +14 -0
  98. package/bundle/typescript/src/commands/game/sideQuest/commands/spells.js +65 -0
  99. package/bundle/typescript/src/commands/game/sideQuest/commands/spells.js.map +1 -0
  100. package/bundle/typescript/src/commands/game/sideQuest/commands/stance.d.ts +12 -0
  101. package/bundle/typescript/src/commands/game/sideQuest/commands/stance.js +41 -0
  102. package/bundle/typescript/src/commands/game/sideQuest/commands/stance.js.map +1 -0
  103. package/bundle/typescript/src/commands/game/sideQuest/commands/subdue.d.ts +21 -0
  104. package/bundle/typescript/src/commands/game/sideQuest/commands/subdue.js +43 -0
  105. package/bundle/typescript/src/commands/game/sideQuest/commands/subdue.js.map +1 -0
  106. package/bundle/typescript/src/commands/game/sideQuest/commands/surrender.d.ts +20 -0
  107. package/bundle/typescript/src/commands/game/sideQuest/commands/surrender.js +45 -0
  108. package/bundle/typescript/src/commands/game/sideQuest/commands/surrender.js.map +1 -0
  109. package/bundle/typescript/src/commands/game/sideQuest/commands/take.d.ts +8 -0
  110. package/bundle/typescript/src/commands/game/sideQuest/commands/take.js +80 -1
  111. package/bundle/typescript/src/commands/game/sideQuest/commands/take.js.map +1 -1
  112. package/bundle/typescript/src/commands/game/sideQuest/commands/unlock.js +1 -0
  113. package/bundle/typescript/src/commands/game/sideQuest/commands/unlock.js.map +1 -1
  114. package/bundle/typescript/src/commands/game/sideQuest/commands/use.d.ts +24 -0
  115. package/bundle/typescript/src/commands/game/sideQuest/commands/use.js +101 -1
  116. package/bundle/typescript/src/commands/game/sideQuest/commands/use.js.map +1 -1
  117. package/bundle/typescript/src/commands/game/sideQuest/factories/item-factory.js +2 -1
  118. package/bundle/typescript/src/commands/game/sideQuest/factories/item-factory.js.map +1 -1
  119. package/bundle/typescript/src/commands/game/sideQuest/factories/npc-factory.js +7 -2
  120. package/bundle/typescript/src/commands/game/sideQuest/factories/npc-factory.js.map +1 -1
  121. package/bundle/typescript/src/commands/game/sideQuest/factories/puzzle-factory.js +2 -1
  122. package/bundle/typescript/src/commands/game/sideQuest/factories/puzzle-factory.js.map +1 -1
  123. package/bundle/typescript/src/commands/game/sideQuest/factories/room-factory.js +2 -0
  124. package/bundle/typescript/src/commands/game/sideQuest/factories/room-factory.js.map +1 -1
  125. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.d.ts +8 -1
  126. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js +138 -21
  127. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js.map +1 -1
  128. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.d.ts +119 -0
  129. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.js +816 -0
  130. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.js.map +1 -0
  131. package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +70 -9
  132. package/bundle/typescript/src/commands/game/sideQuest/game.js +403 -164
  133. package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
  134. package/bundle/typescript/src/commands/game/sideQuest/minigames/hack-minigame.d.ts +16 -0
  135. package/bundle/typescript/src/commands/game/sideQuest/minigames/hack-minigame.js +101 -0
  136. package/bundle/typescript/src/commands/game/sideQuest/minigames/hack-minigame.js.map +1 -0
  137. package/bundle/typescript/src/commands/game/sideQuest/minigames/lockpick-minigame.d.ts +15 -0
  138. package/bundle/typescript/src/commands/game/sideQuest/minigames/lockpick-minigame.js +95 -0
  139. package/bundle/typescript/src/commands/game/sideQuest/minigames/lockpick-minigame.js.map +1 -0
  140. package/bundle/typescript/src/commands/game/sideQuest/models/door.d.ts +10 -0
  141. package/bundle/typescript/src/commands/game/sideQuest/models/door.js +15 -2
  142. package/bundle/typescript/src/commands/game/sideQuest/models/door.js.map +1 -1
  143. package/bundle/typescript/src/commands/game/sideQuest/models/inventory.d.ts +10 -0
  144. package/bundle/typescript/src/commands/game/sideQuest/models/inventory.js +54 -4
  145. package/bundle/typescript/src/commands/game/sideQuest/models/inventory.js.map +1 -1
  146. package/bundle/typescript/src/commands/game/sideQuest/models/item.d.ts +41 -0
  147. package/bundle/typescript/src/commands/game/sideQuest/models/item.js +106 -3
  148. package/bundle/typescript/src/commands/game/sideQuest/models/item.js.map +1 -1
  149. package/bundle/typescript/src/commands/game/sideQuest/models/npc.d.ts +25 -0
  150. package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +220 -47
  151. package/bundle/typescript/src/commands/game/sideQuest/models/npc.js.map +1 -1
  152. package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +213 -0
  153. package/bundle/typescript/src/commands/game/sideQuest/models/player.js +525 -9
  154. package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
  155. package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.d.ts +75 -1
  156. package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.js +174 -6
  157. package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.js.map +1 -1
  158. package/bundle/typescript/src/commands/game/sideQuest/models/room.d.ts +20 -0
  159. package/bundle/typescript/src/commands/game/sideQuest/models/room.js +39 -18
  160. package/bundle/typescript/src/commands/game/sideQuest/models/room.js.map +1 -1
  161. package/bundle/typescript/src/commands/game/sideQuest/models/scene.d.ts +69 -0
  162. package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +175 -2
  163. package/bundle/typescript/src/commands/game/sideQuest/models/scene.js.map +1 -1
  164. package/bundle/typescript/src/commands/game/sideQuest/scenes/scene1.json +80 -1
  165. package/bundle/typescript/src/commands/game/sideQuest/scenes/scene2.json +7 -2
  166. package/bundle/typescript/src/commands/game/sideQuest/types/player-equipment.d.ts +1 -0
  167. package/bundle/typescript/src/commands/game/sideQuest/types/seed/exit-seed.d.ts +1 -0
  168. package/bundle/typescript/src/commands/game/sideQuest/types/seed/item-seed.d.ts +1 -0
  169. package/bundle/typescript/src/commands/game/sideQuest/types/seed/npc-seed.d.ts +1 -0
  170. package/bundle/typescript/src/commands/game/sideQuest/types/seed/player-seed.d.ts +34 -0
  171. package/bundle/typescript/src/commands/game/sideQuest/types/seed/puzzle-seed.d.ts +15 -1
  172. package/bundle/typescript/src/commands/game/sideQuest/types/seed/room-seed.d.ts +3 -0
  173. package/bundle/typescript/src/commands/game/sideQuest/types/seed/scene-seed.d.ts +5 -0
  174. package/bundle/typescript/src/commands/game/sideQuest/types/seed/win-condition-seed.d.ts +15 -0
  175. package/bundle/typescript/src/commands/game/sideQuest/types/seed/win-condition-seed.js +2 -0
  176. package/bundle/typescript/src/commands/game/sideQuest/types/seed/win-condition-seed.js.map +1 -0
  177. package/bundle/typescript/src/commands/game/sideQuest/types/shared/abstracts.d.ts +1 -0
  178. package/bundle/typescript/src/commands/game/sideQuest/types/shared/abstracts.js +5 -0
  179. package/bundle/typescript/src/commands/game/sideQuest/types/shared/abstracts.js.map +1 -1
  180. package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.d.ts +1 -0
  181. package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.js +1 -0
  182. package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.js.map +1 -1
  183. package/bundle/typescript/src/commands/game/sideQuest/ui.d.ts +3 -0
  184. package/bundle/typescript/src/commands/game/sideQuest/ui.js +95 -5
  185. package/bundle/typescript/src/commands/game/sideQuest/ui.js.map +1 -1
  186. package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.d.ts +60 -0
  187. package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.js +138 -0
  188. package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.js.map +1 -0
  189. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.d.ts +143 -0
  190. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +328 -0
  191. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js.map +1 -0
  192. package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.d.ts +2 -0
  193. package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.js +12 -0
  194. package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.js.map +1 -0
  195. package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.d.ts +22 -0
  196. package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.js +34 -0
  197. package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.js.map +1 -0
  198. package/bundle/typescript/src/commands/game/sideQuest/utilities/fuzzy-match.d.ts +36 -0
  199. package/bundle/typescript/src/commands/game/sideQuest/utilities/fuzzy-match.js +92 -0
  200. package/bundle/typescript/src/commands/game/sideQuest/utilities/fuzzy-match.js.map +1 -0
  201. package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.d.ts +44 -0
  202. package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js +193 -0
  203. package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js.map +1 -0
  204. package/bundle/typescript/src/commands/game/sideQuest/utilities/pursuit.d.ts +16 -0
  205. package/bundle/typescript/src/commands/game/sideQuest/utilities/pursuit.js +36 -0
  206. package/bundle/typescript/src/commands/game/sideQuest/utilities/pursuit.js.map +1 -0
  207. package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js +54 -7
  208. package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js.map +1 -1
  209. package/bundle/typescript/src/commands/set/global.sub.cmd.js +59 -5
  210. package/bundle/typescript/src/commands/set/global.sub.cmd.js.map +1 -1
  211. package/bundle/typescript/src/tools/ai/ai.class.d.ts +35 -0
  212. package/bundle/typescript/src/tools/ai/ai.class.js +112 -0
  213. package/bundle/typescript/src/tools/ai/ai.class.js.map +1 -0
  214. package/bundle/typescript/src/tools/fsi/fsi.interfaces.d.ts +1 -0
  215. package/bundle/typescript/src/tools/fsi/fsi.interfaces.js.map +1 -1
  216. package/bundle/typescript/src/tools/https/https.class.d.ts +2 -1
  217. package/bundle/typescript/src/tools/https/https.class.js +43 -0
  218. package/bundle/typescript/src/tools/https/https.class.js.map +1 -1
  219. package/bundle/typescript/src/tools/https/https.interface.d.ts +8 -0
  220. package/package.json +1 -1
@@ -0,0 +1,224 @@
1
+ import { Command } from './command.js';
2
+ import { CombatExchange } from '../utilities/combat-exchange.js';
3
+ import { fuzzyPickName } from '../utilities/fuzzy-match.js';
4
+ import { describeFledTarget } from '../utilities/pursuit.js';
5
+ import { shellsInRoom, resolveBodyAttack } from '../utilities/planes.js';
6
+ /**
7
+ * Shadowrun-style dice-pool combat, one full exchange per command:
8
+ *
9
+ * 1. Attacker rolls Agility + combat skill + weapon bonus, opposed by the
10
+ * defender's Reaction. Net hits land the blow.
11
+ * 2. Damage = weapon DV + net hits, soaked by Body + armor (each soak hit
12
+ * cancels one box).
13
+ * 3. Whatever gets through fills the defender's condition monitor; a full
14
+ * track is death.
15
+ * 4. If the defender survives, they immediately strike back with the same
16
+ * math reversed -- starting a fight means eating one, so a fight is a
17
+ * real decision, not a free action.
18
+ *
19
+ * Wound modifiers (-1 die per 3 boxes taken) apply to every pool on both
20
+ * sides, so a fight tilts as it goes. Pools/derivations live on Player
21
+ * (getAttackPool() etc.); narration pacing, strike resolution, and death
22
+ * handling live in CombatExchange (shared with spell combat -- see
23
+ * cast.ts). This command only orchestrates the weapon exchange.
24
+ */
25
+ export class AttackCommand extends Command {
26
+ static verb = 'attack';
27
+ static description = 'Attack someone in the room -- dice-pool combat with your equipped weapon. They WILL hit back.';
28
+ async execute(args = []) {
29
+ if (!args || args.length === 0) {
30
+ return 'Who do you want to attack? Try: `attack <name>`';
31
+ }
32
+ // The dead (and the unconscious) don't fight. Covers a downed player
33
+ // (who stays in the scene -- endGame only freezes input) and a downed
34
+ // NPC whose in-flight AI loop is still issuing commands after removal
35
+ // from the scene.
36
+ if (this.actor.isIncapacitated()) {
37
+ return `You're down. The fight is over.`;
38
+ }
39
+ const room = this.actor.currentLocation;
40
+ if (!room) {
41
+ return 'You are not in a valid location to fight anyone.';
42
+ }
43
+ // Same fuzzy matching "take" uses (see utilities/fuzzy-match.ts) --
44
+ // punctuation, case, typos, and partial names all resolve, but an
45
+ // ambiguous guess deliberately misses rather than shooting someone
46
+ // at random. You can only fight what shares your plane: meat can't
47
+ // touch a Matrix persona or an astral form, and vice versa.
48
+ const targetName = args.join(' ');
49
+ const candidates = this.scene.getActorsInRoom(room).filter(a => a !== this.actor && a.plane === this.actor.plane);
50
+ const pickedName = fuzzyPickName(targetName, candidates.map(a => a.name));
51
+ const target = pickedName ? candidates.find(a => a.name === pickedName) : undefined;
52
+ if (!target) {
53
+ // An empty body IS attackable from the meat plane -- one unanswered
54
+ // strike, and its owner gets slammed back into it (see planes.ts).
55
+ if (this.actor.plane === 'meat') {
56
+ const shells = shellsInRoom(this.scene, room);
57
+ const shellPick = fuzzyPickName(targetName, shells.map(s => s.name));
58
+ const shell = shellPick ? shells.find(s => s.name === shellPick) : undefined;
59
+ if (shell) {
60
+ const lines = resolveBodyAttack(this.scene, this.actor, shell);
61
+ const exchange = new CombatExchange(this.scene, this.logger, this.actor);
62
+ await exchange.announce(lines);
63
+ if (shell.isDown()) {
64
+ const death = await exchange.resolveDeath(shell, this.actor, {
65
+ fallLine: `${shell.name}'s body never wakes -- whatever came rushing back arrived too late. Their gear scatters across ${room.name}.`,
66
+ });
67
+ lines.push(...death);
68
+ }
69
+ return this.actor === this.scene.getPlayer() ? '' : lines.join('\n');
70
+ }
71
+ }
72
+ // If they exist but fled the room, say WHERE and how to chase --
73
+ // this result is what lands in a pursuing NPC's AI history, and
74
+ // without the explicit direction a hostile NPC just spams failed
75
+ // attacks at the empty room instead of following (real session:
76
+ // dozens of them while the player ping-ponged between two rooms).
77
+ const fled = describeFledTarget(this.scene, room, targetName);
78
+ if (fled)
79
+ return fled;
80
+ const present = candidates.map(a => a.name).join(', ') || 'no one';
81
+ return `You don't see anyone named "${targetName}" here to attack. Present: ${present}.`;
82
+ }
83
+ // The dead can't be fought either. A downed NPC is removed from the
84
+ // scene, so a still-present downed target can only be the dead player
85
+ // -- found via a real session where the killer's AI loop, with nothing
86
+ // gating this, beat the player's corpse for a full minute, re-running
87
+ // the whole death sequence every swing. The plain-text result below is
88
+ // what actually lands in the attacker's AI history and tells it the
89
+ // fight is over.
90
+ if (target.isIncapacitated()) {
91
+ return `${target.name} is already down. The fight is over -- there's nothing left to attack.`;
92
+ }
93
+ // One exchange at a time per combatant: overlapping AI loops firing
94
+ // parallel exchanges once double-killed a target (two death events,
95
+ // two narrations). The player is refused cleanly (they can just retype
96
+ // in the lull); an NPC's attack instead WAITS for the lull and re-runs
97
+ // -- discarding it meant NPCs never got a single action off, because
98
+ // their AI always reacts while the player's exchange is still beating
99
+ // out (see CombatExchange.waitForLull).
100
+ if (this.actor.inExchange || target.inExchange) {
101
+ if (this.actor === this.scene.getPlayer()) {
102
+ return this.actor.inExchange
103
+ ? `You're already mid-exchange -- let it resolve.`
104
+ : `${target.name} is already trading fire -- wait for that exchange to resolve.`;
105
+ }
106
+ if (this.actor.waitingForLull) {
107
+ return `You're already waiting for an opening -- one move at a time.`;
108
+ }
109
+ this.actor.waitingForLull = true;
110
+ try {
111
+ const settled = await CombatExchange.waitForLull([this.actor, target]);
112
+ if (!settled) {
113
+ return `You're already mid-exchange -- let it resolve.`;
114
+ }
115
+ this.logger.write(`${this.actor.name}'s attack waited out an exchange and now retries.`);
116
+ return await this.execute(args);
117
+ }
118
+ finally {
119
+ this.actor.waitingForLull = false;
120
+ }
121
+ }
122
+ // A deliberately INITIATED attack with a dry gun is blocked with a
123
+ // reload prompt -- the involuntary strikes (retaliation) auto-fall-back
124
+ // to fists instead (see CombatExchange.weaponProfile).
125
+ const weaponItem = this.actor.equipment.rightHand?.weapon ?? this.actor.equipment.leftHand?.weapon;
126
+ if (this.usesEquippedWeapon() && weaponItem?.isFirearm() && weaponItem.jammed) {
127
+ return `The ${weaponItem.name}'s smartlink is LOCKED -- someone cracked your PAN. "reboot" to clear it, or go in swinging.`;
128
+ }
129
+ if (this.usesEquippedWeapon() && weaponItem?.isFirearm() && weaponItem.ammo <= 0) {
130
+ return `Click -- the ${weaponItem.name} is dry. "reload" (needs ammunition), or go in swinging with something else.`;
131
+ }
132
+ const exchange = new CombatExchange(this.scene, this.logger, this.actor);
133
+ const report = [];
134
+ // Movement locks for the duration of the exchange when the player is a
135
+ // combatant (see go.ts) -- the try/finally is exception safety so a
136
+ // crash mid-exchange can never leave the player permanently rooted.
137
+ exchange.beginExchange(target);
138
+ try {
139
+ this.actor.performAction(this.actionVerb(), target.name);
140
+ this.scene.addWorldEvent(`${this.actor.name} ${this.actionVerb()} ${target.name} in ${room.name}.`);
141
+ // ---- Initiative decides who acts first (see rollInitiative) ----
142
+ const init = exchange.rollInitiative(this.actor, target);
143
+ report.push(...init.lines);
144
+ await exchange.announce(init.lines);
145
+ // ---- The two strikes, in initiative order. The aggressor's strike
146
+ // uses this command's profile (weapon, or subdue's stun hold); the
147
+ // defender's answer is always their own lethal weapon. Being
148
+ // preempted and dropped means your swing never happens. ----
149
+ for (const striker of [init.first, init.second]) {
150
+ if (striker.isIncapacitated())
151
+ continue;
152
+ const defender = striker === this.actor ? target : this.actor;
153
+ if (defender.isIncapacitated())
154
+ break;
155
+ // A mid-exchange forced dump (cold-sim persona crash) yanks a
156
+ // combatant off this plane -- the fight is over, not continued
157
+ // across realities.
158
+ if (striker.plane !== defender.plane)
159
+ break;
160
+ const profile = striker === this.actor ? this.strikeProfile(exchange, target) : undefined;
161
+ const prefix = striker === init.second ? [`${striker.name} retaliates!`] : [];
162
+ const strike = [...prefix, ...exchange.resolveStrike(striker, defender, profile)];
163
+ report.push('', ...strike);
164
+ await exchange.announce(strike);
165
+ const takedown = await this.resolveTakedown(exchange, defender, striker);
166
+ if (takedown) {
167
+ report.push(...takedown);
168
+ return await exchange.finishExchange(report);
169
+ }
170
+ }
171
+ // ---- The initiative pass: a decisive winner presses the advantage
172
+ // with one extra strike. ----
173
+ if (init.extraStriker && !init.extraStriker.isIncapacitated()) {
174
+ const defender = init.extraStriker === this.actor ? target : this.actor;
175
+ if (!defender.isIncapacitated() && init.extraStriker.plane === defender.plane) {
176
+ const profile = init.extraStriker === this.actor ? this.strikeProfile(exchange, target) : undefined;
177
+ const strike = [`${init.extraStriker.name} presses the advantage!`, ...exchange.resolveStrike(init.extraStriker, defender, profile)];
178
+ report.push('', ...strike);
179
+ await exchange.announce(strike);
180
+ const takedown = await this.resolveTakedown(exchange, defender, init.extraStriker);
181
+ if (takedown)
182
+ report.push(...takedown);
183
+ }
184
+ }
185
+ return await exchange.finishExchange(report);
186
+ }
187
+ finally {
188
+ this.scene.endPlayerExchange();
189
+ exchange.releaseExchange();
190
+ }
191
+ }
192
+ /** subdue.ts overrides to false: its strike ignores the equipped gun entirely. */
193
+ usesEquippedWeapon() {
194
+ return true;
195
+ }
196
+ /** Overridden by subdue.ts to swap in a stun-dealing strike. undefined = weapon default. */
197
+ strikeProfile(_exchange, _target) {
198
+ return undefined;
199
+ }
200
+ /** The verb observers see ("attacks" / "moves to subdue"). */
201
+ actionVerb() {
202
+ return 'attacks';
203
+ }
204
+ /**
205
+ * Post-strike incapacitation, both flavors: a full physical track is a
206
+ * death; a full stun track (only reachable via a stun-dealing strike,
207
+ * or its overflow) is a live capture. Returns the narration, or null if
208
+ * the fallen still stands. Either combatant can be the one who falls --
209
+ * initiative order means the aggressor eats preemptive strikes too.
210
+ */
211
+ async resolveTakedown(exchange, fallen, killer) {
212
+ if (fallen.isDown()) {
213
+ return exchange.resolveDeath(fallen, killer);
214
+ }
215
+ if (fallen.isUnconscious()) {
216
+ return exchange.resolveDeath(fallen, killer, {
217
+ fatal: false,
218
+ fallLine: `${fallen.name} sags and drops -- out cold, breathing. Their gear scatters across ${fallen.currentLocation.name}.`,
219
+ });
220
+ }
221
+ return null;
222
+ }
223
+ }
224
+ //# sourceMappingURL=attack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attack.js","sourceRoot":"","sources":["../../../../../../../src/commands/game/sideQuest/commands/attack.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { Command, ICommand } from './command.js';
2
+ import { PuzzleBypassType } from '../types/seed/puzzle-seed.js';
3
+ /**
4
+ * Shared implementation for pick/hack/cast. The gate check (already
5
+ * solved? prerequisite met? does this puzzle even have this bypassType?)
6
+ * always runs first and synchronously, so a doomed attempt never makes the
7
+ * player sit through an interactive challenge they were never going to be
8
+ * allowed to attempt. Each concrete command differs in its verb,
9
+ * description, bypass type, the "nothing to do that to" message, and
10
+ * optionally how the outcome itself gets determined (see
11
+ * determineOutcome() -- defaults to a coin flip; lockpicking overrides it
12
+ * with a real minigame).
13
+ */
14
+ export declare abstract class BypassCommand extends Command implements ICommand {
15
+ protected abstract bypassType: PuzzleBypassType;
16
+ protected abstract noPuzzleMessage: string;
17
+ /** Default: a flat coin flip. Override for a type with its own minigame. */
18
+ protected determineOutcome(): Promise<boolean>;
19
+ /**
20
+ * There's no typed answer here to disambiguate with (unlike solve.ts),
21
+ * so with more than one barrier in the room the player has to name which
22
+ * one: a direction ("pick north", for a locked door) or a word from the
23
+ * barrier's own name ("pick maglock"). With only one candidate, a bare
24
+ * "pick" still works exactly as before.
25
+ */
26
+ private resolveTarget;
27
+ execute(args: string[]): Promise<string>;
28
+ }
@@ -0,0 +1,101 @@
1
+ import { Command } from './command.js';
2
+ /**
3
+ * Shared implementation for pick/hack/cast. The gate check (already
4
+ * solved? prerequisite met? does this puzzle even have this bypassType?)
5
+ * always runs first and synchronously, so a doomed attempt never makes the
6
+ * player sit through an interactive challenge they were never going to be
7
+ * allowed to attempt. Each concrete command differs in its verb,
8
+ * description, bypass type, the "nothing to do that to" message, and
9
+ * optionally how the outcome itself gets determined (see
10
+ * determineOutcome() -- defaults to a coin flip; lockpicking overrides it
11
+ * with a real minigame).
12
+ */
13
+ export class BypassCommand extends Command {
14
+ /** Default: a flat coin flip. Override for a type with its own minigame. */
15
+ async determineOutcome() {
16
+ return Math.random() < 0.5;
17
+ }
18
+ /**
19
+ * There's no typed answer here to disambiguate with (unlike solve.ts),
20
+ * so with more than one barrier in the room the player has to name which
21
+ * one: a direction ("pick north", for a locked door) or a word from the
22
+ * barrier's own name ("pick maglock"). With only one candidate, a bare
23
+ * "pick" still works exactly as before.
24
+ */
25
+ resolveTarget(args, targets) {
26
+ const raw = args.join(' ').trim().toLowerCase();
27
+ if (!raw) {
28
+ if (targets.length === 1)
29
+ return { target: targets[0] };
30
+ const options = targets.map(t => t.direction ?? t.label).join(', ');
31
+ return { error: `There's more than one thing to ${this.bypassType === 'lockpick' ? 'pick' : this.bypassType === 'hack' ? 'hack' : 'cast'} at here -- say which: ${options}.` };
32
+ }
33
+ if (this.isValidDirection(raw)) {
34
+ const match = targets.find(t => t.direction === raw);
35
+ return match ? { target: match } : { error: `There's nothing to bypass in that direction.` };
36
+ }
37
+ const matches = targets.filter(t => t.label.toLowerCase().includes(raw));
38
+ if (matches.length === 1)
39
+ return { target: matches[0] };
40
+ if (matches.length > 1)
41
+ return { error: `That could mean more than one thing here -- be more specific.` };
42
+ return { error: `There's nothing here called "${raw}".` };
43
+ }
44
+ async execute(args) {
45
+ const room = this.actor.currentLocation;
46
+ const targets = (room?.getPuzzleTargets() ?? []).filter(t => t.puzzle.bypassType === this.bypassType);
47
+ if (targets.length === 0) {
48
+ return this.noPuzzleMessage;
49
+ }
50
+ const { target, error } = this.resolveTarget(args, targets);
51
+ if (!target) {
52
+ return error ?? this.noPuzzleMessage;
53
+ }
54
+ const gate = target.puzzle.checkBypassGate(this.bypassType);
55
+ if (!gate.allowed) {
56
+ return gate.message ?? `You can't do that here.`;
57
+ }
58
+ const succeeded = await this.determineOutcome();
59
+ const result = target.puzzle.resolveBypass(this.bypassType, succeeded);
60
+ if (result.success) {
61
+ if (target.door) {
62
+ target.door.isLocked = false;
63
+ this.scene.updateExits(room);
64
+ this.scene.addWorldEvent(`${this.actor.name} bypassed (${this.bypassType}) the way ${target.direction} in ${room.name}.`);
65
+ }
66
+ else if (target.puzzle.rewardItem) {
67
+ this.actor.inventory.addItem(target.puzzle.rewardItem);
68
+ this.scene.addWorldEvent(`${this.actor.name} bypassed "${target.puzzle.name}" (${this.bypassType}) in ${room.name}.`);
69
+ this.scene.updateInventory(this.scene.getPlayer().inventory);
70
+ }
71
+ }
72
+ if (result.alarmed) {
73
+ // The consequence for a failed bypass: NPCs already listen for world
74
+ // events (see scene.ts's getWorldEventsSummary()), so this alone lets
75
+ // them react organically without a dedicated alarm/alert subsystem.
76
+ this.scene.addWorldEvent(`${this.actor.name}'s ${this.bypassType} attempt on "${target.puzzle.name}" in ${room.name} went loud.`);
77
+ }
78
+ this.logger.write(`${this.actor.name} attempted to ${this.bypassType} "${target.puzzle.name}": ${result.success ? 'success' : 'failure'}${result.alarmed ? ' (alarmed)' : ''}`);
79
+ this.actor.performAction(this.bypassType, result.message);
80
+ // A failed bypass is instant, unrecoverable death -- there's no partial
81
+ // credit for tripping an alarm or a ward's backlash mid-run. Reuses the
82
+ // same endGame() a player-kill already uses (see kill.ts): freezes
83
+ // input and self-announces directly to the screen, so the fatal reason
84
+ // stays on screen instead of vanishing the instant blessed's alternate
85
+ // buffer tears down. That announcement happens synchronously before
86
+ // this returns, so it renders BEFORE this command's own return value --
87
+ // found via a real session where an immediate process.exit() killed the
88
+ // alternate screen before the player had any chance to read why. Only
89
+ // ends the actual player's session: an NPC has no `game` in its context
90
+ // (see command.ts), so this is a no-op for an NPC's own failed attempt.
91
+ if (result.alarmed && this.game) {
92
+ await this.scene.endGame();
93
+ this.logger.logWithColor(result.message, 'red');
94
+ this.logger.logWithColor(`💀 GAME OVER -- your run ends here.`, 'red');
95
+ this.logger.logWithColor(`Press ctrl-c to quit.`, 'red');
96
+ return '';
97
+ }
98
+ return result.message;
99
+ }
100
+ }
101
+ //# sourceMappingURL=bypass-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bypass-command.js","sourceRoot":"","sources":["../../../../../../../src/commands/game/sideQuest/commands/bypass-command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { Command, ICommand } from './command.js';
2
+ export declare class CallCommand extends Command implements ICommand {
3
+ protected static verb: string;
4
+ protected static description: string;
5
+ execute(args: string[]): Promise<string>;
6
+ private hasEquippedCommlink;
7
+ private hasCommlink;
8
+ /**
9
+ * Multi-word names ("Mr. Johnson") shouldn't require the player to quote
10
+ * them just to call someone, and partial names shouldn't fail -- found
11
+ * via real sessions where "call Mr. Johnson" (period parsing), then
12
+ * "call mr" and "call Mr. J" (partials) all bounced. Tries progressively
13
+ * shorter word-prefixes of the input (longest first), resolving each
14
+ * through the same fuzzy matcher "take"/"attack"/"cast" use (punctuation
15
+ * and typo tolerant; an ambiguous fragment deliberately misses).
16
+ * Whatever's left over after the matched name becomes the message.
17
+ */
18
+ private resolveTargetAndMessage;
19
+ }
@@ -0,0 +1,149 @@
1
+ import { Command } from './command.js';
2
+ import { Category } from '../types/shared/item-enum.js';
3
+ import { parseArgsWithQuotes } from './talk.js';
4
+ import { CALL_ICON, CALL_COLOR } from '../utilities/comm-style.js';
5
+ import { FIXER_NAME } from '../factories/scene-seed-generator.js';
6
+ import { fuzzyPickName } from '../utilities/fuzzy-match.js';
7
+ export class CallCommand extends Command {
8
+ static verb = 'call';
9
+ static description = 'Call someone remotely over a commlink (no need to be in the same room; commlink must be equipped).';
10
+ async execute(args) {
11
+ if (!args || args.length === 0) {
12
+ return 'Usage: call <name> [message]';
13
+ }
14
+ const rawWords = parseArgsWithQuotes(args.join(' '));
15
+ if (!this.hasEquippedCommlink(this.actor)) {
16
+ return `You need to equip a commlink before you can call anyone.`;
17
+ }
18
+ // Running silent kills the broadcast both ways (see silent.ts).
19
+ if (this.actor.runningSilent) {
20
+ return `You're running silent -- your link isn't broadcasting. Go loud ("silent") before calling anyone.`;
21
+ }
22
+ // Hacked dark is a different kind of quiet (see hack.ts PAN attacks).
23
+ if (this.actor.arOffline) {
24
+ return `Your AR is CUT -- someone cracked your PAN and your link is dead air. "reboot" first.`;
25
+ }
26
+ const { target, remainingWords } = this.resolveTargetAndMessage(rawWords);
27
+ if (!target) {
28
+ return `You don't know anyone by the name "${rawWords[0]}" to call.`;
29
+ }
30
+ if (target === this.actor) {
31
+ return "You can't call yourself.";
32
+ }
33
+ if (!this.hasCommlink(target)) {
34
+ return `You don't get through -- ${target.name} doesn't seem to have a commlink.`;
35
+ }
36
+ if (target.runningSilent) {
37
+ return `You can't raise ${target.name} -- their link isn't broadcasting. They're running silent.`;
38
+ }
39
+ const message = remainingWords.join(' ');
40
+ const greeting = message || `(calling) You there, ${target.name}?`;
41
+ this.actor.performAction('calls', target.name);
42
+ this.logger.write(`${this.actor.name} calls ${target.name}: "${greeting}"`);
43
+ // Cleanly disconnect any stale prior call on either side before linking
44
+ // the new one -- otherwise a former partner would be left thinking
45
+ // they're still connected (their own "say" would keep routing to
46
+ // someone who's since moved on). See end-call.ts for the explicit
47
+ // version of this same disconnect.
48
+ if (this.actor.activeCallPartner && this.actor.activeCallPartner !== target) {
49
+ this.actor.activeCallPartner.activeCallPartner = undefined;
50
+ }
51
+ if (target.activeCallPartner && target.activeCallPartner !== this.actor) {
52
+ target.activeCallPartner.activeCallPartner = undefined;
53
+ }
54
+ // Symmetric: either side can now reach the other with an ordinary
55
+ // "say", not just this initial hail -- see say.ts. Stays connected
56
+ // until "end-call" (or a new call replaces it, per above).
57
+ this.actor.activeCallPartner = target;
58
+ target.activeCallPartner = this.actor;
59
+ // If the caller is holding this run's win-condition macguffin and
60
+ // calls its intended recipient, treat the call itself as the hand-off.
61
+ // Fixers work deals over comms, not face to face -- there's no in-world
62
+ // reason a physical "give" should ever be required for a job-giver
63
+ // who's meant to be unreachable in person. Resolved synchronously
64
+ // (doesn't depend on the NPC's reply), so it's reflected in this
65
+ // command's own return text right away.
66
+ let deliveryNote = '';
67
+ let deliveredItemName = '';
68
+ const wc = this.scene.getWinCondition();
69
+ if (wc?.type === 'give' && wc.toNpc?.toLowerCase() === target.name.toLowerCase()) {
70
+ const item = this.actor.inventory.getItem(wc.item);
71
+ if (item) {
72
+ const delivered = this.actor.inventory.transferItemTo(item.name, target.inventory);
73
+ if (delivered) {
74
+ this.scene.updateInventory(this.scene.getPlayer().inventory);
75
+ this.scene.checkWinCondition(item.name, target.name);
76
+ deliveryNote = ` You relay that you have the ${item.name} -- consider it delivered.`;
77
+ deliveredItemName = item.name;
78
+ }
79
+ }
80
+ }
81
+ // Fold the delivery into what the NPC actually hears (not just the
82
+ // player-facing confirmation above) -- otherwise an NPC whose reply is
83
+ // AI-generated has no way to know the job's already done, and keeps
84
+ // asking about it on a callback.
85
+ const heardMessage = deliveredItemName
86
+ ? `${greeting} [System note: ${this.actor.name} just delivered "${deliveredItemName}" to you over this call.]`
87
+ : greeting;
88
+ // Deliberately does NOT require sharing a room with the target -- the
89
+ // whole point of a commlink call is reaching someone you can't walk up
90
+ // to (a fixer working from a hideout you'll never set foot in, say).
91
+ // Deliberately NOT awaited: the target's reply (if any) comes from an
92
+ // AI round-trip and announces itself to the screen on its own once it
93
+ // arrives. Awaiting it here would block this confirmation behind that
94
+ // delay, so the reply could render before "You call X" does -- making
95
+ // it look like the call was never actually made.
96
+ void Promise.resolve(target.hear(this.actor, heardMessage)).catch(err => {
97
+ this.logger.error(`${target.name} failed to handle a call from ${this.actor.name}: ${err}`);
98
+ });
99
+ // Calling ${FIXER_NAME} once the job's done is what actually gets you
100
+ // the next one -- the in-fiction equivalent of typing "continue", which
101
+ // still works too as an explicit fallback. Deliberately checks the
102
+ // fixer's identity directly, NOT the win condition's toNpc: a job's
103
+ // "give" target is often a one-off contact ${FIXER_NAME} pointed the
104
+ // runner toward, not ${FIXER_NAME} himself (see the "broker, not always
105
+ // the destination" convention), so it's ${FIXER_NAME} specifically who
106
+ // lines up the next gig, regardless of who this job's delivery went to.
107
+ // Not gated on this call being the one that JUST completed the job:
108
+ // checking in on an already-finished job (e.g. delivered via "give"
109
+ // instead) triggers it just as well. Runs in the background (see
110
+ // Game.draftNextSceneInBackground()) -- job generation can take 20-40+
111
+ // seconds, and this confirmation shouldn't wait behind it.
112
+ if (this.game && this.scene.isCompleted() && target.name === FIXER_NAME) {
113
+ this.game.draftNextSceneInBackground();
114
+ }
115
+ return `{${CALL_COLOR}-fg}${CALL_ICON} You call ${target.name}: "${greeting}"${deliveryNote}{/${CALL_COLOR}-fg}`;
116
+ }
117
+ hasEquippedCommlink(actor) {
118
+ return actor.equipment.head?.commlink != null;
119
+ }
120
+ hasCommlink(actor) {
121
+ const inInventory = actor.inventory.getAllItems().some(item => item.category === Category.Commlink);
122
+ return inInventory || this.hasEquippedCommlink(actor);
123
+ }
124
+ /**
125
+ * Multi-word names ("Mr. Johnson") shouldn't require the player to quote
126
+ * them just to call someone, and partial names shouldn't fail -- found
127
+ * via real sessions where "call Mr. Johnson" (period parsing), then
128
+ * "call mr" and "call Mr. J" (partials) all bounced. Tries progressively
129
+ * shorter word-prefixes of the input (longest first), resolving each
130
+ * through the same fuzzy matcher "take"/"attack"/"cast" use (punctuation
131
+ * and typo tolerant; an ambiguous fragment deliberately misses).
132
+ * Whatever's left over after the matched name becomes the message.
133
+ */
134
+ resolveTargetAndMessage(words) {
135
+ // Includes the caller too, so "call <own name>" resolves into the
136
+ // actual "can't call yourself" check below rather than a confusing
137
+ // "don't know anyone" -- self is never a real target.
138
+ const candidates = [this.actor, ...this.scene.allActors];
139
+ const names = candidates.map(a => a.name);
140
+ for (let i = words.length; i >= 1; i--) {
141
+ const picked = fuzzyPickName(words.slice(0, i).join(' '), names);
142
+ if (picked) {
143
+ return { target: candidates.find(a => a.name === picked), remainingWords: words.slice(i) };
144
+ }
145
+ }
146
+ return { target: undefined, remainingWords: [] };
147
+ }
148
+ }
149
+ //# sourceMappingURL=call.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"call.js","sourceRoot":"","sources":["../../../../../../../src/commands/game/sideQuest/commands/call.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ import { BypassCommand } from './bypass-command.js';
2
+ import { PuzzleBypassType } from '../types/seed/puzzle-seed.js';
3
+ /**
4
+ * Gear-driven spellcasting (see Player's magic section): you know a spell
5
+ * if you carry its SpellFormula (or any Grimoire), and the best Focus on
6
+ * you scales the casting pool. "cast <spell> [at <target>]" casts; a bare
7
+ * "cast" (or "cast <direction/barrier>") is still the ward-bypass route
8
+ * inherited from BypassCommand -- now gated on actually having magic gear,
9
+ * instead of being a free-floating coin flip anyone could attempt.
10
+ */
11
+ export declare class CastCommand extends BypassCommand {
12
+ protected static verb: string;
13
+ protected static description: string;
14
+ protected bypassType: PuzzleBypassType;
15
+ protected noPuzzleMessage: string;
16
+ execute(args?: string[]): Promise<string>;
17
+ /**
18
+ * Lenient spell-name matching: the exact key, or a unique fragment of at
19
+ * least 3 characters ("bolt"/"mana" -> manabolt). Anything that doesn't
20
+ * resolve falls through to the ward-bypass path, which is what a
21
+ * direction or barrier name ("cast north", "cast maglock") needs.
22
+ */
23
+ private resolveSpellName;
24
+ private castSpell;
25
+ /**
26
+ * Support spells (heal/armor) default to SELF but can target an ally:
27
+ * "cast heal on jinx". Touch range -- the target must share the room --
28
+ * and the same fuzzy name matching as everything else. Returns an error
29
+ * string instead of a target when the name can't be resolved here.
30
+ */
31
+ private resolveAllyTarget;
32
+ /** How the caster's pool reads in narration: focus-assisted, raw talent, or book-learned. */
33
+ private castingLabel;
34
+ /**
35
+ * Drain, resolved AFTER the spell: resist with Body, take whatever gets
36
+ * through as STUN damage -- casting doesn't cut you, it wrings you out.
37
+ * A full stun track is a knockout, and overflow past it spills into the
38
+ * physical track (enough drain doesn't just put you down, it starts
39
+ * killing you). Callers check drainCollapse() afterward.
40
+ */
41
+ private applyDrain;
42
+ /**
43
+ * What the drain just did to the caster, if anything: overflow filling
44
+ * the physical track is a burnout DEATH; a full stun track alone is a
45
+ * knockout -- out of the run, but honestly narrated as unconsciousness.
46
+ */
47
+ private drainCollapse;
48
+ private castManabolt;
49
+ private castHeal;
50
+ private castArmor;
51
+ /**
52
+ * Non-combat casts are a single block, no exchange pacing needed: the
53
+ * player's own casts return the text for ui.ts to print, while an NPC's
54
+ * cast self-announces to a witnessing player (its return value only ever
55
+ * feeds its AI history) -- same rationale as CombatExchange.
56
+ */
57
+ private finishSpell;
58
+ }