cyberia 3.2.12 → 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 (194) 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 +34 -24
  11. package/.github/workflows/engine-cyberia.ci.yml +27 -5
  12. package/.github/workflows/ghpkg.ci.yml +89 -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 +19 -12
  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 +451 -1
  21. package/CLI-HELP.md +1040 -1130
  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 +128 -136
  28. package/bin/build.template.js +25 -179
  29. package/bin/cyberia.js +1089 -188
  30. package/bin/deploy.js +6 -3
  31. package/bin/index.js +1089 -188
  32. package/bump.config.js +1 -0
  33. package/compose.env +131 -0
  34. package/conf.js +19 -1
  35. package/deployment.yaml +74 -2
  36. package/docker-compose.yml +316 -0
  37. package/hardhat/hardhat.config.js +2 -2
  38. package/hardhat/package-lock.json +620 -2041
  39. package/hardhat/package.json +7 -5
  40. package/hardhat/scripts/deployObjectLayerToken.js +18 -18
  41. package/hardhat/test/ObjectLayerToken.js +378 -274
  42. package/ipfs/configure-ipfs.sh +13 -0
  43. package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +2 -2
  44. package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
  45. package/manifests/deployment/dd-cyberia-development/deployment.yaml +74 -2
  46. package/manifests/deployment/dd-cyberia-development/grpc-service.yaml +17 -0
  47. package/manifests/deployment/dd-cyberia-development/pv-pvc.yaml +32 -0
  48. package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
  49. package/mongodb/entrypoint.sh +76 -0
  50. package/nginx.conf +87 -0
  51. package/package.json +34 -22
  52. package/pv-pvc.yaml +32 -0
  53. package/scripts/disk-clean.sh +85 -60
  54. package/scripts/kubeadm-node-setup.sh +317 -0
  55. package/scripts/link-local-underpost-cli.sh +6 -0
  56. package/scripts/rocky-kickstart.sh +877 -185
  57. package/scripts/rpmfusion-ffmpeg-setup.sh +26 -50
  58. package/scripts/test-monitor.sh +248 -0
  59. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +43 -7
  60. package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +17 -25
  61. package/src/api/cyberia-action/cyberia-action.controller.js +19 -0
  62. package/src/api/cyberia-action/cyberia-action.model.js +21 -29
  63. package/src/api/cyberia-action/cyberia-action.router.js +42 -7
  64. package/src/api/cyberia-action/cyberia-action.service.js +20 -4
  65. package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +58 -57
  66. package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +37 -6
  67. package/src/api/cyberia-dialogue/cyberia-dialogue.service.js +8 -2
  68. package/src/api/cyberia-entity/cyberia-entity.router.js +39 -7
  69. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +74 -0
  70. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.model.js +67 -0
  71. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +63 -0
  72. package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +46 -0
  73. package/src/api/cyberia-instance/cyberia-fallback-world.js +62 -10
  74. package/src/api/cyberia-instance/cyberia-instance.model.js +32 -2
  75. package/src/api/cyberia-instance/cyberia-instance.router.js +6 -6
  76. package/src/api/cyberia-instance/cyberia-world-generator.js +116 -3
  77. package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +3 -0
  78. package/src/api/cyberia-instance-conf/cyberia-instance-conf.router.js +39 -7
  79. package/src/api/cyberia-map/cyberia-map.router.js +21 -6
  80. package/src/api/cyberia-quest/cyberia-quest.controller.js +38 -0
  81. package/src/api/cyberia-quest/cyberia-quest.router.js +47 -7
  82. package/src/api/cyberia-quest/cyberia-quest.service.js +59 -4
  83. package/src/api/cyberia-saga/cyberia-saga.controller.js +74 -0
  84. package/src/api/cyberia-saga/cyberia-saga.model.js +59 -0
  85. package/src/api/cyberia-saga/cyberia-saga.router.js +63 -0
  86. package/src/api/cyberia-saga/cyberia-saga.service.js +42 -0
  87. package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +551 -122
  88. package/src/api/cyberia-skill/cyberia-skill.controller.js +74 -0
  89. package/src/api/cyberia-skill/cyberia-skill.model.js +50 -0
  90. package/src/api/cyberia-skill/cyberia-skill.router.js +63 -0
  91. package/src/api/cyberia-skill/cyberia-skill.service.js +42 -0
  92. package/src/api/ipfs/ipfs.service.js +28 -15
  93. package/src/api/object-layer/object-layer.router.js +25 -15
  94. package/src/api/object-layer/object-layer.service.js +15 -20
  95. package/src/api/object-layer-render-frames/object-layer-render-frames.router.js +39 -7
  96. package/src/cli/baremetal.js +717 -16
  97. package/src/cli/cluster.js +80 -5
  98. package/src/cli/deploy.js +326 -292
  99. package/src/cli/docker-compose.js +648 -0
  100. package/src/cli/env.js +12 -6
  101. package/src/cli/fs.js +75 -30
  102. package/src/cli/image.js +187 -55
  103. package/src/cli/index.js +157 -6
  104. package/src/cli/kickstart.js +142 -20
  105. package/src/cli/monitor.js +387 -6
  106. package/src/cli/release.js +72 -15
  107. package/src/cli/repository.js +339 -40
  108. package/src/cli/run.js +678 -186
  109. package/src/cli/secrets.js +82 -48
  110. package/src/cli/ssh.js +234 -0
  111. package/src/cli/static.js +2 -2
  112. package/src/client/components/core/PanelForm.js +44 -44
  113. package/src/client/components/cyberia/ActionEngineCyberia.js +1867 -0
  114. package/src/client/components/cyberia/EntityEngineCyberia.js +585 -0
  115. package/src/client/components/cyberia/InstanceEngineCyberia.js +219 -13
  116. package/src/client/components/cyberia/MapEngineCyberia.js +154 -71
  117. package/src/client/components/cyberia/ObjectLayerEngineModal.js +40 -63
  118. package/src/client/components/cyberia/ObjectLayerEngineViewer.js +34 -20
  119. package/src/client/components/cyberia/SharedDefaultsCyberia.js +195 -4
  120. package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +66 -0
  121. package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +8 -0
  122. package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +8 -0
  123. package/src/client/public/cyberia-docs/ACTION-SYSTEM.md +92 -20
  124. package/src/client/public/cyberia-docs/ARCHITECTURE.md +272 -50
  125. package/src/client/public/cyberia-docs/CYBERIA-CLI.md +3 -3
  126. package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +39 -22
  127. package/src/client/public/cyberia-docs/CYBERIA-LORE.md +88 -0
  128. package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +394 -0
  129. package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +28 -12
  130. package/src/client/public/cyberia-docs/CYBERIA.md +1 -1
  131. package/src/client/public/cyberia-docs/QUEST-SYSTEM.md +27 -5
  132. package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
  133. package/src/client/public/cyberia-docs/WHITE-PAPER.md +1 -1
  134. package/src/client/services/cyberia-entity-type-default/cyberia-entity-type-default.service.js +99 -0
  135. package/src/client/services/cyberia-instance/cyberia-instance.management.js +2 -2
  136. package/src/client/services/cyberia-map/cyberia-map.management.js +2 -2
  137. package/src/client/services/cyberia-saga/cyberia-saga.service.js +99 -0
  138. package/src/client/services/cyberia-skill/cyberia-skill.service.js +99 -0
  139. package/src/client/services/object-layer/object-layer.management.js +6 -6
  140. package/src/{server → client-builder}/client-build-docs.js +15 -5
  141. package/src/{server → client-builder}/client-build-live.js +3 -3
  142. package/src/{server → client-builder}/client-build.js +25 -22
  143. package/src/{server → client-builder}/client-dev-server.js +3 -3
  144. package/src/{server → client-builder}/client-icons.js +2 -2
  145. package/src/{server → client-builder}/ssr.js +5 -5
  146. package/src/client.build.js +1 -3
  147. package/src/client.dev.js +1 -1
  148. package/src/db/mongo/MongoBootstrap.js +12 -12
  149. package/src/db/mongo/MongooseDB.js +2 -1
  150. package/src/grpc/cyberia/grpc-server.js +255 -70
  151. package/src/index.js +12 -1
  152. package/src/mailer/EmailRender.js +1 -1
  153. package/src/{server → projects/cyberia}/atlas-sprite-sheet-generator.js +2 -2
  154. package/src/{server → projects/cyberia}/besu-genesis-generator.js +3 -3
  155. package/src/projects/cyberia/catalog-cyberia.js +81 -0
  156. package/src/projects/cyberia/gemini-client.js +175 -0
  157. package/src/projects/cyberia/generate-saga.js +2107 -0
  158. package/src/{server → projects/cyberia}/ipfs-client.js +6 -4
  159. package/src/{server → projects/cyberia}/object-layer.js +12 -108
  160. package/src/{server → projects/cyberia}/semantic-layer-generator-floor.js +1 -1
  161. package/src/{server → projects/cyberia}/semantic-layer-generator-resource.js +1 -1
  162. package/src/{server → projects/cyberia}/semantic-layer-generator-skin.js +1 -1
  163. package/src/{server → projects/cyberia}/semantic-layer-generator.js +2 -2
  164. package/src/{server → projects/cyberia}/shape-generator.js +2 -2
  165. package/src/projects/underpost/catalog-underpost.js +60 -0
  166. package/src/runtime/cyberia-client/Dockerfile +31 -83
  167. package/src/runtime/cyberia-client/Dockerfile.dev +23 -30
  168. package/src/runtime/cyberia-server/Dockerfile +23 -42
  169. package/src/runtime/cyberia-server/Dockerfile.dev +20 -35
  170. package/src/runtime/engine-cyberia/Dockerfile +143 -0
  171. package/src/runtime/engine-cyberia/Dockerfile.dev +143 -0
  172. package/src/runtime/engine-cyberia/Dockerfile.test +165 -0
  173. package/src/runtime/engine-cyberia/compose.env +131 -0
  174. package/src/runtime/engine-cyberia/docker-compose.yml +316 -0
  175. package/src/runtime/engine-cyberia/ipfs/configure-ipfs.sh +13 -0
  176. package/src/runtime/engine-cyberia/mongodb/entrypoint.sh +76 -0
  177. package/src/runtime/engine-cyberia/nginx.conf +87 -0
  178. package/src/runtime/express/Express.js +2 -2
  179. package/src/runtime/nginx/Nginx.js +250 -0
  180. package/src/runtime/wp/Dockerfile +3 -3
  181. package/src/server/catalog.js +72 -0
  182. package/src/server/conf.js +415 -60
  183. package/src/server/runtime-status.js +252 -0
  184. package/src/server/start.js +42 -11
  185. package/src/server.js +6 -2
  186. package/test/cyberia-instance-conf-defaults.test.js +140 -0
  187. package/test/deploy-monitor.test.js +267 -0
  188. package/test/shape-generator.test.js +7 -1
  189. package/typedoc.dd-cyberia.json +3 -1
  190. package/typedoc.json +3 -1
  191. package/manifests/deployment/dd-test-development/deployment.yaml +0 -256
  192. package/manifests/deployment/dd-test-development/proxy.yaml +0 -102
  193. /package/src/client/ssr/{Render.js → RootDocument.js} +0 -0
  194. /package/src/{server → client-builder}/client-formatted.js +0 -0
@@ -1,56 +1,32 @@
1
1
  #!/usr/bin/env bash
2
2
  set -euo pipefail
3
3
 
4
- # Purpose: enable required repos (CRB, EPEL, RPM Fusion) and attempt to install ffmpeg on Rocky/Alma/RHEL-9 compatible systems.
4
+ echo "1) Ensure dnf-plugins-core is installed"
5
+ dnf -y install dnf-plugins-core
5
6
 
6
- if [ "${EUID:-$(id -u)}" -ne 0 ]; then
7
- echo "This script must be run as root or with sudo. Exiting."
8
- exit 1
9
- fi
7
+ echo "2) Enable CRB"
8
+ dnf config-manager --set-enabled crb || true
10
9
 
11
- echo "1) Ensure dnf-plugins-core is available (for config-manager)"
12
- dnf -y install dnf-plugins-core
10
+ echo "3) Install EPEL"
11
+ dnf -y install epel-release \
12
+ || dnf -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm
13
+
14
+ echo "4) Install RPM Fusion repositories"
15
+ dnf -y install \
16
+ https://download1.rpmfusion.org/free/el/rpmfusion-free-release-9.noarch.rpm \
17
+ https://download1.rpmfusion.org/nonfree/el/rpmfusion-nonfree-release-9.noarch.rpm
18
+
19
+
20
+ echo "5) Install libwebp-tools (for ffmpeg to support WebP)"
21
+ dnf -y install libwebp-tools
22
+
23
+ echo "6) Refresh metadata"
24
+ dnf clean all
25
+ dnf makecache --refresh
26
+
27
+ echo "7) Install ffmpeg"
28
+ dnf -y install ffmpeg ffmpeg-devel --allowerasing
13
29
 
14
- echo "2) Enable CodeReady / CRB (needed for some deps, e.g. ladspa)"
15
- # On RHEL you'd use subscription-manager; on CentOS/Rocky/Alma use config-manager
16
- dnf config-manager --set-enabled crb
17
-
18
- echo "3) Install EPEL release (required by some ffmpeg deps)"
19
- dnf -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm
20
-
21
- echo "4) Add RPM Fusion (free + nonfree) repositories"
22
- # Using mirrors.rpmfusion.org recommended links
23
- dnf -y install https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-9.noarch.rpm \
24
- https://mirrors.rpmfusion.org/nonfree/el/rpmfusion-nonfree-release-9.noarch.rpm
25
-
26
- echo "5) Refresh metadata and update system"
27
- dnf -y makecache
28
- # Optional: update system packages (comment out if you don't want a full update)
29
- # dnf -y update
30
-
31
- echo "6) Try to install audio helper packages that sometimes block ffmpeg (ladspa, rubberband)"
32
- # These may be provided by CRB/EPEL or other compatible repos
33
- dnf -y install ladspa || echo "ladspa not available from enabled repos (will try later)"
34
- dnf -y install rubberband || echo "rubberband not available from enabled repos (will try later)"
35
- dnf -y install libwebp-tools || echo "libwebp-tools not available from enabled repos (will try later)"
36
-
37
- echo "7) Try installing ffmpeg (several fallbacks tried)"
38
- if dnf -y install ffmpeg ffmpeg-devel --allowerasing; then
39
- echo "ffmpeg installed successfully (used --allowerasing)."
40
- elif dnf -y install ffmpeg ffmpeg-devel --nobest; then
41
- echo "ffmpeg installed successfully (used --nobest)."
42
- elif dnf -y install ffmpeg ffmpeg-devel --skip-broken; then
43
- echo "ffmpeg installed (skip-broken). Some optional packages may have been skipped."
44
- else
45
- echo "Automatic install failed."
46
- echo "Helpful troubleshooting steps:"
47
- echo " - Check which repo provides ladspa: dnf repoquery --whatprovides 'ladspa'"
48
- echo " - Check which package provides librubberband: dnf repoquery --whatprovides 'librubberband.so.2'"
49
- echo " - Try enabling CRB and EPEL (we already attempted that). If ladspa/rubberband are still missing you can fetch their EL9 rpm from a trusted mirror or build them."
50
- echo " - Example manual install (ONLY if you trust the source): sudo dnf install /path/to/ladspa-*.el9.rpm /path/to/rubberband-*.el9.rpm"
51
- echo " - After satisfying ladspa/rubberband, rerun: sudo dnf install ffmpeg ffmpeg-devel"
52
- exit 2
53
- fi
54
-
55
- echo "\nInstallation finished. Verify with: ffmpeg -version"
56
- exit 0
30
+ echo
31
+ echo "Done."
32
+ ffmpeg -version | head -n 1
@@ -0,0 +1,248 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # test-monitor.sh — end-to-end deploy + two-phase monitor smoke test.
4
+ #
5
+ # Two deployment shapes are supported:
6
+ #
7
+ # --mode runtime `underpost start` deploy (e.g. dd-test). Monitored with the
8
+ # HTTP gate: /_internal/ready probes + port-forward status.
9
+ # --mode instance Custom instances from conf.instances.json (e.g. cyberia
10
+ # mmo-server / mmo-client). Monitored with the kubernetes gate
11
+ # (TCP readinessProbe) + exec status transport.
12
+ #
13
+ # Every variable in the DEFAULTS block below is overridable with a flag of the
14
+ # same name (--env, --deploy-id, …). Run with --help for the full list.
15
+ #
16
+ set -euo pipefail
17
+
18
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
19
+ ENGINE_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
20
+ cd "$ENGINE_DIR"
21
+
22
+ # ─────────────────────────── DEFAULTS (all overridable) ───────────────────────────
23
+ MODE=runtime # runtime | instance
24
+ ENV=development # development | production
25
+ DEPLOY_ID=dd-test # deploy id (instance mode: parent of conf.instances.json)
26
+ INSTANCE_IDS= # instance mode: csv of ids (default: all in conf.instances.json)
27
+ IMAGE=underpost/wp:v3.2.70 # runtime mode image (instance mode reads image from conf)
28
+ VERSIONS=green # csv of blue/green versions
29
+ REPLICAS=1 # replicas per deployment
30
+ NAMESPACE=default # k8s namespace
31
+ CLUSTER=kind # kind | kubeadm | k3s | "" (auto/none)
32
+ TIMEOUT_RESPONSE=300000ms # HTTPProxy per-route response timeout
33
+ TEMPLATE_REPO=underpostnet/pwa-microservices-template-private # runtime mode link repo
34
+ ENVOY_NAMESPACE=projectcontour # ingress namespace (instance TLS exposure)
35
+ ENVOY_SERVICE=envoy # ingress service (instance TLS exposure)
36
+ HTTPS_PORT=443 # local https port for instance TLS exposure
37
+
38
+ USE_CERT=false # runtime: pass --cert to the proxy step
39
+ USE_PULL_BUNDLE=false # runtime: include --pull-bundle in the start cmd
40
+ USE_TLS=false # generate self-signed certs + expose over HTTPS
41
+ USE_TEST_REPO=false # runtime: publish src to engine-test-<id> + pod clones it (--private-test-repo)
42
+ DO_BUILD_TEMPLATE=true # run `node bin/build.template --update-private`
43
+ DO_CLUSTER_MANIFESTS=true # run `node bin run build-cluster-deployment-manifests`
44
+ DO_EXPOSE=true # expose locally after deploy + monitor
45
+
46
+ usage() {
47
+ cat <<'EOF'
48
+ Usage: scripts/test-monitor.sh [flags]
49
+
50
+ Modes:
51
+ --mode <runtime|instance> Deployment shape to test (default: runtime)
52
+
53
+ Common (every default is overridable):
54
+ --env <development|production>
55
+ --deploy-id <id>
56
+ --instance-ids <csv> instance mode only; default = all ids in conf.instances.json
57
+ --image <image> runtime mode only
58
+ --versions <csv> e.g. green or blue,green
59
+ --replicas <n>
60
+ --namespace <ns>
61
+ --cluster <kind|kubeadm|k3s|none>
62
+ --timeout-response <dur> e.g. 300000ms
63
+ --template-repo <owner/repo> runtime mode link repo
64
+ --envoy-namespace <ns> instance TLS exposure ingress namespace
65
+ --envoy-service <name> instance TLS exposure ingress service
66
+ --https-port <port> local https port for instance TLS exposure
67
+
68
+ Toggles (use --x / --no-x):
69
+ --tls | --no-tls self-signed certs + HTTPS exposure (default: off)
70
+ --cert | --no-cert runtime proxy --cert (default: off)
71
+ --pull-bundle | --no-pull-bundle runtime start --pull-bundle (default: off)
72
+ --test-repo | --no-test-repo runtime: publish src to engine-test-<id> and have the
73
+ pod clone it via --private-test-repo (default: off)
74
+ --expose | --no-expose local exposure after monitor (default: on)
75
+ --build-template | --no-build-template sync private template (default: on)
76
+ --cluster-manifests | --no-cluster-manifests rebuild cluster manifests (default: on)
77
+
78
+ -h, --help
79
+
80
+ Examples:
81
+ # Runtime deploy (default), no TLS
82
+ scripts/test-monitor.sh
83
+
84
+ # Runtime deploy over HTTPS
85
+ scripts/test-monitor.sh --tls
86
+
87
+ # Runtime deploy from a work-in-progress test source repo (engine-test-<id>)
88
+ scripts/test-monitor.sh --test-repo
89
+
90
+ # Cyberia instances (both), dev on kind, no TLS
91
+ scripts/test-monitor.sh --mode instance --deploy-id dd-cyberia --cluster kind
92
+
93
+ # Only the mmo-server instance, production, over HTTPS
94
+ scripts/test-monitor.sh --mode instance --deploy-id dd-cyberia \
95
+ --instance-ids mmo-server --env production --cluster kubeadm --tls
96
+ EOF
97
+ }
98
+
99
+ # ─────────────────────────── flag parsing ───────────────────────────
100
+ # Supports `--key value` and `--key=value` for value flags, plus --x/--no-x toggles.
101
+ needval() { [[ $# -ge 2 ]] || { echo "Missing value for $1" >&2; exit 1; }; }
102
+ while [[ $# -gt 0 ]]; do
103
+ case "$1" in
104
+ --mode) needval "$@"; MODE="$2"; shift 2;; --mode=*) MODE="${1#*=}"; shift;;
105
+ --env) needval "$@"; ENV="$2"; shift 2;; --env=*) ENV="${1#*=}"; shift;;
106
+ --deploy-id) needval "$@"; DEPLOY_ID="$2"; shift 2;; --deploy-id=*) DEPLOY_ID="${1#*=}"; shift;;
107
+ --instance-ids) needval "$@"; INSTANCE_IDS="$2"; shift 2;; --instance-ids=*) INSTANCE_IDS="${1#*=}"; shift;;
108
+ --image) needval "$@"; IMAGE="$2"; shift 2;; --image=*) IMAGE="${1#*=}"; shift;;
109
+ --versions) needval "$@"; VERSIONS="$2"; shift 2;; --versions=*) VERSIONS="${1#*=}"; shift;;
110
+ --replicas) needval "$@"; REPLICAS="$2"; shift 2;; --replicas=*) REPLICAS="${1#*=}"; shift;;
111
+ --namespace) needval "$@"; NAMESPACE="$2"; shift 2;; --namespace=*) NAMESPACE="${1#*=}"; shift;;
112
+ --cluster) needval "$@"; CLUSTER="$2"; shift 2;; --cluster=*) CLUSTER="${1#*=}"; shift;;
113
+ --timeout-response) needval "$@"; TIMEOUT_RESPONSE="$2"; shift 2;; --timeout-response=*) TIMEOUT_RESPONSE="${1#*=}"; shift;;
114
+ --template-repo) needval "$@"; TEMPLATE_REPO="$2"; shift 2;; --template-repo=*) TEMPLATE_REPO="${1#*=}"; shift;;
115
+ --envoy-namespace) needval "$@"; ENVOY_NAMESPACE="$2"; shift 2;; --envoy-namespace=*) ENVOY_NAMESPACE="${1#*=}"; shift;;
116
+ --envoy-service) needval "$@"; ENVOY_SERVICE="$2"; shift 2;; --envoy-service=*) ENVOY_SERVICE="${1#*=}"; shift;;
117
+ --https-port) needval "$@"; HTTPS_PORT="$2"; shift 2;; --https-port=*) HTTPS_PORT="${1#*=}"; shift;;
118
+ --tls) USE_TLS=true; shift;; --no-tls) USE_TLS=false; shift;;
119
+ --cert) USE_CERT=true; shift;; --no-cert) USE_CERT=false; shift;;
120
+ --pull-bundle) USE_PULL_BUNDLE=true; shift;; --no-pull-bundle) USE_PULL_BUNDLE=false; shift;;
121
+ --test-repo) USE_TEST_REPO=true; shift;; --no-test-repo) USE_TEST_REPO=false; shift;;
122
+ --expose) DO_EXPOSE=true; shift;; --no-expose) DO_EXPOSE=false; shift;;
123
+ --build-template) DO_BUILD_TEMPLATE=true; shift;; --no-build-template) DO_BUILD_TEMPLATE=false; shift;;
124
+ --cluster-manifests) DO_CLUSTER_MANIFESTS=true; shift;; --no-cluster-manifests) DO_CLUSTER_MANIFESTS=false; shift;;
125
+ -h|--help) usage; exit 0;;
126
+ *) echo "Unknown flag: $1" >&2; usage; exit 1;;
127
+ esac
128
+ done
129
+
130
+ # ─────────────────────────── derived flags ───────────────────────────
131
+ CLUSTER_FLAG=""
132
+ case "$CLUSTER" in
133
+ kind) CLUSTER_FLAG="--kind";;
134
+ kubeadm) CLUSTER_FLAG="--kubeadm";;
135
+ k3s) CLUSTER_FLAG="--k3s";;
136
+ ""|none) CLUSTER_FLAG="";;
137
+ *) echo "Unknown --cluster: $CLUSTER (expected kind|kubeadm|k3s|none)" >&2; exit 1;;
138
+ esac
139
+ DEV_FLAG=""; [ "$ENV" = development ] && DEV_FLAG="--dev"
140
+ INSTANCES_CONF="./engine-private/conf/${DEPLOY_ID}/conf.instances.json"
141
+ SERVER_CONF="./engine-private/conf/${DEPLOY_ID}/conf.server.json"
142
+
143
+ echo "[test-monitor] mode=$MODE env=$ENV deploy=$DEPLOY_ID cluster=${CLUSTER:-none} tls=$USE_TLS test-repo=$USE_TEST_REPO expose=$DO_EXPOSE"
144
+
145
+ # ─────────────────────────── shared helpers ───────────────────────────
146
+
147
+ # setup_tls_secrets <host> [<host> …] — regenerate self-signed certs and create
148
+ # the per-host k8s TLS secret the HTTPProxy references (name == host).
149
+ setup_tls_secrets() {
150
+ local ssl_base="${ENGINE_DIR}/engine-private/ssl"
151
+ for host in "$@"; do
152
+ local cert_dir="${ssl_base}/${host}"
153
+ mkdir -p "$cert_dir"
154
+ bash "${SCRIPT_DIR}/ssl.sh" "$cert_dir" "$host"
155
+ local name_safe="${host//[^a-zA-Z0-9_.-]/_}"
156
+ kubectl delete secret "$host" -n "$NAMESPACE" --ignore-not-found
157
+ kubectl create secret tls "$host" \
158
+ --cert="${cert_dir}/${name_safe}.pem" \
159
+ --key="${cert_dir}/${name_safe}-key.pem" \
160
+ -n "$NAMESPACE"
161
+ done
162
+ }
163
+
164
+ # hosts_from <conf.json> — unique hosts declared in a conf.server.json or conf.instances.json.
165
+ hosts_from() {
166
+ node -e "
167
+ const c = require('$1');
168
+ const hosts = Array.isArray(c) ? c.map(i => i.host) : Object.keys(c);
169
+ console.log([...new Set(hosts)].join(' '));
170
+ "
171
+ }
172
+
173
+ # ─────────────────────────── pre-steps ───────────────────────────
174
+ [ "$DO_CLUSTER_MANIFESTS" = true ] && node bin run build-cluster-deployment-manifests
175
+ [ "$DO_BUILD_TEMPLATE" = true ] && node bin/build.template --update-private
176
+
177
+ # ─────────────────────────── runtime mode ───────────────────────────
178
+ run_runtime_mode() {
179
+ local link_cmd="cd /home/dd,underpost clone ${TEMPLATE_REPO},cd /home/dd/${TEMPLATE_REPO##*/},npm install,npm link"
180
+ local deploy_cmd proxy_flag="" expose_flags="" start_flags=""
181
+
182
+ # Publish the local engine src to underpostnet/engine-test-<id> so the pod
183
+ # (started with --private-test-repo) clones this work-in-progress source.
184
+ [ "$USE_TEST_REPO" = true ] && node bin/build "$DEPLOY_ID" --update-private
185
+
186
+ [ "$USE_PULL_BUNDLE" = true ] && start_flags="${start_flags} --pull-bundle"
187
+ [ "$USE_TEST_REPO" = true ] && start_flags="${start_flags} --private-test-repo"
188
+ deploy_cmd="${link_cmd},underpost start --build --run${start_flags} ${DEPLOY_ID} ${ENV}"
189
+
190
+ [ "$USE_CERT" = true ] && proxy_flag="--cert"
191
+ [ "$USE_TLS" = true ] && proxy_flag="--self-signed"
192
+
193
+ node bin deploy "$DEPLOY_ID" "$ENV" $CLUSTER_FLAG --sync --build-manifest \
194
+ --image "$IMAGE" --timeout-response "$TIMEOUT_RESPONSE" --versions "$VERSIONS" --replicas "$REPLICAS" --cmd "$deploy_cmd"
195
+ node bin deploy "$DEPLOY_ID" "$ENV" $CLUSTER_FLAG --disable-update-proxy $proxy_flag
196
+ node bin monitor "$DEPLOY_ID" "$ENV" --ready-deployment --promote \
197
+ --timeout-response "$TIMEOUT_RESPONSE" --versions "$VERSIONS" --replicas "$REPLICAS"
198
+
199
+ node bin run etc-hosts --deploy-id "$DEPLOY_ID"
200
+
201
+ if [ "$USE_TLS" = true ]; then
202
+ setup_tls_secrets $(hosts_from "$SERVER_CONF")
203
+ expose_flags="--tls"
204
+ fi
205
+ [ "$DO_EXPOSE" = true ] && node bin deploy --expose --local-proxy "$DEPLOY_ID" "$ENV" $expose_flags
206
+ }
207
+
208
+ # ─────────────────────────── instance mode ───────────────────────────
209
+ run_instance_mode() {
210
+ [ -f "$INSTANCES_CONF" ] || { echo "Missing $INSTANCES_CONF" >&2; exit 1; }
211
+ if [ -z "$INSTANCE_IDS" ]; then
212
+ INSTANCE_IDS=$(node -e "console.log(require('$INSTANCES_CONF').map(i=>i.id).join(','))")
213
+ fi
214
+ local tls_flag=""; [ "$USE_TLS" = true ] && tls_flag="--tls --test"
215
+
216
+ # `run instance` builds/pulls the image, applies the manifest, monitors with the
217
+ # kubernetes gate (TCP readinessProbe) + exec status, then promotes traffic.
218
+ IFS=',' read -ra ids <<< "$INSTANCE_IDS"
219
+ for id in "${ids[@]}"; do
220
+ [ -n "$id" ] || continue
221
+ echo "[test-monitor] deploying instance ${DEPLOY_ID},${id}"
222
+ node bin run instance "${DEPLOY_ID},${id},${REPLICAS}" \
223
+ $DEV_FLAG $CLUSTER_FLAG --namespace "$NAMESPACE" --etc-hosts $tls_flag \
224
+ ${IMAGE:+--image-name "$IMAGE"}
225
+ done
226
+
227
+ [ "$DO_EXPOSE" = true ] || return 0
228
+ if [ "$USE_TLS" = true ]; then
229
+ # Instances are routed by the Contour ingress; port-forward Envoy so the
230
+ # HTTPProxy host routing (server/client.cyberiaonline.com) works locally.
231
+ echo "[test-monitor] exposing HTTPS via ${ENVOY_NAMESPACE}/${ENVOY_SERVICE} on :${HTTPS_PORT}"
232
+ sudo kubectl port-forward -n "$ENVOY_NAMESPACE" "svc/${ENVOY_SERVICE}" "${HTTPS_PORT}:443" &
233
+ else
234
+ # Plain HTTP: port-forward each instance service port directly.
235
+ for id in "${ids[@]}"; do
236
+ [ -n "$id" ] || continue
237
+ node bin deploy --expose "${DEPLOY_ID}-${id}" "$ENV" --namespace "$NAMESPACE" || true
238
+ done
239
+ fi
240
+ }
241
+
242
+ case "$MODE" in
243
+ runtime) run_runtime_mode;;
244
+ instance) run_instance_mode;;
245
+ *) echo "Unknown --mode: $MODE (expected runtime|instance)" >&2; exit 1;;
246
+ esac
247
+
248
+ echo "[test-monitor] done (mode=$MODE tls=$USE_TLS)"
@@ -1,6 +1,7 @@
1
1
  import { loggerFactory } from '../../server/logger.js';
2
2
  import { AtlasSpriteSheetController } from './atlas-sprite-sheet.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
 
@@ -14,19 +15,34 @@ class AtlasSpriteSheetRouter {
14
15
  router.post(
15
16
  `/generate/:id`,
16
17
  options.authMiddleware,
18
+ moderatorGuard,
17
19
  async (req, res) => await AtlasSpriteSheetController.generate(req, res, options),
18
20
  );
19
21
  router.delete(
20
22
  `/object-layer/:id`,
21
23
  options.authMiddleware,
24
+ moderatorGuard,
22
25
  async (req, res) => await AtlasSpriteSheetController.deleteByObjectLayerId(req, res, options),
23
26
  );
24
- router.post(`/:id`, options.authMiddleware, async (req, res) => await AtlasSpriteSheetController.post(req, res, options));
25
- router.post(`/`, options.authMiddleware, async (req, res) => await AtlasSpriteSheetController.post(req, res, options));
27
+ router.post(
28
+ `/:id`,
29
+ options.authMiddleware,
30
+ moderatorGuard,
31
+ async (req, res) => await AtlasSpriteSheetController.post(req, res, options),
32
+ );
33
+ router.post(
34
+ `/`,
35
+ options.authMiddleware,
36
+ moderatorGuard,
37
+ async (req, res) => await AtlasSpriteSheetController.post(req, res, options),
38
+ );
26
39
  router.get(`/blob/:itemKey`, async (req, res) => await AtlasSpriteSheetController.blob(req, res, options));
27
40
  // Metadata endpoints: returns itemKey, atlasWidth, atlasHeight, cellPixelDim, frames (no fileId).
28
41
  // Client fetches /metadata/:itemKey once, caches it, then fetches /blob/:itemKey for the PNG.
29
- router.get(`/metadata/:itemKey`, async (req, res) => await AtlasSpriteSheetController.getMetadata(req, res, options));
42
+ router.get(
43
+ `/metadata/:itemKey`,
44
+ async (req, res) => await AtlasSpriteSheetController.getMetadata(req, res, options),
45
+ );
30
46
  router.get(`/metadata`, async (req, res) => await AtlasSpriteSheetController.getMetadata(req, res, options));
31
47
  router.get(
32
48
  `/:id`,
@@ -34,10 +50,30 @@ class AtlasSpriteSheetRouter {
34
50
  async (req, res) => await AtlasSpriteSheetController.get(req, res, options),
35
51
  );
36
52
  router.get(`/`, async (req, res) => await AtlasSpriteSheetController.get(req, res, options));
37
- router.put(`/:id`, options.authMiddleware, async (req, res) => await AtlasSpriteSheetController.put(req, res, options));
38
- router.put(`/`, options.authMiddleware, async (req, res) => await AtlasSpriteSheetController.put(req, res, options));
39
- router.delete(`/:id`, options.authMiddleware, async (req, res) => await AtlasSpriteSheetController.delete(req, res, options));
40
- router.delete(`/`, options.authMiddleware, async (req, res) => await AtlasSpriteSheetController.delete(req, res, options));
53
+ router.put(
54
+ `/:id`,
55
+ options.authMiddleware,
56
+ moderatorGuard,
57
+ async (req, res) => await AtlasSpriteSheetController.put(req, res, options),
58
+ );
59
+ router.put(
60
+ `/`,
61
+ options.authMiddleware,
62
+ moderatorGuard,
63
+ async (req, res) => await AtlasSpriteSheetController.put(req, res, options),
64
+ );
65
+ router.delete(
66
+ `/:id`,
67
+ options.authMiddleware,
68
+ moderatorGuard,
69
+ async (req, res) => await AtlasSpriteSheetController.delete(req, res, options),
70
+ );
71
+ router.delete(
72
+ `/`,
73
+ options.authMiddleware,
74
+ adminGuard,
75
+ async (req, res) => await AtlasSpriteSheetController.delete(req, res, options),
76
+ );
41
77
  return router;
42
78
  }
43
79
  }
@@ -1,10 +1,10 @@
1
1
  import { DataBaseProviderService } from '../../db/DataBaseProvider.js';
2
2
  import { loggerFactory } from '../../server/logger.js';
3
3
  import { DataQuery } from '../../server/data-query.js';
4
- import { AtlasSpriteSheetGenerator } from '../../server/atlas-sprite-sheet-generator.js';
4
+ import { AtlasSpriteSheetGenerator } from '../../projects/cyberia/atlas-sprite-sheet-generator.js';
5
5
  import { FileFactory } from '../file/file.service.js';
6
6
  import { AtlasSpriteSheetDto } from './atlas-sprite-sheet.model.js';
7
- import { IpfsClient } from '../../server/ipfs-client.js';
7
+ import { IpfsClient } from '../../projects/cyberia/ipfs-client.js';
8
8
  import { createPinRecord, removePinRecordsAndUnpin } from '../ipfs/ipfs.service.js';
9
9
 
10
10
  const logger = loggerFactory(import.meta);
@@ -54,10 +54,9 @@ async function withResolvedAtlasFrameDuration(doc, ObjectLayer) {
54
54
  class AtlasSpriteSheetService {
55
55
  static blob = async (req, res, options) => {
56
56
  /** @type {import('./atlas-sprite-sheet.model.js').AtlasSpriteSheetModel} */
57
- const AtlasSpriteSheet =
58
- DataBaseProviderService.getModel("AtlasSpriteSheet", options);
57
+ const AtlasSpriteSheet = DataBaseProviderService.getModel('AtlasSpriteSheet', options);
59
58
  /** @type {import('../file/file.model.js').FileModel} */
60
- const File = DataBaseProviderService.getModel("File", options);
59
+ const File = DataBaseProviderService.getModel('File', options);
61
60
 
62
61
  const itemKey = req.params.itemKey;
63
62
  const atlasDoc = await AtlasSpriteSheet.findOne({ 'metadata.itemKey': itemKey }).lean();
@@ -70,12 +69,11 @@ class AtlasSpriteSheetService {
70
69
  };
71
70
  static generate = async (req, res, options, generateOptions = {}) => {
72
71
  /** @type {import('../object-layer/object-layer.model.js').ObjectLayerModel} */
73
- const ObjectLayer = DataBaseProviderService.getModel("ObjectLayer", options);
72
+ const ObjectLayer = DataBaseProviderService.getModel('ObjectLayer', options);
74
73
  /** @type {import('../file/file.model.js').FileModel} */
75
- const File = DataBaseProviderService.getModel("File", options);
74
+ const File = DataBaseProviderService.getModel('File', options);
76
75
  /** @type {import('./atlas-sprite-sheet.model.js').AtlasSpriteSheetModel} */
77
- const AtlasSpriteSheet =
78
- DataBaseProviderService.getModel("AtlasSpriteSheet", options);
76
+ const AtlasSpriteSheet = DataBaseProviderService.getModel('AtlasSpriteSheet', options);
79
77
 
80
78
  let objectLayer = req.objectLayer;
81
79
 
@@ -195,12 +193,11 @@ class AtlasSpriteSheetService {
195
193
  };
196
194
  static deleteByObjectLayerId = async (req, res, options) => {
197
195
  /** @type {import('../object-layer/object-layer.model.js').ObjectLayerModel} */
198
- const ObjectLayer = DataBaseProviderService.getModel("ObjectLayer", options);
196
+ const ObjectLayer = DataBaseProviderService.getModel('ObjectLayer', options);
199
197
  /** @type {import('../file/file.model.js').FileModel} */
200
- const File = DataBaseProviderService.getModel("File", options);
198
+ const File = DataBaseProviderService.getModel('File', options);
201
199
  /** @type {import('./atlas-sprite-sheet.model.js').AtlasSpriteSheetModel} */
202
- const AtlasSpriteSheet =
203
- DataBaseProviderService.getModel("AtlasSpriteSheet", options);
200
+ const AtlasSpriteSheet = DataBaseProviderService.getModel('AtlasSpriteSheet', options);
204
201
 
205
202
  const objectLayer = await ObjectLayer.findById(req.params.id);
206
203
  if (!objectLayer) {
@@ -254,14 +251,12 @@ class AtlasSpriteSheetService {
254
251
  };
255
252
  static post = async (req, res, options) => {
256
253
  /** @type {import('./atlas-sprite-sheet.model.js').AtlasSpriteSheetModel} */
257
- const AtlasSpriteSheet =
258
- DataBaseProviderService.getModel("AtlasSpriteSheet", options);
254
+ const AtlasSpriteSheet = DataBaseProviderService.getModel('AtlasSpriteSheet', options);
259
255
  return await new AtlasSpriteSheet(req.body).save();
260
256
  };
261
257
  static get = async (req, res, options) => {
262
258
  /** @type {import('./atlas-sprite-sheet.model.js').AtlasSpriteSheetModel} */
263
- const AtlasSpriteSheet =
264
- DataBaseProviderService.getModel("AtlasSpriteSheet", options);
259
+ const AtlasSpriteSheet = DataBaseProviderService.getModel('AtlasSpriteSheet', options);
265
260
  if (req.params.id)
266
261
  return await AtlasSpriteSheet.findById(req.params.id)
267
262
  .select(AtlasSpriteSheetDto.select.get())
@@ -287,10 +282,9 @@ class AtlasSpriteSheetService {
287
282
  // Client fetches this once per itemKey, caches it, then fetches the PNG blob.
288
283
  static getMetadata = async (req, res, options) => {
289
284
  /** @type {import('../object-layer/object-layer.model.js').ObjectLayerModel} */
290
- const ObjectLayer = DataBaseProviderService.getModel("ObjectLayer", options);
285
+ const ObjectLayer = DataBaseProviderService.getModel('ObjectLayer', options);
291
286
  /** @type {import('./atlas-sprite-sheet.model.js').AtlasSpriteSheetModel} */
292
- const AtlasSpriteSheet =
293
- DataBaseProviderService.getModel("AtlasSpriteSheet", options);
287
+ const AtlasSpriteSheet = DataBaseProviderService.getModel('AtlasSpriteSheet', options);
294
288
 
295
289
  if (req.params.itemKey) {
296
290
  const doc = await AtlasSpriteSheet.findOne({ 'metadata.itemKey': req.params.itemKey })
@@ -328,16 +322,14 @@ class AtlasSpriteSheetService {
328
322
  };
329
323
  static put = async (req, res, options) => {
330
324
  /** @type {import('./atlas-sprite-sheet.model.js').AtlasSpriteSheetModel} */
331
- const AtlasSpriteSheet =
332
- DataBaseProviderService.getModel("AtlasSpriteSheet", options);
325
+ const AtlasSpriteSheet = DataBaseProviderService.getModel('AtlasSpriteSheet', options);
333
326
  return await AtlasSpriteSheet.findByIdAndUpdate(req.params.id, req.body);
334
327
  };
335
328
  static delete = async (req, res, options) => {
336
329
  /** @type {import('./atlas-sprite-sheet.model.js').AtlasSpriteSheetModel} */
337
- const AtlasSpriteSheet =
338
- DataBaseProviderService.getModel("AtlasSpriteSheet", options);
330
+ const AtlasSpriteSheet = DataBaseProviderService.getModel('AtlasSpriteSheet', options);
339
331
  /** @type {import('../file/file.model.js').FileModel} */
340
- const File = DataBaseProviderService.getModel("File", options);
332
+ const File = DataBaseProviderService.getModel('File', options);
341
333
 
342
334
  if (req.params.id) {
343
335
  const atlasDoc = await AtlasSpriteSheet.findById(req.params.id);
@@ -39,6 +39,25 @@ class CyberiaActionController {
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 CyberiaActionService.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
+ };
42
61
  static put = async (req, res, options) => {
43
62
  try {
44
63
  const result = await CyberiaActionService.put(req, res, options);
@@ -1,48 +1,42 @@
1
1
  import { Schema, model } from 'mongoose';
2
- import { CYBERIA_ACTION_TYPES } from '../../client/components/cyberia/SharedDefaultsCyberia.js';
3
2
 
4
3
  // https://mongoosejs.com/docs/2.7.x/docs/schematypes.html
5
4
 
6
5
  const CyberiaActionSchema = new Schema(
7
6
  {
8
- // Spatial origin: the NPC/entity cell that provides this action.
9
- // Matched against map entity initCellX/initCellY during instance init
10
- // and assignable from ObjectLayerEngineModal.
7
+ // Spatial origin: the cell whose matching bot entity provides this action.
8
+ // An action has NO type its capabilities are whatever payloads are
9
+ // populated (questDialogueCodes / shopItems / craftRecipes / storageSlots),
10
+ // resolved per player. The quest-provider capability is active when there
11
+ // are CyberiaQuests bound to this same (sourceMapCode, sourceCellX,
12
+ // sourceCellY).
11
13
  sourceMapCode: { type: String, trim: true },
12
14
  sourceCellX: { type: Number },
13
15
  sourceCellY: { type: Number },
14
16
 
15
17
  // ── Identity ──────────────────────────────────────────────────────────
18
+ // `code` is a generic, location-scoped slug (it associates the action with
19
+ // its cell, not a single behavior). `label` is the name shown on the bot's
20
+ // overhead nameplate (the client fetches it by code via REST).
16
21
  code: { type: String, required: true, trim: true },
17
- type: {
18
- type: String,
19
- required: true,
20
- trim: true,
21
- enum: CYBERIA_ACTION_TYPES,
22
- },
23
22
  label: { type: String, trim: true, default: '' },
24
23
 
25
- // The item ID of the NPC/entity that provides this action.
26
- // Used to match 'talk' quest objectives: a step with { type: 'talk', itemId: X }
27
- // is satisfied when the player triggers an action where provideItemId === X.
28
- // Typically the entity's active skin ObjectLayer item ID (e.g. 'wason', 'alex').
29
- provideItemId: { type: String, trim: true, default: '' },
30
-
31
- // Quest code granted to the player on their first interaction with this action.
32
- // Used by 'quest-talk' NPCs to start a quest chain on first contact.
33
- // Empty string = no quest granted.
34
- grantQuestCode: { type: String, trim: true, default: '' },
35
-
36
24
  // ── Dialogue ──────────────────────────────────────────────────────────
37
- // General greeting/intro dialogue shown when the action button is tapped.
25
+ // General greeting shown when the interaction modal opens. Empty = client
26
+ // falls back to /code/default-<skin> from the entity's active skin.
38
27
  dialogCode: { type: String, trim: true, default: '' },
39
28
 
40
- // Ordered list of CyberiaDialogue codes the player must view to satisfy
41
- // a 'talk' quest objective linked to this action via provideItemId.
42
- // For simple actions this may match dialogCode; for multi-stage NPCs it can diverge.
43
- questDialogueCodes: [{ type: String, trim: true }],
29
+ // Per-quest dialogue map: the CyberiaDialogue code to display for each quest
30
+ // this NPC handles. The server validates a `talk` objective only when the
31
+ // viewed dialogCode matches the entry for that quest.
32
+ questDialogueCodes: [
33
+ {
34
+ questCode: { type: String, required: true, trim: true },
35
+ dialogCode: { type: String, required: true, trim: true },
36
+ },
37
+ ],
44
38
 
45
- // ── Shop payload (type="shop") ─────────────────────────────────────────
39
+ // ── Shop payload ──────────────────────────────────────────────────────
46
40
  shopItems: [
47
41
  {
48
42
  itemId: { type: String, required: true, trim: true },
@@ -76,8 +70,6 @@ const CyberiaActionSchema = new Schema(
76
70
  );
77
71
 
78
72
  CyberiaActionSchema.index({ code: 1 }, { unique: true });
79
- CyberiaActionSchema.index({ provideItemId: 1 });
80
- CyberiaActionSchema.index({ grantQuestCode: 1 }, { sparse: true });
81
73
  CyberiaActionSchema.index({ sourceMapCode: 1, sourceCellX: 1, sourceCellY: 1 });
82
74
 
83
75
  const CyberiaActionModel = model('CyberiaAction', CyberiaActionSchema);