Release Healer Man 0.1.14

This commit is contained in:
phenom committed 2026-09-05 11:40:50 -04:00
1 parent 67d7a8ebe7
commit 7b41b102fa
147 files changed
+169892 -1342

No files matched your search

@@ -1,6 +1,7 @@
import { copyFile, mkdir, rm, writeFile } from "node:fs/promises";
import path from "node:path";
import {
BASIC_HUMAN_TEXTURE_PATHS,
PROJECT_ROOT,
createContentManifest,
scanContentSources,
@@ -12,8 +13,21 @@ if (path.dirname(outputDirectory) !== PROJECT_ROOT || path.basename(outputDirect
}
const sources = [
{ directory: path.join(PROJECT_ROOT, "public", "basis"), logicalPrefix: "basis" },
{ directory: path.join(PROJECT_ROOT, "public", "assets", "ui"), logicalPrefix: "assets/ui" },
{ directory: path.join(PROJECT_ROOT, "public", "assets", "equipment"), logicalPrefix: "assets/equipment" },
{
directory: path.join(PROJECT_ROOT, "public", "assets", "characters", "wow335a-v1", "manifest.json"),
logicalPrefix: "assets/characters/wow335a-v1/manifest.json",
},
{
directory: path.join(PROJECT_ROOT, "public", "assets", "characters", "wow335a-v1", "human"),
logicalPrefix: "assets/characters/wow335a-v1/human",
},
...BASIC_HUMAN_TEXTURE_PATHS.map((logicalPath) => ({
directory: path.join(PROJECT_ROOT, "public", ...logicalPath.split("/")),
logicalPrefix: logicalPath,
})),
{
directory: path.join(PROJECT_ROOT, "public", "assets", "creatures", "wailing-caverns"),
logicalPrefix: "assets/creatures/wailing-caverns",
+71
View File
@@ -0,0 +1,71 @@
import { copyFile, mkdir, rm, writeFile } from "node:fs/promises";
import path from "node:path";
import {
BASIC_HUMAN_TEXTURE_PATHS,
PROJECT_ROOT,
createContentManifest,
scanContentSources,
} from "./publish-content.mjs";
const outputDirectory = path.join(PROJECT_ROOT, ".web-public");
if (path.dirname(outputDirectory) !== PROJECT_ROOT || path.basename(outputDirectory) !== ".web-public") {
throw new Error(`Refusing to replace unexpected browser staging directory: ${outputDirectory}`);
}
const sources = [
{ directory: path.join(PROJECT_ROOT, "public", "basis"), logicalPrefix: "basis" },
{ directory: path.join(PROJECT_ROOT, "public", "data"), logicalPrefix: "data" },
{ directory: path.join(PROJECT_ROOT, "public", "assets", "ui"), logicalPrefix: "assets/ui" },
{ directory: path.join(PROJECT_ROOT, "public", "assets", "equipment"), logicalPrefix: "assets/equipment" },
{
directory: path.join(PROJECT_ROOT, "public", "assets", "characters", "wow335a-v1", "manifest.json"),
logicalPrefix: "assets/characters/wow335a-v1/manifest.json",
},
{
directory: path.join(PROJECT_ROOT, "public", "assets", "characters", "wow335a-v1", "human"),
logicalPrefix: "assets/characters/wow335a-v1/human",
},
...BASIC_HUMAN_TEXTURE_PATHS.map((logicalPath) => ({
directory: path.join(PROJECT_ROOT, "public", ...logicalPath.split("/")),
logicalPrefix: logicalPath,
})),
{
directory: path.join(PROJECT_ROOT, "public", "assets", "creatures", "wailing-caverns"),
logicalPrefix: "assets/creatures/wailing-caverns",
},
{
directory: path.join(PROJECT_ROOT, "src", "assets", "game", "dungeons", "wailing-caverns"),
logicalPrefix: "assets/game/dungeons/wailing-caverns",
},
];
await rm(outputDirectory, { recursive: true, force: true });
await mkdir(outputDirectory, { recursive: true });
const files = await scanContentSources(sources);
for (const file of files) {
const destination = path.join(outputDirectory, ...file.logicalPath.split("/"));
await mkdir(path.dirname(destination), { recursive: true });
await copyFile(file.sourcePath, destination);
}
await copyFile(
path.join(PROJECT_ROOT, "public", "content-cache-sw.js"),
path.join(outputDirectory, "content-cache-sw.js"),
);
const generated = createContentManifest(files, { baseUrl: "/" });
const bootstrapManifest = {
...generated,
files: Object.fromEntries(Object.entries(generated.files).map(([logicalPath, entry]) => [
logicalPath,
{ ...entry, url: logicalPath },
])),
};
const manifestPath = path.join(outputDirectory, "content", "bootstrap-manifest.json");
await mkdir(path.dirname(manifestPath), { recursive: true });
await writeFile(manifestPath, `${JSON.stringify(bootstrapManifest, null, 2)}\n`, "utf8");
const bytes = files.reduce((total, file) => total + file.bytes, 0);
console.log(`Prepared browser bootstrap content: ${files.length} files (${bytes} bytes)`);
console.log(`Bootstrap version: ${bootstrapManifest.version}`);
+55 -7
View File
@@ -18,14 +18,34 @@ export const DEFAULT_MINIMUM_APP_VERSION = "0.1.0";
export const BUNDLED_ANDROID_PACKS = Object.freeze([
"core:ui",
"core:equipment",
"core:character-basic",
"core:runtime",
"dungeon:wailing-caverns",
]);
const RUNTIME_EXTENSIONS = new Set([
".avif", ".glb", ".json", ".ktx2", ".mp3", ".ogg", ".png",
".svg", ".wav", ".webm", ".webp",
".avif", ".glb", ".js", ".json", ".ktx2", ".mp3", ".ogg", ".png",
".svg", ".wasm", ".wav", ".webm", ".webp",
]);
export const BASIC_HUMAN_TEXTURE_PATHS = Object.freeze([
"assets/characters/wow335a-v1/textures/character/human/faciallowerhair00_00.webp",
"assets/characters/wow335a-v1/textures/character/human/facialupperhair00_00.webp",
"assets/characters/wow335a-v1/textures/character/human/female/humanfemalefacelower00_00.webp",
"assets/characters/wow335a-v1/textures/character/human/female/humanfemalefaceupper00_00.webp",
"assets/characters/wow335a-v1/textures/character/human/female/humanfemalenakedpelvisskin00_00.webp",
"assets/characters/wow335a-v1/textures/character/human/female/humanfemalenakedtorsoskin00_00.webp",
"assets/characters/wow335a-v1/textures/character/human/female/humanfemaleskin00_00.webp",
"assets/characters/wow335a-v1/textures/character/human/hair00_00.webp",
"assets/characters/wow335a-v1/textures/character/human/male/humanmalefacelower00_00.webp",
"assets/characters/wow335a-v1/textures/character/human/male/humanmalefaceupper00_00.webp",
"assets/characters/wow335a-v1/textures/character/human/male/humanmalenakedpelvisskin00_00.webp",
"assets/characters/wow335a-v1/textures/character/human/male/humanmaleskin00_00.webp",
"assets/characters/wow335a-v1/textures/character/human/scalplowerhair00_00.webp",
"assets/characters/wow335a-v1/textures/character/human/scalpupperhair00_00.webp",
]);
const BASIC_HUMAN_TEXTURE_PATH_SET = new Set(BASIC_HUMAN_TEXTURE_PATHS);
export const DEFAULT_CONTENT_SOURCES = Object.freeze([
{
directory: path.join(PROJECT_ROOT, "public", "assets"),
@@ -49,18 +69,33 @@ function packLabel(packId) {
const [category, id = category] = packId.split(":", 2);
const name = id.split("-").map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`).join(" ");
if (category === "dungeon") return name;
if (category === "characters") return `${name} Characters`;
if (packId === "characters:wow335a-v1") return "Extra Wrath Character Models & Skins";
if (packId === "characters:ascension-v1") return "Ascension Character Models & Skins";
if (packId === "characters:runewaker-v1") return "RuneWaker Character Models & Skins";
if (category === "characters") return `${name} Character Models & Skins`;
if (category === "manastorm") return `${name} Manastorm`;
if (packId === "shared:creatures") return "Shared Creatures";
if (packId === "core:ui") return "Interface";
if (packId === "core:equipment") return "Starter Equipment";
if (packId === "core:character-basic") return "Basic Human Character Model";
if (packId === "core:runtime") return "Game Runtime";
if (packId === "core:combat-media") return "Original Spell Effects & Sounds";
return name;
}
export function contentPackForPath(logicalPath) {
const segments = normalizedRelativePath(logicalPath).split("/");
const normalizedPath = normalizedRelativePath(logicalPath);
const segments = normalizedPath.split("/");
if (segments[0] === "basis" || segments[0] === "data") return "core:runtime";
if (segments[0] !== "assets") return "core:misc";
if (segments[1] === "characters" && segments[2]) return `characters:${segments[2]}`;
if (segments[1] === "characters" && segments[2]) {
const basicWrathHuman = segments[2] === "wow335a-v1" && (
segments[3] === "manifest.json"
|| segments[3] === "human"
|| BASIC_HUMAN_TEXTURE_PATH_SET.has(normalizedPath)
);
return basicWrathHuman ? "core:character-basic" : `characters:${segments[2]}`;
}
if (segments[1] === "creatures" && segments[2]) {
return segments[2] === "shared" ? "shared:creatures" : `dungeon:${segments[2]}`;
}
@@ -73,12 +108,17 @@ export function contentPackForPath(logicalPath) {
if (segments[1] === "game" && segments[2] === "manastorm" && segments[3]) {
return `manastorm:${segments[3]}`;
}
if (segments[1] === "game" && segments[2] === "combat-effects") return "core:combat-media";
if (segments[1] === "ui") return "core:ui";
if (segments[1] === "equipment") return "core:equipment";
return "core:misc";
}
async function walkFiles(directory) {
const rootStats = await stat(directory);
if (rootStats.isFile()) {
return RUNTIME_EXTENSIONS.has(path.extname(directory).toLowerCase()) ? [directory] : [];
}
const files = [];
async function visit(current) {
const entries = await readdir(current, { withFileTypes: true });
@@ -111,7 +151,9 @@ export async function scanContentSources(sources = DEFAULT_CONTENT_SOURCES) {
const files = await walkFiles(sourceDirectory);
for (const sourcePath of files) {
const relative = normalizedRelativePath(path.relative(sourceDirectory, sourcePath));
const logicalPath = normalizedRelativePath(`${source.logicalPrefix}/${relative}`);
const logicalPath = normalizedRelativePath(relative
? `${source.logicalPrefix}/${relative}`
: source.logicalPrefix);
const fileStats = await stat(sourcePath);
const sha256 = await sha256File(sourcePath);
const prior = discovered.get(logicalPath);
@@ -170,11 +212,17 @@ export function createContentManifest(files, options = {}) {
if (packs.has("shared:creatures")) {
for (const pack of packs.values()) {
if (pack.id.startsWith("dungeon:") || pack.id.startsWith("manastorm:")) {
if ((pack.id.startsWith("dungeon:") && pack.id !== "dungeon:wailing-caverns")
|| pack.id.startsWith("manastorm:")) {
pack.dependencies = ["shared:creatures"];
}
}
}
if (packs.has("core:character-basic")) {
for (const pack of packs.values()) {
if (pack.id.startsWith("characters:")) pack.dependencies = ["core:character-basic"];
}
}
return {
schemaVersion: CONTENT_SCHEMA_VERSION,
+13 -1
View File
@@ -4,6 +4,7 @@ import os from "node:os";
import path from "node:path";
import test from "node:test";
import {
BUNDLED_ANDROID_PACKS,
contentPackForPath,
createContentManifest,
publishContent,
@@ -13,8 +14,19 @@ import {
test("assigns stable player-facing packs from logical asset paths", () => {
assert.equal(contentPackForPath("assets/game/dungeons/wailing-caverns/visual.glb"), "dungeon:wailing-caverns");
assert.equal(contentPackForPath("assets/creatures/wailing-caverns/mutanus.glb"), "dungeon:wailing-caverns");
assert.equal(contentPackForPath("assets/characters/wow335a-v1/manifest.json"), "characters:wow335a-v1");
assert.equal(contentPackForPath("assets/characters/wow335a-v1/manifest.json"), "core:character-basic");
assert.equal(contentPackForPath("assets/characters/wow335a-v1/human/male/rig.glb"), "core:character-basic");
assert.equal(contentPackForPath("assets/characters/wow335a-v1/textures/character/human/male/humanmaleskin00_00.webp"), "core:character-basic");
assert.equal(contentPackForPath("assets/characters/wow335a-v1/textures/character/human/male/humanmaleskin00_01.webp"), "characters:wow335a-v1");
assert.equal(contentPackForPath("assets/characters/wow335a-v1/orc/male/rig.glb"), "characters:wow335a-v1");
assert.equal(contentPackForPath("assets/game/manastorm/stage-1/visual.glb"), "manastorm:stage-1");
assert.equal(contentPackForPath("assets/game/combat-effects/audio/smite.wav"), "core:combat-media");
assert.equal(
contentPackForPath("assets/game/combat-effects/audio/manastorm/Manastorm_BossKill_GenericPortalsReady.wav"),
"core:combat-media",
);
assert.equal(BUNDLED_ANDROID_PACKS.includes("core:combat-media"), false);
assert.equal(contentPackForPath("basis/basis_transcoder.wasm"), "core:runtime");
});
test("publishes objects before a deterministic latest manifest", async () => {