Worldshaper/src/worldshaperStudio/worldChunkRuntimeHelpers.ts

603 lines
24 KiB
TypeScript
Raw Normal View History

2026-06-27 04:36:26 -04:00
// @ts-nocheck
export function createFilledRows(width, height, fillChar) {
return Array.from({ length: Math.max(1, Number(height) || 1) }, () => String(fillChar || " ").repeat(Math.max(1, Number(width) || 1)));
}
function writeRowSegment(rows, y, x, segment) {
if (!Array.isArray(rows) || !segment) {
return;
}
const targetY = Math.floor(Number(y) || 0);
if (targetY < 0 || targetY >= rows.length) {
return;
}
const safeX = Math.max(0, Math.floor(Number(x) || 0));
const sourceRow = String(rows[targetY] || "");
const paddedRow = sourceRow.length >= safeX
? sourceRow
: (sourceRow + " ".repeat(Math.max(0, safeX - sourceRow.length)));
const before = paddedRow.slice(0, safeX);
const afterStart = safeX + segment.length;
const after = afterStart < paddedRow.length ? paddedRow.slice(afterStart) : "";
rows[targetY] = before + segment + after;
}
export function composeWorldRoomLayers(chunks, chunkWidth, chunkHeight, originChunkX, originChunkY, worldWidth, worldHeight) {
const layerMap = new Map();
(Array.isArray(chunks) ? chunks : []).forEach((chunk) => {
const baseChunkX = Math.floor(Number(chunk?.chunkX) || 0);
const baseChunkY = Math.floor(Number(chunk?.chunkY) || 0);
const offsetX = (baseChunkX - originChunkX) * chunkWidth;
const offsetY = (baseChunkY - originChunkY) * chunkHeight;
const rawLayers = Array.isArray(chunk?.roomLayers) ? chunk.roomLayers : [];
rawLayers.forEach((rawLayer) => {
const layerNumber = Number(rawLayer?.layer) || 0;
const fillChar = layerNumber === 0 ? "." : " ";
if (!layerMap.has(layerNumber)) {
layerMap.set(layerNumber, {
layer: layerNumber,
name: typeof rawLayer?.name === "string" && String(rawLayer.name).trim() ? String(rawLayer.name).trim() : undefined,
rows: createFilledRows(worldWidth, worldHeight, fillChar),
instanceIds: [],
});
}
const targetLayer = layerMap.get(layerNumber);
const sourceRows = Array.isArray(rawLayer?.rows) ? rawLayer.rows.map((row) => String(row || "")) : [];
sourceRows.forEach((row, localY) => {
const targetY = offsetY + localY;
if (targetY < 0 || targetY >= targetLayer.rows.length) {
return;
}
const maxWidth = Math.max(0, worldWidth - offsetX);
writeRowSegment(targetLayer.rows, targetY, offsetX, row.slice(0, maxWidth));
});
const sourceInstanceIds = Array.isArray(rawLayer?.instanceIds)
? rawLayer.instanceIds.map((entry) => String(entry || "").trim()).filter(Boolean)
: [];
targetLayer.instanceIds = Array.from(new Set([...(targetLayer.instanceIds || []), ...sourceInstanceIds]));
});
});
if (!layerMap.has(0)) {
layerMap.set(0, {
layer: 0,
rows: createFilledRows(worldWidth, worldHeight, "."),
instanceIds: [],
});
}
return Array.from(layerMap.values()).sort((a, b) => (Number(a.layer) || 0) - (Number(b.layer) || 0));
}
export function composeWorldHeightLayers(chunks, chunkWidth, chunkHeight, originChunkX, originChunkY) {
const patches = [];
(Array.isArray(chunks) ? chunks : []).forEach((chunk) => {
const baseChunkX = Math.floor(Number(chunk?.chunkX) || 0);
const baseChunkY = Math.floor(Number(chunk?.chunkY) || 0);
const offsetX = (baseChunkX - originChunkX) * chunkWidth;
const offsetY = (baseChunkY - originChunkY) * chunkHeight;
const rawHeightLayers = Array.isArray(chunk?.heightLayers) ? chunk.heightLayers : [];
rawHeightLayers.forEach((entry, index) => {
const fallbackId = `height_${baseChunkX}_${baseChunkY}_${index + 1}`;
patches.push({
id: String(entry?.id || fallbackId).trim() || fallbackId,
name: typeof entry?.name === "string" && String(entry.name).trim() ? String(entry.name).trim() : undefined,
z: Math.max(1, Math.floor(Number(entry?.z) || 1)),
x: offsetX + Math.max(0, Number(entry?.x) || 0),
y: offsetY + Math.max(0, Number(entry?.y) || 0),
rows: Array.isArray(entry?.rows) ? entry.rows.map((row) => String(row || "")) : [],
});
});
});
return patches.sort((a, b) => {
if (a.z !== b.z) {
return a.z - b.z;
}
return String(a.name || a.id).localeCompare(String(b.name || b.id));
});
}
export function buildWorldLayerMetadata(chunks) {
const layerMap = new Map();
(Array.isArray(chunks) ? chunks : []).forEach((chunk) => {
const rawLayers = Array.isArray(chunk?.roomLayers) ? chunk.roomLayers : [];
rawLayers.forEach((rawLayer) => {
const layerNumber = Number(rawLayer?.layer) || 0;
if (layerMap.has(layerNumber)) {
return;
}
layerMap.set(layerNumber, {
layer: layerNumber,
name: typeof rawLayer?.name === "string" && String(rawLayer.name).trim() ? String(rawLayer.name).trim() : undefined,
rows: [],
instanceIds: Array.isArray(rawLayer?.instanceIds) ? rawLayer.instanceIds.map((entry) => String(entry || "").trim()).filter(Boolean) : [],
});
});
});
if (!layerMap.has(0)) {
layerMap.set(0, {
layer: 0,
rows: [],
instanceIds: [],
});
}
if (!Array.from(layerMap.keys()).some((layerNumber) => layerNumber > 0)) {
layerMap.set(1, {
layer: 1,
rows: [],
instanceIds: [],
});
}
return Array.from(layerMap.values()).sort((a, b) => (Number(a.layer) || 0) - (Number(b.layer) || 0));
}
export function sliceNormalizedRows(rows, startX, startY, width, height, fillChar) {
return Array.from({ length: Math.max(1, Number(height) || 1) }, (_, rowOffset) => {
const sourceRow = String((Array.isArray(rows) ? rows[startY + rowOffset] : "") || "");
const paddedRow = sourceRow.length >= startX + width
? sourceRow
: sourceRow + String(fillChar || " ").repeat(Math.max(0, (startX + width) - sourceRow.length));
return paddedRow.slice(startX, startX + width);
});
}
export function buildChunkHeightLayersFromDocument({ mapDocument, cloneHeightLayers, baseTileX, baseTileY, chunkWidth, chunkHeight }) {
return (Array.isArray(mapDocument.heightLayers) ? cloneHeightLayers(mapDocument.heightLayers) : [])
.map((entry) => {
const patchX = Math.max(0, Number(entry?.x) || 0);
const patchY = Math.max(0, Number(entry?.y) || 0);
const rows = Array.isArray(entry?.rows) ? entry.rows.map((row) => String(row || "")) : [];
const patchWidth = rows.reduce((max, row) => Math.max(max, row.length), 0);
const patchHeight = rows.length;
const patchRight = patchX + patchWidth;
const patchBottom = patchY + patchHeight;
const chunkRight = baseTileX + chunkWidth;
const chunkBottom = baseTileY + chunkHeight;
const overlapLeft = Math.max(baseTileX, patchX);
const overlapTop = Math.max(baseTileY, patchY);
const overlapRight = Math.min(chunkRight, patchRight);
const overlapBottom = Math.min(chunkBottom, patchBottom);
if (overlapRight <= overlapLeft || overlapBottom <= overlapTop) {
return null;
}
const localRows = [];
for (let y = overlapTop; y < overlapBottom; y += 1) {
const sourceRow = String(rows[y - patchY] || "");
localRows.push(sourceRow.slice(overlapLeft - patchX, overlapRight - patchX).replace(/\s+$/g, ""));
}
return {
id: String(entry?.id || "").trim(),
name: typeof entry?.name === "string" && entry.name.trim() ? entry.name.trim() : undefined,
z: Math.max(1, Number(entry?.z) || 1),
x: overlapLeft - baseTileX,
y: overlapTop - baseTileY,
rows: localRows,
};
})
.filter((entry) => entry && entry.id);
}
export function buildChunkInstancesFromDocument({ mapDocument, cloneValue, baseTileX, baseTileY, chunkWidth, chunkHeight, tileOffsetX, tileOffsetY }) {
const chunkInstances = cloneValue(mapDocument.npcOverlays)
.filter((npc) => {
const localX = Math.floor(Number(npc?.x));
const localY = Math.floor(Number(npc?.y));
return Number.isFinite(localX)
&& Number.isFinite(localY)
&& localX >= baseTileX
&& localX < baseTileX + chunkWidth
&& localY >= baseTileY
&& localY < baseTileY + chunkHeight;
})
.map((npc) => ({
id: String(npc.id || "").trim(),
templateId: String(npc?.record?.templateId || "").trim(),
layer: Number(npc.layer) || 0,
x: Math.floor(Number(npc.x) || 0) - baseTileX,
y: Math.floor(Number(npc.y) || 0) - baseTileY,
record: {
...cloneValue(npc.record || {}),
id: String(npc.id || "").trim(),
layer: Number(npc.layer) || 0,
templateId: String(npc?.record?.templateId || "").trim(),
name: String(npc.name || npc?.record?.name || ""),
entityType: String(npc?.record?.entityType || npc?.entityType || "friendly"),
faction: String(npc.faction || npc?.record?.faction || ""),
spriteId: String(npc.spriteId || npc?.record?.spriteId || ""),
dialogueId: String(npc.dialogueId || npc?.record?.dialogueId || ""),
description: String(npc.description || npc?.record?.description || ""),
tags: cloneValue(npc?.record?.tags) || [],
enabled: typeof npc?.record?.enabled === "boolean" ? npc.record.enabled : true,
position: {
x: Math.floor(Number(npc.x) || 0) + tileOffsetX,
y: Math.floor(Number(npc.y) || 0) + tileOffsetY,
},
},
}))
.filter((entry) => entry.id);
const npcIdsByLayer = new Map();
chunkInstances.forEach((entry) => {
const layerNumber = Number(entry.layer) || 0;
if (!npcIdsByLayer.has(layerNumber)) {
npcIdsByLayer.set(layerNumber, []);
}
npcIdsByLayer.get(layerNumber).push(entry.id);
});
return {
chunkInstances,
npcIdsByLayer,
};
}
export function normalizeWorldChunkRows(rows, width, height, fillChar) {
const safeWidth = Math.max(1, Math.floor(Number(width) || 1));
const safeHeight = Math.max(1, Math.floor(Number(height) || 1));
return Array.from({ length: safeHeight }, (_entry, rowIndex) => {
const sourceRow = String((Array.isArray(rows) ? rows[rowIndex] : "") || "");
return sourceRow.length >= safeWidth
? sourceRow.slice(0, safeWidth)
: (sourceRow + String(fillChar || " ").repeat(Math.max(0, safeWidth - sourceRow.length)));
});
}
export function cloneWorldChunkHeightLayers(source) {
return (Array.isArray(source) ? source : [])
.map((entry, index) => ({
id: String(entry?.id || `height_patch_${index + 1}`).trim() || `height_patch_${index + 1}`,
name: typeof entry?.name === "string" && entry.name.trim() ? entry.name.trim() : undefined,
z: Math.max(1, Math.floor(Number(entry?.z) || 1)),
x: Math.max(0, Math.floor(Number(entry?.x) || 0)),
y: Math.max(0, Math.floor(Number(entry?.y) || 0)),
rows: Array.isArray(entry?.rows) ? entry.rows.map((row) => String(row || "")) : [],
}))
.filter((entry) => entry.id);
}
export function buildWorldChunkLayerInstanceIds(roomLayers, instances, width, height) {
const safeWidth = Math.max(1, Math.floor(Number(width) || 1));
const safeHeight = Math.max(1, Math.floor(Number(height) || 1));
const nextLayers = new Map();
(Array.isArray(roomLayers) ? roomLayers : []).forEach((layer) => {
const layerNumber = Math.max(0, Math.floor(Number(layer?.layer) || 0));
nextLayers.set(layerNumber, {
layer: layerNumber,
name: typeof layer?.name === "string" && layer.name.trim() ? layer.name.trim() : undefined,
rows: normalizeWorldChunkRows(layer?.rows, safeWidth, safeHeight, layerNumber === 0 ? "." : " "),
instanceIds: [],
});
});
if (!nextLayers.has(0)) {
nextLayers.set(0, {
layer: 0,
rows: normalizeWorldChunkRows([], safeWidth, safeHeight, "."),
instanceIds: [],
});
}
if (!Array.from(nextLayers.keys()).some((layerNumber) => layerNumber > 0)) {
nextLayers.set(1, {
layer: 1,
rows: normalizeWorldChunkRows([], safeWidth, safeHeight, " "),
instanceIds: [],
});
}
(Array.isArray(instances) ? instances : []).forEach((entry) => {
const layerNumber = Math.max(0, Math.floor(Number(entry?.layer) || 0));
const instanceId = String(entry?.id || "").trim();
if (!instanceId) {
return;
}
if (!nextLayers.has(layerNumber)) {
nextLayers.set(layerNumber, {
layer: layerNumber,
rows: normalizeWorldChunkRows([], safeWidth, safeHeight, layerNumber === 0 ? "." : " "),
instanceIds: [],
});
}
nextLayers.get(layerNumber).instanceIds.push(instanceId);
});
return Array.from(nextLayers.values())
.map((entry) => ({
...entry,
instanceIds: Array.from(new Set((Array.isArray(entry.instanceIds) ? entry.instanceIds : []).map((id) => String(id || "").trim()).filter(Boolean))),
}))
.sort((left, right) => (Number(left.layer) || 0) - (Number(right.layer) || 0));
}
export function normalizeWorldChunkInstances({ sourceInstances, chunkX, chunkY, width, height, options, cloneValue, runtimeUniqueId }) {
const config = options && typeof options === "object" ? options : {};
const duplicateIds = config.duplicateIds === true;
const safeChunkX = Math.floor(Number(chunkX) || 0);
const safeChunkY = Math.floor(Number(chunkY) || 0);
const safeWidth = Math.max(1, Math.floor(Number(width) || 1));
const safeHeight = Math.max(1, Math.floor(Number(height) || 1));
return (Array.isArray(sourceInstances) ? sourceInstances : [])
.map((entry) => {
const record = entry?.record && typeof entry.record === "object" && !Array.isArray(entry.record)
? cloneValue(entry.record)
: {};
const nextId = duplicateIds
? runtimeUniqueId()
: (String(entry?.id || record?.id || runtimeUniqueId()).trim() || runtimeUniqueId());
const nextLayer = Math.max(0, Math.floor(Number(entry?.layer ?? record?.layer) || 0));
const nextX = Math.max(0, Math.min(safeWidth - 1, Math.floor(Number(entry?.x) || 0)));
const nextY = Math.max(0, Math.min(safeHeight - 1, Math.floor(Number(entry?.y) || 0)));
const nextTemplateId = String(entry?.templateId || record?.templateId || "").trim();
record.id = nextId;
record.layer = nextLayer;
record.templateId = nextTemplateId;
record.position = {
x: (safeChunkX * safeWidth) + nextX,
y: (safeChunkY * safeHeight) + nextY,
};
return {
id: nextId,
templateId: nextTemplateId,
layer: nextLayer,
x: nextX,
y: nextY,
record,
};
})
.filter((entry) => entry.id);
}
export function createEmptyWorldChunkPayload({ chunkX, chunkY, chunkWidth, chunkHeight, worldId }) {
const safeChunkX = Math.floor(Number(chunkX) || 0);
const safeChunkY = Math.floor(Number(chunkY) || 0);
return {
schemaVersion: 1,
worldId: String(worldId || "").trim(),
chunkX: safeChunkX,
chunkY: safeChunkY,
width: chunkWidth,
height: chunkHeight,
backgroundTileId: "",
roomLayers: [
{
layer: 0,
rows: Array.from({ length: chunkHeight }, () => ".".repeat(chunkWidth)),
instanceIds: [],
},
{
layer: 1,
rows: Array.from({ length: chunkHeight }, () => " ".repeat(chunkWidth)),
instanceIds: [],
},
],
heightLayers: [],
instances: [],
};
}
export function normalizeCachedWorldChunkPayload({ chunkPayload, chunkX, chunkY, chunkWidth, chunkHeight, worldId, cloneValue, runtimeUniqueId, options }) {
const safeChunkX = Math.floor(Number(chunkX ?? chunkPayload?.chunkX) || 0);
const safeChunkY = Math.floor(Number(chunkY ?? chunkPayload?.chunkY) || 0);
const safeWidth = Math.max(1, Math.floor(Number(chunkPayload?.width) || Number(chunkWidth) || 32));
const safeHeight = Math.max(1, Math.floor(Number(chunkPayload?.height) || Number(chunkHeight) || 32));
const instances = normalizeWorldChunkInstances({
sourceInstances: chunkPayload?.instances,
chunkX: safeChunkX,
chunkY: safeChunkY,
width: safeWidth,
height: safeHeight,
options,
cloneValue,
runtimeUniqueId,
});
const roomLayers = buildWorldChunkLayerInstanceIds(chunkPayload?.roomLayers, instances, safeWidth, safeHeight);
return {
schemaVersion: Math.max(1, Math.floor(Number(chunkPayload?.schemaVersion) || 1)),
worldId: String(chunkPayload?.worldId || worldId || "").trim(),
chunkX: safeChunkX,
chunkY: safeChunkY,
width: safeWidth,
height: safeHeight,
backgroundTileId: String(chunkPayload?.backgroundTileId || "").trim(),
roomLayers,
heightLayers: cloneWorldChunkHeightLayers(chunkPayload?.heightLayers),
instances,
};
}
export function isChunkFillSymbol(ch, fillChar) {
const symbol = String(ch || "").charAt(0);
return !symbol || symbol === fillChar || symbol === "." || symbol === " ";
}
export function isWorldChunkPayloadEmpty({ chunkPayload, chunkWidth, chunkHeight, worldId, cloneValue, runtimeUniqueId }) {
const normalized = normalizeCachedWorldChunkPayload({
chunkPayload,
chunkX: chunkPayload?.chunkX,
chunkY: chunkPayload?.chunkY,
chunkWidth,
chunkHeight,
worldId,
cloneValue,
runtimeUniqueId,
});
if (String(normalized?.backgroundTileId || "").trim()) {
return false;
}
if (Array.isArray(normalized?.instances) && normalized.instances.length > 0) {
return false;
}
if ((Array.isArray(normalized?.heightLayers) ? normalized.heightLayers : []).some((entry) => (
Array.isArray(entry?.rows) && entry.rows.some((row) => /[^ .]/.test(String(row || "")))
))) {
return false;
}
return !(Array.isArray(normalized?.roomLayers) ? normalized.roomLayers : []).some((layer) => {
const fillChar = (Number(layer?.layer) || 0) === 0 ? "." : " ";
return (Array.isArray(layer?.rows) ? layer.rows : []).some((row) => {
const sourceRow = String(row || "");
for (let index = 0; index < sourceRow.length; index += 1) {
if (!isChunkFillSymbol(sourceRow.charAt(index), fillChar)) {
return true;
}
}
return false;
});
});
}
export function transformChunkLocalCoord(localX, localY, width, height, operation) {
const safeX = Math.floor(Number(localX) || 0);
const safeY = Math.floor(Number(localY) || 0);
const safeWidth = Math.max(1, Math.floor(Number(width) || 1));
const safeHeight = Math.max(1, Math.floor(Number(height) || 1));
switch (String(operation || "").trim()) {
case "flipHorizontal":
return { x: (safeWidth - 1) - safeX, y: safeY };
case "flipVertical":
return { x: safeX, y: (safeHeight - 1) - safeY };
case "rotate180":
return { x: (safeWidth - 1) - safeX, y: (safeHeight - 1) - safeY };
case "rotate90cw":
if (safeWidth !== safeHeight) {
return null;
}
return { x: (safeWidth - 1) - safeY, y: safeX };
case "rotate90ccw":
if (safeWidth !== safeHeight) {
return null;
}
return { x: safeY, y: (safeHeight - 1) - safeX };
default:
return { x: safeX, y: safeY };
}
}
export function transformChunkRows(rows, width, height, fillChar, operation) {
const safeWidth = Math.max(1, Math.floor(Number(width) || 1));
const safeHeight = Math.max(1, Math.floor(Number(height) || 1));
const sourceRows = normalizeWorldChunkRows(rows, safeWidth, safeHeight, fillChar);
const nextRows = Array.from({ length: safeHeight }, () => Array.from({ length: safeWidth }, () => String(fillChar || " ").charAt(0) || " "));
for (let rowIndex = 0; rowIndex < safeHeight; rowIndex += 1) {
const sourceRow = sourceRows[rowIndex];
for (let columnIndex = 0; columnIndex < safeWidth; columnIndex += 1) {
const char = String(sourceRow.charAt(columnIndex) || fillChar).charAt(0) || String(fillChar || " ").charAt(0) || " ";
if (isChunkFillSymbol(char, fillChar)) {
continue;
}
const nextCoord = transformChunkLocalCoord(columnIndex, rowIndex, safeWidth, safeHeight, operation);
if (!nextCoord) {
continue;
}
nextRows[nextCoord.y][nextCoord.x] = char;
}
}
return nextRows.map((row) => row.join(""));
}
export function transformChunkHeightPatch(patch, width, height, operation) {
const safeWidth = Math.max(1, Math.floor(Number(width) || 1));
const safeHeight = Math.max(1, Math.floor(Number(height) || 1));
const sourceRows = Array.isArray(patch?.rows) ? patch.rows.map((row) => String(row || "")) : [];
const patchWidth = sourceRows.reduce((max, row) => Math.max(max, row.length), 0);
const patchHeight = sourceRows.length;
const transformedCells = [];
for (let localY = 0; localY < patchHeight; localY += 1) {
const row = sourceRows[localY] || "";
for (let localX = 0; localX < patchWidth; localX += 1) {
const char = String(row.charAt(localX) || " ").charAt(0) || " ";
if (char === " " || char === ".") {
continue;
}
const worldX = Math.max(0, Math.floor(Number(patch?.x) || 0)) + localX;
const worldY = Math.max(0, Math.floor(Number(patch?.y) || 0)) + localY;
if (worldX < 0 || worldY < 0 || worldX >= safeWidth || worldY >= safeHeight) {
continue;
}
const nextCoord = transformChunkLocalCoord(worldX, worldY, safeWidth, safeHeight, operation);
if (!nextCoord) {
continue;
}
transformedCells.push({
x: nextCoord.x,
y: nextCoord.y,
char,
});
}
}
if (transformedCells.length <= 0) {
return null;
}
const minX = transformedCells.reduce((min, entry) => Math.min(min, entry.x), transformedCells[0].x);
const maxX = transformedCells.reduce((max, entry) => Math.max(max, entry.x), transformedCells[0].x);
const minY = transformedCells.reduce((min, entry) => Math.min(min, entry.y), transformedCells[0].y);
const maxY = transformedCells.reduce((max, entry) => Math.max(max, entry.y), transformedCells[0].y);
const nextRows = Array.from({ length: (maxY - minY) + 1 }, () => Array.from({ length: (maxX - minX) + 1 }, () => " "));
transformedCells.forEach((entry) => {
nextRows[entry.y - minY][entry.x - minX] = entry.char;
});
return {
id: String(patch?.id || "").trim(),
name: typeof patch?.name === "string" && patch.name.trim() ? patch.name.trim() : undefined,
z: Math.max(1, Math.floor(Number(patch?.z) || 1)),
x: minX,
y: minY,
rows: nextRows.map((row) => row.join("").replace(/\s+$/g, "")),
};
}
export function transformWorldChunkPayload({ chunkPayload, operation, chunkWidth, chunkHeight, worldId, cloneValue, runtimeUniqueId, options }) {
const config = options && typeof options === "object" ? options : {};
const normalized = normalizeCachedWorldChunkPayload({
chunkPayload,
chunkX: chunkPayload?.chunkX,
chunkY: chunkPayload?.chunkY,
chunkWidth,
chunkHeight,
worldId,
cloneValue,
runtimeUniqueId,
options: config,
});
const safeWidth = Math.max(1, Math.floor(Number(normalized?.width) || 1));
const safeHeight = Math.max(1, Math.floor(Number(normalized?.height) || 1));
const normalizedOperation = String(operation || "").trim();
if ((normalizedOperation === "rotate90cw" || normalizedOperation === "rotate90ccw") && safeWidth !== safeHeight) {
throw new Error("Chunk rotation requires square chunks.");
}
const instances = normalizeWorldChunkInstances({
sourceInstances: (Array.isArray(normalized.instances) ? normalized.instances : []).map((entry) => {
const nextCoord = transformChunkLocalCoord(entry.x, entry.y, safeWidth, safeHeight, normalizedOperation);
return {
...cloneValue(entry),
x: nextCoord?.x ?? entry.x,
y: nextCoord?.y ?? entry.y,
};
}),
chunkX: normalized.chunkX,
chunkY: normalized.chunkY,
width: safeWidth,
height: safeHeight,
options: config,
cloneValue,
runtimeUniqueId,
});
const roomLayers = buildWorldChunkLayerInstanceIds(
(Array.isArray(normalized.roomLayers) ? normalized.roomLayers : []).map((layer) => ({
...cloneValue(layer),
rows: transformChunkRows(layer?.rows, safeWidth, safeHeight, (Number(layer?.layer) || 0) === 0 ? "." : " ", normalizedOperation),
})),
instances,
safeWidth,
safeHeight,
);
const heightLayers = cloneWorldChunkHeightLayers(normalized.heightLayers)
.map((entry) => transformChunkHeightPatch(entry, safeWidth, safeHeight, normalizedOperation))
.filter(Boolean)
.sort((left, right) => {
if ((Number(left?.z) || 0) !== (Number(right?.z) || 0)) {
return (Number(left?.z) || 0) - (Number(right?.z) || 0);
}
return String(left?.name || left?.id || "").localeCompare(String(right?.name || right?.id || ""));
});
return {
...normalized,
roomLayers,
heightLayers,
instances,
};
}