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.
- package/README.md +171 -0
- package/dist/affix/AffixRegistry.d.ts +68 -0
- package/dist/affix/AffixRegistry.d.ts.map +1 -0
- package/dist/affix/AffixRegistry.js +245 -0
- package/dist/affix/AffixRegistry.js.map +1 -0
- package/dist/affix/PluginLoader.d.ts +66 -0
- package/dist/affix/PluginLoader.d.ts.map +1 -0
- package/dist/affix/PluginLoader.js +321 -0
- package/dist/affix/PluginLoader.js.map +1 -0
- package/dist/affix/builtin.d.ts +6 -0
- package/dist/affix/builtin.d.ts.map +1 -0
- package/dist/affix/builtin.js +311 -0
- package/dist/affix/builtin.js.map +1 -0
- package/dist/affix/index.d.ts +8 -0
- package/dist/affix/index.d.ts.map +1 -0
- package/dist/affix/index.js +11 -0
- package/dist/affix/index.js.map +1 -0
- package/dist/affix/types.d.ts +152 -0
- package/dist/affix/types.d.ts.map +1 -0
- package/dist/affix/types.js +8 -0
- package/dist/affix/types.js.map +1 -0
- package/dist/ai/PlayKitClient.d.ts +208 -0
- package/dist/ai/PlayKitClient.d.ts.map +1 -0
- package/dist/ai/PlayKitClient.js +721 -0
- package/dist/ai/PlayKitClient.js.map +1 -0
- package/dist/ai/index.d.ts +5 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +8 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/app/App.d.ts +85 -0
- package/dist/app/App.d.ts.map +1 -0
- package/dist/app/App.js +296 -0
- package/dist/app/App.js.map +1 -0
- package/dist/app/EventBus.d.ts +222 -0
- package/dist/app/EventBus.d.ts.map +1 -0
- package/dist/app/EventBus.js +88 -0
- package/dist/app/EventBus.js.map +1 -0
- package/dist/app/Keybind.d.ts +83 -0
- package/dist/app/Keybind.d.ts.map +1 -0
- package/dist/app/Keybind.js +184 -0
- package/dist/app/Keybind.js.map +1 -0
- package/dist/app/Router.d.ts +123 -0
- package/dist/app/Router.d.ts.map +1 -0
- package/dist/app/Router.js +142 -0
- package/dist/app/Router.js.map +1 -0
- package/dist/app/ScreenManager.d.ts +97 -0
- package/dist/app/ScreenManager.d.ts.map +1 -0
- package/dist/app/ScreenManager.js +216 -0
- package/dist/app/ScreenManager.js.map +1 -0
- package/dist/app/index.d.ts +14 -0
- package/dist/app/index.d.ts.map +1 -0
- package/dist/app/index.js +19 -0
- package/dist/app/index.js.map +1 -0
- package/dist/app/screens/BaseScreen.d.ts +101 -0
- package/dist/app/screens/BaseScreen.d.ts.map +1 -0
- package/dist/app/screens/BaseScreen.js +132 -0
- package/dist/app/screens/BaseScreen.js.map +1 -0
- package/dist/app/screens/CharacterCreationScreen.d.ts +42 -0
- package/dist/app/screens/CharacterCreationScreen.d.ts.map +1 -0
- package/dist/app/screens/CharacterCreationScreen.js +467 -0
- package/dist/app/screens/CharacterCreationScreen.js.map +1 -0
- package/dist/app/screens/CombatScreen.d.ts +30 -0
- package/dist/app/screens/CombatScreen.d.ts.map +1 -0
- package/dist/app/screens/CombatScreen.js +309 -0
- package/dist/app/screens/CombatScreen.js.map +1 -0
- package/dist/app/screens/DialogScreen.d.ts +29 -0
- package/dist/app/screens/DialogScreen.d.ts.map +1 -0
- package/dist/app/screens/DialogScreen.js +295 -0
- package/dist/app/screens/DialogScreen.js.map +1 -0
- package/dist/app/screens/ExploreScreen.d.ts +50 -0
- package/dist/app/screens/ExploreScreen.d.ts.map +1 -0
- package/dist/app/screens/ExploreScreen.js +308 -0
- package/dist/app/screens/ExploreScreen.js.map +1 -0
- package/dist/app/screens/HelpScreen.d.ts +12 -0
- package/dist/app/screens/HelpScreen.d.ts.map +1 -0
- package/dist/app/screens/HelpScreen.js +155 -0
- package/dist/app/screens/HelpScreen.js.map +1 -0
- package/dist/app/screens/InventoryScreen.d.ts +27 -0
- package/dist/app/screens/InventoryScreen.d.ts.map +1 -0
- package/dist/app/screens/InventoryScreen.js +326 -0
- package/dist/app/screens/InventoryScreen.js.map +1 -0
- package/dist/app/screens/PrologueScreen.d.ts +24 -0
- package/dist/app/screens/PrologueScreen.d.ts.map +1 -0
- package/dist/app/screens/PrologueScreen.js +176 -0
- package/dist/app/screens/PrologueScreen.js.map +1 -0
- package/dist/app/screens/TitleScreen.d.ts +42 -0
- package/dist/app/screens/TitleScreen.d.ts.map +1 -0
- package/dist/app/screens/TitleScreen.js +380 -0
- package/dist/app/screens/TitleScreen.js.map +1 -0
- package/dist/app/screens/TravelScreen.d.ts +22 -0
- package/dist/app/screens/TravelScreen.d.ts.map +1 -0
- package/dist/app/screens/TravelScreen.js +122 -0
- package/dist/app/screens/TravelScreen.js.map +1 -0
- package/dist/app/screens/index.d.ts +14 -0
- package/dist/app/screens/index.d.ts.map +1 -0
- package/dist/app/screens/index.js +17 -0
- package/dist/app/screens/index.js.map +1 -0
- package/dist/commands/CommandRegistry.d.ts +91 -0
- package/dist/commands/CommandRegistry.d.ts.map +1 -0
- package/dist/commands/CommandRegistry.js +159 -0
- package/dist/commands/CommandRegistry.js.map +1 -0
- package/dist/commands/index.d.ts +7 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +10 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/core/Actor.d.ts +103 -0
- package/dist/core/Actor.d.ts.map +1 -0
- package/dist/core/Actor.js +409 -0
- package/dist/core/Actor.js.map +1 -0
- package/dist/core/Combat.d.ts +37 -0
- package/dist/core/Combat.d.ts.map +1 -0
- package/dist/core/Combat.js +294 -0
- package/dist/core/Combat.js.map +1 -0
- package/dist/core/DungeonRunner.d.ts +169 -0
- package/dist/core/DungeonRunner.d.ts.map +1 -0
- package/dist/core/DungeonRunner.js +627 -0
- package/dist/core/DungeonRunner.js.map +1 -0
- package/dist/core/Game.d.ts +133 -0
- package/dist/core/Game.d.ts.map +1 -0
- package/dist/core/Game.js +644 -0
- package/dist/core/Game.js.map +1 -0
- package/dist/core/IdleCombat.d.ts +61 -0
- package/dist/core/IdleCombat.d.ts.map +1 -0
- package/dist/core/IdleCombat.js +461 -0
- package/dist/core/IdleCombat.js.map +1 -0
- package/dist/core/IdleGameManager.d.ts +198 -0
- package/dist/core/IdleGameManager.d.ts.map +1 -0
- package/dist/core/IdleGameManager.js +688 -0
- package/dist/core/IdleGameManager.js.map +1 -0
- package/dist/core/IdleSaveManager.d.ts +109 -0
- package/dist/core/IdleSaveManager.d.ts.map +1 -0
- package/dist/core/IdleSaveManager.js +296 -0
- package/dist/core/IdleSaveManager.js.map +1 -0
- package/dist/core/NewGameFlowManager.d.ts +64 -0
- package/dist/core/NewGameFlowManager.d.ts.map +1 -0
- package/dist/core/NewGameFlowManager.js +153 -0
- package/dist/core/NewGameFlowManager.js.map +1 -0
- package/dist/core/Player.d.ts +65 -0
- package/dist/core/Player.d.ts.map +1 -0
- package/dist/core/Player.js +261 -0
- package/dist/core/Player.js.map +1 -0
- package/dist/core/RoomHandlers.d.ts +75 -0
- package/dist/core/RoomHandlers.d.ts.map +1 -0
- package/dist/core/RoomHandlers.js +383 -0
- package/dist/core/RoomHandlers.js.map +1 -0
- package/dist/core/SaveManager.d.ts +84 -0
- package/dist/core/SaveManager.d.ts.map +1 -0
- package/dist/core/SaveManager.js +281 -0
- package/dist/core/SaveManager.js.map +1 -0
- package/dist/core/SaveMigration.d.ts +69 -0
- package/dist/core/SaveMigration.d.ts.map +1 -0
- package/dist/core/SaveMigration.js +408 -0
- package/dist/core/SaveMigration.js.map +1 -0
- package/dist/core/StateAdapter.d.ts +79 -0
- package/dist/core/StateAdapter.d.ts.map +1 -0
- package/dist/core/StateAdapter.js +397 -0
- package/dist/core/StateAdapter.js.map +1 -0
- package/dist/core/Team.d.ts +145 -0
- package/dist/core/Team.d.ts.map +1 -0
- package/dist/core/Team.js +371 -0
- package/dist/core/Team.js.map +1 -0
- package/dist/core/TeamCombat.d.ts +88 -0
- package/dist/core/TeamCombat.d.ts.map +1 -0
- package/dist/core/TeamCombat.js +405 -0
- package/dist/core/TeamCombat.js.map +1 -0
- package/dist/core/TeamDungeonRunner.d.ts +186 -0
- package/dist/core/TeamDungeonRunner.d.ts.map +1 -0
- package/dist/core/TeamDungeonRunner.js +758 -0
- package/dist/core/TeamDungeonRunner.js.map +1 -0
- package/dist/core/TimeManager.d.ts +114 -0
- package/dist/core/TimeManager.d.ts.map +1 -0
- package/dist/core/TimeManager.js +318 -0
- package/dist/core/TimeManager.js.map +1 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +12 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/timeConstants.d.ts +135 -0
- package/dist/core/timeConstants.d.ts.map +1 -0
- package/dist/core/timeConstants.js +157 -0
- package/dist/core/timeConstants.js.map +1 -0
- package/dist/core/types.d.ts +780 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +16 -0
- package/dist/core/types.js.map +1 -0
- package/dist/data/continents/index.d.ts +163 -0
- package/dist/data/continents/index.d.ts.map +1 -0
- package/dist/data/continents/index.js +31 -0
- package/dist/data/continents/index.js.map +1 -0
- package/dist/data/continents/verdantia.d.ts +294 -0
- package/dist/data/continents/verdantia.d.ts.map +1 -0
- package/dist/data/continents/verdantia.js +327 -0
- package/dist/data/continents/verdantia.js.map +1 -0
- package/dist/handlers/DialogHandler.d.ts +95 -0
- package/dist/handlers/DialogHandler.d.ts.map +1 -0
- package/dist/handlers/DialogHandler.js +450 -0
- package/dist/handlers/DialogHandler.js.map +1 -0
- package/dist/handlers/SaveLoadHandler.d.ts +60 -0
- package/dist/handlers/SaveLoadHandler.d.ts.map +1 -0
- package/dist/handlers/SaveLoadHandler.js +187 -0
- package/dist/handlers/SaveLoadHandler.js.map +1 -0
- package/dist/handlers/TitleScreenHandler.d.ts +43 -0
- package/dist/handlers/TitleScreenHandler.d.ts.map +1 -0
- package/dist/handlers/TitleScreenHandler.js +508 -0
- package/dist/handlers/TitleScreenHandler.js.map +1 -0
- package/dist/handlers/WorkshopHandler.d.ts +75 -0
- package/dist/handlers/WorkshopHandler.d.ts.map +1 -0
- package/dist/handlers/WorkshopHandler.js +401 -0
- package/dist/handlers/WorkshopHandler.js.map +1 -0
- package/dist/handlers/index.d.ts +12 -0
- package/dist/handlers/index.d.ts.map +1 -0
- package/dist/handlers/index.js +14 -0
- package/dist/handlers/index.js.map +1 -0
- package/dist/handlers/types.d.ts +34 -0
- package/dist/handlers/types.d.ts.map +1 -0
- package/dist/handlers/types.js +8 -0
- package/dist/handlers/types.js.map +1 -0
- package/dist/i18n/en.d.ts +3 -0
- package/dist/i18n/en.d.ts.map +1 -0
- package/dist/i18n/en.js +130 -0
- package/dist/i18n/en.js.map +1 -0
- package/dist/i18n/index.d.ts +40 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +105 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n/types.d.ts +133 -0
- package/dist/i18n/types.d.ts.map +1 -0
- package/dist/i18n/types.js +8 -0
- package/dist/i18n/types.js.map +1 -0
- package/dist/i18n/zh.d.ts +3 -0
- package/dist/i18n/zh.d.ts.map +1 -0
- package/dist/i18n/zh.js +130 -0
- package/dist/i18n/zh.js.map +1 -0
- package/dist/instrument.d.ts +8 -0
- package/dist/instrument.d.ts.map +1 -0
- package/dist/instrument.js +33 -0
- package/dist/instrument.js.map +1 -0
- package/dist/main-new.d.ts +12 -0
- package/dist/main-new.d.ts.map +1 -0
- package/dist/main-new.js +32 -0
- package/dist/main-new.js.map +1 -0
- package/dist/main.d.ts +7 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +2816 -0
- package/dist/main.js.map +1 -0
- package/dist/map/ContinentManager.d.ts +88 -0
- package/dist/map/ContinentManager.d.ts.map +1 -0
- package/dist/map/ContinentManager.js +241 -0
- package/dist/map/ContinentManager.js.map +1 -0
- package/dist/map/DungeonGenerator.d.ts +32 -0
- package/dist/map/DungeonGenerator.d.ts.map +1 -0
- package/dist/map/DungeonGenerator.js +615 -0
- package/dist/map/DungeonGenerator.js.map +1 -0
- package/dist/map/MapGenerator.d.ts +27 -0
- package/dist/map/MapGenerator.d.ts.map +1 -0
- package/dist/map/MapGenerator.js +485 -0
- package/dist/map/MapGenerator.js.map +1 -0
- package/dist/map/index.d.ts +5 -0
- package/dist/map/index.d.ts.map +1 -0
- package/dist/map/index.js +8 -0
- package/dist/map/index.js.map +1 -0
- package/dist/npc/NPCGenerator.d.ts +68 -0
- package/dist/npc/NPCGenerator.d.ts.map +1 -0
- package/dist/npc/NPCGenerator.js +468 -0
- package/dist/npc/NPCGenerator.js.map +1 -0
- package/dist/npc/NPCManager.d.ts +86 -0
- package/dist/npc/NPCManager.d.ts.map +1 -0
- package/dist/npc/NPCManager.js +217 -0
- package/dist/npc/NPCManager.js.map +1 -0
- package/dist/npc/fixedNPCs.d.ts +7 -0
- package/dist/npc/fixedNPCs.d.ts.map +1 -0
- package/dist/npc/fixedNPCs.js +196 -0
- package/dist/npc/fixedNPCs.js.map +1 -0
- package/dist/npc/index.d.ts +9 -0
- package/dist/npc/index.d.ts.map +1 -0
- package/dist/npc/index.js +12 -0
- package/dist/npc/index.js.map +1 -0
- package/dist/npc/traits.d.ts +33 -0
- package/dist/npc/traits.d.ts.map +1 -0
- package/dist/npc/traits.js +795 -0
- package/dist/npc/traits.js.map +1 -0
- package/dist/npc/types.d.ts +193 -0
- package/dist/npc/types.d.ts.map +1 -0
- package/dist/npc/types.js +9 -0
- package/dist/npc/types.js.map +1 -0
- package/dist/quest/QuestManager.d.ts +79 -0
- package/dist/quest/QuestManager.d.ts.map +1 -0
- package/dist/quest/QuestManager.js +273 -0
- package/dist/quest/QuestManager.js.map +1 -0
- package/dist/quest/index.d.ts +6 -0
- package/dist/quest/index.d.ts.map +1 -0
- package/dist/quest/index.js +9 -0
- package/dist/quest/index.js.map +1 -0
- package/dist/quest/types.d.ts +81 -0
- package/dist/quest/types.d.ts.map +1 -0
- package/dist/quest/types.js +8 -0
- package/dist/quest/types.js.map +1 -0
- package/dist/strategy/StrategyExecutor.d.ts +159 -0
- package/dist/strategy/StrategyExecutor.d.ts.map +1 -0
- package/dist/strategy/StrategyExecutor.js +479 -0
- package/dist/strategy/StrategyExecutor.js.map +1 -0
- package/dist/strategy/StrategyParser.d.ts +48 -0
- package/dist/strategy/StrategyParser.d.ts.map +1 -0
- package/dist/strategy/StrategyParser.js +321 -0
- package/dist/strategy/StrategyParser.js.map +1 -0
- package/dist/strategy/defaultStrategy.d.ts +40 -0
- package/dist/strategy/defaultStrategy.d.ts.map +1 -0
- package/dist/strategy/defaultStrategy.js +254 -0
- package/dist/strategy/defaultStrategy.js.map +1 -0
- package/dist/strategy/index.d.ts +8 -0
- package/dist/strategy/index.d.ts.map +1 -0
- package/dist/strategy/index.js +14 -0
- package/dist/strategy/index.js.map +1 -0
- package/dist/tui/ExploreMenu.d.ts +106 -0
- package/dist/tui/ExploreMenu.d.ts.map +1 -0
- package/dist/tui/ExploreMenu.js +282 -0
- package/dist/tui/ExploreMenu.js.map +1 -0
- package/dist/tui/GameUI.d.ts +313 -0
- package/dist/tui/GameUI.d.ts.map +1 -0
- package/dist/tui/GameUI.js +2116 -0
- package/dist/tui/GameUI.js.map +1 -0
- package/dist/tui/GameUIAdapter.d.ts +207 -0
- package/dist/tui/GameUIAdapter.d.ts.map +1 -0
- package/dist/tui/GameUIAdapter.js +1342 -0
- package/dist/tui/GameUIAdapter.js.map +1 -0
- package/dist/tui/Input.d.ts +139 -0
- package/dist/tui/Input.d.ts.map +1 -0
- package/dist/tui/Input.js +278 -0
- package/dist/tui/Input.js.map +1 -0
- package/dist/tui/Menu.d.ts +110 -0
- package/dist/tui/Menu.d.ts.map +1 -0
- package/dist/tui/Menu.js +365 -0
- package/dist/tui/Menu.js.map +1 -0
- package/dist/tui/Screen.d.ts +228 -0
- package/dist/tui/Screen.d.ts.map +1 -0
- package/dist/tui/Screen.js +502 -0
- package/dist/tui/Screen.js.map +1 -0
- package/dist/tui/components/Box.d.ts +36 -0
- package/dist/tui/components/Box.d.ts.map +1 -0
- package/dist/tui/components/Box.js +43 -0
- package/dist/tui/components/Box.js.map +1 -0
- package/dist/tui/components/List.d.ts +69 -0
- package/dist/tui/components/List.d.ts.map +1 -0
- package/dist/tui/components/List.js +136 -0
- package/dist/tui/components/List.js.map +1 -0
- package/dist/tui/components/ProgressBar.d.ts +42 -0
- package/dist/tui/components/ProgressBar.d.ts.map +1 -0
- package/dist/tui/components/ProgressBar.js +75 -0
- package/dist/tui/components/ProgressBar.js.map +1 -0
- package/dist/tui/components/index.d.ts +8 -0
- package/dist/tui/components/index.d.ts.map +1 -0
- package/dist/tui/components/index.js +11 -0
- package/dist/tui/components/index.js.map +1 -0
- package/dist/tui/core/BaseSection.d.ts +98 -0
- package/dist/tui/core/BaseSection.d.ts.map +1 -0
- package/dist/tui/core/BaseSection.js +174 -0
- package/dist/tui/core/BaseSection.js.map +1 -0
- package/dist/tui/core/Component.d.ts +61 -0
- package/dist/tui/core/Component.d.ts.map +1 -0
- package/dist/tui/core/Component.js +32 -0
- package/dist/tui/core/Component.js.map +1 -0
- package/dist/tui/core/Section.d.ts +101 -0
- package/dist/tui/core/Section.d.ts.map +1 -0
- package/dist/tui/core/Section.js +24 -0
- package/dist/tui/core/Section.js.map +1 -0
- package/dist/tui/core/SectionManager.d.ts +108 -0
- package/dist/tui/core/SectionManager.d.ts.map +1 -0
- package/dist/tui/core/SectionManager.js +258 -0
- package/dist/tui/core/SectionManager.js.map +1 -0
- package/dist/tui/core/index.d.ts +9 -0
- package/dist/tui/core/index.d.ts.map +1 -0
- package/dist/tui/core/index.js +12 -0
- package/dist/tui/core/index.js.map +1 -0
- package/dist/tui/index.d.ts +15 -0
- package/dist/tui/index.d.ts.map +1 -0
- package/dist/tui/index.js +23 -0
- package/dist/tui/index.js.map +1 -0
- package/dist/tui/screens/BaseScreen.d.ts +62 -0
- package/dist/tui/screens/BaseScreen.d.ts.map +1 -0
- package/dist/tui/screens/BaseScreen.js +55 -0
- package/dist/tui/screens/BaseScreen.js.map +1 -0
- package/dist/tui/screens/CombatScreen.d.ts +43 -0
- package/dist/tui/screens/CombatScreen.d.ts.map +1 -0
- package/dist/tui/screens/CombatScreen.js +125 -0
- package/dist/tui/screens/CombatScreen.js.map +1 -0
- package/dist/tui/screens/DialogScreen.d.ts +53 -0
- package/dist/tui/screens/DialogScreen.d.ts.map +1 -0
- package/dist/tui/screens/DialogScreen.js +90 -0
- package/dist/tui/screens/DialogScreen.js.map +1 -0
- package/dist/tui/screens/DungeonScreen.d.ts +80 -0
- package/dist/tui/screens/DungeonScreen.d.ts.map +1 -0
- package/dist/tui/screens/DungeonScreen.js +317 -0
- package/dist/tui/screens/DungeonScreen.js.map +1 -0
- package/dist/tui/screens/ExploreScreen.d.ts +69 -0
- package/dist/tui/screens/ExploreScreen.d.ts.map +1 -0
- package/dist/tui/screens/ExploreScreen.js +224 -0
- package/dist/tui/screens/ExploreScreen.js.map +1 -0
- package/dist/tui/screens/SectionScreen.d.ts +84 -0
- package/dist/tui/screens/SectionScreen.d.ts.map +1 -0
- package/dist/tui/screens/SectionScreen.js +156 -0
- package/dist/tui/screens/SectionScreen.js.map +1 -0
- package/dist/tui/screens/TitleScreen.d.ts +40 -0
- package/dist/tui/screens/TitleScreen.d.ts.map +1 -0
- package/dist/tui/screens/TitleScreen.js +253 -0
- package/dist/tui/screens/TitleScreen.js.map +1 -0
- package/dist/tui/screens/TownScreen.d.ts +98 -0
- package/dist/tui/screens/TownScreen.d.ts.map +1 -0
- package/dist/tui/screens/TownScreen.js +370 -0
- package/dist/tui/screens/TownScreen.js.map +1 -0
- package/dist/tui/screens/TravelScreen.d.ts +67 -0
- package/dist/tui/screens/TravelScreen.d.ts.map +1 -0
- package/dist/tui/screens/TravelScreen.js +286 -0
- package/dist/tui/screens/TravelScreen.js.map +1 -0
- package/dist/tui/screens/index.d.ts +8 -0
- package/dist/tui/screens/index.d.ts.map +1 -0
- package/dist/tui/screens/index.js +17 -0
- package/dist/tui/screens/index.js.map +1 -0
- package/dist/tui/sections/ActionsSection.d.ts +71 -0
- package/dist/tui/sections/ActionsSection.d.ts.map +1 -0
- package/dist/tui/sections/ActionsSection.js +184 -0
- package/dist/tui/sections/ActionsSection.js.map +1 -0
- package/dist/tui/sections/DungeonSection.d.ts +65 -0
- package/dist/tui/sections/DungeonSection.d.ts.map +1 -0
- package/dist/tui/sections/DungeonSection.js +144 -0
- package/dist/tui/sections/DungeonSection.js.map +1 -0
- package/dist/tui/sections/EventsSection.d.ts +50 -0
- package/dist/tui/sections/EventsSection.d.ts.map +1 -0
- package/dist/tui/sections/EventsSection.js +134 -0
- package/dist/tui/sections/EventsSection.js.map +1 -0
- package/dist/tui/sections/MapSection.d.ts +66 -0
- package/dist/tui/sections/MapSection.d.ts.map +1 -0
- package/dist/tui/sections/MapSection.js +669 -0
- package/dist/tui/sections/MapSection.js.map +1 -0
- package/dist/tui/sections/StatusSection.d.ts +47 -0
- package/dist/tui/sections/StatusSection.d.ts.map +1 -0
- package/dist/tui/sections/StatusSection.js +133 -0
- package/dist/tui/sections/StatusSection.js.map +1 -0
- package/dist/tui/sections/TeamSection.d.ts +71 -0
- package/dist/tui/sections/TeamSection.d.ts.map +1 -0
- package/dist/tui/sections/TeamSection.js +224 -0
- package/dist/tui/sections/TeamSection.js.map +1 -0
- package/dist/tui/sections/TravelingSection.d.ts +51 -0
- package/dist/tui/sections/TravelingSection.d.ts.map +1 -0
- package/dist/tui/sections/TravelingSection.js +106 -0
- package/dist/tui/sections/TravelingSection.js.map +1 -0
- package/dist/tui/sections/index.d.ts +9 -0
- package/dist/tui/sections/index.d.ts.map +1 -0
- package/dist/tui/sections/index.js +12 -0
- package/dist/tui/sections/index.js.map +1 -0
- package/dist/ui/Terminal.d.ts +68 -0
- package/dist/ui/Terminal.d.ts.map +1 -0
- package/dist/ui/Terminal.js +297 -0
- package/dist/ui/Terminal.js.map +1 -0
- package/dist/ui/index.d.ts +5 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +8 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/utils/configMigration.d.ts +14 -0
- package/dist/utils/configMigration.d.ts.map +1 -0
- package/dist/utils/configMigration.js +92 -0
- package/dist/utils/configMigration.js.map +1 -0
- package/dist/utils/errorHandler.d.ts +86 -0
- package/dist/utils/errorHandler.d.ts.map +1 -0
- package/dist/utils/errorHandler.js +224 -0
- package/dist/utils/errorHandler.js.map +1 -0
- package/dist/utils/logger.d.ts +14 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +43 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/weapon/WeaponFactory.d.ts +32 -0
- package/dist/weapon/WeaponFactory.d.ts.map +1 -0
- package/dist/weapon/WeaponFactory.js +216 -0
- package/dist/weapon/WeaponFactory.js.map +1 -0
- package/dist/weapon/index.d.ts +5 -0
- package/dist/weapon/index.d.ts.map +1 -0
- package/dist/weapon/index.js +8 -0
- package/dist/weapon/index.js.map +1 -0
- 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
|