@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
|
@@ -29,7 +29,7 @@ class ObjectLayerEngineViewer {
|
|
|
29
29
|
currentMode: 'idle',
|
|
30
30
|
webp: null,
|
|
31
31
|
isGenerating: false,
|
|
32
|
-
|
|
32
|
+
currentItemId: undefined, // Item id in the URL, so an unchanged route skips a reload
|
|
33
33
|
atlasSpriteSheet: null,
|
|
34
34
|
isGeneratingAtlas: false,
|
|
35
35
|
webpMetadata: null,
|
|
@@ -52,8 +52,8 @@ class ObjectLayerEngineViewer {
|
|
|
52
52
|
}
|
|
53
53
|
static async instance({ appStore }) {
|
|
54
54
|
const id = 'object-layer-engine-viewer';
|
|
55
|
-
// Reset
|
|
56
|
-
ObjectLayerEngineViewer.Data.
|
|
55
|
+
// Reset so a modal render always triggers Reload.
|
|
56
|
+
ObjectLayerEngineViewer.Data.currentItemId = undefined;
|
|
57
57
|
Modal.Data[`modal-${id}`].onReloadModalListener[id] = async () => {
|
|
58
58
|
ObjectLayerEngineViewer.Reload({ appStore });
|
|
59
59
|
};
|
|
@@ -61,13 +61,12 @@ class ObjectLayerEngineViewer {
|
|
|
61
61
|
listenQueryParamsChange({
|
|
62
62
|
id: `${id}-query-listener`,
|
|
63
63
|
event: async (queryParams) => {
|
|
64
|
-
const
|
|
64
|
+
const itemId = queryParams.itemId || null;
|
|
65
65
|
if (!s(`.modal-${id}`) || !s(`#${id}`)) {
|
|
66
66
|
logger.warn('ObjectLayerEngineViewer DOM not ready for query param change');
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
69
|
-
|
|
70
|
-
if (objectId !== ObjectLayerEngineViewer.Data.currentObjectId) {
|
|
69
|
+
if (itemId !== ObjectLayerEngineViewer.Data.currentItemId) {
|
|
71
70
|
await ObjectLayerEngineViewer.Reload({ appStore });
|
|
72
71
|
}
|
|
73
72
|
},
|
|
@@ -90,8 +89,7 @@ class ObjectLayerEngineViewer {
|
|
|
90
89
|
logger.warn('ObjectLayerEngineViewer DOM not ready for renderEmpty');
|
|
91
90
|
return;
|
|
92
91
|
}
|
|
93
|
-
|
|
94
|
-
ObjectLayerEngineViewer.Data.currentObjectId = null;
|
|
92
|
+
ObjectLayerEngineViewer.Data.currentItemId = null;
|
|
95
93
|
// Check if the management table grid already exists AND its DOM is still present
|
|
96
94
|
// If it does, don't re-render (just let DefaultManagement's RouterEvents handle URL changes)
|
|
97
95
|
const gridId = `object-layer-engine-management-grid-${idModal}`;
|
|
@@ -116,7 +114,7 @@ class ObjectLayerEngineViewer {
|
|
|
116
114
|
}),
|
|
117
115
|
);
|
|
118
116
|
}
|
|
119
|
-
static async loadObjectLayer(
|
|
117
|
+
static async loadObjectLayer(itemId, appStore, options = {}) {
|
|
120
118
|
const { skipWebp = false } = options;
|
|
121
119
|
const id = 'object-layer-engine-viewer';
|
|
122
120
|
// Check if DOM element exists
|
|
@@ -126,7 +124,7 @@ class ObjectLayerEngineViewer {
|
|
|
126
124
|
}
|
|
127
125
|
try {
|
|
128
126
|
// Load metadata first
|
|
129
|
-
const { status: metaStatus, data: metadata } = await ObjectLayerService.getMetadata({ id:
|
|
127
|
+
const { status: metaStatus, data: metadata } = await ObjectLayerService.getMetadata({ id: itemId });
|
|
130
128
|
if (metaStatus !== 'success' || !metadata) {
|
|
131
129
|
throw new Error('Failed to load object layer metadata');
|
|
132
130
|
}
|
|
@@ -142,15 +140,11 @@ class ObjectLayerEngineViewer {
|
|
|
142
140
|
ObjectLayerEngineViewer.Data.atlasSpriteSheet = null;
|
|
143
141
|
}
|
|
144
142
|
// Load frame counts for all directions
|
|
145
|
-
const { status: frameStatus, data: frameData } = await ObjectLayerService.getFrameCounts({ id:
|
|
143
|
+
const { status: frameStatus, data: frameData } = await ObjectLayerService.getFrameCounts({ id: itemId });
|
|
146
144
|
if (frameStatus !== 'success' || !frameData) {
|
|
147
145
|
throw new Error('Failed to load frame counts');
|
|
148
146
|
}
|
|
149
147
|
ObjectLayerEngineViewer.Data.frameCounts = frameData.frameCounts;
|
|
150
|
-
// Priority order for directions
|
|
151
|
-
const directions = ['down', 'up', 'left', 'right'];
|
|
152
|
-
// Priority order for modes
|
|
153
|
-
const modes = ['idle', 'walking'];
|
|
154
148
|
ObjectLayerEngineViewer.Data.currentDirection = 'down';
|
|
155
149
|
ObjectLayerEngineViewer.Data.currentMode = 'idle';
|
|
156
150
|
// instance the viewer UI
|
|
@@ -162,7 +156,7 @@ class ObjectLayerEngineViewer {
|
|
|
162
156
|
} catch (error) {
|
|
163
157
|
logger.error('Error loading object layer:', error);
|
|
164
158
|
NotificationManager.Push({
|
|
165
|
-
html: `Failed to load object layer: ${error.message}`,
|
|
159
|
+
html: `Failed to load object layer "${itemId}": ${error.message}`,
|
|
166
160
|
status: 'error',
|
|
167
161
|
});
|
|
168
162
|
htmls(
|
|
@@ -170,12 +164,16 @@ class ObjectLayerEngineViewer {
|
|
|
170
164
|
html`
|
|
171
165
|
<div class="in section-mp">
|
|
172
166
|
<div class="in">
|
|
173
|
-
<h3>
|
|
174
|
-
<p>
|
|
167
|
+
<h3>Object layer not found</h3>
|
|
168
|
+
<p>No object layer answers to the item id <strong>${itemId}</strong>.</p>
|
|
169
|
+
<button class="default-viewer-btn" id="return-to-list-btn">
|
|
170
|
+
<i class="fa-solid fa-list"></i> Back to the list
|
|
171
|
+
</button>
|
|
175
172
|
</div>
|
|
176
173
|
</div>
|
|
177
174
|
`,
|
|
178
175
|
);
|
|
176
|
+
ObjectLayerEngineViewer.attachReturnToList({ appStore });
|
|
179
177
|
}
|
|
180
178
|
}
|
|
181
179
|
static async renderViewer({ appStore }) {
|
|
@@ -208,6 +206,29 @@ class ObjectLayerEngineViewer {
|
|
|
208
206
|
const numericCode = ObjectLayerEngineViewer.getDirectionCode(direction, mode);
|
|
209
207
|
return numericCode ? frameCounts[numericCode] || 0 : 0;
|
|
210
208
|
};
|
|
209
|
+
// One atlas render: which one it is, how large it is, and its PNG.
|
|
210
|
+
// `native` draws the blob at its own pixel size, with no fit to the panel.
|
|
211
|
+
const atlasRender = ({ label, fileId, pixelsPerCell, emptyText, native }) => {
|
|
212
|
+
const metadata = ObjectLayerEngineViewer.Data.atlasSpriteSheet.metadata;
|
|
213
|
+
const size =
|
|
214
|
+
pixelsPerCell > 0
|
|
215
|
+
? `${metadata.atlasWidth * pixelsPerCell}x${metadata.atlasHeight * pixelsPerCell}px · ${pixelsPerCell}px per cell`
|
|
216
|
+
: '';
|
|
217
|
+
return html`<div class="atlas-render">
|
|
218
|
+
<p class="atlas-render-label">
|
|
219
|
+
<strong class="item-data-key-label">${label}</strong>
|
|
220
|
+
<span class="atlas-render-size">${size}</span>
|
|
221
|
+
</p>
|
|
222
|
+
<div class="atlas-img-wrapper">
|
|
223
|
+
${fileId
|
|
224
|
+
? html`<img
|
|
225
|
+
src="${getProxyPath()}api/file/blob/${fileId._id || fileId}"
|
|
226
|
+
class="in atlas-img-preview ${native ? 'atlas-img-native' : ''}"
|
|
227
|
+
/>`
|
|
228
|
+
: html`<div class="atlas-img-placeholder">${emptyText}</div>`}
|
|
229
|
+
</div>
|
|
230
|
+
</div>`;
|
|
231
|
+
};
|
|
211
232
|
ThemeEvents[id] = () => {
|
|
212
233
|
if (!s(`.style-${id}`)) return;
|
|
213
234
|
htmls(
|
|
@@ -240,6 +261,32 @@ class ObjectLayerEngineViewer {
|
|
|
240
261
|
}
|
|
241
262
|
.atlas-img-preview {
|
|
242
263
|
width: 100%;
|
|
264
|
+
image-rendering: pixelated;
|
|
265
|
+
background: repeating-conic-gradient(#80808020 0% 25%, #fff0 0% 50%) 50% / 20px 20px;
|
|
266
|
+
}
|
|
267
|
+
/* Own pixel size, so one atlas pixel is one screen pixel. */
|
|
268
|
+
.atlas-img-native {
|
|
269
|
+
width: auto;
|
|
270
|
+
height: auto;
|
|
271
|
+
margin: auto;
|
|
272
|
+
}
|
|
273
|
+
.atlas-img-placeholder {
|
|
274
|
+
padding: 20px;
|
|
275
|
+
text-align: center;
|
|
276
|
+
font-size: 13px;
|
|
277
|
+
color: ${darkTheme ? '#aaa' : '#666'};
|
|
278
|
+
}
|
|
279
|
+
.atlas-render-label {
|
|
280
|
+
display: flex;
|
|
281
|
+
flex-wrap: wrap;
|
|
282
|
+
align-items: baseline;
|
|
283
|
+
gap: 8px;
|
|
284
|
+
padding: 2px;
|
|
285
|
+
font-size: 14px;
|
|
286
|
+
}
|
|
287
|
+
.atlas-render-size {
|
|
288
|
+
font-size: 12px;
|
|
289
|
+
opacity: 0.75;
|
|
243
290
|
}
|
|
244
291
|
.atlas-metadata-grid {
|
|
245
292
|
display: grid;
|
|
@@ -723,7 +770,7 @@ class ObjectLayerEngineViewer {
|
|
|
723
770
|
/>
|
|
724
771
|
<span class="item-data-key-label">${statInfo.title}</span>
|
|
725
772
|
</div>
|
|
726
|
-
<span class="item-data-value-label">${statValue}</span>
|
|
773
|
+
<span class="item-data-value-label" style="color: ${statValue < 0 ? '#ef7777' : statValue > 0 ? '#7bdd9a' : '#aaa'}">${statValue > 0 ? '+' : ''}${statValue}</span>
|
|
727
774
|
</div>
|
|
728
775
|
`;
|
|
729
776
|
})
|
|
@@ -871,24 +918,19 @@ class ObjectLayerEngineViewer {
|
|
|
871
918
|
${ObjectLayerEngineViewer.Data.atlasSpriteSheet
|
|
872
919
|
? html`
|
|
873
920
|
<div class="atlas-preview-container">
|
|
874
|
-
${
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
<div class="atlas-img-wrapper">
|
|
888
|
-
<div class="atlas-img-placeholder">Atlas image not available</div>
|
|
889
|
-
</div>
|
|
890
|
-
`
|
|
891
|
-
}
|
|
921
|
+
${atlasRender({
|
|
922
|
+
label: 'Upscaled',
|
|
923
|
+
fileId: ObjectLayerEngineViewer.Data.atlasSpriteSheet.fileId,
|
|
924
|
+
pixelsPerCell: ObjectLayerEngineViewer.Data.atlasSpriteSheet.metadata.upscaleFactor,
|
|
925
|
+
emptyText: 'Upscaled render not available',
|
|
926
|
+
})}
|
|
927
|
+
${atlasRender({
|
|
928
|
+
label: 'Minified',
|
|
929
|
+
fileId: ObjectLayerEngineViewer.Data.atlasSpriteSheet.minifyFileId,
|
|
930
|
+
pixelsPerCell: ObjectLayerEngineViewer.Data.atlasSpriteSheet.metadata.cellPixelDim,
|
|
931
|
+
emptyText: 'Minified render not available',
|
|
932
|
+
native: true,
|
|
933
|
+
})}
|
|
892
934
|
<div class="atlas-metadata-grid">
|
|
893
935
|
<div>
|
|
894
936
|
<p style="padding: 2px"><strong class="item-data-key-label">ID:</strong></p>
|
|
@@ -904,16 +946,6 @@ class ObjectLayerEngineViewer {
|
|
|
904
946
|
</div>`
|
|
905
947
|
: ''
|
|
906
948
|
}
|
|
907
|
-
<div>
|
|
908
|
-
<p style="padding: 2px"><strong class="item-data-key-label">Dimensions:</strong></p>
|
|
909
|
-
<p style="padding: 2px">
|
|
910
|
-
${ObjectLayerEngineViewer.Data.atlasSpriteSheet.metadata.atlasWidth}x${ObjectLayerEngineViewer.Data.atlasSpriteSheet.metadata.atlasHeight}
|
|
911
|
-
</p>
|
|
912
|
-
</div>
|
|
913
|
-
<div>
|
|
914
|
-
<p style="padding: 2px"><strong class="item-data-key-label">Cell Dim:</strong></p>
|
|
915
|
-
<p style="padding: 2px">${ObjectLayerEngineViewer.Data.atlasSpriteSheet.metadata.cellPixelDim}px</p>
|
|
916
|
-
</div>
|
|
917
949
|
<div>
|
|
918
950
|
<p style="padding: 2px"><strong class="item-data-key-label">Item Key:</strong></p>
|
|
919
951
|
<p style="padding: 2px">${ObjectLayerEngineViewer.Data.atlasSpriteSheet.metadata.itemKey}</p>
|
|
@@ -1125,12 +1157,12 @@ class ObjectLayerEngineViewer {
|
|
|
1125
1157
|
}
|
|
1126
1158
|
delete ThemeEvents['metadata-json-editor-theme'];
|
|
1127
1159
|
// Navigate back to list
|
|
1128
|
-
ObjectLayerEngineViewer.Data.
|
|
1160
|
+
ObjectLayerEngineViewer.Data.currentItemId = undefined;
|
|
1129
1161
|
ObjectLayerEngineViewer.Data.objectLayer = null;
|
|
1130
1162
|
ObjectLayerEngineViewer.Data.webp = null;
|
|
1131
1163
|
ObjectLayerEngineViewer.Data.webpMetadata = null;
|
|
1132
1164
|
ObjectLayerEngineViewer.Data.atlasSpriteSheet = null;
|
|
1133
|
-
setQueryParams({
|
|
1165
|
+
setQueryParams({ itemId: null }, { replace: false });
|
|
1134
1166
|
} else {
|
|
1135
1167
|
throw new Error(result.message || 'Failed to delete object layer');
|
|
1136
1168
|
}
|
|
@@ -1142,6 +1174,21 @@ class ObjectLayerEngineViewer {
|
|
|
1142
1174
|
});
|
|
1143
1175
|
}
|
|
1144
1176
|
}
|
|
1177
|
+
static attachReturnToList({ appStore }) {
|
|
1178
|
+
const listBtn = s('#return-to-list-btn');
|
|
1179
|
+
if (!listBtn) return;
|
|
1180
|
+
listBtn.addEventListener('click', async () => {
|
|
1181
|
+
ObjectLayerEngineViewer.Data.webp = null;
|
|
1182
|
+
ObjectLayerEngineViewer.Data.webpMetadata = null;
|
|
1183
|
+
ObjectLayerEngineViewer.Data.objectLayer = null;
|
|
1184
|
+
ObjectLayerEngineViewer.Data.frameCounts = null;
|
|
1185
|
+
// Cleared before the URL changes, so the query listener sees a match and
|
|
1186
|
+
// skips the Reload that would render the list twice.
|
|
1187
|
+
ObjectLayerEngineViewer.Data.currentItemId = null;
|
|
1188
|
+
setQueryParams({ itemId: null }, { replace: false });
|
|
1189
|
+
await ObjectLayerEngineViewer.renderEmpty({ appStore });
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1145
1192
|
static attachEventListeners({ appStore }) {
|
|
1146
1193
|
// Direction buttons
|
|
1147
1194
|
const directionButtons = document.querySelectorAll('[data-direction]');
|
|
@@ -1178,28 +1225,7 @@ class ObjectLayerEngineViewer {
|
|
|
1178
1225
|
ObjectLayerEngineViewer.downloadWebp();
|
|
1179
1226
|
});
|
|
1180
1227
|
}
|
|
1181
|
-
|
|
1182
|
-
const listBtn = s('#return-to-list-btn');
|
|
1183
|
-
if (listBtn) {
|
|
1184
|
-
listBtn.addEventListener('click', async () => {
|
|
1185
|
-
// Clear object data and reset state
|
|
1186
|
-
ObjectLayerEngineViewer.Data.webp = null;
|
|
1187
|
-
ObjectLayerEngineViewer.Data.webpMetadata = null;
|
|
1188
|
-
ObjectLayerEngineViewer.Data.objectLayer = null;
|
|
1189
|
-
ObjectLayerEngineViewer.Data.frameCounts = null;
|
|
1190
|
-
// Set currentObjectId to null BEFORE setQueryParams so the
|
|
1191
|
-
// listenQueryParamsChange listener sees the id already matches
|
|
1192
|
-
// and skips calling Reload (avoids double-render race condition)
|
|
1193
|
-
ObjectLayerEngineViewer.Data.currentObjectId = null;
|
|
1194
|
-
// Update the URL to remove the id parameter
|
|
1195
|
-
setQueryParams({ id: null }, { replace: false });
|
|
1196
|
-
// Directly render the list view instead of relying on the
|
|
1197
|
-
// listener → Reload → renderEmpty chain which can silently
|
|
1198
|
-
// fail when the URL was already clean or currentObjectId
|
|
1199
|
-
// was already null
|
|
1200
|
-
await ObjectLayerEngineViewer.renderEmpty({ appStore });
|
|
1201
|
-
});
|
|
1202
|
-
}
|
|
1228
|
+
ObjectLayerEngineViewer.attachReturnToList({ appStore });
|
|
1203
1229
|
// Edit button
|
|
1204
1230
|
const editBtn = s('#edit-object-layer-btn');
|
|
1205
1231
|
if (editBtn) {
|
|
@@ -1477,12 +1503,10 @@ class ObjectLayerEngineViewer {
|
|
|
1477
1503
|
});
|
|
1478
1504
|
}
|
|
1479
1505
|
static toEngine() {
|
|
1480
|
-
const
|
|
1481
|
-
if (!
|
|
1482
|
-
// Navigate to editor route first
|
|
1506
|
+
const itemId = ObjectLayerEngineViewer.Data.objectLayer?.data?.item?.id;
|
|
1507
|
+
if (!itemId) return;
|
|
1483
1508
|
setPath(`${getProxyPath()}object-layer-engine`);
|
|
1484
|
-
|
|
1485
|
-
setQueryParams({ id: objectLayer._id }, { replace: true });
|
|
1509
|
+
setQueryParams({ itemId }, { replace: true });
|
|
1486
1510
|
if (s(`.modal-object-layer-engine`)) {
|
|
1487
1511
|
ObjectLayerEngineModal.Reload();
|
|
1488
1512
|
} else {
|
|
@@ -1491,31 +1515,22 @@ class ObjectLayerEngineViewer {
|
|
|
1491
1515
|
}
|
|
1492
1516
|
static async Reload(options = {}) {
|
|
1493
1517
|
const { appStore, force = false, skipWebp = false } = options;
|
|
1494
|
-
const
|
|
1495
|
-
const
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
if (objectId !== ObjectLayerEngineViewer.Data.currentObjectId && !skipWebp) {
|
|
1518
|
+
const itemId = getQueryParams().itemId || null;
|
|
1519
|
+
const changed = itemId !== ObjectLayerEngineViewer.Data.currentItemId;
|
|
1520
|
+
if (changed || force) {
|
|
1521
|
+
if (changed && !skipWebp) {
|
|
1499
1522
|
ObjectLayerEngineViewer.Data.webp = null;
|
|
1500
1523
|
ObjectLayerEngineViewer.Data.webpMetadata = null;
|
|
1501
1524
|
}
|
|
1502
|
-
ObjectLayerEngineViewer.Data.
|
|
1503
|
-
if (
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
const id = 'object-layer-engine-viewer';
|
|
1512
|
-
const idModal = 'modal-object-layer-engine-viewer';
|
|
1513
|
-
const gridId = `object-layer-engine-management-grid-${idModal}`;
|
|
1514
|
-
const gridDomExists = s(`.${gridId}`);
|
|
1515
|
-
if (!gridDomExists) {
|
|
1516
|
-
// DOM was reset (e.g., modal HTML reloaded), re-render the table
|
|
1517
|
-
await ObjectLayerEngineViewer.renderEmpty({ appStore });
|
|
1518
|
-
}
|
|
1525
|
+
ObjectLayerEngineViewer.Data.currentItemId = itemId;
|
|
1526
|
+
if (itemId) await ObjectLayerEngineViewer.loadObjectLayer(itemId, appStore, { skipWebp });
|
|
1527
|
+
else await ObjectLayerEngineViewer.renderEmpty({ appStore });
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
// Already on the list, but a modal reopen can drop its DOM.
|
|
1531
|
+
if (!itemId && ObjectLayerEngineViewer.Data.currentItemId === null) {
|
|
1532
|
+
const gridId = `object-layer-engine-management-grid-modal-object-layer-engine-viewer`;
|
|
1533
|
+
if (!s(`.${gridId}`)) await ObjectLayerEngineViewer.renderEmpty({ appStore });
|
|
1519
1534
|
}
|
|
1520
1535
|
}
|
|
1521
1536
|
}
|
|
@@ -1,51 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Shared Cyberia defaults — isomorphic, dependency-free, importable from
|
|
3
|
-
* **both** Node and the browser.
|
|
4
|
-
*
|
|
5
|
-
* Pure plain-data ESM module: no Node built-ins, no DOM APIs, no
|
|
6
|
-
* environment guards. Every export is a frozen literal, a constant
|
|
7
|
-
* lookup table, or a pure function over those tables. Side-effect free
|
|
8
|
-
* on import.
|
|
9
|
-
*
|
|
10
|
-
* Two responsibilities, no others:
|
|
11
|
-
*
|
|
12
|
-
* 1. **Presentation values** — palette, entity colour keys, status-icon
|
|
13
|
-
* visuals (icon stem + border colour + bounce), camera tunings,
|
|
14
|
-
* render flags. Mirrored 1-to-1 by the C/WASM client's compile-time
|
|
15
|
-
* defaults in `cyberia-client/src/domain/presentation_defaults.h`.
|
|
16
|
-
* Optionally overridable per deployment through the REST endpoint
|
|
17
|
-
* `/api/cyberia-client-hints/:code`.
|
|
18
|
-
*
|
|
19
|
-
* 2. **Shared content vocabulary** — `ITEM_TYPES`, `ENTITY_TYPES`,
|
|
20
|
-
* `DefaultCyberiaItems` registry + lookups, type-to-item mapping,
|
|
21
|
-
* quest step objective enum. The data shape both the
|
|
22
|
-
* browser-side editor UI and the engine REST controllers need to
|
|
23
|
-
* understand; it is **not** simulation state.
|
|
24
|
-
*
|
|
25
|
-
* STRICT BOUNDARIES
|
|
26
|
-
* -----------------
|
|
27
|
-
* - The cyberia-server (Go) MUST NOT load this file. None of these
|
|
28
|
-
* values influence the authoritative simulation. The server owns the
|
|
29
|
-
* numeric Entity Status Indicator IDs only (see `cyberia-server-defaults`).
|
|
30
|
-
*
|
|
31
|
-
* - The C/WASM cyberia-client embeds *presentation* defaults at compile
|
|
32
|
-
* time and fetches optional overrides through REST. It does NOT carry
|
|
33
|
-
* the JS vocabulary constants — entity-type and item-id strings
|
|
34
|
-
* arrive on the wire and are matched directly.
|
|
35
|
-
*
|
|
36
|
-
* - The browser editor bundles this file via esbuild. It MUST NOT
|
|
37
|
-
* import `cyberia-server-defaults.js` — that would pull simulation
|
|
38
|
-
* rules, economy, and seed content into the browser bundle. All
|
|
39
|
-
* shared vocabulary the editor needs lives here.
|
|
40
|
-
*
|
|
41
|
-
* Naming: this file is intentionally placed under `src/client/` so its
|
|
42
|
-
* URL is `/components/cyberia/SharedDefaultsCyberia.js` when the engine
|
|
43
|
-
* static server resolves it. Node-side importers reach in with a
|
|
44
|
-
* relative path; both halves see the same module instance.
|
|
45
|
-
*
|
|
46
|
-
* @module src/client/components/cyberia/SharedDefaultsCyberia.js
|
|
47
|
-
*/
|
|
48
|
-
|
|
1
|
+
// Shared content, stat contract, and presentation defaults.
|
|
49
2
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
50
3
|
// Shared content vocabulary
|
|
51
4
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -150,6 +103,126 @@ export const SKILL_LOGIC_ID_VALUES = Object.freeze(SKILL_LOGIC_IDS.map((l) => l.
|
|
|
150
103
|
/** True when `logicEventId` is a known canonical skill LogicId. */
|
|
151
104
|
export const isCanonicalSkillLogicId = (logicEventId) => SKILL_LOGIC_ID_VALUES.includes(logicEventId);
|
|
152
105
|
|
|
106
|
+
/**
|
|
107
|
+
* The two audio routes: a held music bed, and one-shot effects over it.
|
|
108
|
+
*
|
|
109
|
+
* One bus vocabulary, spelled once: the same ids name the `src/audio-module/<bus-id>/` directory
|
|
110
|
+
* an asset is authored in, the argument `cyberia-audio <bus-id> <id>` dispatches on, the `bus` a
|
|
111
|
+
* recorded manifest carries, and the route a binding's `settings.bus` selects.
|
|
112
|
+
*/
|
|
113
|
+
export const AUDIO_BUS_MUSIC = 'music';
|
|
114
|
+
export const AUDIO_BUS_SFX = 'sfx';
|
|
115
|
+
export const AUDIO_BUSES = Object.freeze([AUDIO_BUS_MUSIC, AUDIO_BUS_SFX]);
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Canonical audio LogicId registry — the runtime events an audio binding may answer.
|
|
119
|
+
*
|
|
120
|
+
* Audio is a consumer of the game's semantic event vocabulary, never a second one: a
|
|
121
|
+
* `cyberia-map-audio-conf` binding resolves one of these ids (or a canonical skill LogicId, which
|
|
122
|
+
* the client emits by the same name) to an `audioCode`. The ids live here, beside the other shared
|
|
123
|
+
* vocabularies, because three parties must agree on them — the C client that emits them, the seed
|
|
124
|
+
* that binds them, and the editor that offers them.
|
|
125
|
+
*
|
|
126
|
+
* `bus` is the binding's natural routing: music events take over the map's bed until `idle`
|
|
127
|
+
* restores it; sfx events are one shots. A binding may override it in its own settings.
|
|
128
|
+
*
|
|
129
|
+
* MUST stay aligned with the events emitted by cyberia-client (`src/audio/audio_events.h`).
|
|
130
|
+
*
|
|
131
|
+
* @type {ReadonlyArray<{id:string,name:string,description:string,bus:'music'|'sfx'}>}
|
|
132
|
+
*/
|
|
133
|
+
export const AUDIO_LOGIC_IDS = Object.freeze([
|
|
134
|
+
Object.freeze({
|
|
135
|
+
id: 'idle',
|
|
136
|
+
name: 'Idle',
|
|
137
|
+
description: 'No event is holding the bed: the map returns to its default music.',
|
|
138
|
+
bus: AUDIO_BUS_MUSIC,
|
|
139
|
+
}),
|
|
140
|
+
Object.freeze({
|
|
141
|
+
id: 'combat',
|
|
142
|
+
name: 'Combat',
|
|
143
|
+
description: 'The player is taking damage: combat music takes over until the exchange ends.',
|
|
144
|
+
bus: AUDIO_BUS_MUSIC,
|
|
145
|
+
}),
|
|
146
|
+
Object.freeze({ id: 'boss', name: 'Boss', description: 'A boss encounter is under way.', bus: AUDIO_BUS_MUSIC }),
|
|
147
|
+
Object.freeze({
|
|
148
|
+
id: 'portal-cooldown',
|
|
149
|
+
name: 'Portal Cooldown',
|
|
150
|
+
description: 'The player is standing on a portal while its teleport charge runs.',
|
|
151
|
+
bus: AUDIO_BUS_MUSIC,
|
|
152
|
+
}),
|
|
153
|
+
Object.freeze({
|
|
154
|
+
id: 'portal',
|
|
155
|
+
name: 'Portal',
|
|
156
|
+
description: 'The teleport charge completed and the player jumps — within a map or across one.',
|
|
157
|
+
bus: AUDIO_BUS_SFX,
|
|
158
|
+
}),
|
|
159
|
+
Object.freeze({
|
|
160
|
+
id: 'hit',
|
|
161
|
+
name: 'Hit',
|
|
162
|
+
description: 'Damage landed on any entity in view, not only on the player.',
|
|
163
|
+
bus: AUDIO_BUS_SFX,
|
|
164
|
+
}),
|
|
165
|
+
Object.freeze({ id: 'heal', name: 'Heal', description: 'Life was restored to an entity in view.', bus: AUDIO_BUS_SFX }),
|
|
166
|
+
Object.freeze({ id: 'drop', name: 'Drop', description: 'An item drop spawned in the world.', bus: AUDIO_BUS_SFX }),
|
|
167
|
+
Object.freeze({
|
|
168
|
+
id: 'victory',
|
|
169
|
+
name: 'Victory',
|
|
170
|
+
description: 'A quest or encounter was completed.',
|
|
171
|
+
bus: AUDIO_BUS_SFX,
|
|
172
|
+
}),
|
|
173
|
+
Object.freeze({
|
|
174
|
+
id: 'level-up',
|
|
175
|
+
name: 'Level Up',
|
|
176
|
+
description: 'An entity in view reached a new level.',
|
|
177
|
+
bus: AUDIO_BUS_SFX,
|
|
178
|
+
}),
|
|
179
|
+
Object.freeze({
|
|
180
|
+
id: 'death',
|
|
181
|
+
name: 'Death',
|
|
182
|
+
description: 'An entity in view was defeated.',
|
|
183
|
+
bus: AUDIO_BUS_SFX,
|
|
184
|
+
}),
|
|
185
|
+
Object.freeze({
|
|
186
|
+
id: 'item-pickup',
|
|
187
|
+
name: 'Item Pickup',
|
|
188
|
+
description: 'An item landed in the player inventory — loot, a reward, a purchase, an assembly output.',
|
|
189
|
+
bus: AUDIO_BUS_SFX,
|
|
190
|
+
}),
|
|
191
|
+
Object.freeze({
|
|
192
|
+
id: 'craft',
|
|
193
|
+
name: 'Craft',
|
|
194
|
+
description: 'An assembly is charging: the bed is held until the recipe progress bar finishes.',
|
|
195
|
+
bus: AUDIO_BUS_MUSIC,
|
|
196
|
+
}),
|
|
197
|
+
Object.freeze({
|
|
198
|
+
id: 'ui-click',
|
|
199
|
+
name: 'UI Click',
|
|
200
|
+
description: 'The interface accepted a tap — a button, a modal control, a bubble.',
|
|
201
|
+
bus: AUDIO_BUS_SFX,
|
|
202
|
+
}),
|
|
203
|
+
Object.freeze({
|
|
204
|
+
id: 'footsteps',
|
|
205
|
+
name: 'Footsteps',
|
|
206
|
+
description: 'Something with feet is walking in view; repeats on a gait cadence while it lasts.',
|
|
207
|
+
bus: AUDIO_BUS_SFX,
|
|
208
|
+
}),
|
|
209
|
+
]);
|
|
210
|
+
|
|
211
|
+
/** Ordered list of canonical audio LogicId values. */
|
|
212
|
+
export const AUDIO_LOGIC_ID_VALUES = Object.freeze(AUDIO_LOGIC_IDS.map((l) => l.id));
|
|
213
|
+
|
|
214
|
+
/** Map: audio LogicId → its natural bus. */
|
|
215
|
+
export const AUDIO_LOGIC_ID_BUSES = Object.freeze(
|
|
216
|
+
Object.fromEntries(AUDIO_LOGIC_IDS.map((l) => [l.id, l.bus])),
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* True when `logicEventId` is a runtime event an audio binding may answer: an audio event, or a
|
|
221
|
+
* skill event the dispatcher fires (a skill's own id is what the client emits when it runs).
|
|
222
|
+
*/
|
|
223
|
+
export const isCanonicalAudioLogicId = (logicEventId) =>
|
|
224
|
+
AUDIO_LOGIC_ID_VALUES.includes(logicEventId) || isCanonicalSkillLogicId(logicEventId);
|
|
225
|
+
|
|
153
226
|
/**
|
|
154
227
|
* Canonical entity-behavior registry — the authoritative vocabulary for the
|
|
155
228
|
* `behavior` an entity-type default may bind to its matched entities. The Go
|
|
@@ -277,19 +350,105 @@ export const OBJECT_LAYER_DIRECTION_NAME_TO_CODE = Object.freeze(
|
|
|
277
350
|
}, {}),
|
|
278
351
|
);
|
|
279
352
|
|
|
353
|
+
// Stat order is shared by authoring, simulation, and snapshots. `scale` is what one point is
|
|
354
|
+
// worth in the simulation; the server reads it from the generated contract.
|
|
355
|
+
export const STAT_DEFINITIONS = Object.freeze([
|
|
356
|
+
{ key: 'effect', title: 'Effect', description: 'Removes life on impact.', detail: 'Each point removes one life point per hit.', floor: 1, scale: 1 },
|
|
357
|
+
{ key: 'resistance', title: 'Resistance', description: 'Adds maximum life and regeneration amount.', detail: 'Each point adds one point of maximum life and a tenth of a point to each regeneration.', floor: 0, scale: 1 },
|
|
358
|
+
{ key: 'agility', title: 'Agility', description: 'Changes movement speed.', detail: 'Each point adds one percent of base speed.', floor: -90, scale: 0.01 },
|
|
359
|
+
{ key: 'range', title: 'Range', description: 'Adds lifetime to summoned entities.', detail: 'Each point adds fifty milliseconds of summon lifetime.', floor: 0, scale: 50 },
|
|
360
|
+
{ key: 'intelligence', title: 'Intelligence', description: 'Adds summon success chance.', detail: 'Each point adds five percentage points of summon chance.', floor: 0, scale: 0.05 },
|
|
361
|
+
{ key: 'utility', title: 'Utility', description: 'Reduces action cooldown and increases regeneration chance.', detail: 'Each point removes one percent of base cooldown and adds one percentage point of regeneration chance.', floor: 0, scale: 0.01 },
|
|
362
|
+
].map((stat) => Object.freeze({ ...stat, icon: `stat-${stat.key}.png` })));
|
|
363
|
+
export const STAT_TYPES = Object.freeze(STAT_DEFINITIONS.map(({ key }) => key));
|
|
364
|
+
/** What one point of each stat is worth in the simulation. */
|
|
365
|
+
export const STAT_SCALES = Object.freeze(Object.fromEntries(STAT_DEFINITIONS.map(({ key, scale }) => [key, scale])));
|
|
366
|
+
/** Life regenerated per trigger for each point of resistance, on top of the entity's base regeneration. */
|
|
367
|
+
export const STAT_REGEN_PER_RESISTANCE = 0.1;
|
|
368
|
+
export const STAT_MODIFIER_MIN = -100;
|
|
369
|
+
export const STAT_MODIFIER_MAX = 100;
|
|
370
|
+
export const STAT_DEFAULT = 0;
|
|
371
|
+
export const ENTITY_LEVEL_MIN = 1;
|
|
372
|
+
export const ENTITY_LEVEL_MAX = 65535;
|
|
373
|
+
export function validateEntityLevel(level) {
|
|
374
|
+
if (!Number.isInteger(level) || level < ENTITY_LEVEL_MIN || level > ENTITY_LEVEL_MAX) {
|
|
375
|
+
throw new RangeError('Entity level must be an integer from 1 to 65535.');
|
|
376
|
+
}
|
|
377
|
+
return level;
|
|
378
|
+
}
|
|
379
|
+
export const STAT_CONTRACT_VERSION = 2;
|
|
380
|
+
export const STAT_DEFAULTS = Object.freeze(Object.fromEntries(STAT_TYPES.map((key) => [key, STAT_DEFAULT])));
|
|
381
|
+
export const STAT_EFFECTIVE_FLOORS = Object.freeze(Object.fromEntries(STAT_DEFINITIONS.map(({ key, floor }) => [key, floor])));
|
|
382
|
+
export const STAT_DESCRIPTIONS = Object.freeze(Object.fromEntries(STAT_DEFINITIONS.map(({ key, ...info }) => [key, Object.freeze(info)])));
|
|
383
|
+
|
|
384
|
+
export function validateStatModifier(value) {
|
|
385
|
+
if (!Number.isInteger(value) || value < STAT_MODIFIER_MIN || value > STAT_MODIFIER_MAX) {
|
|
386
|
+
throw new RangeError(`Stat modifiers must be integers from ${STAT_MODIFIER_MIN} to +${STAT_MODIFIER_MAX}.`);
|
|
387
|
+
}
|
|
388
|
+
return value;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export function validateStats(stats = {}) {
|
|
392
|
+
if (!stats || typeof stats !== 'object' || Array.isArray(stats)) throw new TypeError('Stats must be an object.');
|
|
393
|
+
for (const key of Object.keys(stats)) {
|
|
394
|
+
if (!STAT_TYPES.includes(key)) throw new TypeError(`Unknown stat: ${key}`);
|
|
395
|
+
}
|
|
396
|
+
return Object.fromEntries(STAT_TYPES.map((key) => [key, validateStatModifier(stats[key] === undefined ? STAT_DEFAULT : stats[key])]));
|
|
397
|
+
}
|
|
398
|
+
|
|
280
399
|
/**
|
|
281
|
-
*
|
|
282
|
-
*
|
|
400
|
+
* Semantic stat bounds per item type, inclusive, as `[min, max]` per stat.
|
|
401
|
+
*
|
|
402
|
+
* What a type may carry: a weapon deals its effect and can cost resistance, a skin and a
|
|
403
|
+
* breastplate defend, a skill reaches, and world content carries nothing. A type absent here,
|
|
404
|
+
* or a stat a type leaves out, keeps the contract bounds.
|
|
283
405
|
*/
|
|
284
|
-
export const
|
|
406
|
+
export const STAT_TYPE_BOUNDS = Object.freeze({
|
|
407
|
+
weapon: { effect: [5, 20], resistance: [-10, 2], agility: [-5, 5], range: [0, 10], intelligence: [0, 5], utility: [0, 10] },
|
|
408
|
+
skin: { effect: [1, 1], resistance: [0, 20], agility: [-5, 10], range: [0, 0], intelligence: [0, 2], utility: [0, 5] },
|
|
409
|
+
breastplate: { effect: [0, 0], resistance: [5, 30], agility: [-15, 0], range: [0, 0], intelligence: [0, 3], utility: [0, 5] },
|
|
410
|
+
skill: { effect: [0, 15], resistance: [0, 5], agility: [0, 5], range: [5, 30], intelligence: [0, 10], utility: [0, 10] },
|
|
411
|
+
resource: { effect: [0, 0], resistance: [0, 10], agility: [0, 0], range: [0, 0], intelligence: [0, 0], utility: [0, 0] },
|
|
412
|
+
coin: { effect: [0, 0], resistance: [0, 0], agility: [0, 0], range: [0, 0], intelligence: [0, 0], utility: [0, 0] },
|
|
413
|
+
floor: { effect: [0, 0], resistance: [0, 0], agility: [0, 0], range: [0, 0], intelligence: [0, 0], utility: [0, 0] },
|
|
414
|
+
obstacle: { effect: [0, 0], resistance: [0, 0], agility: [0, 0], range: [0, 0], intelligence: [0, 0], utility: [0, 0] },
|
|
415
|
+
portal: { effect: [0, 0], resistance: [0, 0], agility: [0, 0], range: [0, 0], intelligence: [0, 0], utility: [0, 0] },
|
|
416
|
+
foreground: { effect: [0, 0], resistance: [0, 0], agility: [0, 0], range: [0, 0], intelligence: [0, 0], utility: [0, 0] },
|
|
417
|
+
static: { effect: [0, 0], resistance: [0, 0], agility: [0, 0], range: [0, 0], intelligence: [0, 0], utility: [0, 0] },
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
for (const [type, bounds] of Object.entries(STAT_TYPE_BOUNDS)) {
|
|
421
|
+
if (!Object.values(ITEM_TYPES).includes(type)) throw new Error(`STAT_TYPE_BOUNDS: unknown item type "${type}"`);
|
|
422
|
+
for (const [key, [min, max]] of Object.entries(bounds)) {
|
|
423
|
+
if (!STAT_TYPES.includes(key)) throw new Error(`STAT_TYPE_BOUNDS: ${type} names unknown stat "${key}"`);
|
|
424
|
+
validateStatModifier(min);
|
|
425
|
+
validateStatModifier(max);
|
|
426
|
+
if (min > max) throw new Error(`STAT_TYPE_BOUNDS: ${type}.${key} minimum exceeds its maximum`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
285
429
|
|
|
286
430
|
/**
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
431
|
+
* The inclusive `[min, max]` every stat may take for an item type: the semantic bound where the
|
|
432
|
+
* type declares one, the contract bound where it does not.
|
|
433
|
+
*
|
|
434
|
+
* @param {string} itemType
|
|
435
|
+
* @returns {Readonly<Record<string,[number,number]>>}
|
|
290
436
|
*/
|
|
291
|
-
export
|
|
292
|
-
|
|
437
|
+
export function statBoundsForType(itemType) {
|
|
438
|
+
const typed = STAT_TYPE_BOUNDS[itemType] ?? {};
|
|
439
|
+
return Object.freeze(Object.fromEntries(STAT_TYPES.map((key) => [key, typed[key] ?? [STAT_MODIFIER_MIN, STAT_MODIFIER_MAX]])));
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
export function generateRandomStats(min = STAT_MODIFIER_MIN, max = STAT_MODIFIER_MAX, random = Math.random) {
|
|
443
|
+
validateStatModifier(min);
|
|
444
|
+
validateStatModifier(max);
|
|
445
|
+
if (min > max) throw new RangeError('Random minimum must not exceed maximum.');
|
|
446
|
+
return Object.fromEntries(STAT_TYPES.map((key) => {
|
|
447
|
+
const value = random();
|
|
448
|
+
if (!Number.isFinite(value) || value < 0 || value >= 1) throw new RangeError('Random source must return [0, 1).');
|
|
449
|
+
return [key, Math.floor(value * (max - min + 1)) + min];
|
|
450
|
+
}));
|
|
451
|
+
}
|
|
293
452
|
|
|
294
453
|
/**
|
|
295
454
|
* Canonical (itemId → itemType) registry shipped with the engine. Used
|