cyberia 3.2.22 → 3.2.70

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 (185) hide show
  1. package/.env.example +127 -68
  2. package/.github/workflows/cyberia-client.cd.yml +40 -0
  3. package/.github/workflows/cyberia-server.cd.yml +40 -0
  4. package/.github/workflows/docker-image.cyberia-client.ci.yml +49 -0
  5. package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +48 -0
  6. package/.github/workflows/docker-image.cyberia-server.ci.yml +69 -0
  7. package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +69 -0
  8. package/.github/workflows/docker-image.engine-cyberia.ci.yml +52 -0
  9. package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +52 -0
  10. package/.github/workflows/engine-cyberia.cd.yml +33 -24
  11. package/.github/workflows/engine-cyberia.ci.yml +13 -3
  12. package/.github/workflows/ghpkg.ci.yml +88 -1
  13. package/.github/workflows/gitlab.ci.yml +1 -1
  14. package/.github/workflows/hardhat.ci.yml +1 -1
  15. package/.github/workflows/npmpkg.ci.yml +10 -7
  16. package/.github/workflows/publish.ci.yml +2 -2
  17. package/.github/workflows/publish.cyberia.ci.yml +5 -16
  18. package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -8
  19. package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
  20. package/CHANGELOG.md +301 -1
  21. package/CLI-HELP.md +71 -6
  22. package/Dockerfile +141 -43
  23. package/Dockerfile.dev +143 -0
  24. package/Dockerfile.test +165 -0
  25. package/README.md +1 -1
  26. package/baremetal/commission-workflows.json +1 -0
  27. package/bin/build.js +31 -0
  28. package/bin/cyberia.js +1080 -184
  29. package/bin/deploy.js +2 -2
  30. package/bin/index.js +1080 -184
  31. package/bump.config.js +1 -0
  32. package/compose.env +131 -0
  33. package/conf.js +18 -1
  34. package/deployment.yaml +2 -2
  35. package/docker-compose.yml +316 -0
  36. package/hardhat/hardhat.config.js +2 -2
  37. package/hardhat/package-lock.json +620 -2041
  38. package/hardhat/package.json +7 -5
  39. package/hardhat/scripts/deployObjectLayerToken.js +18 -18
  40. package/hardhat/test/ObjectLayerToken.js +378 -274
  41. package/ipfs/configure-ipfs.sh +13 -0
  42. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
  43. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
  44. package/manifests/deployment/dd-cyberia-development/deployment.yaml +2 -2
  45. package/manifests/deployment/dd-cyberia-development/grpc-service.yaml +17 -0
  46. package/manifests/deployment/dd-cyberia-development/pv-pvc.yaml +32 -0
  47. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  48. package/mongodb/entrypoint.sh +76 -0
  49. package/nginx.conf +87 -0
  50. package/package.json +31 -19
  51. package/pv-pvc.yaml +32 -0
  52. package/scripts/disk-clean.sh +85 -60
  53. package/scripts/kubeadm-node-setup.sh +317 -0
  54. package/scripts/rocky-kickstart.sh +877 -185
  55. package/scripts/rpmfusion-ffmpeg-setup.sh +26 -50
  56. package/scripts/test-monitor.sh +3 -5
  57. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +43 -7
  58. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +17 -25
  59. package/src/api/cyberia-action/cyberia-action.controller.js +19 -0
  60. package/src/api/cyberia-action/cyberia-action.model.js +21 -29
  61. package/src/api/cyberia-action/cyberia-action.router.js +42 -7
  62. package/src/api/cyberia-action/cyberia-action.service.js +20 -4
  63. package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +58 -57
  64. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +37 -6
  65. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +8 -2
  66. package/src/api/cyberia-entity/cyberia-entity.router.js +39 -7
  67. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +74 -0
  68. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.model.js +67 -0
  69. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +63 -0
  70. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +46 -0
  71. package/src/api/cyberia-instance/cyberia-fallback-world.js +62 -10
  72. package/src/api/cyberia-instance/cyberia-instance.model.js +32 -2
  73. package/src/api/cyberia-instance/cyberia-instance.router.js +6 -6
  74. package/src/api/cyberia-instance/cyberia-world-generator.js +116 -3
  75. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +3 -0
  76. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +39 -7
  77. package/src/api/cyberia-map/cyberia-map.router.js +21 -6
  78. package/src/api/cyberia-quest/cyberia-quest.controller.js +38 -0
  79. package/src/api/cyberia-quest/cyberia-quest.router.js +47 -7
  80. package/src/api/cyberia-quest/cyberia-quest.service.js +59 -4
  81. package/src/api/cyberia-saga/cyberia-saga.controller.js +74 -0
  82. package/src/api/cyberia-saga/cyberia-saga.model.js +59 -0
  83. package/src/api/cyberia-saga/cyberia-saga.router.js +63 -0
  84. package/src/api/cyberia-saga/cyberia-saga.service.js +42 -0
  85. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +551 -129
  86. package/src/api/cyberia-skill/cyberia-skill.controller.js +74 -0
  87. package/src/api/cyberia-skill/cyberia-skill.model.js +50 -0
  88. package/src/api/cyberia-skill/cyberia-skill.router.js +63 -0
  89. package/src/api/cyberia-skill/cyberia-skill.service.js +42 -0
  90. package/src/api/ipfs/ipfs.service.js +28 -15
  91. package/src/api/object-layer/object-layer.router.js +25 -15
  92. package/src/api/object-layer/object-layer.service.js +15 -20
  93. package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +39 -7
  94. package/src/cli/baremetal.js +717 -16
  95. package/src/cli/cluster.js +80 -5
  96. package/src/cli/deploy.js +127 -11
  97. package/src/cli/docker-compose.js +648 -0
  98. package/src/cli/env.js +11 -2
  99. package/src/cli/fs.js +75 -30
  100. package/src/cli/image.js +129 -51
  101. package/src/cli/index.js +110 -6
  102. package/src/cli/kickstart.js +142 -20
  103. package/src/cli/release.js +46 -4
  104. package/src/cli/repository.js +238 -33
  105. package/src/cli/run.js +520 -114
  106. package/src/cli/secrets.js +82 -48
  107. package/src/cli/ssh.js +234 -0
  108. package/src/cli/static.js +2 -2
  109. package/src/client/components/cyberia/ActionEngineCyberia.js +1867 -0
  110. package/src/client/components/cyberia/EntityEngineCyberia.js +585 -0
  111. package/src/client/components/cyberia/InstanceEngineCyberia.js +219 -13
  112. package/src/client/components/cyberia/MapEngineCyberia.js +154 -71
  113. package/src/client/components/cyberia/ObjectLayerEngineModal.js +40 -63
  114. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +34 -20
  115. package/src/client/components/cyberia/SharedDefaultsCyberia.js +195 -4
  116. package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +66 -0
  117. package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +8 -0
  118. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +8 -0
  119. package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +45 -27
  120. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +3 -3
  121. package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +39 -22
  122. package/src/client/public/cyberia-docs/CYBERIA-LORE.md +88 -0
  123. package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +394 -0
  124. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +8 -1
  125. package/src/client/public/cyberia-docs/CYBERIA.md +1 -1
  126. package/src/client/public/cyberia-docs/QUEST-SYSTEM.md +5 -5
  127. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  128. package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
  129. package/src/client/services/cyberia-entity-type-default/cyberia-entity-type-default.service.js +99 -0
  130. package/src/client/services/cyberia-instance/cyberia-instance.management.js +2 -2
  131. package/src/client/services/cyberia-map/cyberia-map.management.js +2 -2
  132. package/src/client/services/cyberia-saga/cyberia-saga.service.js +99 -0
  133. package/src/client/services/cyberia-skill/cyberia-skill.service.js +99 -0
  134. package/src/client/services/object-layer/object-layer.management.js +6 -6
  135. package/src/{server → client-builder}/client-build-docs.js +15 -5
  136. package/src/{server → client-builder}/client-build-live.js +3 -3
  137. package/src/{server → client-builder}/client-build.js +25 -22
  138. package/src/{server → client-builder}/client-dev-server.js +3 -3
  139. package/src/{server → client-builder}/client-icons.js +2 -2
  140. package/src/{server → client-builder}/ssr.js +5 -5
  141. package/src/client.build.js +1 -3
  142. package/src/client.dev.js +1 -1
  143. package/src/db/mongo/MongoBootstrap.js +12 -12
  144. package/src/grpc/cyberia/grpc-server.js +255 -70
  145. package/src/index.js +12 -1
  146. package/src/mailer/EmailRender.js +1 -1
  147. package/src/{server → projects/cyberia}/atlas-sprite-sheet-generator.js +2 -2
  148. package/src/{server → projects/cyberia}/besu-genesis-generator.js +3 -3
  149. package/src/projects/cyberia/catalog-cyberia.js +81 -0
  150. package/src/projects/cyberia/gemini-client.js +175 -0
  151. package/src/projects/cyberia/generate-saga.js +2107 -0
  152. package/src/{server → projects/cyberia}/ipfs-client.js +2 -2
  153. package/src/{server → projects/cyberia}/object-layer.js +12 -108
  154. package/src/{server → projects/cyberia}/semantic-layer-generator-floor.js +1 -1
  155. package/src/{server → projects/cyberia}/semantic-layer-generator-resource.js +1 -1
  156. package/src/{server → projects/cyberia}/semantic-layer-generator-skin.js +1 -1
  157. package/src/{server → projects/cyberia}/semantic-layer-generator.js +2 -2
  158. package/src/{server → projects/cyberia}/shape-generator.js +2 -2
  159. package/src/{server → projects/underpost}/catalog-underpost.js +1 -2
  160. package/src/runtime/cyberia-client/Dockerfile +27 -61
  161. package/src/runtime/cyberia-client/Dockerfile.dev +20 -12
  162. package/src/runtime/cyberia-server/Dockerfile +21 -20
  163. package/src/runtime/cyberia-server/Dockerfile.dev +17 -8
  164. package/src/runtime/engine-cyberia/Dockerfile +143 -0
  165. package/src/runtime/engine-cyberia/Dockerfile.dev +143 -0
  166. package/src/runtime/engine-cyberia/Dockerfile.test +165 -0
  167. package/src/runtime/engine-cyberia/compose.env +131 -0
  168. package/src/runtime/engine-cyberia/docker-compose.yml +316 -0
  169. package/src/runtime/engine-cyberia/ipfs/configure-ipfs.sh +13 -0
  170. package/src/runtime/engine-cyberia/mongodb/entrypoint.sh +76 -0
  171. package/src/runtime/engine-cyberia/nginx.conf +87 -0
  172. package/src/runtime/express/Express.js +2 -2
  173. package/src/runtime/nginx/Nginx.js +250 -0
  174. package/src/server/catalog.js +9 -14
  175. package/src/server/conf.js +3 -6
  176. package/src/server/runtime-status.js +18 -1
  177. package/src/server/start.js +12 -2
  178. package/src/server.js +6 -2
  179. package/test/cyberia-instance-conf-defaults.test.js +140 -0
  180. package/test/deploy-monitor.test.js +26 -10
  181. package/test/shape-generator.test.js +7 -1
  182. package/typedoc.dd-cyberia.json +3 -1
  183. package/typedoc.json +3 -1
  184. /package/src/client/ssr/{Render.js → RootDocument.js} +0 -0
  185. /package/src/{server → client-builder}/client-formatted.js +0 -0
@@ -24,21 +24,25 @@ import {
24
24
  generateObstacles,
25
25
  generateForeground,
26
26
  generateResources,
27
+ generateStatic,
27
28
  generateFloorEntities,
28
29
  generatePortalEntity,
29
30
  generatePortalEntities,
30
31
  generateBots,
32
+ generateActionProviderBots,
31
33
  OccupancyGrid,
32
34
  } from './cyberia-world-generator.js';
33
35
 
34
36
  import {
35
37
  CYBERIA_INSTANCE_CONF_DEFAULTS,
38
+ DEFAULT_PLAYER_SPAWN,
36
39
  ENTITY_TYPE_DEFAULTS,
37
40
  RESOURCE_ENTITY_TYPE_DEFAULTS,
38
- DefaultCyberiaItems,
41
+ DefaultSkillConfig,
39
42
  } from '../cyberia-server-defaults/cyberia-server-defaults.js';
40
43
  import {
41
44
  CYBERIA_CLIENT_HINTS_DEFAULTS,
45
+ DefaultCyberiaItems,
42
46
  PALETTE,
43
47
  } from '../../client/components/cyberia/SharedDefaultsCyberia.js';
44
48
 
@@ -62,7 +66,9 @@ const KNOWN_DEFAULT_ITEM_IDS = new Set(DefaultCyberiaItems.map((e) => e.item.id)
62
66
 
63
67
  function collectReferencedItemIds() {
64
68
  const ids = new Set();
65
- const push = (id) => { if (typeof id === 'string' && id.length > 0) ids.add(id); };
69
+ const push = (id) => {
70
+ if (typeof id === 'string' && id.length > 0) ids.add(id);
71
+ };
66
72
 
67
73
  for (const e of ENTITY_TYPE_DEFAULTS) {
68
74
  (e.liveItemIds || []).forEach(push);
@@ -75,6 +81,17 @@ function collectReferencedItemIds() {
75
81
  (r.deadItemIds || []).forEach(push);
76
82
  (r.dropItemIds || []).forEach(push);
77
83
  }
84
+ // Skill trigger items and the entities they summon must also resolve to an
85
+ // ObjectLayer. Runtime placeholders ('$active_skin', …) are resolved by the
86
+ // server and are intentionally excluded.
87
+ for (const sk of DefaultSkillConfig) {
88
+ push(sk.triggerItemId);
89
+ for (const s of sk.skills || []) {
90
+ if (typeof s.summonedEntityItemId === 'string' && !s.summonedEntityItemId.startsWith('$')) {
91
+ push(s.summonedEntityItemId);
92
+ }
93
+ }
94
+ }
78
95
  return ids;
79
96
  }
80
97
 
@@ -96,6 +113,16 @@ function auditFallbackItemIds() {
96
113
 
97
114
  // ── Single map generator ─────────────────────────────────────────────────────
98
115
 
116
+ /** AABB overlap test for two entities in cell space. */
117
+ function entitiesOverlap(a, b) {
118
+ return (
119
+ a.initCellX < b.initCellX + b.dimX &&
120
+ a.initCellX + a.dimX > b.initCellX &&
121
+ a.initCellY < b.initCellY + b.dimY &&
122
+ a.initCellY + a.dimY > b.initCellY
123
+ );
124
+ }
125
+
99
126
  /**
100
127
  * Generate a complete in-memory map with all entity types.
101
128
  * Everything except floor tiles is randomized.
@@ -108,6 +135,7 @@ function auditFallbackItemIds() {
108
135
  * @param {number} [opts.foregroundCount]
109
136
  * @param {number} [opts.botCount]
110
137
  * @param {number} [opts.resourceCount]
138
+ * @param {number} [opts.staticCount]
111
139
  * @returns {object} CyberiaMap-shaped plain object.
112
140
  */
113
141
  function generateFallbackMap(mapCode, colors, opts = {}) {
@@ -117,26 +145,46 @@ function generateFallbackMap(mapCode, colors, opts = {}) {
117
145
  // 1. Floor — deterministic full coverage
118
146
  const floors = generateFloorEntities(mapDims, colors);
119
147
 
120
- // 2. Obstaclesrandom count, position, dimensions (placed first)
121
- const obstacles = generateObstacles(mapDims, colors, { count: opts.obstacleCount });
148
+ // 2. Action-provider NPCs fixed cells from DefaultCyberiaActions. Built
149
+ // first so their mission cells are guaranteed clear and walkable.
150
+ const actionProviders = generateActionProviderBots(mapCode, colors);
122
151
 
123
- // 3. Build occupancy grid from obstacles
152
+ // 3. Obstacles random, minus any overlapping an action-provider cell so
153
+ // the mission NPCs always stand on walkable ground.
154
+ const obstacles = generateObstacles(mapDims, colors, { count: opts.obstacleCount }).filter(
155
+ (o) => !actionProviders.some((ap) => entitiesOverlap(o, ap)),
156
+ );
157
+
158
+ // 4. Build occupancy grid from obstacles, then reserve action-provider cells.
124
159
  const grid = new OccupancyGrid(gridSize, gridSize);
125
160
  grid.addObstacles(obstacles);
161
+ for (const ap of actionProviders) grid.block(ap.initCellX, ap.initCellY, ap.dimX, ap.dimY);
126
162
 
127
- // 4. Portals — placed on walkable cells only, then blocked so bots avoid them
163
+ // 5. Portals — placed on walkable cells only, then blocked so bots avoid them
128
164
  const portalEntities = generatePortalEntities(mapDims, colors, { grid });
129
165
 
130
- // 5. Bots — placed on walkable cells (avoids obstacles and portals)
166
+ // 6. Bots — placed on walkable cells (avoids obstacles, portals, NPCs)
131
167
  const bots = generateBots(mapDims, colors, { count: opts.botCount, grid });
132
168
 
133
- // 6. Resources — static exploitable entities placed on walkable cells
169
+ // 7. Resources — static exploitable entities placed on walkable cells
134
170
  const resources = generateResources(mapDims, colors, { count: opts.resourceCount, grid });
135
171
 
136
- // 7. Foreground — decorative, no collision restriction
172
+ // 8. Foreground — decorative, no collision restriction
137
173
  const foreground = generateForeground(mapDims, colors, { count: opts.foregroundCount });
138
174
 
139
- const entities = [...floors, ...obstacles, ...portalEntities, ...foreground, ...bots, ...resources];
175
+ // 9. Statics non-moving, passable decorators; depth-sorted with entities.
176
+ const statics = generateStatic(mapDims, colors, { count: opts.staticCount });
177
+
178
+ const entities = [
179
+ ...floors,
180
+ ...obstacles,
181
+ ...portalEntities,
182
+ ...foreground,
183
+ ...actionProviders,
184
+ ...bots,
185
+ ...resources,
186
+ ...statics,
187
+ ];
140
188
 
141
189
  return {
142
190
  code: mapCode,
@@ -167,6 +215,7 @@ function generateFallbackMap(mapCode, colors, opts = {}) {
167
215
  * @param {number} [opts.foregroundCount] Foreground entities per map (random if omitted).
168
216
  * @param {number} [opts.botCount] Bots per map (random if omitted).
169
217
  * @param {number} [opts.resourceCount] Resources per map (random if omitted).
218
+ * @param {number} [opts.staticCount] Static decorators per map (random if omitted).
170
219
  * @param {Array} [opts.colors] Override palette.
171
220
  * @returns {{
172
221
  * instance: object,
@@ -185,6 +234,7 @@ function generateFallbackWorld(opts = {}) {
185
234
  foregroundCount,
186
235
  botCount,
187
236
  resourceCount,
237
+ staticCount,
188
238
  // Palette lives in SharedDefaultsCyberia (presentation-owned). The
189
239
  // world generator only uses it to stamp a cosmetic rgba(...) string on
190
240
  // entities so the browser editor / preview can render a coloured
@@ -220,6 +270,7 @@ function generateFallbackWorld(opts = {}) {
220
270
  foregroundCount,
221
271
  botCount,
222
272
  resourceCount,
273
+ staticCount,
223
274
  }),
224
275
  );
225
276
 
@@ -235,6 +286,7 @@ function generateFallbackWorld(opts = {}) {
235
286
  cyberiaMapCodes: mapCodes,
236
287
  portals,
237
288
  topologyMode: 'procedural',
289
+ playerSpawn: { ...DEFAULT_PLAYER_SPAWN },
238
290
  };
239
291
 
240
292
  return {
@@ -31,6 +31,22 @@ const PortalEdgeSchema = new Schema(
31
31
  { _id: false },
32
32
  );
33
33
 
34
+ /**
35
+ * Authoritative initial spawn position for new players joining this instance.
36
+ * When `random` is false and `sourceMapCode` names a loaded map, players spawn at
37
+ * (sourceCellX, sourceCellY) on it; otherwise (random true, or no/unknown map)
38
+ * the server spawns them at a random walkable cell on a random map.
39
+ */
40
+ const PlayerSpawnSchema = new Schema(
41
+ {
42
+ sourceMapCode: { type: String, trim: true },
43
+ sourceCellX: { type: Number },
44
+ sourceCellY: { type: Number },
45
+ random: { type: Boolean, default: false },
46
+ },
47
+ { _id: false },
48
+ );
49
+
34
50
  /**
35
51
  * Graph container / instance.
36
52
  * The instance stores:
@@ -52,12 +68,26 @@ const CyberiaInstanceSchema = new Schema(
52
68
  // Vertex set of the graph
53
69
  cyberiaMapCodes: { type: [String], default: [] },
54
70
 
55
- // Instance-level object layer item IDs.
56
- itemIds: { type: [String], default: [] },
71
+ // Instance-level object layer item IDs. Each entry binds an item id to the
72
+ // instance and flags whether it seeds every player's starting inventory
73
+ // (defaultPlayerInventory → merged into the player entityDefault's
74
+ // defaultObjectLayers when the world is served to cyberia-server).
75
+ itemIds: {
76
+ type: [
77
+ {
78
+ id: { type: String, required: true },
79
+ defaultPlayerInventory: { type: Boolean, default: false },
80
+ },
81
+ ],
82
+ default: [],
83
+ },
57
84
 
58
85
  // Directed edge set of the graph
59
86
  portals: { type: [PortalEdgeSchema], default: [] },
60
87
 
88
+ // Authoritative initial spawn for new players (see PlayerSpawnSchema).
89
+ playerSpawn: { type: PlayerSpawnSchema, default: () => ({}) },
90
+
61
91
  // Optional topology generation mode
62
92
  seed: { type: String, default: '' },
63
93
  topologyMode: {
@@ -1,6 +1,6 @@
1
1
  import { loggerFactory } from '../../server/logger.js';
2
2
  import { CyberiaInstanceController } from './cyberia-instance.controller.js';
3
- import { userGuard, adminGuard } from '../../server/auth.js';
3
+ import { userGuard, moderatorGuard, adminGuard } from '../../server/auth.js';
4
4
  import express from 'express';
5
5
 
6
6
  const logger = loggerFactory(import.meta);
@@ -24,13 +24,13 @@ class CyberiaInstanceRouter {
24
24
  router.post(
25
25
  `/:id`,
26
26
  options.authMiddleware,
27
- userGuard,
27
+ moderatorGuard,
28
28
  async (req, res) => await CyberiaInstanceController.post(req, res, options),
29
29
  );
30
30
  router.post(
31
31
  `/`,
32
32
  options.authMiddleware,
33
- userGuard,
33
+ moderatorGuard,
34
34
  async (req, res) => await CyberiaInstanceController.post(req, res, options),
35
35
  );
36
36
  router.get(`/:id`, async (req, res) => await CyberiaInstanceController.get(req, res, options));
@@ -38,19 +38,19 @@ class CyberiaInstanceRouter {
38
38
  router.put(
39
39
  `/:id`,
40
40
  options.authMiddleware,
41
- userGuard,
41
+ moderatorGuard,
42
42
  async (req, res) => await CyberiaInstanceController.put(req, res, options),
43
43
  );
44
44
  router.put(
45
45
  `/`,
46
46
  options.authMiddleware,
47
- userGuard,
47
+ moderatorGuard,
48
48
  async (req, res) => await CyberiaInstanceController.put(req, res, options),
49
49
  );
50
50
  router.delete(
51
51
  `/:id`,
52
52
  options.authMiddleware,
53
- userGuard,
53
+ moderatorGuard,
54
54
  async (req, res) => await CyberiaInstanceController.delete(req, res, options),
55
55
  );
56
56
  router.delete(
@@ -19,10 +19,11 @@
19
19
  import {
20
20
  ENTITY_TYPE_DEFAULTS,
21
21
  RESOURCE_ENTITY_TYPE_DEFAULTS,
22
- DefaultCyberiaItems,
23
- ITEM_TYPES,
22
+ DefaultCyberiaActions,
24
23
  } from '../cyberia-server-defaults/cyberia-server-defaults.js';
25
24
 
25
+ import { DefaultCyberiaItems, ITEM_TYPES } from '../../client/components/cyberia/SharedDefaultsCyberia.js';
26
+
26
27
  import { PORTAL_MODES, PORTAL_MODE_COLOR_KEY, EXTRA_PORTAL_MODES } from './cyberia-portal-connector.js';
27
28
 
28
29
  // ── Color helpers ────────────────────────────────────────────────────────────
@@ -59,6 +60,7 @@ const OBSTACLE_RANGE = [20, 35];
59
60
  const FOREGROUND_RANGE = [10, 20];
60
61
  const BOT_RANGE = [8, 16];
61
62
  const RESOURCE_RANGE = [6, 12];
63
+ const STATIC_RANGE = [8, 16];
62
64
  const BOT_WEAPON_CHANCE = 0.6;
63
65
  const PORTAL_DIM_RANGE = [2, 3];
64
66
  const PORTAL_COUNT_RANGE = [2, 4];
@@ -273,6 +275,51 @@ function generateForeground(mapDims, colors, opts = {}) {
273
275
  return entities;
274
276
  }
275
277
 
278
+ /**
279
+ * Generate procedural static decorator entities for a map.
280
+ *
281
+ * Statics are non-moving, passable decorations (rocks, bushes, signage, etc.).
282
+ * Unlike obstacles they never block movement; unlike foreground they are
283
+ * depth-sorted with bots/players by the client (Y-axis ordering) so the player
284
+ * can pass behind or in front of them. They carry no life and are never
285
+ * exploitable. Placed on any cell (no occupancy reservation — they are
286
+ * transferable).
287
+ *
288
+ * @param {{ gridX: number, gridY: number }} mapDims Map grid dimensions.
289
+ * @param {Array<{ key: string, r: number, g: number, b: number, a: number }>} colors Palette.
290
+ * @param {object} [opts]
291
+ * @param {number} [opts.count] Override count (ignores range).
292
+ * @param {number} [opts.minDim=2] Minimum width/height (cells).
293
+ * @param {number} [opts.maxDim=4] Maximum width/height (cells).
294
+ * @returns {object[]} Array of CyberiaEntity plain objects.
295
+ */
296
+ function generateStatic(mapDims, colors, opts = {}) {
297
+ const { minDim = 2, maxDim = 4 } = opts;
298
+ const count = opts.count ?? randInt(STATIC_RANGE[0], STATIC_RANGE[1]);
299
+ const { gridX, gridY } = mapDims;
300
+
301
+ const staticColor = findColor(colors, 'STATIC');
302
+ const rgba = staticColor ? colorToRgba(staticColor) : 'rgba(120, 140, 110, 1)';
303
+
304
+ const entities = [];
305
+ for (let i = 0; i < count; i++) {
306
+ const dimX = randInt(minDim, maxDim);
307
+ const dimY = randInt(minDim, maxDim);
308
+ const maxX = Math.max(0, gridX - dimX);
309
+ const maxY = Math.max(0, gridY - dimY);
310
+ entities.push({
311
+ entityType: 'static',
312
+ initCellX: randInt(0, maxX),
313
+ initCellY: randInt(0, maxY),
314
+ dimX,
315
+ dimY,
316
+ color: rgba,
317
+ objectLayerItemIds: [],
318
+ });
319
+ }
320
+ return entities;
321
+ }
322
+
276
323
  /**
277
324
  * Generate procedural resource entities for a map.
278
325
  *
@@ -435,7 +482,8 @@ function generateBots(mapDims, colors, opts = {}) {
435
482
  cellY = randInt(0, maxY);
436
483
  }
437
484
 
438
- const skin = BOT_SKIN_POOL[Math.floor(Math.random() * BOT_SKIN_POOL.length)];
485
+ // const skin = BOT_SKIN_POOL[Math.floor(Math.random() * BOT_SKIN_POOL.length)];
486
+ const skin = 'kishins';
439
487
  const hasWeapon = Math.random() < BOT_WEAPON_CHANCE;
440
488
  const itemIds = hasWeapon ? [skin, 'atlas_pistol_mk2'] : [skin];
441
489
 
@@ -456,6 +504,67 @@ function generateBots(mapDims, colors, opts = {}) {
456
504
  return entities;
457
505
  }
458
506
 
507
+ /**
508
+ * Action-provider NPC bots — the fixed-position entities that back the
509
+ * default mission system. One passive bot is placed per `CyberiaAction`
510
+ * whose `sourceMapCode` matches `mapCode`, at the action's
511
+ * (`sourceCellX`, `sourceCellY`). The Go server binds each bot back to its
512
+ * action by these coordinates, so the positions here are the single source
513
+ * of truth shared with `DefaultCyberiaActions`.
514
+ *
515
+ * The NPC skin is derived from the action's first `default-<skin>` dialogue
516
+ * code (e.g. `default-wason` → skin `wason`) — there is no separate
517
+ * provideItemId; the bot's own active skin is what 'talk' objectives match.
518
+ *
519
+ * @param {string} mapCode
520
+ * @param {Array<{ key: string, r: number, g: number, b: number, a: number }>} colors
521
+ * @param {object} [opts]
522
+ * @param {number} [opts.dim=2]
523
+ * @param {OccupancyGrid} [opts.grid] Cells are reserved when provided.
524
+ * @returns {object[]} Bot-shaped entities.
525
+ */
526
+ function actionProviderSkin(action) {
527
+ // Skin derives from the action's greeting dialogCode (`default-<skin>`).
528
+ const code = action.dialogCode || '';
529
+ return code.startsWith('default-') ? code.slice('default-'.length) : '';
530
+ }
531
+
532
+ function generateActionProviderBots(mapCode, colors, opts = {}) {
533
+ const dim = opts.dim ?? 2;
534
+ const botColor = findColor(colors, 'BOT');
535
+ const rgba = botColor ? colorToRgba(botColor) : 'rgba(255, 128, 0, 1)';
536
+
537
+ const entities = [];
538
+ for (const action of DefaultCyberiaActions) {
539
+ if (action.sourceMapCode !== mapCode) continue;
540
+ if (typeof action.sourceCellX !== 'number' || typeof action.sourceCellY !== 'number') continue;
541
+ const skin = actionProviderSkin(action);
542
+ if (!skin) continue;
543
+ const cellX = action.sourceCellX;
544
+ const cellY = action.sourceCellY;
545
+
546
+ if (opts.grid) opts.grid.block(cellX, cellY, dim, dim);
547
+
548
+ // Passive quest-giver NPC: no aggro, and a small wander radius so it
549
+ // drifts near its source cell but stays easy to find. The Go server binds
550
+ // it by its spawn-centre (the initial cell), so the bind survives movement.
551
+ entities.push({
552
+ entityType: 'bot',
553
+ initCellX: cellX,
554
+ initCellY: cellY,
555
+ dimX: dim,
556
+ dimY: dim,
557
+ color: rgba,
558
+ objectLayerItemIds: [skin],
559
+ spawnRadius: 2,
560
+ aggroRange: 0,
561
+ maxLife: 100,
562
+ lifeRegen: 0,
563
+ });
564
+ }
565
+ return entities;
566
+ }
567
+
459
568
  /**
460
569
  * Generate all procedural fallback entities (obstacles + foreground + resources) for a map.
461
570
  *
@@ -473,6 +582,7 @@ function generateProceduralEntities(mapDims, colors, opts = {}) {
473
582
  obstacles: generateObstacles(mapDims, colors, { count: opts.obstacleCount }),
474
583
  foreground: generateForeground(mapDims, colors, { count: opts.foregroundCount }),
475
584
  resources: generateResources(mapDims, colors, { count: opts.resourceCount, grid: opts.grid }),
585
+ statics: generateStatic(mapDims, colors, { count: opts.staticCount }),
476
586
  };
477
587
  }
478
588
 
@@ -490,15 +600,18 @@ export {
490
600
  generateObstacles,
491
601
  generateForeground,
492
602
  generateResources,
603
+ generateStatic,
493
604
  generatePortalEntity,
494
605
  generatePortalEntities,
495
606
  generateBots,
607
+ generateActionProviderBots,
496
608
  generateProceduralEntities,
497
609
  // Ranges
498
610
  OBSTACLE_RANGE,
499
611
  FOREGROUND_RANGE,
500
612
  BOT_RANGE,
501
613
  RESOURCE_RANGE,
614
+ STATIC_RANGE,
502
615
  BOT_WEAPON_CHANCE,
503
616
  PORTAL_DIM_RANGE,
504
617
  PORTAL_COUNT_RANGE,
@@ -31,6 +31,9 @@ const EntityDefaultSchema = new Schema(
31
31
  // The coin slot must always have active:false — coins are non-activable.
32
32
  // When non-empty this supersedes liveItemIds for inventory initialization.
33
33
  defaultObjectLayers: { type: [ObjectLayerSlotSchema], default: [] },
34
+ // Canonical entity behavior (SharedDefaultsCyberia.ENTITY_BEHAVIORS). Empty
35
+ // lets the runtime derive it (armed → hostile, else passive).
36
+ behavior: { type: String, default: '' },
34
37
  },
35
38
  { _id: false },
36
39
  );
@@ -1,6 +1,7 @@
1
1
  import { loggerFactory } from '../../server/logger.js';
2
2
  import { CyberiaInstanceConfController } from './cyberia-instance-conf.controller.js';
3
3
  import express from 'express';
4
+ import { moderatorGuard, adminGuard } from '../../server/auth.js';
4
5
 
5
6
  const logger = loggerFactory(import.meta);
6
7
 
@@ -11,17 +12,48 @@ class CyberiaInstanceConfRouter {
11
12
  */
12
13
  static router(options) {
13
14
  const router = express.Router();
14
- router.post(`/:id`, async (req, res) => await CyberiaInstanceConfController.post(req, res, options));
15
- router.post(`/`, async (req, res) => await CyberiaInstanceConfController.post(req, res, options));
16
- router.get(`/:id`,
15
+ router.post(
16
+ `/:id`,
17
+ options.authMiddleware,
18
+ moderatorGuard,
19
+ async (req, res) => await CyberiaInstanceConfController.post(req, res, options),
20
+ );
21
+ router.post(
22
+ `/`,
23
+ options.authMiddleware,
24
+ moderatorGuard,
25
+ async (req, res) => await CyberiaInstanceConfController.post(req, res, options),
26
+ );
27
+ router.get(
28
+ `/:id`,
17
29
  // options.authMiddleware,
18
30
  async (req, res) => await CyberiaInstanceConfController.get(req, res, options),
19
31
  );
20
32
  router.get(`/`, async (req, res) => await CyberiaInstanceConfController.get(req, res, options));
21
- router.put(`/:id`, async (req, res) => await CyberiaInstanceConfController.put(req, res, options));
22
- router.put(`/`, async (req, res) => await CyberiaInstanceConfController.put(req, res, options));
23
- router.delete(`/:id`, async (req, res) => await CyberiaInstanceConfController.delete(req, res, options));
24
- router.delete(`/`, async (req, res) => await CyberiaInstanceConfController.delete(req, res, options));
33
+ router.put(
34
+ `/:id`,
35
+ options.authMiddleware,
36
+ moderatorGuard,
37
+ async (req, res) => await CyberiaInstanceConfController.put(req, res, options),
38
+ );
39
+ router.put(
40
+ `/`,
41
+ options.authMiddleware,
42
+ moderatorGuard,
43
+ async (req, res) => await CyberiaInstanceConfController.put(req, res, options),
44
+ );
45
+ router.delete(
46
+ `/:id`,
47
+ options.authMiddleware,
48
+ moderatorGuard,
49
+ async (req, res) => await CyberiaInstanceConfController.delete(req, res, options),
50
+ );
51
+ router.delete(
52
+ `/`,
53
+ options.authMiddleware,
54
+ adminGuard,
55
+ async (req, res) => await CyberiaInstanceConfController.delete(req, res, options),
56
+ );
25
57
  return router;
26
58
  }
27
59
  }
@@ -1,6 +1,6 @@
1
1
  import { loggerFactory } from '../../server/logger.js';
2
2
  import { CyberiaMapController } from './cyberia-map.controller.js';
3
- import { userGuard, adminGuard } from '../../server/auth.js';
3
+ import { moderatorGuard, adminGuard } from '../../server/auth.js';
4
4
  import express from 'express';
5
5
 
6
6
  const logger = loggerFactory(import.meta);
@@ -15,19 +15,34 @@ class CyberiaMapRouter {
15
15
  router.post(
16
16
  `/:id`,
17
17
  options.authMiddleware,
18
- userGuard,
18
+ moderatorGuard,
19
+ async (req, res) => await CyberiaMapController.post(req, res, options),
20
+ );
21
+ router.post(
22
+ `/`,
23
+ options.authMiddleware,
24
+ moderatorGuard,
19
25
  async (req, res) => await CyberiaMapController.post(req, res, options),
20
26
  );
21
- router.post(`/`, options.authMiddleware, userGuard, async (req, res) => await CyberiaMapController.post(req, res, options));
22
27
  router.get(`/search-codes`, async (req, res) => await CyberiaMapController.get(req, res, options));
23
28
  router.get(`/:id`, async (req, res) => await CyberiaMapController.get(req, res, options));
24
29
  router.get(`/`, async (req, res) => await CyberiaMapController.get(req, res, options));
25
- router.put(`/:id`, options.authMiddleware, userGuard, async (req, res) => await CyberiaMapController.put(req, res, options));
26
- router.put(`/`, options.authMiddleware, userGuard, async (req, res) => await CyberiaMapController.put(req, res, options));
30
+ router.put(
31
+ `/:id`,
32
+ options.authMiddleware,
33
+ moderatorGuard,
34
+ async (req, res) => await CyberiaMapController.put(req, res, options),
35
+ );
36
+ router.put(
37
+ `/`,
38
+ options.authMiddleware,
39
+ moderatorGuard,
40
+ async (req, res) => await CyberiaMapController.put(req, res, options),
41
+ );
27
42
  router.delete(
28
43
  `/:id`,
29
44
  options.authMiddleware,
30
- userGuard,
45
+ moderatorGuard,
31
46
  async (req, res) => await CyberiaMapController.delete(req, res, options),
32
47
  );
33
48
  router.delete(
@@ -39,6 +39,44 @@ class CyberiaQuestController {
39
39
  });
40
40
  }
41
41
  };
42
+ static getByCode = async (req, res, options) => {
43
+ try {
44
+ if (req && req.headers && req.headers.origin) {
45
+ res.set('Access-Control-Allow-Origin', req.headers.origin);
46
+ } else res.setHeader('Access-Control-Allow-Origin', '*');
47
+ res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
48
+ const result = await CyberiaQuestService.getByCode(req, res, options);
49
+ return res.status(200).json({
50
+ status: 'success',
51
+ data: result,
52
+ });
53
+ } catch (error) {
54
+ logger.error(error, error.stack);
55
+ return res.status(404).json({
56
+ status: 'error',
57
+ message: error.message,
58
+ });
59
+ }
60
+ };
61
+ static getByCell = async (req, res, options) => {
62
+ try {
63
+ if (req && req.headers && req.headers.origin) {
64
+ res.set('Access-Control-Allow-Origin', req.headers.origin);
65
+ } else res.setHeader('Access-Control-Allow-Origin', '*');
66
+ res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
67
+ const result = await CyberiaQuestService.getByCell(req, res, options);
68
+ return res.status(200).json({
69
+ status: 'success',
70
+ data: result,
71
+ });
72
+ } catch (error) {
73
+ logger.error(error, error.stack);
74
+ return res.status(404).json({
75
+ status: 'error',
76
+ message: error.message,
77
+ });
78
+ }
79
+ };
42
80
  static put = async (req, res, options) => {
43
81
  try {
44
82
  const result = await CyberiaQuestService.put(req, res, options);
@@ -1,6 +1,7 @@
1
1
  import { loggerFactory } from '../../server/logger.js';
2
2
  import { CyberiaQuestController } from './cyberia-quest.controller.js';
3
3
  import express from 'express';
4
+ import { moderatorGuard, adminGuard } from '../../server/auth.js';
4
5
 
5
6
  const logger = loggerFactory(import.meta);
6
7
 
@@ -11,17 +12,56 @@ class CyberiaQuestRouter {
11
12
  */
12
13
  static router(options) {
13
14
  const router = express.Router();
14
- router.post(`/:id`, async (req, res) => await CyberiaQuestController.post(req, res, options));
15
- router.post(`/`, async (req, res) => await CyberiaQuestController.post(req, res, options));
16
- router.get(`/:id`,
15
+ router.post(
16
+ `/:id`,
17
+ options.authMiddleware,
18
+ moderatorGuard,
19
+ async (req, res) => await CyberiaQuestController.post(req, res, options),
20
+ );
21
+ router.post(
22
+ `/`,
23
+ options.authMiddleware,
24
+ moderatorGuard,
25
+ async (req, res) => await CyberiaQuestController.post(req, res, options),
26
+ );
27
+ // Direct lookup by code — C client fetches quest metadata by code.
28
+ router.get(`/code/:code`, async (req, res) => await CyberiaQuestController.getByCode(req, res, options));
29
+ // Quest offers located by binding cell — C client resolves the Quest tab from
30
+ // here using the interacted entity's cell, decoupled from CyberiaAction.
31
+ router.get(
32
+ `/cell/:mapCode/:cellX/:cellY`,
33
+ async (req, res) => await CyberiaQuestController.getByCell(req, res, options),
34
+ );
35
+ router.get(
36
+ `/:id`,
17
37
  // options.authMiddleware,
18
38
  async (req, res) => await CyberiaQuestController.get(req, res, options),
19
39
  );
20
40
  router.get(`/`, async (req, res) => await CyberiaQuestController.get(req, res, options));
21
- router.put(`/:id`, async (req, res) => await CyberiaQuestController.put(req, res, options));
22
- router.put(`/`, async (req, res) => await CyberiaQuestController.put(req, res, options));
23
- router.delete(`/:id`, async (req, res) => await CyberiaQuestController.delete(req, res, options));
24
- router.delete(`/`, async (req, res) => await CyberiaQuestController.delete(req, res, options));
41
+ router.put(
42
+ `/:id`,
43
+ options.authMiddleware,
44
+ moderatorGuard,
45
+ async (req, res) => await CyberiaQuestController.put(req, res, options),
46
+ );
47
+ router.put(
48
+ `/`,
49
+ options.authMiddleware,
50
+ moderatorGuard,
51
+ async (req, res) => await CyberiaQuestController.put(req, res, options),
52
+ );
53
+ router.delete(
54
+ `/:id`,
55
+ options.authMiddleware,
56
+ moderatorGuard,
57
+ async (req, res) => await CyberiaQuestController.delete(req, res, options),
58
+ );
59
+ router.delete(
60
+ `/`,
61
+ options.authMiddleware,
62
+ adminGuard,
63
+ async (req, res) => await CyberiaQuestController.delete(req, res, options),
64
+ );
25
65
  return router;
26
66
  }
27
67
  }