cyberia 3.2.5 → 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 (301) hide show
  1. package/.github/workflows/engine-cyberia.cd.yml +2 -2
  2. package/.github/workflows/release.cd.yml +1 -2
  3. package/CHANGELOG.md +351 -1
  4. package/CLI-HELP.md +40 -13
  5. package/Dockerfile +0 -4
  6. package/README.md +242 -497
  7. package/bin/build.js +19 -5
  8. package/bin/cyberia.js +1149 -240
  9. package/bin/deploy.js +570 -1
  10. package/bin/file.js +6 -0
  11. package/bin/index.js +1149 -240
  12. package/bin/vs.js +1 -1
  13. package/conf.js +67 -89
  14. package/deployment.yaml +4 -222
  15. package/hardhat/package-lock.json +32 -32
  16. package/hardhat/package.json +3 -3
  17. package/jsconfig.json +1 -1
  18. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +2 -2
  19. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +2 -2
  20. package/manifests/deployment/dd-cyberia-development/deployment.yaml +4 -222
  21. package/manifests/deployment/dd-cyberia-development/proxy.yaml +10 -118
  22. package/manifests/deployment/dd-default-development/deployment.yaml +2 -6
  23. package/manifests/deployment/dd-test-development/deployment.yaml +136 -66
  24. package/manifests/deployment/dd-test-development/proxy.yaml +41 -5
  25. package/package.json +23 -14
  26. package/proxy.yaml +10 -118
  27. package/scripts/k3s-node-setup.sh +2 -2
  28. package/scripts/nat-iptables.sh +103 -18
  29. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +18 -18
  30. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +7 -14
  31. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +76 -21
  32. package/src/api/core/core.controller.js +10 -10
  33. package/src/api/core/core.service.js +10 -10
  34. package/src/api/crypto/crypto.controller.js +8 -8
  35. package/src/api/crypto/crypto.service.js +8 -8
  36. package/src/api/cyberia-action/cyberia-action.controller.js +74 -0
  37. package/src/api/cyberia-action/cyberia-action.model.js +87 -0
  38. package/src/api/cyberia-action/cyberia-action.router.js +27 -0
  39. package/src/api/cyberia-action/cyberia-action.service.js +42 -0
  40. package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +13 -13
  41. package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +11 -11
  42. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +2 -2
  43. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +16 -16
  44. package/src/api/cyberia-entity/cyberia-entity.controller.js +10 -10
  45. package/src/api/cyberia-entity/cyberia-entity.service.js +10 -10
  46. package/src/api/cyberia-instance/cyberia-fallback-world.js +19 -209
  47. package/src/api/cyberia-instance/cyberia-instance.controller.js +14 -14
  48. package/src/api/cyberia-instance/cyberia-instance.model.js +3 -0
  49. package/src/api/cyberia-instance/cyberia-instance.service.js +22 -57
  50. package/src/api/cyberia-instance/cyberia-portal-connector.js +20 -246
  51. package/src/api/cyberia-instance/cyberia-world-generator.js +505 -0
  52. package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +10 -10
  53. package/src/api/cyberia-instance-conf/cyberia-instance-conf.defaults.js +216 -55
  54. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +4 -1
  55. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +18 -14
  56. package/src/api/cyberia-map/cyberia-map.controller.js +10 -10
  57. package/src/api/cyberia-map/cyberia-map.service.js +10 -10
  58. package/src/api/cyberia-quest/cyberia-quest.controller.js +74 -0
  59. package/src/api/cyberia-quest/cyberia-quest.model.js +67 -0
  60. package/src/api/cyberia-quest/cyberia-quest.router.js +27 -0
  61. package/src/api/cyberia-quest/cyberia-quest.service.js +42 -0
  62. package/src/api/cyberia-quest-progress/cyberia-quest-progress.controller.js +74 -0
  63. package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +49 -0
  64. package/src/api/cyberia-quest-progress/cyberia-quest-progress.router.js +27 -0
  65. package/src/api/cyberia-quest-progress/cyberia-quest-progress.service.js +42 -0
  66. package/src/api/default/default.controller.js +10 -10
  67. package/src/api/default/default.service.js +10 -10
  68. package/src/api/document/document.controller.js +12 -12
  69. package/src/api/document/document.model.js +10 -16
  70. package/src/api/file/file.controller.js +8 -8
  71. package/src/api/file/file.model.js +10 -10
  72. package/src/api/file/file.service.js +36 -36
  73. package/src/api/instance/instance.controller.js +10 -10
  74. package/src/api/instance/instance.model.js +4 -10
  75. package/src/api/instance/instance.service.js +10 -10
  76. package/src/api/ipfs/ipfs.controller.js +12 -12
  77. package/src/api/ipfs/ipfs.model.js +4 -13
  78. package/src/api/ipfs/ipfs.service.js +14 -28
  79. package/src/api/object-layer/object-layer.controller.js +12 -12
  80. package/src/api/object-layer/object-layer.model.js +4 -17
  81. package/src/api/object-layer/object-layer.service.js +12 -12
  82. package/src/api/object-layer-render-frames/object-layer-render-frames.controller.js +10 -10
  83. package/src/api/object-layer-render-frames/object-layer-render-frames.model.js +6 -16
  84. package/src/api/object-layer-render-frames/object-layer-render-frames.service.js +18 -14
  85. package/src/api/test/test.controller.js +8 -8
  86. package/src/api/test/test.service.js +8 -8
  87. package/src/api/user/guest.service.js +99 -0
  88. package/src/api/user/user.controller.js +6 -6
  89. package/src/api/user/user.model.js +8 -13
  90. package/src/api/user/user.service.js +3 -20
  91. package/src/cli/cluster.js +61 -14
  92. package/src/cli/db.js +47 -2
  93. package/src/cli/deploy.js +67 -35
  94. package/src/cli/fs.js +79 -8
  95. package/src/cli/image.js +43 -1
  96. package/src/cli/index.js +26 -1
  97. package/src/cli/release.js +57 -1
  98. package/src/cli/repository.js +69 -31
  99. package/src/cli/run.js +415 -36
  100. package/src/cli/ssh.js +1 -1
  101. package/src/cli/static.js +43 -115
  102. package/src/client/Cryptokoyn.index.js +18 -21
  103. package/src/client/CyberiaPortal.index.js +19 -23
  104. package/src/client/Default.index.js +21 -33
  105. package/src/client/Itemledger.index.js +20 -26
  106. package/src/client/Underpost.index.js +19 -23
  107. package/src/client/components/core/404.js +4 -4
  108. package/src/client/components/core/500.js +4 -4
  109. package/src/client/components/core/Account.js +73 -60
  110. package/src/client/components/core/AgGrid.js +23 -33
  111. package/src/client/components/core/Alert.js +12 -13
  112. package/src/client/components/core/AppStore.js +1 -1
  113. package/src/client/components/core/Auth.js +35 -37
  114. package/src/client/components/core/Badge.js +7 -13
  115. package/src/client/components/core/BtnIcon.js +15 -17
  116. package/src/client/components/core/CalendarCore.js +42 -63
  117. package/src/client/components/core/Chat.js +13 -15
  118. package/src/client/components/core/ClientEvents.js +87 -0
  119. package/src/client/components/core/ColorPaletteElement.js +309 -0
  120. package/src/client/components/core/Content.js +17 -14
  121. package/src/client/components/core/Css.js +15 -71
  122. package/src/client/components/core/CssCore.js +12 -16
  123. package/src/client/components/core/D3Chart.js +4 -4
  124. package/src/client/components/core/Docs.js +64 -91
  125. package/src/client/components/core/DropDown.js +69 -91
  126. package/src/client/components/core/EventBus.js +92 -0
  127. package/src/client/components/core/EventsUI.js +14 -17
  128. package/src/client/components/core/FileExplorer.js +96 -228
  129. package/src/client/components/core/FullScreen.js +47 -75
  130. package/src/client/components/core/Input.js +24 -69
  131. package/src/client/components/core/Keyboard.js +25 -18
  132. package/src/client/components/core/KeyboardAvoidance.js +145 -0
  133. package/src/client/components/core/LoadingAnimation.js +25 -31
  134. package/src/client/components/core/LogIn.js +41 -41
  135. package/src/client/components/core/LogOut.js +23 -14
  136. package/src/client/components/core/Modal.js +462 -178
  137. package/src/client/components/core/NotificationManager.js +14 -18
  138. package/src/client/components/core/Panel.js +54 -50
  139. package/src/client/components/core/PanelForm.js +25 -125
  140. package/src/client/components/core/Polyhedron.js +110 -214
  141. package/src/client/components/core/PublicProfile.js +39 -32
  142. package/src/client/components/core/Recover.js +48 -44
  143. package/src/client/components/core/Responsive.js +88 -32
  144. package/src/client/components/core/RichText.js +9 -18
  145. package/src/client/components/core/Router.js +24 -3
  146. package/src/client/components/core/SearchBox.js +37 -37
  147. package/src/client/components/core/SignUp.js +39 -30
  148. package/src/client/components/core/SocketIo.js +31 -2
  149. package/src/client/components/core/SocketIoHandler.js +6 -6
  150. package/src/client/components/core/ToggleSwitch.js +8 -20
  151. package/src/client/components/core/ToolTip.js +5 -17
  152. package/src/client/components/core/Translate.js +56 -59
  153. package/src/client/components/core/Validator.js +26 -16
  154. package/src/client/components/core/Wallet.js +15 -26
  155. package/src/client/components/core/Worker.js +163 -27
  156. package/src/client/components/core/windowGetDimensions.js +7 -7
  157. package/src/client/components/cryptokoyn/{MenuCryptokoyn.js → AppShellCryptokoyn.js} +57 -57
  158. package/src/client/components/cryptokoyn/CssCryptokoyn.js +15 -15
  159. package/src/client/components/cryptokoyn/LogInCryptokoyn.js +6 -4
  160. package/src/client/components/cryptokoyn/LogOutCryptokoyn.js +6 -4
  161. package/src/client/components/cryptokoyn/RouterCryptokoyn.js +37 -0
  162. package/src/client/components/cryptokoyn/SettingsCryptokoyn.js +4 -4
  163. package/src/client/components/cryptokoyn/SignUpCryptokoyn.js +6 -4
  164. package/src/client/components/cyberia/InstanceEngineCyberia.js +141 -60
  165. package/src/client/components/cyberia/MapEngineCyberia.js +691 -214
  166. package/src/client/components/cyberia/ObjectLayerEngine.js +19 -0
  167. package/src/client/components/cyberia/ObjectLayerEngineModal.js +1204 -94
  168. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +196 -298
  169. package/src/client/components/cyberia-portal/{MenuCyberiaPortal.js → AppShellCyberiaPortal.js} +102 -102
  170. package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +305 -61
  171. package/src/client/components/cyberia-portal/CssCyberiaPortal.js +15 -15
  172. package/src/client/components/cyberia-portal/LogInCyberiaPortal.js +6 -4
  173. package/src/client/components/cyberia-portal/LogOutCyberiaPortal.js +6 -4
  174. package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +4 -4
  175. package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +60 -0
  176. package/src/client/components/cyberia-portal/SettingsCyberiaPortal.js +4 -4
  177. package/src/client/components/cyberia-portal/SignUpCyberiaPortal.js +6 -4
  178. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +4 -4
  179. package/src/client/components/default/{MenuDefault.js → AppShellDefault.js} +87 -87
  180. package/src/client/components/default/CssDefault.js +12 -12
  181. package/src/client/components/default/LogInDefault.js +6 -4
  182. package/src/client/components/default/LogOutDefault.js +6 -4
  183. package/src/client/components/default/RouterDefault.js +47 -0
  184. package/src/client/components/default/SettingsDefault.js +4 -4
  185. package/src/client/components/default/SignUpDefault.js +6 -4
  186. package/src/client/components/default/TranslateDefault.js +3 -3
  187. package/src/client/components/itemledger/{MenuItemledger.js → AppShellItemledger.js} +57 -57
  188. package/src/client/components/itemledger/CssItemledger.js +15 -15
  189. package/src/client/components/itemledger/LogInItemledger.js +6 -4
  190. package/src/client/components/itemledger/LogOutItemledger.js +6 -4
  191. package/src/client/components/itemledger/RouterItemledger.js +38 -0
  192. package/src/client/components/itemledger/SettingsItemledger.js +4 -4
  193. package/src/client/components/itemledger/SignUpItemledger.js +6 -4
  194. package/src/client/components/itemledger/TranslateItemledger.js +3 -3
  195. package/src/client/components/underpost/{MenuUnderpost.js → AppShellUnderpost.js} +88 -88
  196. package/src/client/components/underpost/CssUnderpost.js +14 -14
  197. package/src/client/components/underpost/CyberpunkBloggerUnderpost.js +4 -4
  198. package/src/client/components/underpost/DocumentSearchProvider.js +1 -1
  199. package/src/client/components/underpost/LabGalleryUnderpost.js +12 -15
  200. package/src/client/components/underpost/LogInUnderpost.js +6 -4
  201. package/src/client/components/underpost/LogOutUnderpost.js +6 -4
  202. package/src/client/components/underpost/RouterUnderpost.js +45 -0
  203. package/src/client/components/underpost/SettingsUnderpost.js +4 -4
  204. package/src/client/components/underpost/SignUpUnderpost.js +6 -4
  205. package/src/client/components/underpost/TranslateUnderpost.js +4 -4
  206. package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +235 -0
  207. package/src/client/public/cyberia-docs/ARCHITECTURE.md +443 -0
  208. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +417 -0
  209. package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +313 -0
  210. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +260 -0
  211. package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +241 -0
  212. package/src/client/public/cyberia-docs/HARDHAT-MODULE.md +300 -0
  213. package/src/client/public/cyberia-docs/OFF-CHAIN-ECONOMY.md +279 -0
  214. package/src/client/public/cyberia-docs/QUEST-SYSTEM.md +206 -0
  215. package/src/client/public/cyberia-docs/ROADMAP.md +240 -0
  216. package/src/client/public/cyberia-docs/WHITE-PAPER.md +732 -0
  217. package/src/client/services/atlas-sprite-sheet/atlas-sprite-sheet.service.js +14 -20
  218. package/src/client/services/core/core.service.js +17 -49
  219. package/src/client/services/crypto/crypto.service.js +8 -13
  220. package/src/client/services/cyberia-action/cyberia-action.service.js +99 -0
  221. package/src/client/services/cyberia-dialogue/cyberia-dialogue.service.js +10 -16
  222. package/src/client/services/cyberia-entity/cyberia-entity.management.js +5 -5
  223. package/src/client/services/cyberia-entity/cyberia-entity.service.js +10 -16
  224. package/src/client/services/cyberia-instance/cyberia-instance.management.js +6 -6
  225. package/src/client/services/cyberia-instance/cyberia-instance.service.js +12 -18
  226. package/src/client/services/cyberia-instance-conf/cyberia-instance-conf.service.js +10 -16
  227. package/src/client/services/cyberia-map/cyberia-map.management.js +6 -6
  228. package/src/client/services/cyberia-map/cyberia-map.service.js +12 -18
  229. package/src/client/services/cyberia-quest/cyberia-quest.service.js +99 -0
  230. package/src/client/services/cyberia-quest-progress/cyberia-quest-progress.service.js +99 -0
  231. package/src/client/services/default/default.management.js +159 -267
  232. package/src/client/services/default/default.service.js +10 -16
  233. package/src/client/services/document/document.service.js +14 -19
  234. package/src/client/services/file/file.service.js +8 -13
  235. package/src/client/services/instance/instance.management.js +5 -5
  236. package/src/client/services/instance/instance.service.js +10 -15
  237. package/src/client/services/ipfs/ipfs.service.js +12 -18
  238. package/src/client/services/object-layer/object-layer.management.js +12 -12
  239. package/src/client/services/object-layer/object-layer.service.js +20 -26
  240. package/src/client/services/object-layer-render-frames/object-layer-render-frames.service.js +10 -16
  241. package/src/client/services/test/test.service.js +8 -13
  242. package/src/client/services/user/guest.service.js +86 -0
  243. package/src/client/services/user/user.management.js +5 -5
  244. package/src/client/services/user/user.service.js +14 -20
  245. package/src/client/ssr/body/404.js +3 -3
  246. package/src/client/ssr/body/500.js +3 -3
  247. package/src/client/ssr/body/CacheControl.js +5 -2
  248. package/src/client/ssr/body/DefaultSplashScreen.js +19 -12
  249. package/src/client/ssr/body/UnderpostDefaultSplashScreen.js +13 -6
  250. package/src/client/ssr/head/PwaItemledger.js +197 -60
  251. package/src/client/ssr/mailer/DefaultRecoverEmail.js +19 -20
  252. package/src/client/ssr/mailer/DefaultVerifyEmail.js +15 -16
  253. package/src/client/ssr/offline/Maintenance.js +12 -11
  254. package/src/client/ssr/offline/NoNetworkConnection.js +3 -3
  255. package/src/client/ssr/pages/Test.js +2 -2
  256. package/src/client/sw/core.sw.js +212 -0
  257. package/src/grpc/cyberia/grpc-server.js +179 -67
  258. package/src/index.js +1 -1
  259. package/src/runtime/cyberia-client/Dockerfile +80 -0
  260. package/src/runtime/cyberia-server/Dockerfile +37 -0
  261. package/src/runtime/express/Dockerfile +4 -4
  262. package/src/runtime/lampp/Dockerfile +8 -7
  263. package/src/runtime/wp/Dockerfile +11 -17
  264. package/src/server/atlas-sprite-sheet-generator.js +4 -2
  265. package/src/server/client-build-docs.js +45 -46
  266. package/src/server/client-build.js +334 -60
  267. package/src/server/client-formatted.js +47 -16
  268. package/src/server/conf.js +5 -4
  269. package/src/server/data-query.js +32 -20
  270. package/src/server/dns.js +22 -0
  271. package/src/server/ipfs-client.js +232 -91
  272. package/src/server/object-layer.js +1 -6
  273. package/src/server/process.js +13 -27
  274. package/src/server/semantic-layer-generator-floor.js +11 -51
  275. package/src/server/semantic-layer-generator-resource.js +259 -0
  276. package/src/server/semantic-layer-generator-skin.js +41 -171
  277. package/src/server/semantic-layer-generator.js +122 -14
  278. package/src/server/shape-generator.js +108 -0
  279. package/src/server/start.js +17 -3
  280. package/src/server/valkey.js +141 -235
  281. package/tsconfig.docs.json +15 -0
  282. package/typedoc.dd-cyberia.json +29 -0
  283. package/typedoc.json +29 -0
  284. package/WHITE-PAPER.md +0 -1540
  285. package/hardhat/README.md +0 -531
  286. package/hardhat/WHITE-PAPER.md +0 -1540
  287. package/jsdoc.dd-cyberia.json +0 -68
  288. package/jsdoc.json +0 -68
  289. package/src/api/object-layer/README.md +0 -672
  290. package/src/client/components/core/ColorPalette.js +0 -5267
  291. package/src/client/components/core/JoyStick.js +0 -80
  292. package/src/client/components/cryptokoyn/RoutesCryptokoyn.js +0 -39
  293. package/src/client/components/cyberia-portal/RoutesCyberiaPortal.js +0 -62
  294. package/src/client/components/cyberia-portal/ServerCyberiaPortal.js +0 -136
  295. package/src/client/components/default/RoutesDefault.js +0 -49
  296. package/src/client/components/itemledger/RoutesItemledger.js +0 -40
  297. package/src/client/components/underpost/RoutesUnderpost.js +0 -47
  298. package/src/client/sw/default.sw.js +0 -127
  299. package/src/client/sw/template.sw.js +0 -84
  300. package/src/grpc/cyberia/OFF_CHAIN_ECONOMY.md +0 -305
  301. package/src/grpc/cyberia/README.md +0 -326
@@ -10,6 +10,7 @@
10
10
 
11
11
  import * as grpc from '@grpc/grpc-js';
12
12
  import * as protoLoader from '@grpc/proto-loader';
13
+ import crypto from 'crypto';
13
14
  import path from 'path';
14
15
  import { fileURLToPath } from 'url';
15
16
  import { DataBaseProvider } from '../../db/DataBaseProvider.js';
@@ -50,6 +51,101 @@ function getModels(dbKey) {
50
51
  return bucket.mongoose.models;
51
52
  }
52
53
 
54
+ function countSharedItemIds(source = [], target = []) {
55
+ if (!Array.isArray(source) || source.length === 0 || !Array.isArray(target) || target.length === 0) {
56
+ return 0;
57
+ }
58
+ const targetSet = new Set(target.filter(Boolean));
59
+ let count = 0;
60
+ for (const itemId of source) {
61
+ if (targetSet.has(itemId)) {
62
+ count += 1;
63
+ }
64
+ }
65
+ return count;
66
+ }
67
+
68
+ function normalizeEntityDefault(entityDefault = {}, canonical = {}) {
69
+ const defaultObjectLayers = entityDefault.defaultObjectLayers ?? canonical.defaultObjectLayers ?? [];
70
+ return {
71
+ entityType: entityDefault.entityType ?? canonical.entityType ?? '',
72
+ liveItemIds: [...(entityDefault.liveItemIds ?? canonical.liveItemIds ?? [])],
73
+ deadItemIds: [...(entityDefault.deadItemIds ?? canonical.deadItemIds ?? [])],
74
+ dropItemIds: [...(entityDefault.dropItemIds ?? canonical.dropItemIds ?? [])],
75
+ colorKey: entityDefault.colorKey ?? canonical.colorKey ?? '',
76
+ defaultObjectLayers: defaultObjectLayers.map((ol) => ({
77
+ itemId: ol.itemId || '',
78
+ active: !!ol.active,
79
+ quantity: ol.quantity || 0,
80
+ })),
81
+ };
82
+ }
83
+
84
+ function selectCanonicalEntityDefaultIndex(entityDefault, canonicalDefaults, usedIndexes) {
85
+ const pickBestIndex = (candidateIndexes) => {
86
+ let firstSameTypeIndex = -1;
87
+ let bestLiveOverlapIndex = -1;
88
+ let bestLiveOverlap = 0;
89
+ let bestLiveItemCount = Number.POSITIVE_INFINITY;
90
+
91
+ for (const index of candidateIndexes) {
92
+ const canonical = canonicalDefaults[index];
93
+ if (canonical.entityType !== entityDefault.entityType) {
94
+ continue;
95
+ }
96
+ if (firstSameTypeIndex === -1) {
97
+ firstSameTypeIndex = index;
98
+ }
99
+ if (entityDefault.colorKey && canonical.colorKey === entityDefault.colorKey) {
100
+ return index;
101
+ }
102
+
103
+ const liveOverlap = countSharedItemIds(entityDefault.liveItemIds, canonical.liveItemIds);
104
+ if (
105
+ liveOverlap > 0 &&
106
+ (bestLiveOverlapIndex === -1 ||
107
+ liveOverlap > bestLiveOverlap ||
108
+ (liveOverlap === bestLiveOverlap && (canonical.liveItemIds?.length ?? 0) < bestLiveItemCount))
109
+ ) {
110
+ bestLiveOverlapIndex = index;
111
+ bestLiveOverlap = liveOverlap;
112
+ bestLiveItemCount = canonical.liveItemIds?.length ?? 0;
113
+ }
114
+ }
115
+
116
+ if (bestLiveOverlapIndex !== -1) {
117
+ return bestLiveOverlapIndex;
118
+ }
119
+ return firstSameTypeIndex;
120
+ };
121
+
122
+ const unusedCandidateIndexes = canonicalDefaults.map((_, index) => index).filter((index) => !usedIndexes.has(index));
123
+
124
+ const preferredIndex = pickBestIndex(unusedCandidateIndexes);
125
+ if (preferredIndex !== -1) {
126
+ return preferredIndex;
127
+ }
128
+ return pickBestIndex(canonicalDefaults.map((_, index) => index));
129
+ }
130
+
131
+ function mergeEntityDefaults(entityDefaults = []) {
132
+ const mergedDefaults = ENTITY_TYPE_DEFAULTS.map((canonical) => normalizeEntityDefault(canonical, canonical));
133
+ const usedCanonicalIndexes = new Set();
134
+
135
+ for (const entityDefault of entityDefaults) {
136
+ const canonicalIndex = selectCanonicalEntityDefaultIndex(entityDefault, ENTITY_TYPE_DEFAULTS, usedCanonicalIndexes);
137
+ if (canonicalIndex === -1) {
138
+ mergedDefaults.push(normalizeEntityDefault(entityDefault));
139
+ continue;
140
+ }
141
+
142
+ mergedDefaults[canonicalIndex] = normalizeEntityDefault(entityDefault, ENTITY_TYPE_DEFAULTS[canonicalIndex]);
143
+ usedCanonicalIndexes.add(canonicalIndex);
144
+ }
145
+
146
+ return mergedDefaults;
147
+ }
148
+
53
149
  // ── Mongoose doc → protobuf message converters ───────────────────
54
150
 
55
151
  function toObjectLayerMsg(doc) {
@@ -58,13 +154,6 @@ function toObjectLayerMsg(doc) {
58
154
  const stats = d.stats || {};
59
155
  const ledger = d.ledger || {};
60
156
  const render = d.render || {};
61
- const rf = doc.objectLayerRenderFramesId;
62
- let frameDuration = 250;
63
- let isStateless = false;
64
- if (rf && typeof rf === 'object') {
65
- if (rf.frame_duration != null) frameDuration = rf.frame_duration;
66
- if (rf.is_stateless != null) isStateless = rf.is_stateless;
67
- }
68
157
  return {
69
158
  mongoId: String(doc._id),
70
159
  stats: {
@@ -92,12 +181,24 @@ function toObjectLayerMsg(doc) {
92
181
  },
93
182
  sha256: doc.sha256 || '',
94
183
  cid: doc.cid || '',
95
- frameDuration,
96
- isStateless,
97
184
  };
98
185
  }
99
186
 
187
+ /**
188
+ * Parse a CSS rgba() colour string into {r, g, b, a} integer components.
189
+ * Returns {r:0, g:0, b:0, a:0} (transparent) when the string is absent or malformed.
190
+ */
191
+ function parseRgba(str) {
192
+ if (!str) return { r: 0, g: 0, b: 0, a: 0 };
193
+ const m = str.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+))?\s*\)/);
194
+ if (!m) return { r: 0, g: 0, b: 0, a: 0 };
195
+ // CSS alpha is 0-1 (float); proto uses 0-255 (int).
196
+ const cssAlpha = m[4] !== undefined ? parseFloat(m[4]) : 1;
197
+ return { r: Number(m[1]), g: Number(m[2]), b: Number(m[3]), a: Math.round(cssAlpha * 255) };
198
+ }
199
+
100
200
  function toEntityMsg(ent) {
201
+ const rgba = parseRgba(ent.color);
101
202
  return {
102
203
  entityType: ent.entityType || 'floor',
103
204
  initCellX: ent.initCellX || 0,
@@ -111,6 +212,10 @@ function toEntityMsg(ent) {
111
212
  maxLife: ent.maxLife || 0,
112
213
  lifeRegen: ent.lifeRegen || 0,
113
214
  portalSubtype: ent.portalSubtype || '',
215
+ colorR: rgba.r,
216
+ colorG: rgba.g,
217
+ colorB: rgba.b,
218
+ colorA: rgba.a,
114
219
  };
115
220
  }
116
221
 
@@ -159,36 +264,23 @@ function toInstanceConfig(gc) {
159
264
  const fb = FALLBACK_CONFIG_DEFAULTS;
160
265
  if (!gc) return buildFallbackConfig();
161
266
 
162
- const colors =
163
- gc.colors && gc.colors.length > 0
164
- ? gc.colors.map((c) => ({
165
- key: c.key || '',
166
- r: c.r ?? 0,
167
- g: c.g ?? 0,
168
- b: c.b ?? 0,
169
- a: c.a ?? 255,
170
- }))
171
- : fb.colors.map((c) => ({ ...c }));
172
-
173
- // Merge entity defaults: use canonical ENTITY_TYPE_DEFAULTS as base, overlay with
174
- // any instance-specific overrides stored in gc.entityDefaults.
175
- const gcDefaults = gc.entityDefaults && gc.entityDefaults.length > 0 ? gc.entityDefaults : [];
176
- const gcDefaultsMap = Object.fromEntries(gcDefaults.map((d) => [d.entityType, d]));
177
- const entityDefaults = ENTITY_TYPE_DEFAULTS.map((canonical) => {
178
- const override = gcDefaultsMap[canonical.entityType] ?? {};
179
- const dols = override.defaultObjectLayers ?? canonical.defaultObjectLayers ?? [];
180
- return {
181
- entityType: canonical.entityType,
182
- liveItemIds: override.liveItemIds ?? canonical.liveItemIds,
183
- deadItemIds: override.deadItemIds ?? canonical.deadItemIds,
184
- colorKey: override.colorKey ?? canonical.colorKey,
185
- defaultObjectLayers: dols.map((ol) => ({
186
- itemId: ol.itemId || '',
187
- active: !!ol.active,
188
- quantity: ol.quantity || 0,
189
- })),
190
- };
267
+ // Per-key merge: start with canonical defaults, overlay any DB-defined colours.
268
+ const dbColorMap = new Map((gc.colors || []).map((c) => [c.key, c]));
269
+ const colors = fb.colors.map((c) => {
270
+ const ov = dbColorMap.get(c.key);
271
+ return ov ? { key: c.key, r: ov.r ?? c.r, g: ov.g ?? c.g, b: ov.b ?? c.b, a: ov.a ?? c.a } : { ...c };
191
272
  });
273
+ // Append any DB colours whose keys are absent from the canonical defaults.
274
+ for (const [key, c] of dbColorMap) {
275
+ if (!colors.some((fc) => fc.key === key)) {
276
+ colors.push({ key, r: c.r ?? 0, g: c.g ?? 0, b: c.b ?? 0, a: c.a ?? 255 });
277
+ }
278
+ }
279
+
280
+ // Merge entity defaults while preserving duplicate builds (for example,
281
+ // multiple resource or portal variants sharing the same entityType).
282
+ const gcDefaults = gc.entityDefaults && gc.entityDefaults.length > 0 ? gc.entityDefaults : [];
283
+ const entityDefaults = mergeEntityDefaults(gcDefaults);
192
284
 
193
285
  return {
194
286
  cellSize: gc.cellSize ?? fb.cellSize,
@@ -233,9 +325,14 @@ function toInstanceConfig(gc) {
233
325
  lifeRegenChance: gc.lifeRegenChance ?? fb.lifeRegenChance,
234
326
  maxChance: gc.maxChance ?? fb.maxChance,
235
327
  entityDefaults,
236
- skillConfig: (gc.skillConfig || []).map((sc) => ({
328
+ skillConfig: (gc.skillConfig && gc.skillConfig.length > 0 ? gc.skillConfig : fb.skillConfig).map((sc) => ({
237
329
  triggerItemId: sc.triggerItemId || '',
238
- logicEventIds: sc.logicEventIds || [],
330
+ skills: (sc.skills || []).map((sk) => ({
331
+ logicEventId: sk.logicEventId || '',
332
+ name: sk.name || '',
333
+ description: sk.description || '',
334
+ summonedEntityItemId: sk.summonedEntityItemId || '',
335
+ })),
239
336
  })),
240
337
  skillRules: {
241
338
  projectileSpawnChance: gc.skillRules?.projectileSpawnChance ?? fb.skillRules.projectileSpawnChance,
@@ -302,10 +399,7 @@ function buildHandlers(dbKey) {
302
399
  if (call.request.itemTypeFilter) {
303
400
  filter['data.item.type'] = call.request.itemTypeFilter;
304
401
  }
305
- const cursor = models.ObjectLayer.find(filter)
306
- .populate('objectLayerRenderFramesId', { _id: 1, frame_duration: 1, is_stateless: 1 })
307
- .lean()
308
- .cursor();
402
+ const cursor = models.ObjectLayer.find(filter).lean().cursor();
309
403
  for await (const doc of cursor) {
310
404
  call.write(toObjectLayerMsg(doc));
311
405
  }
@@ -319,9 +413,7 @@ function buildHandlers(dbKey) {
319
413
  async getObjectLayer(call, callback) {
320
414
  try {
321
415
  const models = getModels(dbKey);
322
- const doc = await models.ObjectLayer.findOne({ 'data.item.id': call.request.itemId })
323
- .populate('objectLayerRenderFramesId', { _id: 1, frame_duration: 1, is_stateless: 1 })
324
- .lean();
416
+ const doc = await models.ObjectLayer.findOne({ 'data.item.id': call.request.itemId }).lean();
325
417
  if (!doc)
326
418
  return callback({ code: grpc.status.NOT_FOUND, message: `ObjectLayer "${call.request.itemId}" not found` });
327
419
  callback(null, toObjectLayerMsg(doc));
@@ -344,7 +436,7 @@ function buildHandlers(dbKey) {
344
436
  models.GlobalMapCodeRegistry.findOneAndUpdate(
345
437
  { mapCode },
346
438
  { instanceCode, status: 'active' },
347
- { upsert: true, new: true, timestamps: true },
439
+ { upsert: true, returnDocument: 'after', timestamps: true },
348
440
  ).catch((err) => logger.warn('getMapData registry update failed:', err.message));
349
441
  }
350
442
 
@@ -380,15 +472,14 @@ function buildHandlers(dbKey) {
380
472
  for (const d of fallbackConf.entityDefaults || []) {
381
473
  for (const id of d.liveItemIds || []) fallbackItemIds.add(id);
382
474
  for (const id of d.deadItemIds || []) fallbackItemIds.add(id);
475
+ for (const id of d.dropItemIds || []) fallbackItemIds.add(id);
383
476
  for (const ol of d.defaultObjectLayers || []) {
384
477
  if (ol.itemId) fallbackItemIds.add(ol.itemId);
385
478
  }
386
479
  }
387
480
 
388
481
  const fallbackOlDocs = fallbackItemIds.size
389
- ? await models.ObjectLayer.find({ 'data.item.id': { $in: [...fallbackItemIds] } })
390
- .populate('objectLayerRenderFramesId', { _id: 1, frame_duration: 1, is_stateless: 1 })
391
- .lean()
482
+ ? await models.ObjectLayer.find({ 'data.item.id': { $in: [...fallbackItemIds] } }).lean()
392
483
  : [];
393
484
 
394
485
  callback(null, {
@@ -415,12 +506,27 @@ function buildHandlers(dbKey) {
415
506
  })),
416
507
  objectLayers: fallbackOlDocs.map(toObjectLayerMsg),
417
508
  config: toInstanceConfig(fallbackConf),
509
+ version: 'fallback',
418
510
  });
419
511
  return;
420
512
  }
421
513
 
422
514
  // ── Instance found — load maps + entity OLs + config default OLs ──────
423
- const conf = inst.conf || {};
515
+ // `populate('conf')` returns null when the ObjectId ref is missing or
516
+ // orphaned (e.g. after a --conf import that preserved a stale _id).
517
+ // Fall back to a direct instanceCode lookup so aoiRadius and all other
518
+ // tuning fields are always read from the database, never silently
519
+ // replaced by FALLBACK_CONFIG_DEFAULTS.
520
+ let conf = inst.conf;
521
+ if (!conf) {
522
+ conf = await models.CyberiaInstanceConf.findOne({ instanceCode }).lean();
523
+ if (conf) {
524
+ logger.warn(
525
+ `getFullInstance: conf ref missing on instance "${instanceCode}" — resolved by instanceCode lookup`,
526
+ );
527
+ }
528
+ }
529
+ conf = conf || {};
424
530
  const mapCodes = inst.cyberiaMapCodes || [];
425
531
  const mapDocs = mapCodes.length ? await models.CyberiaMap.find({ code: { $in: mapCodes } }).lean() : [];
426
532
 
@@ -432,29 +538,35 @@ function buildHandlers(dbKey) {
432
538
  }
433
539
  }
434
540
 
435
- // Also include "system" item IDs from the instance config so the
436
- // Go server has their OL data cached without extra round trips:
437
- // Include OL item IDs from entityDefaults so the Go server has all
438
- // default atlas data cached at startup without needing extra round trips.
439
- for (const d of conf.entityDefaults || []) {
541
+ // Include system OL items from BOTH canonical ENTITY_TYPE_DEFAULTS AND
542
+ // the DB conf so the Go server always caches all default atlases even if
543
+ // the DB doc has incomplete entityDefaults.
544
+ for (const d of [...ENTITY_TYPE_DEFAULTS, ...(conf.entityDefaults || [])]) {
440
545
  for (const id of d.liveItemIds || []) itemIds.add(id);
441
546
  for (const id of d.deadItemIds || []) itemIds.add(id);
547
+ for (const id of d.dropItemIds || []) itemIds.add(id);
442
548
  for (const ol of d.defaultObjectLayers || []) {
443
549
  if (ol.itemId) itemIds.add(ol.itemId);
444
550
  }
445
551
  }
446
552
 
447
553
  const olDocs = itemIds.size
448
- ? await models.ObjectLayer.find({ 'data.item.id': { $in: [...itemIds] } })
449
- .populate('objectLayerRenderFramesId', { _id: 1, frame_duration: 1, is_stateless: 1 })
450
- .lean()
554
+ ? await models.ObjectLayer.find({ 'data.item.id': { $in: [...itemIds] } }).lean()
451
555
  : [];
452
556
 
557
+ // Opaque version string from updatedAt timestamps — the Go server
558
+ // compares this to skip full world rebuilds when nothing changed.
559
+ const versionParts = [String(inst.updatedAt || inst._id)];
560
+ for (const m of mapDocs) versionParts.push(String(m.updatedAt || m._id));
561
+ if (conf.updatedAt) versionParts.push(String(conf.updatedAt));
562
+ const version = crypto.createHash('sha256').update(versionParts.join('|')).digest('hex');
563
+
453
564
  callback(null, {
454
565
  instance: toInstanceMsg(inst),
455
566
  maps: mapDocs.map(toMapMsg),
456
567
  objectLayers: olDocs.map(toObjectLayerMsg),
457
568
  config: toInstanceConfig(conf),
569
+ version,
458
570
  });
459
571
  } catch (err) {
460
572
  logger.error('getFullInstance:', err);
@@ -481,8 +593,8 @@ function buildHandlers(dbKey) {
481
593
  // Server lifecycle
482
594
  // ═══════════════════════════════════════════════════════════════════
483
595
 
484
- const GrpcServer = {
485
- _server: null,
596
+ class GrpcServer {
597
+ static _server = null;
486
598
 
487
599
  /**
488
600
  * @param {Object} opts
@@ -490,7 +602,7 @@ const GrpcServer = {
490
602
  * @param {string} opts.path - DataBaseProvider path key
491
603
  * @param {number} [opts.port=50051]
492
604
  */
493
- async start({ host, path: dbPath, port = 50051 } = {}) {
605
+ static async start({ host, path: dbPath, port = 50051 } = {}) {
494
606
  const dbKey = `${host}${dbPath}`;
495
607
  const server = new grpc.Server({
496
608
  'grpc.max_send_message_length': 64 * 1024 * 1024,
@@ -513,9 +625,9 @@ const GrpcServer = {
513
625
  resolve(server);
514
626
  });
515
627
  });
516
- },
628
+ }
517
629
 
518
- async stop() {
630
+ static async stop() {
519
631
  if (!GrpcServer._server) return;
520
632
  return new Promise((resolve) => {
521
633
  GrpcServer._server.tryShutdown(() => {
@@ -524,7 +636,7 @@ const GrpcServer = {
524
636
  resolve();
525
637
  });
526
638
  });
527
- },
528
- };
639
+ }
640
+ }
529
641
 
530
642
  export { GrpcServer };
package/src/index.js CHANGED
@@ -44,7 +44,7 @@ class Underpost {
44
44
  * @type {String}
45
45
  * @memberof Underpost
46
46
  */
47
- static version = 'v3.2.5';
47
+ static version = 'v3.2.9';
48
48
 
49
49
  /**
50
50
  * Required Node.js major version
@@ -0,0 +1,80 @@
1
+ # BUILD_MODE: RELEASE | DEBUG
2
+ ARG BUILD_MODE=RELEASE
3
+
4
+ # --- Build Image
5
+ FROM rockylinux/rockylinux:9 AS builder
6
+ # Re-declare ARG after FROM so it is in scope for RUN
7
+ ARG BUILD_MODE=RELEASE
8
+
9
+ RUN dnf -y update && \
10
+ dnf -y install epel-release && \
11
+ dnf -y install --allowerasing \
12
+ git \
13
+ curl \
14
+ wget \
15
+ openssl-devel \
16
+ libnsl \
17
+ perl \
18
+ gnupg2 \
19
+ bzip2 && \
20
+ dnf clean all
21
+
22
+ # System packages (raylib gfx + build dependencies)
23
+ RUN dnf groupinstall -y "Development Tools" && \
24
+ dnf install -y \
25
+ cmake \
26
+ unzip \
27
+ python3 \
28
+ python3.11 \
29
+ alsa-lib-devel \
30
+ mesa-libGL-devel \
31
+ mesa-libGLU-devel \
32
+ libX11-devel \
33
+ libXrandr-devel \
34
+ libXi-devel \
35
+ libXcursor-devel \
36
+ libXinerama-devel \
37
+ libXfixes-devel \
38
+ freeglut-devel \
39
+ glfw-devel \
40
+ libatomic.x86_64 && \
41
+ dnf clean all && \
42
+ alternatives --install /usr/bin/python3 python3 /usr/bin/python3.11 2 && \
43
+ alternatives --set python3 /usr/bin/python3.11
44
+ ENV EMSDK=/opt/emsdk
45
+ ENV PATH="${EMSDK}:${EMSDK}/upstream/emscripten:${PATH}"
46
+
47
+ # Pin emsdk version for reproducible builds
48
+ ARG EMSDK_VERSION=5.0.6
49
+ WORKDIR /opt
50
+ RUN git clone https://github.com/emscripten-core/emsdk.git ${EMSDK}
51
+ WORKDIR ${EMSDK}
52
+ RUN ./emsdk install ${EMSDK_VERSION} && \
53
+ ./emsdk activate ${EMSDK_VERSION}
54
+
55
+ WORKDIR /cyberia-client
56
+ COPY cyberia-client/ .
57
+
58
+ RUN make -f Web.mk all BUILD_MODE=${BUILD_MODE} OUTPUT_DIR=bin/
59
+
60
+ # --- Runtime Image
61
+ FROM rockylinux/rockylinux:9 AS runtime
62
+
63
+ RUN dnf -y update && \
64
+ dnf -y install epel-release && \
65
+ dnf -y install --allowerasing curl git python3 && \
66
+ curl -fsSL https://rpm.nodesource.com/setup_24.x | bash - && \
67
+ dnf install -y nodejs && \
68
+ dnf clean all
69
+
70
+ WORKDIR /home/dd/engine/cyberia-client
71
+
72
+ COPY --from=builder /cyberia-client/server.py ./server.py
73
+ COPY --from=builder /cyberia-client/bin ./bin/
74
+
75
+ ENV CYBERIA_PORT=8081
76
+ ENV CYBERIA_MODE=production
77
+
78
+ EXPOSE 8081 8082
79
+
80
+ CMD ["sh", "-c", "exec python3 server.py ${CYBERIA_PORT} bin ${CYBERIA_MODE}"]
@@ -0,0 +1,37 @@
1
+ # --- Build Image
2
+ FROM rockylinux/rockylinux:9 AS builder
3
+
4
+ RUN dnf -y update && \
5
+ dnf -y install epel-release && \
6
+ dnf -y install --allowerasing \
7
+ git \
8
+ make \
9
+ golang && \
10
+ dnf clean all
11
+
12
+ WORKDIR /build
13
+
14
+ # Cache dependency downloads independently of source changes
15
+ COPY cyberia-server/go.mod cyberia-server/go.sum ./
16
+ RUN go mod download
17
+
18
+ COPY cyberia-server/ ./
19
+ RUN chmod +x build.sh && ./build.sh
20
+
21
+ # --- Runtime Image
22
+ FROM rockylinux/rockylinux:9 AS runtime
23
+
24
+ RUN dnf -y update && \
25
+ dnf -y install epel-release && \
26
+ dnf -y install --allowerasing curl git && \
27
+ curl -fsSL https://rpm.nodesource.com/setup_24.x | bash - && \
28
+ dnf install -y nodejs && \
29
+ dnf clean all
30
+
31
+ WORKDIR /home/dd/engine/cyberia-server
32
+
33
+ COPY --from=builder /build/server ./server
34
+
35
+ EXPOSE 8081
36
+
37
+ ENTRYPOINT ["/home/dd/engine/cyberia-server/server"]
@@ -30,11 +30,11 @@ RUN dnf clean all
30
30
  RUN node --version
31
31
  RUN npm --version
32
32
 
33
- # Create non-root user for secure container execution (cron jobs, init containers)
34
- # Deployment containers override to root via securityContext when npm install -g is needed
35
- RUN useradd -m -u 1000 -s /bin/bash dd
33
+ # Install underpost ci/cd cli
34
+ RUN npm install -g underpost
35
+ RUN underpost --version
36
36
 
37
- # Set working directory
37
+ # Create working directory
38
38
  WORKDIR /home/dd
39
39
 
40
40
  # Expose necessary ports
@@ -1,6 +1,6 @@
1
1
  FROM rockylinux:9
2
2
 
3
- # Update and install required packages
3
+ # System packages
4
4
  RUN dnf -y update && \
5
5
  dnf -y install epel-release && \
6
6
  dnf -y install --allowerasing \
@@ -20,13 +20,14 @@ RUN dnf -y update && \
20
20
  perl && \
21
21
  dnf clean all
22
22
 
23
- # --- Download and install XAMPP (PHP 8.2)
23
+
24
+ # Download and install XAMPP (PHP 8.2)
24
25
  RUN curl -L -o /tmp/xampp-linux-installer.run "https://sourceforge.net/projects/xampp/files/XAMPP%20Linux/8.2.12/xampp-linux-x64-8.2.12-0-installer.run" && \
25
26
  chmod +x /tmp/xampp-linux-installer.run && \
26
27
  bash -c "/tmp/xampp-linux-installer.run --mode unattended" && \
27
28
  ln -sf /opt/lampp/lampp /usr/bin/lampp
28
29
 
29
- # --- Create /xampp/htdocs (static root) and set permissions
30
+ # Create /xampp/htdocs (static root) and set permissions
30
31
  RUN mkdir -p /opt/lampp/htdocs && \
31
32
  chown -R root:root /opt/lampp/htdocs && \
32
33
  chmod -R a+rX /opt/lampp/htdocs
@@ -47,11 +48,11 @@ RUN dnf clean all
47
48
  RUN node --version
48
49
  RUN npm --version
49
50
 
50
- # Create non-root user for secure container execution (cron jobs, init containers)
51
- # Deployment containers override to root via securityContext when npm install -g is needed
52
- RUN useradd -m -u 1000 -s /bin/bash dd
51
+ # Install underpost CLI
52
+ RUN npm install -g underpost
53
+ RUN underpost --version
53
54
 
54
- # Set working directory
55
+ # Runtime root expected by startup/build scripts.
55
56
  WORKDIR /home/dd
56
57
 
57
58
  EXPOSE 22
@@ -1,13 +1,12 @@
1
1
  FROM rockylinux:9
2
2
 
3
- # Update and install required packages
3
+ # System packages
4
4
  RUN dnf -y update && \
5
5
  dnf -y install epel-release && \
6
6
  dnf -y install --allowerasing \
7
7
  bzip2 \
8
8
  sudo \
9
9
  curl \
10
- unzip \
11
10
  net-tools \
12
11
  openssh-server \
13
12
  nano \
@@ -21,13 +20,13 @@ RUN dnf -y update && \
21
20
  perl && \
22
21
  dnf clean all
23
22
 
24
- # --- Download and install XAMPP (PHP 8.2)
23
+ # Download and install XAMPP (PHP 8.2)
25
24
  RUN curl -L -o /tmp/xampp-linux-installer.run "https://sourceforge.net/projects/xampp/files/XAMPP%20Linux/8.2.12/xampp-linux-x64-8.2.12-0-installer.run" && \
26
25
  chmod +x /tmp/xampp-linux-installer.run && \
27
26
  bash -c "/tmp/xampp-linux-installer.run --mode unattended" && \
28
27
  ln -sf /opt/lampp/lampp /usr/bin/lampp
29
28
 
30
- # --- Create /xampp/htdocs (static root) and set permissions
29
+ # Create /xampp/htdocs (static root) and set permissions
31
30
  RUN mkdir -p /opt/lampp/htdocs && \
32
31
  chown -R root:root /opt/lampp/htdocs && \
33
32
  chmod -R a+rX /opt/lampp/htdocs
@@ -39,25 +38,20 @@ RUN echo 'export PATH="/opt/lampp/bin:/usr/local/bin:${PATH}"' > /etc/profile.d/
39
38
  # Provide a no-op sendmail so WP plugins don't error on mail calls
40
39
  RUN printf '#!/bin/sh\ncat > /dev/null\n' > /usr/sbin/sendmail && chmod +x /usr/sbin/sendmail
41
40
 
41
+ # Install Node.js (includes npm)
42
+ RUN curl -fsSL https://rpm.nodesource.com/setup_24.x | bash - && \
43
+ dnf install -y nodejs && \
44
+ dnf clean all
45
+
42
46
  # Install WP-CLI
43
47
  RUN curl -sL https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar -o /usr/local/bin/wp && \
44
48
  chmod +x /usr/local/bin/wp && \
45
49
  wp --info --allow-root
46
50
 
47
- # Install Node.js
48
- RUN curl -fsSL https://rpm.nodesource.com/setup_24.x | bash -
49
- RUN dnf install nodejs -y
50
- RUN dnf clean all
51
-
52
- # Verify Node.js and npm versions
53
- RUN node --version
54
- RUN npm --version
55
-
56
- # Create non-root user for secure container execution (cron jobs, init containers)
57
- # Deployment containers override to root via securityContext when npm install -g is needed
58
- RUN useradd -m -u 1000 -s /bin/bash dd
51
+ # Install underpost CLI
52
+ RUN npm install -g underpost
59
53
 
60
- # Set working directory
54
+ # Runtime root expected by startup/build scripts.
61
55
  WORKDIR /home/dd
62
56
 
63
57
  EXPOSE 22
@@ -133,6 +133,7 @@ export class AtlasSpriteSheetGenerator {
133
133
  */
134
134
  static async generateAtlas(objectLayerRenderFrames, itemKey, cellPixelDim = 20, maxAtlasDim = null) {
135
135
  const { frames, colors } = objectLayerRenderFrames;
136
+ const frameDuration = Number(objectLayerRenderFrames?.frame_duration);
136
137
 
137
138
  // Direction order for consistent packing
138
139
  const directionOrder = [
@@ -255,13 +256,13 @@ export class AtlasSpriteSheetGenerator {
255
256
  frameMetadata[direction] = [];
256
257
  }
257
258
 
258
- frameMetadata[direction].push({
259
+ frameMetadata[direction][frameIndex] = {
259
260
  x,
260
261
  y,
261
262
  width: image.bitmap.width,
262
263
  height: image.bitmap.height,
263
264
  frameIndex,
264
- });
265
+ };
265
266
  }
266
267
 
267
268
  // Convert to PNG buffer
@@ -272,6 +273,7 @@ export class AtlasSpriteSheetGenerator {
272
273
  atlasWidth,
273
274
  atlasHeight,
274
275
  cellPixelDim,
276
+ frame_duration: Number.isFinite(frameDuration) ? frameDuration : 100,
275
277
  frames: frameMetadata,
276
278
  };
277
279