cyberia 3.1.3 → 3.2.5

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 (208) 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 +13 -8
  10. package/CHANGELOG.md +433 -1
  11. package/CLI-HELP.md +57 -7
  12. package/Dockerfile +4 -2
  13. package/README.md +347 -22
  14. package/bin/build.js +5 -2
  15. package/bin/cyberia.js +1789 -112
  16. package/bin/deploy.js +177 -124
  17. package/bin/file.js +3 -0
  18. package/bin/index.js +1789 -112
  19. package/conf.js +64 -8
  20. package/deployment.yaml +92 -20
  21. package/hardhat/hardhat.config.js +13 -13
  22. package/hardhat/ignition/modules/ObjectLayerToken.js +1 -1
  23. package/hardhat/package-lock.json +2554 -5859
  24. package/hardhat/package.json +13 -22
  25. package/hardhat/scripts/deployObjectLayerToken.js +1 -1
  26. package/hardhat/test/ObjectLayerToken.js +4 -2
  27. package/hardhat/types/ethers-contracts/ObjectLayerToken.ts +690 -0
  28. package/hardhat/types/ethers-contracts/common.ts +92 -0
  29. package/hardhat/types/ethers-contracts/factories/ObjectLayerToken__factory.ts +1055 -0
  30. package/hardhat/types/ethers-contracts/factories/index.ts +4 -0
  31. package/hardhat/types/ethers-contracts/hardhat.d.ts +47 -0
  32. package/hardhat/types/ethers-contracts/index.ts +6 -0
  33. package/jsdoc.dd-cyberia.json +64 -55
  34. package/jsdoc.json +64 -55
  35. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +5 -4
  36. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +5 -4
  37. package/manifests/deployment/dd-cyberia-development/deployment.yaml +92 -20
  38. package/manifests/deployment/dd-cyberia-development/proxy.yaml +54 -18
  39. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  40. package/manifests/deployment/dd-test-development/deployment.yaml +88 -74
  41. package/manifests/deployment/dd-test-development/proxy.yaml +13 -4
  42. package/manifests/deployment/playwright/deployment.yaml +1 -1
  43. package/nodemon.json +1 -1
  44. package/package.json +22 -16
  45. package/proxy.yaml +54 -18
  46. package/scripts/rhel-grpc-setup.sh +56 -0
  47. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +44 -0
  48. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +16 -0
  49. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +5 -0
  50. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +80 -7
  51. package/src/api/cyberia-dialogue/cyberia-dialogue.controller.js +93 -0
  52. package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +36 -0
  53. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +29 -0
  54. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +51 -0
  55. package/src/api/cyberia-entity/cyberia-entity.controller.js +74 -0
  56. package/src/api/cyberia-entity/cyberia-entity.model.js +24 -0
  57. package/src/api/cyberia-entity/cyberia-entity.router.js +27 -0
  58. package/src/api/cyberia-entity/cyberia-entity.service.js +42 -0
  59. package/src/api/cyberia-instance/cyberia-fallback-world.js +368 -0
  60. package/src/api/cyberia-instance/cyberia-instance.controller.js +92 -0
  61. package/src/api/cyberia-instance/cyberia-instance.model.js +84 -0
  62. package/src/api/cyberia-instance/cyberia-instance.router.js +63 -0
  63. package/src/api/cyberia-instance/cyberia-instance.service.js +191 -0
  64. package/src/api/cyberia-instance/cyberia-portal-connector.js +486 -0
  65. package/src/api/cyberia-instance-conf/cyberia-instance-conf.controller.js +74 -0
  66. package/src/api/cyberia-instance-conf/cyberia-instance-conf.defaults.js +413 -0
  67. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +228 -0
  68. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +27 -0
  69. package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +42 -0
  70. package/src/api/cyberia-map/cyberia-map.controller.js +79 -0
  71. package/src/api/cyberia-map/cyberia-map.model.js +30 -0
  72. package/src/api/cyberia-map/cyberia-map.router.js +40 -0
  73. package/src/api/cyberia-map/cyberia-map.service.js +74 -0
  74. package/src/api/file/file.ref.json +18 -0
  75. package/src/api/ipfs/ipfs.controller.js +4 -25
  76. package/src/api/ipfs/ipfs.model.js +43 -34
  77. package/src/api/ipfs/ipfs.router.js +8 -13
  78. package/src/api/ipfs/ipfs.service.js +54 -102
  79. package/src/api/object-layer/README.md +347 -22
  80. package/src/api/object-layer/object-layer.router.js +30 -0
  81. package/src/api/object-layer/object-layer.service.js +114 -31
  82. package/src/api/user/user.service.js +8 -7
  83. package/src/cli/cluster.js +7 -7
  84. package/src/cli/db.js +710 -827
  85. package/src/cli/deploy.js +151 -93
  86. package/src/cli/env.js +29 -0
  87. package/src/cli/fs.js +5 -2
  88. package/src/cli/index.js +48 -2
  89. package/src/cli/kubectl.js +211 -0
  90. package/src/cli/release.js +284 -0
  91. package/src/cli/repository.js +438 -75
  92. package/src/cli/run.js +195 -35
  93. package/src/cli/secrets.js +73 -0
  94. package/src/cli/test.js +3 -3
  95. package/src/client/Cryptokoyn.index.js +3 -4
  96. package/src/client/CyberiaPortal.index.js +3 -4
  97. package/src/client/Default.index.js +3 -4
  98. package/src/client/Itemledger.index.js +3 -4
  99. package/src/client/Underpost.index.js +3 -4
  100. package/src/client/components/core/AppStore.js +69 -0
  101. package/src/client/components/core/CalendarCore.js +2 -2
  102. package/src/client/components/core/DropDown.js +137 -17
  103. package/src/client/components/core/Keyboard.js +2 -2
  104. package/src/client/components/core/LogIn.js +2 -2
  105. package/src/client/components/core/LogOut.js +2 -2
  106. package/src/client/components/core/Modal.js +0 -1
  107. package/src/client/components/core/Panel.js +0 -1
  108. package/src/client/components/core/PanelForm.js +19 -19
  109. package/src/client/components/core/SocketIo.js +82 -29
  110. package/src/client/components/core/SocketIoHandler.js +75 -0
  111. package/src/client/components/core/Stream.js +143 -95
  112. package/src/client/components/core/Webhook.js +40 -7
  113. package/src/client/components/cryptokoyn/AppStoreCryptokoyn.js +5 -0
  114. package/src/client/components/cryptokoyn/LogInCryptokoyn.js +3 -3
  115. package/src/client/components/cryptokoyn/LogOutCryptokoyn.js +2 -2
  116. package/src/client/components/cryptokoyn/MenuCryptokoyn.js +3 -3
  117. package/src/client/components/cryptokoyn/SocketIoCryptokoyn.js +3 -51
  118. package/src/client/components/cyberia/InstanceEngineCyberia.js +700 -0
  119. package/src/client/components/cyberia/MapEngineCyberia.js +1359 -2
  120. package/src/client/components/cyberia/ObjectLayerEngineModal.js +17 -6
  121. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +92 -54
  122. package/src/client/components/cyberia-portal/AppStoreCyberiaPortal.js +5 -0
  123. package/src/client/components/cyberia-portal/CommonCyberiaPortal.js +216 -30
  124. package/src/client/components/cyberia-portal/LogInCyberiaPortal.js +3 -3
  125. package/src/client/components/cyberia-portal/LogOutCyberiaPortal.js +2 -2
  126. package/src/client/components/cyberia-portal/MenuCyberiaPortal.js +40 -7
  127. package/src/client/components/cyberia-portal/RoutesCyberiaPortal.js +4 -0
  128. package/src/client/components/cyberia-portal/SocketIoCyberiaPortal.js +3 -49
  129. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +4 -0
  130. package/src/client/components/default/AppStoreDefault.js +5 -0
  131. package/src/client/components/default/LogInDefault.js +3 -3
  132. package/src/client/components/default/LogOutDefault.js +2 -2
  133. package/src/client/components/default/MenuDefault.js +5 -5
  134. package/src/client/components/default/SocketIoDefault.js +3 -51
  135. package/src/client/components/itemledger/AppStoreItemledger.js +5 -0
  136. package/src/client/components/itemledger/LogInItemledger.js +3 -3
  137. package/src/client/components/itemledger/LogOutItemledger.js +2 -2
  138. package/src/client/components/itemledger/MenuItemledger.js +3 -3
  139. package/src/client/components/itemledger/SocketIoItemledger.js +3 -51
  140. package/src/client/components/underpost/AppStoreUnderpost.js +5 -0
  141. package/src/client/components/underpost/LogInUnderpost.js +3 -3
  142. package/src/client/components/underpost/LogOutUnderpost.js +2 -2
  143. package/src/client/components/underpost/MenuUnderpost.js +5 -5
  144. package/src/client/components/underpost/SocketIoUnderpost.js +3 -51
  145. package/src/client/services/core/core.service.js +20 -8
  146. package/src/client/services/cyberia-dialogue/cyberia-dialogue.service.js +105 -0
  147. package/src/client/services/cyberia-entity/cyberia-entity.management.js +57 -0
  148. package/src/client/services/cyberia-entity/cyberia-entity.service.js +105 -0
  149. package/src/client/services/cyberia-instance/cyberia-instance.management.js +194 -0
  150. package/src/client/services/cyberia-instance/cyberia-instance.service.js +122 -0
  151. package/src/client/services/cyberia-instance-conf/cyberia-instance-conf.service.js +105 -0
  152. package/src/client/services/cyberia-map/cyberia-map.management.js +193 -0
  153. package/src/client/services/cyberia-map/cyberia-map.service.js +126 -0
  154. package/src/client/services/instance/instance.management.js +2 -2
  155. package/src/client/services/ipfs/ipfs.service.js +3 -23
  156. package/src/client/services/object-layer/object-layer.management.js +3 -3
  157. package/src/client/services/object-layer/object-layer.service.js +21 -0
  158. package/src/client/services/user/user.management.js +2 -2
  159. package/src/client/ssr/pages/CyberiaServerMetrics.js +1 -1
  160. package/src/grpc/cyberia/OFF_CHAIN_ECONOMY.md +305 -0
  161. package/src/grpc/cyberia/README.md +326 -0
  162. package/src/grpc/cyberia/grpc-server.js +530 -0
  163. package/src/index.js +24 -1
  164. package/src/runtime/express/Dockerfile +4 -0
  165. package/src/runtime/express/Express.js +18 -1
  166. package/src/runtime/lampp/Dockerfile +13 -2
  167. package/src/runtime/lampp/Lampp.js +27 -4
  168. package/src/runtime/wp/Dockerfile +68 -0
  169. package/src/runtime/wp/Wp.js +639 -0
  170. package/src/server/auth.js +24 -1
  171. package/src/server/backup.js +37 -9
  172. package/src/server/client-build-docs.js +9 -2
  173. package/src/server/client-build.js +31 -31
  174. package/src/server/client-formatted.js +109 -57
  175. package/src/server/conf.js +24 -9
  176. package/src/server/cron.js +25 -23
  177. package/src/server/dns.js +2 -1
  178. package/src/server/ipfs-client.js +24 -1
  179. package/src/server/object-layer.js +149 -108
  180. package/src/server/peer.js +8 -0
  181. package/src/server/runtime.js +25 -1
  182. package/src/server/semantic-layer-generator-floor.js +359 -0
  183. package/src/server/semantic-layer-generator-skin.js +1294 -0
  184. package/src/server/semantic-layer-generator.js +116 -555
  185. package/src/server/start.js +2 -2
  186. package/src/ws/IoInterface.js +1 -10
  187. package/src/ws/IoServer.js +14 -33
  188. package/src/ws/core/channels/core.ws.chat.js +65 -20
  189. package/src/ws/core/channels/core.ws.mailer.js +113 -32
  190. package/src/ws/core/channels/core.ws.stream.js +90 -31
  191. package/src/ws/core/core.ws.connection.js +12 -33
  192. package/src/ws/core/core.ws.emit.js +10 -26
  193. package/src/ws/core/core.ws.server.js +25 -58
  194. package/src/ws/default/channels/default.ws.main.js +53 -12
  195. package/src/ws/default/default.ws.connection.js +26 -13
  196. package/src/ws/default/default.ws.server.js +30 -12
  197. package/src/client/components/cryptokoyn/CommonCryptokoyn.js +0 -29
  198. package/src/client/components/cryptokoyn/ElementsCryptokoyn.js +0 -38
  199. package/src/client/components/cyberia-portal/ElementsCyberiaPortal.js +0 -38
  200. package/src/client/components/default/ElementsDefault.js +0 -38
  201. package/src/client/components/itemledger/CommonItemledger.js +0 -29
  202. package/src/client/components/itemledger/ElementsItemledger.js +0 -38
  203. package/src/client/components/underpost/CommonUnderpost.js +0 -29
  204. package/src/client/components/underpost/ElementsUnderpost.js +0 -38
  205. package/src/ws/core/management/core.ws.chat.js +0 -8
  206. package/src/ws/core/management/core.ws.mailer.js +0 -16
  207. package/src/ws/core/management/core.ws.stream.js +0 -8
  208. package/src/ws/default/management/default.ws.main.js +0 -8
@@ -0,0 +1,74 @@
1
+ import { loggerFactory } from '../../server/logger.js';
2
+ import { CyberiaEntityService } from './cyberia-entity.service.js';
3
+
4
+ const logger = loggerFactory(import.meta);
5
+
6
+ const CyberiaEntityController = {
7
+ post: async (req, res, options) => {
8
+ try {
9
+ const result = await CyberiaEntityService.post(req, res, options);
10
+ return res.status(200).json({
11
+ status: 'success',
12
+ data: result,
13
+ });
14
+ } catch (error) {
15
+ logger.error(error, error.stack);
16
+ return res.status(400).json({
17
+ status: 'error',
18
+ message: error.message,
19
+ });
20
+ }
21
+ },
22
+ get: async (req, res, options) => {
23
+ try {
24
+ const { page, limit } = req.query;
25
+ const result = await CyberiaEntityService.get(
26
+ { ...req, query: { ...req.query, page: parseInt(page), limit: parseInt(limit) } },
27
+ res,
28
+ options,
29
+ );
30
+ return res.status(200).json({
31
+ status: 'success',
32
+ data: result,
33
+ });
34
+ } catch (error) {
35
+ logger.error(error, error.stack);
36
+ return res.status(400).json({
37
+ status: 'error',
38
+ message: error.message,
39
+ });
40
+ }
41
+ },
42
+ put: async (req, res, options) => {
43
+ try {
44
+ const result = await CyberiaEntityService.put(req, res, options);
45
+ return res.status(200).json({
46
+ status: 'success',
47
+ data: result,
48
+ });
49
+ } catch (error) {
50
+ logger.error(error, error.stack);
51
+ return res.status(400).json({
52
+ status: 'error',
53
+ message: error.message,
54
+ });
55
+ }
56
+ },
57
+ delete: async (req, res, options) => {
58
+ try {
59
+ const result = await CyberiaEntityService.delete(req, res, options);
60
+ return res.status(200).json({
61
+ status: 'success',
62
+ data: result,
63
+ });
64
+ } catch (error) {
65
+ logger.error(error, error.stack);
66
+ return res.status(400).json({
67
+ status: 'error',
68
+ message: error.message,
69
+ });
70
+ }
71
+ },
72
+ };
73
+
74
+ export { CyberiaEntityController };
@@ -0,0 +1,24 @@
1
+ import { Schema, model, Types } from 'mongoose';
2
+
3
+ // https://mongoosejs.com/docs/2.7.x/docs/schematypes.html
4
+
5
+ const CyberiaEntitySchema = new Schema({
6
+ entityType: { type: String, default: 'floor' },
7
+ initCellX: { type: Number, default: 0 },
8
+ initCellY: { type: Number, default: 0 },
9
+ dimX: { type: Number, default: 1 },
10
+ dimY: { type: Number, default: 1 },
11
+ color: { type: String, default: 'rgba(255, 0, 0, 1)' },
12
+ objectLayerItemIds: { type: [String], default: [] },
13
+ // Bot-specific fields (ignored for non-bot entities)
14
+ spawnRadius: { type: Number, default: 0 },
15
+ aggroRange: { type: Number, default: 0 },
16
+ maxLife: { type: Number, default: 0 },
17
+ lifeRegen: { type: Number, default: 0 },
18
+ });
19
+
20
+ const CyberiaEntityModel = model('CyberiaEntity', CyberiaEntitySchema);
21
+
22
+ const ProviderSchema = CyberiaEntitySchema;
23
+
24
+ export { CyberiaEntitySchema, CyberiaEntityModel, ProviderSchema };
@@ -0,0 +1,27 @@
1
+ import { loggerFactory } from '../../server/logger.js';
2
+ import { CyberiaEntityController } from './cyberia-entity.controller.js';
3
+ import express from 'express';
4
+
5
+ const logger = loggerFactory(import.meta);
6
+
7
+ const CyberiaEntityRouter = (options) => {
8
+ const router = express.Router();
9
+ const authMiddleware = options.authMiddleware;
10
+ router.post(`/:id`, async (req, res) => await CyberiaEntityController.post(req, res, options));
11
+ router.post(`/`, async (req, res) => await CyberiaEntityController.post(req, res, options));
12
+ router.get(
13
+ `/:id`,
14
+ // authMiddleware,
15
+ async (req, res) => await CyberiaEntityController.get(req, res, options),
16
+ );
17
+ router.get(`/`, async (req, res) => await CyberiaEntityController.get(req, res, options));
18
+ router.put(`/:id`, async (req, res) => await CyberiaEntityController.put(req, res, options));
19
+ router.put(`/`, async (req, res) => await CyberiaEntityController.put(req, res, options));
20
+ router.delete(`/:id`, async (req, res) => await CyberiaEntityController.delete(req, res, options));
21
+ router.delete(`/`, async (req, res) => await CyberiaEntityController.delete(req, res, options));
22
+ return router;
23
+ };
24
+
25
+ const ApiRouter = CyberiaEntityRouter;
26
+
27
+ export { ApiRouter, CyberiaEntityRouter };
@@ -0,0 +1,42 @@
1
+ import { DataBaseProvider } from '../../db/DataBaseProvider.js';
2
+ import { loggerFactory } from '../../server/logger.js';
3
+ import { DataQuery } from '../../server/data-query.js';
4
+
5
+ const logger = loggerFactory(import.meta);
6
+
7
+ const CyberiaEntityService = {
8
+ post: async (req, res, options) => {
9
+ /** @type {import('./cyberia-entity.model.js').CyberiaEntityModel} */
10
+ const CyberiaEntity = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.CyberiaEntity;
11
+ return await new CyberiaEntity(req.body).save();
12
+ },
13
+ get: async (req, res, options) => {
14
+ /** @type {import('./cyberia-entity.model.js').CyberiaEntityModel} */
15
+ const CyberiaEntity = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.CyberiaEntity;
16
+ if (req.params.id) return await CyberiaEntity.findById(req.params.id);
17
+
18
+ // Parse query parameters using DataQuery helper
19
+ const { query, sort, skip, limit, page } = DataQuery.parse(req.query);
20
+
21
+ const [data, total] = await Promise.all([
22
+ CyberiaEntity.find(query).sort(sort).limit(limit).skip(skip),
23
+ CyberiaEntity.countDocuments(query),
24
+ ]);
25
+
26
+ const totalPages = Math.ceil(total / limit);
27
+ return { data, total, page, totalPages };
28
+ },
29
+ put: async (req, res, options) => {
30
+ /** @type {import('./cyberia-entity.model.js').CyberiaEntityModel} */
31
+ const CyberiaEntity = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.CyberiaEntity;
32
+ return await CyberiaEntity.findByIdAndUpdate(req.params.id, req.body);
33
+ },
34
+ delete: async (req, res, options) => {
35
+ /** @type {import('./cyberia-entity.model.js').CyberiaEntityModel} */
36
+ const CyberiaEntity = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.CyberiaEntity;
37
+ if (req.params.id) return await CyberiaEntity.findByIdAndDelete(req.params.id);
38
+ else return await CyberiaEntity.deleteMany();
39
+ },
40
+ };
41
+
42
+ export { CyberiaEntityService };
@@ -0,0 +1,368 @@
1
+ /**
2
+ * Fallback World Generator — pure-function module.
3
+ *
4
+ * Produces a complete in-memory multi-map world with portal topology,
5
+ * floor tiles, obstacles, foreground, and bots. Nothing is persisted
6
+ * to MongoDB — the result is regenerated on every call.
7
+ *
8
+ * Everything that can be random IS random (counts within ranges,
9
+ * positions, dimensions). The floor is the exception: it deterministically
10
+ * covers the entire map grid so there are no gaps.
11
+ *
12
+ * Shared by:
13
+ * - gRPC server (getFullInstance fallback path)
14
+ * - CyberiaInstanceService (API / GUI fallback endpoint)
15
+ *
16
+ * All exported functions are stateless and synchronous.
17
+ *
18
+ * @module src/api/cyberia-instance/cyberia-fallback-world
19
+ */
20
+
21
+ import {
22
+ connectPortals,
23
+ generateObstacles,
24
+ generateForeground,
25
+ colorToRgba,
26
+ findColor,
27
+ randInt,
28
+ OccupancyGrid,
29
+ BOT_RANGE,
30
+ BOT_WEAPON_CHANCE,
31
+ PORTAL_DIM_RANGE,
32
+ PORTAL_COUNT_RANGE,
33
+ PORTAL_MODE_LIST,
34
+ PORTAL_MODE_COLOR_KEY,
35
+ EXTRA_PORTAL_MODES,
36
+ PORTAL_MODES,
37
+ } from './cyberia-portal-connector.js';
38
+
39
+ import {
40
+ CYBERIA_INSTANCE_CONF_DEFAULTS,
41
+ ENTITY_TYPE_DEFAULTS,
42
+ } from '../cyberia-instance-conf/cyberia-instance-conf.defaults.js';
43
+
44
+ import { DefaultCyberiaItems } from '../../client/components/cyberia-portal/CommonCyberiaPortal.js';
45
+
46
+ // ── Defaults ─────────────────────────────────────────────────────────────────
47
+
48
+ const DEFAULT_MAP_COUNT = 4;
49
+ const DEFAULT_GRID_SIZE = 64;
50
+ const DEFAULT_FLOOR_TILE_DIM = 4;
51
+ const DEFAULT_BOT_DIM_RANGE = [2, 3];
52
+
53
+ /** NPC skin pool — all items with type 'skin' from DefaultCyberiaItems. */
54
+ const BOT_SKIN_POOL = DefaultCyberiaItems.filter((e) => e.item.type === 'skin').map((e) => e.item.id);
55
+
56
+ // ── Floor generator ──────────────────────────────────────────────────────────
57
+
58
+ /**
59
+ * Generate floor tiles that cover the entire map grid.
60
+ * Floor is NOT random — it tiles deterministically so every cell is covered.
61
+ *
62
+ * @param {{ gridX: number, gridY: number }} mapDims
63
+ * @param {Array<{ key: string, r: number, g: number, b: number, a: number }>} colors
64
+ * @param {object} [opts]
65
+ * @param {number} [opts.tileDim=4] Floor tile size in cells.
66
+ * @returns {object[]}
67
+ */
68
+ function generateFloorEntities(mapDims, colors, opts = {}) {
69
+ const { tileDim = DEFAULT_FLOOR_TILE_DIM } = opts;
70
+ const floorDefault = ENTITY_TYPE_DEFAULTS.find((d) => d.entityType === 'floor');
71
+ const floorItemIds = floorDefault?.liveItemIds?.length ? [...floorDefault.liveItemIds] : [];
72
+ const floorColor = findColor(colors, 'FLOOR');
73
+ const rgba = floorColor ? colorToRgba(floorColor) : '';
74
+
75
+ const entities = [];
76
+ for (let y = 0; y < mapDims.gridY; y += tileDim) {
77
+ for (let x = 0; x < mapDims.gridX; x += tileDim) {
78
+ entities.push({
79
+ entityType: 'floor',
80
+ initCellX: x,
81
+ initCellY: y,
82
+ dimX: Math.min(tileDim, mapDims.gridX - x),
83
+ dimY: Math.min(tileDim, mapDims.gridY - y),
84
+ color: rgba,
85
+ objectLayerItemIds: floorItemIds,
86
+ });
87
+ }
88
+ }
89
+ return entities;
90
+ }
91
+
92
+ // ── Portal entity generator ──────────────────────────────────────────────────
93
+
94
+ /**
95
+ * Generate a portal entity at a random walkable position with random dimensions.
96
+ *
97
+ * @param {{ gridX: number, gridY: number }} mapDims
98
+ * @param {Array<{ key: string, r: number, g: number, b: number, a: number }>} colors
99
+ * @param {OccupancyGrid} [grid] If provided, places portal only on walkable cells and marks them blocked.
100
+ * @param {string} [portalSubtype] One of PORTAL_MODE_LIST values. Determines the portal colour.
101
+ * @returns {object|null} Portal entity or null if no valid position found.
102
+ */
103
+ function generatePortalEntity(mapDims, colors, grid, portalSubtype) {
104
+ // Resolve colour from subtype-specific palette key, falling back to generic PORTAL
105
+ const colorKey = portalSubtype ? PORTAL_MODE_COLOR_KEY[portalSubtype] : 'PORTAL';
106
+ const portalColor = findColor(colors, colorKey) || findColor(colors, 'PORTAL');
107
+ const rgba = portalColor ? colorToRgba(portalColor) : 'rgba(0, 200, 200, 1)';
108
+ const dimX = randInt(PORTAL_DIM_RANGE[0], PORTAL_DIM_RANGE[1]);
109
+ const dimY = randInt(PORTAL_DIM_RANGE[0], PORTAL_DIM_RANGE[1]);
110
+
111
+ if (grid) {
112
+ const pos = grid.findPosition(dimX, dimY);
113
+ if (!pos) return null;
114
+ grid.block(pos.x, pos.y, dimX, dimY);
115
+ return {
116
+ entityType: 'portal',
117
+ portalSubtype: portalSubtype || 'inter-portal',
118
+ initCellX: pos.x,
119
+ initCellY: pos.y,
120
+ dimX,
121
+ dimY,
122
+ color: rgba,
123
+ objectLayerItemIds: [],
124
+ };
125
+ }
126
+
127
+ const maxX = Math.max(0, mapDims.gridX - dimX);
128
+ const maxY = Math.max(0, mapDims.gridY - dimY);
129
+ return {
130
+ entityType: 'portal',
131
+ portalSubtype: portalSubtype || 'inter-portal',
132
+ initCellX: randInt(0, maxX),
133
+ initCellY: randInt(0, maxY),
134
+ dimX,
135
+ dimY,
136
+ color: rgba,
137
+ objectLayerItemIds: [],
138
+ };
139
+ }
140
+
141
+ /**
142
+ * Generate a random number of portal entities for a map, each with a
143
+ * randomly assigned portal subtype (and corresponding colour).
144
+ *
145
+ * @param {{ gridX: number, gridY: number }} mapDims
146
+ * @param {Array<{ key: string, r: number, g: number, b: number, a: number }>} colors
147
+ * @param {object} [opts]
148
+ * @param {number} [opts.count] Override count (ignores range).
149
+ * @param {OccupancyGrid} [opts.grid] If provided, places portals only on walkable cells.
150
+ * @returns {object[]}
151
+ */
152
+ function generatePortalEntities(mapDims, colors, opts = {}) {
153
+ const count = opts.count ?? randInt(PORTAL_COUNT_RANGE[0], PORTAL_COUNT_RANGE[1]);
154
+ const entities = [];
155
+ for (let i = 0; i < count; i++) {
156
+ // First portal is always inter-portal (reserved for the ring topology);
157
+ // extra portals get a random non-ring subtype.
158
+ const subtype =
159
+ i === 0 ? PORTAL_MODES.INTER_PORTAL : EXTRA_PORTAL_MODES[Math.floor(Math.random() * EXTRA_PORTAL_MODES.length)];
160
+ const portal = generatePortalEntity(mapDims, colors, opts.grid, subtype);
161
+ if (portal) entities.push(portal);
162
+ }
163
+ return entities;
164
+ }
165
+
166
+ // ── Bot generator ────────────────────────────────────────────────────────────
167
+
168
+ /**
169
+ * Generate bot entities for a map.
170
+ *
171
+ * - Each bot picks a random skin from BOT_SKIN_POOL for visual variety.
172
+ * - Random chance to also carry `atlas_pistol_mk2` weapon.
173
+ * - Random count within BOT_RANGE, random positions, random dimensions.
174
+ * - Uses the BOT palette color as fallback.
175
+ *
176
+ * @param {{ gridX: number, gridY: number }} mapDims
177
+ * @param {Array<{ key: string, r: number, g: number, b: number, a: number }>} colors
178
+ * @param {object} [opts]
179
+ * @param {number} [opts.count] Override count (ignores range).
180
+ * @param {number} [opts.spawnRadius]
181
+ * @param {number} [opts.aggroRange]
182
+ * @param {number} [opts.maxLife]
183
+ * @param {OccupancyGrid} [opts.grid] If provided, places bots only on walkable cells.
184
+ * @returns {object[]}
185
+ */
186
+ function generateBots(mapDims, colors, opts = {}) {
187
+ const count = opts.count ?? randInt(BOT_RANGE[0], BOT_RANGE[1]);
188
+ const { spawnRadius = 5, aggroRange = 10, maxLife = 100 } = opts;
189
+ const botColor = findColor(colors, 'BOT');
190
+ const rgba = botColor ? colorToRgba(botColor) : 'rgba(255, 128, 0, 1)';
191
+
192
+ const entities = [];
193
+ for (let i = 0; i < count; i++) {
194
+ const dim = randInt(DEFAULT_BOT_DIM_RANGE[0], DEFAULT_BOT_DIM_RANGE[1]);
195
+
196
+ let cellX, cellY;
197
+ if (opts.grid) {
198
+ const pos = opts.grid.findPosition(dim, dim);
199
+ if (!pos) continue;
200
+ opts.grid.block(pos.x, pos.y, dim, dim);
201
+ cellX = pos.x;
202
+ cellY = pos.y;
203
+ } else {
204
+ const maxX = Math.max(0, mapDims.gridX - dim);
205
+ const maxY = Math.max(0, mapDims.gridY - dim);
206
+ cellX = randInt(0, maxX);
207
+ cellY = randInt(0, maxY);
208
+ }
209
+
210
+ const skin = BOT_SKIN_POOL[Math.floor(Math.random() * BOT_SKIN_POOL.length)];
211
+ const hasWeapon = Math.random() < BOT_WEAPON_CHANCE;
212
+ const itemIds = hasWeapon ? [skin, 'atlas_pistol_mk2'] : [skin];
213
+
214
+ entities.push({
215
+ entityType: 'bot',
216
+ initCellX: cellX,
217
+ initCellY: cellY,
218
+ dimX: dim,
219
+ dimY: dim,
220
+ color: rgba,
221
+ objectLayerItemIds: itemIds,
222
+ spawnRadius,
223
+ aggroRange,
224
+ maxLife,
225
+ lifeRegen: 0,
226
+ });
227
+ }
228
+ return entities;
229
+ }
230
+
231
+ // ── Single map generator ─────────────────────────────────────────────────────
232
+
233
+ /**
234
+ * Generate a complete in-memory map with all entity types.
235
+ * Everything except floor tiles is randomized.
236
+ *
237
+ * @param {string} mapCode
238
+ * @param {Array} colors Palette from CYBERIA_INSTANCE_CONF_DEFAULTS.
239
+ * @param {object} [opts]
240
+ * @param {number} [opts.gridSize]
241
+ * @param {number} [opts.obstacleCount]
242
+ * @param {number} [opts.foregroundCount]
243
+ * @param {number} [opts.botCount]
244
+ * @returns {object} CyberiaMap-shaped plain object.
245
+ */
246
+ function generateFallbackMap(mapCode, colors, opts = {}) {
247
+ const gridSize = opts.gridSize || DEFAULT_GRID_SIZE;
248
+ const mapDims = { gridX: gridSize, gridY: gridSize };
249
+
250
+ // 1. Floor — deterministic full coverage
251
+ const floors = generateFloorEntities(mapDims, colors);
252
+
253
+ // 2. Obstacles — random count, position, dimensions (placed first)
254
+ const obstacles = generateObstacles(mapDims, colors, { count: opts.obstacleCount });
255
+
256
+ // 3. Build occupancy grid from obstacles
257
+ const grid = new OccupancyGrid(gridSize, gridSize);
258
+ grid.addObstacles(obstacles);
259
+
260
+ // 4. Portals — placed on walkable cells only, then blocked so bots avoid them
261
+ const portalEntities = generatePortalEntities(mapDims, colors, { grid });
262
+
263
+ // 5. Bots — placed on walkable cells (avoids obstacles and portals)
264
+ const bots = generateBots(mapDims, colors, { count: opts.botCount, grid });
265
+
266
+ // 6. Foreground — decorative, no collision restriction
267
+ const foreground = generateForeground(mapDims, colors, { count: opts.foregroundCount });
268
+
269
+ const entities = [...floors, ...obstacles, ...portalEntities, ...foreground, ...bots];
270
+
271
+ return {
272
+ code: mapCode,
273
+ name: `Fallback ${mapCode}`,
274
+ gridX: gridSize,
275
+ gridY: gridSize,
276
+ cellWidth: 32,
277
+ cellHeight: 32,
278
+ entities,
279
+ };
280
+ }
281
+
282
+ // ── Full world generator ─────────────────────────────────────────────────────
283
+
284
+ /**
285
+ * Generate a complete in-memory fallback world: multiple maps connected
286
+ * by a portal ring topology.
287
+ *
288
+ * Returns a plain-object structure matching what a real CyberiaInstance
289
+ * + CyberiaMap query would return, so consumers can treat it identically.
290
+ *
291
+ * Nothing is persisted to MongoDB.
292
+ *
293
+ * @param {object} [opts]
294
+ * @param {number} [opts.mapCount=4] Number of maps to generate.
295
+ * @param {number} [opts.gridSize=64] Grid size per map.
296
+ * @param {number} [opts.obstacleCount] Obstacles per map (random if omitted).
297
+ * @param {number} [opts.foregroundCount] Foreground entities per map (random if omitted).
298
+ * @param {number} [opts.botCount] Bots per map (random if omitted).
299
+ * @param {Array} [opts.colors] Override palette.
300
+ * @returns {{
301
+ * instance: object,
302
+ * maps: object[],
303
+ * portals: object[],
304
+ * topology: string,
305
+ * config: object,
306
+ * _fallback: true
307
+ * }}
308
+ */
309
+ function generateFallbackWorld(opts = {}) {
310
+ const {
311
+ mapCount = DEFAULT_MAP_COUNT,
312
+ gridSize,
313
+ obstacleCount,
314
+ foregroundCount,
315
+ botCount,
316
+ colors = CYBERIA_INSTANCE_CONF_DEFAULTS.colors,
317
+ } = opts;
318
+
319
+ // Generate map codes.
320
+ const mapCodes = [];
321
+ for (let i = 0; i < mapCount; i++) {
322
+ mapCodes.push(`fallback-map-${i}`);
323
+ }
324
+
325
+ // Generate each map (all randomized independently).
326
+ const maps = mapCodes.map((code) =>
327
+ generateFallbackMap(code, colors, {
328
+ gridSize,
329
+ obstacleCount,
330
+ foregroundCount,
331
+ botCount,
332
+ }),
333
+ );
334
+
335
+ // Connect maps with portal topology using the portal connector module.
336
+ const { portals, topology } = connectPortals(mapCodes, maps);
337
+
338
+ // Build the instance shell (never persisted — no mongoId).
339
+ const instance = {
340
+ code: 'fallback',
341
+ name: 'Fallback Instance',
342
+ description: 'Auto-generated procedural world (not persisted)',
343
+ tags: ['fallback', 'procedural'],
344
+ cyberiaMapCodes: mapCodes,
345
+ portals,
346
+ topologyMode: 'procedural',
347
+ };
348
+
349
+ return {
350
+ instance,
351
+ maps,
352
+ portals,
353
+ topology,
354
+ config: CYBERIA_INSTANCE_CONF_DEFAULTS,
355
+ _fallback: true,
356
+ };
357
+ }
358
+
359
+ // ── Public API ───────────────────────────────────────────────────────────────
360
+
361
+ export {
362
+ generateFallbackWorld,
363
+ generateFallbackMap,
364
+ generateFloorEntities,
365
+ generatePortalEntity,
366
+ generatePortalEntities,
367
+ generateBots,
368
+ };
@@ -0,0 +1,92 @@
1
+ import { loggerFactory } from '../../server/logger.js';
2
+ import { CyberiaInstanceService } from './cyberia-instance.service.js';
3
+
4
+ const logger = loggerFactory(import.meta);
5
+
6
+ const CyberiaInstanceController = {
7
+ fallbackWorld: async (req, res, options) => {
8
+ try {
9
+ const result = await CyberiaInstanceService.fallbackWorld(req);
10
+ return res.status(200).json({ status: 'success', data: result });
11
+ } catch (error) {
12
+ logger.error(error, error.stack);
13
+ return res.status(400).json({ status: 'error', message: error.message });
14
+ }
15
+ },
16
+ portalConnect: async (req, res, options) => {
17
+ try {
18
+ const result = await CyberiaInstanceService.portalConnect(req, res, options);
19
+ return res.status(200).json({ status: 'success', data: result });
20
+ } catch (error) {
21
+ logger.error(error, error.stack);
22
+ return res.status(400).json({ status: 'error', message: error.message });
23
+ }
24
+ },
25
+ post: async (req, res, options) => {
26
+ try {
27
+ const result = await CyberiaInstanceService.post(req, res, options);
28
+ return res.status(200).json({
29
+ status: 'success',
30
+ data: result,
31
+ });
32
+ } catch (error) {
33
+ logger.error(error, error.stack);
34
+ return res.status(400).json({
35
+ status: 'error',
36
+ message: error.message,
37
+ });
38
+ }
39
+ },
40
+ get: async (req, res, options) => {
41
+ try {
42
+ const { page, limit } = req.query;
43
+ const result = await CyberiaInstanceService.get(
44
+ { ...req, query: { ...req.query, page: parseInt(page), limit: parseInt(limit) } },
45
+ res,
46
+ options,
47
+ );
48
+ return res.status(200).json({
49
+ status: 'success',
50
+ data: result,
51
+ });
52
+ } catch (error) {
53
+ logger.error(error, error.stack);
54
+ return res.status(400).json({
55
+ status: 'error',
56
+ message: error.message,
57
+ });
58
+ }
59
+ },
60
+ put: async (req, res, options) => {
61
+ try {
62
+ const result = await CyberiaInstanceService.put(req, res, options);
63
+ return res.status(200).json({
64
+ status: 'success',
65
+ data: result,
66
+ });
67
+ } catch (error) {
68
+ logger.error(error, error.stack);
69
+ return res.status(400).json({
70
+ status: 'error',
71
+ message: error.message,
72
+ });
73
+ }
74
+ },
75
+ delete: async (req, res, options) => {
76
+ try {
77
+ const result = await CyberiaInstanceService.delete(req, res, options);
78
+ return res.status(200).json({
79
+ status: 'success',
80
+ data: result,
81
+ });
82
+ } catch (error) {
83
+ logger.error(error, error.stack);
84
+ return res.status(400).json({
85
+ status: 'error',
86
+ message: error.message,
87
+ });
88
+ }
89
+ },
90
+ };
91
+
92
+ export { CyberiaInstanceController };