@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,46 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Canonical
|
|
2
|
+
* Canonical simulation defaults for the Cyberia runtime: per-entity-type item
|
|
3
|
+
* sets, simulation / AOI / combat / economy / skill rules, equipment rules,
|
|
4
|
+
* status-icon numeric IDs, and the seed content (dialogues, actions, quests)
|
|
5
|
+
* the CLI writes to Mongo.
|
|
3
6
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
7
|
+
* STRICT BOUNDARY: never import this from `src/client/`. The browser bundler
|
|
8
|
+
* resolves imports recursively, so one browser-side import ships the whole
|
|
9
|
+
* simulation defaults in the public JS payload.
|
|
6
10
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* - status-icon **numeric IDs** (visuals live in client defaults)
|
|
11
|
-
* - seed content (dialogues, actions, quests) consumed by the
|
|
12
|
-
* persistence/CLI tooling that bootstraps the world
|
|
13
|
-
* - native-dependency pin list for chain-bridge tooling
|
|
14
|
-
*
|
|
15
|
-
* STRICT BOUNDARY
|
|
16
|
-
* ---------------
|
|
17
|
-
* NEVER imported from any file under `src/client/`. The browser bundler
|
|
18
|
-
* resolves imports recursively, so a single browser-side import would drag
|
|
19
|
-
* the entire simulation defaults into the public JS payload.
|
|
20
|
-
*
|
|
21
|
-
* Shared content **vocabulary** (item/entity type enums, the
|
|
22
|
-
* `DefaultCyberiaItems` registry, `ENTITY_TYPE_TO_ITEM_TYPES`, the quest
|
|
23
|
-
* step objective enum) lives in `SharedDefaultsCyberia.js`. This file
|
|
24
|
-
* re-imports those so the browser editor never needs to reach into
|
|
25
|
-
* server-defaults to learn the schema.
|
|
26
|
-
*
|
|
27
|
-
* Consumers:
|
|
28
|
-
* - cyberia-instance-conf model + grpc-server (gameplay config)
|
|
29
|
-
* - cyberia-world-generator / cyberia-fallback-world (world build)
|
|
30
|
-
* - cyberia-quest / cyberia-action / Mongo seed scripts (content)
|
|
31
|
-
* - bin/cyberia + bin/build + bin/deploy (CLI + chain bridge)
|
|
11
|
+
* Shared content vocabulary (item / entity type enums, `DefaultCyberiaItems`,
|
|
12
|
+
* `ENTITY_TYPE_TO_ITEM_TYPES`, quest step objectives) lives in
|
|
13
|
+
* `SharedDefaultsCyberia.js`.
|
|
32
14
|
*
|
|
33
15
|
* @module src/api/cyberia-server-defaults/cyberia-server-defaults.js
|
|
34
16
|
*/
|
|
35
17
|
|
|
36
|
-
//
|
|
37
|
-
// browser bundler can resolve the URL inside the client tree. Engine-side
|
|
38
|
-
// Node imports work from any path, so we reach into it from here.
|
|
18
|
+
// Shared vocabulary lives under src/client/ so the browser bundler resolves it.
|
|
39
19
|
import {
|
|
40
20
|
ITEM_TYPES,
|
|
21
|
+
STAT_TYPES,
|
|
22
|
+
STAT_MODIFIER_MAX,
|
|
23
|
+
ENTITY_LEVEL_MAX,
|
|
41
24
|
ENTITY_TYPES,
|
|
42
25
|
SKILL_LOGIC_ID_VALUES,
|
|
43
26
|
isCanonicalSkillLogicId,
|
|
27
|
+
AUDIO_BUSES,
|
|
28
|
+
AUDIO_BUS_MUSIC,
|
|
29
|
+
AUDIO_BUS_SFX,
|
|
30
|
+
AUDIO_LOGIC_ID_BUSES,
|
|
31
|
+
AUDIO_LOGIC_ID_VALUES,
|
|
32
|
+
isCanonicalAudioLogicId,
|
|
44
33
|
} from '../../client/components/cyberia/SharedDefaultsCyberia.js';
|
|
45
34
|
|
|
46
35
|
export const DOCKER_COMPOSE_ID = 'cyberia';
|
|
@@ -60,10 +49,9 @@ export const DEPLOY_ID = 'dd-cyberia';
|
|
|
60
49
|
* - `skills` (expanded metadata with name, description, summoned entity)
|
|
61
50
|
*
|
|
62
51
|
* Consumers:
|
|
63
|
-
* - `
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* `skills` metadata the instance-conf skillConfig schema drops)
|
|
52
|
+
* - `bin/cyberia.js seed-skills` (upsert into the cyberia-skill collection, which is the
|
|
53
|
+
* authoritative store — an instance then runs the subset its own content triggers)
|
|
54
|
+
* - the fallback world, which has no collection to read
|
|
67
55
|
*/
|
|
68
56
|
export const DefaultSkillConfig = [
|
|
69
57
|
{
|
|
@@ -92,18 +80,6 @@ export const DefaultSkillConfig = [
|
|
|
92
80
|
},
|
|
93
81
|
],
|
|
94
82
|
},
|
|
95
|
-
// {
|
|
96
|
-
// triggerItemId: 'anon',
|
|
97
|
-
// logicEventIds: ['doppelganger'],
|
|
98
|
-
// skills: [
|
|
99
|
-
// {
|
|
100
|
-
// logicEventId: 'doppelganger',
|
|
101
|
-
// name: 'Doppelganger',
|
|
102
|
-
// description: 'Summons a passive clone of yourself that wanders nearby. Spawn chance scales with Intelligence.',
|
|
103
|
-
// summonedEntityItemId: '$active_skin',
|
|
104
|
-
// },
|
|
105
|
-
// ],
|
|
106
|
-
// },
|
|
107
83
|
{
|
|
108
84
|
triggerItemId: 'hatchet',
|
|
109
85
|
logicEventIds: ['projectile'],
|
|
@@ -119,9 +95,7 @@ export const DefaultSkillConfig = [
|
|
|
119
95
|
},
|
|
120
96
|
];
|
|
121
97
|
|
|
122
|
-
//
|
|
123
|
-
// SharedDefaultsCyberia.js is the single source of truth, so a typo or a handler
|
|
124
|
-
// the dispatcher does not know must surface at boot, not as a silent no-op skill.
|
|
98
|
+
// A logicEventId absent from the SharedDefaultsCyberia registry fails at boot.
|
|
125
99
|
for (const cfg of DefaultSkillConfig) {
|
|
126
100
|
for (const logicEventId of [...(cfg.logicEventIds || []), ...(cfg.skills || []).map((sk) => sk.logicEventId)]) {
|
|
127
101
|
if (!isCanonicalSkillLogicId(logicEventId)) {
|
|
@@ -137,18 +111,11 @@ for (const cfg of DefaultSkillConfig) {
|
|
|
137
111
|
// Lain demo quests (testing only) — single source of truth
|
|
138
112
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
139
113
|
//
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
//
|
|
143
|
-
// These specs drive THREE derived structures so they can never drift apart:
|
|
144
|
-
// - DefaultCyberiaQuests — the quest definitions
|
|
145
|
-
// - DefaultCyberiaDialogues — one quest-talk dialogue per talk objective
|
|
146
|
-
// - DefaultCyberiaActions — Lain's questDialogueCodes mapping
|
|
114
|
+
// No-prerequisite quests offered by Lain (15,22). These specs derive the quest
|
|
115
|
+
// definitions, their quest-talk dialogues, and Lain's questDialogueCodes map.
|
|
147
116
|
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
// completed dialogue matches that mapping, so a quest without one could be
|
|
151
|
-
// satisfied by reading the NPC's default greeting.
|
|
117
|
+
// Each `talk` objective needs its own quest-talk dialogue on the provider's
|
|
118
|
+
// action. The server validates a talk only against that mapping.
|
|
152
119
|
|
|
153
120
|
const LAIN_DEMO_QUEST_SPECS = [
|
|
154
121
|
{
|
|
@@ -262,12 +229,7 @@ const LAIN_DEMO_TALK_SPECS = LAIN_DEMO_QUEST_SPECS.filter((q) =>
|
|
|
262
229
|
(q.steps || []).some((s) => (s.objectives || []).some((o) => o.type === 'talk' && o.itemId === 'lain')),
|
|
263
230
|
);
|
|
264
231
|
|
|
265
|
-
/**
|
|
266
|
-
* Default dialogue seeds. Mirrors `CyberiaDialogue` model schema:
|
|
267
|
-
* { code, order, speaker, text, mood }
|
|
268
|
-
*
|
|
269
|
-
* Used by the seed/migration script to populate Mongo on first boot.
|
|
270
|
-
*/
|
|
232
|
+
/** Dialogue seeds ({ code, order, speaker, text, mood }) the CLI writes to Mongo. */
|
|
271
233
|
export const DefaultCyberiaDialogues = [
|
|
272
234
|
{
|
|
273
235
|
code: 'default-coin',
|
|
@@ -527,12 +489,10 @@ export const DefaultCyberiaDialogues = [
|
|
|
527
489
|
* Each entry follows the `CyberiaAction` model schema.
|
|
528
490
|
*/
|
|
529
491
|
export const DefaultCyberiaActions = [
|
|
530
|
-
// An action
|
|
531
|
-
//
|
|
532
|
-
//
|
|
533
|
-
//
|
|
534
|
-
// handles to the dialogue shown for it (offer + talk-objective validation).
|
|
535
|
-
// The quests an NPC OFFERS are those whose source cell matches the action's.
|
|
492
|
+
// An action declares the capabilities available at a cell. `code` is a
|
|
493
|
+
// location slug, `label` the bot's overhead name, and the NPC skin comes from
|
|
494
|
+
// `dialogCode` (default-<skin>). `questDialogueCodes` maps a quest to its
|
|
495
|
+
// dialogue. An NPC offers the quests whose source cell matches the action's.
|
|
536
496
|
{
|
|
537
497
|
code: 'loc-fallback-map-0-12-10',
|
|
538
498
|
label: 'Wason',
|
|
@@ -566,9 +526,8 @@ export const DefaultCyberiaActions = [
|
|
|
566
526
|
questDialogueCodes: [{ questCode: 'bounty-quest-alpha', dialogCode: 'quest-talk-agent' }],
|
|
567
527
|
},
|
|
568
528
|
{
|
|
569
|
-
// Shop capability: an action
|
|
570
|
-
//
|
|
571
|
-
// against this catalog (price item + quantity).
|
|
529
|
+
// Shop capability: an action with shopItems is a vendor. The simulation
|
|
530
|
+
// validates every purchase against this catalog (price item + quantity).
|
|
572
531
|
code: 'loc-fallback-map-0-18-16',
|
|
573
532
|
label: 'Punk',
|
|
574
533
|
sourceMapCode: 'fallback-map-0',
|
|
@@ -578,12 +537,9 @@ export const DefaultCyberiaActions = [
|
|
|
578
537
|
shopItems: [{ itemId: 'tim-knife', priceItemId: 'coin', priceQty: 10 }],
|
|
579
538
|
},
|
|
580
539
|
{
|
|
581
|
-
// Assembler capability: an action
|
|
582
|
-
// terminal. The
|
|
583
|
-
//
|
|
584
|
-
// Every id here is obtainable inside the fallback world — wood from the
|
|
585
|
-
// resource nodes, coin from field drops — so the loop is playable on a
|
|
586
|
-
// fresh spawn with no seeding.
|
|
540
|
+
// Assembler capability: an action with craftRecipes is a fabrication
|
|
541
|
+
// terminal. The simulation validates every synthesis against these recipes.
|
|
542
|
+
// Every id here is obtainable in the fallback world with no seeding.
|
|
587
543
|
code: 'loc-fallback-map-0-15-16',
|
|
588
544
|
label: 'Eiri',
|
|
589
545
|
sourceMapCode: 'fallback-map-0',
|
|
@@ -609,10 +565,7 @@ export const DefaultCyberiaActions = [
|
|
|
609
565
|
craftTimeMs: 5000,
|
|
610
566
|
},
|
|
611
567
|
{
|
|
612
|
-
// Multi-output: one blade breaks down into three
|
|
613
|
-
// the assembly UI and the arrival flights are exercised with more than
|
|
614
|
-
// a single result slot. Closes the loop with Punk's shop — buy a knife,
|
|
615
|
-
// salvage it back into parts.
|
|
568
|
+
// Multi-output salvage recipe: one blade breaks down into three stacks.
|
|
616
569
|
outputItems: [
|
|
617
570
|
{ itemId: 'wood-drop-1', qty: 2 },
|
|
618
571
|
{ itemId: 'wood-drop-2', qty: 1 },
|
|
@@ -624,9 +577,8 @@ export const DefaultCyberiaActions = [
|
|
|
624
577
|
],
|
|
625
578
|
},
|
|
626
579
|
{
|
|
627
|
-
// Storage capability: an action
|
|
628
|
-
//
|
|
629
|
-
// capacity (25 → 5x5) and the simulation owns the contents.
|
|
580
|
+
// Storage capability: an action with storageSlots is a personal vault. The
|
|
581
|
+
// simulation owns the contents; the slot count sizes the grid (25 → 5x5).
|
|
630
582
|
code: 'loc-fallback-map-0-12-22',
|
|
631
583
|
label: 'Kaneki',
|
|
632
584
|
sourceMapCode: 'fallback-map-0',
|
|
@@ -642,9 +594,7 @@ export const DefaultCyberiaActions = [
|
|
|
642
594
|
sourceCellX: 15,
|
|
643
595
|
sourceCellY: 22,
|
|
644
596
|
dialogCode: 'default-lain',
|
|
645
|
-
//
|
|
646
|
-
// dialogue here — without the mapping the server would accept the
|
|
647
|
-
// default greeting as the talk, and no quest-talk button would show.
|
|
597
|
+
// One quest-talk dialogue per demo quest that carries a `talk` objective.
|
|
648
598
|
questDialogueCodes: LAIN_DEMO_TALK_SPECS.map((q) => ({
|
|
649
599
|
questCode: lainDemoQuestCode(q.suffix),
|
|
650
600
|
dialogCode: lainDemoTalkDialogCode(q.suffix),
|
|
@@ -687,8 +637,7 @@ export const DefaultCyberiaQuests = [
|
|
|
687
637
|
rewards: [{ itemId: 'coin', quantity: 50 }],
|
|
688
638
|
},
|
|
689
639
|
{
|
|
690
|
-
// Parallel initial mission
|
|
691
|
-
// prerequisites, so the player can accept it alongside the intro quest.
|
|
640
|
+
// Parallel initial mission: same source cell as the intro quest, no prerequisites.
|
|
692
641
|
code: 'wason-errand',
|
|
693
642
|
title: "Wason's Errand",
|
|
694
643
|
description: 'Gather coins from the field and bring them back to Wason.',
|
|
@@ -741,8 +690,7 @@ export const DefaultCyberiaQuests = [
|
|
|
741
690
|
rewards: [{ itemId: 'hatchet', quantity: 1 }],
|
|
742
691
|
},
|
|
743
692
|
// ── Demo quests (testing only) ─────────────────────────────────────────────
|
|
744
|
-
//
|
|
745
|
-
// quest-talk dialogues, and Lain's questDialogueCodes mapping stay in sync.
|
|
693
|
+
// Derived from LAIN_DEMO_QUEST_SPECS.
|
|
746
694
|
...LAIN_DEMO_QUEST_SPECS.map((q) => ({
|
|
747
695
|
code: lainDemoQuestCode(q.suffix),
|
|
748
696
|
title: q.title,
|
|
@@ -762,11 +710,9 @@ export const DefaultCyberiaQuests = [
|
|
|
762
710
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
763
711
|
|
|
764
712
|
/**
|
|
765
|
-
*
|
|
766
|
-
*
|
|
767
|
-
*
|
|
768
|
-
* of the entity's current state: liveItemIds while alive, deadItemIds while
|
|
769
|
-
* in the Fragmented State (see DEFAULT_DEAD_ITEM_ID).
|
|
713
|
+
* Which ObjectLayer item types may be active together on an entity. The server
|
|
714
|
+
* validates every item_activation against these rules, scoped to the item set
|
|
715
|
+
* of the entity state: liveItemIds while alive, deadItemIds while dead.
|
|
770
716
|
*/
|
|
771
717
|
export const EQUIPMENT_RULES_DEFAULTS = Object.freeze({
|
|
772
718
|
activeItemTypes: [ITEM_TYPES.skin, ITEM_TYPES.breastplate, ITEM_TYPES.weapon],
|
|
@@ -779,13 +725,10 @@ export const EQUIPMENT_RULES_DEFAULTS = Object.freeze({
|
|
|
779
725
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
780
726
|
|
|
781
727
|
/**
|
|
782
|
-
* Numeric Entity Status Indicator (ESI) IDs.
|
|
783
|
-
*
|
|
784
|
-
*
|
|
785
|
-
*
|
|
786
|
-
*
|
|
787
|
-
* IDs MUST stay in sync with:
|
|
788
|
-
* cyberia-server/src/entity_status.go (StatusNone … StatusResourceExtracted)
|
|
728
|
+
* Numeric Entity Status Indicator (ESI) IDs. The server stamps one u8 ID on
|
|
729
|
+
* every entity in the AOI wire format; the client resolves the icon from
|
|
730
|
+
* `SharedDefaultsCyberia.js#STATUS_ICONS_PRESENTATION`. These IDs are wire
|
|
731
|
+
* contract: append only, never renumber.
|
|
789
732
|
*/
|
|
790
733
|
export const STATUS_ICONS = Object.freeze([
|
|
791
734
|
{ id: 0, name: 'none', description: 'No icon (skill/coin bots, world objects)' },
|
|
@@ -836,14 +779,14 @@ export const RESOURCE_ENTITY_TYPE_DEFAULTS = Object.freeze([
|
|
|
836
779
|
liveItemIds: ['wood-1'],
|
|
837
780
|
deadItemIds: ['wood-extracted-1'],
|
|
838
781
|
dropItemIds: ['wood-drop-1'],
|
|
839
|
-
|
|
782
|
+
inventoryItemsIds: [],
|
|
840
783
|
}),
|
|
841
784
|
Object.freeze({
|
|
842
785
|
entityType: ENTITY_TYPES.resource,
|
|
843
786
|
liveItemIds: ['wood-2'],
|
|
844
787
|
deadItemIds: ['wood-extracted-2'],
|
|
845
788
|
dropItemIds: ['wood-drop-2'],
|
|
846
|
-
|
|
789
|
+
inventoryItemsIds: [],
|
|
847
790
|
}),
|
|
848
791
|
]);
|
|
849
792
|
|
|
@@ -851,63 +794,51 @@ export const RESOURCE_ENTITY_TYPE_DEFAULTS = Object.freeze([
|
|
|
851
794
|
export const RESOURCE_ENTITY_TYPE_DEFAULT = RESOURCE_ENTITY_TYPE_DEFAULTS[0];
|
|
852
795
|
|
|
853
796
|
/**
|
|
854
|
-
* Canonical dead-state visual (Fragmentation)
|
|
855
|
-
*
|
|
856
|
-
*
|
|
857
|
-
*
|
|
858
|
-
* EQUIPMENT_RULES_DEFAULTS scoped to the dead-item set, and remembered across
|
|
859
|
-
* deaths. Only this default id is excluded from the player inventory wire;
|
|
860
|
-
* the rest appear (lock-badged while alive). No defaultObjectLayers row is
|
|
861
|
-
* needed — the death handler appends missing dead items on demand.
|
|
862
|
-
* Mirrored by `defaultDeadItemID` in cyberia-server/game/dead_items.go.
|
|
797
|
+
* Canonical dead-state visual (Fragmentation). The server applies it when an
|
|
798
|
+
* entity-type default declares no deadItemIds. Dead items are equippable only
|
|
799
|
+
* while the entity is dead and are kept across deaths. This id stays out of
|
|
800
|
+
* the player inventory wire; other dead items appear in it.
|
|
863
801
|
*/
|
|
864
802
|
export const DEFAULT_DEAD_ITEM_ID = 'fragmentation';
|
|
865
803
|
|
|
866
804
|
/**
|
|
867
|
-
* Per-entity-type defaults
|
|
868
|
-
*
|
|
805
|
+
* Per-entity-type defaults the simulation consumes: live / dead / drop item IDs
|
|
806
|
+
* and the seed inventory of a new entity.
|
|
869
807
|
*
|
|
870
808
|
* Field reference:
|
|
871
|
-
* entityType
|
|
872
|
-
* liveItemIds
|
|
873
|
-
*
|
|
874
|
-
* dropItemIds
|
|
875
|
-
*
|
|
809
|
+
* entityType — server-side category string.
|
|
810
|
+
* liveItemIds — ObjectLayer item IDs while the entity is alive.
|
|
811
|
+
* deadItemIDs — IDs swapped in on death / ghost state.
|
|
812
|
+
* dropItemIds — IDs granted to the killer on resource depletion.
|
|
813
|
+
* inventoryItemsIds — IDs the entity carries but never activates by lifecycle.
|
|
814
|
+
* overrideItemsIdsState — per-id overrides of what those lists derive.
|
|
815
|
+
*
|
|
816
|
+
* The three lifecycle lists are discriminators, not separate inventories: an entity holds one
|
|
817
|
+
* inventory and the runtime activates the slots the context calls for. `inventoryItemsIds` is only
|
|
818
|
+
* for what no lifecycle state ever activates — a coin balance, say. See
|
|
819
|
+
* {@link resolveEntityInventory}, which is where that single inventory is derived.
|
|
876
820
|
*/
|
|
877
821
|
export const ENTITY_TYPE_DEFAULTS = Object.freeze([
|
|
878
822
|
{
|
|
879
823
|
entityType: ENTITY_TYPES.player,
|
|
880
824
|
liveItemIds: ['anon', 'atlas_pistol_mk2'],
|
|
881
825
|
deadItemIds: [DEFAULT_DEAD_ITEM_ID],
|
|
882
|
-
|
|
883
|
-
{ itemId: 'anon', active: true, quantity: 1 },
|
|
884
|
-
{ itemId: 'atlas_pistol_mk2', active: true, quantity: 1 },
|
|
885
|
-
{ itemId: 'coin', active: false, quantity: 0 },
|
|
886
|
-
],
|
|
826
|
+
inventoryItemsIds: ['coin'],
|
|
887
827
|
},
|
|
888
828
|
{
|
|
889
829
|
entityType: ENTITY_TYPES.other_player,
|
|
890
830
|
liveItemIds: ['anon', 'atlas_pistol_mk2'],
|
|
891
831
|
deadItemIds: [DEFAULT_DEAD_ITEM_ID],
|
|
892
|
-
|
|
893
|
-
{ itemId: 'anon', active: true, quantity: 1 },
|
|
894
|
-
{ itemId: 'atlas_pistol_mk2', active: true, quantity: 1 },
|
|
895
|
-
{ itemId: 'coin', active: false, quantity: 0 },
|
|
896
|
-
],
|
|
832
|
+
inventoryItemsIds: ['coin'],
|
|
897
833
|
},
|
|
898
834
|
{
|
|
899
835
|
entityType: ENTITY_TYPES.bot,
|
|
900
836
|
liveItemIds: ['purple'],
|
|
901
837
|
deadItemIds: [DEFAULT_DEAD_ITEM_ID],
|
|
902
|
-
|
|
903
|
-
{ itemId: 'purple', active: true, quantity: 1 },
|
|
904
|
-
{ itemId: 'coin', active: false, quantity: 0 },
|
|
905
|
-
],
|
|
838
|
+
inventoryItemsIds: ['coin'],
|
|
906
839
|
},
|
|
907
|
-
// Fallback-world mission
|
|
908
|
-
//
|
|
909
|
-
// barely move from their spawn and are immortal. Lain only talks in place, so
|
|
910
|
-
// she is fully static. Authors can retarget any of these via EntityEngineCyberia.
|
|
840
|
+
// Fallback-world mission givers, keyed by their active skin. `provider` bots
|
|
841
|
+
// stay near their spawn and are immortal; `provider-static` bots do not move.
|
|
911
842
|
{ entityType: ENTITY_TYPES.bot, liveItemIds: ['wason'], deadItemIds: [DEFAULT_DEAD_ITEM_ID], behavior: 'provider' },
|
|
912
843
|
{ entityType: ENTITY_TYPES.bot, liveItemIds: ['alex'], deadItemIds: [DEFAULT_DEAD_ITEM_ID], behavior: 'provider' },
|
|
913
844
|
{ entityType: ENTITY_TYPES.bot, liveItemIds: ['agent'], deadItemIds: [DEFAULT_DEAD_ITEM_ID], behavior: 'provider' },
|
|
@@ -934,37 +865,277 @@ export const ENTITY_TYPE_DEFAULTS = Object.freeze([
|
|
|
934
865
|
entityType: ENTITY_TYPES.skill,
|
|
935
866
|
liveItemIds: ['atlas_pistol_mk2_bullet'],
|
|
936
867
|
deadItemIds: [],
|
|
937
|
-
|
|
868
|
+
inventoryItemsIds: [],
|
|
938
869
|
},
|
|
939
870
|
{
|
|
940
871
|
entityType: ENTITY_TYPES.coin,
|
|
941
872
|
liveItemIds: ['coin'],
|
|
942
873
|
deadItemIds: [],
|
|
943
|
-
|
|
874
|
+
inventoryItemsIds: [],
|
|
944
875
|
},
|
|
945
|
-
{ entityType: ENTITY_TYPES.floor, liveItemIds: ['grass'], deadItemIds: [], dropItemIds: [],
|
|
946
|
-
{ entityType: ENTITY_TYPES.obstacle, liveItemIds: [], deadItemIds: [], dropItemIds: [],
|
|
947
|
-
{ entityType: ENTITY_TYPES.portal, liveItemIds: [], deadItemIds: [], dropItemIds: [],
|
|
948
|
-
{ entityType: ENTITY_TYPES.foreground, liveItemIds: [], deadItemIds: [], dropItemIds: [],
|
|
876
|
+
{ entityType: ENTITY_TYPES.floor, liveItemIds: ['grass'], deadItemIds: [], dropItemIds: [], inventoryItemsIds: [] },
|
|
877
|
+
{ entityType: ENTITY_TYPES.obstacle, liveItemIds: [], deadItemIds: [], dropItemIds: [], inventoryItemsIds: [] },
|
|
878
|
+
{ entityType: ENTITY_TYPES.portal, liveItemIds: [], deadItemIds: [], dropItemIds: [], inventoryItemsIds: [] },
|
|
879
|
+
{ entityType: ENTITY_TYPES.foreground, liveItemIds: [], deadItemIds: [], dropItemIds: [], inventoryItemsIds: [] },
|
|
949
880
|
// Static decorator — non-moving, passable, depth-sorted. Visuals come from the
|
|
950
881
|
// map definition / world generator; no live/dead/drop rotation.
|
|
951
|
-
{ entityType: ENTITY_TYPES.static, liveItemIds: [], deadItemIds: [], dropItemIds: [],
|
|
882
|
+
{ entityType: ENTITY_TYPES.static, liveItemIds: [], deadItemIds: [], dropItemIds: [], inventoryItemsIds: [] },
|
|
952
883
|
...RESOURCE_ENTITY_TYPE_DEFAULTS,
|
|
953
884
|
]);
|
|
954
885
|
|
|
886
|
+
/**
|
|
887
|
+
* The default a placed entity resolves to, by the item ids it already carries.
|
|
888
|
+
*
|
|
889
|
+
* Mirrors `resolveEntityDefaultBuild` in cyberia-server/game/entity_defaults.go, which is the rule
|
|
890
|
+
* the simulation applies at spawn: a default matches only when the entity carries ALL of its live
|
|
891
|
+
* ids, and the most specific match wins — the one requiring the largest set, so `{purple, pistol}`
|
|
892
|
+
* beats `{purple}`. Ties keep the earlier default, which makes the order a world declares them in
|
|
893
|
+
* significant. With no containing match, the first default of that type answers.
|
|
894
|
+
*
|
|
895
|
+
* @param {{entityType: string, itemIds?: string[]}} entity - Placed entity type and its item ids.
|
|
896
|
+
* @param {Array<{entityType: string, liveItemIds?: string[]}>} defaults - Candidate defaults, in order.
|
|
897
|
+
* @returns {object|undefined} The matched default, or undefined when the type has none.
|
|
898
|
+
*/
|
|
899
|
+
export function resolveEntityDefaultBuild({ entityType, itemIds = [] }, defaults = []) {
|
|
900
|
+
const carried = new Set(itemIds.filter(Boolean));
|
|
901
|
+
let firstOfType;
|
|
902
|
+
let matched;
|
|
903
|
+
let matchedSize = 0;
|
|
904
|
+
for (const candidate of defaults) {
|
|
905
|
+
if (candidate?.entityType !== entityType) continue;
|
|
906
|
+
firstOfType ??= candidate;
|
|
907
|
+
const liveItemIds = candidate.liveItemIds || [];
|
|
908
|
+
if (carried.size === 0 || liveItemIds.length === 0) continue;
|
|
909
|
+
if (!liveItemIds.every((itemId) => carried.has(itemId))) continue;
|
|
910
|
+
if (liveItemIds.length > matchedSize) {
|
|
911
|
+
matched = candidate;
|
|
912
|
+
matchedSize = liveItemIds.length;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
return matched ?? firstOfType;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* The canonical inventory contract: one entity, one inventory, derived.
|
|
920
|
+
*
|
|
921
|
+
* An entity carries the union of every item id its default names — the lifecycle discriminators
|
|
922
|
+
* (live / dead / drop) and the inventory-only extras — deduplicated, in that order. Which slots
|
|
923
|
+
* are *active* is not stored anywhere: it follows from the discriminator the id belongs to, and
|
|
924
|
+
* the runtime flips them as context changes (`activateOrAppendLayer` in
|
|
925
|
+
* cyberia-server/game/dead_items.go activates the slot that is already there). Seeding the whole
|
|
926
|
+
* union is what lets it activate rather than append.
|
|
927
|
+
*
|
|
928
|
+
* Spawn state is alive, so the live ids are the active ones. A worn item and an inventory-only
|
|
929
|
+
* item are stock the entity holds, so each starts as one unit. A dead or drop id is a lifecycle
|
|
930
|
+
* slot the runtime activates or scatters later, so it starts empty. The server owns the coin
|
|
931
|
+
* quantity from spawn on, whatever the seed says.
|
|
932
|
+
*
|
|
933
|
+
* `overrideItemsIdsState` adjusts that derivation for one carried id: `active` forces the spawn
|
|
934
|
+
* state — a skin the equipment rules would otherwise leave inactive — and `quantity` sizes a
|
|
935
|
+
* stack, which is how a drop bundle declares how many it scatters. It never adds an id: the union
|
|
936
|
+
* decides membership, an override only what a member starts as, so a rule naming an id no list
|
|
937
|
+
* carries does nothing at all.
|
|
938
|
+
*
|
|
939
|
+
* Activating one item can deactivate another. `EQUIPMENT_RULES_DEFAULTS` allows one active item
|
|
940
|
+
* per type, so an override that activates a skin is a decision about which skin the entity wears:
|
|
941
|
+
* the one the lists derived gives way to the one the author named. Only the governed types are
|
|
942
|
+
* constrained, and only where the item's type is known — a resource visual, a coin, anything the
|
|
943
|
+
* rules do not name, passes through untouched.
|
|
944
|
+
*
|
|
945
|
+
* @param {{liveItemIds?:string[],deadItemIds?:string[],dropItemIds?:string[],inventoryItemsIds?:string[],
|
|
946
|
+
* overrideItemsIdsState?:Array<{itemId:string,active?:boolean,quantity?:number}>}} entityDefault
|
|
947
|
+
* @param {{itemTypes?:Object<string,string>|Map<string,string>}} [context] - itemId → item type, where known.
|
|
948
|
+
* @returns {Array<{itemId:string,active:boolean,quantity:number}>} Inventory rows, as the wire carries them.
|
|
949
|
+
*/
|
|
950
|
+
export function resolveEntityInventory(entityDefault = {}, { itemTypes } = {}) {
|
|
951
|
+
const live = new Set(entityDefault.liveItemIds || []);
|
|
952
|
+
const overrides = new Map(
|
|
953
|
+
(entityDefault.overrideItemsIdsState || []).filter((rule) => rule?.itemId).map((rule) => [rule.itemId, rule]),
|
|
954
|
+
);
|
|
955
|
+
const itemIds = [
|
|
956
|
+
...new Set(
|
|
957
|
+
[
|
|
958
|
+
...(entityDefault.liveItemIds || []),
|
|
959
|
+
...(entityDefault.deadItemIds || []),
|
|
960
|
+
...(entityDefault.dropItemIds || []),
|
|
961
|
+
...(entityDefault.inventoryItemsIds || []),
|
|
962
|
+
].filter(Boolean),
|
|
963
|
+
),
|
|
964
|
+
];
|
|
965
|
+
const drops = new Set(entityDefault.dropItemIds || []);
|
|
966
|
+
const stock = new Set(entityDefault.inventoryItemsIds || []);
|
|
967
|
+
const rows = itemIds.map((itemId) => {
|
|
968
|
+
const override = overrides.get(itemId);
|
|
969
|
+
const active = 'boolean' === typeof override?.active ? override.active : live.has(itemId);
|
|
970
|
+
const held = active || stock.has(itemId);
|
|
971
|
+
const quantity = Number.isFinite(override?.quantity) ? override.quantity : held ? 1 : 0;
|
|
972
|
+
// A drop id scatters on death unless an override says how often. Rows that are not drops
|
|
973
|
+
// carry the same 1, so every consumer reads one field and never a missing one.
|
|
974
|
+
const dropChance =
|
|
975
|
+
drops.has(itemId) && Number.isFinite(override?.dropChance)
|
|
976
|
+
? Math.min(1, Math.max(0, override.dropChance))
|
|
977
|
+
: 1;
|
|
978
|
+
return { itemId, active, quantity, dropChance };
|
|
979
|
+
});
|
|
980
|
+
return applyEquipmentRules(rows, { itemTypes, overrides });
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
/**
|
|
984
|
+
* One active item per governed type, with the authored choice ahead of the derived one.
|
|
985
|
+
*
|
|
986
|
+
* An override that activates an item states which item of its type the entity wears; a row the
|
|
987
|
+
* lists merely derived yields to it. Two overrides of the same type resolve in list order, first
|
|
988
|
+
* kept — the same first-wins the runtime applies when it normalizes a loadout.
|
|
989
|
+
*
|
|
990
|
+
* @param {Array<{itemId:string,active:boolean,quantity:number}>} rows - Resolved inventory rows.
|
|
991
|
+
* @param {{itemTypes?:Object<string,string>|Map<string,string>, overrides?:Map<string,object>}} context
|
|
992
|
+
* @returns {Array<{itemId:string,active:boolean,quantity:number}>} The same rows, contested types settled.
|
|
993
|
+
*/
|
|
994
|
+
function applyEquipmentRules(rows, { itemTypes, overrides = new Map() } = {}) {
|
|
995
|
+
if (!itemTypes || !EQUIPMENT_RULES_DEFAULTS.onePerType) return rows;
|
|
996
|
+
const typeOf = (itemId) => (itemTypes instanceof Map ? itemTypes.get(itemId) : itemTypes[itemId]) || '';
|
|
997
|
+
const governed = new Set(EQUIPMENT_RULES_DEFAULTS.activeItemTypes);
|
|
998
|
+
const claimed = new Set();
|
|
999
|
+
// Authored first, so an override wins the slot it asks for; the derived rows fill what is left.
|
|
1000
|
+
const contenders = [
|
|
1001
|
+
...rows.filter((row) => row.active && overrides.get(row.itemId)?.active === true),
|
|
1002
|
+
...rows.filter((row) => row.active && overrides.get(row.itemId)?.active !== true),
|
|
1003
|
+
];
|
|
1004
|
+
const deactivated = new Set();
|
|
1005
|
+
for (const row of contenders) {
|
|
1006
|
+
const type = typeOf(row.itemId);
|
|
1007
|
+
if (!governed.has(type)) continue;
|
|
1008
|
+
if (claimed.has(type)) deactivated.add(row.itemId);
|
|
1009
|
+
else claimed.add(type);
|
|
1010
|
+
}
|
|
1011
|
+
// Taking something off does not take it away: the entity still carries the stack, so only the
|
|
1012
|
+
// worn flag changes. A row emptied here would leave the inventory holding nothing.
|
|
1013
|
+
return rows.map((row) => (deactivated.has(row.itemId) ? { ...row, active: false } : row));
|
|
1014
|
+
}
|
|
1015
|
+
|
|
955
1016
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
956
|
-
//
|
|
1017
|
+
// Audio seed content
|
|
957
1018
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
958
1019
|
|
|
959
1020
|
/**
|
|
960
|
-
*
|
|
961
|
-
*
|
|
962
|
-
*
|
|
963
|
-
*
|
|
964
|
-
*
|
|
1021
|
+
* The audio bank the seeded world needs: one `cyberia-audio` module per asset code.
|
|
1022
|
+
*
|
|
1023
|
+
* `bus` names the module directory (`cyberia-audio/src/audio-module/<bus-id>/<code>`) and is the
|
|
1024
|
+
* asset's natural route; `options` are that module's render parameters. The code is also the
|
|
1025
|
+
* identity the client fetches by, so a code here must exist as a module there.
|
|
1026
|
+
*
|
|
1027
|
+
* @type {ReadonlyArray<{code:string,bus:'music'|'sfx',options?:object}>}
|
|
1028
|
+
*/
|
|
1029
|
+
export const DEFAULT_AUDIO_BANK = Object.freeze([
|
|
1030
|
+
...['shoot', 'coin', 'drop', 'item-pickup', 'victory', 'level-up', 'death', 'heal', 'hit', 'portal', 'ui-click', 'footsteps'].map(
|
|
1031
|
+
(code) => Object.freeze({ code, bus: AUDIO_BUS_SFX }),
|
|
1032
|
+
),
|
|
1033
|
+
Object.freeze({ code: 'exploration', bus: AUDIO_BUS_MUSIC, options: { cycles: 1 } }),
|
|
1034
|
+
Object.freeze({ code: 'combat', bus: AUDIO_BUS_MUSIC, options: { rounds: 1 } }),
|
|
1035
|
+
Object.freeze({ code: 'boss', bus: AUDIO_BUS_MUSIC }),
|
|
1036
|
+
Object.freeze({ code: 'portal-cooldown', bus: AUDIO_BUS_MUSIC }),
|
|
1037
|
+
Object.freeze({ code: 'craft', bus: AUDIO_BUS_MUSIC }),
|
|
1038
|
+
]);
|
|
1039
|
+
|
|
1040
|
+
/** Map-level audio settings the seed writes onto every seeded map. */
|
|
1041
|
+
export const DEFAULT_AUDIO_SETTINGS = Object.freeze({ volume: 0.8, crossfadeMs: 800 });
|
|
1042
|
+
|
|
1043
|
+
/**
|
|
1044
|
+
* The music bed a seeded map falls back to when no event is holding the bus.
|
|
1045
|
+
*
|
|
1046
|
+
* One bed for every map of a world: what a map sounds like when nothing is happening is the
|
|
1047
|
+
* world's ambient identity, and the events are what make a place sound different — a combat or a
|
|
1048
|
+
* boss bed is bound to the moment it belongs to, not to a map index.
|
|
1049
|
+
*/
|
|
1050
|
+
export const DEFAULT_MAP_MUSIC = 'exploration';
|
|
1051
|
+
|
|
1052
|
+
/**
|
|
1053
|
+
* Canonical runtime-event → asset bindings.
|
|
1054
|
+
*
|
|
1055
|
+
* `logicEventId` is the semantic event the client emits (an audio LogicId, or a skill LogicId the
|
|
1056
|
+
* dispatcher runs); `audioCode` is the bank entry that answers it.
|
|
965
1057
|
*
|
|
966
|
-
*
|
|
967
|
-
|
|
1058
|
+
* @type {ReadonlyArray<{logicEventId:string,audioCode:string}>}
|
|
1059
|
+
*/
|
|
1060
|
+
export const DEFAULT_AUDIO_BINDINGS = Object.freeze([
|
|
1061
|
+
Object.freeze({ logicEventId: 'projectile', audioCode: 'shoot' }),
|
|
1062
|
+
Object.freeze({ logicEventId: 'coin_drop_or_transaction', audioCode: 'coin' }),
|
|
1063
|
+
Object.freeze({ logicEventId: 'drop', audioCode: 'drop' }),
|
|
1064
|
+
Object.freeze({ logicEventId: 'item-pickup', audioCode: 'item-pickup' }),
|
|
1065
|
+
Object.freeze({ logicEventId: 'craft', audioCode: 'craft' }),
|
|
1066
|
+
Object.freeze({ logicEventId: 'heal', audioCode: 'heal' }),
|
|
1067
|
+
Object.freeze({ logicEventId: 'hit', audioCode: 'hit' }),
|
|
1068
|
+
Object.freeze({ logicEventId: 'portal', audioCode: 'portal' }),
|
|
1069
|
+
Object.freeze({ logicEventId: 'ui-click', audioCode: 'ui-click' }),
|
|
1070
|
+
Object.freeze({ logicEventId: 'footsteps', audioCode: 'footsteps' }),
|
|
1071
|
+
Object.freeze({ logicEventId: 'combat', audioCode: 'combat' }),
|
|
1072
|
+
Object.freeze({ logicEventId: 'boss', audioCode: 'boss' }),
|
|
1073
|
+
Object.freeze({ logicEventId: 'victory', audioCode: 'victory' }),
|
|
1074
|
+
Object.freeze({ logicEventId: 'level-up', audioCode: 'level-up' }),
|
|
1075
|
+
Object.freeze({ logicEventId: 'death', audioCode: 'death' }),
|
|
1076
|
+
Object.freeze({ logicEventId: 'portal-cooldown', audioCode: 'portal-cooldown' }),
|
|
1077
|
+
]);
|
|
1078
|
+
|
|
1079
|
+
const AUDIO_BANK_CODES = DEFAULT_AUDIO_BANK.map(({ code }) => code);
|
|
1080
|
+
|
|
1081
|
+
for (const { code, bus } of DEFAULT_AUDIO_BANK) {
|
|
1082
|
+
if (!AUDIO_BUSES.includes(bus)) {
|
|
1083
|
+
throw new Error(`DEFAULT_AUDIO_BANK: "${code}" names bus "${bus}". Allowed: ${AUDIO_BUSES.join(', ')}`);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// Fail fast on an audio binding the runtime cannot honour: an unknown event would never fire, and
|
|
1088
|
+
// an unbanked code would leave the client requesting an asset the seed never imported. Both read
|
|
1089
|
+
// as silence at play time, so they must surface at boot instead.
|
|
1090
|
+
for (const { logicEventId, audioCode } of DEFAULT_AUDIO_BINDINGS) {
|
|
1091
|
+
if (!isCanonicalAudioLogicId(logicEventId)) {
|
|
1092
|
+
throw new Error(
|
|
1093
|
+
`DEFAULT_AUDIO_BINDINGS: unknown logicEventId "${logicEventId}". ` +
|
|
1094
|
+
`Allowed (SharedDefaultsCyberia): ${[...AUDIO_LOGIC_ID_VALUES, ...SKILL_LOGIC_ID_VALUES].join(', ')}`,
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
if (!AUDIO_BANK_CODES.includes(audioCode)) {
|
|
1098
|
+
throw new Error(
|
|
1099
|
+
`DEFAULT_AUDIO_BINDINGS: "${logicEventId}" binds audioCode "${audioCode}", ` +
|
|
1100
|
+
`absent from DEFAULT_AUDIO_BANK: ${AUDIO_BANK_CODES.join(', ')}`,
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
if (!AUDIO_BANK_CODES.includes(DEFAULT_MAP_MUSIC)) {
|
|
1105
|
+
throw new Error(`Default map music "${DEFAULT_MAP_MUSIC}" is absent from DEFAULT_AUDIO_BANK`);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
/**
|
|
1109
|
+
* Expands {@link DEFAULT_AUDIO_BINDINGS} into `cyberia-map-audio-conf` event records.
|
|
1110
|
+
*
|
|
1111
|
+
* Routing is derived, not restated: an event's bus comes from the canonical audio registry
|
|
1112
|
+
* (a skill LogicId is a one-shot, so it falls back to sfx), music holds the bed in a loop unless
|
|
1113
|
+
* the binding is a one-shot cue, and only music crossfades.
|
|
1114
|
+
*
|
|
1115
|
+
* @param {{volume?:number,crossfadeMs?:number}} [settings=DEFAULT_AUDIO_SETTINGS]
|
|
1116
|
+
* @returns {Array<{logicEventId:string,audioCode:string,settings:object}>}
|
|
1117
|
+
*/
|
|
1118
|
+
export function buildAudioEventBindings(settings = DEFAULT_AUDIO_SETTINGS) {
|
|
1119
|
+
const { volume, crossfadeMs } = { ...DEFAULT_AUDIO_SETTINGS, ...settings };
|
|
1120
|
+
return DEFAULT_AUDIO_BINDINGS.map(({ logicEventId, audioCode }) => {
|
|
1121
|
+
const bus = AUDIO_LOGIC_ID_BUSES[logicEventId] ?? AUDIO_BUS_SFX;
|
|
1122
|
+
const music = AUDIO_BUS_MUSIC === bus;
|
|
1123
|
+
return {
|
|
1124
|
+
logicEventId,
|
|
1125
|
+
audioCode,
|
|
1126
|
+
settings: { bus, volume, loop: music, crossfadeMs: music ? crossfadeMs : 0 },
|
|
1127
|
+
};
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1132
|
+
// Player spawn
|
|
1133
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1134
|
+
|
|
1135
|
+
/**
|
|
1136
|
+
* Default player spawn policy, the shape of PlayerSpawnSchema. With `random`
|
|
1137
|
+
* false and `sourceMapCode` naming a loaded map, players spawn at
|
|
1138
|
+
* (sourceCellX, sourceCellY). Otherwise they spawn on a random walkable cell.
|
|
968
1139
|
*/
|
|
969
1140
|
export const DEFAULT_PLAYER_SPAWN = Object.freeze({
|
|
970
1141
|
sourceMapCode: 'fallback-map-0',
|
|
@@ -986,6 +1157,58 @@ export const DEFAULT_PLAYER_SPAWN = Object.freeze({
|
|
|
986
1157
|
* forbidden — see `SharedDefaultsCyberia.js` and the
|
|
987
1158
|
* `/api/cyberia-client-hints` REST endpoint for presentation overrides.
|
|
988
1159
|
*/
|
|
1160
|
+
export const PROGRESSION_RULES_DEFAULTS = Object.freeze({
|
|
1161
|
+
maxLevel: 100,
|
|
1162
|
+
xpPerLevel: 100,
|
|
1163
|
+
baseStats: Object.freeze({ effect: 5, resistance: 10, agility: 0, range: 0, intelligence: 0, utility: 0 }),
|
|
1164
|
+
perLevelStats: Object.freeze({ effect: 2, resistance: 5, agility: 1, range: 10, intelligence: 1, utility: 1 }),
|
|
1165
|
+
killXp: 25,
|
|
1166
|
+
questXp: 100,
|
|
1167
|
+
objectiveXp: 10,
|
|
1168
|
+
minAwardIntervalMs: 500,
|
|
1169
|
+
repeatWindowMs: 60000,
|
|
1170
|
+
maxRepeatAwards: 4,
|
|
1171
|
+
maxAwardsPerWindow: 30,
|
|
1172
|
+
defaultBotLevel: 1,
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
export const PROGRESSION_RULE_LIMITS = Object.freeze(Object.fromEntries(Object.entries({
|
|
1176
|
+
maxLevel: [1, ENTITY_LEVEL_MAX], xpPerLevel: [1, 1000000],
|
|
1177
|
+
killXp: [0, 1000000], questXp: [0, 1000000], objectiveXp: [0, 1000000],
|
|
1178
|
+
minAwardIntervalMs: [1, 60000], repeatWindowMs: [1000, 3600000],
|
|
1179
|
+
maxRepeatAwards: [1, 100], maxAwardsPerWindow: [1, 1000], defaultBotLevel: [1, ENTITY_LEVEL_MAX],
|
|
1180
|
+
}).map(([key, bounds]) => [key, Object.freeze(bounds)])));
|
|
1181
|
+
|
|
1182
|
+
export function resolveProgressionRules(input = {}) {
|
|
1183
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) throw new TypeError('Progression rules must be an object.');
|
|
1184
|
+
const defaults = PROGRESSION_RULES_DEFAULTS;
|
|
1185
|
+
const rules = {
|
|
1186
|
+
...defaults, ...input,
|
|
1187
|
+
baseStats: { ...defaults.baseStats, ...input.baseStats },
|
|
1188
|
+
perLevelStats: { ...defaults.perLevelStats, ...input.perLevelStats },
|
|
1189
|
+
};
|
|
1190
|
+
for (const key of ['baseStats', 'perLevelStats']) {
|
|
1191
|
+
if (input[key] !== undefined && (!input[key] || typeof input[key] !== 'object' || Array.isArray(input[key]))) {
|
|
1192
|
+
throw new TypeError('Base stat curve must be an object: ' + key);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
if (rules.defaultBotLevel > rules.maxLevel) throw new RangeError('Default bot level exceeds maximum level.');
|
|
1196
|
+
for (const [key, [min, max]] of Object.entries(PROGRESSION_RULE_LIMITS)) {
|
|
1197
|
+
if (!Number.isInteger(rules[key]) || rules[key] < min || rules[key] > max) throw new RangeError('Invalid progression rule: ' + key);
|
|
1198
|
+
}
|
|
1199
|
+
for (const block of [rules.baseStats, rules.perLevelStats]) {
|
|
1200
|
+
for (const [key, value] of Object.entries(block)) {
|
|
1201
|
+
if (!STAT_TYPES.includes(key) || !Number.isInteger(value) || value < 0 || value > STAT_MODIFIER_MAX) {
|
|
1202
|
+
throw new RangeError('Invalid base stat curve: ' + key);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
for (const key of Object.keys(rules)) {
|
|
1207
|
+
if (!(key in defaults)) throw new TypeError('Unknown progression rule: ' + key);
|
|
1208
|
+
}
|
|
1209
|
+
return rules;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
989
1212
|
export const CYBERIA_INSTANCE_CONF_DEFAULTS = {
|
|
990
1213
|
// ── Tick model ─────────────────────────────────────────────────────
|
|
991
1214
|
tickRate: 60,
|
|
@@ -1014,7 +1237,7 @@ export const CYBERIA_INSTANCE_CONF_DEFAULTS = {
|
|
|
1014
1237
|
// projectiles and every other entity keep entityBaseSpeed. 0 falls back to
|
|
1015
1238
|
// entityBaseSpeed.
|
|
1016
1239
|
playerBaseSpeed: 8,
|
|
1017
|
-
|
|
1240
|
+
progressionRules: PROGRESSION_RULES_DEFAULTS,
|
|
1018
1241
|
maxActiveLayers: 4,
|
|
1019
1242
|
initialLifeFraction: 1.0,
|
|
1020
1243
|
|
|
@@ -1034,29 +1257,33 @@ export const CYBERIA_INSTANCE_CONF_DEFAULTS = {
|
|
|
1034
1257
|
craftingFeePercent: 0.0,
|
|
1035
1258
|
},
|
|
1036
1259
|
|
|
1037
|
-
// ──
|
|
1038
|
-
lifeRegenChance
|
|
1039
|
-
maxChance
|
|
1260
|
+
// ── Chances, as fractions of 1 ───────────────────────────────────
|
|
1261
|
+
// A tap has lifeRegenChance to regenerate life; utility raises it. No chance the
|
|
1262
|
+
// stats raise passes maxChance, so nothing becomes a certainty.
|
|
1263
|
+
lifeRegenChance: 0.15,
|
|
1264
|
+
maxChance: 0.95,
|
|
1040
1265
|
|
|
1041
1266
|
// ── Per-entity-type defaults ───────────────────────────────────────
|
|
1042
|
-
|
|
1267
|
+
// References into the CyberiaEntityTypeDefault collection — see the schema.
|
|
1268
|
+
// Empty by default: a conf that names no document resolves against the
|
|
1269
|
+
// canonical ENTITY_TYPE_DEFAULTS above, so a fresh world is complete without
|
|
1270
|
+
// owning a single row.
|
|
1271
|
+
entityDefaults: [],
|
|
1043
1272
|
|
|
1044
1273
|
// ── Status icons (numeric IDs only — visuals live in client defaults) ──
|
|
1045
1274
|
statusIcons: STATUS_ICONS.map((s) => ({ ...s })),
|
|
1046
1275
|
|
|
1047
1276
|
// ── Skill system ───────────────────────────────────────────────────
|
|
1048
|
-
//
|
|
1049
|
-
//
|
|
1050
|
-
//
|
|
1051
|
-
skillConfig: DefaultSkillConfig,
|
|
1052
|
-
|
|
1277
|
+
// No skillConfig: the skills an instance runs are derived from its own content, never
|
|
1278
|
+
// stored on the conf. See DefaultSkillConfig above for the definitions and
|
|
1279
|
+
// cyberia-instance-items.js for the membership rule.
|
|
1053
1280
|
skillRules: {
|
|
1054
|
-
projectileSpawnChance: 0.
|
|
1281
|
+
projectileSpawnChance: 0.75,
|
|
1055
1282
|
projectileLifetimeMs: 2000,
|
|
1056
1283
|
projectileWidth: 1,
|
|
1057
1284
|
projectileHeight: 1,
|
|
1058
1285
|
projectileSpeedMultiplier: 3,
|
|
1059
|
-
doppelgangerSpawnChance: 0.
|
|
1286
|
+
doppelgangerSpawnChance: 0.6,
|
|
1060
1287
|
doppelgangerLifetimeMs: 5000,
|
|
1061
1288
|
doppelgangerSpawnRadius: 3,
|
|
1062
1289
|
doppelgangerInitialLifeFraction: 1.0,
|
|
@@ -1078,7 +1305,7 @@ const _cloneDefault = (v) => JSON.parse(JSON.stringify(v));
|
|
|
1078
1305
|
* Recursively backfill one value against its canonical default:
|
|
1079
1306
|
* - null / undefined → deep clone of the default
|
|
1080
1307
|
* - empty array → deep clone of the default (config lists must never
|
|
1081
|
-
* export empty — e.g.
|
|
1308
|
+
* export empty — e.g. entityDefaults, statusIcons)
|
|
1082
1309
|
* - non-empty array → kept verbatim (author content)
|
|
1083
1310
|
* - plain object → keep author-set keys, recurse to fill missing default keys
|
|
1084
1311
|
* - scalar → kept verbatim when present (0, '', false count as present)
|
|
@@ -1098,26 +1325,12 @@ function _deepFillDefaults(value, defaultValue) {
|
|
|
1098
1325
|
return value;
|
|
1099
1326
|
}
|
|
1100
1327
|
|
|
1101
|
-
/**
|
|
1102
|
-
* Normalise a skillConfig array to the shape CyberiaInstanceConfSchema stores
|
|
1103
|
-
* (`SkillConfigEntrySchema` = { triggerItemId, logicEventIds }). The canonical
|
|
1104
|
-
* DefaultSkillConfig carries extra `skills` metadata the schema drops on write;
|
|
1105
|
-
* stripping it here keeps export ⇄ DB round-trips churn-free.
|
|
1106
|
-
*/
|
|
1107
|
-
function _normaliseSkillConfig(skillConfig) {
|
|
1108
|
-
if (!Array.isArray(skillConfig)) return [];
|
|
1109
|
-
return skillConfig.map((entry) => ({
|
|
1110
|
-
triggerItemId: entry.triggerItemId,
|
|
1111
|
-
logicEventIds: [...(entry.logicEventIds || [])],
|
|
1112
|
-
}));
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
1328
|
/**
|
|
1116
1329
|
* Return a copy of a CyberiaInstanceConf document with every field defined by
|
|
1117
1330
|
* CyberiaInstanceConfSchema present. Missing, null/undefined, or empty-array
|
|
1118
1331
|
* fields — common when a doc is read with `.lean()` (which skips Mongoose schema
|
|
1119
1332
|
* defaults), was created before a schema field existed, or was seeded with the
|
|
1120
|
-
* schema's empty-array default (e.g. `
|
|
1333
|
+
* schema's empty-array default (e.g. `entityDefaults`) — are filled from
|
|
1121
1334
|
* CYBERIA_INSTANCE_CONF_DEFAULTS, the same canonical values the fallback world
|
|
1122
1335
|
* uses. Author-set scalars (including 0, '', false), non-empty arrays, and DB
|
|
1123
1336
|
* metadata (_id, instanceCode, timestamps) are preserved untouched.
|
|
@@ -1131,6 +1344,8 @@ export function fillInstanceConfDefaults(conf = {}) {
|
|
|
1131
1344
|
for (const key of Object.keys(CYBERIA_INSTANCE_CONF_DEFAULTS)) {
|
|
1132
1345
|
out[key] = _deepFillDefaults(source[key], CYBERIA_INSTANCE_CONF_DEFAULTS[key]);
|
|
1133
1346
|
}
|
|
1134
|
-
|
|
1347
|
+
// A conf from before skills were derived still carries the field; it is not part of the
|
|
1348
|
+
// schema any more, and leaving it would put a stale second answer back on the wire.
|
|
1349
|
+
delete out.skillConfig;
|
|
1135
1350
|
return out;
|
|
1136
1351
|
}
|