Merge branch 'main' into ue

This commit is contained in:
TrèsAbhi committed 2026-04-09 21:21:20 -05:00
commit d9c7d31017
17 files changed
+228 -43

No files matched your search

@@ -1,5 +1,6 @@
import sharp from "sharp";
import { AssetUploader } from "../core/github/assetUploader";
import { parsePackedSpriteRect } from "../core/blitz/parsePackedSpriteRect";
import { vfs } from "./constants";
import { ConsumablesCommon, ProvisionsCommon } from "./definitions";
@@ -13,6 +14,42 @@ interface Mappings {
const listItemsPattern = /<items path="(.+)\.xml"\/>/g;
async function extractPackedIcon(
texture: sharp.Sharp,
sizes: number[],
context: string,
) {
const [left, top, width, height] = sizes;
const bounds = [left, top, width, height];
if (!bounds.every(Number.isInteger)) {
throw new Error(
`Invalid RIFF sprite bounds for ${context}: ${bounds.join(" ")}`,
);
}
const metadata = await texture.metadata();
if (metadata.width === undefined || metadata.height === undefined) {
throw new Error(`Failed to read image dimensions for ${context}`);
}
if (
left < 0 ||
top < 0 ||
width <= 0 ||
height <= 0 ||
left + width > metadata.width ||
top + height > metadata.height
) {
throw new Error(
`Out-of-bounds RIFF sprite for ${context}: ${bounds.join(" ")} in ${metadata.width}x${metadata.height}`,
);
}
return await texture.clone().extract({ left, top, width, height }).toBuffer();
}
export async function consumableProvisionIcons() {
console.log("Building consumable and provision icons...");
@@ -57,8 +94,20 @@ export async function consumableProvisionIcons() {
.replace(".txt", "");
if (await vfs.resolve(`Data/${configPath}.packed.webp`)) {
const image = sharp(await vfs.file(`Data/${configPath}.packed.webp`));
const content = await image.trim({ threshold: 100 }).toBuffer();
const packedBuffer = await vfs.file(`Data/${configPath}.packed.webp`);
const consumablesTexture = sharp(packedBuffer);
const sizes = parsePackedSpriteRect(packedBuffer);
let content: Buffer;
if (sizes) {
content = await extractPackedIcon(
consumablesTexture,
sizes,
`consumable ${consumable.icon}`,
);
} else {
content = await consumablesTexture.trim({ threshold: 80 }).toBuffer();
}
await uploader.add({
path: `icons/consumables/${consumable.id}.webp`,
@@ -122,8 +171,20 @@ export async function consumableProvisionIcons() {
.replace(".txt", "");
if (await vfs.resolve(`Data/${configPath}.packed.webp`)) {
const image = sharp(await vfs.file(`Data/${configPath}.packed.webp`));
const content = await image.trim({ threshold: 100 }).toBuffer();
const packedBuffer = await vfs.file(`Data/${configPath}.packed.webp`);
const provisionsTexture = sharp(packedBuffer);
const sizes = parsePackedSpriteRect(packedBuffer);
let content: Buffer;
if (sizes) {
content = await extractPackedIcon(
provisionsTexture,
sizes,
`provision ${provision.icon}`,
);
} else {
content = await provisionsTexture.trim({ threshold: 80 }).toBuffer();
}
await uploader.add({
path: `icons/provisions/${provision.id}.webp`,
@@ -170,8 +170,12 @@ export async function extractModel(vfs: AbstractVFS, path: string) {
break;
case "TransformComponent": {
component["tc.localTranslation"] = [0, 0, 0]; // Game when is loading model resets position to [0, 0, 0], so any set value is ignored.
node.setTranslation(component["tc.localTranslation"]);
const localTranslation = component["tc.localTranslation"];
// The game resets top-level node translation to [0, 0, 0] on load.
// Child nodes must keep their authored local offsets.
node.setTranslation(
parent instanceof Scene ? [0, 0, 0] : localTranslation,
);
node.setRotation(component["tc.localRotation"]);
node.setScale(component["tc.localScale"]);
@@ -0,0 +1,53 @@
const textDecoder = new TextDecoder();
function readFourCC(buffer: Uint8Array, offset: number) {
return textDecoder.decode(buffer.subarray(offset, offset + 4));
}
export function parsePackedSpriteRect(buffer: Uint8Array): number[] | null {
if (
buffer.length < 12 ||
readFourCC(buffer, 0) !== "RIFF" ||
readFourCC(buffer, 8) !== "WEBP"
) {
return null;
}
const view = new DataView(
buffer.buffer,
buffer.byteOffset,
buffer.byteLength,
);
let offset = 12;
while (offset + 8 <= buffer.length) {
const chunkId = readFourCC(buffer, offset);
const chunkSize = view.getUint32(offset + 4, true);
const dataStart = offset + 8;
const dataEnd = dataStart + chunkSize;
if (dataEnd > buffer.length) return null;
if (chunkId === "extr") {
const lines = textDecoder
.decode(buffer.subarray(dataStart, dataEnd))
.replace(/\0/g, "")
.split(/\r?\n/)
.map((line) => line.trim().split(/\s+/).map(Number));
for (const values of lines) {
if (values.length < 4) continue;
if (!values.slice(0, 4).every(Number.isFinite)) continue;
if (values[2] <= 0 || values[3] <= 0) continue;
return values;
}
return null;
}
offset = dataEnd + (chunkSize % 2);
}
return null;
}
@@ -1,4 +1,4 @@
import { assertSecret } from "@blitzkit/core";
import { assertSecret, asset } from "@blitzkit/core";
import { Octokit } from "@octokit/rest";
import ProgressBar from "progress";
import { compareUint8Arrays } from "./compareUint8Arrays";
@@ -49,9 +49,8 @@ export class AssetUploader {
}
async add(change: FileChange) {
const blobPath = `${this.owner}/${this.repo}/${this.branch}/${change.path}`;
const response = await fetch(
`https://raw.githubusercontent.com/${blobPath}`,
asset(change.path),
{ headers: { "Accept-Encoding": "identity" } },
);
let diff: { change: number; status: DiffStatus };