@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
@@ -16,6 +16,11 @@ export class Item extends AbstractItem {
16
16
  content;
17
17
  details;
18
18
  satisfiesLifestyle;
19
+ plane = 'meat';
20
+ // PAN warfare (see commands/hack.ts): a cracked smart-device locks up.
21
+ // A jammed firearm can't fire (fists until cleared); "reboot" clears
22
+ // every jammed device you carry, in the lull.
23
+ jammed = false;
19
24
  _transferable;
20
25
  // New currency-related fields
21
26
  _currencyAmount = 0;
@@ -45,9 +50,15 @@ export class Item extends AbstractItem {
45
50
  this.content = options.content;
46
51
  this.satisfiesLifestyle = options.satisfiesLifestyle;
47
52
  this._transferable = options.transferable !== false; // default true
53
+ this.plane = options.plane ?? 'meat';
48
54
  this._id = uuidv4();
49
- // Optional: you can define capacity by size
50
- if (this.isCurrencyCarrier()) {
55
+ // Optional: you can define capacity by size. Deliberately scoped to
56
+ // CurrencyContainer only (a physical wallet/pouch) -- a Credstick is
57
+ // digital storage, not physically size-limited, so it's left uncapped
58
+ // (falls through to Infinity in addCurrency()). Capping it here would
59
+ // silently truncate a seed's declared "currencyAmount" down to the
60
+ // size-based cap the moment Credstick became a currency carrier.
61
+ if (this.category === Category.CurrencyContainer) {
51
62
  switch (this.size) {
52
63
  case Size.Small:
53
64
  this._currencyCapacity = 100;
@@ -60,6 +71,34 @@ export class Item extends AbstractItem {
60
71
  break;
61
72
  }
62
73
  }
74
+ // Firearms track ammunition: a flat magazine, starting full. Melee
75
+ // weapons never touch it. See fireRound()/reload() and combat-exchange.
76
+ if (this.isFirearm()) {
77
+ this.ammo = Item.MAGAZINE_CAPACITY;
78
+ }
79
+ }
80
+ // ======================== Ammunition ========================
81
+ static MAGAZINE_CAPACITY = 10;
82
+ /** Rounds left in the magazine. Only meaningful for firearms. */
83
+ ammo = 0;
84
+ /** Any weapon that shoots -- everything weapon-ish except melee. */
85
+ isFirearm() {
86
+ return this.category === Category.RangedWeapon || this.category === Category.Weapon;
87
+ }
88
+ /** Spends one round; returns false on an empty magazine. */
89
+ fireRound() {
90
+ if (!this.isFirearm())
91
+ return true; // knives don't run dry
92
+ if (this.ammo <= 0)
93
+ return false;
94
+ this.ammo -= 1;
95
+ return true;
96
+ }
97
+ /** Refills the magazine (the spent Ammunition item is the caller's job). */
98
+ reload() {
99
+ if (this.isFirearm()) {
100
+ this.ammo = Item.MAGAZINE_CAPACITY;
101
+ }
63
102
  }
64
103
  get owner() {
65
104
  return this._owner;
@@ -76,6 +115,61 @@ export class Item extends AbstractItem {
76
115
  use() {
77
116
  return `You use the ${this.name}. Nothing special happens.`;
78
117
  }
118
+ /**
119
+ * How much this item's quality tier is worth in game mechanics: extra
120
+ * weapon damage/attack dice, armor soak, medkit healing (see
121
+ * Player.getWeaponProfile/getArmorValue and commands/use.ts). Gear
122
+ * quality is the game's whole progression system, so every mechanic
123
+ * that gear feeds scales off this one number.
124
+ */
125
+ get qualityBonus() {
126
+ switch (this.rating) {
127
+ case Rating.Common: return 0;
128
+ case Rating.Standard: return 1;
129
+ case Rating.Rare: return 2;
130
+ case Rating.Legendary: return 3;
131
+ case Rating.MilitaryGrade: return 4;
132
+ default: return 0;
133
+ }
134
+ }
135
+ // ================= Device condition monitor (cyberdecks) =================
136
+ // Matrix combat damage lands on HARDWARE: the deck you jacked in with
137
+ // (see combat-exchange.ts routing and Player.takeMatrixDamage). A full
138
+ // track BRICKS the deck -- forced dump, and no jacking in on it again
139
+ // until it's repaired (rest somewhere safe -- see rest.ts). Damage
140
+ // persists across jack-outs; rebooting doesn't un-melt circuitry.
141
+ _deckDamage = 0;
142
+ isCyberdeck() {
143
+ return this.category === Category.Cyberdeck;
144
+ }
145
+ /** Better decks have more board to burn through: 8 + 2 per quality step. */
146
+ get maxDeckBoxes() {
147
+ return 8 + 2 * this.qualityBonus;
148
+ }
149
+ get deckDamage() {
150
+ return this._deckDamage;
151
+ }
152
+ /** Returns boxes actually taken. Full track = bricked. */
153
+ takeDeckDamage(boxes) {
154
+ const taken = Math.max(0, Math.floor(boxes));
155
+ this._deckDamage = Math.min(this.maxDeckBoxes, this._deckDamage + taken);
156
+ return taken;
157
+ }
158
+ get isBricked() {
159
+ return this._deckDamage >= this.maxDeckBoxes;
160
+ }
161
+ /** Clears boxes; returns how many were actually repaired. */
162
+ repairDeck(boxes) {
163
+ const before = this._deckDamage;
164
+ this._deckDamage = Math.max(0, before - Math.max(0, Math.floor(boxes)));
165
+ return before - this._deckDamage;
166
+ }
167
+ /** The deck track, same rendering as the body's condition monitors. */
168
+ deckSummary() {
169
+ const max = this.maxDeckBoxes;
170
+ const filled = Math.min(max, this._deckDamage);
171
+ return `${'■'.repeat(filled)}${'□'.repeat(max - filled)} (${filled}/${max})`;
172
+ }
79
173
  supportsAddiction(addiction) {
80
174
  return this.satisfiesLifestyle?.addictionTypes?.includes(addiction) ?? false;
81
175
  }
@@ -83,8 +177,17 @@ export class Item extends AbstractItem {
83
177
  return this.satisfiesLifestyle?.habitTags?.includes(habit) ?? false;
84
178
  }
85
179
  // --- Currency Support ---
180
+ /**
181
+ * The generation schema explicitly allows "currencyAmount" on either a
182
+ * "Credstick" or "CurrencyContainer" item (see scene-seed-generator.ts),
183
+ * but this only ever recognized the latter -- so a Credstick's nuyen
184
+ * silently never got set at all (item-factory.ts gates addCurrency() on
185
+ * this), and never counted toward the inventory panel's Total. Found via
186
+ * a real session where a 1,000-nuyen credstick added nothing to the
187
+ * displayed total.
188
+ */
86
189
  isCurrencyCarrier() {
87
- return this.category === Category.CurrencyContainer;
190
+ return this.category === Category.CurrencyContainer || this.category === Category.Credstick;
88
191
  }
89
192
  get currencyAmount() {
90
193
  return this._currencyAmount;
@@ -1 +1 @@
1
- {"version":3,"file":"item.js","sourceRoot":"","sources":["../../../../../../../src/commands/game/sideQuest/models/item.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"item.js","sourceRoot":"","sources":["../../../../../../../src/commands/game/sideQuest/models/item.ts"],"names":[],"mappings":"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"}
@@ -10,8 +10,11 @@ export interface INPCConstructorConfig {
10
10
  export declare class NPC extends Player {
11
11
  private _dialogue;
12
12
  private _description;
13
+ private _appearance?;
14
+ private hasAIAppearance;
13
15
  private _scene?;
14
16
  private _commandHelp;
17
+ private _objective?;
15
18
  private _history;
16
19
  private readonly _maxHistory;
17
20
  private commandRegistry;
@@ -25,9 +28,31 @@ export declare class NPC extends Player {
25
28
  set scene(scene: Scene);
26
29
  get description(): string;
27
30
  set description(description: string);
31
+ get appearance(): string | undefined;
32
+ /**
33
+ * Generates a spoiler-free, player-facing first impression once per scene
34
+ * load. Deliberately kept separate from `_description`, which holds
35
+ * GM-only behavior notes (e.g. "will kill on sight") that would ruin the
36
+ * game if surfaced through `look at <npc>`.
37
+ */
38
+ ensureAIAppearance(sceneContext: string): Promise<void>;
28
39
  act(commandString: string): Promise<void>;
29
40
  hear(actor: Player, message: string): Promise<void>;
30
41
  see(actor: Player, verb: string, details?: string): Promise<void>;
42
+ /**
43
+ * The NPC's own condition, in words its AI can act on -- without this,
44
+ * an NPC literally cannot perceive being hurt: found via a real session
45
+ * where a nearly-dead fighter battled on exactly like a fresh one,
46
+ * because nothing ever put its wounds in its prompt. The badly-wounded
47
+ * nudge is what lets flee/surrender/beg behavior emerge.
48
+ */
49
+ /**
50
+ * Off-plane natives get told WHAT they are, so the AI plays ice like a
51
+ * program and a spirit like a spirit -- and neither wanders off its
52
+ * post or tries to pick up physical objects it can't touch.
53
+ */
54
+ private buildPlaneSummary;
55
+ private buildConditionSummary;
31
56
  private _addToHistory;
32
57
  private getHistorySummary;
33
58
  speak(message?: string): string;
@@ -1,12 +1,19 @@
1
1
  import { capitalCase } from 'change-case';
2
2
  import { Player } from './player.js';
3
3
  import { Door } from './door.js';
4
- import { HTTPS } from '../../../../tools/https/https.class.js';
4
+ import { AI } from '../../../../tools/ai/ai.class.js';
5
+ import { CALL_ICON, CALL_COLOR } from '../utilities/comm-style.js';
5
6
  export class NPC extends Player {
6
7
  _dialogue;
7
8
  _description;
9
+ _appearance;
10
+ hasAIAppearance = false;
8
11
  _scene;
9
12
  _commandHelp;
13
+ // An evolving one-line goal, revised by the AI as significant events happen.
14
+ // Purely in-memory -- gives an NPC a throughline across a session without
15
+ // needing any persistence.
16
+ _objective;
10
17
  _history = [];
11
18
  _maxHistory = 200;
12
19
  commandRegistry;
@@ -26,23 +33,57 @@ Available in-game commands (use with [COMMAND] prefix):
26
33
 
27
34
  - go <direction> — move in a direction like north, south, east, west
28
35
  - say <message> — speak a message
29
- - take <item name> — pick up an item from the current room
36
+ - take <item name> — pick up an item from the current room ("take all" sweeps everything here, but only AFTER you have searched this room)
30
37
  - drop <item name> — drop an item
31
- - use <item name> — use an item
38
+ - use <item name> — use an item (a Medical item like a medkit heals your PHYSICAL damage;
39
+ a StimPatch item recovers your STUN but costs 1 physical as a crash -- both are consumed.
40
+ Use them if you're hurt and carrying one)
41
+ - rest — recover Stun by resting. Only works in a Residence room or a room with no one
42
+ else present; never rest mid-fight
32
43
  - equip <item name> — equip an item (e.g., equip a weapon to use)
33
44
  - equip all - equip all wearable items in inventory
34
45
  - unequip <item name> — remove an equipped item
35
46
  - unlock <door name> — unlock a door (if you have the key or solution)
36
47
  - lock <door name> — lock a door
37
- - solve <puzzle name> — attempt to solve a puzzle
48
+ - solve <answer> — attempt to solve the puzzle in this room by stating the actual answer (safe, always works if correct)
49
+ - pick — attempt to pick the lock on the puzzle in this room without knowing the answer (50/50, a failure sets off an alarm)
50
+ - hack — attempt to hack the terminal/system in this room without knowing the answer (50/50, a failure trips a countermeasure)
51
+ - cast <spell> [at <target>] — cast a spell you know. Requires being Awakened
52
+ (innate talent -- you'll be told if you are) OR carrying that spell's formula
53
+ item or a grimoire (a Focus item boosts your pool either way). Spells:
54
+ "manabolt" (combat damage resisted by body, not armor), "heal [on <ally>]" (heal
55
+ physical wounds -- your own by default, or an ally's in your room; healing a
56
+ wounded ally is a strong support move), "armor [on <ally>]" (sustained protection
57
+ on yourself or an ally; its bearer gets +soak at -2 dice to all else; cast at them
58
+ again to release). EVERY cast costs drain -- stun damage to YOU the caster; fill
59
+ your stun track and you're out cold. Never cast if you're mundane with no magic gear.
60
+ - cast — bare "cast" attempts to magically bypass the ward/barrier in this room
61
+ without knowing the answer (requires the same talent or gear; a failure is fatal)
38
62
  - search — search the room for hidden things
39
63
  - look — look around the current room
40
64
  - look at <target> - looks at an item, player, or direction
41
65
  - store <item name> — store an item (in bank, safe, etc.)
42
66
  - give <item name> to <target> — give an item to someone
43
67
  - talk-to <target> — initiate conversation with another character
68
+ - call <target> [message] — call someone remotely over a commlink (works even if they're not in your room; requires having a commlink equipped, and the target having one too)
69
+ - end-call — hang up your current commlink call
70
+ - silent — toggle your commlink broadcast. While running silent, you can't place
71
+ calls and nobody can call YOU. Toggle again to go loud.
44
72
  - read <target> - Reads an item of Category "Book"
45
- - kill <target> - Kills an actor, removing them from the game.
73
+ - attack <target> attack someone in your room. Resolved as opposed dice-pool combat
74
+ (your weapon and armor matter); an initiative roll decides who strikes first each
75
+ exchange (fast fighters can even land an extra strike), the target strikes back, and
76
+ it usually takes several exchanges to bring someone down. Only start a fight your
77
+ character would actually risk dying in. If your target flees the room, a failed
78
+ attack tells you exactly where they went and which direction to "go" -- CHASE them
79
+ with that instead of attacking the empty room again.
80
+ - kill <target> - alias for attack: real combat, NOT an instant kill.
81
+ - subdue <target> — take someone down ALIVE: stun damage, bare-handed, slower than
82
+ shooting; they retaliate lethally. Use when you want them breathing.
83
+ - reload — refill your equipped firearm (consumes an Ammunition item from your pack).
84
+ Firearms run dry: if your attack reports "dry", reload or switch to melee.
85
+ Fleeing someone you just fought gives them one free parting shot at your back
86
+ (and fleeing enemies give YOU one).
46
87
  `.trim();
47
88
  }
48
89
  set scene(scene) {
@@ -54,11 +95,46 @@ Available in-game commands (use with [COMMAND] prefix):
54
95
  set description(description) {
55
96
  this._description = description;
56
97
  }
98
+ get appearance() {
99
+ return this._appearance;
100
+ }
101
+ /**
102
+ * Generates a spoiler-free, player-facing first impression once per scene
103
+ * load. Deliberately kept separate from `_description`, which holds
104
+ * GM-only behavior notes (e.g. "will kill on sight") that would ruin the
105
+ * game if surfaced through `look at <npc>`.
106
+ */
107
+ async ensureAIAppearance(sceneContext) {
108
+ if (this.hasAIAppearance)
109
+ return;
110
+ if (!AI.isConfigured())
111
+ return;
112
+ const prompt = `
113
+ You are a game narrator. Write a brief (2-3 sentence), visually evocative first-impression
114
+ description of a character's appearance and demeanor, in a gritty cyberpunk tone.
115
+
116
+ Scene context: ${sceneContext}
117
+
118
+ Character name: ${this.name}
119
+
120
+ Only describe what a stranger would immediately notice on first meeting them. Do not
121
+ reveal hidden motives, secret plans, backstory, or intentions. Respond with ONLY the
122
+ description text.`;
123
+ try {
124
+ const reply = await AI.ask(prompt);
125
+ this._appearance = reply;
126
+ this.hasAIAppearance = true;
127
+ this.logger.write(`Generated AI appearance for ${this.name}`);
128
+ }
129
+ catch (err) {
130
+ this.logger.write(`AI fallback for appearance (${this.name}): ${err}`);
131
+ }
132
+ }
57
133
  async act(commandString) {
58
- const command = await this.commandRegistry.parse(commandString, this);
59
- if (command) {
134
+ const parsed = await this.commandRegistry.parse(commandString, this);
135
+ if (parsed) {
60
136
  // Capture the result of the command
61
- const result = await command.execute();
137
+ const result = await parsed.command.execute(parsed.args);
62
138
  await this.recursiveReflect(commandString, result);
63
139
  }
64
140
  else {
@@ -68,6 +144,11 @@ Available in-game commands (use with [COMMAND] prefix):
68
144
  async hear(actor, message) {
69
145
  if (actor === this || actor.name === this.name)
70
146
  return;
147
+ // Cross-plane silence (see Player.canPerceive): a meat guard never
148
+ // hears a Matrix persona or an astral form, so its AI never reacts to
149
+ // things its character couldn't possibly know about.
150
+ if (!this.canPerceive(actor))
151
+ return;
71
152
  // Player wakes us up
72
153
  if (!(actor instanceof NPC)) {
73
154
  if (this.waitingForPlayer)
@@ -100,6 +181,9 @@ Available in-game commands (use with [COMMAND] prefix):
100
181
  try {
101
182
  if (actor === this || actor.name === this.name)
102
183
  return;
184
+ // Same cross-plane blindness as hear() above.
185
+ if (!this.canPerceive(actor))
186
+ return;
103
187
  if (!(actor instanceof NPC)) {
104
188
  if (this.waitingForPlayer)
105
189
  this.logger.write(`[DEBUG] ${this.name} received player action, waking up.`);
@@ -129,6 +213,42 @@ Available in-game commands (use with [COMMAND] prefix):
129
213
  throw new Error(error);
130
214
  }
131
215
  }
216
+ /**
217
+ * The NPC's own condition, in words its AI can act on -- without this,
218
+ * an NPC literally cannot perceive being hurt: found via a real session
219
+ * where a nearly-dead fighter battled on exactly like a fresh one,
220
+ * because nothing ever put its wounds in its prompt. The badly-wounded
221
+ * nudge is what lets flee/surrender/beg behavior emerge.
222
+ */
223
+ /**
224
+ * Off-plane natives get told WHAT they are, so the AI plays ice like a
225
+ * program and a spirit like a spirit -- and neither wanders off its
226
+ * post or tries to pick up physical objects it can't touch.
227
+ */
228
+ buildPlaneSummary() {
229
+ if (this.plane === 'matrix') {
230
+ return `You are ICE -- a Matrix security program. You exist ONLY on the grid: you perceive and fight jacked-in personas, never the physical world. Defend your host node; do not leave it. You speak in clipped machine terms. You cannot take physical items, and "surrender" is not in your code -- you crash or you purge the intruder.`;
231
+ }
232
+ if (this.plane === 'astral') {
233
+ return `You are a SPIRIT -- an astral entity. You exist ONLY on the astral plane: you perceive the meat world dimly as auras but can only interact with astral beings (like projecting mages). You speak the way something made of will and memory speaks. You cannot touch physical objects.`;
234
+ }
235
+ return '';
236
+ }
237
+ buildConditionSummary() {
238
+ if (this.damageTaken === 0 && this.stunTaken === 0) {
239
+ return `You are unhurt.`;
240
+ }
241
+ const parts = [`Your current condition: physical damage ${this.damageTaken}/${this.maxConditionBoxes}, stun ${this.stunTaken}/${this.maxStunBoxes}${this.woundModifier < 0 ? ` (wound penalty ${this.woundModifier} dice on everything you attempt)` : ''}.`];
242
+ const nearDeath = this.damageTaken >= this.maxConditionBoxes - 3;
243
+ const nearCollapse = this.stunTaken >= this.maxStunBoxes - 3;
244
+ if (nearDeath || nearCollapse) {
245
+ parts.push(`You are BADLY WOUNDED -- ${nearDeath ? 'a solid hit or two will kill you' : 'you are close to blacking out'}. Your life matters to you: seriously consider fleeing (go <direction>), surrendering (the literal command "surrender" -- it visibly ends the fight and an honorable opponent will hold fire), begging off, or de-escalating instead of fighting on. Only keep fighting if dying for this would truly be in character.`);
246
+ }
247
+ else if (this.woundModifier <= -2) {
248
+ parts.push(`You are hurt badly enough that it's affecting you -- weigh whether this fight is still worth it.`);
249
+ }
250
+ return parts.join(' ');
251
+ }
132
252
  _addToHistory(entry) {
133
253
  this._history.push(entry);
134
254
  if (this._history.length > this._maxHistory) {
@@ -151,9 +271,16 @@ Available in-game commands (use with [COMMAND] prefix):
151
271
  return;
152
272
  }
153
273
  const historySummary = this.getHistorySummary();
274
+ const worldEventsSummary = this._scene?.getWorldEventsSummary() ?? 'Nothing notable has happened elsewhere yet.';
275
+ const objectiveSummary = this._objective
276
+ ? `Your current objective: ${this._objective}`
277
+ : `You don't have a specific objective right now.`;
154
278
  // Build the prompt context
155
279
  const context = `
156
280
  You are an NPC named ${this.name}.
281
+ ${this.buildPlaneSummary()}
282
+ ${objectiveSummary}
283
+ ${this.buildConditionSummary()}
157
284
 
158
285
  You just performed the action: "${commandString}"
159
286
  With the result: "${result}"
@@ -167,14 +294,18 @@ Based on this, what is the most logical next in-game command (from the following
167
294
 
168
295
  This is what has already happened:
169
296
  ${historySummary}
297
+
298
+ ${worldEventsSummary}
170
299
  `;
171
- // Send to GPT
172
- const gptResult = await HTTPS.askGPT({
173
- prompt: context,
174
- openApiKey: process.env.OPENAI_API_KEY || '',
175
- model: 'gpt-4.1'
176
- });
177
- const reply = JSON.parse(gptResult.data)?.choices?.[0]?.message?.content?.trim();
300
+ // Send to the configured AI provider
301
+ let reply;
302
+ try {
303
+ reply = await AI.ask(context);
304
+ }
305
+ catch (err) {
306
+ this.logger.write(`${this.name} AI reflection failed: ${err}`);
307
+ return;
308
+ }
178
309
  if (!reply || reply.toLowerCase().includes('wait')) {
179
310
  this.logger.write(`${this.name} is finished reflecting for now.`);
180
311
  return;
@@ -183,9 +314,9 @@ ${historySummary}
183
314
  const nextCommandMatch = reply.match(/^\[COMMAND\]\s*(.*)$/i);
184
315
  if (nextCommandMatch && nextCommandMatch[1]) {
185
316
  const nextCommand = nextCommandMatch[1];
186
- const command = await this.commandRegistry.parse(nextCommand, this);
187
- if (command) {
188
- const nextResult = await command.execute();
317
+ const parsed = await this.commandRegistry.parse(nextCommand, this);
318
+ if (parsed) {
319
+ const nextResult = await parsed.command.execute(parsed.args);
189
320
  if (nextResult) {
190
321
  this._addToHistory(nextResult);
191
322
  }
@@ -196,12 +327,12 @@ ${historySummary}
196
327
  }
197
328
  }
198
329
  else {
199
- this.logger.write('No valid command parsed from GPT reflection.');
330
+ this.logger.write('No valid command parsed from AI reflection.');
200
331
  }
201
332
  }
202
333
  async respondTo(actor, message) {
203
334
  this.logger.write('Attempting to respond');
204
- if (!process.env.OPENAI_API_KEY) {
335
+ if (!AI.isConfigured()) {
205
336
  this.logger.write(`No AI available`);
206
337
  await actor.hear(this, this.speak());
207
338
  return;
@@ -224,10 +355,48 @@ ${historySummary}
224
355
  ? `The overall storyline for this scene is: ${this._scene.story}`
225
356
  : `This scene has no overall storyline`;
226
357
  const historySummary = this.getHistorySummary();
358
+ const worldEventsSummary = this._scene?.getWorldEventsSummary() ?? 'Nothing notable has happened elsewhere yet.';
359
+ const objectiveSummary = this._objective
360
+ ? `Your current objective: ${this._objective}`
361
+ : `You don't have a specific objective yet -- if this event gives you one, set it.`;
362
+ // Scripted plot facts from scene generation (the job, the target, the
363
+ // stakes) previously only surfaced in the non-AI fallback speak() --
364
+ // never in this prompt, so a live AI reply had no way to know them and
365
+ // could never actually brief the player.
366
+ const dialogueSummary = safeArray(this._dialogue, 'dialogue').length > 0
367
+ ? `Key things you know and may want to bring up naturally in conversation (do not recite these verbatim -- weave them into your own words): ${this._dialogue.join(' | ')}`
368
+ : `You have no specific scripted talking points.`;
369
+ // Money talk is mechanically real: the engine pays exactly
370
+ // winCondition.payout on completion, so a freely-invented figure in
371
+ // dialogue becomes a broken promise -- found via a real session where
372
+ // the fixer offered "50k" and the job actually paid 2000.
373
+ const payout = this._scene?.getWinCondition?.()?.payout;
374
+ const payoutSummary = payout
375
+ ? `The agreed payout for the current job is EXACTLY ${payout} nuyen. Any time you mention payment, reward, or what the job is worth, use exactly this figure -- never invent a different one. Payment transfers automatically when the job completes; you never need to hand it over.`
376
+ : '';
377
+ // If nothing else has happened to/around this NPC yet this scene, this
378
+ // is the first time anyone's reached out -- nudge toward volunteering
379
+ // the job rather than waiting to be asked, which real playtesting
380
+ // showed otherwise doesn't happen reliably. hear() already records the
381
+ // current message into history before calling respondTo(), so "first
382
+ // contact" means history has just this one entry, not zero.
383
+ const firstContactNudge = this._history.length <= 1
384
+ ? `This is the first contact anyone has made with you this scene. If you have a job, objective, or key information for them (see above), proactively bring it up now instead of waiting to be asked.`
385
+ : '';
227
386
  const npcDescription = this.description
228
387
  ? `This NPC has the following characteristics: ${this._description}`
229
388
  : `This NPC is non-descript.`;
230
389
  this.logger.write(npcDescription);
390
+ // Casting is stat/gear-gated (see cast.ts), but this prompt is all the
391
+ // NPC "knows" about itself -- without an explicit line here, a seeded
392
+ // Awakened mage has no way to realize it can sling spells, and a
393
+ // mundane may hallucinate that it can.
394
+ const magicSummary = this.isAwakened()
395
+ ? `You are AWAKENED (magic ${this.magic}): you can cast manabolt, heal, and armor innately, no gear needed.`
396
+ : this.isCaster()
397
+ ? `You are mundane but carry magic gear, so you can cast the spells your formulas/grimoire teach.`
398
+ : `You are mundane with no magic gear: you CANNOT cast spells at all.`;
399
+ const conditionSummary = this.buildConditionSummary();
231
400
  const lifestyleSummary = Array.isArray(this.lifestyleChoices) && this.lifestyleChoices.length > 0
232
401
  ? `This NPC has the following lifestyle characteristics: ${this.lifestyleChoices.join(', ')}.`
233
402
  : `This NPC has no defined lifestyle preferences.`;
@@ -306,13 +475,22 @@ You are an NPC named ${this.name} in a gritty cyberpunk text adventure game.
306
475
  - Attempt to use [COMMAND] when appropriate for interacting with the environment. (e.g., "take <item>" when picking up)
307
476
  - You should only provide one [COMMAND] at a time.
308
477
  - Do not respond on behalf of another player or NPC
478
+ - If this event genuinely changes what you're trying to accomplish, include a separate line:
479
+ [OBJECTIVE] <your new one-sentence objective>
480
+ Only include this line if your objective has actually changed. Do not include it otherwise.
309
481
 
310
482
  Your objective is to drive the story line.
311
483
  ${story}
484
+ ${firstContactNudge}
312
485
 
313
486
  ${this._commandHelp}
314
487
 
315
488
  ${npcDescription}
489
+ ${conditionSummary}
490
+ ${magicSummary}
491
+ ${objectiveSummary}
492
+ ${dialogueSummary}
493
+ ${payoutSummary}
316
494
 
317
495
  ${roomDesc}
318
496
  ${formattedExitsStr}
@@ -326,31 +504,13 @@ Your objective is to drive the story line.
326
504
 
327
505
  ${historySummary}
328
506
 
507
+ ${worldEventsSummary}
508
+
329
509
  ${actor.name}: ${message}
330
510
  `.trim();
331
- // ---------- GPT call + parsing (defensive) ----------
511
+ // ---------- AI call + parsing (defensive) ----------
332
512
  try {
333
- const result = await HTTPS.askGPT({
334
- prompt,
335
- openApiKey: process.env.OPENAI_API_KEY,
336
- model: 'gpt-4.1'
337
- });
338
- let parsed;
339
- try {
340
- parsed = JSON.parse(result?.data ?? '{}');
341
- }
342
- catch (e) {
343
- this.logger.error(`[ERROR][${this.name}] JSON.parse failed on result.data: ${e}`);
344
- return;
345
- }
346
- if (parsed?.error) {
347
- throw new Error(parsed.error.message ?? 'Unknown GPT error');
348
- }
349
- const reply = parsed?.choices?.[0]?.message?.content?.trim?.();
350
- if (!reply || typeof reply !== 'string') {
351
- this.logger.write(`[DEBUG][${this.name}] Empty or invalid reply from GPT; no action taken.`);
352
- return;
353
- }
513
+ const reply = await AI.ask(prompt);
354
514
  this._addToHistory(reply);
355
515
  const lines = toLines(reply);
356
516
  // Spoken lines
@@ -359,8 +519,17 @@ Your objective is to drive the story line.
359
519
  const commandLines = lines.filter(line => line.startsWith('[COMMAND]') &&
360
520
  !line.startsWith('[COMMAND] say') &&
361
521
  !line.startsWith('[COMMAND] talk-to'));
362
- // Exposition (non-command lines)
363
- const expositionLines = lines.filter(line => !line.startsWith('[COMMAND]'));
522
+ // Objective updates (parsed out before exposition, never shown to the player)
523
+ const objectiveLines = lines.filter(line => line.startsWith('[OBJECTIVE]'));
524
+ // Exposition (non-command, non-objective lines)
525
+ const expositionLines = lines.filter(line => !line.startsWith('[COMMAND]') && !line.startsWith('[OBJECTIVE]'));
526
+ if (objectiveLines.length > 0) {
527
+ const newObjective = objectiveLines[objectiveLines.length - 1].replace('[OBJECTIVE]', '').trim();
528
+ if (newObjective.length > 0) {
529
+ this._objective = newObjective;
530
+ this.logger.write(`${this.name}'s objective is now: ${newObjective}`);
531
+ }
532
+ }
364
533
  // Speak each say command
365
534
  for (const spokenText of spokenLines) {
366
535
  const commandText = (spokenText ?? '').replace('[COMMAND]', '').trim();
@@ -375,10 +544,14 @@ Your objective is to drive the story line.
375
544
  // continue; don't throw so we can process remaining lines
376
545
  }
377
546
  }
378
- // Log exposition
547
+ // Log exposition. Tagged distinctly while this NPC is mid-call (see
548
+ // call.ts/say.ts) so a comm exchange reads as a different "space"
549
+ // than in-person dialogue, even though it renders in the same log.
550
+ const inCall = !!this.activeCallPartner;
551
+ const prefix = inCall ? `${CALL_ICON} ` : '';
379
552
  for (const exposition of expositionLines) {
380
553
  if (exposition?.length) {
381
- this.logger.logWithColor(`${exposition}`, 'blue');
554
+ this.logger.logWithColor(`${prefix}${exposition}`, inCall ? CALL_COLOR : 'blue');
382
555
  }
383
556
  }
384
557
  // Execute other commands
@@ -397,7 +570,7 @@ Your objective is to drive the story line.
397
570
  }
398
571
  }
399
572
  catch (err) {
400
- this.logger.write(`GPT fallback due to error: ${err}`);
573
+ this.logger.write(`AI fallback due to error: ${err}`);
401
574
  // surface a consistent Error instance, but don't double-wrap Errors
402
575
  const e = err instanceof Error ? err : new Error(String(err));
403
576
  throw e;