idlerpg.sh 0.1.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 (478) hide show
  1. package/README.md +171 -0
  2. package/dist/affix/AffixRegistry.d.ts +68 -0
  3. package/dist/affix/AffixRegistry.d.ts.map +1 -0
  4. package/dist/affix/AffixRegistry.js +245 -0
  5. package/dist/affix/AffixRegistry.js.map +1 -0
  6. package/dist/affix/PluginLoader.d.ts +66 -0
  7. package/dist/affix/PluginLoader.d.ts.map +1 -0
  8. package/dist/affix/PluginLoader.js +321 -0
  9. package/dist/affix/PluginLoader.js.map +1 -0
  10. package/dist/affix/builtin.d.ts +6 -0
  11. package/dist/affix/builtin.d.ts.map +1 -0
  12. package/dist/affix/builtin.js +311 -0
  13. package/dist/affix/builtin.js.map +1 -0
  14. package/dist/affix/index.d.ts +8 -0
  15. package/dist/affix/index.d.ts.map +1 -0
  16. package/dist/affix/index.js +11 -0
  17. package/dist/affix/index.js.map +1 -0
  18. package/dist/affix/types.d.ts +152 -0
  19. package/dist/affix/types.d.ts.map +1 -0
  20. package/dist/affix/types.js +8 -0
  21. package/dist/affix/types.js.map +1 -0
  22. package/dist/ai/PlayKitClient.d.ts +208 -0
  23. package/dist/ai/PlayKitClient.d.ts.map +1 -0
  24. package/dist/ai/PlayKitClient.js +721 -0
  25. package/dist/ai/PlayKitClient.js.map +1 -0
  26. package/dist/ai/index.d.ts +5 -0
  27. package/dist/ai/index.d.ts.map +1 -0
  28. package/dist/ai/index.js +8 -0
  29. package/dist/ai/index.js.map +1 -0
  30. package/dist/app/App.d.ts +85 -0
  31. package/dist/app/App.d.ts.map +1 -0
  32. package/dist/app/App.js +296 -0
  33. package/dist/app/App.js.map +1 -0
  34. package/dist/app/EventBus.d.ts +222 -0
  35. package/dist/app/EventBus.d.ts.map +1 -0
  36. package/dist/app/EventBus.js +88 -0
  37. package/dist/app/EventBus.js.map +1 -0
  38. package/dist/app/Keybind.d.ts +83 -0
  39. package/dist/app/Keybind.d.ts.map +1 -0
  40. package/dist/app/Keybind.js +184 -0
  41. package/dist/app/Keybind.js.map +1 -0
  42. package/dist/app/Router.d.ts +123 -0
  43. package/dist/app/Router.d.ts.map +1 -0
  44. package/dist/app/Router.js +142 -0
  45. package/dist/app/Router.js.map +1 -0
  46. package/dist/app/ScreenManager.d.ts +97 -0
  47. package/dist/app/ScreenManager.d.ts.map +1 -0
  48. package/dist/app/ScreenManager.js +216 -0
  49. package/dist/app/ScreenManager.js.map +1 -0
  50. package/dist/app/index.d.ts +14 -0
  51. package/dist/app/index.d.ts.map +1 -0
  52. package/dist/app/index.js +19 -0
  53. package/dist/app/index.js.map +1 -0
  54. package/dist/app/screens/BaseScreen.d.ts +101 -0
  55. package/dist/app/screens/BaseScreen.d.ts.map +1 -0
  56. package/dist/app/screens/BaseScreen.js +132 -0
  57. package/dist/app/screens/BaseScreen.js.map +1 -0
  58. package/dist/app/screens/CharacterCreationScreen.d.ts +42 -0
  59. package/dist/app/screens/CharacterCreationScreen.d.ts.map +1 -0
  60. package/dist/app/screens/CharacterCreationScreen.js +467 -0
  61. package/dist/app/screens/CharacterCreationScreen.js.map +1 -0
  62. package/dist/app/screens/CombatScreen.d.ts +30 -0
  63. package/dist/app/screens/CombatScreen.d.ts.map +1 -0
  64. package/dist/app/screens/CombatScreen.js +309 -0
  65. package/dist/app/screens/CombatScreen.js.map +1 -0
  66. package/dist/app/screens/DialogScreen.d.ts +29 -0
  67. package/dist/app/screens/DialogScreen.d.ts.map +1 -0
  68. package/dist/app/screens/DialogScreen.js +295 -0
  69. package/dist/app/screens/DialogScreen.js.map +1 -0
  70. package/dist/app/screens/ExploreScreen.d.ts +50 -0
  71. package/dist/app/screens/ExploreScreen.d.ts.map +1 -0
  72. package/dist/app/screens/ExploreScreen.js +308 -0
  73. package/dist/app/screens/ExploreScreen.js.map +1 -0
  74. package/dist/app/screens/HelpScreen.d.ts +12 -0
  75. package/dist/app/screens/HelpScreen.d.ts.map +1 -0
  76. package/dist/app/screens/HelpScreen.js +155 -0
  77. package/dist/app/screens/HelpScreen.js.map +1 -0
  78. package/dist/app/screens/InventoryScreen.d.ts +27 -0
  79. package/dist/app/screens/InventoryScreen.d.ts.map +1 -0
  80. package/dist/app/screens/InventoryScreen.js +326 -0
  81. package/dist/app/screens/InventoryScreen.js.map +1 -0
  82. package/dist/app/screens/PrologueScreen.d.ts +24 -0
  83. package/dist/app/screens/PrologueScreen.d.ts.map +1 -0
  84. package/dist/app/screens/PrologueScreen.js +176 -0
  85. package/dist/app/screens/PrologueScreen.js.map +1 -0
  86. package/dist/app/screens/TitleScreen.d.ts +42 -0
  87. package/dist/app/screens/TitleScreen.d.ts.map +1 -0
  88. package/dist/app/screens/TitleScreen.js +380 -0
  89. package/dist/app/screens/TitleScreen.js.map +1 -0
  90. package/dist/app/screens/TravelScreen.d.ts +22 -0
  91. package/dist/app/screens/TravelScreen.d.ts.map +1 -0
  92. package/dist/app/screens/TravelScreen.js +122 -0
  93. package/dist/app/screens/TravelScreen.js.map +1 -0
  94. package/dist/app/screens/index.d.ts +14 -0
  95. package/dist/app/screens/index.d.ts.map +1 -0
  96. package/dist/app/screens/index.js +17 -0
  97. package/dist/app/screens/index.js.map +1 -0
  98. package/dist/commands/CommandRegistry.d.ts +91 -0
  99. package/dist/commands/CommandRegistry.d.ts.map +1 -0
  100. package/dist/commands/CommandRegistry.js +159 -0
  101. package/dist/commands/CommandRegistry.js.map +1 -0
  102. package/dist/commands/index.d.ts +7 -0
  103. package/dist/commands/index.d.ts.map +1 -0
  104. package/dist/commands/index.js +10 -0
  105. package/dist/commands/index.js.map +1 -0
  106. package/dist/core/Actor.d.ts +103 -0
  107. package/dist/core/Actor.d.ts.map +1 -0
  108. package/dist/core/Actor.js +409 -0
  109. package/dist/core/Actor.js.map +1 -0
  110. package/dist/core/Combat.d.ts +37 -0
  111. package/dist/core/Combat.d.ts.map +1 -0
  112. package/dist/core/Combat.js +294 -0
  113. package/dist/core/Combat.js.map +1 -0
  114. package/dist/core/DungeonRunner.d.ts +169 -0
  115. package/dist/core/DungeonRunner.d.ts.map +1 -0
  116. package/dist/core/DungeonRunner.js +627 -0
  117. package/dist/core/DungeonRunner.js.map +1 -0
  118. package/dist/core/Game.d.ts +133 -0
  119. package/dist/core/Game.d.ts.map +1 -0
  120. package/dist/core/Game.js +644 -0
  121. package/dist/core/Game.js.map +1 -0
  122. package/dist/core/IdleCombat.d.ts +61 -0
  123. package/dist/core/IdleCombat.d.ts.map +1 -0
  124. package/dist/core/IdleCombat.js +461 -0
  125. package/dist/core/IdleCombat.js.map +1 -0
  126. package/dist/core/IdleGameManager.d.ts +198 -0
  127. package/dist/core/IdleGameManager.d.ts.map +1 -0
  128. package/dist/core/IdleGameManager.js +688 -0
  129. package/dist/core/IdleGameManager.js.map +1 -0
  130. package/dist/core/IdleSaveManager.d.ts +109 -0
  131. package/dist/core/IdleSaveManager.d.ts.map +1 -0
  132. package/dist/core/IdleSaveManager.js +296 -0
  133. package/dist/core/IdleSaveManager.js.map +1 -0
  134. package/dist/core/NewGameFlowManager.d.ts +64 -0
  135. package/dist/core/NewGameFlowManager.d.ts.map +1 -0
  136. package/dist/core/NewGameFlowManager.js +153 -0
  137. package/dist/core/NewGameFlowManager.js.map +1 -0
  138. package/dist/core/Player.d.ts +65 -0
  139. package/dist/core/Player.d.ts.map +1 -0
  140. package/dist/core/Player.js +261 -0
  141. package/dist/core/Player.js.map +1 -0
  142. package/dist/core/RoomHandlers.d.ts +75 -0
  143. package/dist/core/RoomHandlers.d.ts.map +1 -0
  144. package/dist/core/RoomHandlers.js +383 -0
  145. package/dist/core/RoomHandlers.js.map +1 -0
  146. package/dist/core/SaveManager.d.ts +84 -0
  147. package/dist/core/SaveManager.d.ts.map +1 -0
  148. package/dist/core/SaveManager.js +281 -0
  149. package/dist/core/SaveManager.js.map +1 -0
  150. package/dist/core/SaveMigration.d.ts +69 -0
  151. package/dist/core/SaveMigration.d.ts.map +1 -0
  152. package/dist/core/SaveMigration.js +408 -0
  153. package/dist/core/SaveMigration.js.map +1 -0
  154. package/dist/core/StateAdapter.d.ts +79 -0
  155. package/dist/core/StateAdapter.d.ts.map +1 -0
  156. package/dist/core/StateAdapter.js +397 -0
  157. package/dist/core/StateAdapter.js.map +1 -0
  158. package/dist/core/Team.d.ts +145 -0
  159. package/dist/core/Team.d.ts.map +1 -0
  160. package/dist/core/Team.js +371 -0
  161. package/dist/core/Team.js.map +1 -0
  162. package/dist/core/TeamCombat.d.ts +88 -0
  163. package/dist/core/TeamCombat.d.ts.map +1 -0
  164. package/dist/core/TeamCombat.js +405 -0
  165. package/dist/core/TeamCombat.js.map +1 -0
  166. package/dist/core/TeamDungeonRunner.d.ts +186 -0
  167. package/dist/core/TeamDungeonRunner.d.ts.map +1 -0
  168. package/dist/core/TeamDungeonRunner.js +758 -0
  169. package/dist/core/TeamDungeonRunner.js.map +1 -0
  170. package/dist/core/TimeManager.d.ts +114 -0
  171. package/dist/core/TimeManager.d.ts.map +1 -0
  172. package/dist/core/TimeManager.js +318 -0
  173. package/dist/core/TimeManager.js.map +1 -0
  174. package/dist/core/index.d.ts +9 -0
  175. package/dist/core/index.d.ts.map +1 -0
  176. package/dist/core/index.js +12 -0
  177. package/dist/core/index.js.map +1 -0
  178. package/dist/core/timeConstants.d.ts +135 -0
  179. package/dist/core/timeConstants.d.ts.map +1 -0
  180. package/dist/core/timeConstants.js +157 -0
  181. package/dist/core/timeConstants.js.map +1 -0
  182. package/dist/core/types.d.ts +780 -0
  183. package/dist/core/types.d.ts.map +1 -0
  184. package/dist/core/types.js +16 -0
  185. package/dist/core/types.js.map +1 -0
  186. package/dist/data/continents/index.d.ts +163 -0
  187. package/dist/data/continents/index.d.ts.map +1 -0
  188. package/dist/data/continents/index.js +31 -0
  189. package/dist/data/continents/index.js.map +1 -0
  190. package/dist/data/continents/verdantia.d.ts +294 -0
  191. package/dist/data/continents/verdantia.d.ts.map +1 -0
  192. package/dist/data/continents/verdantia.js +327 -0
  193. package/dist/data/continents/verdantia.js.map +1 -0
  194. package/dist/handlers/DialogHandler.d.ts +95 -0
  195. package/dist/handlers/DialogHandler.d.ts.map +1 -0
  196. package/dist/handlers/DialogHandler.js +450 -0
  197. package/dist/handlers/DialogHandler.js.map +1 -0
  198. package/dist/handlers/SaveLoadHandler.d.ts +60 -0
  199. package/dist/handlers/SaveLoadHandler.d.ts.map +1 -0
  200. package/dist/handlers/SaveLoadHandler.js +187 -0
  201. package/dist/handlers/SaveLoadHandler.js.map +1 -0
  202. package/dist/handlers/TitleScreenHandler.d.ts +43 -0
  203. package/dist/handlers/TitleScreenHandler.d.ts.map +1 -0
  204. package/dist/handlers/TitleScreenHandler.js +508 -0
  205. package/dist/handlers/TitleScreenHandler.js.map +1 -0
  206. package/dist/handlers/WorkshopHandler.d.ts +75 -0
  207. package/dist/handlers/WorkshopHandler.d.ts.map +1 -0
  208. package/dist/handlers/WorkshopHandler.js +401 -0
  209. package/dist/handlers/WorkshopHandler.js.map +1 -0
  210. package/dist/handlers/index.d.ts +12 -0
  211. package/dist/handlers/index.d.ts.map +1 -0
  212. package/dist/handlers/index.js +14 -0
  213. package/dist/handlers/index.js.map +1 -0
  214. package/dist/handlers/types.d.ts +34 -0
  215. package/dist/handlers/types.d.ts.map +1 -0
  216. package/dist/handlers/types.js +8 -0
  217. package/dist/handlers/types.js.map +1 -0
  218. package/dist/i18n/en.d.ts +3 -0
  219. package/dist/i18n/en.d.ts.map +1 -0
  220. package/dist/i18n/en.js +130 -0
  221. package/dist/i18n/en.js.map +1 -0
  222. package/dist/i18n/index.d.ts +40 -0
  223. package/dist/i18n/index.d.ts.map +1 -0
  224. package/dist/i18n/index.js +105 -0
  225. package/dist/i18n/index.js.map +1 -0
  226. package/dist/i18n/types.d.ts +133 -0
  227. package/dist/i18n/types.d.ts.map +1 -0
  228. package/dist/i18n/types.js +8 -0
  229. package/dist/i18n/types.js.map +1 -0
  230. package/dist/i18n/zh.d.ts +3 -0
  231. package/dist/i18n/zh.d.ts.map +1 -0
  232. package/dist/i18n/zh.js +130 -0
  233. package/dist/i18n/zh.js.map +1 -0
  234. package/dist/instrument.d.ts +8 -0
  235. package/dist/instrument.d.ts.map +1 -0
  236. package/dist/instrument.js +33 -0
  237. package/dist/instrument.js.map +1 -0
  238. package/dist/main-new.d.ts +12 -0
  239. package/dist/main-new.d.ts.map +1 -0
  240. package/dist/main-new.js +32 -0
  241. package/dist/main-new.js.map +1 -0
  242. package/dist/main.d.ts +7 -0
  243. package/dist/main.d.ts.map +1 -0
  244. package/dist/main.js +2816 -0
  245. package/dist/main.js.map +1 -0
  246. package/dist/map/ContinentManager.d.ts +88 -0
  247. package/dist/map/ContinentManager.d.ts.map +1 -0
  248. package/dist/map/ContinentManager.js +241 -0
  249. package/dist/map/ContinentManager.js.map +1 -0
  250. package/dist/map/DungeonGenerator.d.ts +32 -0
  251. package/dist/map/DungeonGenerator.d.ts.map +1 -0
  252. package/dist/map/DungeonGenerator.js +615 -0
  253. package/dist/map/DungeonGenerator.js.map +1 -0
  254. package/dist/map/MapGenerator.d.ts +27 -0
  255. package/dist/map/MapGenerator.d.ts.map +1 -0
  256. package/dist/map/MapGenerator.js +485 -0
  257. package/dist/map/MapGenerator.js.map +1 -0
  258. package/dist/map/index.d.ts +5 -0
  259. package/dist/map/index.d.ts.map +1 -0
  260. package/dist/map/index.js +8 -0
  261. package/dist/map/index.js.map +1 -0
  262. package/dist/npc/NPCGenerator.d.ts +68 -0
  263. package/dist/npc/NPCGenerator.d.ts.map +1 -0
  264. package/dist/npc/NPCGenerator.js +468 -0
  265. package/dist/npc/NPCGenerator.js.map +1 -0
  266. package/dist/npc/NPCManager.d.ts +86 -0
  267. package/dist/npc/NPCManager.d.ts.map +1 -0
  268. package/dist/npc/NPCManager.js +217 -0
  269. package/dist/npc/NPCManager.js.map +1 -0
  270. package/dist/npc/fixedNPCs.d.ts +7 -0
  271. package/dist/npc/fixedNPCs.d.ts.map +1 -0
  272. package/dist/npc/fixedNPCs.js +196 -0
  273. package/dist/npc/fixedNPCs.js.map +1 -0
  274. package/dist/npc/index.d.ts +9 -0
  275. package/dist/npc/index.d.ts.map +1 -0
  276. package/dist/npc/index.js +12 -0
  277. package/dist/npc/index.js.map +1 -0
  278. package/dist/npc/traits.d.ts +33 -0
  279. package/dist/npc/traits.d.ts.map +1 -0
  280. package/dist/npc/traits.js +795 -0
  281. package/dist/npc/traits.js.map +1 -0
  282. package/dist/npc/types.d.ts +193 -0
  283. package/dist/npc/types.d.ts.map +1 -0
  284. package/dist/npc/types.js +9 -0
  285. package/dist/npc/types.js.map +1 -0
  286. package/dist/quest/QuestManager.d.ts +79 -0
  287. package/dist/quest/QuestManager.d.ts.map +1 -0
  288. package/dist/quest/QuestManager.js +273 -0
  289. package/dist/quest/QuestManager.js.map +1 -0
  290. package/dist/quest/index.d.ts +6 -0
  291. package/dist/quest/index.d.ts.map +1 -0
  292. package/dist/quest/index.js +9 -0
  293. package/dist/quest/index.js.map +1 -0
  294. package/dist/quest/types.d.ts +81 -0
  295. package/dist/quest/types.d.ts.map +1 -0
  296. package/dist/quest/types.js +8 -0
  297. package/dist/quest/types.js.map +1 -0
  298. package/dist/strategy/StrategyExecutor.d.ts +159 -0
  299. package/dist/strategy/StrategyExecutor.d.ts.map +1 -0
  300. package/dist/strategy/StrategyExecutor.js +479 -0
  301. package/dist/strategy/StrategyExecutor.js.map +1 -0
  302. package/dist/strategy/StrategyParser.d.ts +48 -0
  303. package/dist/strategy/StrategyParser.d.ts.map +1 -0
  304. package/dist/strategy/StrategyParser.js +321 -0
  305. package/dist/strategy/StrategyParser.js.map +1 -0
  306. package/dist/strategy/defaultStrategy.d.ts +40 -0
  307. package/dist/strategy/defaultStrategy.d.ts.map +1 -0
  308. package/dist/strategy/defaultStrategy.js +254 -0
  309. package/dist/strategy/defaultStrategy.js.map +1 -0
  310. package/dist/strategy/index.d.ts +8 -0
  311. package/dist/strategy/index.d.ts.map +1 -0
  312. package/dist/strategy/index.js +14 -0
  313. package/dist/strategy/index.js.map +1 -0
  314. package/dist/tui/ExploreMenu.d.ts +106 -0
  315. package/dist/tui/ExploreMenu.d.ts.map +1 -0
  316. package/dist/tui/ExploreMenu.js +282 -0
  317. package/dist/tui/ExploreMenu.js.map +1 -0
  318. package/dist/tui/GameUI.d.ts +313 -0
  319. package/dist/tui/GameUI.d.ts.map +1 -0
  320. package/dist/tui/GameUI.js +2116 -0
  321. package/dist/tui/GameUI.js.map +1 -0
  322. package/dist/tui/GameUIAdapter.d.ts +207 -0
  323. package/dist/tui/GameUIAdapter.d.ts.map +1 -0
  324. package/dist/tui/GameUIAdapter.js +1342 -0
  325. package/dist/tui/GameUIAdapter.js.map +1 -0
  326. package/dist/tui/Input.d.ts +139 -0
  327. package/dist/tui/Input.d.ts.map +1 -0
  328. package/dist/tui/Input.js +278 -0
  329. package/dist/tui/Input.js.map +1 -0
  330. package/dist/tui/Menu.d.ts +110 -0
  331. package/dist/tui/Menu.d.ts.map +1 -0
  332. package/dist/tui/Menu.js +365 -0
  333. package/dist/tui/Menu.js.map +1 -0
  334. package/dist/tui/Screen.d.ts +228 -0
  335. package/dist/tui/Screen.d.ts.map +1 -0
  336. package/dist/tui/Screen.js +502 -0
  337. package/dist/tui/Screen.js.map +1 -0
  338. package/dist/tui/components/Box.d.ts +36 -0
  339. package/dist/tui/components/Box.d.ts.map +1 -0
  340. package/dist/tui/components/Box.js +43 -0
  341. package/dist/tui/components/Box.js.map +1 -0
  342. package/dist/tui/components/List.d.ts +69 -0
  343. package/dist/tui/components/List.d.ts.map +1 -0
  344. package/dist/tui/components/List.js +136 -0
  345. package/dist/tui/components/List.js.map +1 -0
  346. package/dist/tui/components/ProgressBar.d.ts +42 -0
  347. package/dist/tui/components/ProgressBar.d.ts.map +1 -0
  348. package/dist/tui/components/ProgressBar.js +75 -0
  349. package/dist/tui/components/ProgressBar.js.map +1 -0
  350. package/dist/tui/components/index.d.ts +8 -0
  351. package/dist/tui/components/index.d.ts.map +1 -0
  352. package/dist/tui/components/index.js +11 -0
  353. package/dist/tui/components/index.js.map +1 -0
  354. package/dist/tui/core/BaseSection.d.ts +98 -0
  355. package/dist/tui/core/BaseSection.d.ts.map +1 -0
  356. package/dist/tui/core/BaseSection.js +174 -0
  357. package/dist/tui/core/BaseSection.js.map +1 -0
  358. package/dist/tui/core/Component.d.ts +61 -0
  359. package/dist/tui/core/Component.d.ts.map +1 -0
  360. package/dist/tui/core/Component.js +32 -0
  361. package/dist/tui/core/Component.js.map +1 -0
  362. package/dist/tui/core/Section.d.ts +101 -0
  363. package/dist/tui/core/Section.d.ts.map +1 -0
  364. package/dist/tui/core/Section.js +24 -0
  365. package/dist/tui/core/Section.js.map +1 -0
  366. package/dist/tui/core/SectionManager.d.ts +108 -0
  367. package/dist/tui/core/SectionManager.d.ts.map +1 -0
  368. package/dist/tui/core/SectionManager.js +258 -0
  369. package/dist/tui/core/SectionManager.js.map +1 -0
  370. package/dist/tui/core/index.d.ts +9 -0
  371. package/dist/tui/core/index.d.ts.map +1 -0
  372. package/dist/tui/core/index.js +12 -0
  373. package/dist/tui/core/index.js.map +1 -0
  374. package/dist/tui/index.d.ts +15 -0
  375. package/dist/tui/index.d.ts.map +1 -0
  376. package/dist/tui/index.js +23 -0
  377. package/dist/tui/index.js.map +1 -0
  378. package/dist/tui/screens/BaseScreen.d.ts +62 -0
  379. package/dist/tui/screens/BaseScreen.d.ts.map +1 -0
  380. package/dist/tui/screens/BaseScreen.js +55 -0
  381. package/dist/tui/screens/BaseScreen.js.map +1 -0
  382. package/dist/tui/screens/CombatScreen.d.ts +43 -0
  383. package/dist/tui/screens/CombatScreen.d.ts.map +1 -0
  384. package/dist/tui/screens/CombatScreen.js +125 -0
  385. package/dist/tui/screens/CombatScreen.js.map +1 -0
  386. package/dist/tui/screens/DialogScreen.d.ts +53 -0
  387. package/dist/tui/screens/DialogScreen.d.ts.map +1 -0
  388. package/dist/tui/screens/DialogScreen.js +90 -0
  389. package/dist/tui/screens/DialogScreen.js.map +1 -0
  390. package/dist/tui/screens/DungeonScreen.d.ts +80 -0
  391. package/dist/tui/screens/DungeonScreen.d.ts.map +1 -0
  392. package/dist/tui/screens/DungeonScreen.js +317 -0
  393. package/dist/tui/screens/DungeonScreen.js.map +1 -0
  394. package/dist/tui/screens/ExploreScreen.d.ts +69 -0
  395. package/dist/tui/screens/ExploreScreen.d.ts.map +1 -0
  396. package/dist/tui/screens/ExploreScreen.js +224 -0
  397. package/dist/tui/screens/ExploreScreen.js.map +1 -0
  398. package/dist/tui/screens/SectionScreen.d.ts +84 -0
  399. package/dist/tui/screens/SectionScreen.d.ts.map +1 -0
  400. package/dist/tui/screens/SectionScreen.js +156 -0
  401. package/dist/tui/screens/SectionScreen.js.map +1 -0
  402. package/dist/tui/screens/TitleScreen.d.ts +40 -0
  403. package/dist/tui/screens/TitleScreen.d.ts.map +1 -0
  404. package/dist/tui/screens/TitleScreen.js +253 -0
  405. package/dist/tui/screens/TitleScreen.js.map +1 -0
  406. package/dist/tui/screens/TownScreen.d.ts +98 -0
  407. package/dist/tui/screens/TownScreen.d.ts.map +1 -0
  408. package/dist/tui/screens/TownScreen.js +370 -0
  409. package/dist/tui/screens/TownScreen.js.map +1 -0
  410. package/dist/tui/screens/TravelScreen.d.ts +67 -0
  411. package/dist/tui/screens/TravelScreen.d.ts.map +1 -0
  412. package/dist/tui/screens/TravelScreen.js +286 -0
  413. package/dist/tui/screens/TravelScreen.js.map +1 -0
  414. package/dist/tui/screens/index.d.ts +8 -0
  415. package/dist/tui/screens/index.d.ts.map +1 -0
  416. package/dist/tui/screens/index.js +17 -0
  417. package/dist/tui/screens/index.js.map +1 -0
  418. package/dist/tui/sections/ActionsSection.d.ts +71 -0
  419. package/dist/tui/sections/ActionsSection.d.ts.map +1 -0
  420. package/dist/tui/sections/ActionsSection.js +184 -0
  421. package/dist/tui/sections/ActionsSection.js.map +1 -0
  422. package/dist/tui/sections/DungeonSection.d.ts +65 -0
  423. package/dist/tui/sections/DungeonSection.d.ts.map +1 -0
  424. package/dist/tui/sections/DungeonSection.js +144 -0
  425. package/dist/tui/sections/DungeonSection.js.map +1 -0
  426. package/dist/tui/sections/EventsSection.d.ts +50 -0
  427. package/dist/tui/sections/EventsSection.d.ts.map +1 -0
  428. package/dist/tui/sections/EventsSection.js +134 -0
  429. package/dist/tui/sections/EventsSection.js.map +1 -0
  430. package/dist/tui/sections/MapSection.d.ts +66 -0
  431. package/dist/tui/sections/MapSection.d.ts.map +1 -0
  432. package/dist/tui/sections/MapSection.js +669 -0
  433. package/dist/tui/sections/MapSection.js.map +1 -0
  434. package/dist/tui/sections/StatusSection.d.ts +47 -0
  435. package/dist/tui/sections/StatusSection.d.ts.map +1 -0
  436. package/dist/tui/sections/StatusSection.js +133 -0
  437. package/dist/tui/sections/StatusSection.js.map +1 -0
  438. package/dist/tui/sections/TeamSection.d.ts +71 -0
  439. package/dist/tui/sections/TeamSection.d.ts.map +1 -0
  440. package/dist/tui/sections/TeamSection.js +224 -0
  441. package/dist/tui/sections/TeamSection.js.map +1 -0
  442. package/dist/tui/sections/TravelingSection.d.ts +51 -0
  443. package/dist/tui/sections/TravelingSection.d.ts.map +1 -0
  444. package/dist/tui/sections/TravelingSection.js +106 -0
  445. package/dist/tui/sections/TravelingSection.js.map +1 -0
  446. package/dist/tui/sections/index.d.ts +9 -0
  447. package/dist/tui/sections/index.d.ts.map +1 -0
  448. package/dist/tui/sections/index.js +12 -0
  449. package/dist/tui/sections/index.js.map +1 -0
  450. package/dist/ui/Terminal.d.ts +68 -0
  451. package/dist/ui/Terminal.d.ts.map +1 -0
  452. package/dist/ui/Terminal.js +297 -0
  453. package/dist/ui/Terminal.js.map +1 -0
  454. package/dist/ui/index.d.ts +5 -0
  455. package/dist/ui/index.d.ts.map +1 -0
  456. package/dist/ui/index.js +8 -0
  457. package/dist/ui/index.js.map +1 -0
  458. package/dist/utils/configMigration.d.ts +14 -0
  459. package/dist/utils/configMigration.d.ts.map +1 -0
  460. package/dist/utils/configMigration.js +92 -0
  461. package/dist/utils/configMigration.js.map +1 -0
  462. package/dist/utils/errorHandler.d.ts +86 -0
  463. package/dist/utils/errorHandler.d.ts.map +1 -0
  464. package/dist/utils/errorHandler.js +224 -0
  465. package/dist/utils/errorHandler.js.map +1 -0
  466. package/dist/utils/logger.d.ts +14 -0
  467. package/dist/utils/logger.d.ts.map +1 -0
  468. package/dist/utils/logger.js +43 -0
  469. package/dist/utils/logger.js.map +1 -0
  470. package/dist/weapon/WeaponFactory.d.ts +32 -0
  471. package/dist/weapon/WeaponFactory.d.ts.map +1 -0
  472. package/dist/weapon/WeaponFactory.js +216 -0
  473. package/dist/weapon/WeaponFactory.js.map +1 -0
  474. package/dist/weapon/index.d.ts +5 -0
  475. package/dist/weapon/index.d.ts.map +1 -0
  476. package/dist/weapon/index.js +8 -0
  477. package/dist/weapon/index.js.map +1 -0
  478. package/package.json +46 -0
@@ -0,0 +1,1342 @@
1
+ /**
2
+ * Game UI Adapter - Unified UI interface using section-based architecture
3
+ * This replaces the monolithic GameUI.ts with a clean adapter pattern
4
+ */
5
+
6
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="49e26bda-c9cc-543b-8e45-e2d60795329c")}catch(e){}}();
7
+ import { getScreen, ANSI } from './Screen.js';
8
+ import { getInput } from './Input.js';
9
+ import { getStrings } from '../i18n/index.js';
10
+ // Section-based architecture
11
+ import { SectionManager, createGridLayout } from './core/SectionManager.js';
12
+ import { MapSection } from './sections/MapSection.js';
13
+ import { StatusSection } from './sections/StatusSection.js';
14
+ import { ActionsSection } from './sections/ActionsSection.js';
15
+ import { EventsSection } from './sections/EventsSection.js';
16
+ import { TravelingSection } from './sections/TravelingSection.js';
17
+ import { DungeonSection } from './sections/DungeonSection.js';
18
+ /**
19
+ * Game UI Adapter - Clean interface using section-based architecture
20
+ */
21
+ export class GameUI {
22
+ PADDING = 2;
23
+ screen;
24
+ input;
25
+ mode = 'explore';
26
+ refreshInterval = null;
27
+ // Section management
28
+ sectionManager;
29
+ mapSection;
30
+ statusSection;
31
+ actionsSection;
32
+ eventsSection;
33
+ travelingSection;
34
+ dungeonSection;
35
+ // Mode state
36
+ isTravelMode = false;
37
+ isDungeonMode = false;
38
+ // Cached state
39
+ cachedState = null;
40
+ cachedNPCs = [];
41
+ cachedQuests = [];
42
+ // Dialog state
43
+ dialogNPC = null;
44
+ dialogHistory = [];
45
+ dialogOptions = [];
46
+ isWaitingForAI = false;
47
+ // Callbacks
48
+ onCommand = null;
49
+ onDialogInput = null;
50
+ onCharacterEdit = null;
51
+ // Modal state
52
+ activeModal = null;
53
+ modalSelectedIndex = 0;
54
+ modalScrollOffset = 0;
55
+ // Inventory modal state
56
+ inventoryTab = 0;
57
+ INVENTORY_TABS = ['All', 'Weapons', 'Armor', 'Consumables', 'Materials'];
58
+ // Character modal state
59
+ characterEditMode = false;
60
+ characterEditField = '';
61
+ characterEditBuffer = '';
62
+ characterExternalEditMode = false; // 是否在等待外部编辑器确认
63
+ characterExternalEditTempFile = ''; // 临时文件路径
64
+ // Size warning
65
+ showingSizeWarning = false;
66
+ constructor() {
67
+ this.screen = getScreen();
68
+ this.input = getInput();
69
+ // Initialize section-based architecture
70
+ this.sectionManager = new SectionManager();
71
+ this.mapSection = new MapSection();
72
+ this.statusSection = new StatusSection();
73
+ this.actionsSection = new ActionsSection();
74
+ this.eventsSection = new EventsSection();
75
+ this.travelingSection = new TravelingSection();
76
+ this.dungeonSection = new DungeonSection();
77
+ // Register sections
78
+ this.sectionManager.register(this.mapSection);
79
+ this.sectionManager.register(this.statusSection);
80
+ this.sectionManager.register(this.actionsSection);
81
+ this.sectionManager.register(this.eventsSection);
82
+ // Set up layout - Map on left (full height), Actions/Status on right
83
+ const layout = createGridLayout([
84
+ ['map', 'actions'],
85
+ ['map', 'status'],
86
+ ['events', 'events'],
87
+ ]);
88
+ this.sectionManager.setLayout(layout);
89
+ this.sectionManager.setDefaultFocus('map');
90
+ // Set up section callbacks
91
+ this.setupSectionCallbacks();
92
+ }
93
+ setupSectionCallbacks() {
94
+ this.mapSection.setOnTravel((roomIndex) => {
95
+ this.onCommand?.(String(roomIndex + 1));
96
+ });
97
+ this.actionsSection.setOnNpcSelect((npcIndex) => {
98
+ this.onCommand?.('npc');
99
+ setTimeout(() => this.onCommand?.(String(npcIndex + 1)), 50);
100
+ });
101
+ this.actionsSection.setOnOpenModal((modal) => {
102
+ this.openModal(modal);
103
+ });
104
+ }
105
+ /**
106
+ * Initialize UI
107
+ *
108
+ * Pushes the main key handler onto the input focus stack.
109
+ * This handler will be at the bottom of the stack and will receive
110
+ * input when no full-screen overlays are active.
111
+ */
112
+ async initialize() {
113
+ this.screen.hideCursor();
114
+ this.input.start();
115
+ // Push main UI handler onto focus stack (base handler)
116
+ this.input.pushFocus((key) => this.handleKeypress(key));
117
+ this.input.onLine((line) => this.handleLine(line));
118
+ this.screen.onResize((w, h) => this.handleResize(w, h));
119
+ this.sectionManager.focusDefault();
120
+ this.setMode('explore');
121
+ }
122
+ /**
123
+ * Cleanup
124
+ */
125
+ cleanup() {
126
+ this.stopRefresh();
127
+ this.input.stop();
128
+ this.screen.cleanup();
129
+ }
130
+ /**
131
+ * Set UI mode
132
+ */
133
+ setMode(mode) {
134
+ this.mode = mode;
135
+ if (mode === 'travel' || mode === 'combat') {
136
+ this.startRefresh(100);
137
+ }
138
+ else if (mode === 'explore' || mode === 'dialog') {
139
+ this.startRefresh(200);
140
+ }
141
+ else {
142
+ this.stopRefresh();
143
+ }
144
+ if (mode === 'dialog') {
145
+ this.input.setMode('line');
146
+ }
147
+ else {
148
+ this.input.setMode('raw');
149
+ }
150
+ }
151
+ /**
152
+ * Add event to log
153
+ */
154
+ addEventLog(message) {
155
+ this.eventsSection.addEvent(message);
156
+ }
157
+ /**
158
+ * Enter travel mode - replaces ActionsSection with TravelingSection
159
+ */
160
+ enterTravelMode() {
161
+ if (this.isTravelMode)
162
+ return;
163
+ this.sectionManager.unregister('actions');
164
+ this.sectionManager.register(this.travelingSection);
165
+ // Update layout to use 'traveling' instead of 'actions'
166
+ const travelLayout = createGridLayout([
167
+ ['map', 'traveling'],
168
+ ['map', 'status'],
169
+ ['events', 'events'],
170
+ ]);
171
+ this.sectionManager.setLayout(travelLayout);
172
+ this.isTravelMode = true;
173
+ }
174
+ /**
175
+ * Exit travel mode - restores ActionsSection
176
+ */
177
+ exitTravelMode() {
178
+ if (!this.isTravelMode)
179
+ return;
180
+ this.sectionManager.unregister('traveling');
181
+ this.sectionManager.register(this.actionsSection);
182
+ // Restore original layout with 'actions'
183
+ const normalLayout = createGridLayout([
184
+ ['map', 'actions'],
185
+ ['map', 'status'],
186
+ ['events', 'events'],
187
+ ]);
188
+ this.sectionManager.setLayout(normalLayout);
189
+ this.isTravelMode = false;
190
+ }
191
+ /**
192
+ * Update travel state for both TravelingSection and MapSection
193
+ */
194
+ updateTravelState(data) {
195
+ // Update TravelingSection
196
+ this.travelingSection.update({
197
+ fromName: data.fromName,
198
+ toName: data.toName,
199
+ progress: data.progress,
200
+ duration: data.duration,
201
+ events: data.events,
202
+ phase: data.phase,
203
+ });
204
+ // Update MapSection travel state (for showing moving icon)
205
+ this.mapSection.setTravelState({
206
+ fromRoomId: data.fromRoomId,
207
+ toRoomId: data.toRoomId,
208
+ progress: data.progress,
209
+ });
210
+ }
211
+ /**
212
+ * Clear travel state from MapSection
213
+ */
214
+ clearTravelState() {
215
+ this.mapSection.setTravelState(null);
216
+ }
217
+ /**
218
+ * Set cancel travel callback
219
+ */
220
+ setOnCancelTravel(callback) {
221
+ this.travelingSection.setOnCancelTravel(callback);
222
+ }
223
+ /**
224
+ * Enter dungeon mode - replaces ActionsSection with DungeonSection
225
+ * Layout changes to horizontal: Map | Dungeon | Status
226
+ */
227
+ enterDungeonMode() {
228
+ if (this.isDungeonMode)
229
+ return;
230
+ // Exit travel mode first if active
231
+ if (this.isTravelMode) {
232
+ this.exitTravelMode();
233
+ }
234
+ this.sectionManager.unregister('actions');
235
+ this.sectionManager.register(this.dungeonSection);
236
+ // Update layout for dungeon mode - horizontal arrangement
237
+ // Map takes left side, Dungeon and Status share right side horizontally
238
+ const dungeonLayout = createGridLayout([
239
+ ['map', 'dungeon', 'status'],
240
+ ['events', 'events', 'events'],
241
+ ]);
242
+ this.sectionManager.setLayout(dungeonLayout);
243
+ this.isDungeonMode = true;
244
+ }
245
+ /**
246
+ * Exit dungeon mode - restores ActionsSection
247
+ */
248
+ exitDungeonMode() {
249
+ if (!this.isDungeonMode)
250
+ return;
251
+ this.sectionManager.unregister('dungeon');
252
+ this.sectionManager.register(this.actionsSection);
253
+ // Restore normal layout
254
+ const normalLayout = createGridLayout([
255
+ ['map', 'actions'],
256
+ ['map', 'status'],
257
+ ['events', 'events'],
258
+ ]);
259
+ this.sectionManager.setLayout(normalLayout);
260
+ this.isDungeonMode = false;
261
+ }
262
+ /**
263
+ * Update dungeon progress data
264
+ */
265
+ updateDungeonData(data) {
266
+ this.dungeonSection.update(data);
267
+ }
268
+ /**
269
+ * Set abort dungeon callback
270
+ */
271
+ setOnAbortDungeon(callback) {
272
+ this.dungeonSection.setOnAbort(callback);
273
+ }
274
+ /**
275
+ * Register command handler
276
+ */
277
+ onCommandInput(handler) {
278
+ this.onCommand = handler;
279
+ }
280
+ /**
281
+ * Register dialog input handler
282
+ */
283
+ onDialogInputHandler(handler) {
284
+ this.onDialogInput = handler;
285
+ }
286
+ /**
287
+ * Register character edit handler
288
+ */
289
+ onCharacterEditHandler(handler) {
290
+ this.onCharacterEdit = handler;
291
+ }
292
+ // =========================================================================
293
+ // RENDERING
294
+ // =========================================================================
295
+ /**
296
+ * Render main exploration screen
297
+ */
298
+ renderMainScreen(state, npcsInRoom = [], activeQuests = []) {
299
+ this.cachedState = state;
300
+ this.cachedNPCs = npcsInRoom;
301
+ this.cachedQuests = activeQuests;
302
+ if (this.activeModal)
303
+ return;
304
+ const { width: screenWidth, height: screenHeight } = this.screen.getSize();
305
+ this.screen.clear();
306
+ const px = this.PADDING;
307
+ const py = this.PADDING;
308
+ const width = screenWidth - this.PADDING * 2;
309
+ const height = screenHeight - this.PADDING * 2;
310
+ // Layout
311
+ const headerHeight = 3;
312
+ const eventsHeight = Math.min(8, Math.floor(height * 0.25));
313
+ const mainAreaHeight = height - headerHeight - eventsHeight - 2;
314
+ // Render header
315
+ this.renderHeader(state.player, width, px, py);
316
+ // Update sections
317
+ this.mapSection.update(state);
318
+ this.statusSection.update({ state, npcs: npcsInRoom, quests: activeQuests });
319
+ this.actionsSection.update({ npcs: npcsInRoom, quests: activeQuests });
320
+ // Calculate bounds based on current mode
321
+ const boundsMap = new Map();
322
+ if (this.isDungeonMode) {
323
+ // Dungeon mode: horizontal layout - Map | Dungeon | Status
324
+ const mapWidth = Math.floor(width * 0.5);
325
+ const dungeonWidth = Math.floor(width * 0.25);
326
+ const statusWidth = width - mapWidth - dungeonWidth;
327
+ boundsMap.set('map', { x: px, y: py + headerHeight, width: mapWidth, height: mainAreaHeight });
328
+ boundsMap.set('dungeon', { x: px + mapWidth, y: py + headerHeight, width: dungeonWidth, height: mainAreaHeight });
329
+ boundsMap.set('status', { x: px + mapWidth + dungeonWidth, y: py + headerHeight, width: statusWidth, height: mainAreaHeight });
330
+ }
331
+ else {
332
+ // Normal mode: Map on left, Actions/Status on right (vertical)
333
+ const menuWidth = Math.min(28, Math.floor(width * 0.28));
334
+ const leftWidth = width - menuWidth - 1;
335
+ const actionsHeight = Math.floor(mainAreaHeight * 0.6);
336
+ const statusHeight = mainAreaHeight - actionsHeight;
337
+ boundsMap.set('map', { x: px, y: py + headerHeight, width: leftWidth, height: mainAreaHeight });
338
+ // Use traveling section bounds when in travel mode, otherwise actions
339
+ if (this.isTravelMode) {
340
+ boundsMap.set('traveling', { x: px + leftWidth, y: py + headerHeight, width: menuWidth + 1, height: actionsHeight });
341
+ }
342
+ else {
343
+ boundsMap.set('actions', { x: px + leftWidth, y: py + headerHeight, width: menuWidth + 1, height: actionsHeight });
344
+ }
345
+ boundsMap.set('status', { x: px + leftWidth, y: py + headerHeight + actionsHeight, width: menuWidth + 1, height: statusHeight });
346
+ }
347
+ boundsMap.set('events', { x: px, y: py + headerHeight + mainAreaHeight, width, height: eventsHeight });
348
+ // Render sections
349
+ for (const section of this.sectionManager.getAll()) {
350
+ const bounds = boundsMap.get(section.id);
351
+ if (bounds) {
352
+ section.bounds = bounds;
353
+ section.render({ screen: this.screen, bounds });
354
+ }
355
+ }
356
+ // Hint bar
357
+ this.renderHintBar(px + 1, py + height - 1, width - 2);
358
+ this.screen.render();
359
+ }
360
+ /**
361
+ * Render travel screen
362
+ */
363
+ renderTravelScreen(state, fromRoom, toRoom, progress, duration, events) {
364
+ const { width: screenWidth, height: screenHeight } = this.screen.getSize();
365
+ this.screen.clear();
366
+ const px = this.PADDING;
367
+ const py = this.PADDING;
368
+ const width = screenWidth - this.PADDING * 2;
369
+ const height = screenHeight - this.PADDING * 2;
370
+ // Header
371
+ this.renderHeader(state.player, width, px, py);
372
+ // Travel info
373
+ const contentY = py + 4;
374
+ const dots = '.'.repeat(Math.floor(Date.now() / 300) % 4);
375
+ this.screen.drawBox(px, contentY, width, height - 5, `Traveling${dots}`);
376
+ const centerX = px + Math.floor(width / 2);
377
+ const centerY = contentY + Math.floor((height - 7) / 2);
378
+ // From/To
379
+ const fromName = fromRoom?.name || 'Unknown';
380
+ const toName = toRoom?.name || 'Unknown';
381
+ this.screen.write(centerX - 15, centerY - 2, `From: ${fromName}`, ANSI.fg.gray);
382
+ this.screen.write(centerX - 15, centerY, `To: ${toName}`, ANSI.fg.yellow);
383
+ // Progress bar
384
+ const barWidth = 30;
385
+ const barX = centerX - Math.floor(barWidth / 2);
386
+ this.screen.drawProgressBar(barX, centerY + 2, barWidth, progress, 1);
387
+ const pct = Math.floor(progress * 100);
388
+ this.screen.write(centerX - 5, centerY + 3, `${pct}% complete`, ANSI.fg.cyan);
389
+ // Time remaining
390
+ const remaining = Math.ceil(duration * (1 - progress));
391
+ this.screen.write(centerX - 8, centerY + 5, `${remaining}s remaining`, ANSI.fg.gray);
392
+ // Events
393
+ if (events.length > 0) {
394
+ const eventY = contentY + height - 8;
395
+ this.screen.write(px + 2, eventY, '- Events -', ANSI.fg.cyan);
396
+ events.slice(-3).forEach((ev, i) => {
397
+ this.screen.write(px + 2, eventY + 1 + i, `* ${ev}`.slice(0, width - 4), ANSI.fg.white);
398
+ });
399
+ }
400
+ // Hint
401
+ this.screen.write(px + 2, py + height - 1, '[ESC] Cancel Travel', ANSI.fg.yellow);
402
+ this.screen.render();
403
+ }
404
+ /**
405
+ * Render combat screen
406
+ */
407
+ renderCombatScreen(combat, player) {
408
+ const { width: screenWidth, height: screenHeight } = this.screen.getSize();
409
+ this.screen.clear();
410
+ const px = this.PADDING;
411
+ const py = this.PADDING;
412
+ const width = screenWidth - this.PADDING * 2;
413
+ const height = screenHeight - this.PADDING * 2;
414
+ // Header
415
+ this.renderHeader(player, width, px, py);
416
+ // Combat box
417
+ this.screen.drawBox(px, py + 3, width, height - 4, '⚔ COMBAT ⚔');
418
+ const contentY = py + 5;
419
+ const centerX = px + Math.floor(width / 2);
420
+ // Turn info
421
+ this.screen.write(centerX - 5, contentY, `Turn: ${combat.turn}`, ANSI.fg.yellow);
422
+ // Enemy info
423
+ const enemyY = contentY + 2;
424
+ this.screen.write(px + 4, enemyY, '-- Enemies --', ANSI.fg.red, undefined, ANSI.bold);
425
+ combat.enemies.forEach((enemy, i) => {
426
+ const hp = enemy.hp;
427
+ const maxHp = enemy.maxHp;
428
+ const hpPct = hp / maxHp;
429
+ const status = hp <= 0 ? ' [DEAD]' : '';
430
+ const color = hp <= 0 ? ANSI.fg.gray : ANSI.fg.white;
431
+ this.screen.write(px + 4, enemyY + 1 + i, `${enemy.name}${status}`, color);
432
+ if (hp > 0) {
433
+ this.screen.drawProgressBar(px + 20, enemyY + 1 + i, 15, hp, maxHp, ANSI.fg.red);
434
+ this.screen.write(px + 36, enemyY + 1 + i, `${hp}/${maxHp}`, ANSI.fg.white);
435
+ }
436
+ });
437
+ // Player stats
438
+ const playerY = enemyY + combat.enemies.length + 3;
439
+ this.screen.write(px + 4, playerY, '-- Your Stats --', ANSI.fg.green, undefined, ANSI.bold);
440
+ this.screen.write(px + 4, playerY + 1, `HP: ${player.stats.hp}/${player.stats.maxHp}`, ANSI.fg.white);
441
+ this.screen.write(px + 4, playerY + 2, `ATK: ${player.stats.attack} DEF: ${player.stats.defense}`, ANSI.fg.white);
442
+ // Combat log
443
+ const logY = playerY + 5;
444
+ this.screen.write(px + 4, logY, '-- Combat Log --', ANSI.fg.cyan, undefined, ANSI.bold);
445
+ // Hint
446
+ this.screen.write(px + 2, py + height - 1, 'Combat is automatic... | 1:Aggressive 2:Defensive 3:Balanced', ANSI.fg.gray);
447
+ this.screen.render();
448
+ }
449
+ /**
450
+ * Render dialog screen
451
+ */
452
+ renderDialogScreen(npc, history, options, waiting) {
453
+ this.dialogNPC = npc;
454
+ this.dialogHistory = history;
455
+ this.dialogOptions = options;
456
+ this.isWaitingForAI = waiting;
457
+ const { width: screenWidth, height: screenHeight } = this.screen.getSize();
458
+ this.screen.clear();
459
+ const px = this.PADDING;
460
+ const py = this.PADDING;
461
+ const width = screenWidth - this.PADDING * 2;
462
+ const height = screenHeight - this.PADDING * 2;
463
+ // Dialog box
464
+ this.screen.drawBox(px, py, width, height, `Dialog: ${npc.name}`);
465
+ // NPC info
466
+ const infoY = py + 2;
467
+ this.screen.write(px + 2, infoY, `${npc.name} (${npc.role})`, ANSI.fg.yellow, undefined, ANSI.bold);
468
+ const rel = npc.playerRelationship;
469
+ const relText = rel > 50 ? 'Friendly' : rel > 0 ? 'Neutral+' : rel > -50 ? 'Neutral' : 'Unfriendly';
470
+ this.screen.write(px + 2, infoY + 1, `Relationship: ${relText}`, ANSI.fg.gray);
471
+ // Dialog history
472
+ const historyY = infoY + 3;
473
+ const maxHistoryLines = height - 12;
474
+ const recentHistory = history.slice(-maxHistoryLines);
475
+ recentHistory.forEach((entry, i) => {
476
+ const prefix = entry.role === 'player' ? 'You: ' : `${npc.name}: `;
477
+ const color = entry.role === 'player' ? ANSI.fg.cyan : ANSI.fg.white;
478
+ const text = `${prefix}${entry.text}`.slice(0, width - 4);
479
+ this.screen.write(px + 2, historyY + i, text, color);
480
+ });
481
+ // Waiting indicator
482
+ if (waiting) {
483
+ const dots = '.'.repeat(Math.floor(Date.now() / 300) % 4);
484
+ this.screen.write(px + 2, historyY + recentHistory.length + 1, `${npc.name} is thinking${dots}`, ANSI.fg.yellow);
485
+ }
486
+ // Quick options
487
+ const optionsY = py + height - 5;
488
+ this.screen.write(px + 2, optionsY, '-- Quick Options --', ANSI.fg.gray);
489
+ options.forEach((opt, i) => {
490
+ this.screen.write(px + 2, optionsY + 1 + i, `[${opt.key}] ${opt.text}`, ANSI.fg.white);
491
+ });
492
+ // Input hint
493
+ const inputY = py + height - 1;
494
+ const inputBuffer = this.input.getInputBuffer();
495
+ this.screen.write(px + 2, inputY, `> ${inputBuffer}▌`, ANSI.fg.white);
496
+ this.screen.render();
497
+ }
498
+ // =========================================================================
499
+ // PRIVATE HELPERS
500
+ // =========================================================================
501
+ renderHeader(player, width, x, y) {
502
+ const s = getStrings();
503
+ this.screen.drawBox(x, y, width, 3, '', true);
504
+ this.screen.write(x + 2, y + 1, 'IDLERPG.TERMINAL', ANSI.fg.cyan, undefined, ANSI.bold);
505
+ const playerInfo = `${s.status.level}.${player.stats.level} ${player.name}`;
506
+ this.screen.write(x + 25, y + 1, playerInfo, ANSI.fg.yellow);
507
+ this.screen.write(x + 45, y + 1, `${s.status.hp}:`, ANSI.fg.white);
508
+ this.screen.drawProgressBar(x + 49, y + 1, 10, player.stats.hp, player.stats.maxHp, ANSI.fg.red);
509
+ this.screen.write(x + 60, y + 1, `${player.stats.hp}/${player.stats.maxHp}`, ANSI.fg.white);
510
+ const time = new Date().toLocaleTimeString('en-US', { hour12: false });
511
+ this.screen.write(x + width - time.length - 2, y + 1, time, ANSI.fg.gray);
512
+ }
513
+ renderHintBar(x, y, width) {
514
+ const isAnyEntered = this.sectionManager.isAnyEntered();
515
+ let hints;
516
+ if (isAnyEntered) {
517
+ hints = this.sectionManager.getCurrentHint() || 'Up/Down: navigate Enter: select Esc: exit';
518
+ }
519
+ else {
520
+ hints = 'Arrows: focus Enter: enter | 1-9: Travel I C Q W [M]enu';
521
+ }
522
+ this.screen.write(x, y, hints.slice(0, width), ANSI.fg.gray);
523
+ }
524
+ startRefresh(intervalMs) {
525
+ this.stopRefresh();
526
+ this.refreshInterval = setInterval(() => {
527
+ if (this.showingSizeWarning || this.screen.isCurrentlyResizing())
528
+ return;
529
+ if (this.activeModal) {
530
+ this.renderModal();
531
+ return;
532
+ }
533
+ if (this.mode === 'explore' && this.cachedState) {
534
+ this.renderMainScreen(this.cachedState, this.cachedNPCs, this.cachedQuests);
535
+ }
536
+ else if (this.mode === 'dialog' && this.dialogNPC) {
537
+ this.renderDialogScreen(this.dialogNPC, this.dialogHistory, this.dialogOptions, this.isWaitingForAI);
538
+ }
539
+ else {
540
+ this.screen.render();
541
+ }
542
+ }, intervalMs);
543
+ }
544
+ stopRefresh() {
545
+ if (this.refreshInterval) {
546
+ clearInterval(this.refreshInterval);
547
+ this.refreshInterval = null;
548
+ }
549
+ }
550
+ /**
551
+ * Pause automatic rendering (for full-screen overlays like Prologue, CharacterCreation)
552
+ * This stops the refresh interval to prevent it from overwriting overlay screens.
553
+ *
554
+ * Note: Key handling is now managed by the InputManager's focus stack.
555
+ * When a full-screen overlay pushes its handler onto the stack, it receives
556
+ * exclusive input. This method only controls the render loop.
557
+ */
558
+ pauseRendering() {
559
+ this.stopRefresh();
560
+ }
561
+ /**
562
+ * Resume automatic rendering after full-screen overlay exits.
563
+ * Restores the refresh interval based on current mode.
564
+ *
565
+ * Note: Key handling is restored automatically when the overlay pops its
566
+ * handler from the InputManager's focus stack.
567
+ */
568
+ resumeRendering() {
569
+ if (this.mode === 'travel' || this.mode === 'combat') {
570
+ this.startRefresh(100);
571
+ }
572
+ else if (this.mode === 'explore' || this.mode === 'dialog') {
573
+ this.startRefresh(200);
574
+ }
575
+ }
576
+ handleResize(width, height) {
577
+ const sizeCheck = this.screen.checkMinimumSize();
578
+ if (!sizeCheck.valid) {
579
+ this.showingSizeWarning = true;
580
+ this.screen.renderSizeWarning();
581
+ }
582
+ else {
583
+ this.showingSizeWarning = false;
584
+ this.screen.clear();
585
+ if (this.mode === 'explore' && this.cachedState) {
586
+ this.renderMainScreen(this.cachedState, this.cachedNPCs, this.cachedQuests);
587
+ }
588
+ this.screen.forceRedraw();
589
+ }
590
+ }
591
+ handleKeypress(key) {
592
+ if (key.ctrl && key.name === 'c') {
593
+ this.cleanup();
594
+ process.exit(0);
595
+ }
596
+ // Note: Full-screen overlays (prologue, character creation) now push their
597
+ // own handlers onto the InputManager's focus stack, so they automatically
598
+ // receive exclusive input. No need for isFullScreenActive check here.
599
+ if (this.activeModal) {
600
+ this.handleModalKey(key);
601
+ return;
602
+ }
603
+ switch (this.mode) {
604
+ case 'travel':
605
+ // Delegate to section manager - TravelingSection handles 'q' to cancel
606
+ this.sectionManager.handleKey(key);
607
+ break;
608
+ case 'explore':
609
+ this.handleExploreKey(key);
610
+ break;
611
+ case 'dialog':
612
+ break;
613
+ }
614
+ }
615
+ handleExploreKey(key) {
616
+ // When a section is entered OR focused, let it handle keys first
617
+ // This allows sections like Map to capture WASD/+- for panning/zooming
618
+ const focused = this.sectionManager.getFocused();
619
+ if (focused) {
620
+ // Try to let the focused section handle the key
621
+ if (focused.handleKey(key)) {
622
+ if (this.cachedState) {
623
+ this.renderMainScreen(this.cachedState, this.cachedNPCs, this.cachedQuests);
624
+ }
625
+ return;
626
+ }
627
+ }
628
+ // Modal shortcuts (only when section didn't handle the key)
629
+ // Note: Don't intercept WASD when map is focused (map uses these for panning)
630
+ const isMapFocused = focused?.id === 'map';
631
+ const modalKeys = {
632
+ 'i': 'inventory',
633
+ 'c': 'character',
634
+ 'q': 'quests',
635
+ 'm': 'menu',
636
+ };
637
+ // Only add workshop shortcut if map is not focused
638
+ if (!isMapFocused) {
639
+ modalKeys['w'] = 'workshop';
640
+ }
641
+ if (key.raw && modalKeys[key.raw.toLowerCase()]) {
642
+ this.openModal(modalKeys[key.raw.toLowerCase()]);
643
+ return;
644
+ }
645
+ // Number shortcuts for travel
646
+ if (key.raw && /^[1-9]$/.test(key.raw) && this.onCommand) {
647
+ this.onCommand(key.raw);
648
+ return;
649
+ }
650
+ // Global shortcuts
651
+ // Note: Don't intercept 's' when map is focused (map uses S for panning)
652
+ const globalShortcuts = {
653
+ 'n': 'npc',
654
+ 'h': 'help',
655
+ 'l': 'load',
656
+ };
657
+ // Only add save shortcut if map is not focused
658
+ if (!isMapFocused) {
659
+ globalShortcuts['s'] = 'save';
660
+ }
661
+ if (key.raw) {
662
+ const cmd = globalShortcuts[key.raw.toLowerCase()];
663
+ if (cmd && this.onCommand) {
664
+ this.onCommand(cmd);
665
+ return;
666
+ }
667
+ }
668
+ // Delegate to section manager for navigation (arrows, enter, etc.)
669
+ if (this.sectionManager.handleKey(key)) {
670
+ if (this.cachedState) {
671
+ this.renderMainScreen(this.cachedState, this.cachedNPCs, this.cachedQuests);
672
+ }
673
+ }
674
+ }
675
+ handleLine(line) {
676
+ if (this.mode === 'explore' && this.onCommand) {
677
+ this.onCommand(line.trim().toLowerCase());
678
+ }
679
+ else if (this.mode === 'dialog' && this.onDialogInput) {
680
+ this.onDialogInput(line);
681
+ }
682
+ }
683
+ // =========================================================================
684
+ // MODAL HANDLING
685
+ // =========================================================================
686
+ openModal(modal) {
687
+ this.activeModal = modal;
688
+ this.modalSelectedIndex = 0;
689
+ this.modalScrollOffset = 0;
690
+ }
691
+ closeModal() {
692
+ this.activeModal = null;
693
+ if (this.cachedState) {
694
+ this.renderMainScreen(this.cachedState, this.cachedNPCs, this.cachedQuests);
695
+ }
696
+ }
697
+ handleModalKey(key) {
698
+ // Handle based on active modal
699
+ switch (this.activeModal) {
700
+ case 'inventory':
701
+ this.handleInventoryKey(key);
702
+ break;
703
+ case 'character':
704
+ this.handleCharacterKey(key);
705
+ break;
706
+ case 'quests':
707
+ this.handleQuestsKey(key);
708
+ break;
709
+ case 'menu':
710
+ this.handleMenuKey(key);
711
+ break;
712
+ default:
713
+ if (key.name === 'escape') {
714
+ this.closeModal();
715
+ }
716
+ }
717
+ }
718
+ handleInventoryKey(key) {
719
+ const items = this.getFilteredInventory();
720
+ // Escape to close
721
+ if (key.name === 'escape' || key.raw === 'i') {
722
+ this.closeModal();
723
+ return;
724
+ }
725
+ // Tab switching with 1-5
726
+ if (key.raw && /^[1-5]$/.test(key.raw)) {
727
+ this.inventoryTab = parseInt(key.raw) - 1;
728
+ this.modalSelectedIndex = 0;
729
+ this.modalScrollOffset = 0;
730
+ this.renderModal();
731
+ return;
732
+ }
733
+ // Left/Right for tabs
734
+ if (key.name === 'left' || key.raw === 'h') {
735
+ if (this.inventoryTab > 0) {
736
+ this.inventoryTab--;
737
+ this.modalSelectedIndex = 0;
738
+ this.modalScrollOffset = 0;
739
+ }
740
+ this.renderModal();
741
+ return;
742
+ }
743
+ if (key.name === 'right' || key.raw === 'l') {
744
+ if (this.inventoryTab < this.INVENTORY_TABS.length - 1) {
745
+ this.inventoryTab++;
746
+ this.modalSelectedIndex = 0;
747
+ this.modalScrollOffset = 0;
748
+ }
749
+ this.renderModal();
750
+ return;
751
+ }
752
+ // Up/Down for item selection
753
+ if (key.name === 'up' || key.raw === 'k') {
754
+ if (this.modalSelectedIndex > 0) {
755
+ this.modalSelectedIndex--;
756
+ if (this.modalSelectedIndex < this.modalScrollOffset) {
757
+ this.modalScrollOffset = this.modalSelectedIndex;
758
+ }
759
+ }
760
+ this.renderModal();
761
+ return;
762
+ }
763
+ if (key.name === 'down' || key.raw === 'j') {
764
+ if (this.modalSelectedIndex < items.length - 1) {
765
+ this.modalSelectedIndex++;
766
+ const maxVisible = 10;
767
+ if (this.modalSelectedIndex >= this.modalScrollOffset + maxVisible) {
768
+ this.modalScrollOffset = this.modalSelectedIndex - maxVisible + 1;
769
+ }
770
+ }
771
+ this.renderModal();
772
+ return;
773
+ }
774
+ // E to equip
775
+ if (key.raw === 'e' || key.raw === 'E') {
776
+ this.equipSelectedItem();
777
+ return;
778
+ }
779
+ // U to unequip or use
780
+ if (key.raw === 'u' || key.raw === 'U') {
781
+ this.useOrUnequipSelectedItem();
782
+ return;
783
+ }
784
+ // D to drop
785
+ if (key.raw === 'd' || key.raw === 'D') {
786
+ this.dropSelectedItem();
787
+ return;
788
+ }
789
+ }
790
+ handleCharacterKey(key) {
791
+ const editableFields = ['name', 'backstory', 'appearance', 'personality'];
792
+ if (key.name === 'escape' || key.raw === 'c') {
793
+ if (this.characterEditMode) {
794
+ this.characterEditMode = false;
795
+ this.characterEditBuffer = '';
796
+ }
797
+ else if (this.characterExternalEditMode) {
798
+ // Cancel external edit
799
+ this.characterExternalEditMode = false;
800
+ this.characterExternalEditTempFile = '';
801
+ }
802
+ else {
803
+ this.closeModal();
804
+ }
805
+ this.renderModal();
806
+ return;
807
+ }
808
+ // In external edit mode, wait for Enter to confirm
809
+ if (this.characterExternalEditMode) {
810
+ if (key.name === 'return') {
811
+ // Read the temp file and apply
812
+ this.confirmExternalEdit();
813
+ return;
814
+ }
815
+ // Ignore other keys while waiting for confirmation
816
+ return;
817
+ }
818
+ // In edit mode, handle text input
819
+ if (this.characterEditMode) {
820
+ if (key.name === 'return') {
821
+ // Submit edit
822
+ if (this.characterEditBuffer.trim()) {
823
+ this.onCharacterEdit?.(this.characterEditField, this.characterEditBuffer.trim());
824
+ }
825
+ this.characterEditMode = false;
826
+ this.characterEditBuffer = '';
827
+ this.renderModal();
828
+ return;
829
+ }
830
+ if (key.name === 'backspace') {
831
+ this.characterEditBuffer = this.characterEditBuffer.slice(0, -1);
832
+ this.renderModal();
833
+ return;
834
+ }
835
+ if (key.raw && key.raw.length === 1 && key.raw.charCodeAt(0) >= 32) {
836
+ this.characterEditBuffer += key.raw;
837
+ this.renderModal();
838
+ return;
839
+ }
840
+ return;
841
+ }
842
+ // Navigate editable fields
843
+ if (key.name === 'up' || key.raw === 'k') {
844
+ this.modalSelectedIndex = Math.max(0, this.modalSelectedIndex - 1);
845
+ this.renderModal();
846
+ return;
847
+ }
848
+ if (key.name === 'down' || key.raw === 'j') {
849
+ this.modalSelectedIndex = Math.min(editableFields.length - 1, this.modalSelectedIndex + 1);
850
+ this.renderModal();
851
+ return;
852
+ }
853
+ // Ctrl+E to open external editor
854
+ if (key.ctrl && key.name === 'e') {
855
+ this.openExternalEditor(editableFields[this.modalSelectedIndex]);
856
+ return;
857
+ }
858
+ // Enter to edit
859
+ if (key.name === 'return') {
860
+ this.characterEditMode = true;
861
+ this.characterEditField = editableFields[this.modalSelectedIndex];
862
+ this.characterEditBuffer = '';
863
+ this.renderModal();
864
+ return;
865
+ }
866
+ }
867
+ async openExternalEditor(fieldKey) {
868
+ const player = this.cachedState?.player;
869
+ if (!player)
870
+ return;
871
+ const fs = await import('fs');
872
+ const path = await import('path');
873
+ const os = await import('os');
874
+ const { spawn } = await import('child_process');
875
+ // Get current value
876
+ let currentValue = '';
877
+ if (fieldKey === 'name') {
878
+ currentValue = player.name;
879
+ }
880
+ else if (player.character) {
881
+ currentValue = player.character[fieldKey] || '';
882
+ }
883
+ // Create temp file
884
+ const tempDir = os.tmpdir();
885
+ const tempFile = path.join(tempDir, `rougelike_edit_${fieldKey}.txt`);
886
+ fs.writeFileSync(tempFile, currentValue, 'utf-8');
887
+ // Save state
888
+ this.characterExternalEditMode = true;
889
+ this.characterEditField = fieldKey;
890
+ this.characterExternalEditTempFile = tempFile;
891
+ // Open editor (Windows uses notepad)
892
+ const editor = process.platform === 'win32' ? 'notepad' : (process.env.EDITOR || 'vi');
893
+ const child = spawn(editor, [tempFile], {
894
+ stdio: 'inherit',
895
+ detached: false,
896
+ });
897
+ child.on('close', () => {
898
+ // Re-render the modal after editor closes
899
+ this.renderModal();
900
+ });
901
+ }
902
+ async confirmExternalEdit() {
903
+ if (!this.characterExternalEditTempFile)
904
+ return;
905
+ try {
906
+ const fs = await import('fs');
907
+ const content = fs.readFileSync(this.characterExternalEditTempFile, 'utf-8').trim();
908
+ if (content) {
909
+ this.onCharacterEdit?.(this.characterEditField, content);
910
+ }
911
+ // Clean up temp file
912
+ try {
913
+ fs.unlinkSync(this.characterExternalEditTempFile);
914
+ }
915
+ catch {
916
+ // Ignore cleanup errors
917
+ }
918
+ }
919
+ catch {
920
+ // Failed to read temp file
921
+ }
922
+ // Reset external edit state
923
+ this.characterExternalEditMode = false;
924
+ this.characterExternalEditTempFile = '';
925
+ this.renderModal();
926
+ }
927
+ handleQuestsKey(key) {
928
+ if (key.name === 'escape' || key.raw === 'q') {
929
+ this.closeModal();
930
+ return;
931
+ }
932
+ const quests = this.cachedQuests;
933
+ if (key.name === 'up' || key.raw === 'k') {
934
+ this.modalSelectedIndex = Math.max(0, this.modalSelectedIndex - 1);
935
+ this.renderModal();
936
+ return;
937
+ }
938
+ if (key.name === 'down' || key.raw === 'j') {
939
+ this.modalSelectedIndex = Math.min(quests.length - 1, this.modalSelectedIndex + 1);
940
+ this.renderModal();
941
+ return;
942
+ }
943
+ }
944
+ handleMenuKey(key) {
945
+ if (key.name === 'escape' || key.raw === 'm') {
946
+ this.closeModal();
947
+ return;
948
+ }
949
+ const menuItems = ['New Game', 'Save Game', 'Load Game', 'Help', 'Settings', 'Exit'];
950
+ if (key.name === 'up' || key.raw === 'k') {
951
+ this.modalSelectedIndex = Math.max(0, this.modalSelectedIndex - 1);
952
+ this.renderModal();
953
+ return;
954
+ }
955
+ if (key.name === 'down' || key.raw === 'j') {
956
+ this.modalSelectedIndex = Math.min(menuItems.length - 1, this.modalSelectedIndex + 1);
957
+ this.renderModal();
958
+ return;
959
+ }
960
+ if (key.name === 'return') {
961
+ const action = ['newgame', 'save', 'load', 'help', 'settings', 'exit'][this.modalSelectedIndex];
962
+ this.closeModal();
963
+ this.onCommand?.(action);
964
+ return;
965
+ }
966
+ }
967
+ getFilteredInventory() {
968
+ if (!this.cachedState?.player)
969
+ return [];
970
+ const items = this.cachedState.player.inventory;
971
+ const tabTypes = ['all', 'weapon', 'armor', 'consumable', 'material'];
972
+ const filterType = tabTypes[this.inventoryTab];
973
+ if (filterType === 'all')
974
+ return items;
975
+ return items.filter(i => i.type === filterType);
976
+ }
977
+ equipSelectedItem() {
978
+ const items = this.getFilteredInventory();
979
+ const item = items[this.modalSelectedIndex];
980
+ if (!item || !this.cachedState?.player)
981
+ return;
982
+ if (item.type === 'weapon' || item.type === 'armor') {
983
+ this.onCommand?.(`equip:${item.id}`);
984
+ this.addEventLog(`Equipped ${item.name}`);
985
+ }
986
+ this.renderModal();
987
+ }
988
+ useOrUnequipSelectedItem() {
989
+ const items = this.getFilteredInventory();
990
+ const item = items[this.modalSelectedIndex];
991
+ if (!item || !this.cachedState?.player)
992
+ return;
993
+ const eq = this.cachedState.player.equipment;
994
+ const isEquipped = eq.weapon?.id === item.id || eq.armor?.id === item.id || eq.accessory?.id === item.id;
995
+ if (isEquipped) {
996
+ this.onCommand?.(`unequip:${item.type}`);
997
+ this.addEventLog(`Unequipped ${item.name}`);
998
+ }
999
+ else if (item.type === 'consumable') {
1000
+ this.onCommand?.(`use:${item.id}`);
1001
+ this.addEventLog(`Used ${item.name}`);
1002
+ }
1003
+ this.renderModal();
1004
+ }
1005
+ dropSelectedItem() {
1006
+ const items = this.getFilteredInventory();
1007
+ const item = items[this.modalSelectedIndex];
1008
+ if (!item)
1009
+ return;
1010
+ this.onCommand?.(`drop:${item.id}`);
1011
+ this.addEventLog(`Dropped ${item.name}`);
1012
+ if (this.modalSelectedIndex >= items.length - 1) {
1013
+ this.modalSelectedIndex = Math.max(0, items.length - 2);
1014
+ }
1015
+ this.renderModal();
1016
+ }
1017
+ renderModal() {
1018
+ if (!this.activeModal || !this.cachedState)
1019
+ return;
1020
+ const { width: screenWidth, height: screenHeight } = this.screen.getSize();
1021
+ const modalWidth = Math.min(70, screenWidth - 6);
1022
+ const modalHeight = Math.min(24, screenHeight - 4);
1023
+ const mx = Math.floor((screenWidth - modalWidth) / 2);
1024
+ const my = Math.floor((screenHeight - modalHeight) / 2);
1025
+ // Fill modal background to cover the main screen
1026
+ for (let row = my; row < my + modalHeight; row++) {
1027
+ this.screen.write(mx, row, ' '.repeat(modalWidth), ANSI.fg.white, ANSI.bg.black);
1028
+ }
1029
+ // Draw modal box with title
1030
+ this.screen.drawBox(mx, my, modalWidth, modalHeight, this.activeModal.toUpperCase());
1031
+ // Content based on modal type
1032
+ switch (this.activeModal) {
1033
+ case 'inventory':
1034
+ this.renderInventoryModal(mx, my, modalWidth, modalHeight);
1035
+ break;
1036
+ case 'character':
1037
+ this.renderCharacterModal(mx, my, modalWidth, modalHeight);
1038
+ break;
1039
+ case 'quests':
1040
+ this.renderQuestsModal(mx, my, modalWidth, modalHeight);
1041
+ break;
1042
+ case 'workshop':
1043
+ this.renderWorkshopModal(mx, my, modalWidth, modalHeight);
1044
+ break;
1045
+ case 'menu':
1046
+ this.renderMenuModal(mx, my, modalWidth, modalHeight);
1047
+ break;
1048
+ }
1049
+ // Hint
1050
+ this.screen.write(mx + 2, my + modalHeight - 1, '[ESC] Close', ANSI.fg.gray);
1051
+ this.screen.render();
1052
+ }
1053
+ renderInventoryModal(x, y, width, height) {
1054
+ const player = this.cachedState?.player;
1055
+ if (!player)
1056
+ return;
1057
+ // Tabs
1058
+ let tabX = x + 2;
1059
+ for (let i = 0; i < this.INVENTORY_TABS.length; i++) {
1060
+ const isActive = i === this.inventoryTab;
1061
+ const fg = isActive ? ANSI.fg.black : ANSI.fg.white;
1062
+ const bg = isActive ? ANSI.bg.cyan : undefined;
1063
+ const label = `[${i + 1}]${this.INVENTORY_TABS[i]}`;
1064
+ this.screen.write(tabX, y + 2, label, fg, bg);
1065
+ tabX += label.length + 1;
1066
+ }
1067
+ // Gold
1068
+ this.screen.write(x + width - 15, y + 2, `Gold: ${player.stats.gold}`, ANSI.fg.yellow);
1069
+ // Split view: items list (left) + details (right)
1070
+ const listWidth = Math.floor((width - 4) * 0.5);
1071
+ const detailX = x + listWidth + 3;
1072
+ const detailWidth = width - listWidth - 5;
1073
+ const listY = y + 4;
1074
+ const listHeight = height - 7;
1075
+ // Items list
1076
+ this.screen.write(x + 2, listY - 1, '─'.repeat(listWidth), ANSI.fg.gray);
1077
+ const items = this.getFilteredInventory();
1078
+ if (items.length === 0) {
1079
+ this.screen.write(x + 2, listY + 1, '(No items)', ANSI.fg.gray);
1080
+ }
1081
+ else {
1082
+ const maxVisible = Math.min(listHeight, 10);
1083
+ for (let i = 0; i < maxVisible; i++) {
1084
+ const itemIdx = this.modalScrollOffset + i;
1085
+ if (itemIdx >= items.length)
1086
+ break;
1087
+ const item = items[itemIdx];
1088
+ const isSelected = itemIdx === this.modalSelectedIndex;
1089
+ const eq = player.equipment;
1090
+ const isEquipped = eq.weapon?.id === item.id || eq.armor?.id === item.id || eq.accessory?.id === item.id;
1091
+ const equipMark = isEquipped ? '[E]' : ' ';
1092
+ const fg = isSelected ? ANSI.fg.black : ANSI.fg.white;
1093
+ const bg = isSelected ? ANSI.bg.cyan : undefined;
1094
+ const icon = this.getItemIcon(item.type);
1095
+ const label = `${icon} ${item.name} ${equipMark}`.slice(0, listWidth - 2);
1096
+ this.screen.write(x + 2, listY + i, isSelected ? '▸' : ' ', ANSI.fg.yellow);
1097
+ this.screen.write(x + 4, listY + i, label.padEnd(listWidth - 4), fg, bg);
1098
+ }
1099
+ // Scroll indicator
1100
+ if (items.length > maxVisible) {
1101
+ const pct = this.modalScrollOffset / Math.max(1, items.length - maxVisible);
1102
+ const barY = Math.floor(pct * (maxVisible - 1));
1103
+ for (let i = 0; i < maxVisible; i++) {
1104
+ this.screen.write(x + listWidth, listY + i, i === barY ? '█' : '░', ANSI.fg.gray);
1105
+ }
1106
+ }
1107
+ }
1108
+ // Item details (right panel)
1109
+ this.screen.write(detailX, listY - 1, '─ Details ─', ANSI.fg.gray);
1110
+ const selectedItem = items[this.modalSelectedIndex];
1111
+ if (selectedItem) {
1112
+ let lineY = listY;
1113
+ this.screen.write(detailX, lineY++, selectedItem.name, ANSI.fg.yellow, undefined, ANSI.bold);
1114
+ this.screen.write(detailX, lineY++, `Type: ${selectedItem.type}`, ANSI.fg.gray);
1115
+ lineY++;
1116
+ if (selectedItem.type === 'weapon') {
1117
+ const w = selectedItem;
1118
+ this.screen.write(detailX, lineY++, `Rarity: ${w.rarity}`, this.getRarityColor(w.rarity));
1119
+ this.screen.write(detailX, lineY++, `Attack: ${w.baseAttack}`, ANSI.fg.red);
1120
+ if (w.durability !== undefined) {
1121
+ this.screen.write(detailX, lineY++, `Durability: ${w.durability}/${w.maxDurability}`, ANSI.fg.white);
1122
+ }
1123
+ }
1124
+ if (selectedItem.type === 'armor') {
1125
+ const a = selectedItem;
1126
+ this.screen.write(detailX, lineY++, `Rarity: ${a.rarity}`, this.getRarityColor(a.rarity));
1127
+ this.screen.write(detailX, lineY++, `Defense: ${a.baseDefense}`, ANSI.fg.blue);
1128
+ }
1129
+ if (selectedItem.description) {
1130
+ lineY++;
1131
+ const desc = selectedItem.description.slice(0, detailWidth * 2);
1132
+ this.screen.write(detailX, lineY++, desc.slice(0, detailWidth), ANSI.fg.gray);
1133
+ if (desc.length > detailWidth) {
1134
+ this.screen.write(detailX, lineY++, desc.slice(detailWidth), ANSI.fg.gray);
1135
+ }
1136
+ }
1137
+ // Actions
1138
+ lineY = y + height - 4;
1139
+ const eq = player.equipment;
1140
+ const isEquipped = eq.weapon?.id === selectedItem.id || eq.armor?.id === selectedItem.id;
1141
+ if (isEquipped) {
1142
+ this.screen.write(detailX, lineY++, '[U] Unequip', ANSI.fg.white);
1143
+ }
1144
+ else if (selectedItem.type === 'weapon' || selectedItem.type === 'armor') {
1145
+ this.screen.write(detailX, lineY++, '[E] Equip', ANSI.fg.white);
1146
+ }
1147
+ else if (selectedItem.type === 'consumable') {
1148
+ this.screen.write(detailX, lineY++, '[U] Use', ANSI.fg.white);
1149
+ }
1150
+ this.screen.write(detailX, lineY, '[D] Drop', ANSI.fg.red);
1151
+ }
1152
+ // Equipment summary
1153
+ const eqY = y + height - 2;
1154
+ const eqWeapon = player.equipment.weapon?.name || '(none)';
1155
+ const eqArmor = player.equipment.armor?.name || '(none)';
1156
+ this.screen.write(x + 2, eqY, `⚔${eqWeapon} 🛡${eqArmor}`, ANSI.fg.gray);
1157
+ }
1158
+ renderCharacterModal(x, y, width, height) {
1159
+ const player = this.cachedState?.player;
1160
+ if (!player)
1161
+ return;
1162
+ const s = player.stats;
1163
+ let lineY = y + 2;
1164
+ // Header
1165
+ this.screen.write(x + 2, lineY++, player.name, ANSI.fg.yellow, undefined, ANSI.bold);
1166
+ this.screen.write(x + 2, lineY++, `Level ${s.level} Adventurer`, ANSI.fg.gray);
1167
+ lineY++;
1168
+ // Stats section
1169
+ this.screen.write(x + 2, lineY++, '─── Stats ───', ANSI.fg.cyan);
1170
+ this.screen.write(x + 2, lineY, `HP: `, ANSI.fg.white);
1171
+ this.screen.drawProgressBar(x + 6, lineY, 12, s.hp, s.maxHp, ANSI.fg.red);
1172
+ this.screen.write(x + 19, lineY++, `${s.hp}/${s.maxHp}`, ANSI.fg.white);
1173
+ this.screen.write(x + 2, lineY, `MP: `, ANSI.fg.white);
1174
+ this.screen.drawProgressBar(x + 6, lineY, 12, s.mp, s.maxMp, ANSI.fg.blue);
1175
+ this.screen.write(x + 19, lineY++, `${s.mp}/${s.maxMp}`, ANSI.fg.white);
1176
+ this.screen.write(x + 2, lineY, `EXP:`, ANSI.fg.white);
1177
+ this.screen.drawProgressBar(x + 6, lineY, 12, s.exp, s.expToNextLevel, ANSI.fg.green);
1178
+ this.screen.write(x + 19, lineY++, `${s.exp}/${s.expToNextLevel}`, ANSI.fg.white);
1179
+ lineY++;
1180
+ this.screen.write(x + 2, lineY++, `ATK: ${s.attack} DEF: ${s.defense} SPD: ${s.speed}`, ANSI.fg.white);
1181
+ lineY++;
1182
+ // Editable fields section
1183
+ this.screen.write(x + 2, lineY++, '─── Profile ───', ANSI.fg.cyan);
1184
+ const editableFields = [
1185
+ { key: 'name', label: 'Name', value: player.name },
1186
+ { key: 'backstory', label: 'Backstory', value: player.character?.backstory || '(not set)' },
1187
+ { key: 'appearance', label: 'Appearance', value: player.character?.appearance || '(not set)' },
1188
+ { key: 'personality', label: 'Personality', value: player.character?.personality || '(not set)' },
1189
+ ];
1190
+ for (let i = 0; i < editableFields.length; i++) {
1191
+ const field = editableFields[i];
1192
+ const isSelected = i === this.modalSelectedIndex && !this.characterEditMode && !this.characterExternalEditMode;
1193
+ const isEditing = this.characterEditMode && this.characterEditField === field.key;
1194
+ const isExternalEditing = this.characterExternalEditMode && this.characterEditField === field.key;
1195
+ const fg = isSelected ? ANSI.fg.black : ANSI.fg.white;
1196
+ const bg = isSelected ? ANSI.bg.cyan : undefined;
1197
+ const indicator = isSelected ? '▸' : ' ';
1198
+ this.screen.write(x + 2, lineY, indicator, ANSI.fg.yellow);
1199
+ this.screen.write(x + 4, lineY, `${field.label}: `, fg, bg);
1200
+ if (isEditing) {
1201
+ this.screen.write(x + 4 + field.label.length + 2, lineY, this.characterEditBuffer + '▌', ANSI.fg.cyan);
1202
+ }
1203
+ else if (isExternalEditing) {
1204
+ this.screen.write(x + 4 + field.label.length + 2, lineY, 'Press Enter To Confirm', ANSI.fg.cyan);
1205
+ }
1206
+ else {
1207
+ const displayValue = field.value.slice(0, width - field.label.length - 10);
1208
+ this.screen.write(x + 4 + field.label.length + 2, lineY, displayValue, ANSI.fg.gray);
1209
+ }
1210
+ lineY++;
1211
+ }
1212
+ // Hint
1213
+ if (this.characterExternalEditMode) {
1214
+ this.screen.write(x + 2, y + height - 2, 'Enter: confirm Esc: cancel', ANSI.fg.gray);
1215
+ }
1216
+ else if (this.characterEditMode) {
1217
+ this.screen.write(x + 2, y + height - 2, 'Enter: save Esc: cancel', ANSI.fg.gray);
1218
+ }
1219
+ else {
1220
+ this.screen.write(x + 2, y + height - 2, '↑↓: select Enter: edit Ctrl+E: external editor Esc: close', ANSI.fg.gray);
1221
+ }
1222
+ }
1223
+ renderQuestsModal(x, y, width, height) {
1224
+ const quests = this.cachedQuests;
1225
+ if (quests.length === 0) {
1226
+ this.screen.write(x + 2, y + 2, '(No active quests)', ANSI.fg.gray);
1227
+ this.screen.write(x + 2, y + 4, 'Talk to NPCs to get quests!', ANSI.fg.white);
1228
+ return;
1229
+ }
1230
+ // Quest list (left) + details (right)
1231
+ const listWidth = Math.floor((width - 4) * 0.4);
1232
+ const detailX = x + listWidth + 3;
1233
+ // Quest list
1234
+ this.screen.write(x + 2, y + 2, '─── Quests ───', ANSI.fg.cyan);
1235
+ quests.slice(0, height - 6).forEach((quest, i) => {
1236
+ const completed = quest.objectives.filter(o => o.isComplete).length;
1237
+ const total = quest.objectives.length;
1238
+ const isSelected = i === this.modalSelectedIndex;
1239
+ const fg = isSelected ? ANSI.fg.black : ANSI.fg.white;
1240
+ const bg = isSelected ? ANSI.bg.cyan : undefined;
1241
+ const status = completed === total ? '✓' : `${completed}/${total}`;
1242
+ const label = `${quest.name} [${status}]`.slice(0, listWidth - 2);
1243
+ this.screen.write(x + 2, y + 3 + i, isSelected ? '▸' : ' ', ANSI.fg.yellow);
1244
+ this.screen.write(x + 4, y + 3 + i, label.padEnd(listWidth - 4), fg, bg);
1245
+ });
1246
+ // Quest details
1247
+ const selectedQuest = quests[this.modalSelectedIndex];
1248
+ if (selectedQuest) {
1249
+ let lineY = y + 2;
1250
+ this.screen.write(detailX, lineY++, selectedQuest.name, ANSI.fg.yellow, undefined, ANSI.bold);
1251
+ this.screen.write(detailX, lineY++, `Type: ${selectedQuest.type}`, ANSI.fg.gray);
1252
+ lineY++;
1253
+ if (selectedQuest.description) {
1254
+ const desc = selectedQuest.description.slice(0, (width - listWidth - 6) * 2);
1255
+ this.screen.write(detailX, lineY++, desc.slice(0, width - listWidth - 6), ANSI.fg.white);
1256
+ }
1257
+ lineY++;
1258
+ this.screen.write(detailX, lineY++, '─── Objectives ───', ANSI.fg.cyan);
1259
+ for (const obj of selectedQuest.objectives.slice(0, 5)) {
1260
+ const check = obj.isComplete ? '✓' : '○';
1261
+ const color = obj.isComplete ? ANSI.fg.green : ANSI.fg.white;
1262
+ const progress = obj.required > 1 ? ` (${obj.current}/${obj.required})` : '';
1263
+ this.screen.write(detailX, lineY++, `${check} ${obj.description}${progress}`, color);
1264
+ }
1265
+ // Rewards
1266
+ if (selectedQuest.rewards && selectedQuest.rewards.length > 0) {
1267
+ lineY++;
1268
+ this.screen.write(detailX, lineY++, '─── Rewards ───', ANSI.fg.cyan);
1269
+ for (const reward of selectedQuest.rewards) {
1270
+ const qty = reward.quantity ? ` x${reward.quantity}` : '';
1271
+ if (reward.type === 'gold') {
1272
+ this.screen.write(detailX, lineY++, `Gold: ${reward.value}${qty}`, ANSI.fg.yellow);
1273
+ }
1274
+ else if (reward.type === 'exp') {
1275
+ this.screen.write(detailX, lineY++, `EXP: ${reward.value}${qty}`, ANSI.fg.green);
1276
+ }
1277
+ else if (reward.type === 'item') {
1278
+ this.screen.write(detailX, lineY++, `Item: ${reward.value}${qty}`, ANSI.fg.cyan);
1279
+ }
1280
+ }
1281
+ }
1282
+ }
1283
+ }
1284
+ renderWorkshopModal(x, y, width, height) {
1285
+ this.screen.write(x + 2, y + 2, 'Affix Workshop', ANSI.fg.cyan, undefined, ANSI.bold);
1286
+ this.screen.write(x + 2, y + 4, 'Modify and enhance your equipment with affixes.', ANSI.fg.white);
1287
+ this.screen.write(x + 2, y + 6, '─── Options ───', ANSI.fg.cyan);
1288
+ const options = [
1289
+ 'Add Affix - Add a new modifier to equipment',
1290
+ 'Remove Affix - Remove an existing modifier',
1291
+ 'Reroll Affix - Reroll modifier values',
1292
+ 'Craft Item - Create new equipment',
1293
+ ];
1294
+ options.forEach((opt, i) => {
1295
+ const isSelected = i === this.modalSelectedIndex;
1296
+ const fg = isSelected ? ANSI.fg.black : ANSI.fg.white;
1297
+ const bg = isSelected ? ANSI.bg.cyan : undefined;
1298
+ this.screen.write(x + 2, y + 7 + i, isSelected ? '▸' : ' ', ANSI.fg.yellow);
1299
+ this.screen.write(x + 4, y + 7 + i, opt.slice(0, width - 6), fg, bg);
1300
+ });
1301
+ this.screen.write(x + 2, y + height - 3, 'Workshop requires materials and gold.', ANSI.fg.gray);
1302
+ this.screen.write(x + 2, y + height - 2, 'Feature coming soon!', ANSI.fg.yellow);
1303
+ }
1304
+ getItemIcon(type) {
1305
+ switch (type) {
1306
+ case 'weapon': return '⚔';
1307
+ case 'armor': return '🛡';
1308
+ case 'consumable': return '🧪';
1309
+ case 'material': return '📦';
1310
+ default: return '•';
1311
+ }
1312
+ }
1313
+ getRarityColor(rarity) {
1314
+ switch (rarity?.toLowerCase()) {
1315
+ case 'common': return ANSI.fg.white;
1316
+ case 'uncommon': return ANSI.fg.green;
1317
+ case 'rare': return ANSI.fg.blue;
1318
+ case 'epic': return ANSI.fg.magenta;
1319
+ case 'legendary': return ANSI.fg.yellow;
1320
+ default: return ANSI.fg.gray;
1321
+ }
1322
+ }
1323
+ renderMenuModal(x, y, width, height) {
1324
+ const menuItems = ['New Game', 'Save Game', 'Load Game', 'Help', 'Settings', 'Exit'];
1325
+ menuItems.forEach((item, i) => {
1326
+ const isSelected = i === this.modalSelectedIndex;
1327
+ const fg = isSelected ? ANSI.fg.black : ANSI.fg.white;
1328
+ const bg = isSelected ? ANSI.bg.cyan : undefined;
1329
+ this.screen.write(x + 2, y + 2 + i, item.padEnd(width - 4), fg, bg);
1330
+ });
1331
+ }
1332
+ }
1333
+ // Singleton instance
1334
+ let gameUIInstance = null;
1335
+ export function getGameUI() {
1336
+ if (!gameUIInstance) {
1337
+ gameUIInstance = new GameUI();
1338
+ }
1339
+ return gameUIInstance;
1340
+ }
1341
+ //# sourceMappingURL=GameUIAdapter.js.map
1342
+ //# debugId=49e26bda-c9cc-543b-8e45-e2d60795329c