remove assets

This commit is contained in:
tresabhi committed 2026-08-05 13:19:27 -05:00
1 parent 24297dcae8
commit 84290b1c0e
9 files changed
-571

No files matched your search

-83
View File
@@ -1,83 +0,0 @@
name: Assets
on:
push:
branches: production
workflow_dispatch:
inputs:
asset_repo:
type: choice
required: false
description: What asset repo to write to
options:
- blitzkit/assets
- blitzkit/assets-preview
- blitzkit/assets-dev
default: blitzkit/assets
asset_source:
type: string
required: false
description: "Archive URL or Steam to download (example: steam:APP_ID/DEPOT_ID or zip:URL_TO_ZIP)"
default: "steam:444200/444202"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event.inputs.asset_repo }}
cancel-in-progress: true
env:
DISCORD_CLIENT_ID: ${{ secrets.DISCORD_CLIENT_ID }}
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
VIDEOS_SHEET: ${{ secrets.VIDEOS_SHEET }}
GOOGLE_APPLICATION_CREDENTIALS: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}
WOTB_GLOSSARY: ${{ secrets.WOTB_GLOSSARY }}
WOTB_DLC_CDN: ${{ secrets.WOTB_DLC_CDN }}
PUBLIC_WARGAMING_APPLICATION_ID: ${{ secrets.PUBLIC_WARGAMING_APPLICATION_ID }}
PUBLIC_ASSET_REPO: ${{ github.event.inputs.asset_repo != '' && github.event.inputs.asset_repo || secrets.PUBLIC_ASSET_REPO }}
PUBLIC_ASSET_BRANCH: ${{ secrets.PUBLIC_ASSET_BRANCH }}
WOTB_GLOSSARY_DOMAIN: ${{ secrets.WOTB_GLOSSARY_DOMAIN }}
WOTB_GLOSSARY_PATH: ${{ secrets.WOTB_GLOSSARY_PATH }}
PUBLIC_PROMOTE_OPENTEST: ${{ secrets.PUBLIC_PROMOTE_OPENTEST }}
ALLOW_ROBOTS: true
PUBLIC_GOOGLE_TAG_MANAGER_ID: ${{ secrets.PUBLIC_GOOGLE_TAG_MANAGER_ID }}
PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: ${{ secrets.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID }}
PUBLIC_PATREON_CLIENT_ID: ${{ secrets.PUBLIC_PATREON_CLIENT_ID }}
PUBLIC_PATREON_REDIRECT_URI: ${{ secrets.PUBLIC_PATREON_REDIRECT_URI }}
PATREON_CLIENT_SECRET: ${{ secrets.PATREON_CLIENT_SECRET }}
PUBLIC_NITROPAY_ID: ${{ secrets.PUBLIC_NITROPAY_ID }}
PUBLIC_DEBUG_MISSING_I18N: false
PUBLIC_DISCORD_SERVER_ID: ${{ secrets.PUBLIC_DISCORD_SERVER_ID }}
PUBLIC_ASSET_FAST_USER: blitzkit
PUBLIC_ASSET_FAST_REPO: assets
PUBLIC_ASSET_BASE: ${{ secrets.PUBLIC_ASSET_BASE }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
STEAM_USERNAME: ${{ secrets.STEAM_USERNAME }}
STEAM_PASSWORD: ${{ secrets.STEAM_PASSWORD }}
jobs:
build_assets:
name: Build assets
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
token: ${{ secrets.GH_TOKEN }}
- uses: arduino/setup-protoc@v3
with:
repo-token: ${{ secrets.GH_TOKEN }}
- uses: oven-sh/setup-bun@v2
- run: bun run setup
- name: AppArmor hack
run: echo 0 | sudo tee /proc/sys/kernel/apparmor_restrict_unprivileged_userns
- name: Install patches
run: bun run setup:patches --source=${{ github.event.inputs.asset_source || 'steam:444200/444202' }}
- name: Build assets
run: bun run build:assets --all-targets --source=${{ github.event.inputs.asset_source || 'steam:444200/444202' }}
@@ -1,43 +0,0 @@
import { assertSecret } from "@blitzkit/core";
import { AbstractVFS } from "@blitzkit/core/src/blitzkit/vfs/abstract";
import { LocalVFS } from "@blitzkit/core/src/blitzkit/vfs/local";
import { MixedVFS } from "@blitzkit/core/src/blitzkit/vfs/mixed";
import { SteamVFS } from "@blitzkit/core/src/blitzkit/vfs/steam";
import { ZipVFS } from "@blitzkit/core/src/blitzkit/vfs/zip";
export const PATCHES_ROOT = "../../temp/patches";
const steamPattern = /steam:(\d+)\/(\d+)/;
const zipPattern = /zip:(.+)/;
const localePattern = /local:"?(.+)"?/;
const source = Bun.argv
.find((arg) => arg.startsWith("--source="))
?.split("=")[1];
let resolved: AbstractVFS;
if (!source) {
throw new Error("No source specified");
} else if (steamPattern.test(source)) {
const [, app, depot] = source.match(steamPattern)!.map(Number);
resolved = await new SteamVFS(
assertSecret(import.meta.env.STEAM_USERNAME),
assertSecret(import.meta.env.STEAM_PASSWORD),
app,
depot,
).init();
} else if (zipPattern.test(source)) {
const [, url] = source.match(zipPattern)!;
resolved = await new ZipVFS(url).init();
} else if (localePattern.test(source)) {
const [, path] = source.match(localePattern)!;
resolved = await new LocalVFS(path).init();
} else {
throw new Error("No valid source specified");
}
resolved = new MixedVFS([new LocalVFS(PATCHES_ROOT), resolved]);
export const vfs = resolved;
@@ -1,73 +0,0 @@
import sharp from "sharp";
import { AssetUploader } from "../core/github/assetUploader";
import { vfs } from "./constants";
import { OptionalDevices } from "./definitions";
export async function equipmentIcons() {
console.log("Building equipment icons...");
using uploader = new AssetUploader("equipment icons");
const optionalDevices = await vfs.xml<{ root: OptionalDevices }>(
`Data/XML/item_defs/vehicles/common/optional_devices.xml`,
);
const mappings = await vfs.yaml<Mappings>(
`Data/UI/Screens3/Lobby/Inventory/OptionalDevices/OptionalDevicesItemImage.style.yaml`,
);
const image = sharp(
await vfs.file(
`Data/Gfx/UI/InventoryIcons/Big/OptionalDevices/texture0.packed.webp`,
),
);
for (const optionalDeviceKey in optionalDevices.root) {
const optionalDevice = optionalDevices.root[optionalDeviceKey];
if (optionalDeviceKey === "nextAvailableId") continue;
const mapping = mappings.StyleSheets.find((mapping) =>
mapping.selectors.includes(
`.optional_device_item.${optionalDevice.icon} #Img`,
),
);
if (!mapping) {
console.warn(`No mapping found for ${optionalDevice.icon}; skipping...`);
continue;
}
const configPathRaw = mapping.properties["bg-sprite"];
const configPath = configPathRaw.replace("~res:/", "");
if (configPath.startsWith("Gfx/Lobby")) {
const configPathWebp = configPath.replace(".txt", "");
const image = sharp(await vfs.file(`Data/${configPathWebp}.packed.webp`));
const content = await image.trim().toBuffer();
await uploader.add({
path: `icons/equipment/${optionalDevice.id}.webp`,
content,
});
} else {
const config = await vfs.text(`Data/${configPath}`);
const sizes = config.split("\n")[4].split(" ").map(Number);
const content = await image
.clone()
.extract({
left: sizes[0],
top: sizes[1],
width: sizes[2],
height: sizes[3],
})
.toBuffer();
await uploader.add({
path: `icons/equipment/${optionalDevice.id}.webp`,
content,
});
}
}
await uploader.flush();
}
@@ -1,89 +0,0 @@
import { Avatar, fetchGlossary, Gallery } from "@blitzkit/core";
import locales from "@blitzkit/i18n/locales.json";
import { extname } from "path";
import ProgressBar from "progress";
import { AssetUploader } from "../core/github/assetUploader";
export async function glossary() {
console.log("Building glossary...");
using uploader = new AssetUploader("glossary");
const avatars: Record<string, { avatar: Avatar; url: string }> = {};
await Promise.all(
locales.supported.map(async (supported) => {
const glossary = await fetchGlossary(supported.blitz ?? supported.locale);
console.log(
`Found ${Object.keys(glossary).length} things for ${supported.locale}`,
);
for (const key in glossary) {
const glossaryEntry = glossary[key];
if (
!key.startsWith("avatar") ||
key.endsWith("_part") ||
glossaryEntry.image_url === null
) {
continue;
}
if (key in avatars) {
avatars[key].avatar.name!.locales[supported.locale] =
glossaryEntry.title;
} else {
const extension = extname(glossaryEntry.image_url);
avatars[key] = {
url: glossaryEntry.image_url,
avatar: {
id: key,
name: { locales: { [supported.locale]: glossaryEntry.title } },
extension,
},
};
}
}
}),
);
const gallery: Gallery = { avatars: [] };
const totalCount = Object.keys(avatars).length;
const bar = new ProgressBar(
`Fetching avatars of ${totalCount} things :bar`,
totalCount,
);
await Promise.all(
Object.entries(avatars).map(async ([key, { url, avatar }]) => {
if (!(locales.default in avatars[key].avatar.name!.locales)) {
console.warn(`Avatar ${key} has no ${locales.default} name`);
bar.tick();
return;
}
gallery.avatars.push(avatar);
const content = await fetch(url)
.then((response) => response.arrayBuffer())
.then((buffer) => new Uint8Array(buffer));
await uploader.add({
path: `gallery/avatars/${key}${avatar.extension}`,
content,
});
bar.tick();
}),
);
await uploader.add({
path: "definitions/gallery.pb",
content: Gallery.encode(gallery).finish(),
});
await uploader.flush();
}
-64
View File
@@ -1,64 +0,0 @@
import { config } from "dotenv";
import { argv } from "process";
import { boosterIcons } from "./boosterIcons";
import { camouflageIcons } from "./camouflageIcons";
import { vfs } from "./constants";
import { consumableProvisionIcons } from "./consumableProvisionIcons";
import { currencies } from "./currencies";
import { definitions } from "./definitions";
import { equipmentIcons } from "./equipmentIcons";
import { flags } from "./flags";
import { gameModeBanners } from "./gameModeBanners";
import { glossary } from "./glossary";
import { migration } from "./migration";
import { moduleIcons } from "./moduleIcons";
import { popularTanks } from "./popularTanks";
import { shellIcons } from "./shellIcons";
import { skillIcons } from "./skillIcons";
import { tankArmors } from "./tankArmors";
import { tankIcons } from "./tankIcons";
import { tankModels } from "./tankModels";
config();
const allTargets = argv.includes("--all-targets");
const targets = argv
.find((argument) => argument.startsWith("--target"))
?.split("=")[1]
.split(",");
if (!targets && !allTargets) throw new Error("No target(s) specified");
const methods = [
definitions,
tankIcons,
tankModels,
tankArmors,
gameModeBanners,
currencies,
skillIcons,
flags,
shellIcons,
moduleIcons,
equipmentIcons,
consumableProvisionIcons,
camouflageIcons,
boosterIcons,
glossary,
migration,
popularTanks,
];
for (const method of methods) {
if (allTargets || targets?.includes(method.name)) {
try {
await method();
} catch (error) {
console.warn(`Failed method ${method.name}; skipping...`);
console.error(error);
}
}
}
vfs.dispose();
process.exit(0);
@@ -1,34 +0,0 @@
import { toUniqueId } from "@blitzkit/core";
import { AssetUploader } from "../core/github/assetUploader";
import { vfs } from "./constants";
import { VehicleDefinitionList } from "./definitions";
export async function migration() {
console.log("Building migration map...");
using uploader = new AssetUploader("migration map");
const map: Record<string, number> = {};
const nations = await vfs
.dir(`Data/XML/item_defs/vehicles`)
.then((files) => files.filter((nation) => nation !== "common"));
for (const nation of nations) {
const tankList = await vfs.xml<{ root: VehicleDefinitionList }>(
`Data/XML/item_defs/vehicles/${nation}/list.xml`,
);
for (const tankKey in tankList.root) {
const tank = tankList.root[tankKey];
const tankId = toUniqueId(nation, tank.id);
const davaKey = `${nation}:${tankKey}`;
map[davaKey] = tankId;
}
}
await uploader.add({
path: "definitions/migration.json",
content: new TextEncoder().encode(JSON.stringify(map)),
});
await uploader.flush();
}
@@ -1,77 +0,0 @@
import {
assertSecret,
fetchTankDefinitions,
PopularTanks,
} from "@blitzkit/core";
import { google } from "googleapis";
import { AssetUploader } from "../core/github/assetUploader";
export async function popularTanks() {
using uploader = new AssetUploader("popular tanks");
const tankDefinitions = await fetchTankDefinitions();
const auth = await google.auth.getClient({
keyFile: import.meta.env.GOOGLE_APPLICATION_CREDENTIALS,
scopes: ["https://www.googleapis.com/auth/analytics.readonly"],
});
const analytics = google.analyticsdata({ version: "v1beta", auth });
const report = await analytics.properties.runReport({
property: `properties/${assertSecret(
import.meta.env.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID,
)}`,
requestBody: {
dateRanges: [{ startDate: "7daysAgo", endDate: "today" }],
dimensions: [{ name: "pagePath" }],
metrics: [{ name: "screenPageViews" }],
orderBys: [{ metric: { metricName: "screenPageViews" }, desc: true }],
dimensionFilter: {
filter: {
fieldName: "pagePath",
stringFilter: {
matchType: "BEGINS_WITH",
value: "/tanks/",
},
},
},
},
});
if (!report.data.rows) {
throw new Error("No rows in report");
}
const tanks = report.data.rows
.filter(
(row) =>
row.dimensionValues &&
row.dimensionValues[0].value &&
row.dimensionValues[0].value !== "/tanks/" &&
row.metricValues &&
row.metricValues[0].value,
)
.map((row) => ({
id: Number(
row.dimensionValues![0].value!.match(
/\/tools\/tankopedia\/(\d+)\/?/,
)?.[1],
),
views: Number(row.metricValues![0].value!),
}))
.filter((row) => {
if (!(row.id in tankDefinitions.tanks)) {
console.warn("Skipping unknown popular tank", row.id);
return false;
}
return row.id in tankDefinitions.tanks;
})
.slice(0, 8)
.map(({ id, views }) => ({ id, views }));
const popularTanks: PopularTanks = { tanks };
await uploader.add({
path: "definitions/popular-tanks.pb",
content: PopularTanks.encode(popularTanks).finish(),
});
await uploader.flush();
}
@@ -1,64 +0,0 @@
import { NATION_IDS } from "@blitzkit/core";
import sharp from "sharp";
import { parsePackedSpriteRect } from "../core/blitz/parsePackedSpriteRect";
import { AssetUploader } from "../core/github/assetUploader";
import { vfs } from "./constants";
import type { VehicleDefinitionList } from "./definitions";
export async function tankIcons() {
console.log("Building tank icons...");
using uploader = new AssetUploader("tank icons");
const nations = await vfs
.dir(`Data/XML/item_defs/vehicles`)
.then((files) => files.filter((nation) => nation !== "common"));
for (const nation of nations) {
const tanks = await vfs.xml<{ root: VehicleDefinitionList }>(
`Data/XML/item_defs/vehicles/${nation}/list.xml`,
);
for (const tankKey in tanks.root) {
const tank = tanks.root[tankKey];
if (tankKey.includes("tutorial_bot")) continue;
const nationVehicleId = tank.id;
const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1;
const parameters = await vfs.yaml<TankParameters>(
`Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`,
);
const smallPath = `Data/${parameters.resourcesPath.smallIconPath
.replace(/~res:\//, "")
.replace(/\..+/, "")}.packed.webp`;
const bigPath = `Data/${parameters.resourcesPath.bigIconPath
.replace(/~res:\//, "")
.replace(/\..+/, "")}.packed.webp`;
const big = await extractPackedTankIcon(
await vfs.file(bigPath),
`tank ${tankKey} big`,
);
const small = await extractPackedTankIcon(
await vfs.file(smallPath),
`tank ${tankKey} small`,
);
if (big) {
await uploader.add({
content: big,
path: `icons/tanks/big/${id}.webp`,
});
}
if (small) {
await uploader.add({
content: small,
path: `icons/tanks/small/${id}.webp`,
});
}
}
}
await uploader.flush();
}
@@ -1,44 +0,0 @@
import { toUniqueId } from "@blitzkit/core";
import { NodeIO } from "@gltf-transform/core";
import { ALL_EXTENSIONS } from "@gltf-transform/extensions";
import { extractModel } from "../core/blitz/extractModel";
import { AssetUploader } from "../core/github/assetUploader";
import { vfs } from "./constants";
import { VehicleDefinitionList } from "./definitions";
import { TankParameters } from "./tankIcons";
export async function tankModels() {
console.log("Building tank models...");
using uploader = new AssetUploader("tank models");
const nodeIO = new NodeIO().registerExtensions(ALL_EXTENSIONS);
const nations = await vfs
.dir(`Data/XML/item_defs/vehicles`)
.then((files) => files.filter((nation) => nation !== "common"));
for (const nationIndex in nations) {
const nation = nations[nationIndex];
const tanks = await vfs.xml<{ root: VehicleDefinitionList }>(
`Data/XML/item_defs/vehicles/${nation}/list.xml`,
);
const entries = Object.entries(tanks.root);
for (const [tankKey, tank] of entries) {
if (tankKey.includes("tutorial_bot")) continue;
const id = toUniqueId(nation, tank.id);
const parameters = await vfs.yaml<TankParameters>(
`Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`,
);
const model = await extractModel(
vfs,
parameters.resourcesPath.blitzModelPath.replace(/\.sc2$/, ""),
);
const content = Buffer.from(await nodeIO.writeBinary(model));
await uploader.add({ path: `3d/tanks/models/${id}.glb`, content });
}
}
await uploader.flush();
}