cyberia 3.1.3 → 3.2.9

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 (377) hide show
  1. package/.env.example +0 -2
  2. package/.github/workflows/engine-cyberia.cd.yml +10 -8
  3. package/.github/workflows/engine-cyberia.ci.yml +12 -29
  4. package/.github/workflows/ghpkg.ci.yml +4 -4
  5. package/.github/workflows/npmpkg.ci.yml +28 -11
  6. package/.github/workflows/publish.ci.yml +21 -2
  7. package/.github/workflows/pwa-microservices-template-page.cd.yml +4 -5
  8. package/.github/workflows/pwa-microservices-template-test.ci.yml +3 -3
  9. package/.github/workflows/release.cd.yml +14 -10
  10. package/CHANGELOG.md +783 -1
  11. package/CLI-HELP.md +95 -18
  12. package/Dockerfile +0 -2
  13. package/README.md +290 -220
  14. package/bin/build.js +24 -7
  15. package/bin/cyberia.js +2838 -252
  16. package/bin/deploy.js +747 -125
  17. package/bin/file.js +9 -0
  18. package/bin/index.js +2838 -252
  19. package/bin/vs.js +1 -1
  20. package/conf.js +99 -65
  21. package/deployment.yaml +18 -164
  22. package/hardhat/hardhat.config.js +13 -13
  23. package/hardhat/ignition/modules/ObjectLayerToken.js +1 -1
  24. package/hardhat/package-lock.json +2559 -5864
  25. package/hardhat/package.json +14 -23
  26. package/hardhat/scripts/deployObjectLayerToken.js +1 -1
  27. package/hardhat/test/ObjectLayerToken.js +4 -2
  28. package/hardhat/types/ethers-contracts/ObjectLayerToken.ts +690 -0
  29. package/hardhat/types/ethers-contracts/common.ts +92 -0
  30. package/hardhat/types/ethers-contracts/factories/ObjectLayerToken__factory.ts +1055 -0
  31. package/hardhat/types/ethers-contracts/factories/index.ts +4 -0
  32. package/hardhat/types/ethers-contracts/hardhat.d.ts +47 -0
  33. package/hardhat/types/ethers-contracts/index.ts +6 -0
  34. package/jsconfig.json +1 -1
  35. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +6 -5
  36. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +6 -5
  37. package/manifests/deployment/dd-cyberia-development/deployment.yaml +18 -164
  38. package/manifests/deployment/dd-cyberia-development/proxy.yaml +7 -79
  39. package/manifests/deployment/dd-default-development/deployment.yaml +2 -6
  40. package/manifests/deployment/dd-test-development/deployment.yaml +112 -28
  41. package/manifests/deployment/dd-test-development/proxy.yaml +46 -1
  42. package/manifests/deployment/playwright/deployment.yaml +1 -1
  43. package/nodemon.json +1 -1
  44. package/package.json +39 -24
  45. package/proxy.yaml +7 -79
  46. package/scripts/k3s-node-setup.sh +2 -2
  47. package/scripts/nat-iptables.sh +103 -18
  48. package/scripts/rhel-grpc-setup.sh +56 -0
  49. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +58 -14
  50. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +23 -14
  51. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +5 -0
  52. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +148 -20
  53. package/src/api/core/core.controller.js +10 -10
  54. package/src/api/core/core.service.js +10 -10
  55. package/src/api/crypto/crypto.controller.js +8 -8
  56. package/src/api/crypto/crypto.service.js +8 -8
  57. package/src/api/cyberia-action/cyberia-action.controller.js +74 -0
  58. package/src/api/cyberia-action/cyberia-action.model.js +87 -0
  59. package/src/api/cyberia-action/cyberia-action.router.js +27 -0
  60. package/src/api/cyberia-action/cyberia-action.service.js +42 -0
  61. package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +93 -0
  62. package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +36 -0
  63. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +29 -0
  64. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +51 -0
  65. package/src/api/cyberia-entity/cyberia-entity.controller.js +74 -0
  66. package/src/api/cyberia-entity/cyberia-entity.model.js +24 -0
  67. package/src/api/cyberia-entity/cyberia-entity.router.js +27 -0
  68. package/src/api/cyberia-entity/cyberia-entity.service.js +42 -0
  69. package/src/api/cyberia-instance/cyberia-fallback-world.js +178 -0
  70. package/src/api/cyberia-instance/cyberia-instance.controller.js +92 -0
  71. package/src/api/cyberia-instance/cyberia-instance.model.js +87 -0
  72. package/src/api/cyberia-instance/cyberia-instance.router.js +63 -0
  73. package/src/api/cyberia-instance/cyberia-instance.service.js +156 -0
  74. package/src/api/cyberia-instance/cyberia-portal-connector.js +260 -0
  75. package/src/api/cyberia-instance/cyberia-world-generator.js +505 -0
  76. package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +74 -0
  77. package/src/api/cyberia-instance-conf/cyberia-instance-conf.defaults.js +574 -0
  78. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +231 -0
  79. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +27 -0
  80. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +46 -0
  81. package/src/api/cyberia-map/cyberia-map.controller.js +79 -0
  82. package/src/api/cyberia-map/cyberia-map.model.js +30 -0
  83. package/src/api/cyberia-map/cyberia-map.router.js +40 -0
  84. package/src/api/cyberia-map/cyberia-map.service.js +74 -0
  85. package/src/api/cyberia-quest/cyberia-quest.controller.js +74 -0
  86. package/src/api/cyberia-quest/cyberia-quest.model.js +67 -0
  87. package/src/api/cyberia-quest/cyberia-quest.router.js +27 -0
  88. package/src/api/cyberia-quest/cyberia-quest.service.js +42 -0
  89. package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +74 -0
  90. package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +49 -0
  91. package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +27 -0
  92. package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +42 -0
  93. package/src/api/default/default.controller.js +10 -10
  94. package/src/api/default/default.service.js +10 -10
  95. package/src/api/document/document.controller.js +12 -12
  96. package/src/api/document/document.model.js +10 -16
  97. package/src/api/file/file.controller.js +8 -8
  98. package/src/api/file/file.model.js +10 -10
  99. package/src/api/file/file.ref.json +18 -0
  100. package/src/api/file/file.service.js +36 -36
  101. package/src/api/instance/instance.controller.js +10 -10
  102. package/src/api/instance/instance.model.js +4 -10
  103. package/src/api/instance/instance.service.js +10 -10
  104. package/src/api/ipfs/ipfs.controller.js +15 -36
  105. package/src/api/ipfs/ipfs.model.js +47 -47
  106. package/src/api/ipfs/ipfs.router.js +8 -13
  107. package/src/api/ipfs/ipfs.service.js +67 -129
  108. package/src/api/object-layer/object-layer.controller.js +12 -12
  109. package/src/api/object-layer/object-layer.model.js +4 -17
  110. package/src/api/object-layer/object-layer.router.js +30 -0
  111. package/src/api/object-layer/object-layer.service.js +126 -43
  112. package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +10 -10
  113. package/src/api/object-layer-render-frames/object-layer-render-frames.model.js +6 -16
  114. package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +18 -14
  115. package/src/api/test/test.controller.js +8 -8
  116. package/src/api/test/test.service.js +8 -8
  117. package/src/api/user/guest.service.js +99 -0
  118. package/src/api/user/user.controller.js +6 -6
  119. package/src/api/user/user.model.js +8 -13
  120. package/src/api/user/user.service.js +11 -27
  121. package/src/cli/cluster.js +68 -21
  122. package/src/cli/db.js +753 -825
  123. package/src/cli/deploy.js +215 -125
  124. package/src/cli/env.js +29 -0
  125. package/src/cli/fs.js +82 -8
  126. package/src/cli/image.js +43 -1
  127. package/src/cli/index.js +74 -3
  128. package/src/cli/kubectl.js +211 -0
  129. package/src/cli/release.js +340 -0
  130. package/src/cli/repository.js +475 -74
  131. package/src/cli/run.js +582 -43
  132. package/src/cli/secrets.js +73 -0
  133. package/src/cli/ssh.js +1 -1
  134. package/src/cli/static.js +43 -115
  135. package/src/cli/test.js +3 -3
  136. package/src/client/Cryptokoyn.index.js +18 -22
  137. package/src/client/CyberiaPortal.index.js +19 -24
  138. package/src/client/Default.index.js +21 -34
  139. package/src/client/Itemledger.index.js +20 -27
  140. package/src/client/Underpost.index.js +19 -24
  141. package/src/client/components/core/404.js +4 -4
  142. package/src/client/components/core/500.js +4 -4
  143. package/src/client/components/core/Account.js +73 -60
  144. package/src/client/components/core/AgGrid.js +23 -33
  145. package/src/client/components/core/Alert.js +12 -13
  146. package/src/client/components/core/AppStore.js +69 -0
  147. package/src/client/components/core/Auth.js +35 -37
  148. package/src/client/components/core/Badge.js +7 -13
  149. package/src/client/components/core/BtnIcon.js +15 -17
  150. package/src/client/components/core/CalendarCore.js +43 -64
  151. package/src/client/components/core/Chat.js +13 -15
  152. package/src/client/components/core/ClientEvents.js +87 -0
  153. package/src/client/components/core/ColorPaletteElement.js +309 -0
  154. package/src/client/components/core/Content.js +17 -14
  155. package/src/client/components/core/Css.js +15 -71
  156. package/src/client/components/core/CssCore.js +12 -16
  157. package/src/client/components/core/D3Chart.js +4 -4
  158. package/src/client/components/core/Docs.js +64 -91
  159. package/src/client/components/core/DropDown.js +194 -96
  160. package/src/client/components/core/EventBus.js +92 -0
  161. package/src/client/components/core/EventsUI.js +14 -17
  162. package/src/client/components/core/FileExplorer.js +96 -228
  163. package/src/client/components/core/FullScreen.js +47 -75
  164. package/src/client/components/core/Input.js +24 -69
  165. package/src/client/components/core/Keyboard.js +26 -19
  166. package/src/client/components/core/KeyboardAvoidance.js +145 -0
  167. package/src/client/components/core/LoadingAnimation.js +25 -31
  168. package/src/client/components/core/LogIn.js +43 -43
  169. package/src/client/components/core/LogOut.js +25 -16
  170. package/src/client/components/core/Modal.js +462 -179
  171. package/src/client/components/core/NotificationManager.js +14 -18
  172. package/src/client/components/core/Panel.js +54 -51
  173. package/src/client/components/core/PanelForm.js +44 -144
  174. package/src/client/components/core/Polyhedron.js +110 -214
  175. package/src/client/components/core/PublicProfile.js +39 -32
  176. package/src/client/components/core/Recover.js +48 -44
  177. package/src/client/components/core/Responsive.js +88 -32
  178. package/src/client/components/core/RichText.js +9 -18
  179. package/src/client/components/core/Router.js +24 -3
  180. package/src/client/components/core/SearchBox.js +37 -37
  181. package/src/client/components/core/SignUp.js +39 -30
  182. package/src/client/components/core/SocketIo.js +112 -30
  183. package/src/client/components/core/SocketIoHandler.js +75 -0
  184. package/src/client/components/core/Stream.js +143 -95
  185. package/src/client/components/core/ToggleSwitch.js +8 -20
  186. package/src/client/components/core/ToolTip.js +5 -17
  187. package/src/client/components/core/Translate.js +56 -59
  188. package/src/client/components/core/Validator.js +26 -16
  189. package/src/client/components/core/Wallet.js +15 -26
  190. package/src/client/components/core/Webhook.js +40 -7
  191. package/src/client/components/core/Worker.js +163 -27
  192. package/src/client/components/core/windowGetDimensions.js +7 -7
  193. package/src/client/components/cryptokoyn/{MenuCryptokoyn.js → AppShellCryptokoyn.js} +59 -59
  194. package/src/client/components/cryptokoyn/AppStoreCryptokoyn.js +5 -0
  195. package/src/client/components/cryptokoyn/CssCryptokoyn.js +15 -15
  196. package/src/client/components/cryptokoyn/LogInCryptokoyn.js +9 -7
  197. package/src/client/components/cryptokoyn/LogOutCryptokoyn.js +8 -6
  198. package/src/client/components/cryptokoyn/RouterCryptokoyn.js +37 -0
  199. package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +4 -4
  200. package/src/client/components/cryptokoyn/SignUpCryptokoyn.js +6 -4
  201. package/src/client/components/cryptokoyn/SocketIoCryptokoyn.js +3 -51
  202. package/src/client/components/cyberia/InstanceEngineCyberia.js +781 -0
  203. package/src/client/components/cyberia/MapEngineCyberia.js +1836 -2
  204. package/src/client/components/cyberia/ObjectLayerEngine.js +19 -0
  205. package/src/client/components/cyberia/ObjectLayerEngineModal.js +1220 -99
  206. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +252 -316
  207. package/src/client/components/cyberia-portal/{MenuCyberiaPortal.js → AppShellCyberiaPortal.js} +136 -103
  208. package/src/client/components/cyberia-portal/AppStoreCyberiaPortal.js +5 -0
  209. package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +462 -32
  210. package/src/client/components/cyberia-portal/CssCyberiaPortal.js +15 -15
  211. package/src/client/components/cyberia-portal/LogInCyberiaPortal.js +9 -7
  212. package/src/client/components/cyberia-portal/LogOutCyberiaPortal.js +8 -6
  213. package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +4 -4
  214. package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +60 -0
  215. package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +4 -4
  216. package/src/client/components/cyberia-portal/SignUpCyberiaPortal.js +6 -4
  217. package/src/client/components/cyberia-portal/SocketIoCyberiaPortal.js +3 -49
  218. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +8 -4
  219. package/src/client/components/default/{MenuDefault.js → AppShellDefault.js} +91 -91
  220. package/src/client/components/default/AppStoreDefault.js +5 -0
  221. package/src/client/components/default/CssDefault.js +12 -12
  222. package/src/client/components/default/LogInDefault.js +9 -7
  223. package/src/client/components/default/LogOutDefault.js +8 -6
  224. package/src/client/components/default/RouterDefault.js +47 -0
  225. package/src/client/components/default/SettingsDefault.js +4 -4
  226. package/src/client/components/default/SignUpDefault.js +6 -4
  227. package/src/client/components/default/SocketIoDefault.js +3 -51
  228. package/src/client/components/default/TranslateDefault.js +3 -3
  229. package/src/client/components/itemledger/{MenuItemledger.js → AppShellItemledger.js} +59 -59
  230. package/src/client/components/itemledger/AppStoreItemledger.js +5 -0
  231. package/src/client/components/itemledger/CssItemledger.js +15 -15
  232. package/src/client/components/itemledger/LogInItemledger.js +9 -7
  233. package/src/client/components/itemledger/LogOutItemledger.js +8 -6
  234. package/src/client/components/itemledger/RouterItemledger.js +38 -0
  235. package/src/client/components/itemledger/SettingsItemledger.js +4 -4
  236. package/src/client/components/itemledger/SignUpItemledger.js +6 -4
  237. package/src/client/components/itemledger/SocketIoItemledger.js +3 -51
  238. package/src/client/components/itemledger/TranslateItemledger.js +3 -3
  239. package/src/client/components/underpost/{MenuUnderpost.js → AppShellUnderpost.js} +92 -92
  240. package/src/client/components/underpost/AppStoreUnderpost.js +5 -0
  241. package/src/client/components/underpost/CssUnderpost.js +14 -14
  242. package/src/client/components/underpost/CyberpunkBloggerUnderpost.js +4 -4
  243. package/src/client/components/underpost/DocumentSearchProvider.js +1 -1
  244. package/src/client/components/underpost/LabGalleryUnderpost.js +12 -15
  245. package/src/client/components/underpost/LogInUnderpost.js +9 -7
  246. package/src/client/components/underpost/LogOutUnderpost.js +8 -6
  247. package/src/client/components/underpost/RouterUnderpost.js +45 -0
  248. package/src/client/components/underpost/SettingsUnderpost.js +4 -4
  249. package/src/client/components/underpost/SignUpUnderpost.js +6 -4
  250. package/src/client/components/underpost/SocketIoUnderpost.js +3 -51
  251. package/src/client/components/underpost/TranslateUnderpost.js +4 -4
  252. package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +235 -0
  253. package/src/client/public/cyberia-docs/ARCHITECTURE.md +443 -0
  254. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +417 -0
  255. package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +313 -0
  256. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +260 -0
  257. package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +241 -0
  258. package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +300 -0
  259. package/src/client/public/cyberia-docs/OFF-CHAIN-ECONOMY.md +279 -0
  260. package/src/client/public/cyberia-docs/QUEST-SYSTEM.md +206 -0
  261. package/src/client/public/cyberia-docs/ROADMAP.md +240 -0
  262. package/src/client/public/cyberia-docs/WHITE-PAPER.md +732 -0
  263. package/src/client/services/atlas-sprite-sheet/atlas-sprite-sheet.service.js +14 -20
  264. package/src/client/services/core/core.service.js +35 -55
  265. package/src/client/services/crypto/crypto.service.js +8 -13
  266. package/src/client/services/cyberia-action/cyberia-action.service.js +99 -0
  267. package/src/client/services/cyberia-dialogue/cyberia-dialogue.service.js +99 -0
  268. package/src/client/services/cyberia-entity/cyberia-entity.management.js +57 -0
  269. package/src/client/services/cyberia-entity/cyberia-entity.service.js +99 -0
  270. package/src/client/services/cyberia-instance/cyberia-instance.management.js +194 -0
  271. package/src/client/services/cyberia-instance/cyberia-instance.service.js +116 -0
  272. package/src/client/services/cyberia-instance-conf/cyberia-instance-conf.service.js +99 -0
  273. package/src/client/services/cyberia-map/cyberia-map.management.js +193 -0
  274. package/src/client/services/cyberia-map/cyberia-map.service.js +120 -0
  275. package/src/client/services/cyberia-quest/cyberia-quest.service.js +99 -0
  276. package/src/client/services/cyberia-quest-progress/cyberia-quest-progress.service.js +99 -0
  277. package/src/client/services/default/default.management.js +159 -267
  278. package/src/client/services/default/default.service.js +10 -16
  279. package/src/client/services/document/document.service.js +14 -19
  280. package/src/client/services/file/file.service.js +8 -13
  281. package/src/client/services/instance/instance.management.js +6 -6
  282. package/src/client/services/instance/instance.service.js +10 -15
  283. package/src/client/services/ipfs/ipfs.service.js +14 -40
  284. package/src/client/services/object-layer/object-layer.management.js +14 -14
  285. package/src/client/services/object-layer/object-layer.service.js +39 -24
  286. package/src/client/services/object-layer-render-frames/object-layer-render-frames.service.js +10 -16
  287. package/src/client/services/test/test.service.js +8 -13
  288. package/src/client/services/user/guest.service.js +86 -0
  289. package/src/client/services/user/user.management.js +6 -6
  290. package/src/client/services/user/user.service.js +14 -20
  291. package/src/client/ssr/body/404.js +3 -3
  292. package/src/client/ssr/body/500.js +3 -3
  293. package/src/client/ssr/body/CacheControl.js +5 -2
  294. package/src/client/ssr/body/DefaultSplashScreen.js +19 -12
  295. package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +13 -6
  296. package/src/client/ssr/head/PwaItemledger.js +197 -60
  297. package/src/client/ssr/mailer/DefaultRecoverEmail.js +19 -20
  298. package/src/client/ssr/mailer/DefaultVerifyEmail.js +15 -16
  299. package/src/client/ssr/offline/Maintenance.js +12 -11
  300. package/src/client/ssr/offline/NoNetworkConnection.js +3 -3
  301. package/src/client/ssr/pages/CyberiaServerMetrics.js +1 -1
  302. package/src/client/ssr/pages/Test.js +2 -2
  303. package/src/client/sw/core.sw.js +212 -0
  304. package/src/grpc/cyberia/grpc-server.js +642 -0
  305. package/src/index.js +24 -1
  306. package/src/runtime/cyberia-client/Dockerfile +80 -0
  307. package/src/runtime/cyberia-server/Dockerfile +37 -0
  308. package/src/runtime/express/Dockerfile +5 -1
  309. package/src/runtime/express/Express.js +18 -1
  310. package/src/runtime/lampp/Dockerfile +17 -5
  311. package/src/runtime/lampp/Lampp.js +27 -4
  312. package/src/runtime/wp/Dockerfile +62 -0
  313. package/src/runtime/wp/Wp.js +639 -0
  314. package/src/server/atlas-sprite-sheet-generator.js +4 -2
  315. package/src/server/auth.js +24 -1
  316. package/src/server/backup.js +37 -9
  317. package/src/server/client-build-docs.js +52 -46
  318. package/src/server/client-build.js +356 -82
  319. package/src/server/client-formatted.js +140 -57
  320. package/src/server/conf.js +29 -13
  321. package/src/server/cron.js +25 -23
  322. package/src/server/data-query.js +32 -20
  323. package/src/server/dns.js +24 -1
  324. package/src/server/ipfs-client.js +253 -89
  325. package/src/server/object-layer.js +150 -114
  326. package/src/server/peer.js +8 -0
  327. package/src/server/process.js +13 -27
  328. package/src/server/runtime.js +25 -1
  329. package/src/server/semantic-layer-generator-floor.js +319 -0
  330. package/src/server/semantic-layer-generator-resource.js +259 -0
  331. package/src/server/semantic-layer-generator-skin.js +1164 -0
  332. package/src/server/semantic-layer-generator.js +211 -542
  333. package/src/server/shape-generator.js +108 -0
  334. package/src/server/start.js +19 -5
  335. package/src/server/valkey.js +141 -235
  336. package/src/ws/IoInterface.js +1 -10
  337. package/src/ws/IoServer.js +14 -33
  338. package/src/ws/core/channels/core.ws.chat.js +65 -20
  339. package/src/ws/core/channels/core.ws.mailer.js +113 -32
  340. package/src/ws/core/channels/core.ws.stream.js +90 -31
  341. package/src/ws/core/core.ws.connection.js +12 -33
  342. package/src/ws/core/core.ws.emit.js +10 -26
  343. package/src/ws/core/core.ws.server.js +25 -58
  344. package/src/ws/default/channels/default.ws.main.js +53 -12
  345. package/src/ws/default/default.ws.connection.js +26 -13
  346. package/src/ws/default/default.ws.server.js +30 -12
  347. package/tsconfig.docs.json +15 -0
  348. package/typedoc.dd-cyberia.json +29 -0
  349. package/typedoc.json +29 -0
  350. package/WHITE-PAPER.md +0 -1540
  351. package/hardhat/README.md +0 -531
  352. package/hardhat/WHITE-PAPER.md +0 -1540
  353. package/jsdoc.dd-cyberia.json +0 -59
  354. package/jsdoc.json +0 -59
  355. package/src/api/object-layer/README.md +0 -347
  356. package/src/client/components/core/ColorPalette.js +0 -5267
  357. package/src/client/components/core/JoyStick.js +0 -80
  358. package/src/client/components/cryptokoyn/CommonCryptokoyn.js +0 -29
  359. package/src/client/components/cryptokoyn/ElementsCryptokoyn.js +0 -38
  360. package/src/client/components/cryptokoyn/RoutesCryptokoyn.js +0 -39
  361. package/src/client/components/cyberia-portal/ElementsCyberiaPortal.js +0 -38
  362. package/src/client/components/cyberia-portal/RoutesCyberiaPortal.js +0 -58
  363. package/src/client/components/cyberia-portal/ServerCyberiaPortal.js +0 -136
  364. package/src/client/components/default/ElementsDefault.js +0 -38
  365. package/src/client/components/default/RoutesDefault.js +0 -49
  366. package/src/client/components/itemledger/CommonItemledger.js +0 -29
  367. package/src/client/components/itemledger/ElementsItemledger.js +0 -38
  368. package/src/client/components/itemledger/RoutesItemledger.js +0 -40
  369. package/src/client/components/underpost/CommonUnderpost.js +0 -29
  370. package/src/client/components/underpost/ElementsUnderpost.js +0 -38
  371. package/src/client/components/underpost/RoutesUnderpost.js +0 -47
  372. package/src/client/sw/default.sw.js +0 -127
  373. package/src/client/sw/template.sw.js +0 -84
  374. package/src/ws/core/management/core.ws.chat.js +0 -8
  375. package/src/ws/core/management/core.ws.mailer.js +0 -16
  376. package/src/ws/core/management/core.ws.stream.js +0 -8
  377. package/src/ws/default/management/default.ws.main.js +0 -8
@@ -1,6 +1,1840 @@
1
+ import { BtnIcon } from '../core/BtnIcon.js';
2
+ import { Input, InputFile, getFileFromBlobEndpoint } from '../core/Input.js';
3
+ import { htmls, s } from '../core/VanillaJs.js';
4
+ import { NotificationManager } from '../core/NotificationManager.js';
5
+ import { Translate } from '../core/Translate.js';
6
+ import { darkTheme, dynamicCol, ThemeEvents } from '../core/Css.js';
7
+ import { DropDown } from '../core/DropDown.js';
8
+ import { ToggleSwitch } from '../core/ToggleSwitch.js';
9
+ import { CyberiaMapManagement } from '../../services/cyberia-map/cyberia-map.management.js';
10
+ import { CyberiaMapService } from '../../services/cyberia-map/cyberia-map.service.js';
11
+ import { FileService } from '../../services/file/file.service.js';
12
+ import { DefaultManagement } from '../../services/default/default.management.js';
13
+ import { getApiBaseUrl } from '../../services/core/core.service.js';
14
+ import { ObjectLayerService } from '../../services/object-layer/object-layer.service.js';
15
+ import { getProxyPath } from '../core/Router.js';
16
+ import { ENTITY_TYPES, getDefaultCyberiaItemById } from '../cyberia-portal/CommonCyberiaPortal.js';
17
+ import '../core/ColorPaletteElement.js';
18
+
19
+ const DEFAULT_ENTITY_TYPE = ENTITY_TYPES.floor;
20
+ const dropdownValueKey = (value = '') => String(value).trim().replaceAll(' ', '-');
21
+ const createDropdownOption = (value, onClick = () => {}, display = value, data = value) => ({
22
+ value,
23
+ display,
24
+ data,
25
+ onClick,
26
+ });
27
+
1
28
  class MapEngineCyberia {
2
- static async render() {
3
- return html`<div>Map Engine Cyberia Component</div>`;
29
+ static entities = [];
30
+ static currentMapId = null;
31
+ static currentThumbnailId = null;
32
+ static thumbnailDirty = false;
33
+ static loadMap = null;
34
+ static showGridBorders = true;
35
+ static addOnClick = true;
36
+ static showObjectLayers = false;
37
+ static enableRandomFactors = false;
38
+ static captureObjLayerThumbnail = true;
39
+ static imageCache = {};
40
+ static entityUndoStack = [];
41
+ static entityRedoStack = [];
42
+ static maxEntityHistory = 200;
43
+ static entityHistorySync = null;
44
+ static entityHistoryHotkeyHandler = null;
45
+ static entityTypeDropdownId = 'map-engine-entity-type';
46
+ static objectLayerDropdownId = 'map-engine-obj-layer-dropdown';
47
+ static objectLayerDropdownHostId = 'map-engine-obj-layer-dropdown-host';
48
+ static renameSourceObjectLayerInputId = 'map-engine-rename-source-object-layer-item-id';
49
+ static renameTargetObjectLayerInputId = 'map-engine-rename-target-object-layer-item-id';
50
+
51
+ static getSelectedDropdownValue(dropdownId, fallback = '') {
52
+ return DropDown.Tokens[dropdownId]?.value || s(`.${dropdownId}`)?.value || fallback;
53
+ }
54
+
55
+ static getSelectedEntityType() {
56
+ return MapEngineCyberia.getSelectedDropdownValue(MapEngineCyberia.entityTypeDropdownId, DEFAULT_ENTITY_TYPE);
57
+ }
58
+
59
+ static getSelectedObjectLayerItemIds() {
60
+ return DropDown.Tokens[MapEngineCyberia.objectLayerDropdownId]?.value
61
+ ? [...DropDown.Tokens[MapEngineCyberia.objectLayerDropdownId].value]
62
+ : [];
63
+ }
64
+
65
+ static getRenameObjectLayerItemIds() {
66
+ return {
67
+ source: s(`.${MapEngineCyberia.renameSourceObjectLayerInputId}`)?.value?.trim() || '',
68
+ target: s(`.${MapEngineCyberia.renameTargetObjectLayerInputId}`)?.value?.trim() || '',
69
+ };
70
+ }
71
+
72
+ static setDropdownValue(dropdownId, value) {
73
+ if (!value || !DropDown.Tokens[dropdownId]) return;
74
+ DropDown.Tokens[dropdownId].value = value;
75
+ if (s(`.${dropdownId}`)) s(`.${dropdownId}`).value = value;
76
+ htmls(`.dropdown-current-${dropdownId}`, value);
77
+ }
78
+
79
+ static getEntityTypeDropdownOptions() {
80
+ return Object.values(ENTITY_TYPES).map((entityType) => createDropdownOption(entityType));
81
+ }
82
+
83
+ static syncObjectLayerDropdownSelection(itemIds = []) {
84
+ const dropdownId = MapEngineCyberia.objectLayerDropdownId;
85
+ if (!DropDown.Tokens[dropdownId]) return;
86
+
87
+ DropDown.Tokens[dropdownId].oncheckvalues = {};
88
+ for (const itemId of itemIds) {
89
+ const key = dropdownValueKey(itemId);
90
+ DropDown.Tokens[dropdownId].oncheckvalues[key] = {
91
+ data: itemId,
92
+ display: itemId,
93
+ value: itemId,
94
+ };
95
+ }
96
+ DropDown.Tokens[dropdownId].value = [...itemIds];
97
+ if (s(`.${dropdownId}`)) s(`.${dropdownId}`).value = [...itemIds];
98
+ DropDown.Tokens[dropdownId]._renderSelectedBadges?.();
99
+ }
100
+
101
+ static async buildObjectLayerDropdown() {
102
+ return await DropDown.instance({
103
+ id: MapEngineCyberia.objectLayerDropdownId,
104
+ label: html`Object Layers`,
105
+ data: [],
106
+ type: 'checkbox',
107
+ containerClass: 'inl',
108
+ excludeSelected: true,
109
+ serviceProvider: async (q) => {
110
+ const result = await ObjectLayerService.searchItemIds({ q });
111
+ if (result.status === 'success' && result.data?.itemIds) {
112
+ return result.data.itemIds.map((itemId) => createDropdownOption(itemId));
113
+ }
114
+ return [];
115
+ },
116
+ });
117
+ }
118
+
119
+ static async renderObjectLayerDropdown({ selectedItemIds = [] } = {}) {
120
+ const hostId = MapEngineCyberia.objectLayerDropdownHostId;
121
+ if (!s(`.${hostId}`)) return;
122
+
123
+ htmls(`.${hostId}`, await MapEngineCyberia.buildObjectLayerDropdown());
124
+ MapEngineCyberia.syncObjectLayerDropdownSelection(selectedItemIds);
125
+ }
126
+
127
+ static renameFilteredObjectLayerItemId() {
128
+ const { source, target } = MapEngineCyberia.getRenameObjectLayerItemIds();
129
+ if (!source || !target) {
130
+ NotificationManager.Push({
131
+ html: 'Source and target ItemId are required.',
132
+ status: 'error',
133
+ });
134
+ return false;
135
+ }
136
+
137
+ if (source === target) {
138
+ NotificationManager.Push({
139
+ html: 'Source and target ItemId must be different.',
140
+ status: 'error',
141
+ });
142
+ return false;
143
+ }
144
+
145
+ const filtered = MapEngineCyberia.getFilteredEntities();
146
+ if (!filtered.length) {
147
+ NotificationManager.Push({
148
+ html: 'No filtered entities available for ItemId rename.',
149
+ status: 'error',
150
+ });
151
+ return false;
152
+ }
153
+
154
+ let matchedEntities = 0;
155
+ let renamedReferences = 0;
156
+ const changed = MapEngineCyberia.commitEntityMutation(() => {
157
+ for (const { i } of filtered) {
158
+ const entity = MapEngineCyberia.entities[i];
159
+ if (!Array.isArray(entity?.objectLayerItemIds) || entity.objectLayerItemIds.length === 0) continue;
160
+
161
+ let entityChanged = false;
162
+ entity.objectLayerItemIds = entity.objectLayerItemIds.map((itemId) => {
163
+ if (itemId !== source) return itemId;
164
+ entityChanged = true;
165
+ renamedReferences += 1;
166
+ return target;
167
+ });
168
+
169
+ if (entityChanged) matchedEntities += 1;
170
+ }
171
+ });
172
+
173
+ if (!changed) {
174
+ NotificationManager.Push({
175
+ html: `No exact ItemId matches for "${source}" were found in the current filtered entities.`,
176
+ status: 'error',
177
+ });
178
+ return false;
179
+ }
180
+
181
+ NotificationManager.Push({
182
+ html: `Renamed ${renamedReferences} object layer reference${renamedReferences === 1 ? '' : 's'} across ${matchedEntities} filtered entit${matchedEntities === 1 ? 'y' : 'ies'}.`,
183
+ status: 'success',
184
+ });
185
+ return true;
186
+ }
187
+
188
+ static cloneEntity(entity) {
189
+ return {
190
+ ...entity,
191
+ objectLayerItemIds: Array.isArray(entity?.objectLayerItemIds) ? [...entity.objectLayerItemIds] : [],
192
+ };
193
+ }
194
+
195
+ static cloneEntities(entities = MapEngineCyberia.entities) {
196
+ return (entities || []).map((entity) => MapEngineCyberia.cloneEntity(entity));
197
+ }
198
+
199
+ static entitySnapshotsEqual(left, right) {
200
+ if (left === right) return true;
201
+ if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) return false;
202
+
203
+ for (let i = 0; i < left.length; i++) {
204
+ const a = left[i] || {};
205
+ const b = right[i] || {};
206
+ const aKeys = Object.keys(a);
207
+ const bKeys = Object.keys(b);
208
+ if (aKeys.length !== bKeys.length) return false;
209
+
210
+ for (const key of aKeys) {
211
+ const aValue = a[key];
212
+ const bValue = b[key];
213
+
214
+ if (Array.isArray(aValue) || Array.isArray(bValue)) {
215
+ if (!Array.isArray(aValue) || !Array.isArray(bValue) || aValue.length !== bValue.length) return false;
216
+ for (let j = 0; j < aValue.length; j++) {
217
+ if (aValue[j] !== bValue[j]) return false;
218
+ }
219
+ continue;
220
+ }
221
+
222
+ if (aValue !== bValue) return false;
223
+ }
224
+ }
225
+
226
+ return true;
227
+ }
228
+
229
+ static pushEntityHistory(stack, snapshot) {
230
+ stack.push(snapshot);
231
+ if (stack.length > MapEngineCyberia.maxEntityHistory) stack.shift();
232
+ }
233
+
234
+ static setEntityHistorySync(callback) {
235
+ MapEngineCyberia.entityHistorySync = callback;
236
+ }
237
+
238
+ static preloadEntityObjectLayers(onLoad = null) {
239
+ for (const entity of MapEngineCyberia.entities) {
240
+ for (const itemId of entity.objectLayerItemIds || []) {
241
+ MapEngineCyberia.loadObjectLayerImage(itemId, onLoad);
242
+ }
243
+ }
244
+ }
245
+
246
+ static refreshEntityEditor() {
247
+ const callback = MapEngineCyberia.entityHistorySync;
248
+ if (typeof callback !== 'function') return;
249
+ MapEngineCyberia.preloadEntityObjectLayers(callback);
250
+ callback();
251
+ }
252
+
253
+ static clearEntityHistory() {
254
+ MapEngineCyberia.entityUndoStack.length = 0;
255
+ MapEngineCyberia.entityRedoStack.length = 0;
256
+ }
257
+
258
+ static setEntities(entities, { clearHistory = false } = {}) {
259
+ MapEngineCyberia.entities = MapEngineCyberia.cloneEntities(entities);
260
+ if (clearHistory) MapEngineCyberia.clearEntityHistory();
261
+ MapEngineCyberia.refreshEntityEditor();
262
+ }
263
+
264
+ static commitEntityMutation(mutate) {
265
+ if (typeof mutate !== 'function') return false;
266
+
267
+ const before = MapEngineCyberia.cloneEntities();
268
+ mutate();
269
+ const after = MapEngineCyberia.cloneEntities();
270
+
271
+ if (MapEngineCyberia.entitySnapshotsEqual(before, after)) return false;
272
+
273
+ MapEngineCyberia.pushEntityHistory(MapEngineCyberia.entityUndoStack, before);
274
+ MapEngineCyberia.entityRedoStack.length = 0;
275
+ MapEngineCyberia.refreshEntityEditor();
276
+ return true;
277
+ }
278
+
279
+ static undoEntityMutation() {
280
+ if (!MapEngineCyberia.entityUndoStack.length) return false;
281
+
282
+ const previous = MapEngineCyberia.entityUndoStack.pop();
283
+ MapEngineCyberia.pushEntityHistory(MapEngineCyberia.entityRedoStack, MapEngineCyberia.cloneEntities());
284
+ MapEngineCyberia.setEntities(previous);
285
+ return true;
286
+ }
287
+
288
+ static redoEntityMutation() {
289
+ if (!MapEngineCyberia.entityRedoStack.length) return false;
290
+
291
+ const next = MapEngineCyberia.entityRedoStack.pop();
292
+ MapEngineCyberia.pushEntityHistory(MapEngineCyberia.entityUndoStack, MapEngineCyberia.cloneEntities());
293
+ MapEngineCyberia.setEntities(next);
294
+ return true;
295
+ }
296
+
297
+ static isEditableTarget(target) {
298
+ if (!target) return false;
299
+ const tagName = target.tagName?.toUpperCase();
300
+ return target.isContentEditable || tagName === 'INPUT' || tagName === 'TEXTAREA' || tagName === 'SELECT';
301
+ }
302
+
303
+ static isEntityHistoryActive() {
304
+ const container = s('.map-engine-container');
305
+ return !!container && container.isConnected && container.getClientRects().length > 0;
306
+ }
307
+
308
+ static bindEntityHistoryHotkeys() {
309
+ if (MapEngineCyberia.entityHistoryHotkeyHandler) {
310
+ window.removeEventListener('keydown', MapEngineCyberia.entityHistoryHotkeyHandler);
311
+ }
312
+
313
+ MapEngineCyberia.entityHistoryHotkeyHandler = (event) => {
314
+ if (
315
+ event.defaultPrevented ||
316
+ event.altKey ||
317
+ !MapEngineCyberia.isEntityHistoryActive() ||
318
+ MapEngineCyberia.isEditableTarget(event.target)
319
+ ) {
320
+ return;
321
+ }
322
+
323
+ const isModifierPressed = event.ctrlKey || event.metaKey;
324
+ if (!isModifierPressed) return;
325
+
326
+ if (event.key === 'z' || event.key === 'Z') {
327
+ const handled = event.shiftKey ? MapEngineCyberia.redoEntityMutation() : MapEngineCyberia.undoEntityMutation();
328
+ if (handled) event.preventDefault();
329
+ return;
330
+ }
331
+
332
+ if (event.key === 'y' || event.key === 'Y') {
333
+ if (MapEngineCyberia.redoEntityMutation()) event.preventDefault();
334
+ }
335
+ };
336
+
337
+ window.addEventListener('keydown', MapEngineCyberia.entityHistoryHotkeyHandler);
338
+ }
339
+
340
+ static getEntityFilters() {
341
+ return {
342
+ filterType: s('.map-engine-filter-entity-type')?.value?.trim().toLowerCase() || '',
343
+ filterX: s('.map-engine-filter-init-x')?.value?.trim() || '',
344
+ filterY: s('.map-engine-filter-init-y')?.value?.trim() || '',
345
+ };
346
+ }
347
+
348
+ static getFilteredEntities() {
349
+ const { filterType, filterX, filterY } = MapEngineCyberia.getEntityFilters();
350
+
351
+ return MapEngineCyberia.entities.reduce((acc, entity, i) => {
352
+ if (filterType && !(entity.entityType || '').toLowerCase().includes(filterType)) return acc;
353
+ if (filterX !== '' && String(entity.initCellX) !== filterX) return acc;
354
+ if (filterY !== '' && String(entity.initCellY) !== filterY) return acc;
355
+ acc.push({ entity, i });
356
+ return acc;
357
+ }, []);
358
+ }
359
+
360
+ static loadObjectLayerImage(itemId, onLoad) {
361
+ if (MapEngineCyberia.imageCache[itemId]) return;
362
+ MapEngineCyberia.imageCache[itemId] = { img: null, loaded: false, error: false };
363
+
364
+ const loadImage = (type, id) => {
365
+ const img = new Image();
366
+ img.onload = () => {
367
+ MapEngineCyberia.imageCache[itemId].img = img;
368
+ MapEngineCyberia.imageCache[itemId].loaded = true;
369
+ if (onLoad) onLoad();
370
+ };
371
+ img.onerror = () => {
372
+ MapEngineCyberia.imageCache[itemId].error = true;
373
+ };
374
+ img.src = `${getProxyPath()}assets/${type}/${id}/08/0.png`;
375
+ };
376
+
377
+ const sharedItem = getDefaultCyberiaItemById(itemId)?.item;
378
+ if (sharedItem?.type && sharedItem?.id) {
379
+ loadImage(sharedItem.type, sharedItem.id);
380
+ return;
381
+ }
382
+
383
+ ObjectLayerService.get({
384
+ limit: 1,
385
+ filterModel: { 'data.item.id': { filterType: 'text', type: 'equals', filter: itemId } },
386
+ })
387
+ .then((res) => {
388
+ const doc = res?.data?.data?.[0];
389
+ if (!doc || !doc.data?.item?.type || !doc.data?.item?.id) {
390
+ MapEngineCyberia.imageCache[itemId].error = true;
391
+ return;
392
+ }
393
+ const { type, id } = doc.data.item;
394
+ loadImage(type, id);
395
+ })
396
+ .catch(() => {
397
+ MapEngineCyberia.imageCache[itemId].error = true;
398
+ });
399
+ }
400
+
401
+ static renderGrid(canvas, cols, rows, cellW, cellH, showGrid = true) {
402
+ canvas.width = cols * cellW;
403
+ canvas.height = rows * cellH;
404
+ const ctx = canvas.getContext('2d');
405
+
406
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
407
+
408
+ // Draw entities
409
+ for (const entity of MapEngineCyberia.entities) {
410
+ const x = entity.initCellX * cellW;
411
+ const y = entity.initCellY * cellH;
412
+ const w = entity.dimX * cellW;
413
+ const h = entity.dimY * cellH;
414
+
415
+ if (MapEngineCyberia.showObjectLayers && entity.objectLayerItemIds?.length) {
416
+ for (const itemId of entity.objectLayerItemIds) {
417
+ const cached = MapEngineCyberia.imageCache[itemId];
418
+ if (cached?.loaded && cached.img) {
419
+ ctx.drawImage(cached.img, x, y, w, h);
420
+ }
421
+ }
422
+ } else {
423
+ ctx.fillStyle = entity.color;
424
+ ctx.fillRect(x, y, w, h);
425
+ }
426
+ }
427
+
428
+ // Draw grid lines on top
429
+ if (showGrid) {
430
+ ctx.strokeStyle = '#aaa';
431
+ ctx.lineWidth = 1;
432
+
433
+ for (let r = 0; r < rows; r++) {
434
+ for (let c = 0; c < cols; c++) {
435
+ ctx.strokeRect(c * cellW, r * cellH, cellW, cellH);
436
+ }
437
+ }
438
+ }
439
+ }
440
+
441
+ static renderToOffscreenCanvas(cols, rows, cellW, cellH, { forceObjectLayers = false } = {}) {
442
+ const offscreen = document.createElement('canvas');
443
+ offscreen.width = cols * cellW;
444
+ offscreen.height = rows * cellH;
445
+ const ctx = offscreen.getContext('2d');
446
+ ctx.clearRect(0, 0, offscreen.width, offscreen.height);
447
+ const useObjectLayers = forceObjectLayers || MapEngineCyberia.showObjectLayers;
448
+ for (const entity of MapEngineCyberia.entities) {
449
+ const x = entity.initCellX * cellW;
450
+ const y = entity.initCellY * cellH;
451
+ const w = entity.dimX * cellW;
452
+ const h = entity.dimY * cellH;
453
+
454
+ if (useObjectLayers && entity.objectLayerItemIds?.length) {
455
+ for (const itemId of entity.objectLayerItemIds) {
456
+ const cached = MapEngineCyberia.imageCache[itemId];
457
+ if (cached?.loaded && cached.img) {
458
+ ctx.drawImage(cached.img, x, y, w, h);
459
+ }
460
+ }
461
+ } else {
462
+ ctx.fillStyle = entity.color;
463
+ ctx.fillRect(x, y, w, h);
464
+ }
465
+ }
466
+ return offscreen;
467
+ }
468
+
469
+ static renderEntityList(containerId) {
470
+ const container = s(`.${containerId}`);
471
+ if (!container) return;
472
+
473
+ const filtered = MapEngineCyberia.getFilteredEntities();
474
+ const counter = s('.map-engine-entity-filter-count');
475
+ if (counter) {
476
+ const total = MapEngineCyberia.entities.length;
477
+ const visible = filtered.length;
478
+ counter.innerHTML = `Showing ${visible} of ${total} entities`;
479
+ }
480
+
481
+ let html = '';
482
+ filtered.forEach(({ entity, i }) => {
483
+ const layerTags = (entity.objectLayerItemIds || [])
484
+ .map(
485
+ (id) =>
486
+ html`<div
487
+ class="badge inl"
488
+ style="background:${darkTheme ? '#335' : '#cde'};color:${darkTheme
489
+ ? '#adf'
490
+ : '#246'};border-radius:4px;font-size:11px;height:auto;min-width:auto;margin:1px 2px;"
491
+ >
492
+ <div class="badge-text"><i class="fa-solid fa-tag" style="margin-right:3px;font-size:9px;"></i>${id}</div>
493
+ </div>`,
494
+ )
495
+ .join('');
496
+ html += html`<div class="fl" style="border-bottom:1px solid #444; padding:4px 0; align-items:center;">
497
+ <div
498
+ class="in fll"
499
+ style="width:20px;height:20px;background:${entity.color};border:1px solid #888;margin-right:6px;"
500
+ ></div>
501
+ <div class="in fll" style="flex:1;font-size:12px;font-family:monospace;">
502
+ ${entity.entityType} (${entity.initCellX},${entity.initCellY}) ${entity.dimX}x${entity.dimY}
503
+ ${layerTags ? html`<div style="margin-top:2px;">${layerTags}</div>` : ''}
504
+ </div>
505
+ <div class="in fll" style="display:flex;gap:3px;">
506
+ <button
507
+ class="btn-map-engine-load-entity-values"
508
+ data-index="${i}"
509
+ style="cursor:pointer;background:#36a;color:#fff;border:none;padding:2px 8px;font-size:12px;"
510
+ >
511
+ <i class="fa-solid fa-clone"></i>
512
+ </button>
513
+ <button
514
+ class="btn-map-engine-remove-entity"
515
+ data-index="${i}"
516
+ style="cursor:pointer;background:#a00;color:#fff;border:none;padding:2px 8px;font-size:12px;"
517
+ >
518
+ <i class="fa-solid fa-trash"></i>
519
+ </button>
520
+ </div>
521
+ </div>`;
522
+ });
523
+ if (!html)
524
+ html = `<div style="color:#888;font-size:13px;">${MapEngineCyberia.entities.length > 0 ? 'No matching entities.' : 'No entities added yet.'}</div>`;
525
+ htmls(`.${containerId}`, html);
526
+
527
+ container.querySelectorAll('.btn-map-engine-remove-entity').forEach((btn) => {
528
+ btn.onclick = () => {
529
+ const idx = parseInt(btn.dataset.index, 10);
530
+ MapEngineCyberia.commitEntityMutation(() => {
531
+ MapEngineCyberia.entities.splice(idx, 1);
532
+ });
533
+ };
534
+ });
535
+
536
+ container.querySelectorAll('.btn-map-engine-load-entity-values').forEach((btn) => {
537
+ btn.onclick = () => {
538
+ const idx = parseInt(btn.dataset.index);
539
+ const entity = MapEngineCyberia.entities[idx];
540
+ if (!entity) return;
541
+
542
+ const entityType = entity.entityType || DEFAULT_ENTITY_TYPE;
543
+ const itemIds = entity.objectLayerItemIds || [];
544
+
545
+ MapEngineCyberia.setDropdownValue(MapEngineCyberia.entityTypeDropdownId, entityType);
546
+ if (s('.map-engine-init-cell-x')) s('.map-engine-init-cell-x').value = entity.initCellX || 0;
547
+ if (s('.map-engine-init-cell-y')) s('.map-engine-init-cell-y').value = entity.initCellY || 0;
548
+ if (s('.map-engine-dim-x')) s('.map-engine-dim-x').value = entity.dimX || 1;
549
+ if (s('.map-engine-dim-y')) s('.map-engine-dim-y').value = entity.dimY || 1;
550
+
551
+ // Parse rgba color back to hex + alpha
552
+ const rgbaMatch = (entity.color || '').match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+))?\)/);
553
+ if (rgbaMatch) {
554
+ const r = parseInt(rgbaMatch[1]).toString(16).padStart(2, '0');
555
+ const g = parseInt(rgbaMatch[2]).toString(16).padStart(2, '0');
556
+ const b = parseInt(rgbaMatch[3]).toString(16).padStart(2, '0');
557
+ const alpha = rgbaMatch[4] !== undefined ? parseFloat(rgbaMatch[4]) : 1;
558
+ if (s('.map-engine-color')) s('.map-engine-color').value = `#${r}${g}${b}`;
559
+ if (s('.map-engine-alpha')) {
560
+ s('.map-engine-alpha').value = alpha;
561
+ s('.map-engine-alpha').dispatchEvent(new Event('input'));
562
+ }
563
+ if (s('.map-engine-color')) s('.map-engine-color').dispatchEvent(new Event('input'));
564
+ }
565
+
566
+ MapEngineCyberia.syncObjectLayerDropdownSelection(itemIds);
567
+ };
568
+ });
569
+ }
570
+
571
+ static async render(options = {}) {
572
+ const { appStore } = options;
573
+ const idCode = 'map-engine-input-code';
574
+ const idName = 'map-engine-input-name';
575
+ const idDescription = 'map-engine-input-description';
576
+ const idTags = 'map-engine-input-tags';
577
+ const idStatus = 'map-engine-input-status';
578
+ const idThumbnail = 'map-engine-input-thumbnail';
579
+ const idCreator = 'map-engine-input-creator';
580
+
581
+ const idX = 'map-engine-input-x';
582
+ const idY = 'map-engine-input-y';
583
+ const idCellW = 'map-engine-input-cell-w';
584
+ const idCellH = 'map-engine-input-cell-h';
585
+ const canvasId = 'map-engine-canvas';
586
+
587
+ const idEntityType = MapEngineCyberia.entityTypeDropdownId;
588
+ const idInitCellX = 'map-engine-init-cell-x';
589
+ const idInitCellY = 'map-engine-init-cell-y';
590
+ const idDimX = 'map-engine-dim-x';
591
+ const idDimY = 'map-engine-dim-y';
592
+ const idColor = 'map-engine-color';
593
+ const idColorPalette = 'map-engine-color-palette';
594
+ const idAlpha = 'map-engine-alpha';
595
+ const idFactorA = 'map-engine-factor-a';
596
+ const idFactorB = 'map-engine-factor-b';
597
+ const idVariationPreserve = 'map-engine-variation-preserve';
598
+ const rgbaDisplayId = 'map-engine-rgba-display';
599
+ const entityListId = 'map-engine-entity-list';
600
+ const idObjLayerDropdown = MapEngineCyberia.objectLayerDropdownId;
601
+ const idObjLayerDropdownHost = MapEngineCyberia.objectLayerDropdownHostId;
602
+ const idRenameSourceObjectLayer = MapEngineCyberia.renameSourceObjectLayerInputId;
603
+ const idRenameTargetObjectLayer = MapEngineCyberia.renameTargetObjectLayerInputId;
604
+ const managementId = 'modal-cyberia-map-engine';
605
+
606
+ MapEngineCyberia.setEntities([], { clearHistory: true });
607
+ MapEngineCyberia.currentMapId = null;
608
+ MapEngineCyberia.currentThumbnailId = null;
609
+
610
+ const hexToRgba = (hex, alpha) => {
611
+ const r = parseInt(hex.slice(1, 3), 16);
612
+ const g = parseInt(hex.slice(3, 5), 16);
613
+ const b = parseInt(hex.slice(5, 7), 16);
614
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
615
+ };
616
+
617
+ const getCanvasParams = () => ({
618
+ cols: parseInt(s(`.${idX}`)?.value) || 16,
619
+ rows: parseInt(s(`.${idY}`)?.value) || 16,
620
+ cellW: parseInt(s(`.${idCellW}`)?.value) || 32,
621
+ cellH: parseInt(s(`.${idCellH}`)?.value) || 32,
622
+ });
623
+
624
+ const getFactorRange = () => {
625
+ const rawA = parseFloat(s(`.${idFactorA}`)?.value);
626
+ const rawB = parseFloat(s(`.${idFactorB}`)?.value);
627
+ const safeA = Number.isFinite(rawA) ? rawA : 0.5;
628
+ const safeB = Number.isFinite(rawB) ? rawB : 1.5;
629
+ return {
630
+ min: Math.min(safeA, safeB),
631
+ max: Math.max(safeA, safeB),
632
+ };
633
+ };
634
+
635
+ const getPreserveSet = () => {
636
+ const preserveRaw = s(`.${idVariationPreserve}`)?.value || '';
637
+ return new Set(
638
+ preserveRaw
639
+ .split(',')
640
+ .map((t) => t.trim().toLowerCase())
641
+ .filter((t) => t),
642
+ );
643
+ };
644
+
645
+ const getPreserveIndices = () => {
646
+ const preserveSet = getPreserveSet();
647
+ return MapEngineCyberia.entities.reduce((acc, entity, index) => {
648
+ if (preserveSet.has((entity.entityType || '').toLowerCase())) acc.push(index);
649
+ return acc;
650
+ }, []);
651
+ };
652
+
653
+ const getAffectedEntityCount = (total) => {
654
+ if (total <= 0) return 0;
655
+ if (!MapEngineCyberia.enableRandomFactors) return Math.max(1, Math.round(total / 2));
656
+
657
+ const { min, max } = getFactorRange();
658
+ const factor = min + Math.random() * (max - min);
659
+ return Math.max(1, Math.min(total, Math.round(total * factor)));
660
+ };
661
+
662
+ const rerenderCanvas = () => {
663
+ const canvas = s(`.${canvasId}`);
664
+ if (!canvas) return;
665
+ const { cols, rows, cellW, cellH } = getCanvasParams();
666
+ MapEngineCyberia.renderGrid(canvas, cols, rows, cellW, cellH, MapEngineCyberia.showGridBorders);
667
+ };
668
+
669
+ const getEntityParams = () => {
670
+ const hex = s(`.${idColor}`)?.value || '#ff0000';
671
+ const alpha = parseFloat(s(`.${idAlpha}`)?.value);
672
+ return {
673
+ entityType: MapEngineCyberia.getSelectedEntityType(),
674
+ initCellX: parseInt(s(`.${idInitCellX}`)?.value) || 0,
675
+ initCellY: parseInt(s(`.${idInitCellY}`)?.value) || 0,
676
+ dimX: parseInt(s(`.${idDimX}`)?.value) || 1,
677
+ dimY: parseInt(s(`.${idDimY}`)?.value) || 1,
678
+ color: hexToRgba(hex, alpha),
679
+ };
680
+ };
681
+
682
+ const applyRandomFactorsToDimensions = (ep) => {
683
+ if (!MapEngineCyberia.enableRandomFactors) return;
684
+ const { min, max } = getFactorRange();
685
+ const factor = min + Math.random() * (max - min);
686
+ ep.dimX = Math.max(1, Math.round(ep.dimX * factor));
687
+ ep.dimY = Math.max(1, Math.round(ep.dimY * factor));
688
+ };
689
+
690
+ const addEntityLocally = () => {
691
+ const ep = getEntityParams();
692
+ ep.objectLayerItemIds = DropDown.Tokens[idObjLayerDropdown]?.value
693
+ ? [...DropDown.Tokens[idObjLayerDropdown].value]
694
+ : [];
695
+ applyRandomFactorsToDimensions(ep);
696
+ MapEngineCyberia.commitEntityMutation(() => {
697
+ MapEngineCyberia.entities.push(ep);
698
+ });
699
+ };
700
+
701
+ const fillMapWithEntity = () => {
702
+ const ep = getEntityParams();
703
+ ep.objectLayerItemIds = DropDown.Tokens[idObjLayerDropdown]?.value
704
+ ? [...DropDown.Tokens[idObjLayerDropdown].value]
705
+ : [];
706
+ const { cols, rows } = getCanvasParams();
707
+ const dimX = ep.dimX || 1;
708
+ const dimY = ep.dimY || 1;
709
+ MapEngineCyberia.commitEntityMutation(() => {
710
+ for (let r = 0; r < rows; r += dimY) {
711
+ for (let c = 0; c < cols; c += dimX) {
712
+ const tile = {
713
+ ...ep,
714
+ initCellX: c,
715
+ initCellY: r,
716
+ objectLayerItemIds: [...ep.objectLayerItemIds],
717
+ };
718
+ applyRandomFactorsToDimensions(tile);
719
+ MapEngineCyberia.entities.push(tile);
720
+ }
721
+ }
722
+ });
723
+ };
724
+
725
+ const generateVariation = () => {
726
+ const { min, max } = getFactorRange();
727
+ const preserveSet = getPreserveSet();
728
+ const { cols, rows } = getCanvasParams();
729
+ MapEngineCyberia.commitEntityMutation(() => {
730
+ for (const entity of MapEngineCyberia.entities) {
731
+ if (preserveSet.has((entity.entityType || '').toLowerCase())) continue;
732
+ const dimFactor = min + Math.random() * (max - min);
733
+ entity.dimX = Math.max(1, Math.round(entity.dimX * dimFactor));
734
+ entity.dimY = Math.max(1, Math.round(entity.dimY * dimFactor));
735
+ const posFactor = min + Math.random() * (max - min);
736
+ entity.initCellX = Math.max(0, Math.min(cols - 1, Math.round(entity.initCellX * posFactor)));
737
+ entity.initCellY = Math.max(0, Math.min(rows - 1, Math.round(entity.initCellY * posFactor)));
738
+ }
739
+ });
740
+ };
741
+
742
+ const clearAllEntities = () => {
743
+ MapEngineCyberia.commitEntityMutation(() => {
744
+ MapEngineCyberia.entities = [];
745
+ });
746
+ };
747
+
748
+ const randomSwapPreserveEntities = () => {
749
+ const preserveIndices = getPreserveIndices();
750
+ if (preserveIndices.length < 2) return;
751
+
752
+ const selectedCount = getAffectedEntityCount(preserveIndices.length);
753
+ const shuffled = [...preserveIndices].sort(() => Math.random() - 0.5).slice(0, selectedCount);
754
+ const swapCount = Math.max(1, Math.floor(shuffled.length / 2));
755
+
756
+ MapEngineCyberia.commitEntityMutation(() => {
757
+ for (let i = 0; i < swapCount * 2; i += 2) {
758
+ const firstIndex = shuffled[i];
759
+ const secondIndex = shuffled[i + 1];
760
+ if (firstIndex === undefined || secondIndex === undefined) continue;
761
+
762
+ const first = MapEngineCyberia.entities[firstIndex];
763
+ const second = MapEngineCyberia.entities[secondIndex];
764
+ const firstPos = { initCellX: first.initCellX, initCellY: first.initCellY };
765
+
766
+ first.initCellX = second.initCellX;
767
+ first.initCellY = second.initCellY;
768
+ second.initCellX = firstPos.initCellX;
769
+ second.initCellY = firstPos.initCellY;
770
+ }
771
+ });
772
+ };
773
+
774
+ const replacePreserveEntitiesWithCurrentConfig = () => {
775
+ const preserveIndices = getPreserveIndices();
776
+ if (preserveIndices.length === 0) return;
777
+
778
+ const selectedCount = getAffectedEntityCount(preserveIndices.length);
779
+ const selected = [...preserveIndices].sort(() => Math.random() - 0.5).slice(0, selectedCount);
780
+ const template = getEntityParams();
781
+ template.objectLayerItemIds = DropDown.Tokens[idObjLayerDropdown]?.value
782
+ ? [...DropDown.Tokens[idObjLayerDropdown].value]
783
+ : [];
784
+
785
+ MapEngineCyberia.commitEntityMutation(() => {
786
+ for (const index of selected) {
787
+ const target = MapEngineCyberia.entities[index];
788
+ const replacement = {
789
+ ...target,
790
+ entityType: template.entityType,
791
+ dimX: template.dimX,
792
+ dimY: template.dimY,
793
+ color: template.color,
794
+ objectLayerItemIds: [...template.objectLayerItemIds],
795
+ };
796
+ applyRandomFactorsToDimensions(replacement);
797
+ MapEngineCyberia.entities[index] = replacement;
798
+ }
799
+ });
800
+ };
801
+
802
+ const flipHorizontal = () => {
803
+ const { cols } = getCanvasParams();
804
+ MapEngineCyberia.commitEntityMutation(() => {
805
+ for (const entity of MapEngineCyberia.entities) {
806
+ entity.initCellX = cols - entity.initCellX - entity.dimX;
807
+ }
808
+ });
809
+ };
810
+
811
+ const flipVertical = () => {
812
+ const { rows } = getCanvasParams();
813
+ MapEngineCyberia.commitEntityMutation(() => {
814
+ for (const entity of MapEngineCyberia.entities) {
815
+ entity.initCellY = rows - entity.initCellY - entity.dimY;
816
+ }
817
+ });
818
+ };
819
+
820
+ const getMapPayload = () => {
821
+ const tagsRaw = s(`.${idTags}`)?.value || '';
822
+ const tags = tagsRaw
823
+ .split(',')
824
+ .map((t) => t.trim())
825
+ .filter((t) => t);
826
+ const { cols, rows, cellW, cellH } = getCanvasParams();
827
+ const payload = {
828
+ code: s(`.${idCode}`)?.value || '',
829
+ name: s(`.${idName}`)?.value || '',
830
+ description: s(`.${idDescription}`)?.value || '',
831
+ tags,
832
+ status: DropDown.Tokens[idStatus]?.value || 'unlisted',
833
+ entities: MapEngineCyberia.entities,
834
+ gridX: cols,
835
+ gridY: rows,
836
+ cellWidth: cellW,
837
+ cellHeight: cellH,
838
+ };
839
+ if (MapEngineCyberia.currentThumbnailId) payload.thumbnail = MapEngineCyberia.currentThumbnailId;
840
+ return payload;
841
+ };
842
+
843
+ const saveMap = async () => {
844
+ // Upload thumbnail file only if user selected a new one
845
+ const thumbnailInput = s(`.${idThumbnail}`);
846
+ if (
847
+ MapEngineCyberia.thumbnailDirty &&
848
+ thumbnailInput &&
849
+ thumbnailInput.files &&
850
+ thumbnailInput.files.length > 0
851
+ ) {
852
+ const formData = new FormData();
853
+ formData.append('file', thumbnailInput.files[0]);
854
+ const uploadResult = await FileService.post({ body: formData });
855
+ if (uploadResult.status === 'success' && uploadResult.data && uploadResult.data.length > 0) {
856
+ MapEngineCyberia.currentThumbnailId = uploadResult.data[0]._id;
857
+ } else {
858
+ NotificationManager.Push({
859
+ html: uploadResult.message || 'Failed to upload thumbnail',
860
+ status: 'error',
861
+ });
862
+ return;
863
+ }
864
+ }
865
+
866
+ // Capture object layer thumbnail on save/update if checkbox is checked
867
+ if (MapEngineCyberia.captureObjLayerThumbnail) {
868
+ const { cols, rows, cellW, cellH } = getCanvasParams();
869
+ const offscreen = MapEngineCyberia.renderToOffscreenCanvas(cols, rows, cellW, cellH, {
870
+ forceObjectLayers: true,
871
+ });
872
+ const blob = await new Promise((resolve) => offscreen.toBlob(resolve, 'image/png'));
873
+ if (blob) {
874
+ const file = new File([blob], 'map-thumbnail.png', { type: 'image/png' });
875
+ const formData = new FormData();
876
+ formData.append('file', file);
877
+ const uploadResult = await FileService.post({ body: formData });
878
+ if (uploadResult.status === 'success' && uploadResult.data?.length > 0) {
879
+ MapEngineCyberia.currentThumbnailId = uploadResult.data[0]._id;
880
+ }
881
+ }
882
+ }
883
+
884
+ const body = getMapPayload();
885
+ let result;
886
+ if (MapEngineCyberia.currentMapId) {
887
+ result = await CyberiaMapService.put({ id: MapEngineCyberia.currentMapId, body });
888
+ } else {
889
+ result = await CyberiaMapService.post({ body });
890
+ }
891
+ NotificationManager.Push({
892
+ html:
893
+ result.status === 'error'
894
+ ? result.message
895
+ : MapEngineCyberia.currentMapId
896
+ ? Translate.instance('success-update-item')
897
+ : Translate.instance('success-create-item'),
898
+ status: result.status,
899
+ });
900
+ if (result.status === 'success') {
901
+ if (result.data?._id) MapEngineCyberia.currentMapId = result.data._id;
902
+ await DefaultManagement.loadTable(managementId, { force: true, reload: true });
903
+ }
904
+ };
905
+
906
+ const cloneMap = async () => {
907
+ if (!MapEngineCyberia.currentMapId) return;
908
+
909
+ let cloneThumbnailId = null;
910
+
911
+ // When enabled, clone should use a fresh object-layer capture instead of reusing the current thumbnail file.
912
+ if (MapEngineCyberia.captureObjLayerThumbnail) {
913
+ const { cols, rows, cellW, cellH } = getCanvasParams();
914
+ const offscreen = MapEngineCyberia.renderToOffscreenCanvas(cols, rows, cellW, cellH, {
915
+ forceObjectLayers: true,
916
+ });
917
+ const blob = await new Promise((resolve) => offscreen.toBlob(resolve, 'image/png'));
918
+ if (blob) {
919
+ const file = new File([blob], 'map-thumbnail.png', { type: 'image/png' });
920
+ const formData = new FormData();
921
+ formData.append('file', file);
922
+ const uploadResult = await FileService.post({ body: formData });
923
+ if (uploadResult.status === 'success' && uploadResult.data?.length > 0) {
924
+ cloneThumbnailId = uploadResult.data[0]._id;
925
+ }
926
+ }
927
+ } else {
928
+ const thumbnailInput = s(`.${idThumbnail}`);
929
+ if (thumbnailInput && thumbnailInput.files && thumbnailInput.files.length > 0) {
930
+ const formData = new FormData();
931
+ formData.append('file', thumbnailInput.files[0]);
932
+ const uploadResult = await FileService.post({ body: formData });
933
+ if (uploadResult.status === 'success' && uploadResult.data && uploadResult.data.length > 0) {
934
+ cloneThumbnailId = uploadResult.data[0]._id;
935
+ } else {
936
+ NotificationManager.Push({
937
+ html: uploadResult.message || 'Failed to upload thumbnail',
938
+ status: 'error',
939
+ });
940
+ return;
941
+ }
942
+ }
943
+ }
944
+
945
+ const body = getMapPayload();
946
+ if (cloneThumbnailId) body.thumbnail = cloneThumbnailId;
947
+ const result = await CyberiaMapService.post({ body });
948
+ NotificationManager.Push({
949
+ html: result.status === 'error' ? result.message : Translate.instance('success-create-item'),
950
+ status: result.status,
951
+ });
952
+ if (result.status === 'success') {
953
+ if (result.data?._id) MapEngineCyberia.currentMapId = result.data._id;
954
+ if (cloneThumbnailId) MapEngineCyberia.currentThumbnailId = cloneThumbnailId;
955
+ await DefaultManagement.loadTable(managementId, { force: true, reload: true });
956
+ }
957
+ };
958
+
959
+ const loadMap = async (mapData) => {
960
+ MapEngineCyberia.currentMapId = mapData._id || null;
961
+ if (s(`.${idCode}`)) s(`.${idCode}`).value = mapData.code || '';
962
+ if (s(`.${idName}`)) s(`.${idName}`).value = mapData.name || '';
963
+ if (s(`.${idDescription}`)) s(`.${idDescription}`).value = mapData.description || '';
964
+ if (s(`.${idTags}`)) s(`.${idTags}`).value = (mapData.tags || []).join(', ');
965
+
966
+ // Restore grid dimensions
967
+ if (s(`.${idX}`)) s(`.${idX}`).value = mapData.gridX || 16;
968
+ if (s(`.${idY}`)) s(`.${idY}`).value = mapData.gridY || 16;
969
+ if (s(`.${idCellW}`)) s(`.${idCellW}`).value = mapData.cellWidth || 32;
970
+ if (s(`.${idCellH}`)) s(`.${idCellH}`).value = mapData.cellHeight || 32;
971
+ const statusValue = mapData.status || 'unlisted';
972
+ if (DropDown.Tokens[idStatus]) {
973
+ const statusIndex = statusOptions.findIndex((opt) => opt.value === statusValue);
974
+ if (statusIndex > -1) s(`.dropdown-option-${idStatus}-${statusIndex}`).click();
975
+ }
976
+
977
+ // Thumbnail
978
+ MapEngineCyberia.currentThumbnailId = mapData.thumbnail || null;
979
+ const thumbnailPreview = s(`.map-engine-thumbnail-preview`);
980
+ if (MapEngineCyberia.currentThumbnailId) {
981
+ const thumbId =
982
+ typeof MapEngineCyberia.currentThumbnailId === 'object'
983
+ ? MapEngineCyberia.currentThumbnailId._id
984
+ : MapEngineCyberia.currentThumbnailId;
985
+
986
+ // Set preview image
987
+ if (thumbnailPreview) {
988
+ thumbnailPreview.innerHTML = html`<img
989
+ src="${getApiBaseUrl({ id: thumbId, endpoint: 'file/blob' })}"
990
+ style="max-width:120px;max-height:120px;border:1px solid #555;"
991
+ onerror="this.style.display='none';"
992
+ />`;
993
+ }
994
+
995
+ // Populate InputFile with the actual file from server
996
+ if (s(`.${idThumbnail}`)) {
997
+ const fileData = await getFileFromBlobEndpoint({ _id: thumbId, mimetype: 'image/png' });
998
+ if (fileData) {
999
+ const dataTransfer = new DataTransfer();
1000
+ dataTransfer.items.add(fileData);
1001
+ s(`.${idThumbnail}`).files = dataTransfer.files;
1002
+ s(`.${idThumbnail}`).onchange({ target: s(`.${idThumbnail}`) });
1003
+ }
1004
+ }
1005
+ MapEngineCyberia.thumbnailDirty = false;
1006
+ } else {
1007
+ if (thumbnailPreview) thumbnailPreview.innerHTML = '';
1008
+ // Clear InputFile
1009
+ if (s(`.${idThumbnail}`)) {
1010
+ s(`.${idThumbnail}`).value = '';
1011
+ s(`.${idThumbnail}`).onchange({ target: s(`.${idThumbnail}`) });
1012
+ }
1013
+ }
1014
+
1015
+ // Creator display
1016
+ const creatorDisplay = s(`.map-engine-creator-display`);
1017
+ if (creatorDisplay) {
1018
+ if (mapData.creator) {
1019
+ const creatorUsername =
1020
+ typeof mapData.creator === 'object' ? mapData.creator.username || mapData.creator._id : mapData.creator;
1021
+ creatorDisplay.innerHTML = html`<span style="font-family:monospace;font-size:12px;"
1022
+ >${creatorUsername}</span
1023
+ >`;
1024
+ } else {
1025
+ creatorDisplay.innerHTML = html`<span style="color:#888;font-size:12px;">—</span>`;
1026
+ }
1027
+ }
1028
+
1029
+ const nextEntities = (mapData.entities || []).map((e) => ({
1030
+ entityType: e.entityType,
1031
+ initCellX: e.initCellX,
1032
+ initCellY: e.initCellY,
1033
+ dimX: e.dimX,
1034
+ dimY: e.dimY,
1035
+ color: e.color,
1036
+ objectLayerItemIds: e.objectLayerItemIds || [],
1037
+ }));
1038
+ MapEngineCyberia.setEntities(nextEntities, { clearHistory: true });
1039
+ };
1040
+
1041
+ MapEngineCyberia.loadMap = loadMap;
1042
+
1043
+ const resetForm = () => {
1044
+ MapEngineCyberia.currentMapId = null;
1045
+ MapEngineCyberia.currentThumbnailId = null;
1046
+ MapEngineCyberia.thumbnailDirty = false;
1047
+ if (s(`.${idCode}`)) s(`.${idCode}`).value = '';
1048
+ if (s(`.${idName}`)) s(`.${idName}`).value = '';
1049
+ if (s(`.${idDescription}`)) s(`.${idDescription}`).value = '';
1050
+ if (s(`.${idTags}`)) s(`.${idTags}`).value = '';
1051
+ if (DropDown.Tokens[idStatus]) {
1052
+ const resetIndex = statusOptions.findIndex((opt) => opt.value === 'unlisted');
1053
+ if (resetIndex > -1) s(`.dropdown-option-${idStatus}-${resetIndex}`).click();
1054
+ }
1055
+ const thumbnailPreview = s(`.map-engine-thumbnail-preview`);
1056
+ if (thumbnailPreview) thumbnailPreview.innerHTML = '';
1057
+ if (s(`.${idThumbnail}`)) {
1058
+ s(`.${idThumbnail}`).value = '';
1059
+ s(`.${idThumbnail}`).onchange({ target: s(`.${idThumbnail}`) });
1060
+ }
1061
+ const creatorDisplay = s(`.map-engine-creator-display`);
1062
+ if (creatorDisplay) creatorDisplay.innerHTML = '<span style="color:#888;font-size:12px;">—</span>';
1063
+ if (s(`.${idX}`)) s(`.${idX}`).value = 16;
1064
+ if (s(`.${idY}`)) s(`.${idY}`).value = 16;
1065
+ if (s(`.${idCellW}`)) s(`.${idCellW}`).value = 32;
1066
+ if (s(`.${idCellH}`)) s(`.${idCellH}`).value = 32;
1067
+ if (s(`.${idVariationPreserve}`)) s(`.${idVariationPreserve}`).value = '';
1068
+ if (s(`.${idRenameSourceObjectLayer}`)) s(`.${idRenameSourceObjectLayer}`).value = '';
1069
+ if (s(`.${idRenameTargetObjectLayer}`)) s(`.${idRenameTargetObjectLayer}`).value = '';
1070
+ MapEngineCyberia.setEntities([], { clearHistory: true });
1071
+ MapEngineCyberia.setDropdownValue(idEntityType, DEFAULT_ENTITY_TYPE);
1072
+ if (DropDown.Tokens[idObjLayerDropdown]) {
1073
+ DropDown.Tokens[idObjLayerDropdown].oncheckvalues = {};
1074
+ DropDown.Tokens[idObjLayerDropdown].value = [];
1075
+ htmls(`.dropdown-current-${idObjLayerDropdown}`, '');
1076
+ htmls(`.${idObjLayerDropdown}-render-container`, '');
1077
+ }
1078
+ };
1079
+
1080
+ setTimeout(() => {
1081
+ const canvas = s(`.${canvasId}`);
1082
+ if (!canvas) return;
1083
+
1084
+ MapEngineCyberia.setEntityHistorySync(() => {
1085
+ MapEngineCyberia.renderEntityList(entityListId);
1086
+ rerenderCanvas();
1087
+ });
1088
+ MapEngineCyberia.bindEntityHistoryHotkeys();
1089
+
1090
+ const syncPaletteFromColorInput = () => {
1091
+ const colorPalette = s(`.${idColorPalette}`);
1092
+ const hex = (s(`.${idColor}`)?.value || '#ff0000').toUpperCase();
1093
+ if (colorPalette && colorPalette.value !== hex) {
1094
+ colorPalette.value = hex;
1095
+ }
1096
+ };
1097
+
1098
+ const updateRgbaDisplay = () => {
1099
+ const hex = s(`.${idColor}`)?.value || '#ff0000';
1100
+ const alpha = parseFloat(s(`.${idAlpha}`)?.value);
1101
+ const rgba = hexToRgba(hex, alpha);
1102
+ if (s(`.${rgbaDisplayId}`))
1103
+ htmls(
1104
+ `.${rgbaDisplayId}`,
1105
+ `<span style="display:inline-block;width:16px;height:16px;background:${rgba};border:1px solid #888;vertical-align:middle;margin-right:6px;"></span>${rgba}`,
1106
+ );
1107
+ syncPaletteFromColorInput();
1108
+ };
1109
+
1110
+ if (s(`.${idColor}`)) s(`.${idColor}`).addEventListener('input', updateRgbaDisplay);
1111
+ if (s(`.${idAlpha}`)) s(`.${idAlpha}`).addEventListener('input', updateRgbaDisplay);
1112
+ if (s(`.${idColorPalette}`)) {
1113
+ s(`.${idColorPalette}`).addEventListener('colorchange', (event) => {
1114
+ const nextHex = (event.detail?.value || event.detail?.hex || '#ff0000').toLowerCase();
1115
+ if (s(`.${idColor}`) && s(`.${idColor}`).value !== nextHex) {
1116
+ s(`.${idColor}`).value = nextHex;
1117
+ s(`.${idColor}`).dispatchEvent(new Event('input'));
1118
+ }
1119
+ });
1120
+ }
1121
+ updateRgbaDisplay();
1122
+
1123
+ const params = getCanvasParams();
1124
+ MapEngineCyberia.renderGrid(
1125
+ canvas,
1126
+ params.cols,
1127
+ params.rows,
1128
+ params.cellW,
1129
+ params.cellH,
1130
+ MapEngineCyberia.showGridBorders,
1131
+ );
1132
+ MapEngineCyberia.refreshEntityEditor();
1133
+
1134
+ canvas.onclick = (e) => {
1135
+ const rect = canvas.getBoundingClientRect();
1136
+ const { cellW, cellH } = getCanvasParams();
1137
+ const col = Math.floor(((e.clientX - rect.left) * (canvas.width / rect.width)) / cellW);
1138
+ const row = Math.floor(((e.clientY - rect.top) * (canvas.height / rect.height)) / cellH);
1139
+ console.log(`Cell clicked: (${col}, ${row})`);
1140
+
1141
+ if (s('.map-engine-cell-coords')) htmls('.map-engine-cell-coords', `Cell: (${col}, ${row})`);
1142
+
1143
+ if (s(`.${idInitCellX}`)) s(`.${idInitCellX}`).value = col;
1144
+ if (s(`.${idInitCellY}`)) s(`.${idInitCellY}`).value = row;
1145
+
1146
+ if (MapEngineCyberia.addOnClick) addEntityLocally();
1147
+ };
1148
+
1149
+ if (s(`.btn-map-engine-add-entity`)) s(`.btn-map-engine-add-entity`).onclick = () => addEntityLocally();
1150
+
1151
+ if (s(`.btn-map-engine-fill-map`)) s(`.btn-map-engine-fill-map`).onclick = () => fillMapWithEntity();
1152
+
1153
+ if (s(`.btn-map-engine-generate-variation`))
1154
+ s(`.btn-map-engine-generate-variation`).onclick = () => generateVariation();
1155
+
1156
+ if (s(`.btn-map-engine-swap-preserve-entities`))
1157
+ s(`.btn-map-engine-swap-preserve-entities`).onclick = () => randomSwapPreserveEntities();
1158
+
1159
+ if (s(`.btn-map-engine-replace-preserve-entities`))
1160
+ s(`.btn-map-engine-replace-preserve-entities`).onclick = () => replacePreserveEntitiesWithCurrentConfig();
1161
+
1162
+ if (s(`.btn-map-engine-flip-horizontal`)) s(`.btn-map-engine-flip-horizontal`).onclick = () => flipHorizontal();
1163
+
1164
+ if (s(`.btn-map-engine-flip-vertical`)) s(`.btn-map-engine-flip-vertical`).onclick = () => flipVertical();
1165
+
1166
+ if (s(`.btn-map-engine-generate`))
1167
+ s(`.btn-map-engine-generate`).onclick = () => {
1168
+ rerenderCanvas();
1169
+ };
1170
+
1171
+ if (s(`.btn-map-engine-save-map`)) s(`.btn-map-engine-save-map`).onclick = () => saveMap();
1172
+
1173
+ if (s(`.btn-map-engine-clone-map`))
1174
+ s(`.btn-map-engine-clone-map`).onclick = () => {
1175
+ if (!MapEngineCyberia.currentMapId) return;
1176
+ cloneMap();
1177
+ };
1178
+
1179
+ if (s(`.btn-map-engine-new-map`)) s(`.btn-map-engine-new-map`).onclick = () => resetForm();
1180
+
1181
+ ThemeEvents['map-engine-theme'] = () => {
1182
+ MapEngineCyberia.renderEntityList(entityListId);
1183
+ };
1184
+
1185
+ if (s(`.btn-map-engine-capture-thumbnail`))
1186
+ s(`.btn-map-engine-capture-thumbnail`).onclick = () => {
1187
+ const { cols, rows, cellW, cellH } = getCanvasParams();
1188
+ const offscreen = MapEngineCyberia.renderToOffscreenCanvas(cols, rows, cellW, cellH);
1189
+ offscreen.toBlob((blob) => {
1190
+ if (!blob) return;
1191
+ const file = new File([blob], 'map-thumbnail.png', { type: 'image/png' });
1192
+ const dataTransfer = new DataTransfer();
1193
+ dataTransfer.items.add(file);
1194
+ const thumbnailInput = s(`.${idThumbnail}`);
1195
+ if (thumbnailInput) {
1196
+ thumbnailInput.files = dataTransfer.files;
1197
+ thumbnailInput.onchange({ target: thumbnailInput });
1198
+ }
1199
+ MapEngineCyberia.thumbnailDirty = true;
1200
+ }, 'image/png');
1201
+ };
1202
+
1203
+ if (s(`.btn-map-engine-toggle-thumbnail`))
1204
+ s(`.btn-map-engine-toggle-thumbnail`).onclick = () => {
1205
+ const body = s(`.map-engine-thumbnail-body`);
1206
+ const caret = s(`.map-engine-thumbnail-caret`);
1207
+ if (body) body.classList.toggle('hide');
1208
+ if (caret) {
1209
+ caret.classList.toggle('fa-caret-right');
1210
+ caret.classList.toggle('fa-caret-down');
1211
+ }
1212
+ };
1213
+
1214
+ if (s('.btn-map-engine-toggle-entity-filter'))
1215
+ s('.btn-map-engine-toggle-entity-filter').onclick = () => {
1216
+ const body = s('.map-engine-entity-filter-body');
1217
+ const caret = s('.map-engine-entity-filter-caret');
1218
+ if (body) body.classList.toggle('hide');
1219
+ if (caret) {
1220
+ caret.classList.toggle('fa-caret-right');
1221
+ caret.classList.toggle('fa-caret-down');
1222
+ }
1223
+ };
1224
+
1225
+ let entityFilterTimeout = null;
1226
+ const applyEntityFilter = () => {
1227
+ clearTimeout(entityFilterTimeout);
1228
+ entityFilterTimeout = setTimeout(() => {
1229
+ MapEngineCyberia.renderEntityList(entityListId);
1230
+ }, 300);
1231
+ };
1232
+ [idFilterEntityType, idFilterInitX, idFilterInitY].forEach((cls) => {
1233
+ if (s(`.${cls}`)) s(`.${cls}`).addEventListener('input', applyEntityFilter);
1234
+ });
1235
+
1236
+ if (s('.btn-map-engine-clear-entity-filter'))
1237
+ s('.btn-map-engine-clear-entity-filter').onclick = () => {
1238
+ [idFilterEntityType, idFilterInitX, idFilterInitY].forEach((cls) => {
1239
+ if (s(`.${cls}`)) s(`.${cls}`).value = '';
1240
+ });
1241
+ MapEngineCyberia.renderEntityList(entityListId);
1242
+ };
1243
+
1244
+ if (s('.btn-map-engine-clear-all-entities'))
1245
+ s('.btn-map-engine-clear-all-entities').onclick = () => clearAllEntities();
1246
+
1247
+ if (s('.btn-map-engine-rename-filtered-object-layer-item-id'))
1248
+ s('.btn-map-engine-rename-filtered-object-layer-item-id').onclick = () => {
1249
+ MapEngineCyberia.renameFilteredObjectLayerItemId();
1250
+ };
1251
+ });
1252
+
1253
+ const statusOptions = [
1254
+ { value: 'unlisted', display: 'unlisted', data: 'unlisted', onClick: () => {} },
1255
+ { value: 'draft', display: 'draft', data: 'draft', onClick: () => {} },
1256
+ { value: 'published', display: 'published', data: 'published', onClick: () => {} },
1257
+ { value: 'archived', display: 'archived', data: 'archived', onClick: () => {} },
1258
+ ];
1259
+
1260
+ const managementTableHtml = await CyberiaMapManagement.instance({
1261
+ idModal: managementId,
1262
+ loadMapCallback: loadMap,
1263
+ appStore,
1264
+ readyRowDataEvent: {
1265
+ 'map-engine-check-deleted': (rowData) => {
1266
+ if (MapEngineCyberia.currentMapId) {
1267
+ const stillExists = rowData.some((row) => row._id === MapEngineCyberia.currentMapId);
1268
+ if (!stillExists) MapEngineCyberia.currentMapId = null;
1269
+ }
1270
+ },
1271
+ },
1272
+ });
1273
+
1274
+ const dcMapFields = 'map-engine-dc-fields';
1275
+ const dcMetaFields = 'map-engine-dc-meta';
1276
+ const dcGridSize = 'map-engine-dc-grid-size';
1277
+ const dcCellSize = 'map-engine-dc-cell-size';
1278
+ const dcEntityType = 'map-engine-dc-entity-type';
1279
+ const dcAlpha = 'map-engine-dc-alpha';
1280
+ const dcCellPos = 'map-engine-dc-cell-pos';
1281
+ const dcDim = 'map-engine-dc-dim';
1282
+ const dcFactors = 'map-engine-dc-factors';
1283
+ const dcSaveNew = 'map-engine-dc-save-new';
1284
+ const dcEntityFilter = 'map-engine-dc-entity-filter';
1285
+ const dcCanvasOpts = 'map-engine-dc-canvas-opts';
1286
+ const idFilterEntityType = 'map-engine-filter-entity-type';
1287
+ const idFilterInitX = 'map-engine-filter-init-x';
1288
+ const idFilterInitY = 'map-engine-filter-init-y';
1289
+
1290
+ return html`<div class="in section-mp map-engine-container">
1291
+ ${dynamicCol({ containerSelector: 'map-engine-container', id: dcMapFields, type: 'search-inputs' })}
1292
+ <div class="fl">
1293
+ <div class="in fll ${dcMapFields}-col-a">
1294
+ ${await Input.instance({
1295
+ id: idCode,
1296
+ label: html`Code`,
1297
+ containerClass: 'inl',
1298
+ type: 'text',
1299
+ })}
1300
+ </div>
1301
+ <div class="in fll ${dcMapFields}-col-b">
1302
+ ${await Input.instance({
1303
+ id: idName,
1304
+ label: html`Name`,
1305
+ containerClass: 'inl',
1306
+ type: 'text',
1307
+ })}
1308
+ </div>
1309
+ <div class="in fll ${dcMapFields}-col-c">
1310
+ ${await Input.instance({
1311
+ id: idDescription,
1312
+ label: html`Description`,
1313
+ containerClass: 'inl',
1314
+ type: 'text',
1315
+ })}
1316
+ </div>
1317
+ </div>
1318
+ ${dynamicCol({ containerSelector: 'map-engine-container', id: dcMetaFields, type: 'search-inputs' })}
1319
+ <div class="fl">
1320
+ <div class="in fll ${dcMetaFields}-col-a">
1321
+ ${await Input.instance({
1322
+ id: idTags,
1323
+ label: html`Tags (comma separated)`,
1324
+ containerClass: 'inl',
1325
+ type: 'text',
1326
+ })}
1327
+ </div>
1328
+ <div class="in fll ${dcMetaFields}-col-b">
1329
+ ${await DropDown.instance({
1330
+ id: idStatus,
1331
+ label: html`Status`,
1332
+ data: statusOptions.map((opt) => ({ ...opt })),
1333
+ value: 'unlisted',
1334
+ containerClass: 'inl',
1335
+ })}
1336
+ </div>
1337
+ <div class="in fll ${dcMetaFields}-col-c">
1338
+ <div class="inl">
1339
+ <div class="in input-label">Creator</div>
1340
+ <div class="in map-engine-creator-display">
1341
+ <span style="color:#888;font-size:12px;">—</span>
1342
+ </div>
1343
+ </div>
1344
+ </div>
1345
+ </div>
1346
+ <div class="in section-mp" style="margin-top: 5px;">
1347
+ <div class="in map-engine-thumbnail-preview" style="margin-bottom: 5px;"></div>
1348
+ ${await BtnIcon.instance({
1349
+ class: 'wfa btn-map-engine-capture-thumbnail',
1350
+ label: html`<i class="fa-solid fa-camera"></i> Capture Thumbnail`,
1351
+ })}
1352
+ <div class="fl" style="align-items: center; gap: 8px; font-size: 20px; text-align: left; margin: 5px 0;">
1353
+ ${await ToggleSwitch.instance({
1354
+ id: 'map-engine-capture-obj-layer-thumb',
1355
+ type: 'checkbox',
1356
+ displayMode: 'checkbox',
1357
+ containerClass: 'in fll',
1358
+ checked: true,
1359
+ on: {
1360
+ checked: () => {
1361
+ MapEngineCyberia.captureObjLayerThumbnail = true;
1362
+ },
1363
+ unchecked: () => {
1364
+ MapEngineCyberia.captureObjLayerThumbnail = false;
1365
+ },
1366
+ },
1367
+ })}
1368
+ <div class="section-mp">&nbsp &nbsp Capture Object Layer Map Thumbnail on Save/Update/Clone</div>
1369
+ </div>
1370
+ ${await BtnIcon.instance({
1371
+ class: 'wfa btn-map-engine-toggle-thumbnail',
1372
+ label: html`<i class="fa-solid fa-caret-right map-engine-thumbnail-caret"></i> Thumbnail`,
1373
+ })}
1374
+ <div class="in map-engine-thumbnail-body hide">
1375
+ ${await InputFile.instance(
1376
+ {
1377
+ id: idThumbnail,
1378
+ multiple: false,
1379
+ extensionsAccept: ['image/png', 'image/jpeg'],
1380
+ },
1381
+ {
1382
+ change: (e) => {
1383
+ const file = e.target.files[0];
1384
+ if (file) {
1385
+ MapEngineCyberia.thumbnailDirty = true;
1386
+ const url = URL.createObjectURL(file);
1387
+ const preview = s('.map-engine-thumbnail-preview');
1388
+ if (preview)
1389
+ preview.innerHTML = html`<img
1390
+ src="${url}"
1391
+ class="in"
1392
+ style="max-width:300px;height:auto;border:1px solid #555;margin:auto"
1393
+ />`;
1394
+ }
1395
+ },
1396
+ clear: () => {
1397
+ MapEngineCyberia.thumbnailDirty = true;
1398
+ MapEngineCyberia.currentThumbnailId = null;
1399
+ const preview = s('.map-engine-thumbnail-preview');
1400
+ if (preview) preview.innerHTML = '';
1401
+ },
1402
+ },
1403
+ )}
1404
+ </div>
1405
+ </div>
1406
+ ${dynamicCol({ containerSelector: 'map-engine-container', id: dcGridSize, type: 'a-50-b-50' })}
1407
+ <div class="fl">
1408
+ <div class="in fll ${dcGridSize}-col-a">
1409
+ ${await Input.instance({
1410
+ id: idX,
1411
+ label: html`X`,
1412
+ containerClass: 'inl',
1413
+ type: 'number',
1414
+ min: 1,
1415
+ value: 16,
1416
+ })}
1417
+ </div>
1418
+ <div class="in fll ${dcGridSize}-col-b">
1419
+ ${await Input.instance({
1420
+ id: idY,
1421
+ label: html`Y`,
1422
+ containerClass: 'inl',
1423
+ type: 'number',
1424
+ min: 1,
1425
+ value: 16,
1426
+ })}
1427
+ </div>
1428
+ </div>
1429
+ ${dynamicCol({ containerSelector: 'map-engine-container', id: dcCellSize, type: 'a-50-b-50' })}
1430
+ <div class="fl">
1431
+ <div class="in fll ${dcCellSize}-col-a">
1432
+ ${await Input.instance({
1433
+ id: idCellW,
1434
+ label: html`Cell Width (px)`,
1435
+ containerClass: 'inl',
1436
+ type: 'number',
1437
+ min: 1,
1438
+ value: 32,
1439
+ })}
1440
+ </div>
1441
+ <div class="in fll ${dcCellSize}-col-b">
1442
+ ${await Input.instance({
1443
+ id: idCellH,
1444
+ label: html`Cell Height (px)`,
1445
+ containerClass: 'inl',
1446
+ type: 'number',
1447
+ min: 1,
1448
+ value: 32,
1449
+ })}
1450
+ </div>
1451
+ </div>
1452
+ <div class="fl">
1453
+ <div class="in wfa" style="padding: 10px; max-width: 200px; margin: auto;">
1454
+ ${await BtnIcon.instance({
1455
+ class: 'wfa btn-map-engine-generate',
1456
+ label: html`<i class="fa-solid fa-arrows-rotate"></i> Generate`,
1457
+ })}
1458
+ </div>
1459
+ </div>
1460
+ <div class="in" style="text-align: center; margin-top: 10px;">
1461
+ ${dynamicCol({ containerSelector: 'map-engine-container', id: dcCanvasOpts, type: 'search-inputs' })}
1462
+ <div class="fl" style="margin-bottom: 5px;">
1463
+ <div class="in fll ${dcCanvasOpts}-col-a">
1464
+ <div class="fl" style="align-items: center; gap: 8px; font-size: 20px; text-align: left;">
1465
+ ${await ToggleSwitch.instance({
1466
+ id: 'map-engine-show-grid',
1467
+ type: 'checkbox',
1468
+ displayMode: 'checkbox',
1469
+ containerClass: 'in fll',
1470
+ checked: true,
1471
+ on: {
1472
+ checked: () => {
1473
+ MapEngineCyberia.showGridBorders = true;
1474
+ rerenderCanvas();
1475
+ },
1476
+ unchecked: () => {
1477
+ MapEngineCyberia.showGridBorders = false;
1478
+ rerenderCanvas();
1479
+ },
1480
+ },
1481
+ })}
1482
+ <div class="section-mp">&nbsp &nbsp Show Grid</div>
1483
+ </div>
1484
+ </div>
1485
+ <div class="in fll ${dcCanvasOpts}-col-b">
1486
+ <div class="fl" style="align-items: center; gap: 8px; font-size: 20px; text-align: left;">
1487
+ ${await ToggleSwitch.instance({
1488
+ id: 'map-engine-add-on-click',
1489
+ type: 'checkbox',
1490
+ displayMode: 'checkbox',
1491
+ containerClass: 'in fll',
1492
+ checked: true,
1493
+ on: {
1494
+ checked: () => {
1495
+ MapEngineCyberia.addOnClick = true;
1496
+ },
1497
+ unchecked: () => {
1498
+ MapEngineCyberia.addOnClick = false;
1499
+ },
1500
+ },
1501
+ })}
1502
+ <div class="section-mp">&nbsp &nbsp Add on Click</div>
1503
+ </div>
1504
+ </div>
1505
+ <div class="in fll ${dcCanvasOpts}-col-c">
1506
+ <div class="fl" style="align-items: center; gap: 8px; font-size: 20px; text-align: left;">
1507
+ ${await ToggleSwitch.instance({
1508
+ id: 'map-engine-show-object-layers',
1509
+ type: 'checkbox',
1510
+ displayMode: 'checkbox',
1511
+ containerClass: 'in fll',
1512
+ checked: false,
1513
+ on: {
1514
+ checked: () => {
1515
+ MapEngineCyberia.showObjectLayers = true;
1516
+ rerenderCanvas();
1517
+ },
1518
+ unchecked: () => {
1519
+ MapEngineCyberia.showObjectLayers = false;
1520
+ rerenderCanvas();
1521
+ },
1522
+ },
1523
+ })}
1524
+ <div class="section-mp">&nbsp &nbsp Object Layers</div>
1525
+ </div>
1526
+ </div>
1527
+ </div>
1528
+ <canvas class="${canvasId}" width="512" height="512" style="border: 1px solid #555;"></canvas>
1529
+ <div class="in map-engine-cell-coords" style="font-family:monospace;font-size:13px;color:#888;margin-top:4px;">
1530
+ Cell: (0, 0)
1531
+ </div>
1532
+ </div>
1533
+ <div class="in section-mp" style="margin-top: 10px;">
1534
+ ${dynamicCol({ containerSelector: 'map-engine-container', id: dcEntityType, type: 'a-50-b-50' })}
1535
+ <div class="fl">
1536
+ <div class="in fll ${dcEntityType}-col-a">
1537
+ ${await DropDown.instance({
1538
+ id: idEntityType,
1539
+ label: html`Entity Type`,
1540
+ data: MapEngineCyberia.getEntityTypeDropdownOptions(),
1541
+ value: DEFAULT_ENTITY_TYPE,
1542
+ containerClass: 'inl',
1543
+ })}
1544
+ </div>
1545
+ <div class="in fll ${dcEntityType}-col-b">
1546
+ ${await Input.instance({
1547
+ id: idColor,
1548
+ label: html`Color`,
1549
+ containerClass: 'inl',
1550
+ type: 'color',
1551
+ value: '#ff0000',
1552
+ })}
1553
+ </div>
1554
+ </div>
1555
+ ${dynamicCol({ containerSelector: 'map-engine-container', id: dcAlpha, type: 'a-50-b-50' })}
1556
+ <div class="fl">
1557
+ <div class="in fll ${dcAlpha}-col-a">
1558
+ <div class="inl input-container-${idAlpha}">
1559
+ <div class="in">
1560
+ <div class="in input-label">Alpha</div>
1561
+ <label for="${idAlpha}-name">
1562
+ <span class="hide">Alpha</span>
1563
+ <input
1564
+ type="range"
1565
+ class="in wfa ${idAlpha}"
1566
+ min="0"
1567
+ max="1"
1568
+ step="0.01"
1569
+ value="1"
1570
+ name="${idAlpha}-name"
1571
+ id="${idAlpha}-name"
1572
+ />
1573
+ </label>
1574
+ </div>
1575
+ </div>
1576
+ </div>
1577
+ <div class="in fll ${dcAlpha}-col-b" style="line-height: 40px;">
1578
+ <div class="in input-label">RGBA</div>
1579
+ <div class="in ${rgbaDisplayId}" style="font-family: monospace; font-size: 13px;"></div>
1580
+ </div>
1581
+ </div>
1582
+ <div class="in section-mp-border" style="margin-top: 10px;">
1583
+ <div class="in input-label">Color Palette</div>
1584
+ <color-palette class="${idColorPalette}" value="#FF0000"></color-palette>
1585
+ </div>
1586
+ ${dynamicCol({ containerSelector: 'map-engine-container', id: dcCellPos, type: 'a-50-b-50' })}
1587
+ <div class="fl">
1588
+ <div class="in fll ${dcCellPos}-col-a">
1589
+ ${await Input.instance({
1590
+ id: idInitCellX,
1591
+ label: html`initCellX`,
1592
+ containerClass: 'inl',
1593
+ type: 'number',
1594
+ min: 0,
1595
+ value: 0,
1596
+ })}
1597
+ </div>
1598
+ <div class="in fll ${dcCellPos}-col-b">
1599
+ ${await Input.instance({
1600
+ id: idInitCellY,
1601
+ label: html`initCellY`,
1602
+ containerClass: 'inl',
1603
+ type: 'number',
1604
+ min: 0,
1605
+ value: 0,
1606
+ })}
1607
+ </div>
1608
+ </div>
1609
+ ${dynamicCol({ containerSelector: 'map-engine-container', id: dcDim, type: 'a-50-b-50' })}
1610
+ <div class="fl">
1611
+ <div class="in fll ${dcDim}-col-a">
1612
+ ${await Input.instance({
1613
+ id: idDimX,
1614
+ label: html`dimX`,
1615
+ containerClass: 'inl',
1616
+ type: 'number',
1617
+ min: 1,
1618
+ value: 1,
1619
+ })}
1620
+ </div>
1621
+ <div class="in fll ${dcDim}-col-b">
1622
+ ${await Input.instance({
1623
+ id: idDimY,
1624
+ label: html`dimY`,
1625
+ containerClass: 'inl',
1626
+ type: 'number',
1627
+ min: 1,
1628
+ value: 1,
1629
+ })}
1630
+ </div>
1631
+ </div>
1632
+ ${dynamicCol({ containerSelector: 'map-engine-container', id: dcFactors, type: 'a-50-b-50' })}
1633
+ <div class="fl">
1634
+ <div class="in fll ${dcFactors}-col-a">
1635
+ ${await Input.instance({
1636
+ id: idFactorA,
1637
+ label: html`factorA`,
1638
+ containerClass: 'inl',
1639
+ type: 'number',
1640
+ step: 0.01,
1641
+ value: 0.5,
1642
+ })}
1643
+ </div>
1644
+ <div class="in fll ${dcFactors}-col-b">
1645
+ ${await Input.instance({
1646
+ id: idFactorB,
1647
+ label: html`factorB`,
1648
+ containerClass: 'inl',
1649
+ type: 'number',
1650
+ step: 0.01,
1651
+ value: 1.5,
1652
+ })}
1653
+ </div>
1654
+ </div>
1655
+ ${await Input.instance({
1656
+ id: idVariationPreserve,
1657
+ label: html`Variation Preserve List`,
1658
+ containerClass: 'inl',
1659
+ type: 'text',
1660
+ placeholder: true,
1661
+ })}
1662
+ <div class="fl" style="align-items: center; gap: 8px; font-size: 20px; text-align: left; margin: 5px 0;">
1663
+ ${await ToggleSwitch.instance({
1664
+ id: 'map-engine-random-dim',
1665
+ type: 'checkbox',
1666
+ displayMode: 'checkbox',
1667
+ containerClass: 'in fll',
1668
+ checked: false,
1669
+ on: {
1670
+ checked: () => {
1671
+ MapEngineCyberia.enableRandomFactors = true;
1672
+ },
1673
+ unchecked: () => {
1674
+ MapEngineCyberia.enableRandomFactors = false;
1675
+ },
1676
+ },
1677
+ })}
1678
+ <div class="section-mp">&nbsp &nbsp Enable Random Factors</div>
1679
+ </div>
1680
+ <div class="in" style="margin: 10px;">
1681
+ <div class="${idObjLayerDropdownHost}">${await MapEngineCyberia.buildObjectLayerDropdown()}</div>
1682
+ </div>
1683
+ <div class="in section-mp-border" style="margin: 10px; padding: 10px;">
1684
+ <div class="in input-label">Rename Object Layer ItemId on Filtered Entities</div>
1685
+ <div class="in" style="font-size:12px;color:#888;margin-bottom:8px;">
1686
+ Replaces only exact source ItemId matches inside entities currently visible through the filters.
1687
+ </div>
1688
+ <div class="fl">
1689
+ <div class="in fll" style="flex:1;padding-right:5px;">
1690
+ ${await Input.instance({
1691
+ id: idRenameSourceObjectLayer,
1692
+ label: html`Source ItemId`,
1693
+ containerClass: 'inl',
1694
+ type: 'text',
1695
+ placeholder: true,
1696
+ })}
1697
+ </div>
1698
+ <div class="in fll" style="flex:1;padding-left:5px;">
1699
+ ${await Input.instance({
1700
+ id: idRenameTargetObjectLayer,
1701
+ label: html`Target ItemId`,
1702
+ containerClass: 'inl',
1703
+ type: 'text',
1704
+ placeholder: true,
1705
+ })}
1706
+ </div>
1707
+ </div>
1708
+ <div class="in" style="margin-top: 5px;">
1709
+ ${await BtnIcon.instance({
1710
+ class: 'wfa btn-map-engine-rename-filtered-object-layer-item-id',
1711
+ label: html`<i class="fa-solid fa-arrow-right-arrow-left"></i> Rename Filtered ItemId`,
1712
+ })}
1713
+ </div>
1714
+ </div>
1715
+ <div class="in">
1716
+ ${await BtnIcon.instance({
1717
+ class: 'wfa btn-map-engine-add-entity',
1718
+ label: html`<i class="fa-solid fa-plus"></i> Add Entity`,
1719
+ })}
1720
+ </div>
1721
+ <div class="in" style="margin-top: 5px;">
1722
+ ${await BtnIcon.instance({
1723
+ class: 'wfa btn-map-engine-fill-map',
1724
+ label: html`<i class="fa-solid fa-fill-drip"></i> Map Fill`,
1725
+ })}
1726
+ </div>
1727
+ <div class="in" style="margin-top: 5px;">
1728
+ ${await BtnIcon.instance({
1729
+ class: 'wfa btn-map-engine-generate-variation',
1730
+ label: html`<i class="fa-solid fa-shuffle"></i> Generate Variation`,
1731
+ })}
1732
+ </div>
1733
+ <div class="in" style="margin-top: 5px;">
1734
+ ${await BtnIcon.instance({
1735
+ class: 'wfa btn-map-engine-swap-preserve-entities',
1736
+ label: html`<i class="fa-solid fa-arrows-rotate"></i> Swap Preserve Positions`,
1737
+ })}
1738
+ </div>
1739
+ <div class="in" style="margin-top: 5px;">
1740
+ ${await BtnIcon.instance({
1741
+ class: 'wfa btn-map-engine-replace-preserve-entities',
1742
+ label: html`<i class="fa-solid fa-wand-magic-sparkles"></i> Replace Preserve Entities`,
1743
+ })}
1744
+ </div>
1745
+ <div class="in" style="margin-top: 5px;">
1746
+ ${await BtnIcon.instance({
1747
+ class: 'wfa btn-map-engine-flip-horizontal',
1748
+ label: html`<i class="fa-solid fa-arrows-left-right"></i> Flip Horizontal`,
1749
+ })}
1750
+ </div>
1751
+ <div class="in" style="margin-top: 5px;">
1752
+ ${await BtnIcon.instance({
1753
+ class: 'wfa btn-map-engine-flip-vertical',
1754
+ label: html`<i class="fa-solid fa-arrows-up-down"></i> Flip Vertical`,
1755
+ })}
1756
+ </div>
1757
+ <div class="in" style="margin-top: 10px;">
1758
+ ${await BtnIcon.instance({
1759
+ class: 'wfa btn-map-engine-toggle-entity-filter',
1760
+ label: html`<i class="fa-solid fa-caret-right map-engine-entity-filter-caret"></i> Filters`,
1761
+ })}
1762
+ <div class="in map-engine-entity-filter-body hide">
1763
+ ${dynamicCol({ containerSelector: 'map-engine-container', id: dcEntityFilter, type: 'search-inputs' })}
1764
+ <div class="fl">
1765
+ <div class="in fll ${dcEntityFilter}-col-a">
1766
+ ${await Input.instance({
1767
+ id: idFilterEntityType,
1768
+ label: html`Entity Type`,
1769
+ containerClass: 'inl',
1770
+ type: 'text',
1771
+ placeholder: true,
1772
+ })}
1773
+ </div>
1774
+ <div class="in fll ${dcEntityFilter}-col-b">
1775
+ ${await Input.instance({
1776
+ id: idFilterInitX,
1777
+ label: html`initCellX`,
1778
+ containerClass: 'inl',
1779
+ type: 'text',
1780
+ placeholder: true,
1781
+ })}
1782
+ </div>
1783
+ <div class="in fll ${dcEntityFilter}-col-c">
1784
+ ${await Input.instance({
1785
+ id: idFilterInitY,
1786
+ label: html`initCellY`,
1787
+ containerClass: 'inl',
1788
+ type: 'text',
1789
+ placeholder: true,
1790
+ })}
1791
+ </div>
1792
+ </div>
1793
+ <div
1794
+ class="in map-engine-entity-filter-count"
1795
+ style="margin-top:5px;font-size:12px;color:#888;font-family:monospace;"
1796
+ >
1797
+ Showing 0 of 0 entities
1798
+ </div>
1799
+ <div class="in" style="margin-top:5px;">
1800
+ ${await BtnIcon.instance({
1801
+ class: 'wfa btn-map-engine-clear-entity-filter',
1802
+ label: html`<i class="fa-solid fa-broom"></i> Clear Filters`,
1803
+ })}
1804
+ </div>
1805
+ <div class="in" style="margin-top:5px;">
1806
+ ${await BtnIcon.instance({
1807
+ class: 'wfa btn-map-engine-clear-all-entities',
1808
+ label: html`<i class="fa-solid fa-trash-can"></i> Delete All Entities`,
1809
+ })}
1810
+ </div>
1811
+ </div>
1812
+ </div>
1813
+ <div class="in ${entityListId}" style="margin-top: 10px; max-height: 200px; overflow-y: auto;"></div>
1814
+ ${dynamicCol({ containerSelector: 'map-engine-container', id: dcSaveNew, type: 'search-inputs' })}
1815
+ <div class="fl" style="margin-top: 10px;">
1816
+ <div class="in fll ${dcSaveNew}-col-a" style="padding: 5px;">
1817
+ ${await BtnIcon.instance({
1818
+ class: 'wfa btn-map-engine-save-map',
1819
+ label: html`<i class="fa-solid fa-floppy-disk"></i> Save Map`,
1820
+ })}
1821
+ </div>
1822
+ <div class="in fll ${dcSaveNew}-col-b" style="padding: 5px;">
1823
+ ${await BtnIcon.instance({
1824
+ class: 'wfa btn-map-engine-clone-map',
1825
+ label: html`<i class="fa-solid fa-clone"></i> Clone Map`,
1826
+ })}
1827
+ </div>
1828
+ <div class="in fll ${dcSaveNew}-col-c" style="padding: 5px;">
1829
+ ${await BtnIcon.instance({
1830
+ class: 'wfa btn-map-engine-new-map',
1831
+ label: html`<i class="fa-solid fa-file"></i> New Map`,
1832
+ })}
1833
+ </div>
1834
+ </div>
1835
+ <div class="in" style="margin-top: 10px;">${managementTableHtml}</div>
1836
+ </div>
1837
+ </div>`;
4
1838
  }
5
1839
  }
6
1840