@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
|
@@ -5,14 +5,28 @@
|
|
|
5
5
|
* @namespace CyberiaAtlasSpriteSheetGenerator
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import fs from 'fs-extra';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import sharp from 'sharp';
|
|
11
8
|
import { Jimp, rgbaToInt } from 'jimp';
|
|
12
9
|
import { loggerFactory } from '../../server/ops/logger.js';
|
|
10
|
+
import { getKeyframeDirectionsByCode } from '../../client/components/cyberia/SharedDefaultsCyberia.js';
|
|
13
11
|
|
|
14
12
|
const logger = loggerFactory(import.meta);
|
|
15
13
|
|
|
14
|
+
/** Direction code of the still an item is previewed by: down idle. */
|
|
15
|
+
export const IDLE_PREVIEW_DIRECTION_CODE = '08';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Pixels per cell of the human-resolution atlas, when no factor is given.
|
|
19
|
+
* @memberof CyberiaAtlasSpriteSheetGenerator
|
|
20
|
+
*/
|
|
21
|
+
export const DEFAULT_ATLAS_UPSCALE_FACTOR = 20;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Pixels per cell of the minified atlas. The client runtime downloads this one,
|
|
25
|
+
* so it stays at one pixel per cell.
|
|
26
|
+
* @memberof CyberiaAtlasSpriteSheetGenerator
|
|
27
|
+
*/
|
|
28
|
+
const MINIFY_CELL_PIXEL_DIM = 1;
|
|
29
|
+
|
|
16
30
|
/**
|
|
17
31
|
* @typedef {Object} AtlasFrame
|
|
18
32
|
* @property {number} x - X position in atlas
|
|
@@ -38,7 +52,7 @@ export class AtlasSpriteSheetGenerator {
|
|
|
38
52
|
* @returns {Promise<Jimp>} The generated image
|
|
39
53
|
* @memberof CyberiaAtlasSpriteSheetGenerator
|
|
40
54
|
*/
|
|
41
|
-
static async frameMatrixToImage(frameMatrix, colors, cellPixelDim =
|
|
55
|
+
static async frameMatrixToImage(frameMatrix, colors, cellPixelDim = 1) {
|
|
42
56
|
if (!frameMatrix || frameMatrix.length === 0 || frameMatrix[0].length === 0) {
|
|
43
57
|
throw new Error('Invalid frame matrix');
|
|
44
58
|
}
|
|
@@ -79,59 +93,47 @@ export class AtlasSpriteSheetGenerator {
|
|
|
79
93
|
* @memberof CyberiaAtlasSpriteSheetGenerator
|
|
80
94
|
*/
|
|
81
95
|
static calculateOptimalDimension(frameImages) {
|
|
82
|
-
if (
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const heights = frameImages.map((f) => f.height).filter((h) => !isNaN(h) && h > 0);
|
|
90
|
-
|
|
91
|
-
if (widths.length === 0 || heights.length === 0) {
|
|
92
|
-
logger.warn('Invalid frame dimensions found, using default 2048');
|
|
93
|
-
return 2048;
|
|
96
|
+
if (
|
|
97
|
+
!frameImages?.length ||
|
|
98
|
+
frameImages.some(
|
|
99
|
+
({ width, height }) => !Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0,
|
|
100
|
+
)
|
|
101
|
+
) {
|
|
102
|
+
throw new Error('Invalid frame dimensions');
|
|
94
103
|
}
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const cols = Math.ceil(Math.sqrt(frameCount));
|
|
104
|
-
const rows = Math.ceil(frameCount / cols);
|
|
105
|
-
|
|
106
|
-
// Calculate required dimensions
|
|
107
|
-
const requiredWidth = cols * maxFrameWidth;
|
|
108
|
-
const requiredHeight = rows * maxFrameHeight;
|
|
109
|
-
const requiredDim = Math.max(requiredWidth, requiredHeight);
|
|
110
|
-
|
|
111
|
-
logger.info(`Grid layout: ${cols}x${rows}, required: ${requiredWidth}x${requiredHeight}`);
|
|
112
|
-
|
|
113
|
-
// Round up to next power of 2
|
|
114
|
-
const optimalDim = AtlasSpriteSheetGenerator.nextPowerOf2(requiredDim);
|
|
115
|
-
|
|
116
|
-
// Ensure minimum 1024, maximum 8192
|
|
117
|
-
const finalDim = Math.max(1024, Math.min(8192, optimalDim));
|
|
118
|
-
|
|
119
|
-
logger.info(`Calculated optimal dimension: ${finalDim}x${finalDim}`);
|
|
120
|
-
|
|
121
|
-
return finalDim;
|
|
104
|
+
const cols = Math.ceil(Math.sqrt(frameImages.length));
|
|
105
|
+
const rows = Math.ceil(frameImages.length / cols);
|
|
106
|
+
return AtlasSpriteSheetGenerator.nextPowerOf2(
|
|
107
|
+
Math.max(
|
|
108
|
+
cols * Math.max(...frameImages.map((frame) => frame.width)),
|
|
109
|
+
rows * Math.max(...frameImages.map((frame) => frame.height)),
|
|
110
|
+
),
|
|
111
|
+
);
|
|
122
112
|
}
|
|
123
113
|
|
|
124
114
|
/**
|
|
125
|
-
* Consolidates all frames
|
|
115
|
+
* Consolidates all frames of an ObjectLayerRenderFrames into one atlas sprite sheet.
|
|
116
|
+
*
|
|
117
|
+
* One packing produces two renders of the same layout: `minifyBuffer` at one
|
|
118
|
+
* pixel per cell, which the client runtime downloads, and `buffer` at
|
|
119
|
+
* `upscaleFactor` pixels per cell for human viewing. `metadata` describes the
|
|
120
|
+
* minified render, so atlas metadata and the served blob always agree.
|
|
121
|
+
*
|
|
126
122
|
* @static
|
|
127
123
|
* @param {Object} objectLayerRenderFrames - The ObjectLayerRenderFrames document
|
|
128
124
|
* @param {string} itemKey - The item key for the atlas
|
|
129
|
-
* @param {number} [
|
|
125
|
+
* @param {number} [upscaleFactor=DEFAULT_ATLAS_UPSCALE_FACTOR] - Pixels per cell of the human-resolution render
|
|
130
126
|
* @param {number} [maxAtlasDim=null] - Maximum atlas dimension (auto-calculated if null)
|
|
131
|
-
* @returns {Promise<{buffer: Buffer, metadata: Object}>}
|
|
127
|
+
* @returns {Promise<{buffer: Buffer, minifyBuffer: Buffer, metadata: Object}>} Both renders and the shared metadata
|
|
132
128
|
* @memberof CyberiaAtlasSpriteSheetGenerator
|
|
133
129
|
*/
|
|
134
|
-
static async generateAtlas(
|
|
130
|
+
static async generateAtlas(
|
|
131
|
+
objectLayerRenderFrames,
|
|
132
|
+
itemKey,
|
|
133
|
+
upscaleFactor = DEFAULT_ATLAS_UPSCALE_FACTOR,
|
|
134
|
+
maxAtlasDim = null,
|
|
135
|
+
) {
|
|
136
|
+
if (!Number.isInteger(upscaleFactor) || upscaleFactor < 1) throw new Error('Invalid pixel scale');
|
|
135
137
|
const { frames, colors } = objectLayerRenderFrames;
|
|
136
138
|
const frameDuration = Number(objectLayerRenderFrames?.frame_duration);
|
|
137
139
|
|
|
@@ -157,7 +159,7 @@ export class AtlasSpriteSheetGenerator {
|
|
|
157
159
|
'none_idle',
|
|
158
160
|
];
|
|
159
161
|
|
|
160
|
-
//
|
|
162
|
+
// The packing works on the minified render; the upscaled render reuses its layout.
|
|
161
163
|
const frameImages = [];
|
|
162
164
|
|
|
163
165
|
for (const direction of directionOrder) {
|
|
@@ -167,10 +169,15 @@ export class AtlasSpriteSheetGenerator {
|
|
|
167
169
|
for (let frameIndex = 0; frameIndex < directionFrames.length; frameIndex++) {
|
|
168
170
|
const frameMatrix = directionFrames[frameIndex];
|
|
169
171
|
try {
|
|
170
|
-
const frameImage = await AtlasSpriteSheetGenerator.frameMatrixToImage(
|
|
172
|
+
const frameImage = await AtlasSpriteSheetGenerator.frameMatrixToImage(
|
|
173
|
+
frameMatrix,
|
|
174
|
+
colors,
|
|
175
|
+
MINIFY_CELL_PIXEL_DIM,
|
|
176
|
+
);
|
|
171
177
|
|
|
172
178
|
frameImages.push({
|
|
173
179
|
image: frameImage,
|
|
180
|
+
frameMatrix,
|
|
174
181
|
direction,
|
|
175
182
|
frameIndex,
|
|
176
183
|
width: frameImage.bitmap.width,
|
|
@@ -194,10 +201,8 @@ export class AtlasSpriteSheetGenerator {
|
|
|
194
201
|
);
|
|
195
202
|
}
|
|
196
203
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
logger.error(`Invalid maxAtlasDim: ${maxAtlasDim}, using default 2048`);
|
|
200
|
-
maxAtlasDim = 2048;
|
|
204
|
+
if (!Number.isInteger(maxAtlasDim) || maxAtlasDim <= 0 || maxAtlasDim > 4096) {
|
|
205
|
+
throw new Error('Atlas dimension must be between 1 and 4096');
|
|
201
206
|
}
|
|
202
207
|
|
|
203
208
|
// Simple grid packing algorithm
|
|
@@ -215,43 +220,29 @@ export class AtlasSpriteSheetGenerator {
|
|
|
215
220
|
);
|
|
216
221
|
}
|
|
217
222
|
|
|
218
|
-
|
|
219
|
-
const
|
|
223
|
+
const minifyImage = new Jimp({ width: atlasWidth, height: atlasHeight, color: 0x00000000 });
|
|
224
|
+
const upscaledImage = new Jimp({
|
|
225
|
+
width: atlasWidth * upscaleFactor,
|
|
226
|
+
height: atlasHeight * upscaleFactor,
|
|
227
|
+
color: 0x00000000,
|
|
228
|
+
});
|
|
220
229
|
|
|
221
|
-
// Build metadata structure first
|
|
222
230
|
const frameMetadata = {};
|
|
223
231
|
|
|
224
|
-
// Composite frames onto atlas
|
|
225
|
-
let loggedCount = 0;
|
|
226
232
|
for (const packedFrame of packedFrames) {
|
|
227
|
-
const { image, x, y, direction, frameIndex } = packedFrame;
|
|
228
|
-
|
|
229
|
-
// Log first few frames for debugging
|
|
230
|
-
if (loggedCount < 3) {
|
|
231
|
-
logger.info(
|
|
232
|
-
`Frame ${loggedCount + 1}: ${direction}[${frameIndex}] → (${x},${y}) ${image.bitmap.width}x${image.bitmap.height}`,
|
|
233
|
-
);
|
|
234
|
-
loggedCount++;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Manually copy pixels to ensure correct positioning
|
|
238
|
-
for (let srcY = 0; srcY < image.bitmap.height; srcY++) {
|
|
239
|
-
for (let srcX = 0; srcX < image.bitmap.width; srcX++) {
|
|
240
|
-
const destX = x + srcX;
|
|
241
|
-
const destY = y + srcY;
|
|
233
|
+
const { image, frameMatrix, x, y, direction, frameIndex } = packedFrame;
|
|
242
234
|
|
|
243
|
-
|
|
244
|
-
if (destX >= atlasWidth || destY >= atlasHeight) continue;
|
|
235
|
+
AtlasSpriteSheetGenerator.blitFrame(minifyImage, image, x, y);
|
|
245
236
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
237
|
+
// The same cell grid at a larger scale, so the upscaled atlas is an exact
|
|
238
|
+
// multiple of the layout the metadata describes.
|
|
239
|
+
if (upscaleFactor > MINIFY_CELL_PIXEL_DIM) {
|
|
240
|
+
const upscaledFrame = await AtlasSpriteSheetGenerator.frameMatrixToImage(frameMatrix, colors, upscaleFactor);
|
|
241
|
+
AtlasSpriteSheetGenerator.blitFrame(upscaledImage, upscaledFrame, x * upscaleFactor, y * upscaleFactor);
|
|
242
|
+
} else {
|
|
243
|
+
AtlasSpriteSheetGenerator.blitFrame(upscaledImage, image, x, y);
|
|
252
244
|
}
|
|
253
245
|
|
|
254
|
-
// Store metadata in correct order
|
|
255
246
|
if (!frameMetadata[direction]) {
|
|
256
247
|
frameMetadata[direction] = [];
|
|
257
248
|
}
|
|
@@ -265,19 +256,80 @@ export class AtlasSpriteSheetGenerator {
|
|
|
265
256
|
};
|
|
266
257
|
}
|
|
267
258
|
|
|
268
|
-
// Convert to PNG buffer
|
|
269
|
-
const buffer = await atlasImage.getBuffer('image/png');
|
|
270
|
-
|
|
271
259
|
const metadata = {
|
|
272
260
|
itemKey,
|
|
273
261
|
atlasWidth,
|
|
274
262
|
atlasHeight,
|
|
275
|
-
cellPixelDim,
|
|
263
|
+
cellPixelDim: MINIFY_CELL_PIXEL_DIM,
|
|
264
|
+
upscaleFactor,
|
|
276
265
|
frame_duration: Number.isFinite(frameDuration) ? frameDuration : 100,
|
|
277
266
|
frames: frameMetadata,
|
|
278
267
|
};
|
|
279
268
|
|
|
280
|
-
return {
|
|
269
|
+
return {
|
|
270
|
+
buffer: await upscaledImage.getBuffer('image/png'),
|
|
271
|
+
minifyBuffer: await minifyImage.getBuffer('image/png'),
|
|
272
|
+
metadata,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* The frame an item is previewed by: the first frame of the first down-idle keyframe the
|
|
278
|
+
* atlas carries, or the first frame of any direction when it has none.
|
|
279
|
+
*
|
|
280
|
+
* @static
|
|
281
|
+
* @param {Object} metadata - Atlas metadata as {@link generateAtlas} returns it.
|
|
282
|
+
* @returns {{x:number,y:number,width:number,height:number}|null} Frame box in minified cells.
|
|
283
|
+
* @memberof CyberiaAtlasSpriteSheetGenerator
|
|
284
|
+
*/
|
|
285
|
+
static idlePreviewFrame(metadata) {
|
|
286
|
+
const frames = metadata?.frames ?? {};
|
|
287
|
+
const preferred = getKeyframeDirectionsByCode(IDLE_PREVIEW_DIRECTION_CODE);
|
|
288
|
+
for (const direction of [...preferred, ...Object.keys(frames)]) {
|
|
289
|
+
const frame = frames[direction]?.[0];
|
|
290
|
+
if (frame) return frame;
|
|
291
|
+
}
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Cuts the idle-preview still out of the human-resolution render, using the same layout
|
|
297
|
+
* `metadata.frames` describes for the minified one scaled by `metadata.upscaleFactor`.
|
|
298
|
+
*
|
|
299
|
+
* @static
|
|
300
|
+
* @param {Buffer} buffer - PNG bytes of the human-resolution atlas render.
|
|
301
|
+
* @param {Object} metadata - Atlas metadata as {@link generateAtlas} returns it.
|
|
302
|
+
* @returns {Promise<Buffer|null>} PNG bytes of the still, or null when the atlas has no frame.
|
|
303
|
+
* @memberof CyberiaAtlasSpriteSheetGenerator
|
|
304
|
+
*/
|
|
305
|
+
static async idlePreviewFromAtlas(buffer, metadata) {
|
|
306
|
+
const frame = AtlasSpriteSheetGenerator.idlePreviewFrame(metadata);
|
|
307
|
+
if (!frame) return null;
|
|
308
|
+
const scale = Number(metadata.upscaleFactor) || 1;
|
|
309
|
+
const image = await Jimp.read(buffer);
|
|
310
|
+
image.crop({ x: frame.x * scale, y: frame.y * scale, w: frame.width * scale, h: frame.height * scale });
|
|
311
|
+
return await image.getBuffer('image/png');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Copies a frame image onto an atlas canvas at the given origin.
|
|
316
|
+
* @static
|
|
317
|
+
* @param {Jimp} atlasImage - Destination atlas canvas.
|
|
318
|
+
* @param {Jimp} frameImage - Source frame image.
|
|
319
|
+
* @param {number} originX - Destination X origin.
|
|
320
|
+
* @param {number} originY - Destination Y origin.
|
|
321
|
+
* @returns {void}
|
|
322
|
+
* @memberof CyberiaAtlasSpriteSheetGenerator
|
|
323
|
+
*/
|
|
324
|
+
static blitFrame(atlasImage, frameImage, originX, originY) {
|
|
325
|
+
for (let srcY = 0; srcY < frameImage.bitmap.height; srcY++) {
|
|
326
|
+
for (let srcX = 0; srcX < frameImage.bitmap.width; srcX++) {
|
|
327
|
+
const destX = originX + srcX;
|
|
328
|
+
const destY = originY + srcY;
|
|
329
|
+
if (destX >= atlasImage.bitmap.width || destY >= atlasImage.bitmap.height) continue;
|
|
330
|
+
atlasImage.setPixelColor(frameImage.getPixelColor(srcX, srcY), destX, destY);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
281
333
|
}
|
|
282
334
|
|
|
283
335
|
/**
|
|
@@ -302,7 +354,6 @@ export class AtlasSpriteSheetGenerator {
|
|
|
302
354
|
let rowHeight = 0;
|
|
303
355
|
let maxWidth = 0;
|
|
304
356
|
let totalHeight = 0;
|
|
305
|
-
let exceedsCount = 0;
|
|
306
357
|
|
|
307
358
|
for (const frame of sortedFrames) {
|
|
308
359
|
// Check if frame fits in current row
|
|
@@ -313,9 +364,8 @@ export class AtlasSpriteSheetGenerator {
|
|
|
313
364
|
rowHeight = 0;
|
|
314
365
|
}
|
|
315
366
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
exceedsCount++;
|
|
367
|
+
if (frame.width > maxDim || currentY + frame.height > maxDim) {
|
|
368
|
+
throw new Error(`Frames exceed atlas dimension ${maxDim}`);
|
|
319
369
|
}
|
|
320
370
|
|
|
321
371
|
packedFrames.push({
|
|
@@ -330,20 +380,8 @@ export class AtlasSpriteSheetGenerator {
|
|
|
330
380
|
totalHeight = Math.max(totalHeight, currentY + frame.height);
|
|
331
381
|
}
|
|
332
382
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
const recommendedSize = AtlasSpriteSheetGenerator.nextPowerOf2(Math.max(maxWidth, totalHeight));
|
|
336
|
-
logger.warn(
|
|
337
|
-
`${exceedsCount} frames exceed atlas dimension (${maxDim}x${maxDim}). ` +
|
|
338
|
-
`Actual required: ${maxWidth}x${totalHeight}. ` +
|
|
339
|
-
`Recommended: --to-atlas-sprite-sheet ${recommendedSize}`,
|
|
340
|
-
);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Use maxDim as the atlas dimensions (already power-of-2 and optimal from auto-calculation)
|
|
344
|
-
// If all frames fit within a smaller power-of-2, use that instead
|
|
345
|
-
const atlasWidth = maxDim;
|
|
346
|
-
const atlasHeight = maxDim;
|
|
383
|
+
const atlasWidth = maxWidth;
|
|
384
|
+
const atlasHeight = totalHeight;
|
|
347
385
|
|
|
348
386
|
return {
|
|
349
387
|
packedFrames,
|
|
@@ -369,45 +407,4 @@ export class AtlasSpriteSheetGenerator {
|
|
|
369
407
|
n |= n >> 16;
|
|
370
408
|
return n + 1;
|
|
371
409
|
}
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* Generates atlas and saves to file system
|
|
375
|
-
* @static
|
|
376
|
-
* @param {Object} objectLayerRenderFrames - The ObjectLayerRenderFrames document
|
|
377
|
-
* @param {string} itemKey - The item key for the atlas
|
|
378
|
-
* @param {string} outputPath - Output file path
|
|
379
|
-
* @param {number} [cellPixelDim=20] - Pixel dimension per cell
|
|
380
|
-
* @param {number} [maxAtlasDim=null] - Maximum atlas dimension (auto-calculated if null)
|
|
381
|
-
* @returns {Promise<{buffer: Buffer, metadata: Object, outputPath: string}>}
|
|
382
|
-
* @memberof CyberiaAtlasSpriteSheetGenerator
|
|
383
|
-
*/
|
|
384
|
-
static async generateAtlasToFile(
|
|
385
|
-
objectLayerRenderFrames,
|
|
386
|
-
itemKey,
|
|
387
|
-
outputPath,
|
|
388
|
-
cellPixelDim = 20,
|
|
389
|
-
maxAtlasDim = null,
|
|
390
|
-
) {
|
|
391
|
-
const { buffer, metadata } = await AtlasSpriteSheetGenerator.generateAtlas(
|
|
392
|
-
objectLayerRenderFrames,
|
|
393
|
-
itemKey,
|
|
394
|
-
cellPixelDim,
|
|
395
|
-
maxAtlasDim,
|
|
396
|
-
);
|
|
397
|
-
|
|
398
|
-
await fs.ensureDir(path.dirname(outputPath));
|
|
399
|
-
await fs.writeFile(outputPath, buffer);
|
|
400
|
-
|
|
401
|
-
logger.info(`Atlas sprite sheet generated: ${outputPath}`);
|
|
402
|
-
|
|
403
|
-
return { buffer, metadata, outputPath };
|
|
404
|
-
}
|
|
405
410
|
}
|
|
406
|
-
|
|
407
|
-
// Export convenience functions
|
|
408
|
-
export const generateAtlas = AtlasSpriteSheetGenerator.generateAtlas;
|
|
409
|
-
export const generateAtlasToFile = AtlasSpriteSheetGenerator.generateAtlasToFile;
|
|
410
|
-
export const frameMatrixToImage = AtlasSpriteSheetGenerator.frameMatrixToImage;
|
|
411
|
-
export const packFramesGrid = AtlasSpriteSheetGenerator.packFramesGrid;
|
|
412
|
-
export const nextPowerOf2 = AtlasSpriteSheetGenerator.nextPowerOf2;
|
|
413
|
-
export const calculateOptimalDimension = AtlasSpriteSheetGenerator.calculateOptimalDimension;
|