@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
|
@@ -1,35 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared Cyberia instance world-load / boot data assembly.
|
|
3
3
|
*
|
|
4
|
-
* Single source of truth for the payloads
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Single source of truth for the payloads the simulation server needs to boot
|
|
5
|
+
* and hot-reload an instance: the full world (instance + maps + object layers +
|
|
6
|
+
* simulation config + actions/quests), single map / object-layer lookups, and
|
|
7
|
+
* the object-layer manifest.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* - gRPC `CyberiaDataService` (src/grpc/cyberia/grpc-server.js)
|
|
11
|
-
* - REST `/api/cyberia-instance/boot
|
|
12
|
-
* server is not enabled for the deploy.
|
|
9
|
+
* Both boot transports adapt this module, so they stay equivalent:
|
|
10
|
+
* - gRPC `CyberiaDataService` (src/grpc/cyberia/grpc-server.js), primary.
|
|
11
|
+
* - REST `/api/cyberia-instance/boot/*`, fallback when gRPC is off.
|
|
13
12
|
*
|
|
14
13
|
* @module src/projects/cyberia/instance-data.js
|
|
15
14
|
*/
|
|
16
15
|
|
|
17
16
|
import crypto from 'crypto';
|
|
18
17
|
import { DataBaseProviderService } from '../../db/DataBaseProvider.js';
|
|
18
|
+
import {
|
|
19
|
+
collectInstanceItemIds,
|
|
20
|
+
collectSummonedItemIds,
|
|
21
|
+
selectInstanceSkills,
|
|
22
|
+
} from '../../api/cyberia-instance/cyberia-instance-items.js';
|
|
19
23
|
import { loggerFactory } from '../../server/ops/logger.js';
|
|
20
24
|
import {
|
|
21
25
|
CYBERIA_INSTANCE_CONF_DEFAULTS as FALLBACK_CONFIG_DEFAULTS,
|
|
22
26
|
DEFAULT_DEAD_ITEM_ID,
|
|
27
|
+
resolveProgressionRules,
|
|
23
28
|
ENTITY_TYPE_DEFAULTS,
|
|
29
|
+
resolveEntityInventory,
|
|
24
30
|
DefaultCyberiaActions,
|
|
25
31
|
DefaultCyberiaQuests,
|
|
32
|
+
DefaultSkillConfig,
|
|
26
33
|
} from '../../api/cyberia-server-defaults/cyberia-server-defaults.js';
|
|
27
34
|
import {
|
|
28
35
|
DEFAULT_INSTANCE_CODE,
|
|
36
|
+
validateStats,
|
|
37
|
+
validateEntityLevel,
|
|
29
38
|
DefaultCyberiaItems,
|
|
30
39
|
} from '../../client/components/cyberia/SharedDefaultsCyberia.js';
|
|
31
40
|
import { generateFallbackWorld } from '../../api/cyberia-instance/cyberia-fallback-world.js';
|
|
32
|
-
import { getFallbackDefaultItems } from '../../api/cyberia-instance/cyberia-fallback-default-items.js';
|
|
33
41
|
|
|
34
42
|
const logger = loggerFactory(import.meta);
|
|
35
43
|
|
|
@@ -69,68 +77,26 @@ function getInstanceModels(context) {
|
|
|
69
77
|
return bucket.models;
|
|
70
78
|
}
|
|
71
79
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
80
|
+
// The wire still carries inventory rows; the model no longer stores them. `defaultObjectLayers`
|
|
81
|
+
// is derived here, once, from the discriminator lists — see resolveEntityInventory().
|
|
82
|
+
function normalizeEntityDefault(entityDefault = {}, canonical = {}, itemTypes) {
|
|
83
|
+
const normalized = {
|
|
75
84
|
entityType: entityDefault.entityType ?? canonical.entityType ?? '',
|
|
76
85
|
liveItemIds: [...(entityDefault.liveItemIds ?? canonical.liveItemIds ?? [])],
|
|
77
86
|
deadItemIds: [...(entityDefault.deadItemIds ?? canonical.deadItemIds ?? [])],
|
|
78
87
|
dropItemIds: [...(entityDefault.dropItemIds ?? canonical.dropItemIds ?? [])],
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
})),
|
|
88
|
+
inventoryItemsIds: [...(entityDefault.inventoryItemsIds ?? canonical.inventoryItemsIds ?? [])],
|
|
89
|
+
overrideItemsIdsState: [
|
|
90
|
+
...(entityDefault.overrideItemsIdsState ?? canonical.overrideItemsIdsState ?? []),
|
|
91
|
+
],
|
|
84
92
|
behavior: entityDefault.behavior ?? canonical.behavior ?? '',
|
|
85
93
|
};
|
|
94
|
+
return { ...normalized, defaultObjectLayers: resolveEntityInventory(normalized, { itemTypes }) };
|
|
86
95
|
}
|
|
87
96
|
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
// present in the inventory, not auto-equipped — so they never violate the
|
|
92
|
-
// one-active-per-type equipment rule.
|
|
93
|
-
//
|
|
94
|
-
// Also **removes** items that are flagged `defaultPlayerInventory: false` from
|
|
95
|
-
// the player's defaultObjectLayers. This ensures the instance's explicit intent
|
|
96
|
-
// is honoured even when the conf's entityDefaults (e.g. from a backup/import)
|
|
97
|
-
// baked those items into the player layer list.
|
|
98
|
-
function applyInstanceDefaultPlayerInventory(config, instanceItemIds = []) {
|
|
99
|
-
const entries = instanceItemIds || [];
|
|
100
|
-
|
|
101
|
-
// ── 1. Remove items explicitly flagged as NOT default player inventory ─────
|
|
102
|
-
const removeIds = new Set(
|
|
103
|
-
entries.filter((entry) => entry && entry.defaultPlayerInventory === false && entry.id).map((entry) => entry.id),
|
|
104
|
-
);
|
|
105
|
-
if (removeIds.size > 0) {
|
|
106
|
-
for (const ed of config.entityDefaults || []) {
|
|
107
|
-
if (ed.defaultObjectLayers) {
|
|
108
|
-
ed.defaultObjectLayers = ed.defaultObjectLayers.filter((ol) => !removeIds.has(ol.itemId));
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// ── 2. Add items flagged as default player inventory ──────────────────────
|
|
114
|
-
const addIds = entries.filter((entry) => entry && entry.defaultPlayerInventory && entry.id).map((entry) => entry.id);
|
|
115
|
-
if (addIds.length === 0) return config;
|
|
116
|
-
|
|
117
|
-
const playerDefault = (config.entityDefaults || []).find((d) => d.entityType === 'player');
|
|
118
|
-
if (!playerDefault) return config;
|
|
119
|
-
|
|
120
|
-
playerDefault.defaultObjectLayers = playerDefault.defaultObjectLayers || [];
|
|
121
|
-
const existing = new Set(playerDefault.defaultObjectLayers.map((ol) => ol.itemId));
|
|
122
|
-
for (const id of addIds) {
|
|
123
|
-
if (existing.has(id)) continue;
|
|
124
|
-
playerDefault.defaultObjectLayers.push({ itemId: id, active: false, quantity: 1 });
|
|
125
|
-
existing.add(id);
|
|
126
|
-
}
|
|
127
|
-
return config;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Map CyberiaSkill collection docs to the proto skillConfig shape. The skill
|
|
131
|
-
// collection is the authoritative own-model source: it carries full skill
|
|
132
|
-
// metadata (summonedEntityItemId, name, description) that the instance-conf
|
|
133
|
-
// skillConfig schema deliberately does not store.
|
|
97
|
+
// Map CyberiaSkill documents to the proto skillConfig shape. The collection is the authoritative
|
|
98
|
+
// source and carries the full metadata (summonedEntityItemId, name, description); the wire message
|
|
99
|
+
// is built from the subset this instance runs, which nothing persists.
|
|
134
100
|
function skillDocsToConfig(skillDocs = []) {
|
|
135
101
|
return skillDocs
|
|
136
102
|
.filter((d) => d && d.triggerItemId)
|
|
@@ -145,19 +111,16 @@ function skillDocsToConfig(skillDocs = []) {
|
|
|
145
111
|
}));
|
|
146
112
|
}
|
|
147
113
|
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
// callers
|
|
151
|
-
async function
|
|
114
|
+
// Every skill definition the deployment knows. Which of them an instance runs is decided by
|
|
115
|
+
// selectInstanceSkills against that instance's own content. Returns [] when the model is not
|
|
116
|
+
// loaded (api not mounted), so callers fall back to the canonical definitions.
|
|
117
|
+
async function loadSkillDocs(models) {
|
|
152
118
|
if (!models.CyberiaSkill) return [];
|
|
153
119
|
return await models.CyberiaSkill.find({}).lean();
|
|
154
120
|
}
|
|
155
121
|
|
|
156
|
-
// Map CyberiaEntityTypeDefault
|
|
157
|
-
//
|
|
158
|
-
// defaults (live/dead/drop + seed object layers). Resolution is by liveItemIds
|
|
159
|
-
// membership, so every variant (e.g. each resource skin) must travel as its own
|
|
160
|
-
// entry — never collapsed by entityType.
|
|
122
|
+
// Map CyberiaEntityTypeDefault docs to the wire entityDefaults shape. Lookup is
|
|
123
|
+
// by liveItemIds membership, so each variant travels as its own entry.
|
|
161
124
|
function entityTypeDefaultDocsToConfig(docs = []) {
|
|
162
125
|
return docs
|
|
163
126
|
.filter((d) => d && d.entityType)
|
|
@@ -166,75 +129,56 @@ function entityTypeDefaultDocsToConfig(docs = []) {
|
|
|
166
129
|
liveItemIds: [...(d.liveItemIds || [])],
|
|
167
130
|
deadItemIds: [...(d.deadItemIds || [])],
|
|
168
131
|
dropItemIds: [...(d.dropItemIds || [])],
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
active: !!ol.active,
|
|
172
|
-
quantity: ol.quantity || 0,
|
|
173
|
-
})),
|
|
132
|
+
inventoryItemsIds: [...(d.inventoryItemsIds || [])],
|
|
133
|
+
overrideItemsIdsState: [...(d.overrideItemsIdsState || [])],
|
|
174
134
|
behavior: d.behavior || '',
|
|
175
135
|
}));
|
|
176
136
|
}
|
|
177
137
|
|
|
178
|
-
// Load
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
async function loadEntityTypeDefaultDocs(models) {
|
|
138
|
+
// Load the entity-type defaults THIS instance references. The conf names them by _id, so a
|
|
139
|
+
// document reaches a world only when that world points at it — never because another instance
|
|
140
|
+
// happens to share an item id with it, which is what used to drag foreign wiring into a build.
|
|
141
|
+
async function loadEntityTypeDefaultDocs(models, conf) {
|
|
182
142
|
if (!models.CyberiaEntityTypeDefault) return [];
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
// when the entity appears in no map (e.g. a new resource skin).
|
|
189
|
-
function addEntityDefaultAtlasIds(entityDefaults, atlasItemIds) {
|
|
190
|
-
for (const d of entityDefaults || []) {
|
|
191
|
-
for (const id of d.liveItemIds || []) if (id) atlasItemIds.add(id);
|
|
192
|
-
for (const id of d.deadItemIds || []) if (id) atlasItemIds.add(id);
|
|
193
|
-
for (const id of d.dropItemIds || []) if (id) atlasItemIds.add(id);
|
|
194
|
-
for (const ol of d.defaultObjectLayers || []) if (ol.itemId) atlasItemIds.add(ol.itemId);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Feed skill trigger items and every concrete summoned-entity id into the atlas
|
|
199
|
-
// id set so the Go server resolves their sprites even when the entity appears in
|
|
200
|
-
// no map. Runtime placeholders ('$active_skin', …) are excluded.
|
|
201
|
-
function addSkillAtlasIds(skillDocs, atlasItemIds) {
|
|
202
|
-
for (const d of skillDocs || []) {
|
|
203
|
-
if (d.triggerItemId) atlasItemIds.add(d.triggerItemId);
|
|
204
|
-
for (const sk of d.skills || []) {
|
|
205
|
-
const id = sk.summonedEntityItemId;
|
|
206
|
-
if (id && !id.startsWith('$')) atlasItemIds.add(id);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
143
|
+
const ids = (conf?.entityDefaults || []).map((id) => String(id?._id ?? id)).filter(Boolean);
|
|
144
|
+
if (ids.length === 0) return [];
|
|
145
|
+
const docs = await models.CyberiaEntityTypeDefault.find({ _id: { $in: ids } }).lean();
|
|
146
|
+
const byId = new Map(docs.map((doc) => [String(doc._id), doc]));
|
|
147
|
+
return ids.filter((id) => byId.has(id)).map((id) => byId.get(id));
|
|
209
148
|
}
|
|
210
149
|
|
|
211
|
-
|
|
212
|
-
|
|
150
|
+
// `itemTypes` lets the equipment rules settle a contested slot: which skin an entity wears when an
|
|
151
|
+
// override names one. Without it every row keeps the state its list derives, which is the right
|
|
152
|
+
// answer for a world whose object layers have not been read yet.
|
|
153
|
+
function mergeEntityDefaults(entityDefaults = [], itemTypes) {
|
|
154
|
+
const merged = entityDefaults.map((entityDefault) => normalizeEntityDefault(entityDefault, {}, itemTypes));
|
|
213
155
|
const coveredTypes = new Set(merged.map((entityDefault) => entityDefault.entityType));
|
|
214
156
|
for (const canonical of ENTITY_TYPE_DEFAULTS) {
|
|
215
|
-
if (!coveredTypes.has(canonical.entityType)) merged.push(normalizeEntityDefault(canonical, canonical));
|
|
157
|
+
if (!coveredTypes.has(canonical.entityType)) merged.push(normalizeEntityDefault(canonical, canonical, itemTypes));
|
|
216
158
|
}
|
|
217
159
|
return merged;
|
|
218
160
|
}
|
|
219
161
|
|
|
162
|
+
/** itemId → item type, read from the ObjectLayer documents a world already loads for its atlases. */
|
|
163
|
+
function itemTypesOf(objectLayerDocs = []) {
|
|
164
|
+
const types = {};
|
|
165
|
+
for (const doc of objectLayerDocs) {
|
|
166
|
+
const item = doc?.data?.item;
|
|
167
|
+
if (item?.id && item?.type) types[item.id] = item.type;
|
|
168
|
+
}
|
|
169
|
+
return types;
|
|
170
|
+
}
|
|
171
|
+
|
|
220
172
|
// ── Mongoose doc → wire message converters (proto camelCase shapes) ────────
|
|
221
173
|
|
|
222
174
|
function toObjectLayerMsg(doc) {
|
|
223
175
|
const d = doc.data || {};
|
|
224
176
|
const item = d.item || {};
|
|
225
|
-
const stats = d.stats || {};
|
|
226
177
|
const ledger = d.ledger || {};
|
|
227
178
|
const render = d.render || {};
|
|
228
179
|
return {
|
|
229
180
|
mongoId: String(doc._id),
|
|
230
|
-
stats:
|
|
231
|
-
effect: stats.effect || 0,
|
|
232
|
-
resistance: stats.resistance || 0,
|
|
233
|
-
agility: stats.agility || 0,
|
|
234
|
-
range: stats.range || 0,
|
|
235
|
-
intelligence: stats.intelligence || 0,
|
|
236
|
-
utility: stats.utility || 0,
|
|
237
|
-
},
|
|
181
|
+
stats: validateStats(d.stats),
|
|
238
182
|
item: {
|
|
239
183
|
id: item.id || '',
|
|
240
184
|
type: item.type || '',
|
|
@@ -263,7 +207,7 @@ function parseRgba(str) {
|
|
|
263
207
|
if (!str) return { r: 0, g: 0, b: 0, a: 0 };
|
|
264
208
|
const m = str.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+))?\s*\)/);
|
|
265
209
|
if (!m) return { r: 0, g: 0, b: 0, a: 0 };
|
|
266
|
-
// CSS alpha is 0-1
|
|
210
|
+
// CSS alpha is 0-1 float; the wire format uses 0-255 int.
|
|
267
211
|
const cssAlpha = m[4] !== undefined ? parseFloat(m[4]) : 1;
|
|
268
212
|
return { r: Number(m[1]), g: Number(m[2]), b: Number(m[3]), a: Math.round(cssAlpha * 255) };
|
|
269
213
|
}
|
|
@@ -272,6 +216,7 @@ function toEntityMsg(ent) {
|
|
|
272
216
|
const rgba = parseRgba(ent.color);
|
|
273
217
|
return {
|
|
274
218
|
entityType: ent.entityType || 'floor',
|
|
219
|
+
level: ent.level === undefined ? 0 : validateEntityLevel(ent.level),
|
|
275
220
|
initCellX: ent.initCellX || 0,
|
|
276
221
|
initCellY: ent.initCellY || 0,
|
|
277
222
|
dimX: ent.dimX || 1,
|
|
@@ -390,24 +335,23 @@ function toQuestMsg(q) {
|
|
|
390
335
|
* Any field that is null/undefined in `gc` falls back to FALLBACK_CONFIG_DEFAULTS,
|
|
391
336
|
* so partial DB documents always produce a fully playable config.
|
|
392
337
|
*/
|
|
338
|
+
// A chance is a fraction of one. Anything else a conf carries is not a chance and resolves to
|
|
339
|
+
// the default, the same way a partial progression curve inherits its missing fields.
|
|
340
|
+
const chanceOf = (value, fallback) => (Number.isFinite(value) && value >= 0 && value <= 1 ? value : fallback);
|
|
341
|
+
|
|
393
342
|
function toInstanceConfig(gc) {
|
|
394
343
|
const fb = FALLBACK_CONFIG_DEFAULTS;
|
|
395
344
|
if (!gc) return buildFallbackConfig();
|
|
396
345
|
|
|
397
|
-
// STRICT BOUNDARY
|
|
398
|
-
//
|
|
399
|
-
//
|
|
400
|
-
//
|
|
401
|
-
|
|
402
|
-
//
|
|
403
|
-
//
|
|
404
|
-
//
|
|
405
|
-
|
|
406
|
-
// the C/WASM cyberia-client owns its own render policy. See
|
|
407
|
-
// src/client/components/cyberia/SharedDefaultsCyberia.js.
|
|
408
|
-
|
|
409
|
-
const gcDefaults = gc.entityDefaults && gc.entityDefaults.length > 0 ? gc.entityDefaults : [];
|
|
410
|
-
const entityDefaults = mergeEntityDefaults(gcDefaults);
|
|
346
|
+
// STRICT BOUNDARY: this config is simulation only. Presentation values
|
|
347
|
+
// (palette, camera tunings, screen factors, devUi, interpolationMs,
|
|
348
|
+
// status-icon visuals, entityDefaults[].colorKey, cellSize, defaultObj*)
|
|
349
|
+
// reach the client through /api/cyberia-client-hints, never a boot transport.
|
|
350
|
+
|
|
351
|
+
// The conf carries references, not documents: the caller resolves them and overwrites this
|
|
352
|
+
// with the result. Canonical defaults alone are the correct answer for a world that
|
|
353
|
+
// references none, and for the fallback build that has no conf at all.
|
|
354
|
+
const entityDefaults = mergeEntityDefaults();
|
|
411
355
|
|
|
412
356
|
return {
|
|
413
357
|
tickRate: gc.tickRate ?? fb.tickRate,
|
|
@@ -425,12 +369,12 @@ function toInstanceConfig(gc) {
|
|
|
425
369
|
playerBaseSpeed: gc.playerBaseSpeed ?? fb.playerBaseSpeed,
|
|
426
370
|
playerBaseLifeRegenMin: gc.playerBaseLifeRegenMin ?? fb.playerBaseLifeRegenMin,
|
|
427
371
|
playerBaseLifeRegenMax: gc.playerBaseLifeRegenMax ?? fb.playerBaseLifeRegenMax,
|
|
428
|
-
|
|
372
|
+
progressionRules: resolveProgressionRules(gc.progressionRules),
|
|
429
373
|
maxActiveLayers: gc.maxActiveLayers ?? fb.maxActiveLayers,
|
|
430
374
|
initialLifeFraction: gc.initialLifeFraction ?? fb.initialLifeFraction,
|
|
431
375
|
respawnDurationMs: gc.respawnDurationMs ?? fb.respawnDurationMs,
|
|
432
376
|
collisionLifeLoss: gc.collisionLifeLoss ?? fb.collisionLifeLoss,
|
|
433
|
-
// Economy — Fountain & Sink (nested
|
|
377
|
+
// Economy — Fountain & Sink (nested EconomyRules message).
|
|
434
378
|
economyRules: {
|
|
435
379
|
botSpawnCoins: gc.economyRules?.botSpawnCoins ?? fb.economyRules.botSpawnCoins,
|
|
436
380
|
playerSpawnCoins: gc.economyRules?.playerSpawnCoins ?? fb.economyRules.playerSpawnCoins,
|
|
@@ -441,31 +385,25 @@ function toInstanceConfig(gc) {
|
|
|
441
385
|
portalFee: gc.economyRules?.portalFee ?? fb.economyRules.portalFee,
|
|
442
386
|
craftingFeePercent: gc.economyRules?.craftingFeePercent ?? fb.economyRules.craftingFeePercent,
|
|
443
387
|
},
|
|
444
|
-
lifeRegenChance: gc.lifeRegenChance
|
|
445
|
-
maxChance: gc.maxChance
|
|
388
|
+
lifeRegenChance: chanceOf(gc.lifeRegenChance, fb.lifeRegenChance),
|
|
389
|
+
maxChance: chanceOf(gc.maxChance, fb.maxChance),
|
|
446
390
|
entityDefaults,
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
logicEventId: sk.logicEventId || '',
|
|
451
|
-
name: sk.name || '',
|
|
452
|
-
description: sk.description || '',
|
|
453
|
-
summonedEntityItemId: sk.summonedEntityItemId || '',
|
|
454
|
-
})),
|
|
455
|
-
})),
|
|
391
|
+
// Filled in by the caller from the skills this world's own content triggers; a conf stores
|
|
392
|
+
// none, so there is nothing here to fall back to.
|
|
393
|
+
skillConfig: [],
|
|
456
394
|
skillRules: {
|
|
457
|
-
projectileSpawnChance: gc.skillRules?.projectileSpawnChance
|
|
395
|
+
projectileSpawnChance: chanceOf(gc.skillRules?.projectileSpawnChance, fb.skillRules.projectileSpawnChance),
|
|
458
396
|
projectileLifetimeMs: gc.skillRules?.projectileLifetimeMs ?? fb.skillRules.projectileLifetimeMs,
|
|
459
397
|
projectileWidth: gc.skillRules?.projectileWidth ?? fb.skillRules.projectileWidth,
|
|
460
398
|
projectileHeight: gc.skillRules?.projectileHeight ?? fb.skillRules.projectileHeight,
|
|
461
399
|
projectileSpeedMultiplier: gc.skillRules?.projectileSpeedMultiplier ?? fb.skillRules.projectileSpeedMultiplier,
|
|
462
|
-
doppelgangerSpawnChance: gc.skillRules?.doppelgangerSpawnChance
|
|
400
|
+
doppelgangerSpawnChance: chanceOf(gc.skillRules?.doppelgangerSpawnChance, fb.skillRules.doppelgangerSpawnChance),
|
|
463
401
|
doppelgangerLifetimeMs: gc.skillRules?.doppelgangerLifetimeMs ?? fb.skillRules.doppelgangerLifetimeMs,
|
|
464
402
|
doppelgangerSpawnRadius: gc.skillRules?.doppelgangerSpawnRadius ?? fb.skillRules.doppelgangerSpawnRadius,
|
|
465
403
|
doppelgangerInitialLifeFraction:
|
|
466
404
|
gc.skillRules?.doppelgangerInitialLifeFraction ?? fb.skillRules.doppelgangerInitialLifeFraction,
|
|
467
405
|
},
|
|
468
|
-
// Equipment rules —
|
|
406
|
+
// Equipment rules — activation constraints (nested EquipmentRules message).
|
|
469
407
|
equipmentRules: {
|
|
470
408
|
activeItemTypes: gc.equipmentRules?.activeItemTypes ?? fb.equipmentRules.activeItemTypes,
|
|
471
409
|
onePerType: gc.equipmentRules?.onePerType ?? fb.equipmentRules.onePerType,
|
|
@@ -483,15 +421,6 @@ function buildFallbackConfig() {
|
|
|
483
421
|
return JSON.parse(JSON.stringify(FALLBACK_CONFIG_DEFAULTS));
|
|
484
422
|
}
|
|
485
423
|
|
|
486
|
-
/**
|
|
487
|
-
* Assembles the InstanceConfig for the procedural fallback world from code
|
|
488
|
-
* defaults only — no own-model collection may override it — then applies the
|
|
489
|
-
* fallback instance's default-player-inventory itemIds.
|
|
490
|
-
*/
|
|
491
|
-
function buildFallbackInstanceConfig(fallbackConf, instanceItemIds) {
|
|
492
|
-
return applyInstanceDefaultPlayerInventory(toInstanceConfig(fallbackConf), instanceItemIds);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
424
|
// ═══════════════════════════════════════════════════════════════════
|
|
496
425
|
// Transport-agnostic fetchers — one per CyberiaDataService RPC
|
|
497
426
|
// ═══════════════════════════════════════════════════════════════════
|
|
@@ -555,40 +484,31 @@ async function fetchFullInstance(models, requestedInstanceCode) {
|
|
|
555
484
|
// ── Fallback: instance not found → return a multi-map procedural world ──
|
|
556
485
|
if (!inst) {
|
|
557
486
|
logger.info(`Instance "${instanceCode}" not found — returning fallback world.`);
|
|
558
|
-
const world = generateFallbackWorld(
|
|
487
|
+
const world = generateFallbackWorld();
|
|
559
488
|
const fallbackConf = buildFallbackConfig();
|
|
560
489
|
|
|
561
|
-
// Collect
|
|
562
|
-
//
|
|
490
|
+
// Collect every objectLayerItemId of the generated maps, so the atlases
|
|
491
|
+
// resolve at startup.
|
|
563
492
|
const fallbackItemIds = new Set();
|
|
564
493
|
for (const m of world.maps) {
|
|
565
494
|
for (const e of m.entities || []) {
|
|
566
495
|
for (const id of e.objectLayerItemIds || []) fallbackItemIds.add(id);
|
|
567
496
|
}
|
|
568
497
|
}
|
|
569
|
-
// Also include system OL items from entity defaults
|
|
570
|
-
|
|
498
|
+
// Also include system OL items from the canonical entity defaults — the fallback world
|
|
499
|
+
// references no collection document, so these are the defaults it actually runs on.
|
|
500
|
+
for (const d of ENTITY_TYPE_DEFAULTS) {
|
|
571
501
|
for (const id of d.liveItemIds || []) fallbackItemIds.add(id);
|
|
572
502
|
for (const id of d.deadItemIds || []) fallbackItemIds.add(id);
|
|
573
503
|
for (const id of d.dropItemIds || []) fallbackItemIds.add(id);
|
|
574
|
-
for (const
|
|
575
|
-
if (ol.itemId) fallbackItemIds.add(ol.itemId);
|
|
576
|
-
}
|
|
504
|
+
for (const id of d.inventoryItemsIds || []) fallbackItemIds.add(id);
|
|
577
505
|
}
|
|
578
|
-
// Include every canonical item so the
|
|
579
|
-
//
|
|
580
|
-
// hatchet, alt weapons, etc.) — required for the one-per-type
|
|
581
|
-
// equipment rule, which silently no-ops when item type is unknown.
|
|
506
|
+
// Include every canonical item so the server knows the type of anything a
|
|
507
|
+
// player can pick up. The one-per-type rule no-ops on an unknown type.
|
|
582
508
|
for (const it of DefaultCyberiaItems || []) {
|
|
583
509
|
if (it?.item?.id) fallbackItemIds.add(it.item.id);
|
|
584
510
|
}
|
|
585
511
|
|
|
586
|
-
// Instance-level itemIds (default-player-inventory) may not appear in
|
|
587
|
-
// any map entity — include them so their atlases resolve.
|
|
588
|
-
for (const entry of world.instance.itemIds || []) {
|
|
589
|
-
if (entry?.id) fallbackItemIds.add(entry.id);
|
|
590
|
-
}
|
|
591
|
-
|
|
592
512
|
// The fallback world is fully self-contained: content config comes
|
|
593
513
|
// exclusively from the canonical code defaults (cyberia-server-defaults
|
|
594
514
|
// + SharedDefaultsCyberia). Own-model collections (CyberiaSkill,
|
|
@@ -599,19 +519,16 @@ async function fetchFullInstance(models, requestedInstanceCode) {
|
|
|
599
519
|
? await models.ObjectLayer.find({ 'data.item.id': { $in: [...fallbackItemIds] } }).lean()
|
|
600
520
|
: [];
|
|
601
521
|
|
|
602
|
-
const fallbackConfig =
|
|
522
|
+
const fallbackConfig = toInstanceConfig(fallbackConf);
|
|
523
|
+
// Same membership rule as a persisted world, over the canonical definitions: this path
|
|
524
|
+
// consults no collection, so DefaultSkillConfig is the only source of skills it has.
|
|
525
|
+
fallbackConfig.skillConfig = skillDocsToConfig(selectInstanceSkills(DefaultSkillConfig, fallbackItemIds));
|
|
603
526
|
|
|
604
527
|
// Opaque version over everything mutable in this payload, mirroring the
|
|
605
528
|
// persisted path below. A constant here would make hot reload a silent
|
|
606
529
|
// no-op: WorldBuilder.ReloadWorld skips the rebuild — and therefore
|
|
607
|
-
// ApplyInstanceConfig
|
|
608
|
-
|
|
609
|
-
// that can actually differ are the staged default items and the
|
|
610
|
-
// ObjectLayer docs, which are the only things read from the DB here.
|
|
611
|
-
const fallbackVersionParts = ['fallback'];
|
|
612
|
-
for (const entry of world.instance.itemIds || []) {
|
|
613
|
-
fallbackVersionParts.push(`${entry.id}:${entry.defaultPlayerInventory ? 1 : 0}`);
|
|
614
|
-
}
|
|
530
|
+
// ApplyInstanceConfig when content and configuration are unchanged.
|
|
531
|
+
const fallbackVersionParts = ['fallback', JSON.stringify(fallbackConfig)];
|
|
615
532
|
for (const doc of fallbackOlDocs) fallbackVersionParts.push(String(doc.sha256 || doc._id));
|
|
616
533
|
const fallbackVersion = `fallback-${crypto
|
|
617
534
|
.createHash('sha256')
|
|
@@ -633,8 +550,7 @@ async function fetchFullInstance(models, requestedInstanceCode) {
|
|
|
633
550
|
objectLayers: fallbackOlDocs.map(toObjectLayerMsg),
|
|
634
551
|
config: fallbackConfig,
|
|
635
552
|
version: fallbackVersion,
|
|
636
|
-
// Mission content for the
|
|
637
|
-
// the canonical defaults — no DB, fully self-contained.
|
|
553
|
+
// Mission content for the fallback comes from the code defaults, no DB.
|
|
638
554
|
actions: DefaultCyberiaActions.map(toActionMsg),
|
|
639
555
|
quests: DefaultCyberiaQuests.map(toQuestMsg),
|
|
640
556
|
};
|
|
@@ -642,10 +558,8 @@ async function fetchFullInstance(models, requestedInstanceCode) {
|
|
|
642
558
|
|
|
643
559
|
// ── Instance found — load maps + entity OLs + config default OLs ──────
|
|
644
560
|
// `populate('conf')` returns null when the ObjectId ref is missing or
|
|
645
|
-
// orphaned
|
|
646
|
-
//
|
|
647
|
-
// tuning fields are always read from the database, never silently
|
|
648
|
-
// replaced by FALLBACK_CONFIG_DEFAULTS.
|
|
561
|
+
// orphaned. The direct instanceCode lookup keeps every tuning field coming
|
|
562
|
+
// from the database instead of FALLBACK_CONFIG_DEFAULTS.
|
|
649
563
|
let conf = inst.conf;
|
|
650
564
|
if (!conf) {
|
|
651
565
|
conf = await models.CyberiaInstanceConf.findOne({ instanceCode }).lean();
|
|
@@ -657,105 +571,112 @@ async function fetchFullInstance(models, requestedInstanceCode) {
|
|
|
657
571
|
const mapCodes = inst.cyberiaMapCodes || [];
|
|
658
572
|
const mapDocs = mapCodes.length ? await models.CyberiaMap.find({ code: { $in: mapCodes } }).lean() : [];
|
|
659
573
|
|
|
660
|
-
//
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
for (const e of m.entities || []) {
|
|
664
|
-
for (const id of e.objectLayerItemIds || []) itemIds.add(id);
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
// Instance-level itemIds (e.g. default-player-inventory items) may not
|
|
669
|
-
// appear in any map entity — include them so their atlases resolve.
|
|
670
|
-
// Tolerate legacy flat-string entries alongside the { id, … } shape.
|
|
671
|
-
for (const entry of inst.itemIds || []) {
|
|
672
|
-
const id = typeof entry === 'string' ? entry : entry?.id;
|
|
673
|
-
if (id) itemIds.add(id);
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
// Authoritative per-entity-type defaults come from the
|
|
677
|
-
// CyberiaEntityTypeDefault collection (own model); empty collection leaves
|
|
678
|
-
// the conf/canonical defaults in place.
|
|
679
|
-
const entityDefaultDocs = await loadEntityTypeDefaultDocs(models);
|
|
574
|
+
// Authoritative per-entity-type defaults are the documents this instance's conf references;
|
|
575
|
+
// referencing none leaves the canonical defaults in place.
|
|
576
|
+
const entityDefaultDocs = await loadEntityTypeDefaultDocs(models, conf);
|
|
680
577
|
const entityDefaultsConfig = entityTypeDefaultDocsToConfig(entityDefaultDocs);
|
|
681
578
|
|
|
682
|
-
//
|
|
683
|
-
//
|
|
684
|
-
//
|
|
685
|
-
for
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
579
|
+
// Mission content for this instance: actions and quests bound to its maps (by sourceMapCode).
|
|
580
|
+
// Delivered with the world so the Go server never opens a separate content channel, and never
|
|
581
|
+
// receives content from other instances' maps. Read before the item set because what a vendor
|
|
582
|
+
// sells and what a quest asks for are part of what this world owns.
|
|
583
|
+
const actionDocs =
|
|
584
|
+
mapCodes.length && models.CyberiaAction
|
|
585
|
+
? await models.CyberiaAction.find({ sourceMapCode: { $in: mapCodes } }).lean()
|
|
586
|
+
: [];
|
|
587
|
+
const questDocs =
|
|
588
|
+
mapCodes.length && models.CyberiaQuest
|
|
589
|
+
? await models.CyberiaQuest.find({ sourceMapCode: { $in: mapCodes } }).lean()
|
|
590
|
+
: [];
|
|
591
|
+
|
|
592
|
+
// Everything this world names, by the one rule the export and the editor's sync also use. The
|
|
593
|
+
// canonical defaults join the instance's own: every world runs on both, so an entity here can
|
|
594
|
+
// hold what they wire even when no map places it.
|
|
595
|
+
const owned = collectInstanceItemIds({
|
|
596
|
+
maps: mapDocs,
|
|
597
|
+
entityDefaults: [...entityDefaultsConfig, ...ENTITY_TYPE_DEFAULTS],
|
|
598
|
+
actions: actionDocs,
|
|
599
|
+
quests: questDocs,
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
const itemIds = new Set(owned);
|
|
693
603
|
// The Go server falls back to this dead visual when a build declares
|
|
694
604
|
// no deadItemIds — its atlas must resolve even when nothing references it.
|
|
695
605
|
itemIds.add(DEFAULT_DEAD_ITEM_ID);
|
|
696
|
-
addEntityDefaultAtlasIds(entityDefaultsConfig, itemIds);
|
|
697
606
|
|
|
698
|
-
//
|
|
699
|
-
//
|
|
700
|
-
|
|
701
|
-
|
|
607
|
+
// The skills this world runs: the collection owns the definitions, and a trigger item the
|
|
608
|
+
// world never names is unreachable here. The canonical definitions stand in only for a
|
|
609
|
+
// deployment that never seeded the collection — never for a world that legitimately selects
|
|
610
|
+
// none of them, which would put back skills an operator deleted. What the selected skills
|
|
611
|
+
// summon needs an atlas, so those ids join the set before the ObjectLayer query.
|
|
612
|
+
const seededSkills = await loadSkillDocs(models);
|
|
613
|
+
const skillDocs = selectInstanceSkills(seededSkills.length ? seededSkills : DefaultSkillConfig, owned);
|
|
614
|
+
for (const summoned of collectSummonedItemIds(skillDocs)) itemIds.add(summoned);
|
|
702
615
|
|
|
703
616
|
const olDocs = itemIds.size ? await models.ObjectLayer.find({ 'data.item.id': { $in: [...itemIds] } }).lean() : [];
|
|
704
617
|
|
|
705
|
-
// Opaque version
|
|
706
|
-
//
|
|
707
|
-
const versionParts = [String(inst.updatedAt || inst._id)];
|
|
618
|
+
// Opaque version over the updatedAt timestamps. The server compares it to
|
|
619
|
+
// skip a full world rebuild when nothing changed.
|
|
620
|
+
const versionParts = [String(inst.updatedAt || inst._id), JSON.stringify(toInstanceConfig(conf))];
|
|
708
621
|
for (const m of mapDocs) versionParts.push(String(m.updatedAt || m._id));
|
|
709
622
|
if (conf.updatedAt) versionParts.push(String(conf.updatedAt));
|
|
710
623
|
// Skill edits live in their own collection — fold them in so a skill
|
|
711
624
|
// change triggers a Go-side world rebuild instead of serving stale skills.
|
|
712
|
-
for (const sk of skillDocs) versionParts.push(String(sk.updatedAt || sk._id));
|
|
625
|
+
for (const sk of skillDocs) versionParts.push(String(sk.updatedAt || sk._id || sk.triggerItemId));
|
|
713
626
|
// Entity-type-default edits live in their own collection too — fold them
|
|
714
627
|
// in so adding/editing a default (e.g. a new resource) rebuilds the world.
|
|
715
628
|
for (const ed of entityDefaultDocs) versionParts.push(String(ed.updatedAt || ed._id));
|
|
716
629
|
const version = crypto.createHash('sha256').update(versionParts.join('|')).digest('hex');
|
|
717
630
|
|
|
718
|
-
// Mission content for this instance: actions and quests bound to its
|
|
719
|
-
// maps (by sourceMapCode). Delivered with the world so the Go server
|
|
720
|
-
// never opens a separate content channel, and never receives content from
|
|
721
|
-
// other instances' maps.
|
|
722
|
-
const actionDocs =
|
|
723
|
-
mapCodes.length && models.CyberiaAction
|
|
724
|
-
? await models.CyberiaAction.find({ sourceMapCode: { $in: mapCodes } }).lean()
|
|
725
|
-
: [];
|
|
726
|
-
const questDocs =
|
|
727
|
-
mapCodes.length && models.CyberiaQuest
|
|
728
|
-
? await models.CyberiaQuest.find({ sourceMapCode: { $in: mapCodes } }).lean()
|
|
729
|
-
: [];
|
|
730
|
-
|
|
731
631
|
const baseConfig = toInstanceConfig(conf);
|
|
732
|
-
//
|
|
733
|
-
//
|
|
734
|
-
//
|
|
735
|
-
//
|
|
736
|
-
if (entityDefaultsConfig.length)
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
//
|
|
740
|
-
|
|
632
|
+
// The referenced documents are the world's defaults, completed by the canonical set for every
|
|
633
|
+
// entity type they do not cover. Every variant — each resource skin, say — reaches the Go
|
|
634
|
+
// runtime keyed by liveItemIds. Applied BEFORE default-player-inventory so the player layer
|
|
635
|
+
// additions survive.
|
|
636
|
+
if (entityDefaultsConfig.length) {
|
|
637
|
+
baseConfig.entityDefaults = mergeEntityDefaults(entityDefaultsConfig, itemTypesOf(olDocs));
|
|
638
|
+
}
|
|
639
|
+
// The wire still carries a skillConfig; nothing stores one. It is exactly the skills selected
|
|
640
|
+
// above, so what the simulation runs and what the export writes come from one decision.
|
|
641
|
+
baseConfig.skillConfig = skillDocsToConfig(skillDocs);
|
|
741
642
|
|
|
742
643
|
return {
|
|
743
644
|
instance: toInstanceMsg(inst),
|
|
744
645
|
maps: mapDocs.map(toMapMsg),
|
|
745
646
|
objectLayers: olDocs.map(toObjectLayerMsg),
|
|
746
|
-
config,
|
|
647
|
+
config: baseConfig,
|
|
747
648
|
version,
|
|
748
649
|
actions: actionDocs.map(toActionMsg),
|
|
749
650
|
quests: questDocs.map(toQuestMsg),
|
|
750
651
|
};
|
|
751
652
|
}
|
|
752
653
|
|
|
654
|
+
/**
|
|
655
|
+
* Every ObjectLayer item id one instance runs on, as stored documents.
|
|
656
|
+
*
|
|
657
|
+
* Reads the world the same way the runtime does, so a tool never disagrees with the
|
|
658
|
+
* simulation about what a world owns. Ids the instance names but the collection does not
|
|
659
|
+
* hold are absent, because {@link fetchFullInstance} resolves the set against ObjectLayer.
|
|
660
|
+
*
|
|
661
|
+
* @param {object} models - Instance models, see {@link getInstanceModels}.
|
|
662
|
+
* @param {string} instanceCode - Instance code, must exist.
|
|
663
|
+
* @returns {Promise<string[]>} Item ids, no duplicates.
|
|
664
|
+
* @throws {Error} When no CyberiaInstance holds that code.
|
|
665
|
+
*/
|
|
666
|
+
async function fetchInstanceObjectLayerItemIds(models, instanceCode) {
|
|
667
|
+
const instance = await models.CyberiaInstance.findOne({ code: instanceCode }, { _id: 1 }).lean();
|
|
668
|
+
if (!instance) throw new Error(`CyberiaInstance "${instanceCode}" not found`);
|
|
669
|
+
const { objectLayers } = await fetchFullInstance(models, instanceCode);
|
|
670
|
+
return [...new Set(objectLayers.map((objectLayer) => objectLayer?.item?.id).filter(Boolean))];
|
|
671
|
+
}
|
|
672
|
+
|
|
753
673
|
export {
|
|
754
674
|
buildCyberiaMmoInstanceEnv as buildInstanceEnv,
|
|
755
675
|
buildCyberiaMmoInstanceEnv,
|
|
676
|
+
fetchInstanceObjectLayerItemIds,
|
|
756
677
|
getInstanceModels,
|
|
678
|
+
itemTypesOf,
|
|
757
679
|
normalizeEntityDefault,
|
|
758
|
-
applyInstanceDefaultPlayerInventory,
|
|
759
680
|
skillDocsToConfig,
|
|
760
681
|
entityTypeDefaultDocsToConfig,
|
|
761
682
|
mergeEntityDefaults,
|
|
@@ -768,7 +689,6 @@ export {
|
|
|
768
689
|
toQuestMsg,
|
|
769
690
|
toInstanceConfig,
|
|
770
691
|
buildFallbackConfig,
|
|
771
|
-
buildFallbackInstanceConfig,
|
|
772
692
|
pingData,
|
|
773
693
|
objectLayerQueryFilter,
|
|
774
694
|
fetchObjectLayerBatch,
|