@underpostnet/cyberia 3.3.73 → 3.3.77
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.
- package/.env.example +17 -5
- package/.github/workflows/coverall.cyberia.ci.yml +10 -0
- package/.github/workflows/docker-image.cyberia-client.ci.yml +1 -1
- package/.github/workflows/docker-image.cyberia-client.dev.ci.yml +1 -1
- package/.github/workflows/docker-image.cyberia-server.ci.yml +1 -1
- package/.github/workflows/docker-image.cyberia-server.dev.ci.yml +1 -1
- package/.github/workflows/docker-image.engine-cyberia.ci.yml +1 -1
- package/.github/workflows/docker-image.engine-cyberia.dev.ci.yml +1 -1
- package/.github/workflows/engine-cyberia.ci.yml +1 -1
- package/.github/workflows/publish.cyberia.ci.yml +44 -0
- package/AGENTS.md +61 -12
- package/CHANGELOG.md +159 -161
- package/CLI-HELP.md +20 -13
- package/Dockerfile +1 -1
- package/Dockerfile.dev +1 -1
- package/Dockerfile.test +2 -2
- package/README.md +28 -28
- package/bin/build.js +96 -21
- package/bin/cyberia.js +1247 -1373
- package/bin/index.js +1247 -1373
- package/compose.env +17 -5
- package/conf.js +7 -4
- package/deploy/cyberia-client/deploy.sh +10 -11
- package/deploy/cyberia-client/package.sh +0 -1
- package/deploy/cyberia-client/state.sh +3 -2
- package/deploy/cyberia-server/deploy.sh +10 -11
- package/deploy/cyberia-server/package.sh +0 -1
- package/deploy/cyberia-server/state.sh +3 -2
- package/deploy/dd-cyberia/deploy.sh +5 -5
- package/deploy/dd-cyberia/init.sh +15 -14
- package/deploy/dd-cyberia/package.sh +0 -1
- package/deploy/dd-cyberia/state.sh +2 -2
- package/deploy/dd-cyberia/sync-deploy.sh +139 -75
- package/deploy/lib/config.sh +29 -0
- package/deploy/lib/github-actions-logging.sh +92 -76
- package/deploy/lib/host.sh +28 -19
- package/deploy/lib/state.sh +4 -1
- package/deploy/pwa-microservices-template/deploy.sh +10 -8
- package/deploy/release/deploy.sh +0 -2
- package/deployment.yaml +1 -1
- package/docker-compose.yml +23 -17
- package/docs/coverage/cyberia/api/cyberia-instance/cyberia-fallback-capture.js.html +1591 -0
- package/docs/coverage/cyberia/api/cyberia-instance/index.html +116 -0
- package/docs/coverage/cyberia/api/cyberia-server-defaults/cyberia-server-defaults.js.html +4138 -0
- package/docs/coverage/cyberia/api/cyberia-server-defaults/index.html +116 -0
- package/docs/coverage/cyberia/api/object-layer/index.html +116 -0
- package/docs/coverage/cyberia/api/object-layer/object-layer.model.js.html +1393 -0
- package/docs/coverage/cyberia/base.css +362 -0
- package/docs/coverage/cyberia/block-navigation.js +82 -0
- package/docs/coverage/cyberia/favicon.png +0 -0
- package/docs/coverage/cyberia/index.html +161 -0
- package/docs/coverage/cyberia/prettify.css +101 -0
- package/docs/coverage/cyberia/prettify.js +937 -0
- package/docs/coverage/cyberia/projects/cyberia/atlas-sprite-sheet-generator.js.html +1315 -0
- package/docs/coverage/cyberia/projects/cyberia/index.html +176 -0
- package/docs/coverage/cyberia/projects/cyberia/instance-backup.js.html +715 -0
- package/docs/coverage/cyberia/projects/cyberia/instance-data.js.html +2182 -0
- package/docs/coverage/cyberia/projects/cyberia/shape-generator.js.html +3265 -0
- package/docs/coverage/cyberia/projects/cyberia/stat-balance.js.html +328 -0
- package/docs/coverage/cyberia/sort-arrow-sprite.png +0 -0
- package/docs/coverage/cyberia/sorter.js +205 -0
- package/hardhat/package-lock.json +2 -2
- package/hardhat/package.json +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +1 -1
- package/manifests/deployment/dd-cyberia-development/deployment.yaml +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/mongo-express/deployment.yaml +46 -27
- package/manifests/deployment/mongo-express/kustomization.yaml +9 -0
- package/manifests/deployment/mongo-express/mongo-express-nodeport.yaml +18 -0
- package/manifests/deployment/mongo-express/service.yaml +16 -0
- package/manifests/deployment/mongo-express-no-auth/disable-mongodb-auth.yaml +16 -0
- package/manifests/deployment/mongo-express-no-auth/kustomization.yaml +16 -0
- package/package.json +19 -17
- package/scripts/test-monitor.sh +3 -3
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +14 -9
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +36 -5
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.router.js +5 -0
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +88 -184
- package/src/api/cyberia-action/cyberia-action.router.js +2 -2
- package/src/api/cyberia-action/cyberia-action.service.js +3 -4
- package/src/api/cyberia-audio/cyberia-audio.controller.js +6 -0
- package/src/api/cyberia-audio/cyberia-audio.model.js +61 -0
- package/src/api/cyberia-audio/cyberia-audio.router.js +21 -0
- package/src/api/cyberia-audio/cyberia-audio.service.js +220 -0
- package/src/api/cyberia-client-hints/cyberia-client-hints.controller.js +1 -3
- package/src/api/cyberia-client-hints/cyberia-client-hints.model.js +10 -19
- package/src/api/cyberia-client-hints/cyberia-client-hints.router.js +12 -34
- package/src/api/cyberia-client-hints/cyberia-client-hints.service.js +15 -32
- package/src/api/cyberia-dialogue/cyberia-dialogue.model.js +1 -1
- package/src/api/cyberia-dialogue/cyberia-dialogue.router.js +1 -1
- package/src/api/cyberia-entity/cyberia-entity.model.js +2 -0
- package/src/api/cyberia-entity/cyberia-entity.service.js +1 -1
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.controller.js +8 -2
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.model.js +31 -20
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.router.js +16 -1
- package/src/api/cyberia-entity-type-default/cyberia-entity-type-default.service.js +466 -3
- package/src/api/cyberia-instance/cyberia-fallback-capture.js +87 -51
- package/src/api/cyberia-instance/cyberia-fallback-world.js +21 -40
- package/src/api/cyberia-instance/cyberia-instance-boot.service.js +5 -5
- package/src/api/cyberia-instance/cyberia-instance-items.js +98 -0
- package/src/api/cyberia-instance/cyberia-instance-map.service.js +34 -141
- package/src/api/cyberia-instance/cyberia-instance.controller.js +1 -4
- package/src/api/cyberia-instance/cyberia-instance.model.js +0 -14
- package/src/api/cyberia-instance/cyberia-instance.router.js +3 -16
- package/src/api/cyberia-instance/cyberia-instance.service.js +3 -43
- package/src/api/cyberia-instance/cyberia-world-generator.js +8 -13
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.model.js +49 -75
- package/src/api/cyberia-instance-conf/cyberia-instance-conf.service.js +74 -1
- package/src/api/cyberia-map/cyberia-map.service.js +6 -2
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.controller.js +31 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.model.js +56 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.router.js +33 -0
- package/src/api/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +189 -0
- package/src/api/cyberia-quest/cyberia-quest.router.js +3 -3
- package/src/api/cyberia-quest/cyberia-quest.service.js +3 -3
- package/src/api/cyberia-quest-progress/cyberia-quest-progress.model.js +1 -1
- package/src/api/cyberia-server-defaults/cyberia-server-defaults.js +403 -188
- package/src/api/cyberia-skill/cyberia-skill.model.js +6 -6
- package/src/api/file/file.ref.js +153 -0
- package/src/api/file/file.ref.json +11 -2
- package/src/api/object-layer/object-layer.model.js +47 -12
- package/src/api/object-layer/object-layer.router.js +10 -3
- package/src/api/object-layer/object-layer.service.js +113 -120
- package/src/cli/app.js +12 -1
- package/src/cli/client.js +2 -2
- package/src/cli/cluster.js +80 -18
- package/src/cli/deploy.js +215 -34
- package/src/cli/docker-compose.js +1 -1
- package/src/cli/fs-selection.js +115 -0
- package/src/cli/fs.js +158 -342
- package/src/cli/index.js +22 -10
- package/src/cli/release.js +3 -1
- package/src/cli/repository.js +35 -9
- package/src/cli/run.js +80 -199
- package/src/cli/test.js +4 -0
- package/src/cli/wireguard.js +32 -31
- package/src/client/components/core/Badge.js +0 -1
- package/src/client/components/core/CssCore.js +8 -1
- package/src/client/components/core/Docs.js +77 -100
- package/src/client/components/core/Modal.js +77 -23
- package/src/client/components/core/ToolTip.js +15 -0
- package/src/client/components/cryptokoyn/AppShellCryptokoyn.js +0 -2
- package/src/client/components/cyberia/ActionEngineCyberia.js +12 -36
- package/src/client/components/cyberia/EntityEngineCyberia.js +492 -121
- package/src/client/components/cyberia/InstanceEngineCyberia.js +129 -160
- package/src/client/components/cyberia/InstanceSelectionView.js +7 -1
- package/src/client/components/cyberia/MapEngineCyberia.js +175 -69
- package/src/client/components/cyberia/ObjectLayerEngine.js +1011 -97
- package/src/client/components/cyberia/ObjectLayerEngineModal.js +81 -155
- package/src/client/components/cyberia/ObjectLayerEngineViewer.js +113 -98
- package/src/client/components/cyberia/SharedDefaultsCyberia.js +215 -56
- package/src/client/components/cyberia-portal/AppShellCyberiaPortal.js +0 -34
- package/src/client/components/cyberia-portal/CssCyberiaPortal.js +1 -1
- package/src/client/components/cyberia-portal/MainBodyCyberiaPortal.js +712 -76
- package/src/client/components/cyberia-portal/RouterCyberiaPortal.js +0 -4
- package/src/client/components/cyberia-portal/TranslateCyberiaPortal.js +0 -4
- package/src/client/components/default/AppShellDefault.js +1 -4
- package/src/client/components/itemledger/AppShellItemledger.js +0 -2
- package/src/client/components/underpost/AppShellUnderpost.js +1 -1
- package/src/client/public/cyberia-docs/ARCHITECTURE.md +15 -15
- package/src/client/public/cyberia-docs/CYBERIA-CLI.md +280 -24
- package/src/client/public/cyberia-docs/CYBERIA-CLIENT.md +51 -29
- package/src/client/public/cyberia-docs/CYBERIA-SAGA.md +52 -53
- package/src/client/public/cyberia-docs/CYBERIA-SERVER.md +16 -11
- package/src/client/public/cyberia-docs/CYBERIA.md +28 -28
- package/src/client/public/cyberia-docs/ENTITY-PROFILE.md +11 -5
- package/src/client/public/cyberia-docs/ROADMAP.md +1 -1
- package/src/client/public/cyberia-docs/STATS-PROGRESSION.md +217 -0
- package/src/client/public/cyberia-docs/WHITE-PAPER.md +20 -12
- package/src/client/services/cyberia-audio/cyberia-audio.service.js +99 -0
- package/src/client/services/cyberia-entity-type-default/cyberia-entity-type-default.service.js +38 -0
- package/src/client/services/cyberia-instance/cyberia-instance.management.js +5 -4
- package/src/client/services/cyberia-instance/cyberia-instance.service.js +0 -40
- package/src/client/services/cyberia-map/cyberia-map.management.js +5 -4
- package/src/client/services/cyberia-map-audio-conf/cyberia-map-audio-conf.service.js +138 -0
- package/src/client/services/object-layer/object-layer.management.js +6 -10
- package/src/client/services/object-layer/object-layer.service.js +3 -1
- package/src/client/ssr/views/Cyberia404.js +3 -3
- package/src/client-builder/client-build-docs.js +52 -52
- package/src/client-builder/client-build.js +67 -44
- package/src/client-builder/client-bundle.js +347 -0
- package/src/db/mongo/MongoExpress.js +187 -0
- package/src/grpc/cyberia/grpc-server.js +5 -7
- package/src/index.js +1 -1
- package/src/projects/cyberia/atlas-sprite-sheet-generator.js +143 -146
- package/src/projects/cyberia/atlas-sprite-sheet-store.js +391 -0
- package/src/projects/cyberia/besu-genesis-generator.js +5 -14
- package/src/projects/cyberia/boot-contract-fixtures.js +32 -0
- package/src/projects/cyberia/catalog-cyberia.js +3 -2
- package/src/projects/cyberia/gemini-client.js +3 -3
- package/src/projects/cyberia/generate-saga.js +4 -11
- package/src/projects/cyberia/hot-reload-trigger.js +17 -28
- package/src/projects/cyberia/instance-backup.js +210 -0
- package/src/projects/cyberia/instance-data.js +177 -257
- package/src/projects/cyberia/map-preview-generator.js +32 -68
- package/src/projects/cyberia/object-layer.js +152 -319
- package/src/projects/cyberia/seed-audio.js +139 -0
- package/src/projects/cyberia/semantic-layer-generator-skin.js +2 -8
- package/src/projects/cyberia/semantic-layer-generator.js +2 -18
- package/src/projects/cyberia/stat-balance.js +81 -0
- package/src/projects/cyberia/stat-commands.js +23 -0
- package/src/projects/cyberia/stat-contract-generator.js +101 -0
- package/src/runtime/cyberia-client/Dockerfile +8 -4
- package/src/runtime/cyberia-client/Dockerfile.dev +5 -2
- package/src/runtime/cyberia-server/Dockerfile +9 -2
- package/src/runtime/cyberia-server/Dockerfile.dev +8 -1
- package/src/runtime/engine-cyberia/Dockerfile +1 -1
- package/src/runtime/engine-cyberia/Dockerfile.dev +1 -1
- package/src/runtime/engine-cyberia/Dockerfile.test +2 -2
- package/src/runtime/engine-cyberia/compose.env +17 -5
- package/src/runtime/engine-cyberia/docker-compose.yml +23 -17
- package/src/server/build/coverage.js +113 -42
- package/src/server/build/package.js +55 -12
- package/src/server/build/testing.js +59 -9
- package/src/server/network/middlewares.js +8 -2
- package/src/server/network/underpost-gateway.js +113 -14
- package/src/server/ops/logger.js +70 -35
- package/src/server/runtime/conf.js +40 -9
- package/src/server/runtime/start.js +11 -2
- package/src/server/storage/data-query.js +16 -0
- package/src/server/storage/downloader.js +62 -14
- package/src/server/storage/zip.js +153 -0
- package/test/integration/app/cyberia/atlas-sprite-sheet-store.test.js +402 -0
- package/test/integration/app/cyberia/atlas-sprite-sheet.test.js +86 -0
- package/test/integration/app/cyberia/cyberia-cli-plain-reads.test.js +34 -0
- package/test/integration/app/cyberia/cyberia-entity-type-default.test.js +842 -0
- package/test/integration/app/cyberia/cyberia-instance-conf-coerce.test.js +53 -0
- package/test/integration/app/cyberia/cyberia-instance-conf-defaults.test.js +16 -22
- package/test/integration/app/cyberia/cyberia-instance-items.test.js +90 -0
- package/test/integration/app/cyberia/cyberia-load.test.js +6 -7
- package/test/integration/app/cyberia/cyberia-map-audio-conf.test.js +256 -0
- package/test/integration/app/cyberia/cyberia-stats.test.js +150 -0
- package/test/integration/app/cyberia/fallback-world-capture.test.js +87 -2
- package/test/integration/app/cyberia/object-layer-item-selection.test.js +56 -0
- package/test/integration/app/cyberia/object-layer-natural-key.test.js +32 -0
- package/test/integration/app/cyberia/seed-audio.test.js +275 -0
- package/test/integration/infra/2-network/wireguard-cli.test.js +33 -9
- package/test/integration/infra/2-network/wireguard-edge.test.js +58 -18
- package/test/integration/infra/3-cluster/mongo-express-deploy.test.js +156 -0
- package/test/integration/infra/4-ingress/api-cross-origin.test.js +79 -0
- package/test/integration/infra/4-ingress/underpost-ingress.test.js +1 -1
- package/test/unit/catalog.test.js +13 -2
- package/test/unit/client-build-docs.test.js +97 -70
- package/test/unit/client-bundle.test.js +313 -0
- package/test/unit/conf-resolution.test.js +58 -7
- package/test/unit/coverage-artifact.test.js +120 -29
- package/test/unit/cyberia/instance-backup.test.js +183 -0
- package/test/unit/cyberia/instance-data.test.js +314 -0
- package/test/unit/cyberia/instance-object-layer-items.test.js +46 -0
- package/test/unit/cyberia/publish-workflow.test.js +22 -0
- package/test/unit/cyberia/stat-balance.test.js +69 -0
- package/test/unit/deploy-legacy-gateway-sweep.test.js +63 -0
- package/test/unit/deploy-log-table.test.js +76 -5
- package/test/unit/downloader-integrity.test.js +98 -0
- package/test/unit/file-reference-registry.test.js +141 -0
- package/test/unit/fs-storage-paths.test.js +596 -0
- package/test/unit/gateway-pod-fetch.test.js +95 -0
- package/test/unit/letsencrypt-issuer.test.js +101 -0
- package/test/unit/logger-redaction.test.js +29 -0
- package/test/unit/package.test.js +70 -16
- package/test/unit/prepare-host.test.js +84 -1
- package/test/unit/release-bump.test.js +3 -1
- package/test/unit/start-options.test.js +12 -0
- package/test/unit/test-tiers.test.js +27 -0
- package/test/unit/zip-archive.test.js +142 -0
- package/vitest.config.js +11 -3
- package/src/api/cyberia-instance/cyberia-fallback-default-items.js +0 -63
- package/src/client/components/cyberia/FallbackWorldEngineCyberia.js +0 -368
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import nodePath from 'path';
|
|
4
|
+
import { Types } from 'mongoose';
|
|
5
|
+
import { DataBaseProviderService } from '../../db/DataBaseProvider.js';
|
|
6
|
+
import { FileCleanup } from '../file/file.service.js';
|
|
7
|
+
import { loggerFactory } from '../../server/ops/logger.js';
|
|
8
|
+
import { DataQuery } from '../../server/storage/data-query.js';
|
|
9
|
+
import { fileRefFields } from '../file/file.ref.js';
|
|
10
|
+
|
|
11
|
+
const logger = loggerFactory(import.meta);
|
|
12
|
+
|
|
13
|
+
const AUDIO_MIMETYPE = 'audio/wav';
|
|
14
|
+
|
|
15
|
+
/** The File-referencing fields of this model, read from the registry that owns that mapping. */
|
|
16
|
+
const FILE_FIELDS = fileRefFields('cyberia-audio');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The generic File `_id` an asset's bytes resolve through, derived from the asset code and the
|
|
20
|
+
* bytes themselves.
|
|
21
|
+
*
|
|
22
|
+
* Content-addressed on purpose. The client fetches a WAV as `/api/file/blob/<fileId>` and caches
|
|
23
|
+
* it, so re-recording an asset under a stable id would serve the old sound from that cache
|
|
24
|
+
* forever. A changed render therefore lands on a new id — and the import deletes the one it
|
|
25
|
+
* replaced, so the change costs nothing. Identical bytes re-derive the same id, which is what
|
|
26
|
+
* makes re-importing an unchanged bank a no-op rather than a churn of File documents.
|
|
27
|
+
*
|
|
28
|
+
* The code is folded in so two assets that happen to render identical bytes still own separate
|
|
29
|
+
* File documents; sharing one would make deleting either orphan the other.
|
|
30
|
+
*
|
|
31
|
+
* @param {string} code - Asset code.
|
|
32
|
+
* @param {string} md5 - Hex MD5 of the WAV bytes.
|
|
33
|
+
* @returns {import('mongoose').Types.ObjectId}
|
|
34
|
+
*/
|
|
35
|
+
const audioFileId = (code, md5) =>
|
|
36
|
+
new Types.ObjectId(crypto.createHash('sha256').update(`cyberia-audio:${code}:${md5}`).digest('hex').slice(0, 24));
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Validates a `<name>.json` sidecar written by the cyberia-audio package, and reads it into the
|
|
40
|
+
* shape this platform stores.
|
|
41
|
+
*
|
|
42
|
+
* Identity is all the import requires: `id` becomes the asset's code, and the rest is the
|
|
43
|
+
* recorder's own metadata — including `bus`, the `src/audio-module/<bus-id>/` directory the
|
|
44
|
+
* module was authored on, which is the same bus vocabulary a map binding routes through.
|
|
45
|
+
*
|
|
46
|
+
* @param {object} manifest - Parsed manifest.
|
|
47
|
+
* @param {string} manifestPath - Source path, for error messages.
|
|
48
|
+
* @returns {object} The manifest as stored.
|
|
49
|
+
* @throws {Error} When the manifest carries no usable asset identity.
|
|
50
|
+
*/
|
|
51
|
+
const readManifest = (manifest, manifestPath) => {
|
|
52
|
+
if (!manifest || typeof manifest !== 'object') throw new Error(`Invalid audio manifest: ${manifestPath}`);
|
|
53
|
+
if (!manifest.id) throw new Error(`Audio manifest has no id: ${manifestPath}`);
|
|
54
|
+
const { bus = '', ...rest } = manifest;
|
|
55
|
+
return { ...rest, bus };
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
class CyberiaAudioService {
|
|
59
|
+
/**
|
|
60
|
+
* Imports one recorded `<name>.wav` + `<name>.json` pair, upserting both the backing
|
|
61
|
+
* File document and the CyberiaAudio document keyed by the asset's code.
|
|
62
|
+
*
|
|
63
|
+
* @param {{wavPath: string, manifestPath: string}} record - Absolute or cwd-relative artifact pair.
|
|
64
|
+
* @param {{host: string, path: string}} options - Provider context.
|
|
65
|
+
* @returns {Promise<object>} The upserted CyberiaAudio document.
|
|
66
|
+
*/
|
|
67
|
+
static importRecord = async ({ wavPath, manifestPath }, options) => {
|
|
68
|
+
const CyberiaAudio = DataBaseProviderService.getModel('CyberiaAudio', options);
|
|
69
|
+
const File = DataBaseProviderService.getModel('file', options);
|
|
70
|
+
|
|
71
|
+
const manifest = readManifest(JSON.parse(await fs.readFile(manifestPath, 'utf8')), manifestPath);
|
|
72
|
+
const data = await fs.readFile(wavPath);
|
|
73
|
+
const md5 = crypto.createHash('md5').update(data).digest('hex');
|
|
74
|
+
const fileData = { name: `${manifest.id}.wav`, data, size: data.length, mimetype: AUDIO_MIMETYPE, md5 };
|
|
75
|
+
|
|
76
|
+
await new CyberiaAudio({ code: manifest.id, manifest }).validate();
|
|
77
|
+
if (
|
|
78
|
+
44 > data.length ||
|
|
79
|
+
data.length > 8 * 1024 * 1024 ||
|
|
80
|
+
data.toString('ascii', 0, 4) !== 'RIFF' ||
|
|
81
|
+
data.toString('ascii', 8, 12) !== 'WAVE' ||
|
|
82
|
+
data.readUInt32LE(4) !== data.length - 8
|
|
83
|
+
)
|
|
84
|
+
throw new Error(`Invalid WAV: ${wavPath}`);
|
|
85
|
+
|
|
86
|
+
const existing = await CyberiaAudio.findOne({ code: manifest.id });
|
|
87
|
+
// Derived from the bytes, so it is the same id on an unchanged re-import and a new one the
|
|
88
|
+
// moment the render changes. Writing the File first leaves the bytes reachable if the run is
|
|
89
|
+
// interrupted before the metadata upsert; the stale document is then dropped on the next run.
|
|
90
|
+
const fileId = audioFileId(manifest.id, md5);
|
|
91
|
+
await File.findOneAndUpdate(
|
|
92
|
+
{ _id: fileId },
|
|
93
|
+
{ $set: fileData },
|
|
94
|
+
{ upsert: true, returnDocument: 'after', runValidators: true },
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const doc = await CyberiaAudio.findOneAndUpdate(
|
|
98
|
+
{ code: manifest.id },
|
|
99
|
+
{ $set: { code: manifest.id, fileId, manifest } },
|
|
100
|
+
{ upsert: true, returnDocument: 'after', runValidators: true },
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// Only once the asset points at the new bytes: the superseded blob is unreferenced from here
|
|
104
|
+
// on, and leaving it behind is what orphans a File document.
|
|
105
|
+
if (existing) {
|
|
106
|
+
await FileCleanup.cleanupReplacedFiles({
|
|
107
|
+
oldDoc: existing,
|
|
108
|
+
newData: { fileId },
|
|
109
|
+
fileFields: FILE_FIELDS,
|
|
110
|
+
File,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return doc;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Deletes an asset and the File document holding its bytes.
|
|
118
|
+
*
|
|
119
|
+
* The asset is the only reference to that blob, so dropping one without the other is exactly
|
|
120
|
+
* what `underpost db clean-fs` would later have to sweep up.
|
|
121
|
+
*
|
|
122
|
+
* @param {Array<object>} assets - CyberiaAudio documents about to be removed.
|
|
123
|
+
* @param {{host: string, path: string}} options - Provider context.
|
|
124
|
+
* @returns {Promise<number>} Count of File documents deleted.
|
|
125
|
+
*/
|
|
126
|
+
static deleteBackingFiles = async (assets, options) => {
|
|
127
|
+
const File = DataBaseProviderService.getModel('file', options);
|
|
128
|
+
let deleted = 0;
|
|
129
|
+
for (const asset of assets) {
|
|
130
|
+
const ids = await FileCleanup.deleteDocumentFiles({ doc: asset, fileFields: FILE_FIELDS, File });
|
|
131
|
+
deleted += ids.length;
|
|
132
|
+
}
|
|
133
|
+
return deleted;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Imports every valid `<name>.wav` + `<name>.json` pair found in a records directory.
|
|
138
|
+
* A WAV with no manifest beside it is skipped with a warning: the pair is the unit of import.
|
|
139
|
+
*
|
|
140
|
+
* @param {{recordsPath: string, codes?: Array<string>|null}} params - Source directory and optional code filter.
|
|
141
|
+
* @param {{host: string, path: string}} options - Provider context.
|
|
142
|
+
* @returns {Promise<Array<object>>} The upserted CyberiaAudio documents.
|
|
143
|
+
*/
|
|
144
|
+
static importRecords = async ({ recordsPath, codes = null }, options) => {
|
|
145
|
+
if (!(await fs.pathExists(recordsPath))) throw new Error(`Records directory not found: ${recordsPath}`);
|
|
146
|
+
|
|
147
|
+
const stems = (await fs.readdir(recordsPath))
|
|
148
|
+
.filter((name) => name.toLowerCase().endsWith('.wav'))
|
|
149
|
+
.map((name) => nodePath.basename(name, nodePath.extname(name)))
|
|
150
|
+
.filter((stem) => !codes || codes.includes(stem))
|
|
151
|
+
.sort();
|
|
152
|
+
|
|
153
|
+
if (codes) {
|
|
154
|
+
for (const code of codes) if (!stems.includes(code)) throw new Error(`No ${code}.wav found in ${recordsPath}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const imported = [];
|
|
158
|
+
for (const stem of stems) {
|
|
159
|
+
const wavPath = nodePath.join(recordsPath, `${stem}.wav`);
|
|
160
|
+
const manifestPath = nodePath.join(recordsPath, `${stem}.json`);
|
|
161
|
+
if (!(await fs.pathExists(manifestPath))) {
|
|
162
|
+
logger.warn(`Skipping ${stem}.wav: no ${stem}.json manifest beside it`);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
const doc = await CyberiaAudioService.importRecord({ wavPath, manifestPath }, options);
|
|
166
|
+
logger.info(`Imported ${doc.code}`, { _id: doc._id.toString(), fileId: doc.fileId.toString() });
|
|
167
|
+
imported.push(doc);
|
|
168
|
+
}
|
|
169
|
+
return imported;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
static post = async (req, res, options) => {
|
|
173
|
+
/** @type {import('./cyberia-audio.model.js').CyberiaAudioModel} */
|
|
174
|
+
const CyberiaAudio = DataBaseProviderService.getModel('CyberiaAudio', options);
|
|
175
|
+
return await new CyberiaAudio(req.body).save();
|
|
176
|
+
};
|
|
177
|
+
static get = async (req, res, options) => {
|
|
178
|
+
/** @type {import('./cyberia-audio.model.js').CyberiaAudioModel} */
|
|
179
|
+
const CyberiaAudio = DataBaseProviderService.getModel('CyberiaAudio', options);
|
|
180
|
+
if (req.params.id) return await CyberiaAudio.findById(req.params.id);
|
|
181
|
+
|
|
182
|
+
// Parse query parameters using DataQuery helper
|
|
183
|
+
const { query, sort, skip, limit, page } = DataQuery.parse(req.query);
|
|
184
|
+
|
|
185
|
+
const [data, total] = await Promise.all([
|
|
186
|
+
CyberiaAudio.find(query).sort(sort).limit(limit).skip(skip),
|
|
187
|
+
CyberiaAudio.countDocuments(query),
|
|
188
|
+
]);
|
|
189
|
+
|
|
190
|
+
const totalPages = Math.ceil(total / limit);
|
|
191
|
+
return { data, total, page, totalPages };
|
|
192
|
+
};
|
|
193
|
+
static put = async (req, res, options) => {
|
|
194
|
+
/** @type {import('./cyberia-audio.model.js').CyberiaAudioModel} */
|
|
195
|
+
const CyberiaAudio = DataBaseProviderService.getModel('CyberiaAudio', options);
|
|
196
|
+
const File = DataBaseProviderService.getModel('file', options);
|
|
197
|
+
const existing = await CyberiaAudio.findById(req.params.id);
|
|
198
|
+
const updated = await CyberiaAudio.findByIdAndUpdate(req.params.id, req.body, { returnDocument: 'after' });
|
|
199
|
+
// Repointing an asset at other bytes leaves the previous blob referenced by nothing.
|
|
200
|
+
if (existing) {
|
|
201
|
+
await FileCleanup.cleanupReplacedFiles({
|
|
202
|
+
oldDoc: existing,
|
|
203
|
+
newData: req.body,
|
|
204
|
+
fileFields: FILE_FIELDS,
|
|
205
|
+
File,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return updated;
|
|
209
|
+
};
|
|
210
|
+
static delete = async (req, res, options) => {
|
|
211
|
+
/** @type {import('./cyberia-audio.model.js').CyberiaAudioModel} */
|
|
212
|
+
const CyberiaAudio = DataBaseProviderService.getModel('CyberiaAudio', options);
|
|
213
|
+
const assets = req.params.id ? await CyberiaAudio.find({ _id: req.params.id }) : await CyberiaAudio.find();
|
|
214
|
+
await CyberiaAudioService.deleteBackingFiles(assets, options);
|
|
215
|
+
if (req.params.id) return await CyberiaAudio.findByIdAndDelete(req.params.id);
|
|
216
|
+
return await CyberiaAudio.deleteMany();
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export { CyberiaAudioService, audioFileId, readManifest };
|
|
@@ -9,9 +9,7 @@ class CyberiaClientHintsController {
|
|
|
9
9
|
host: options.host || 'default',
|
|
10
10
|
path: options.path || '/',
|
|
11
11
|
});
|
|
12
|
-
//
|
|
13
|
-
// operators can see whether the runtime fetched from the new
|
|
14
|
-
// collection, the compatibility read on instance-conf, the cache, or defaults.
|
|
12
|
+
// Diagnostic header: which source answered this request.
|
|
15
13
|
res.setHeader('X-Cyberia-Hints-Source', source);
|
|
16
14
|
return sendSuccess(res, data);
|
|
17
15
|
},
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Mongoose model for `CyberiaClientHints
|
|
2
|
+
* Mongoose model for `CyberiaClientHints`, the per-instance presentation
|
|
3
|
+
* overrides. Editor flows write it; the game client reads it over REST. The
|
|
4
|
+
* simulation never touches it.
|
|
3
5
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Ownership:
|
|
7
|
-
* - Read by: cyberia-client (via /api/cyberia-client-hints/:code), CMS UIs.
|
|
8
|
-
* - Written by: CMS / editor flows. The Cyberia simulation server
|
|
9
|
-
* never reads or writes this collection.
|
|
10
|
-
* - Cacheable: yes — the service layer keeps an in-memory TTL cache.
|
|
11
|
-
*
|
|
12
|
-
* Schema scope: purely visual fields. Anything that influences simulation
|
|
6
|
+
* Schema scope: visual fields only. A field that changes the simulation
|
|
13
7
|
* belongs in `CyberiaInstanceConf`.
|
|
14
8
|
*
|
|
15
9
|
* @module src/api/cyberia-client-hints/cyberia-client-hints.model.js
|
|
@@ -58,13 +52,10 @@ const EntityColorKeySchema = new Schema(
|
|
|
58
52
|
|
|
59
53
|
const CyberiaClientHintsSchema = new Schema(
|
|
60
54
|
{
|
|
61
|
-
// Instance code
|
|
62
|
-
// and CyberiaInstanceConf.instanceCode. Indexed and unique to make the
|
|
63
|
-
// service-layer cache a 1-to-1 keyed lookup.
|
|
55
|
+
// Instance code, matching CyberiaInstance.code. Unique: one hint set per code.
|
|
64
56
|
code: { type: String, required: true, unique: true, index: true },
|
|
65
57
|
|
|
66
|
-
//
|
|
67
|
-
// client's compile-time defaults.
|
|
58
|
+
// Palette overrides. An absent key keeps the client default.
|
|
68
59
|
palette: { type: [PaletteEntrySchema], default: [] },
|
|
69
60
|
|
|
70
61
|
// Optional per-entity-type color-key overrides.
|
|
@@ -73,8 +64,8 @@ const CyberiaClientHintsSchema = new Schema(
|
|
|
73
64
|
// Optional status-icon visual overrides (id → iconId + borderColor).
|
|
74
65
|
statusIcons: { type: [StatusIconHintSchema], default: [] },
|
|
75
66
|
|
|
76
|
-
// Camera, viewport
|
|
77
|
-
//
|
|
67
|
+
// Camera, viewport and cell-size tunings. null keeps the
|
|
68
|
+
// SharedDefaultsCyberia.RENDER_DEFAULTS value.
|
|
78
69
|
cellSize: { type: Number, default: null },
|
|
79
70
|
defaultObjWidth: { type: Number, default: null },
|
|
80
71
|
defaultObjHeight: { type: Number, default: null },
|
|
@@ -85,8 +76,8 @@ const CyberiaClientHintsSchema = new Schema(
|
|
|
85
76
|
interpolationMs: { type: Number, default: null },
|
|
86
77
|
devUi: { type: Boolean, default: null },
|
|
87
78
|
|
|
88
|
-
// Main UI font: TTF file name under engine assets/fonts/ (null =
|
|
89
|
-
//
|
|
79
|
+
// Main UI font: TTF file name under engine assets/fonts/ (null = client
|
|
80
|
+
// built-in font) and a uniform text-size multiplier.
|
|
90
81
|
fontFamily: { type: String, default: null },
|
|
91
82
|
fontFactorSize: { type: Number, default: null },
|
|
92
83
|
},
|
|
@@ -1,40 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module src/api/cyberia-client-hints
|
|
3
3
|
*
|
|
4
|
-
* Client presentation hints — read-only REST endpoint.
|
|
4
|
+
* Client presentation hints — read-only REST endpoint, off the simulation path.
|
|
5
|
+
* It serves optional per-instance overrides of render policy: palette, camera
|
|
6
|
+
* defaults, status-icon visuals, interpolation window, dev-overlay flag.
|
|
5
7
|
*
|
|
6
|
-
*
|
|
7
|
-
* -------
|
|
8
|
-
* The cyberia-server (Go authoritative simulation) and the WS init payload
|
|
9
|
-
* carry *only* simulation contracts. Client-render policy (palette, camera
|
|
10
|
-
* defaults, status-icon visuals, interpolation window, dev-overlay flag)
|
|
11
|
-
* lives off the simulation path entirely.
|
|
12
|
-
*
|
|
13
|
-
* This endpoint exposes optional per-instance overrides of those values.
|
|
14
|
-
* The client is required to function with no calls to this endpoint at
|
|
15
|
-
* all (it ships built-in defaults that match the canonical engine
|
|
16
|
-
* defaults 1-to-1 — see cyberia-client/src/domain/presentation_defaults.h).
|
|
17
|
-
*
|
|
18
|
-
* Endpoint
|
|
19
|
-
* --------
|
|
8
|
+
* Endpoints:
|
|
20
9
|
* GET /api/cyberia-client-hints/:instanceCode
|
|
21
10
|
* -> 200 { palette, entityColorKeys, statusIcons, cameraSmoothing,
|
|
22
11
|
* cameraZoom, defaultWidthScreenFactor,
|
|
23
12
|
* defaultHeightScreenFactor, interpolationMs, devUi }
|
|
24
|
-
* -> 404
|
|
25
|
-
*
|
|
26
|
-
* the normal path for stateless servers / fresh deployments).
|
|
27
|
-
*
|
|
13
|
+
* -> 404 when no instance carries that code. The client then uses its
|
|
14
|
+
* own built-in defaults, the normal path for a fresh deployment.
|
|
28
15
|
* GET /api/cyberia-client-hints/
|
|
29
|
-
* -> 200 canonical defaults
|
|
16
|
+
* -> 200 canonical defaults, same shape, no DB read.
|
|
30
17
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* - It does not touch gameplay state (no entity, map, economy, skill,
|
|
34
|
-
* equipment, or stat fields).
|
|
35
|
-
* - It is not consumed by cyberia-server; the Go process never calls
|
|
36
|
-
* this endpoint.
|
|
37
|
-
* - It is not authenticated. Presentation hints are not secret.
|
|
18
|
+
* Out of scope: gameplay state and authentication. Presentation hints hold no
|
|
19
|
+
* simulation field and no secret.
|
|
38
20
|
*/
|
|
39
21
|
|
|
40
22
|
import express from 'express';
|
|
@@ -50,15 +32,11 @@ class CyberiaClientHintsRouter {
|
|
|
50
32
|
const router = express.Router();
|
|
51
33
|
router.use(crossOriginMiddleware);
|
|
52
34
|
|
|
53
|
-
// GET /:code -> resolved hints.
|
|
54
|
-
//
|
|
55
|
-
// 1. In-memory TTL cache.
|
|
56
|
-
// 2. CyberiaClientHints collection (preferred).
|
|
57
|
-
// 3. Legacy presentation fields on CyberiaInstanceConf (back-compat).
|
|
58
|
-
// 4. Canonical client defaults (never cached so a later DB insert wins).
|
|
35
|
+
// GET /:code -> resolved hints. See the resolution order in
|
|
36
|
+
// cyberia-client-hints.service.js.
|
|
59
37
|
router.get('/:code', async (req, res) => await CyberiaClientHintsController.getByCode(req, res, options));
|
|
60
38
|
|
|
61
|
-
// GET / -> canonical defaults. No DB read.
|
|
39
|
+
// GET / -> canonical defaults. No DB read.
|
|
62
40
|
router.get('/', async (req, res) => await CyberiaClientHintsController.getDefaults(req, res, options));
|
|
63
41
|
|
|
64
42
|
return router;
|
|
@@ -3,22 +3,14 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Read-only service layer for client presentation hints.
|
|
5
5
|
*
|
|
6
|
-
* Resolution order
|
|
7
|
-
* 1. CyberiaClientHints collection
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* 3. Canonical compile-time defaults from
|
|
12
|
-
* SharedDefaultsCyberia.js. The C client bakes the
|
|
13
|
-
* same defaults at compile time, so this branch returns exactly the
|
|
14
|
-
* values the client already has.
|
|
6
|
+
* Resolution order, highest priority first:
|
|
7
|
+
* 1. CyberiaClientHints collection, the dedicated overrides collection.
|
|
8
|
+
* 2. Presentation fields on CyberiaInstanceConf with the same `code`.
|
|
9
|
+
* 3. Canonical defaults from SharedDefaultsCyberia.js, the same values the
|
|
10
|
+
* client holds built in.
|
|
15
11
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* mutate the underlying collection should call `clientHintsInvalidate(code)`.
|
|
19
|
-
*
|
|
20
|
-
* Output is a plain JSON-friendly object whose shape matches the C
|
|
21
|
-
* client's compile-time layout.
|
|
12
|
+
* An in-memory TTL cache keyed by instance code serves the hot path. A write
|
|
13
|
+
* to either collection must call `clientHintsInvalidate(code)`.
|
|
22
14
|
*/
|
|
23
15
|
|
|
24
16
|
import { DataBaseProviderService } from '../../db/DataBaseProvider.js';
|
|
@@ -31,8 +23,7 @@ import {
|
|
|
31
23
|
|
|
32
24
|
const logger = loggerFactory(import.meta);
|
|
33
25
|
|
|
34
|
-
// TTL
|
|
35
|
-
// enough that an editor change shows up within ~30s without manual flush.
|
|
26
|
+
// TTL absorbs bursty client fetches and still shows an editor change in ~30s.
|
|
36
27
|
const CACHE_TTL_MS = 30_000;
|
|
37
28
|
|
|
38
29
|
// One per instance code. value: { data, expiresAt }
|
|
@@ -56,9 +47,7 @@ function cacheSet(code, data) {
|
|
|
56
47
|
cache.set(code, { data, expiresAt: now() + CACHE_TTL_MS });
|
|
57
48
|
}
|
|
58
49
|
|
|
59
|
-
/** Invalidate
|
|
60
|
-
* mutating either CyberiaClientHints or CyberiaInstanceConf for
|
|
61
|
-
* the given code. */
|
|
50
|
+
/** Invalidate one cache entry, or the whole cache when `code` is empty. */
|
|
62
51
|
export function clientHintsInvalidate(code) {
|
|
63
52
|
if (code) {
|
|
64
53
|
cache.delete(code);
|
|
@@ -107,7 +96,7 @@ export async function resolveClientHints(code, options = {}) {
|
|
|
107
96
|
return { data: CYBERIA_CLIENT_HINTS_DEFAULTS, source: 'defaults' };
|
|
108
97
|
}
|
|
109
98
|
|
|
110
|
-
// 1. Preferred
|
|
99
|
+
// 1. Preferred source: the CyberiaClientHints collection.
|
|
111
100
|
if (HintsModel && code) {
|
|
112
101
|
const hint = await HintsModel.findOne({ code }).lean().catch(() => null);
|
|
113
102
|
if (hint) {
|
|
@@ -117,9 +106,7 @@ export async function resolveClientHints(code, options = {}) {
|
|
|
117
106
|
}
|
|
118
107
|
}
|
|
119
108
|
|
|
120
|
-
// 2.
|
|
121
|
-
// existed, look up the same code in CyberiaInstanceConf and read its
|
|
122
|
-
// presentation-shaped fields directly.
|
|
109
|
+
// 2. Instances that keep their presentation fields on CyberiaInstanceConf.
|
|
123
110
|
if (ConfModel && code) {
|
|
124
111
|
const fromConf =
|
|
125
112
|
(await ConfModel.findOne({ code }).lean().catch(() => null)) ||
|
|
@@ -131,22 +118,18 @@ export async function resolveClientHints(code, options = {}) {
|
|
|
131
118
|
}
|
|
132
119
|
}
|
|
133
120
|
|
|
134
|
-
// 3. Any
|
|
135
|
-
//
|
|
136
|
-
// server is currently running) does. This avoids pure built-in
|
|
137
|
-
// defaults in fresh environments where only a different code is seeded.
|
|
121
|
+
// 3. Any CyberiaClientHints document, when the requested code has none but
|
|
122
|
+
// another instance does. Beats falling straight through to defaults.
|
|
138
123
|
if (HintsModel) {
|
|
139
124
|
const anyHint = await HintsModel.findOne({}).lean().catch(() => null);
|
|
140
125
|
if (anyHint) {
|
|
141
126
|
const merged = buildClientHints(anyHint);
|
|
142
|
-
//
|
|
143
|
-
// but with a shorter TTL (5 s) so a proper seed wins quickly.
|
|
127
|
+
// Short TTL under the requested code so a later seed wins quickly.
|
|
144
128
|
if (code) cache.set(code, { data: merged, expiresAt: now() + 5_000 });
|
|
145
129
|
return { data: merged, source: 'presentation-hints-fallback' };
|
|
146
130
|
}
|
|
147
131
|
}
|
|
148
132
|
|
|
149
|
-
// 4. Canonical defaults.
|
|
150
|
-
// a default that could mask a later DB insert.
|
|
133
|
+
// 4. Canonical defaults. Never cached, so a later DB insert wins.
|
|
151
134
|
return { data: CYBERIA_CLIENT_HINTS_DEFAULTS, source: 'defaults' };
|
|
152
135
|
}
|
|
@@ -8,7 +8,7 @@ import { Schema, model, Types } from 'mongoose';
|
|
|
8
8
|
*
|
|
9
9
|
* Schema fields:
|
|
10
10
|
* code – primary grouping key, e.g. "default-lain" or "wason-intro".
|
|
11
|
-
* The
|
|
11
|
+
* The client fetches every line of one code in a single request.
|
|
12
12
|
* order – zero-based display sequence within the code group.
|
|
13
13
|
* speaker – display name shown above the dialogue line.
|
|
14
14
|
* text – the dialogue line itself.
|
|
@@ -9,7 +9,7 @@ class CyberiaDialogueRouter {
|
|
|
9
9
|
*/
|
|
10
10
|
static router(options) {
|
|
11
11
|
const router = express.Router();
|
|
12
|
-
// Direct lookup by code
|
|
12
|
+
// Direct lookup by code, e.g. "default-lain".
|
|
13
13
|
router.get(`/code/:code`, async (req, res) => await CyberiaDialogueController.getByCode(req, res, options));
|
|
14
14
|
return registerCrudRoutes(router, CyberiaDialogueController, options);
|
|
15
15
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Schema, model, Types } from 'mongoose';
|
|
2
|
+
import { ENTITY_LEVEL_MIN, ENTITY_LEVEL_MAX } from '../../client/components/cyberia/SharedDefaultsCyberia.js';
|
|
2
3
|
|
|
3
4
|
// https://mongoosejs.com/docs/2.7.x/docs/schematypes.html
|
|
4
5
|
|
|
5
6
|
const CyberiaEntitySchema = new Schema({
|
|
6
7
|
entityType: { type: String, default: 'floor' },
|
|
8
|
+
level: { type: Number, min: ENTITY_LEVEL_MIN, max: ENTITY_LEVEL_MAX, validate: Number.isInteger },
|
|
7
9
|
initCellX: { type: Number, default: 0 },
|
|
8
10
|
initCellY: { type: Number, default: 0 },
|
|
9
11
|
dimX: { type: Number, default: 1 },
|
|
@@ -29,7 +29,7 @@ class CyberiaEntityService {
|
|
|
29
29
|
static put = async (req, res, options) => {
|
|
30
30
|
/** @type {import('./cyberia-entity.model.js').CyberiaEntityModel} */
|
|
31
31
|
const CyberiaEntity = DataBaseProviderService.getModel("CyberiaEntity", options);
|
|
32
|
-
return await CyberiaEntity.findByIdAndUpdate(req.params.id, req.body);
|
|
32
|
+
return await CyberiaEntity.findByIdAndUpdate(req.params.id, req.body, { runValidators: true });
|
|
33
33
|
};
|
|
34
34
|
static delete = async (req, res, options) => {
|
|
35
35
|
/** @type {import('./cyberia-entity.model.js').CyberiaEntityModel} */
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import { buildCrudController } from '../../server/network/middlewares.js';
|
|
1
|
+
import { buildCrudController, serviceHandler } from '../../server/network/middlewares.js';
|
|
2
2
|
import { CyberiaEntityTypeDefaultService } from './cyberia-entity-type-default.service.js';
|
|
3
3
|
|
|
4
|
-
const CyberiaEntityTypeDefaultController = buildCrudController(CyberiaEntityTypeDefaultService
|
|
4
|
+
const CyberiaEntityTypeDefaultController = buildCrudController(CyberiaEntityTypeDefaultService, {
|
|
5
|
+
// Which instances run on this default. The link is stored on CyberiaInstanceConf.entityDefaults,
|
|
6
|
+
// so reading it is an ordinary conf read; only the write needs a route of its own.
|
|
7
|
+
setInstances: serviceHandler(CyberiaEntityTypeDefaultService.setInstances),
|
|
8
|
+
// Point one instance's conf at every default its own map content needs.
|
|
9
|
+
sync: serviceHandler(CyberiaEntityTypeDefaultService.sync),
|
|
10
|
+
});
|
|
5
11
|
|
|
6
12
|
export { CyberiaEntityTypeDefaultController };
|
|
@@ -6,24 +6,27 @@ import { Schema, model } from 'mongoose';
|
|
|
6
6
|
// item defaults that ship in cyberia-server-defaults.js (ENTITY_TYPE_DEFAULTS).
|
|
7
7
|
// A document binds an entity category (entityType) to the item ids the runtime
|
|
8
8
|
// rotates through by lifecycle state:
|
|
9
|
-
// liveItemIds
|
|
10
|
-
// deadItemIds
|
|
11
|
-
// dropItemIds
|
|
12
|
-
//
|
|
9
|
+
// liveItemIds — ObjectLayer item ids while the entity is alive.
|
|
10
|
+
// deadItemIds — ids swapped in on death / ghost state.
|
|
11
|
+
// dropItemIds — ids granted to the killer on depletion (resources).
|
|
12
|
+
// inventoryItemsIds — ids the entity carries but no lifecycle state activates.
|
|
13
|
+
// overrideItemsIdsState — per-id overrides of what the lists would otherwise derive.
|
|
13
14
|
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
15
|
+
// The three lifecycle lists are discriminators, not separate inventories. The entity holds one
|
|
16
|
+
// inventory — the union of all four lists — and the runtime activates the slots the context
|
|
17
|
+
// calls for. An id's active flag and quantity follow from the list it belongs to, so neither is
|
|
18
|
+
// stored per row — `overrideItemsIdsState` is the one exception, and it only ever adjusts an id
|
|
19
|
+
// the lists already carry. See resolveEntityInventory() in cyberia-server-defaults.js.
|
|
18
20
|
//
|
|
19
|
-
// Resolution
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
// skin map to different defaults by its full
|
|
21
|
+
// Resolution: a document matches an entity when every one of its `liveItemIds`
|
|
22
|
+
// is in the entity's active item ids. The most specific match wins, the document
|
|
23
|
+
// that requires the largest item set. Its remaining lists then drive the entity
|
|
24
|
+
// through its states. So one skin can map to different defaults by its full
|
|
25
|
+
// active set, e.g.
|
|
23
26
|
// { bot, liveItemIds:[purple, atlas_pistol_mk2], behavior:hostile }
|
|
24
27
|
// { bot, liveItemIds:[purple], behavior:passive }
|
|
25
28
|
// where a purple bot carrying the pistol is hostile and a bare purple bot is
|
|
26
|
-
// passive. The
|
|
29
|
+
// passive. The simulation applies the same rule.
|
|
27
30
|
//
|
|
28
31
|
// Invariants:
|
|
29
32
|
// - `entityType` is a label, not a unique key — a category appears in many
|
|
@@ -33,11 +36,18 @@ import { Schema, model } from 'mongoose';
|
|
|
33
36
|
// at different specificity levels). `liveItemIds` is indexed (non-unique
|
|
34
37
|
// multikey) only for fast membership lookups.
|
|
35
38
|
|
|
36
|
-
|
|
39
|
+
// Overrides what the lists derive for one carried id: `active` forces the spawn state (a skin the
|
|
40
|
+
// equipment rules would otherwise leave inactive), `quantity` sizes a stack (a drop bundle).
|
|
41
|
+
// Omitting `active` keeps the derived value; an id no list carries is ignored.
|
|
42
|
+
const OverrideItemStateSchema = new Schema(
|
|
37
43
|
{
|
|
38
|
-
itemId: { type: String,
|
|
39
|
-
active: { type: Boolean
|
|
40
|
-
quantity: { type: Number, default:
|
|
44
|
+
itemId: { type: String, trim: true },
|
|
45
|
+
active: { type: Boolean },
|
|
46
|
+
quantity: { type: Number, default: 1, min: 1 },
|
|
47
|
+
// Probability in [0,1] that this id actually scatters when the entity dies. Absent means 1 —
|
|
48
|
+
// the drop always happens, which is what every world did before this existed. Only meaningful
|
|
49
|
+
// for an id the build carries in `dropItemIds`; on any other row it is inert.
|
|
50
|
+
dropChance: { type: Number, min: 0, max: 1 },
|
|
41
51
|
},
|
|
42
52
|
{ _id: false },
|
|
43
53
|
);
|
|
@@ -48,11 +58,12 @@ const CyberiaEntityTypeDefaultSchema = new Schema(
|
|
|
48
58
|
liveItemIds: { type: [{ type: String, trim: true }], index: true },
|
|
49
59
|
deadItemIds: [{ type: String, trim: true }],
|
|
50
60
|
dropItemIds: [{ type: String, trim: true }],
|
|
51
|
-
|
|
61
|
+
inventoryItemsIds: [{ type: String, trim: true }],
|
|
62
|
+
overrideItemsIdsState: [OverrideItemStateSchema],
|
|
52
63
|
// Canonical entity behavior bound to entities matched by liveItemIds (see
|
|
53
64
|
// SharedDefaultsCyberia.ENTITY_BEHAVIORS). Empty = let the runtime derive it
|
|
54
|
-
// (armed → hostile, else passive). The
|
|
55
|
-
//
|
|
65
|
+
// (armed → hostile, else passive). The simulation resolves it with the same
|
|
66
|
+
// liveItemIds match it uses for the live/dead/drop sets.
|
|
56
67
|
behavior: { type: String, trim: true },
|
|
57
68
|
},
|
|
58
69
|
{
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
2
|
import { registerCrudRoutes } from '../../server/network/middlewares.js';
|
|
3
|
+
import { moderatorGuard } from '../../server/security/auth.js';
|
|
3
4
|
import { CyberiaEntityTypeDefaultController } from './cyberia-entity-type-default.controller.js';
|
|
4
5
|
|
|
5
6
|
class CyberiaEntityTypeDefaultRouter {
|
|
@@ -8,7 +9,21 @@ class CyberiaEntityTypeDefaultRouter {
|
|
|
8
9
|
* @returns {import('express').Router}
|
|
9
10
|
*/
|
|
10
11
|
static router(options) {
|
|
11
|
-
|
|
12
|
+
const router = express.Router();
|
|
13
|
+
// Custom actions first: the generic /:id routes below capture everything.
|
|
14
|
+
router.post(
|
|
15
|
+
`/sync-instance/:instanceCode`,
|
|
16
|
+
options.authMiddleware,
|
|
17
|
+
moderatorGuard,
|
|
18
|
+
async (req, res) => await CyberiaEntityTypeDefaultController.sync(req, res, options),
|
|
19
|
+
);
|
|
20
|
+
router.post(
|
|
21
|
+
`/:id/instances`,
|
|
22
|
+
options.authMiddleware,
|
|
23
|
+
moderatorGuard,
|
|
24
|
+
async (req, res) => await CyberiaEntityTypeDefaultController.setInstances(req, res, options),
|
|
25
|
+
);
|
|
26
|
+
return registerCrudRoutes(router, CyberiaEntityTypeDefaultController, options);
|
|
12
27
|
}
|
|
13
28
|
}
|
|
14
29
|
|