@maka/maka-cli 5.3.17 → 5.5.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 (245) hide show
  1. package/bundle/typescript/package.json +1 -1
  2. package/bundle/typescript/src/commands/game/sideQuest/archetypes.d.ts +62 -0
  3. package/bundle/typescript/src/commands/game/sideQuest/archetypes.js +196 -0
  4. package/bundle/typescript/src/commands/game/sideQuest/archetypes.js.map +1 -0
  5. package/bundle/typescript/src/commands/game/sideQuest/commands/advance.d.ts +16 -0
  6. package/bundle/typescript/src/commands/game/sideQuest/commands/advance.js +65 -0
  7. package/bundle/typescript/src/commands/game/sideQuest/commands/advance.js.map +1 -0
  8. package/bundle/typescript/src/commands/game/sideQuest/commands/assense.d.ts +16 -0
  9. package/bundle/typescript/src/commands/game/sideQuest/commands/assense.js +122 -0
  10. package/bundle/typescript/src/commands/game/sideQuest/commands/assense.js.map +1 -0
  11. package/bundle/typescript/src/commands/game/sideQuest/commands/attack.d.ts +42 -0
  12. package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js +243 -0
  13. package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js.map +1 -0
  14. package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.d.ts +16 -0
  15. package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js +39 -0
  16. package/bundle/typescript/src/commands/game/sideQuest/commands/bluff.js.map +1 -0
  17. package/bundle/typescript/src/commands/game/sideQuest/commands/brandish.d.ts +21 -0
  18. package/bundle/typescript/src/commands/game/sideQuest/commands/brandish.js +82 -0
  19. package/bundle/typescript/src/commands/game/sideQuest/commands/brandish.js.map +1 -0
  20. package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.d.ts +28 -0
  21. package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js +101 -0
  22. package/bundle/typescript/src/commands/game/sideQuest/commands/bypass-command.js.map +1 -0
  23. package/bundle/typescript/src/commands/game/sideQuest/commands/call.d.ts +19 -0
  24. package/bundle/typescript/src/commands/game/sideQuest/commands/call.js +219 -0
  25. package/bundle/typescript/src/commands/game/sideQuest/commands/call.js.map +1 -0
  26. package/bundle/typescript/src/commands/game/sideQuest/commands/cast.d.ts +58 -0
  27. package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js +397 -0
  28. package/bundle/typescript/src/commands/game/sideQuest/commands/cast.js.map +1 -0
  29. package/bundle/typescript/src/commands/game/sideQuest/commands/command-registry.d.ts +11 -1
  30. package/bundle/typescript/src/commands/game/sideQuest/commands/command-registry.js +8 -3
  31. package/bundle/typescript/src/commands/game/sideQuest/commands/command-registry.js.map +1 -1
  32. package/bundle/typescript/src/commands/game/sideQuest/commands/command.d.ts +6 -0
  33. package/bundle/typescript/src/commands/game/sideQuest/commands/command.js +4 -0
  34. package/bundle/typescript/src/commands/game/sideQuest/commands/command.js.map +1 -1
  35. package/bundle/typescript/src/commands/game/sideQuest/commands/drone.d.ts +15 -0
  36. package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js +72 -0
  37. package/bundle/typescript/src/commands/game/sideQuest/commands/drone.js.map +1 -0
  38. package/bundle/typescript/src/commands/game/sideQuest/commands/drop.js +3 -0
  39. package/bundle/typescript/src/commands/game/sideQuest/commands/drop.js.map +1 -1
  40. package/bundle/typescript/src/commands/game/sideQuest/commands/edge.d.ts +13 -0
  41. package/bundle/typescript/src/commands/game/sideQuest/commands/edge.js +31 -0
  42. package/bundle/typescript/src/commands/game/sideQuest/commands/edge.js.map +1 -0
  43. package/bundle/typescript/src/commands/game/sideQuest/commands/end-call.d.ts +6 -0
  44. package/bundle/typescript/src/commands/game/sideQuest/commands/end-call.js +27 -0
  45. package/bundle/typescript/src/commands/game/sideQuest/commands/end-call.js.map +1 -0
  46. package/bundle/typescript/src/commands/game/sideQuest/commands/equip.d.ts +4 -0
  47. package/bundle/typescript/src/commands/game/sideQuest/commands/equip.js +59 -2
  48. package/bundle/typescript/src/commands/game/sideQuest/commands/equip.js.map +1 -1
  49. package/bundle/typescript/src/commands/game/sideQuest/commands/equipment.d.ts +13 -0
  50. package/bundle/typescript/src/commands/game/sideQuest/commands/equipment.js +50 -0
  51. package/bundle/typescript/src/commands/game/sideQuest/commands/equipment.js.map +1 -0
  52. package/bundle/typescript/src/commands/game/sideQuest/commands/give.js +7 -1
  53. package/bundle/typescript/src/commands/game/sideQuest/commands/give.js.map +1 -1
  54. package/bundle/typescript/src/commands/game/sideQuest/commands/go.d.ts +13 -0
  55. package/bundle/typescript/src/commands/game/sideQuest/commands/go.js +147 -4
  56. package/bundle/typescript/src/commands/game/sideQuest/commands/go.js.map +1 -1
  57. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.d.ts +25 -0
  58. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +199 -0
  59. package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js.map +1 -0
  60. package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.d.ts +15 -0
  61. package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.js +75 -0
  62. package/bundle/typescript/src/commands/game/sideQuest/commands/haggle.js.map +1 -0
  63. package/bundle/typescript/src/commands/game/sideQuest/commands/inv.d.ts +12 -0
  64. package/bundle/typescript/src/commands/game/sideQuest/commands/inv.js +43 -0
  65. package/bundle/typescript/src/commands/game/sideQuest/commands/inv.js.map +1 -0
  66. package/bundle/typescript/src/commands/game/sideQuest/commands/jack.d.ts +17 -0
  67. package/bundle/typescript/src/commands/game/sideQuest/commands/jack.js +64 -0
  68. package/bundle/typescript/src/commands/game/sideQuest/commands/jack.js.map +1 -0
  69. package/bundle/typescript/src/commands/game/sideQuest/commands/kill.d.ts +10 -3
  70. package/bundle/typescript/src/commands/game/sideQuest/commands/kill.js +11 -33
  71. package/bundle/typescript/src/commands/game/sideQuest/commands/kill.js.map +1 -1
  72. package/bundle/typescript/src/commands/game/sideQuest/commands/look.js +47 -4
  73. package/bundle/typescript/src/commands/game/sideQuest/commands/look.js.map +1 -1
  74. package/bundle/typescript/src/commands/game/sideQuest/commands/map.d.ts +12 -0
  75. package/bundle/typescript/src/commands/game/sideQuest/commands/map.js +46 -0
  76. package/bundle/typescript/src/commands/game/sideQuest/commands/map.js.map +1 -0
  77. package/bundle/typescript/src/commands/game/sideQuest/commands/pan.d.ts +12 -0
  78. package/bundle/typescript/src/commands/game/sideQuest/commands/pan.js +55 -0
  79. package/bundle/typescript/src/commands/game/sideQuest/commands/pan.js.map +1 -0
  80. package/bundle/typescript/src/commands/game/sideQuest/commands/persuade.d.ts +18 -0
  81. package/bundle/typescript/src/commands/game/sideQuest/commands/persuade.js +100 -0
  82. package/bundle/typescript/src/commands/game/sideQuest/commands/persuade.js.map +1 -0
  83. package/bundle/typescript/src/commands/game/sideQuest/commands/pick.d.ts +10 -0
  84. package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js +33 -0
  85. package/bundle/typescript/src/commands/game/sideQuest/commands/pick.js.map +1 -0
  86. package/bundle/typescript/src/commands/game/sideQuest/commands/project.d.ts +19 -0
  87. package/bundle/typescript/src/commands/game/sideQuest/commands/project.js +54 -0
  88. package/bundle/typescript/src/commands/game/sideQuest/commands/project.js.map +1 -0
  89. package/bundle/typescript/src/commands/game/sideQuest/commands/read.js +6 -5
  90. package/bundle/typescript/src/commands/game/sideQuest/commands/read.js.map +1 -1
  91. package/bundle/typescript/src/commands/game/sideQuest/commands/reboot.d.ts +12 -0
  92. package/bundle/typescript/src/commands/game/sideQuest/commands/reboot.js +52 -0
  93. package/bundle/typescript/src/commands/game/sideQuest/commands/reboot.js.map +1 -0
  94. package/bundle/typescript/src/commands/game/sideQuest/commands/reload.d.ts +14 -0
  95. package/bundle/typescript/src/commands/game/sideQuest/commands/reload.js +40 -0
  96. package/bundle/typescript/src/commands/game/sideQuest/commands/reload.js.map +1 -0
  97. package/bundle/typescript/src/commands/game/sideQuest/commands/rest.d.ts +15 -0
  98. package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js +59 -0
  99. package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js.map +1 -0
  100. package/bundle/typescript/src/commands/game/sideQuest/commands/say.js +31 -2
  101. package/bundle/typescript/src/commands/game/sideQuest/commands/say.js.map +1 -1
  102. package/bundle/typescript/src/commands/game/sideQuest/commands/search.js +71 -7
  103. package/bundle/typescript/src/commands/game/sideQuest/commands/search.js.map +1 -1
  104. package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.d.ts +12 -0
  105. package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js +58 -0
  106. package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js.map +1 -0
  107. package/bundle/typescript/src/commands/game/sideQuest/commands/silent.d.ts +13 -0
  108. package/bundle/typescript/src/commands/game/sideQuest/commands/silent.js +50 -0
  109. package/bundle/typescript/src/commands/game/sideQuest/commands/silent.js.map +1 -0
  110. package/bundle/typescript/src/commands/game/sideQuest/commands/sneak.d.ts +15 -0
  111. package/bundle/typescript/src/commands/game/sideQuest/commands/sneak.js +35 -0
  112. package/bundle/typescript/src/commands/game/sideQuest/commands/sneak.js.map +1 -0
  113. package/bundle/typescript/src/commands/game/sideQuest/commands/solve.js +32 -11
  114. package/bundle/typescript/src/commands/game/sideQuest/commands/solve.js.map +1 -1
  115. package/bundle/typescript/src/commands/game/sideQuest/commands/spells.d.ts +14 -0
  116. package/bundle/typescript/src/commands/game/sideQuest/commands/spells.js +86 -0
  117. package/bundle/typescript/src/commands/game/sideQuest/commands/spells.js.map +1 -0
  118. package/bundle/typescript/src/commands/game/sideQuest/commands/stance.d.ts +12 -0
  119. package/bundle/typescript/src/commands/game/sideQuest/commands/stance.js +41 -0
  120. package/bundle/typescript/src/commands/game/sideQuest/commands/stance.js.map +1 -0
  121. package/bundle/typescript/src/commands/game/sideQuest/commands/subdue.d.ts +21 -0
  122. package/bundle/typescript/src/commands/game/sideQuest/commands/subdue.js +43 -0
  123. package/bundle/typescript/src/commands/game/sideQuest/commands/subdue.js.map +1 -0
  124. package/bundle/typescript/src/commands/game/sideQuest/commands/surrender.d.ts +20 -0
  125. package/bundle/typescript/src/commands/game/sideQuest/commands/surrender.js +45 -0
  126. package/bundle/typescript/src/commands/game/sideQuest/commands/surrender.js.map +1 -0
  127. package/bundle/typescript/src/commands/game/sideQuest/commands/take.d.ts +8 -0
  128. package/bundle/typescript/src/commands/game/sideQuest/commands/take.js +138 -2
  129. package/bundle/typescript/src/commands/game/sideQuest/commands/take.js.map +1 -1
  130. package/bundle/typescript/src/commands/game/sideQuest/commands/talk.js +8 -0
  131. package/bundle/typescript/src/commands/game/sideQuest/commands/talk.js.map +1 -1
  132. package/bundle/typescript/src/commands/game/sideQuest/commands/unequip.js +6 -3
  133. package/bundle/typescript/src/commands/game/sideQuest/commands/unequip.js.map +1 -1
  134. package/bundle/typescript/src/commands/game/sideQuest/commands/unlock.js +1 -0
  135. package/bundle/typescript/src/commands/game/sideQuest/commands/unlock.js.map +1 -1
  136. package/bundle/typescript/src/commands/game/sideQuest/commands/use.d.ts +24 -0
  137. package/bundle/typescript/src/commands/game/sideQuest/commands/use.js +102 -1
  138. package/bundle/typescript/src/commands/game/sideQuest/commands/use.js.map +1 -1
  139. package/bundle/typescript/src/commands/game/sideQuest/factories/item-factory.js +2 -1
  140. package/bundle/typescript/src/commands/game/sideQuest/factories/item-factory.js.map +1 -1
  141. package/bundle/typescript/src/commands/game/sideQuest/factories/npc-factory.js +7 -2
  142. package/bundle/typescript/src/commands/game/sideQuest/factories/npc-factory.js.map +1 -1
  143. package/bundle/typescript/src/commands/game/sideQuest/factories/puzzle-factory.js +2 -1
  144. package/bundle/typescript/src/commands/game/sideQuest/factories/puzzle-factory.js.map +1 -1
  145. package/bundle/typescript/src/commands/game/sideQuest/factories/room-factory.js +3 -0
  146. package/bundle/typescript/src/commands/game/sideQuest/factories/room-factory.js.map +1 -1
  147. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.d.ts +175 -0
  148. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js +673 -0
  149. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js.map +1 -0
  150. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.d.ts +8 -1
  151. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js +139 -21
  152. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js.map +1 -1
  153. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.d.ts +174 -0
  154. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.js +647 -0
  155. package/bundle/typescript/src/commands/game/sideQuest/factories/scene-seed-generator.js.map +1 -0
  156. package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +138 -9
  157. package/bundle/typescript/src/commands/game/sideQuest/game.js +757 -197
  158. package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
  159. package/bundle/typescript/src/commands/game/sideQuest/minigames/hack-minigame.d.ts +16 -0
  160. package/bundle/typescript/src/commands/game/sideQuest/minigames/hack-minigame.js +101 -0
  161. package/bundle/typescript/src/commands/game/sideQuest/minigames/hack-minigame.js.map +1 -0
  162. package/bundle/typescript/src/commands/game/sideQuest/minigames/lockpick-minigame.d.ts +15 -0
  163. package/bundle/typescript/src/commands/game/sideQuest/minigames/lockpick-minigame.js +95 -0
  164. package/bundle/typescript/src/commands/game/sideQuest/minigames/lockpick-minigame.js.map +1 -0
  165. package/bundle/typescript/src/commands/game/sideQuest/models/door.d.ts +10 -0
  166. package/bundle/typescript/src/commands/game/sideQuest/models/door.js +15 -2
  167. package/bundle/typescript/src/commands/game/sideQuest/models/door.js.map +1 -1
  168. package/bundle/typescript/src/commands/game/sideQuest/models/inventory.d.ts +10 -0
  169. package/bundle/typescript/src/commands/game/sideQuest/models/inventory.js +54 -4
  170. package/bundle/typescript/src/commands/game/sideQuest/models/inventory.js.map +1 -1
  171. package/bundle/typescript/src/commands/game/sideQuest/models/item.d.ts +41 -0
  172. package/bundle/typescript/src/commands/game/sideQuest/models/item.js +106 -3
  173. package/bundle/typescript/src/commands/game/sideQuest/models/item.js.map +1 -1
  174. package/bundle/typescript/src/commands/game/sideQuest/models/npc.d.ts +27 -0
  175. package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +312 -50
  176. package/bundle/typescript/src/commands/game/sideQuest/models/npc.js.map +1 -1
  177. package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +278 -0
  178. package/bundle/typescript/src/commands/game/sideQuest/models/player.js +661 -9
  179. package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
  180. package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.d.ts +75 -1
  181. package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.js +199 -6
  182. package/bundle/typescript/src/commands/game/sideQuest/models/puzzle.js.map +1 -1
  183. package/bundle/typescript/src/commands/game/sideQuest/models/room.d.ts +24 -0
  184. package/bundle/typescript/src/commands/game/sideQuest/models/room.js +53 -18
  185. package/bundle/typescript/src/commands/game/sideQuest/models/room.js.map +1 -1
  186. package/bundle/typescript/src/commands/game/sideQuest/models/scene.d.ts +73 -0
  187. package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +208 -3
  188. package/bundle/typescript/src/commands/game/sideQuest/models/scene.js.map +1 -1
  189. package/bundle/typescript/src/commands/game/sideQuest/scenes/scene1.json +319 -70
  190. package/bundle/typescript/src/commands/game/sideQuest/scenes/scene2.json +661 -227
  191. package/bundle/typescript/src/commands/game/sideQuest/types/player-equipment.d.ts +1 -0
  192. package/bundle/typescript/src/commands/game/sideQuest/types/seed/exit-seed.d.ts +1 -0
  193. package/bundle/typescript/src/commands/game/sideQuest/types/seed/item-seed.d.ts +1 -0
  194. package/bundle/typescript/src/commands/game/sideQuest/types/seed/npc-seed.d.ts +1 -0
  195. package/bundle/typescript/src/commands/game/sideQuest/types/seed/player-seed.d.ts +42 -0
  196. package/bundle/typescript/src/commands/game/sideQuest/types/seed/puzzle-seed.d.ts +15 -1
  197. package/bundle/typescript/src/commands/game/sideQuest/types/seed/room-seed.d.ts +4 -0
  198. package/bundle/typescript/src/commands/game/sideQuest/types/seed/scene-seed.d.ts +8 -0
  199. package/bundle/typescript/src/commands/game/sideQuest/types/seed/win-condition-seed.d.ts +15 -0
  200. package/bundle/typescript/src/commands/game/sideQuest/types/seed/win-condition-seed.js +2 -0
  201. package/bundle/typescript/src/commands/game/sideQuest/types/seed/win-condition-seed.js.map +1 -0
  202. package/bundle/typescript/src/commands/game/sideQuest/types/shared/abstracts.d.ts +1 -0
  203. package/bundle/typescript/src/commands/game/sideQuest/types/shared/abstracts.js +5 -0
  204. package/bundle/typescript/src/commands/game/sideQuest/types/shared/abstracts.js.map +1 -1
  205. package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.d.ts +1 -0
  206. package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.js +1 -0
  207. package/bundle/typescript/src/commands/game/sideQuest/types/shared/item-enum.js.map +1 -1
  208. package/bundle/typescript/src/commands/game/sideQuest/ui.d.ts +3 -0
  209. package/bundle/typescript/src/commands/game/sideQuest/ui.js +95 -6
  210. package/bundle/typescript/src/commands/game/sideQuest/ui.js.map +1 -1
  211. package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.d.ts +60 -0
  212. package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.js +138 -0
  213. package/bundle/typescript/src/commands/game/sideQuest/utilities/auto-fight.js.map +1 -0
  214. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.d.ts +145 -0
  215. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +354 -0
  216. package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js.map +1 -0
  217. package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.d.ts +2 -0
  218. package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.js +12 -0
  219. package/bundle/typescript/src/commands/game/sideQuest/utilities/comm-style.js.map +1 -0
  220. package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.d.ts +22 -0
  221. package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.js +34 -0
  222. package/bundle/typescript/src/commands/game/sideQuest/utilities/dice.js.map +1 -0
  223. package/bundle/typescript/src/commands/game/sideQuest/utilities/fuzzy-match.d.ts +36 -0
  224. package/bundle/typescript/src/commands/game/sideQuest/utilities/fuzzy-match.js +92 -0
  225. package/bundle/typescript/src/commands/game/sideQuest/utilities/fuzzy-match.js.map +1 -0
  226. package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.d.ts +77 -0
  227. package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js +265 -0
  228. package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js.map +1 -0
  229. package/bundle/typescript/src/commands/game/sideQuest/utilities/pursuit.d.ts +16 -0
  230. package/bundle/typescript/src/commands/game/sideQuest/utilities/pursuit.js +36 -0
  231. package/bundle/typescript/src/commands/game/sideQuest/utilities/pursuit.js.map +1 -0
  232. package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js +78 -7
  233. package/bundle/typescript/src/commands/game/sideQuest.sub.cmd.js.map +1 -1
  234. package/bundle/typescript/src/commands/set/global.sub.cmd.js +59 -5
  235. package/bundle/typescript/src/commands/set/global.sub.cmd.js.map +1 -1
  236. package/bundle/typescript/src/tools/ai/ai.class.d.ts +42 -0
  237. package/bundle/typescript/src/tools/ai/ai.class.js +117 -0
  238. package/bundle/typescript/src/tools/ai/ai.class.js.map +1 -0
  239. package/bundle/typescript/src/tools/fsi/fsi.interfaces.d.ts +1 -0
  240. package/bundle/typescript/src/tools/fsi/fsi.interfaces.js.map +1 -1
  241. package/bundle/typescript/src/tools/https/https.class.d.ts +3 -2
  242. package/bundle/typescript/src/tools/https/https.class.js +51 -2
  243. package/bundle/typescript/src/tools/https/https.class.js.map +1 -1
  244. package/bundle/typescript/src/tools/https/https.interface.d.ts +10 -0
  245. package/package.json +1 -1
@@ -2,6 +2,24 @@ import { Category } from '../types/shared/item-enum.js';
2
2
  import { Logger } from '../utilities/logger.js';
3
3
  import { Inventory } from './inventory.js';
4
4
  import { AbstractPlayer } from '../types/shared/abstracts.js';
5
+ export const STANCE_CYCLE = ['manual', 'guarded', 'aggressive'];
6
+ export const SKILL_NAMES = [
7
+ 'firearms', 'blades', 'unarmed',
8
+ 'sneaking', 'perception', 'athletics',
9
+ 'hacking', 'locksmith',
10
+ 'negotiation', 'con', 'intimidation',
11
+ 'first-aid',
12
+ 'assensing', 'spellcasting', 'piloting',
13
+ ];
14
+ /**
15
+ * Skills that fall back to the legacy `combatSkill` attribute when no
16
+ * explicit rating is given -- this is what keeps every existing NPC seed
17
+ * (which only carries combatSkill) rolling exactly the pools it always
18
+ * has, ice included (its Matrix strikes ride `hacking`).
19
+ */
20
+ export const COMBAT_SKILL_FALLBACK = new Set([
21
+ 'firearms', 'blades', 'unarmed', 'hacking',
22
+ ]);
5
23
  export class Player extends AbstractPlayer {
6
24
  _playerName;
7
25
  _currentLocation;
@@ -11,6 +29,180 @@ export class Player extends AbstractPlayer {
11
29
  _lifestyleChoices = [];
12
30
  logger;
13
31
  _currency = 0; // for seeding/debug only
32
+ // Whoever this actor most recently placed/received a commlink call with.
33
+ // No explicit "hang up" -- placing a new call just replaces it. Lets a
34
+ // reply via the ordinary "say" command reach a call partner in a
35
+ // different room, the same way a real call would, without the NPC (or
36
+ // player) needing to learn a separate reply verb. See say.ts and call.ts.
37
+ activeCallPartner;
38
+ // Running silent (see commands/silent.ts): the commlink stays equipped
39
+ // but stops broadcasting -- no calls out, and nobody can raise this
40
+ // actor (call.ts checks both ends). Toggling it drops any active call.
41
+ runningSilent = false;
42
+ // Whoever this actor most recently traded combat exchanges with, and
43
+ // when -- set for BOTH sides by CombatExchange.beginExchange(). Walking
44
+ // out on a live, recent opponent invites a parting shot (see go.ts);
45
+ // firing one (or a death) clears it.
46
+ combatOpponent;
47
+ lastExchangeAt = 0;
48
+ // True while this actor is a combatant in an exchange that's still
49
+ // resolving. Serializes combat per actor: a second attack involving a
50
+ // busy combatant is refused instead of interleaving -- found via a real
51
+ // session where an NPC's overlapping AI loops fired two exchanges at
52
+ // once and the target died TWICE (two kill events, two narrations).
53
+ inExchange = false;
54
+ // Fight-or-flight autopilot (see utilities/auto-fight.ts): the FIGHT
55
+ // half runs automatically at the set temperature, the flight stays
56
+ // manual -- always. Tab cycles it in the UI; "stance" sets it by name.
57
+ // manual -- today's behavior, you type every swing
58
+ // guarded -- auto-fights, but finishes non-lethally (switches to
59
+ // subdue when the target is nearly down) and stands down
60
+ // if they surrender
61
+ // aggressive -- fights until they're dead; surrender means nothing
62
+ autoStance = 'manual';
63
+ // Set by the "surrender" command: this actor has thrown their hands up.
64
+ // Cleared the moment they initiate violence again (see
65
+ // CombatExchange.beginExchange). Guarded auto-fight honors it;
66
+ // aggressive doesn't have to.
67
+ surrendered = false;
68
+ // The archetype picked at launch (ui.ts), kept by NAME so downstream
69
+ // consumers -- scene generation's player dossier, above all -- can say
70
+ // "this is a Decker" instead of re-deriving the concept from stats.
71
+ // undefined for NPCs and for players created outside the launch flow.
72
+ archetypeName;
73
+ // An equipped weapon rides in a HOLSTER until it's deliberately drawn --
74
+ // "brandish"/"holster" flip this by hand (see commands/brandish.ts), and
75
+ // opening an exchange draws steel automatically for anyone whose weapon
76
+ // is about to speak (CombatExchange.beginExchange), so combat never
77
+ // punishes forgetting the verb. What the flag actually buys is the
78
+ // walking-around state: a holstered gun is polite, a drawn one is a
79
+ // threat display every NPC in the room sees happen.
80
+ weaponDrawn = false;
81
+ // ==================== Planes (see utilities/planes.ts) ====================
82
+ // Which plane this actor currently exists on. Ice NPCs live on 'matrix',
83
+ // spirits on 'astral', natively and permanently; a player shifts planes
84
+ // via jack in / project and back.
85
+ plane = 'meat';
86
+ // While off the meat plane, the empty body slumps HERE -- visible,
87
+ // attackable (see planes.ts resolveBodyAttack), and where you snap back
88
+ // to. undefined while on the meat plane, or for native off-plane actors
89
+ // (ice and spirits have no body to leave behind).
90
+ bodyRoom;
91
+ // Matrix only: cold or hot sim, chosen at jack-in. undefined off-Matrix
92
+ // and for native ice.
93
+ simMode;
94
+ // Astral only: room-moves made while projecting. The longer you're out,
95
+ // the harder the snap-back -- drained as stun on return (planes.ts).
96
+ astralTicks = 0;
97
+ // The DECK you're jacked in on (set by planes.ts enterMatrix, cleared on
98
+ // leaving). Matrix damage is HARDWARE damage: it lands on this item's
99
+ // device condition monitor (see Item's cyberdeck section), persists
100
+ // across jack-outs, and a full track BRICKS the deck -- forced dump,
101
+ // and no re-entry on that deck until it's repaired.
102
+ activeDeck;
103
+ /** The best working cyberdeck carried (bricked ones don't count). */
104
+ getCyberdeck() {
105
+ return this._inventory.getAllItems()
106
+ .filter(i => i.isCyberdeck() && !i.isBricked)
107
+ .sort((a, b) => b.qualityBonus - a.qualityBonus)[0];
108
+ }
109
+ /** Any deck at all, bricked included -- for status displays and repair. */
110
+ getAnyCyberdeck() {
111
+ return this.activeDeck ?? this._inventory.getAllItems()
112
+ .filter(i => i.isCyberdeck())
113
+ .sort((a, b) => b.qualityBonus - a.qualityBonus)[0];
114
+ }
115
+ // ==================== PAN (see commands/pan.ts, hack.ts) ====================
116
+ // Hacked dark: an enemy cracked your PAN and cut the AR overlay. Costs
117
+ // the commlink initiative bonus and drops calls, until "reboot". Distinct
118
+ // from runningSilent (chosen dark) -- this one was done TO you.
119
+ arOffline = false;
120
+ /**
121
+ * The PAN's master device: your slaved gear defends behind its
122
+ * firewall (SR slaving, simplified). Best working deck beats the
123
+ * equipped commlink; no devices = no PAN (and nothing to hack).
124
+ */
125
+ getPanMaster() {
126
+ const deck = this.getCyberdeck();
127
+ const link = this._equipment.head?.commlink ?? undefined;
128
+ if (deck && link)
129
+ return deck.qualityBonus >= link.qualityBonus ? deck : link;
130
+ return deck ?? link;
131
+ }
132
+ /**
133
+ * Defense against a PAN intrusion: Logic + the master's firewall
134
+ * (+2 while running silent -- a hidden PAN must be FOUND first).
135
+ */
136
+ getPanDefensePool() {
137
+ const master = this.getPanMaster();
138
+ const silentBonus = this.runningSilent ? 2 : 0;
139
+ return Math.max(0, this.logic + (master?.qualityBonus ?? 0) + silentBonus);
140
+ }
141
+ get matrixTaken() {
142
+ return this.getAnyCyberdeck()?.deckDamage ?? 0;
143
+ }
144
+ get maxMatrixBoxes() {
145
+ return this.getAnyCyberdeck()?.maxDeckBoxes ?? 0;
146
+ }
147
+ /** Routes Matrix combat damage into the jacked-in deck's hardware track. */
148
+ takeMatrixDamage(boxes) {
149
+ const deck = this.getAnyCyberdeck();
150
+ return deck ? deck.takeDeckDamage(boxes) : 0;
151
+ }
152
+ get matrixCrashed() {
153
+ return this.getAnyCyberdeck()?.isBricked ?? false;
154
+ }
155
+ /** The deck track, same rendering as conditionSummary(). */
156
+ matrixSummary() {
157
+ return this.getAnyCyberdeck()?.deckSummary() ?? '(no deck)';
158
+ }
159
+ /**
160
+ * Cross-plane perception, the single rule everything filters through:
161
+ * same plane sees same plane; the astral perceives the meat world
162
+ * (shifted, not gone -- auras and movement, dimly); meat and Matrix see
163
+ * nothing beyond their own plane. Used by see/hear guards, targeting,
164
+ * and room listings.
165
+ */
166
+ canPerceive(other) {
167
+ if (this.plane === other.plane)
168
+ return true;
169
+ return this.plane === 'astral' && other.plane === 'meat';
170
+ }
171
+ // True while an AI-driven action is parked waiting for an in-flight
172
+ // exchange to settle (CombatExchange.waitForLull). One queued action per
173
+ // actor: a second colliding attempt is refused outright instead of
174
+ // stacking a burst of delayed attacks behind the same lull.
175
+ waitingForLull = false;
176
+ // Combat attributes (see ICombatAttributes). Defaults make an unseeded
177
+ // actor a competent-but-mortal street-level runner; seeds can override
178
+ // per-NPC to make a hardened enforcer or a soft civilian.
179
+ body;
180
+ agility;
181
+ reaction;
182
+ combatSkill;
183
+ magic;
184
+ strength;
185
+ willpower;
186
+ logic;
187
+ intuition;
188
+ charisma;
189
+ edge;
190
+ resonance;
191
+ adept;
192
+ // Edge is a per-job pool: spend a point (see commands/edge.ts) to arm a
193
+ // boost that supercharges your next strike. Refreshed to `edge` on every
194
+ // new job (see Game.continueToNextScene).
195
+ edgeRemaining = 0;
196
+ edgeBoostArmed = false;
197
+ // Physical condition monitor, straight from SR5: 8 + (Body / 2, rounded
198
+ // up) boxes. Damage fills boxes; a full track is death.
199
+ _damageTaken = 0;
200
+ // Stun condition monitor -- the second track, filled by spell drain (see
201
+ // cast.ts). A full stun track is a knockout, not death, but overflow
202
+ // past it spills into the physical track. Sized off Reaction as the
203
+ // stand-in for SR's Willpower (the closest thing to a mental attribute
204
+ // here without adding one): 8 + (Reaction / 2, rounded up).
205
+ _stunTaken = 0;
14
206
  constructor(config) {
15
207
  super();
16
208
  this._playerName = config.playerName;
@@ -18,8 +210,53 @@ export class Player extends AbstractPlayer {
18
210
  this._inventory = new Inventory();
19
211
  this.logger = Logger.getInstance();
20
212
  this._lifestyleChoices = config.lifestyleChoices ?? [];
213
+ this.body = config.combat?.body ?? 4;
214
+ this.agility = config.combat?.agility ?? 4;
215
+ this.reaction = config.combat?.reaction ?? 4;
216
+ this.combatSkill = config.combat?.combatSkill ?? 3;
217
+ this.magic = config.combat?.magic ?? 0;
218
+ this.strength = config.combat?.strength ?? 3;
219
+ this.willpower = config.combat?.willpower ?? 3;
220
+ this.logic = config.combat?.logic ?? 3;
221
+ this.intuition = config.combat?.intuition ?? 3;
222
+ this.charisma = config.combat?.charisma ?? 3;
223
+ this.edge = config.combat?.edge ?? 2;
224
+ this.edgeRemaining = this.edge;
225
+ this.resonance = config.combat?.resonance ?? 0;
226
+ this.adept = config.combat?.adept ?? false;
227
+ this.skills = { ...(config.combat?.skills ?? {}) };
21
228
  this._equipment = this.initializeEquipment();
22
229
  }
230
+ // ======================== Skills (see SkillName) ========================
231
+ /** Explicit per-skill ratings; anything absent resolves via skillRating(). */
232
+ skills = {};
233
+ // Karma: the advancement currency, earned per completed job (see
234
+ // Scene.checkWinCondition) and spent in the hideout via "advance" --
235
+ // gear stops being the only progression axis.
236
+ karma = 0;
237
+ /**
238
+ * The effective rating for a skill: explicit rating if set; otherwise
239
+ * combat skills fall back to the legacy `combatSkill` attribute (which
240
+ * keeps every skill-less NPC seed rolling its historical pools), and
241
+ * everything else is 0 (unskilled).
242
+ */
243
+ skillRating(name) {
244
+ const explicit = this.skills[name];
245
+ if (typeof explicit === 'number')
246
+ return Math.max(0, explicit);
247
+ return COMBAT_SKILL_FALLBACK.has(name) ? this.combatSkill : 0;
248
+ }
249
+ /**
250
+ * Which combat skill the current armament exercises: firearms for a
251
+ * drawn gun, blades for a drawn melee weapon, unarmed for empty hands
252
+ * (or anything holstered -- see weaponDrawn).
253
+ */
254
+ activeCombatSkill() {
255
+ const weapon = this.weaponDrawn ? this.getCarriedWeapon() : null;
256
+ if (!weapon)
257
+ return 'unarmed';
258
+ return weapon.isFirearm() ? 'firearms' : 'blades';
259
+ }
23
260
  initializeEquipment() {
24
261
  return {
25
262
  head: {
@@ -31,6 +268,7 @@ export class Player extends AbstractPlayer {
31
268
  commlink: null
32
269
  },
33
270
  torso: {
271
+ clothing: null,
34
272
  armorVest: null,
35
273
  armorCoat: null,
36
274
  backpack: null,
@@ -97,7 +335,9 @@ export class Player extends AbstractPlayer {
97
335
  this._equipment = newEquipment;
98
336
  }
99
337
  get maxCarryingWeight() {
100
- return this._maxCarryingWeight;
338
+ // Strength-derived: 20 + 10/point. The old flat default (50kg) is
339
+ // exactly what a strength-3 runner gets, so pre-sheet balance holds.
340
+ return 20 + this.strength * 10;
101
341
  }
102
342
  set maxCarryingWeight(weight) {
103
343
  if (weight > 0) {
@@ -109,10 +349,19 @@ export class Player extends AbstractPlayer {
109
349
  }
110
350
  get currentWeight() {
111
351
  let totalWeight = this._inventory.getWeight();
112
- for (const slot in this._equipment) {
113
- const equippedItem = this._equipment[slot];
114
- if (equippedItem) {
115
- totalWeight += equippedItem.weight;
352
+ // Equipment is nested one level deeper than inventory: groups (head,
353
+ // torso...) containing slots. This used to add `group.weight` --
354
+ // undefined -- which turned the total into NaN, so the carrying cap
355
+ // NEVER triggered (NaN compares false against everything). Found when
356
+ // a "take all" verification watched a 100kg engine block walk into a
357
+ // 50kg pack.
358
+ for (const group of Object.values(this._equipment)) {
359
+ if (!group)
360
+ continue;
361
+ for (const item of Object.values(group)) {
362
+ if (item) {
363
+ totalWeight += item.weight;
364
+ }
116
365
  }
117
366
  }
118
367
  return totalWeight;
@@ -149,12 +398,29 @@ export class Player extends AbstractPlayer {
149
398
  if (!groupRef) {
150
399
  return `You have no ${group} equipment group initialized.`;
151
400
  }
152
- if (groupRef[slot]) {
153
- return `You already have something equipped in your ${group}.${slot} slot.`;
401
+ const previousItem = groupRef[slot];
402
+ let swapNotice = '';
403
+ if (previousItem) {
404
+ if (previousItem.name === item.name) {
405
+ return `You already have ${item.name} equipped in your ${group}.${slot} slot.`;
406
+ }
407
+ this.unequipBySlot(group, slot);
408
+ swapNotice = ` (unequipped ${previousItem.name})`;
154
409
  }
155
410
  groupRef[slot] = item;
156
411
  this.removeInventory(item);
157
- return `You equipped ${item.name} in your ${group}.${slot} slot.`;
412
+ // A freshly equipped weapon goes to the HOLSTER, not the hand -- see
413
+ // the weaponDrawn field. Swapping weapons re-holsters even if the old
414
+ // one was out: the moment of the exchange, both hands are busy.
415
+ if (Player.isWeaponSlot(group, slot)) {
416
+ this.weaponDrawn = false;
417
+ return `You slide ${item.name} into your holster.${swapNotice} "brandish" when you want it in hand.`;
418
+ }
419
+ return `You equipped ${item.name} in your ${group}.${slot} slot.${swapNotice}`;
420
+ }
421
+ /** True for the hand slots that hold a wielded weapon. */
422
+ static isWeaponSlot(group, slot) {
423
+ return (group === 'rightHand' || group === 'leftHand') && slot === 'weapon';
158
424
  }
159
425
  unequip(input) {
160
426
  const slotInfo = this.determineEquipmentSlot(input);
@@ -197,8 +463,14 @@ export class Player extends AbstractPlayer {
197
463
  case Category.Commlink:
198
464
  return { group: 'head', slot: 'commlink' };
199
465
  // TORSO
466
+ case Category.Clothing:
467
+ case 'shirt':
468
+ case 'hoodie':
469
+ case 'top':
470
+ return { group: 'torso', slot: 'clothing' };
200
471
  case Category.Armor:
201
472
  case Category.BodyArmor:
473
+ case Category.ArmorClothing:
202
474
  case 'armor vest':
203
475
  return { group: 'torso', slot: 'armorVest' };
204
476
  case 'armor coat':
@@ -292,14 +564,21 @@ export class Player extends AbstractPlayer {
292
564
  }
293
565
  this.addInventory(itemToUnequip);
294
566
  groupRef[slot] = null;
567
+ // No weapon, nothing drawn -- keeps the flag honest for the next equip.
568
+ if (Player.isWeaponSlot(group, slot))
569
+ this.weaponDrawn = false;
295
570
  return `You unequipped ${itemToUnequip.name} from your ${group}.${slot} slot.`;
296
571
  }
297
572
  hear(actor, message) {
573
+ // Cross-plane silence: a Matrix persona's chatter never reaches meat
574
+ // ears and vice versa; the astral hears the meat world it overlays.
575
+ if (!this.canPerceive(actor))
576
+ return;
298
577
  this.logger.write(`${this.name} heard ${actor.name}`);
299
578
  this.logger.logWithColor(`{white-fg}${actor.name}:{/white-fg} ${message}`, (actor === this) ? 'yellow' : 'green');
300
579
  }
301
580
  see(actor, verb, details) {
302
- if (actor !== this) {
581
+ if (actor !== this && this.canPerceive(actor)) {
303
582
  this.logger.write(`You see ${actor.name} ${verb} ${details}`);
304
583
  this.logger.logWithColor(`${actor.name} ${verb} ${details}`, 'cyan');
305
584
  }
@@ -323,6 +602,379 @@ export class Player extends AbstractPlayer {
323
602
  this.logger.write(`${this.name} has left ${this._currentLocation.name}`);
324
603
  }
325
604
  }
605
+ // ======================== Combat (see commands/attack.ts) ========================
606
+ static ARMOR_CATEGORIES = new Set([
607
+ Category.Armor, Category.BodyArmor, Category.ArmorClothing, Category.Shield,
608
+ Category.Helmet, Category.ArmArmor, Category.LegArmor, Category.Boots, Category.Gloves,
609
+ ]);
610
+ get maxConditionBoxes() {
611
+ return 8 + Math.ceil(this.body / 2);
612
+ }
613
+ get damageTaken() {
614
+ return this._damageTaken;
615
+ }
616
+ get maxStunBoxes() {
617
+ // Willpower-based, as SR intends (Reaction stood in before Willpower
618
+ // existed on the sheet). Default willpower 3 keeps the same 10 boxes
619
+ // the old reaction-4 default produced.
620
+ return 8 + Math.ceil(this.willpower / 2);
621
+ }
622
+ get stunTaken() {
623
+ return this._stunTaken;
624
+ }
625
+ /**
626
+ * SR5 wound modifier: -1 die on every pool per 3 full boxes of damage,
627
+ * and the two tracks STACK -- being shot and drained both make you worse
628
+ * at everything, including not getting shot.
629
+ */
630
+ get woundModifier() {
631
+ return -(Math.floor(this._damageTaken / 3) + Math.floor(this._stunTaken / 3));
632
+ }
633
+ /** Dead: the physical track is full. */
634
+ isDown() {
635
+ return this._damageTaken >= this.maxConditionBoxes;
636
+ }
637
+ /** Out cold: the stun track is full (but not necessarily dead). */
638
+ isUnconscious() {
639
+ return this._stunTaken >= this.maxStunBoxes;
640
+ }
641
+ /** Out of the fight either way -- the gate every action should check. */
642
+ isIncapacitated() {
643
+ return this.isDown() || this.isUnconscious();
644
+ }
645
+ /** Applies damage boxes; returns the number actually taken. */
646
+ takeDamage(boxes) {
647
+ const taken = Math.max(0, Math.floor(boxes));
648
+ this._damageTaken = Math.min(this.maxConditionBoxes, this._damageTaken + taken);
649
+ return taken;
650
+ }
651
+ /**
652
+ * Clears damage boxes; returns how many were actually healed (can be
653
+ * less than asked when the track is nearly clean). Healing also restores
654
+ * wound-modifier dice, since that's derived from damageTaken.
655
+ */
656
+ healDamage(boxes) {
657
+ const before = this._damageTaken;
658
+ this._damageTaken = Math.max(0, before - Math.max(0, Math.floor(boxes)));
659
+ return before - this._damageTaken;
660
+ }
661
+ /**
662
+ * Applies stun boxes; anything past a full stun track OVERFLOWS into the
663
+ * physical track (SR's rule -- enough drain doesn't just knock you out,
664
+ * it starts killing you). Returns what landed where.
665
+ */
666
+ takeStun(boxes) {
667
+ const asked = Math.max(0, Math.floor(boxes));
668
+ const space = this.maxStunBoxes - this._stunTaken;
669
+ const stun = Math.min(asked, space);
670
+ this._stunTaken += stun;
671
+ const overflow = asked - stun;
672
+ if (overflow > 0) {
673
+ this.takeDamage(overflow);
674
+ }
675
+ return { stun, overflow };
676
+ }
677
+ /** e.g. "■■■□□□□□□□ (3/10)" */
678
+ conditionSummary() {
679
+ const max = this.maxConditionBoxes;
680
+ const filled = Math.min(max, this._damageTaken);
681
+ return `${'■'.repeat(filled)}${'□'.repeat(max - filled)} (${filled}/${max})`;
682
+ }
683
+ /** Clears stun boxes; returns how many were actually recovered. */
684
+ healStun(boxes) {
685
+ const before = this._stunTaken;
686
+ this._stunTaken = Math.max(0, before - Math.max(0, Math.floor(boxes)));
687
+ return before - this._stunTaken;
688
+ }
689
+ /** The stun track, same rendering as conditionSummary(). */
690
+ stunSummary() {
691
+ const max = this.maxStunBoxes;
692
+ const filled = Math.min(max, this._stunTaken);
693
+ return `${'■'.repeat(filled)}${'□'.repeat(max - filled)} (${filled}/${max})`;
694
+ }
695
+ /**
696
+ * The weapon actually in hand (right hand first, then left), or bare
697
+ * fists. Damage value and the attack-pool bonus both come off the item's
698
+ * quality rating -- gear is the whole progression system in this game,
699
+ * so it has to be what combat power flows from.
700
+ */
701
+ getWeaponProfile() {
702
+ // Off the meat plane your gun is just a memory: Matrix combat runs on
703
+ // attack programs (Logic-scaled), astral combat on raw will given
704
+ // form (Magic-scaled). Hot-sim's deeper immersion sharpens the
705
+ // programs' bite.
706
+ if (this.plane === 'matrix') {
707
+ const hot = this.simMode === 'hot' ? 1 : 0;
708
+ // The deck IS the arsenal: its quality feeds program damage the way
709
+ // a better gun feeds DV. A technomancer's living persona weaves
710
+ // resonance instead of running programs.
711
+ if (this.isTechnomancer()) {
712
+ return { name: 'resonance surge', damage: 2 + Math.floor(this.logic / 2) + Math.floor(this.resonance / 2) + hot, poolBonus: 0 };
713
+ }
714
+ const deck = this.activeDeck ?? this.getCyberdeck();
715
+ return { name: 'attack programs', damage: 2 + Math.floor(this.logic / 2) + (deck?.qualityBonus ?? 0) + hot, poolBonus: 0 };
716
+ }
717
+ if (this.plane === 'astral') {
718
+ return { name: 'astral strike', damage: 2 + Math.floor(Math.max(1, this.magic) / 2), poolBonus: 0 };
719
+ }
720
+ // Muscle drives melee: unarmed 1 + STR/2, melee weapons 2 + STR/2 +
721
+ // quality (a strength-3 actor lands exactly the old flat numbers).
722
+ // Firearms don't care how strong you are. A HOLSTERED weapon counts
723
+ // for nothing -- until it's drawn (brandish, or the auto-draw when an
724
+ // exchange opens), your hands are empty.
725
+ const strengthBase = Math.floor(this.strength / 2);
726
+ const weapon = this.weaponDrawn ? this.getCarriedWeapon() : null;
727
+ if (!weapon) {
728
+ return { name: 'bare fists', damage: 1 + strengthBase, poolBonus: 0 };
729
+ }
730
+ const bonus = weapon.qualityBonus;
731
+ const base = weapon.category === Category.MeleeWeapon ? 2 + strengthBase : 4;
732
+ return { name: weapon.name, damage: base + bonus, poolBonus: bonus };
733
+ }
734
+ /** The weapon riding in a hand slot, drawn or holstered. */
735
+ getCarriedWeapon() {
736
+ return this._equipment.rightHand?.weapon ?? this._equipment.leftHand?.weapon ?? null;
737
+ }
738
+ /** Draws the carried weapon; returns it, or null if nothing changed
739
+ * (unarmed, or already in hand). */
740
+ drawWeapon() {
741
+ const weapon = this.getCarriedWeapon();
742
+ if (!weapon || this.weaponDrawn)
743
+ return null;
744
+ this.weaponDrawn = true;
745
+ return weapon;
746
+ }
747
+ /** Stows the drawn weapon; returns it, or null if nothing changed. */
748
+ stowWeapon() {
749
+ const weapon = this.getCarriedWeapon();
750
+ if (!weapon || !this.weaponDrawn)
751
+ return null;
752
+ this.weaponDrawn = false;
753
+ return weapon;
754
+ }
755
+ /** Total soak dice from every equipped armor-category item. */
756
+ getArmorValue() {
757
+ let total = 0;
758
+ for (const group of Object.values(this._equipment)) {
759
+ if (!group)
760
+ continue;
761
+ for (const item of Object.values(group)) {
762
+ if (item && Player.ARMOR_CATEGORIES.has(item.category)) {
763
+ total += 1 + item.qualityBonus;
764
+ }
765
+ }
766
+ }
767
+ return total;
768
+ }
769
+ getAttackPool() {
770
+ // Plane decides which attribute drives the fight: meat is muscle
771
+ // (Agility), the Matrix is mind (Logic, hot-sim +2), the astral is
772
+ // will (Willpower + half Magic). The SKILL is per-armament now (see
773
+ // activeCombatSkill / skillRating): firearms, blades, or unarmed in
774
+ // the meat; hacking is cybercombat; astral strikes ride unarmed (will
775
+ // given form). Wounds and sustaining drag everywhere.
776
+ if (this.plane === 'matrix') {
777
+ const hot = this.simMode === 'hot' ? 2 : 0;
778
+ return Math.max(0, this.logic + this.skillRating('hacking') + Math.floor(this.resonance / 2) + hot + this.woundModifier - this.sustainingPenalty);
779
+ }
780
+ if (this.plane === 'astral') {
781
+ return Math.max(0, this.willpower + Math.floor(this.magic / 2) + this.skillRating('unarmed') + this.woundModifier - this.sustainingPenalty);
782
+ }
783
+ // An adept's magic lives in their hands: half of it (rounded down)
784
+ // rides every meat-plane strike -- killing hands, improved reflexes.
785
+ const adeptBonus = this.adept ? Math.floor(this.magic / 2) : 0;
786
+ return Math.max(0, this.agility + this.skillRating(this.activeCombatSkill()) + adeptBonus + this.getWeaponProfile().poolBonus + this.woundModifier - this.sustainingPenalty);
787
+ }
788
+ /**
789
+ * The subdual pool: bare-hands work regardless of what's in the other
790
+ * fist -- Agility + unarmed (never the drawn weapon's skill or bonus).
791
+ * See subdue.ts.
792
+ */
793
+ getUnarmedAttackPool() {
794
+ const adeptBonus = this.adept ? Math.floor(this.magic / 2) : 0;
795
+ return Math.max(0, this.agility + this.skillRating('unarmed') + adeptBonus + this.woundModifier - this.sustainingPenalty);
796
+ }
797
+ getDefensePool() {
798
+ // Reaction plus HALF intuition (rounded down) -- full SR
799
+ // Reaction+Intuition turned text-paced exchanges into whiff-fests, so
800
+ // gut feel contributes at half weight. Intuition 3 (the default) adds
801
+ // +1 over the old reaction-only pool. Off-plane variants swap the
802
+ // reflex attribute but keep the same shape.
803
+ if (this.plane === 'matrix') {
804
+ const hot = this.simMode === 'hot' ? 1 : 0;
805
+ return Math.max(0, this.intuition + Math.floor(this.logic / 2) + Math.floor(this.resonance / 2) + hot + this.woundModifier - this.sustainingPenalty);
806
+ }
807
+ if (this.plane === 'astral') {
808
+ return Math.max(0, this.intuition + Math.floor(this.magic / 2) + this.woundModifier - this.sustainingPenalty);
809
+ }
810
+ // Combat sense: the adept's magic reads the fight before it happens.
811
+ const adeptBonus = this.adept ? Math.floor(this.magic / 2) : 0;
812
+ return Math.max(0, this.reaction + Math.floor(this.intuition / 2) + adeptBonus + this.woundModifier - this.sustainingPenalty);
813
+ }
814
+ getSoakPool() {
815
+ // Matrix: firewall -- Logic plus the DECK's quality (your deck is your
816
+ // armor); a technomancer's firewall is Willpower plus half resonance
817
+ // (their mind guards itself). Astral: pure Willpower; worn armor is
818
+ // meat left behind.
819
+ if (this.plane === 'matrix') {
820
+ if (this.isTechnomancer()) {
821
+ return Math.max(0, this.willpower + Math.floor(this.resonance / 2));
822
+ }
823
+ const deck = this.activeDeck ?? this.getCyberdeck();
824
+ return Math.max(0, this.logic + (deck?.qualityBonus ?? 0));
825
+ }
826
+ if (this.plane === 'astral') {
827
+ return Math.max(0, this.willpower);
828
+ }
829
+ return Math.max(0, this.body + this.getArmorValue() + this._sustainedArmor);
830
+ }
831
+ /**
832
+ * Initiative: Reaction + Intuition (straight SR), plus the AR edge from
833
+ * an equipped, ONLINE commlink -- the tactical overlay feeding you
834
+ * angles. Running silent kills the overlay along with the broadcast, so
835
+ * going dark costs you speed on the draw: the silent tradeoff grows
836
+ * teeth. Wounds and sustained spells slow you like everything else.
837
+ */
838
+ getInitiativePool() {
839
+ // Matrix speed is thought-speed: Logic + Intuition, hot-sim +2 (the
840
+ // whole point of running hot). Astral forms move at the speed of
841
+ // intention: Intuition doubled.
842
+ if (this.plane === 'matrix') {
843
+ const hot = this.simMode === 'hot' ? 2 : 0;
844
+ return Math.max(0, this.logic + this.intuition + Math.floor(this.resonance / 2) + hot + this.woundModifier - this.sustainingPenalty);
845
+ }
846
+ if (this.plane === 'astral') {
847
+ return Math.max(0, this.intuition * 2 + this.woundModifier - this.sustainingPenalty);
848
+ }
849
+ const link = this._equipment.head?.commlink;
850
+ // AR needs a link that's on you, broadcasting, and not hacked dark.
851
+ const arBonus = link && !this.runningSilent && !this.arOffline ? link.qualityBonus : 0;
852
+ return Math.max(0, this.reaction + this.intuition + arBonus + this.woundModifier - this.sustainingPenalty);
853
+ }
854
+ /**
855
+ * If an edge boost is armed (see commands/edge.ts), consume it: returns
856
+ * the bonus dice for the strike being rolled right now and disarms.
857
+ * Called by the strike-profile builders, so the boost lands on the next
858
+ * attack/subdue/manabolt this actor initiates.
859
+ */
860
+ consumeEdgeBoost() {
861
+ if (!this.edgeBoostArmed)
862
+ return 0;
863
+ this.edgeBoostArmed = false;
864
+ return this.edge;
865
+ }
866
+ /** New job, fresh luck -- and a fresh seat at the negotiating table. */
867
+ refreshEdge() {
868
+ this.edgeRemaining = this.edge;
869
+ this.edgeBoostArmed = false;
870
+ this.haggledThisJob = false;
871
+ }
872
+ // ======================== Magic (see commands/cast.ts) ========================
873
+ // Two roads to casting. GEAR-DRIVEN: what a mundane can cast comes from
874
+ // the spell formulas/grimoires they carry, at a flat scholar's base pool
875
+ // -- consistent with the rest of the game, where gear IS the progression
876
+ // system. INNATE: an Awakened actor (magic attribute > 0) knows every
877
+ // spell without any gear -- the talent is the knowledge; formulas are
878
+ // how mundanes fake it -- and casts from magic + 2 instead of the flat
879
+ // base, so a strong mage out-throws any book-taught scholar. A Focus
880
+ // stacks on top of either road.
881
+ /** Base casting dice for a mundane casting from a formula/grimoire. */
882
+ static BASE_MAGIC_POOL = 5;
883
+ /** Awakened: innate talent, no gear required. Adepts count -- their
884
+ * aura burns like any mage's, and assensing works -- but they channel
885
+ * magic into the body, not spells (see knowsSpell/isCaster). */
886
+ isAwakened() {
887
+ return this.magic > 0;
888
+ }
889
+ /** Technomancer: living persona -- the Matrix without a deck (see ICombatAttributes.resonance). */
890
+ isTechnomancer() {
891
+ return this.resonance > 0;
892
+ }
893
+ // A sustained spell (currently only the armor spell) grants its bonus
894
+ // continuously but costs concentration: -2 dice on every active pool
895
+ // while it's up. See cast.ts.
896
+ _sustainedArmor = 0;
897
+ get sustainedArmor() {
898
+ return this._sustainedArmor;
899
+ }
900
+ setSustainedArmor(bonus) {
901
+ this._sustainedArmor = Math.max(0, Math.floor(bonus));
902
+ }
903
+ get sustainingPenalty() {
904
+ // Each sustained effect costs -2 dice across the board: the armor
905
+ // spell, and open astral senses (see commands/assense.ts). They stack
906
+ // -- holding both is walking around half-blind.
907
+ return (this._sustainedArmor > 0 ? 2 : 0) + (this.astralPerceiving ? 2 : 0);
908
+ }
909
+ // Astral perception (see commands/assense.ts): senses held OPEN as a
910
+ // sustained state -- passive aura reads, at -2 dice to everything else.
911
+ // Meat-plane only; projecting IS full astral senses (cleared on
912
+ // project), and the Matrix has no auras at all.
913
+ astralPerceiving = false;
914
+ // Sneak-walking (see commands/sneak.ts): room entries become opposed
915
+ // Sneaking-vs-Perception rolls per witness (NPC.see). Broken by any
916
+ // loud act -- attacking, casting, speaking, brandishing, calling.
917
+ sneaking = false;
918
+ // One haggle per job (see commands/haggle.ts); reset with the edge pool
919
+ // on each new job (refreshEdge).
920
+ haggledThisJob = false;
921
+ // Where this actor just came from and the direction BACK -- the HUD's
922
+ // breadcrumb (see game.ts updateStatus / go.ts). Big scenes (the hub's
923
+ // 18 rooms) made "which way did I come in" a real question. Cleared on
924
+ // scene transitions; only meaningful room-to-room.
925
+ cameFrom;
926
+ // Social-play bookkeeping ON THE TARGET (see commands/persuade.ts):
927
+ // "persuaded-by:<name>" marks who already made their play on this actor
928
+ // this job. NPCs are rebuilt each scene, so it resets naturally.
929
+ socialMarks = new Set();
930
+ /** The best Focus item on this actor, if any -- scales casting power. */
931
+ getBestFocus() {
932
+ return this._inventory.getAllItems()
933
+ .filter(i => i.category === Category.Focus)
934
+ .sort((a, b) => b.qualityBonus - a.qualityBonus)[0];
935
+ }
936
+ getMagicPool() {
937
+ const focus = this.getBestFocus();
938
+ const focusBonus = focus ? 1 + focus.qualityBonus : 0;
939
+ // SR-proper when the SPELLCASTING skill is rated: Magic + Spellcasting
940
+ // for the Awakened, base + skill for a book-caster. Unrated falls back
941
+ // to the legacy shapes (talent max(5, magic+2); scholar's flat 5), so
942
+ // skill-less NPC mages keep their historical pools.
943
+ const spellSkill = this.skillRating('spellcasting');
944
+ const base = this.isAwakened()
945
+ ? (spellSkill > 0 ? this.magic + spellSkill : Math.max(Player.BASE_MAGIC_POOL, this.magic + 2))
946
+ : Player.BASE_MAGIC_POOL + spellSkill;
947
+ return Math.max(0, base + focusBonus + this.woundModifier - this.sustainingPenalty);
948
+ }
949
+ /**
950
+ * Awakened actors know every spell innately. A mundane knows a spell if
951
+ * they carry its formula (item name must contain the spell's name) or
952
+ * any grimoire (a spellbook teaches everything).
953
+ */
954
+ knowsSpell(spellName) {
955
+ // An adept's magic is muscle memory, not incantation -- no spells,
956
+ // not even from a formula in hand. Their magic already lives in their
957
+ // meat-plane combat pools.
958
+ if (this.adept)
959
+ return false;
960
+ if (this.isAwakened())
961
+ return true;
962
+ const wanted = spellName.toLowerCase();
963
+ return this._inventory.getAllItems().some(i => i.category === Category.Grimoire ||
964
+ (i.category === Category.SpellFormula && i.name.toLowerCase().includes(wanted)));
965
+ }
966
+ /**
967
+ * The gate for attempting magic at all (ward bypass included): innate
968
+ * talent, or any magic gear.
969
+ */
970
+ isCaster() {
971
+ if (this.adept)
972
+ return false; // body-magic only -- see knowsSpell
973
+ return this.isAwakened() || this._inventory.getAllItems().some(i => i.category === Category.SpellFormula ||
974
+ i.category === Category.Grimoire ||
975
+ i.category === Category.Focus);
976
+ }
977
+ // ================================================================================
326
978
  get lifestyleChoices() {
327
979
  return this._lifestyleChoices;
328
980
  }