mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-26 03:59:53 +01:00
Compare commits
7
Commits
c55f4c311f
...
d80f47a6c8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d80f47a6c8 | ||
|
|
3a9af7505f | ||
|
|
34ee954502 | ||
|
|
b9878a05a1 | ||
|
|
e9412bc404 | ||
|
|
6755115c8a | ||
|
|
e8ed4615fe |
No files matched your search
@@ -6,13 +6,15 @@ export async function boosterIcons() {
|
||||
console.log("Building booster icons...");
|
||||
|
||||
const uploader = new AssetUploader("booster icons");
|
||||
const boosterFiles = vfs
|
||||
const boosterFiles = await vfs
|
||||
.dir("Data/Gfx/Shared/boosters")
|
||||
.filter(
|
||||
(file) => !file.includes("@2x.txt") && !file.startsWith("texture0")
|
||||
.then((files) =>
|
||||
files.filter(
|
||||
(file) => !file.includes("@2x.txt") && !file.startsWith("texture0"),
|
||||
),
|
||||
);
|
||||
const image = sharp(
|
||||
await vfs.file(`Data/Gfx/Shared/boosters/texture0.packed.webp`)
|
||||
await vfs.file(`Data/Gfx/Shared/boosters/texture0.packed.webp`),
|
||||
);
|
||||
|
||||
for (const file of boosterFiles) {
|
||||
|
||||
@@ -6,7 +6,7 @@ export async function camouflageIcons() {
|
||||
|
||||
using uploader = new AssetUploader("camo icons");
|
||||
const content = await vfs.file(
|
||||
`Data/Gfx/UI/Hangar/IconCamouflage.packed.webp`
|
||||
`Data/Gfx/UI/Hangar/IconCamouflage.packed.webp`,
|
||||
);
|
||||
|
||||
await uploader.add({ content, path: "icons/camo.webp" });
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { assertSecret } from "@blitzkit/core";
|
||||
import { AbstractVFS } from "../core/vfs/abstract";
|
||||
import { LocalVFS } from "../core/vfs/local";
|
||||
import { SteamVFS } from "../core/vfs/steam";
|
||||
import { ZipVFS } from "../core/vfs/zip";
|
||||
|
||||
const steamPattern = /steam:(\d+)\/(\d+)/;
|
||||
const zipPattern = /zip:(.+)/;
|
||||
const localePattern = /local:"?(.+)"?/;
|
||||
|
||||
const source = Bun.argv
|
||||
.find((arg) => arg.startsWith("--source="))
|
||||
@@ -21,11 +23,14 @@ if (!source) {
|
||||
assertSecret(import.meta.env.STEAM_USERNAME),
|
||||
assertSecret(import.meta.env.STEAM_PASSWORD),
|
||||
app,
|
||||
depot
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -26,18 +26,12 @@ export async function consumableProvisionIcons() {
|
||||
];
|
||||
const styleSheets = await Promise.all(
|
||||
styles.map(async (path) =>
|
||||
Object.values(
|
||||
(
|
||||
await vfs.yaml<Mappings>(`Data/${path}`)
|
||||
).StyleSheets
|
||||
)
|
||||
)
|
||||
Object.values((await vfs.yaml<Mappings>(`Data/${path}`)).StyleSheets),
|
||||
),
|
||||
).then((array) => array.flat());
|
||||
|
||||
for (const match of (
|
||||
await vfs.text(
|
||||
`Data/XML/item_defs/vehicles/common/consumables/list.xml`
|
||||
)
|
||||
await vfs.text(`Data/XML/item_defs/vehicles/common/consumables/list.xml`)
|
||||
).matchAll(listItemsPattern)) {
|
||||
const consumablesCommon = await vfs.xml<{
|
||||
root: ConsumablesCommon;
|
||||
@@ -46,13 +40,13 @@ export async function consumableProvisionIcons() {
|
||||
for (const consumable of Object.values(consumablesCommon.root)) {
|
||||
const styleSheet = styleSheets.find((styleSheet) =>
|
||||
styleSheet.selectors.some((selector) =>
|
||||
selector.includes(`${consumable.icon} `)
|
||||
)
|
||||
selector.includes(`${consumable.icon} `),
|
||||
),
|
||||
);
|
||||
|
||||
if (!styleSheet) {
|
||||
console.warn(
|
||||
`No style sheet found for consumable ${consumable.icon}; skipping...`
|
||||
`No style sheet found for consumable ${consumable.icon}; skipping...`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -62,10 +56,8 @@ export async function consumableProvisionIcons() {
|
||||
.replace(".psd", "")
|
||||
.replace(".txt", "");
|
||||
|
||||
if (vfs.resolve(`Data/${configPath}.packed.webp`)) {
|
||||
const image = sharp(
|
||||
await vfs.file(`Data/${configPath}.packed.webp`)
|
||||
);
|
||||
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();
|
||||
|
||||
await uploader.add({
|
||||
@@ -78,8 +70,8 @@ export async function consumableProvisionIcons() {
|
||||
`${`Data/${configPath}`
|
||||
.split("/")
|
||||
.slice(0, -1)
|
||||
.join("/")}/texture0.packed.webp`
|
||||
)
|
||||
.join("/")}/texture0.packed.webp`,
|
||||
),
|
||||
);
|
||||
const sizes = (await vfs.text(`Data/${configPath}.txt`))
|
||||
.split("\n")[4]
|
||||
@@ -104,9 +96,7 @@ export async function consumableProvisionIcons() {
|
||||
}
|
||||
|
||||
for (const match of (
|
||||
await vfs.text(
|
||||
`Data/XML/item_defs/vehicles/common/provisions/list.xml`
|
||||
)
|
||||
await vfs.text(`Data/XML/item_defs/vehicles/common/provisions/list.xml`)
|
||||
).matchAll(listItemsPattern)) {
|
||||
const provisionsCommon = await vfs.xml<{
|
||||
root: ProvisionsCommon;
|
||||
@@ -115,13 +105,13 @@ export async function consumableProvisionIcons() {
|
||||
for (const provision of Object.values(provisionsCommon.root)) {
|
||||
const styleSheet = styleSheets.find((styleSheet) =>
|
||||
styleSheet.selectors.some((selector) =>
|
||||
selector.includes(`${provision.icon} `)
|
||||
)
|
||||
selector.includes(`${provision.icon} `),
|
||||
),
|
||||
);
|
||||
|
||||
if (!styleSheet) {
|
||||
console.warn(
|
||||
`No style sheet found for provision ${provision.icon}; skipping...`
|
||||
`No style sheet found for provision ${provision.icon}; skipping...`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -131,10 +121,8 @@ export async function consumableProvisionIcons() {
|
||||
.replace(".psd", "")
|
||||
.replace(".txt", "");
|
||||
|
||||
if (vfs.resolve(`Data/${configPath}.packed.webp`)) {
|
||||
const image = sharp(
|
||||
await vfs.file(`Data/${configPath}.packed.webp`)
|
||||
);
|
||||
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();
|
||||
|
||||
await uploader.add({
|
||||
@@ -147,8 +135,8 @@ export async function consumableProvisionIcons() {
|
||||
`${`Data/${configPath}`
|
||||
.split("/")
|
||||
.slice(0, -1)
|
||||
.join("/")}/texture0.packed.webp`
|
||||
)
|
||||
.join("/")}/texture0.packed.webp`,
|
||||
),
|
||||
);
|
||||
const sizes = (await vfs.text(`Data/${configPath}.txt`))
|
||||
.split("\n")[4]
|
||||
|
||||
@@ -20,7 +20,7 @@ export async function currencies() {
|
||||
|
||||
for (const [file, name] of ICONS) {
|
||||
const content = await sharp(
|
||||
await vfs.file(`Data/Gfx/Lobby/currency/${file}`)
|
||||
await vfs.file(`Data/Gfx/Lobby/currency/${file}`),
|
||||
)
|
||||
.trim()
|
||||
.toBuffer();
|
||||
@@ -36,14 +36,14 @@ export async function currencies() {
|
||||
.then((glossary) =>
|
||||
Object.values(glossary)
|
||||
.entries()
|
||||
.filter(([key]) => /^prx_season_\d+$/.test(`${key}`))
|
||||
.filter(([key]) => /^prx_season_\d+$/.test(`${key}`)),
|
||||
);
|
||||
|
||||
for (const [key, item] of glossary) {
|
||||
if (item.image_url === null) throw new Error(`No image_url for ${key}`);
|
||||
|
||||
const imageRaw = await fetch(item.image_url).then((response) =>
|
||||
response.arrayBuffer()
|
||||
response.arrayBuffer(),
|
||||
);
|
||||
const content = await sharp(imageRaw)
|
||||
.trim({
|
||||
|
||||
@@ -135,7 +135,7 @@ export interface ChassisDefinitionsList {
|
||||
}
|
||||
type VehicleDefinitionArmor = Record<
|
||||
string,
|
||||
number | { vehicleDamageFactor: 0; "#text": number }
|
||||
number | { vehicleDamageFactor: 0; "#text": number } | number[]
|
||||
>;
|
||||
interface UnlocksInner {
|
||||
cost: number | string;
|
||||
@@ -531,6 +531,22 @@ const blitzModuleTypeToBlitzkit: Record<keyof BlitzModuleType, ModuleType> = {
|
||||
vehicle: ModuleType.MODULE_TYPE_VEHICLE,
|
||||
};
|
||||
|
||||
function assignArmor(
|
||||
raw: VehicleDefinitionArmor[string],
|
||||
id: number,
|
||||
armor: Armor,
|
||||
) {
|
||||
if (typeof raw === "number") {
|
||||
armor.thickness[id] = raw;
|
||||
} else if (Array.isArray(raw)) {
|
||||
armor.thickness[id] = raw.at(-1)!;
|
||||
} else {
|
||||
if (!armor.spaced) armor.spaced = [];
|
||||
armor.thickness[id] = raw["#text"];
|
||||
armor.spaced.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
export async function definitions() {
|
||||
console.log("Building definitions...");
|
||||
|
||||
@@ -561,9 +577,9 @@ export async function definitions() {
|
||||
[TankClass.TANK_CLASS_TANK_DESTROYER]: { skills: [] },
|
||||
},
|
||||
};
|
||||
const nations = vfs
|
||||
const nations = await vfs
|
||||
.dir("Data/XML/item_defs/vehicles")
|
||||
.filter((nation) => nation !== "common");
|
||||
.then((files) => files.filter((nation) => nation !== "common"));
|
||||
const tankStringIdMap: Record<string, number> = {};
|
||||
const optionalDevices = await vfs.xml<{ root: OptionalDevices }>(
|
||||
"Data/XML/item_defs/vehicles/common/optional_devices.xml",
|
||||
@@ -923,13 +939,7 @@ export async function definitions() {
|
||||
const armorId = parseInt(armorIdString);
|
||||
const armorRaw = tankDefinition.root.hull.armor[name];
|
||||
|
||||
if (typeof armorRaw === "number") {
|
||||
hullArmor.thickness[armorId] = armorRaw;
|
||||
} else {
|
||||
if (!hullArmor.spaced) hullArmor.spaced = [];
|
||||
hullArmor.thickness[armorId] = armorRaw["#text"];
|
||||
hullArmor.spaced.push(armorId);
|
||||
}
|
||||
assignArmor(armorRaw, armorId, hullArmor);
|
||||
});
|
||||
const crew: Crew[] = [];
|
||||
const fixedCamouflage = tankTags.includes("eventCamouflage_user");
|
||||
@@ -1151,13 +1161,7 @@ export async function definitions() {
|
||||
const armorId = parseInt(armorIdString);
|
||||
const armorRaw = turret.armor[name];
|
||||
|
||||
if (typeof armorRaw === "number") {
|
||||
turretArmor.thickness[armorId] = armorRaw;
|
||||
} else {
|
||||
if (!turretArmor.spaced) turretArmor.spaced = [];
|
||||
turretArmor.thickness[armorId] = armorRaw["#text"];
|
||||
turretArmor.spaced.push(armorId);
|
||||
}
|
||||
assignArmor(armorRaw, armorId, turretArmor);
|
||||
});
|
||||
|
||||
turret.userString;
|
||||
@@ -1288,13 +1292,8 @@ export async function definitions() {
|
||||
}
|
||||
const armorId = parseInt(armorIdString);
|
||||
const armorRaw = gun.armor[name];
|
||||
if (typeof armorRaw === "number") {
|
||||
gunArmor.thickness[armorId] = armorRaw;
|
||||
} else {
|
||||
if (!gunArmor.spaced) gunArmor.spaced = [];
|
||||
gunArmor.thickness[armorId] = armorRaw["#text"];
|
||||
gunArmor.spaced.push(armorId);
|
||||
}
|
||||
|
||||
assignArmor(armorRaw, armorId, gunArmor);
|
||||
});
|
||||
|
||||
tankDefinitions.tanks[tankId].turrets[turretIndex].guns.push({
|
||||
|
||||
@@ -17,15 +17,15 @@ export async function equipmentIcons() {
|
||||
|
||||
using uploader = new AssetUploader("equipment icons");
|
||||
const optionalDevices = await vfs.xml<{ root: OptionalDevices }>(
|
||||
`Data/XML/item_defs/vehicles/common/optional_devices.xml`
|
||||
`Data/XML/item_defs/vehicles/common/optional_devices.xml`,
|
||||
);
|
||||
const mappings = await vfs.yaml<Mappings>(
|
||||
`Data/UI/Screens3/Lobby/Inventory/OptionalDevices/OptionalDevicesItemImage.style.yaml`
|
||||
`Data/UI/Screens3/Lobby/Inventory/OptionalDevices/OptionalDevicesItemImage.style.yaml`,
|
||||
);
|
||||
const image = sharp(
|
||||
await vfs.file(
|
||||
`Data/Gfx/UI/InventoryIcons/Big/OptionalDevices/texture0.packed.webp`
|
||||
)
|
||||
`Data/Gfx/UI/InventoryIcons/Big/OptionalDevices/texture0.packed.webp`,
|
||||
),
|
||||
);
|
||||
|
||||
for (const optionalDeviceKey in optionalDevices.root) {
|
||||
@@ -35,8 +35,8 @@ export async function equipmentIcons() {
|
||||
|
||||
const mapping = mappings.StyleSheets.find((mapping) =>
|
||||
mapping.selectors.includes(
|
||||
`.optional_device_item.${optionalDevice.icon} #Img`
|
||||
)
|
||||
`.optional_device_item.${optionalDevice.icon} #Img`,
|
||||
),
|
||||
);
|
||||
|
||||
if (!mapping) {
|
||||
@@ -49,9 +49,7 @@ export async function equipmentIcons() {
|
||||
|
||||
if (configPath.startsWith("Gfx/Lobby")) {
|
||||
const configPathWebp = configPath.replace(".txt", "");
|
||||
const image = sharp(
|
||||
await vfs.file(`Data/${configPathWebp}.packed.webp`)
|
||||
);
|
||||
const image = sharp(await vfs.file(`Data/${configPathWebp}.packed.webp`));
|
||||
const content = await image.trim().toBuffer();
|
||||
|
||||
await uploader.add({
|
||||
|
||||
@@ -6,20 +6,32 @@ export async function flags() {
|
||||
console.log("Building flags...");
|
||||
|
||||
using uploader = new AssetUploader("flags");
|
||||
const circleFiles = vfs.dir(`Data/Gfx/Lobby/flags`).filter(
|
||||
(flag) =>
|
||||
flag.startsWith("flag_profile-stat_") &&
|
||||
!flag.endsWith("@2x.packed.webp")
|
||||
const circleFiles = await vfs
|
||||
.dir(`Data/Gfx/Lobby/flags`)
|
||||
.then((files) =>
|
||||
files.filter(
|
||||
(flag) =>
|
||||
flag.startsWith("flag_profile-stat_") &&
|
||||
!flag.endsWith("@2x.packed.webp"),
|
||||
),
|
||||
);
|
||||
const scratchedFiles = vfs.dir(`Data/Gfx/Lobby/flags`).filter(
|
||||
const scratchedFiles = await vfs
|
||||
.dir(`Data/Gfx/Lobby/flags`)
|
||||
.then((files) =>
|
||||
files.filter(
|
||||
(flag) =>
|
||||
flag.startsWith("flag_tutor-tank_") &&
|
||||
!flag.endsWith("@2x.packed.webp")
|
||||
)
|
||||
const fadedFiles = vfs.dir(`Data/Gfx/Lobby/flags`).filter(
|
||||
(flag) =>
|
||||
flag.startsWith("flag_filter_") && flag.endsWith("@2x.packed.webp")
|
||||
)
|
||||
!flag.endsWith("@2x.packed.webp"),
|
||||
),
|
||||
);
|
||||
const fadedFiles = await vfs
|
||||
.dir(`Data/Gfx/Lobby/flags`)
|
||||
.then((files) =>
|
||||
files.filter(
|
||||
(flag) =>
|
||||
flag.startsWith("flag_filter_") && flag.endsWith("@2x.packed.webp"),
|
||||
),
|
||||
);
|
||||
|
||||
for (const flag of circleFiles) {
|
||||
const image = sharp(await vfs.file(`Data/Gfx/Lobby/flags/${flag}`));
|
||||
@@ -43,9 +55,7 @@ export async function flags() {
|
||||
}
|
||||
|
||||
for (const flag of fadedFiles) {
|
||||
const content = await sharp(
|
||||
await vfs.file(`Data/Gfx/Lobby/flags/${flag}`)
|
||||
)
|
||||
const content = await sharp(await vfs.file(`Data/Gfx/Lobby/flags/${flag}`))
|
||||
.trim({
|
||||
threshold: 100,
|
||||
background: { r: 0, g: 0, b: 0, alpha: 0 },
|
||||
|
||||
@@ -8,35 +8,33 @@ export async function gameModeBanners() {
|
||||
|
||||
using uploader = new AssetUploader("game mode banners");
|
||||
const gameTypeSelectorStyles = await vfs.yaml<SquadBattleTypeStyles>(
|
||||
`Data/UI/Screens/Lobby/Hangar/GameTypeSelector.yaml`
|
||||
`Data/UI/Screens/Lobby/Hangar/GameTypeSelector.yaml`,
|
||||
);
|
||||
const squadBattleTypeStyles = await vfs.yaml<SquadBattleTypeStyles>(
|
||||
`Data/UI/Screens3/Lobby/Hangar/Squad/SquadBattleType.yaml`
|
||||
`Data/UI/Screens3/Lobby/Hangar/Squad/SquadBattleType.yaml`,
|
||||
);
|
||||
const bannerMatches: { name: string; path: string }[] = [];
|
||||
|
||||
for (const match of gameTypeSelectorStyles.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll(
|
||||
/eGameMode\.([a-zA-Z]+) -> "~res:([^"]+)"/g
|
||||
/eGameMode\.([a-zA-Z]+) -> "~res:([^"]+)"/g,
|
||||
)) {
|
||||
bannerMatches.push({ name: match[1], path: match[2] });
|
||||
}
|
||||
|
||||
for (const match of squadBattleTypeStyles.Prototypes[0].components.UIDataLocalBindingsComponent.data[1][2].matchAll(
|
||||
/"(\d+)" -> "battleType\/([a-zA-Z]+)"/g
|
||||
/"(\d+)" -> "battleType\/([a-zA-Z]+)"/g,
|
||||
)) {
|
||||
const id = Number(match[1]);
|
||||
const name = match[2];
|
||||
let path = bannerMatches.find(
|
||||
(bannerMatch) => bannerMatch.name.toLowerCase() === name.toLowerCase()
|
||||
(bannerMatch) => bannerMatch.name.toLowerCase() === name.toLowerCase(),
|
||||
)?.path;
|
||||
|
||||
if (path === undefined) {
|
||||
path = `/Gfx/UI/Hangar/GameTypes/battle-type_${name.toLowerCase()}`;
|
||||
}
|
||||
|
||||
const content = await sharp(
|
||||
await vfs.file(`Data${path}.packed.webp`)
|
||||
)
|
||||
const content = await sharp(await vfs.file(`Data${path}.packed.webp`))
|
||||
.trim({ background: { r: 0, g: 0, b: 0, alpha: 0 } })
|
||||
.toBuffer();
|
||||
|
||||
|
||||
@@ -8,11 +8,13 @@ export async function migration() {
|
||||
|
||||
using uploader = new AssetUploader("migration map");
|
||||
const map: Record<string, number> = {};
|
||||
const nations = vfs.dir(`Data/XML/item_defs/vehicles`).filter((nation) => nation !== "common");
|
||||
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`
|
||||
`Data/XML/item_defs/vehicles/${nation}/list.xml`,
|
||||
);
|
||||
|
||||
for (const tankKey in tankList.root) {
|
||||
|
||||
@@ -6,15 +6,20 @@ export async function moduleIcons() {
|
||||
console.log("Building module icons...");
|
||||
|
||||
using uploader = new AssetUploader("module icons");
|
||||
const files = vfs.dir(`Data/Gfx/UI/ModulesTechTree`).filter(
|
||||
(file) => !file.endsWith("@2x.packed.webp") && file.startsWith("vehicle")
|
||||
)
|
||||
const files = await vfs
|
||||
.dir(`Data/Gfx/UI/ModulesTechTree`)
|
||||
.then((files) =>
|
||||
files.filter(
|
||||
(file) =>
|
||||
!file.endsWith("@2x.packed.webp") && file.startsWith("vehicle"),
|
||||
),
|
||||
);
|
||||
|
||||
for (const file of files) {
|
||||
const nameRaw = file.match(/vehicle(.+)\.packed\.webp/)![1];
|
||||
const name = nameRaw[0].toLowerCase() + nameRaw.slice(1);
|
||||
const content = await sharp(
|
||||
await vfs.file(`Data/Gfx/UI/ModulesTechTree/${file}`)
|
||||
await vfs.file(`Data/Gfx/UI/ModulesTechTree/${file}`),
|
||||
)
|
||||
.trim()
|
||||
.toBuffer();
|
||||
|
||||
@@ -17,7 +17,7 @@ export async function popularTanks() {
|
||||
const analytics = google.analyticsdata({ version: "v1beta", auth });
|
||||
const report = await analytics.properties.runReport({
|
||||
property: `properties/${assertSecret(
|
||||
import.meta.env.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID
|
||||
import.meta.env.PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID,
|
||||
)}`,
|
||||
requestBody: {
|
||||
dateRanges: [{ startDate: "7daysAgo", endDate: "today" }],
|
||||
@@ -47,13 +47,13 @@ export async function popularTanks() {
|
||||
row.dimensionValues[0].value &&
|
||||
row.dimensionValues[0].value !== "/tanks/" &&
|
||||
row.metricValues &&
|
||||
row.metricValues[0].value
|
||||
row.metricValues[0].value,
|
||||
)
|
||||
.map((row) => ({
|
||||
id: Number(
|
||||
row.dimensionValues![0].value!.match(
|
||||
/\/tools\/tankopedia\/(\d+)\/?/
|
||||
)?.[1]
|
||||
/\/tools\/tankopedia\/(\d+)\/?/,
|
||||
)?.[1],
|
||||
),
|
||||
views: Number(row.metricValues![0].value!),
|
||||
}))
|
||||
|
||||
@@ -8,19 +8,17 @@ export async function shellIcons() {
|
||||
using uploader = new AssetUploader("shell icons");
|
||||
const image = sharp(
|
||||
await vfs.file(
|
||||
`Data/Gfx/Shared/tank-supply/ammunition/big/texture0.packed.webp`
|
||||
)
|
||||
`Data/Gfx/Shared/tank-supply/ammunition/big/texture0.packed.webp`,
|
||||
),
|
||||
);
|
||||
const files = vfs.dir(
|
||||
`Data/Gfx/Shared/tank-supply/ammunition/big`
|
||||
).filter((file) => file.endsWith("_l.txt"))
|
||||
const files = await vfs
|
||||
.dir(`Data/Gfx/Shared/tank-supply/ammunition/big`)
|
||||
.then((files) => files.filter((file) => file.endsWith("_l.txt")));
|
||||
|
||||
for (const file of files) {
|
||||
const name = file.match(/(.+)_l\.txt/)![1];
|
||||
const sizes = (
|
||||
await vfs.text(
|
||||
`Data/Gfx/Shared/tank-supply/ammunition/big/${file}`
|
||||
)
|
||||
await vfs.text(`Data/Gfx/Shared/tank-supply/ammunition/big/${file}`)
|
||||
)
|
||||
.split("\n")[4]
|
||||
.split(" ")
|
||||
|
||||
@@ -26,7 +26,7 @@ export async function skillIcons() {
|
||||
console.log("Building skill icons...");
|
||||
|
||||
const avatar = await vfs.xml<{ root: Avatar }>(
|
||||
`Data/XML/item_defs/tankmen/avatar.xml`
|
||||
`Data/XML/item_defs/tankmen/avatar.xml`,
|
||||
);
|
||||
const uploader = new AssetUploader("skill icons");
|
||||
|
||||
|
||||
@@ -10,11 +10,13 @@ export async function tankArmors() {
|
||||
|
||||
using uploader = new AssetUploader("tank armors");
|
||||
const nodeIO = new NodeIO();
|
||||
const nations = vfs.dir(`Data/XML/item_defs/vehicles`).filter((nation) => nation !== "common");
|
||||
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`
|
||||
`Data/XML/item_defs/vehicles/${nation}/list.xml`,
|
||||
);
|
||||
|
||||
for (const tankKey in tanks.root) {
|
||||
|
||||
@@ -41,11 +41,13 @@ export async function tankIcons() {
|
||||
console.log("Building tank icons...");
|
||||
|
||||
using uploader = new AssetUploader("tank icons");
|
||||
const nations = vfs.dir(`Data/XML/item_defs/vehicles`).filter((nation) => nation !== "common")
|
||||
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`
|
||||
`Data/XML/item_defs/vehicles/${nation}/list.xml`,
|
||||
);
|
||||
|
||||
for (const tankKey in tanks.root) {
|
||||
@@ -57,7 +59,7 @@ export async function tankIcons() {
|
||||
const id = (nationVehicleId << 8) + (NATION_IDS[nation] << 4) + 1;
|
||||
|
||||
const parameters = await vfs.yaml<TankParameters>(
|
||||
`Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`
|
||||
`Data/3d/Tanks/Parameters/${nation}/${tankKey}.yaml`,
|
||||
);
|
||||
const smallPath = `Data/${parameters.resourcesPath.smallIconPath
|
||||
.replace(/~res:\//, "")
|
||||
|
||||
@@ -12,9 +12,9 @@ export async function tankModels() {
|
||||
|
||||
using uploader = new AssetUploader("tank models");
|
||||
const nodeIO = new NodeIO().registerExtensions(ALL_EXTENSIONS);
|
||||
const nations = vfs
|
||||
const nations = await vfs
|
||||
.dir(`Data/XML/item_defs/vehicles`)
|
||||
.filter((nation) => nation !== "common");
|
||||
.then((files) => files.filter((nation) => nation !== "common"));
|
||||
|
||||
for (const nationIndex in nations) {
|
||||
const nation = nations[nationIndex];
|
||||
@@ -41,4 +41,4 @@ export async function tankModels() {
|
||||
}
|
||||
|
||||
await uploader.flush();
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { vfs } from "../../../buildAssets/constants";
|
||||
|
||||
export async function readTexture(path: string) {
|
||||
const ddsTexturePath = path.replace(".tex", ".dx11.dds");
|
||||
const isDds = vfs.resolve(ddsTexturePath);
|
||||
const isDds = await vfs.resolve(ddsTexturePath);
|
||||
const resolvedTexturePath = isDds
|
||||
? ddsTexturePath
|
||||
: ddsTexturePath.replace(".dds", ".pvr");
|
||||
|
||||
@@ -43,7 +43,7 @@ export class AssetUploader {
|
||||
|
||||
constructor(public message: string) {
|
||||
[this.owner, this.repo] = assertSecret(
|
||||
import.meta.env.PUBLIC_ASSET_REPO
|
||||
import.meta.env.PUBLIC_ASSET_REPO,
|
||||
).split("/");
|
||||
this.branch = assertSecret(import.meta.env.PUBLIC_ASSET_BRANCH);
|
||||
}
|
||||
@@ -52,7 +52,7 @@ export class AssetUploader {
|
||||
const blobPath = `${this.owner}/${this.repo}/${this.branch}/${change.path}`;
|
||||
const response = await fetch(
|
||||
`https://raw.githubusercontent.com/${blobPath}`,
|
||||
{ headers: { "Accept-Encoding": "identity" } }
|
||||
{ headers: { "Accept-Encoding": "identity" } },
|
||||
);
|
||||
let diff: { change: number; status: DiffStatus };
|
||||
|
||||
@@ -71,7 +71,7 @@ export class AssetUploader {
|
||||
} else if (response.status === 200) {
|
||||
if (
|
||||
this.heuristicFormats.some((format) =>
|
||||
change.path.endsWith(`.${format}`)
|
||||
change.path.endsWith(`.${format}`),
|
||||
)
|
||||
) {
|
||||
/**
|
||||
@@ -94,7 +94,7 @@ export class AssetUploader {
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
`Unexpected status code ${response.status} for ${change.path}`
|
||||
`Unexpected status code ${response.status} for ${change.path}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ export class AssetUploader {
|
||||
console.log(
|
||||
`${diff.status === DiffStatus.New ? "🟢" : "🟡"} (${
|
||||
diff.change > 0 ? "+" : ""
|
||||
}${diff.change.toLocaleString()}B) ${change.path}`
|
||||
}${diff.change.toLocaleString()}B) ${change.path}`,
|
||||
);
|
||||
|
||||
// flush BEFORE going over limit
|
||||
@@ -160,8 +160,8 @@ export class AssetUploader {
|
||||
await new Promise((resolve) =>
|
||||
setTimeout(
|
||||
resolve,
|
||||
Math.max(0, this.time_between_blobs + (start - end))
|
||||
)
|
||||
Math.max(0, this.time_between_blobs + (start - end)),
|
||||
),
|
||||
);
|
||||
|
||||
blobs.push({ sha, path: change.path, mode: "100644", type: "blob" });
|
||||
@@ -170,11 +170,11 @@ export class AssetUploader {
|
||||
break;
|
||||
} catch {
|
||||
console.warn(
|
||||
` failed ${change.path}; retrying in ${this.time_per_blob}ms...`
|
||||
` failed ${change.path}; retrying in ${this.time_per_blob}ms...`,
|
||||
);
|
||||
|
||||
await new Promise((resolve) =>
|
||||
setTimeout(resolve, this.time_per_blob)
|
||||
setTimeout(resolve, this.time_per_blob),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -209,7 +209,7 @@ export class AssetUploader {
|
||||
[Symbol.dispose]() {
|
||||
if (this.changes.length > 0) {
|
||||
throw new Error(
|
||||
`${this.changes.length} uncommitted changes. Did you forget to flush?`
|
||||
`${this.changes.length} uncommitted changes. Did you forget to flush?`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,24 +11,22 @@ export abstract class AbstractVFS {
|
||||
|
||||
abstract dispose(): void;
|
||||
|
||||
abstract has(path: string): boolean;
|
||||
abstract has(path: string): Promise<boolean>;
|
||||
|
||||
abstract raw(path: string): Promise<Uint8Array>;
|
||||
|
||||
abstract paths(): string[];
|
||||
|
||||
resolve(path: string) {
|
||||
async resolve(path: string) {
|
||||
const normalized = normalize(path);
|
||||
if (this.has(normalized)) return normalized;
|
||||
if (await this.has(normalized)) return normalized;
|
||||
|
||||
const dvplPath = `${normalized}.dvpl`;
|
||||
if (this.has(dvplPath)) return dvplPath;
|
||||
if (await this.has(dvplPath)) return dvplPath;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
assert(path: string) {
|
||||
const resolved = this.resolve(path);
|
||||
async assert(path: string) {
|
||||
const resolved = await this.resolve(path);
|
||||
|
||||
if (resolved === null) throw new Error(`File not found: ${path}`);
|
||||
|
||||
@@ -36,7 +34,7 @@ export abstract class AbstractVFS {
|
||||
}
|
||||
|
||||
async file(file: string) {
|
||||
const resolved = this.assert(file);
|
||||
const resolved = await this.assert(file);
|
||||
const raw = await this.raw(resolved);
|
||||
let buffer = raw;
|
||||
|
||||
@@ -47,21 +45,7 @@ export abstract class AbstractVFS {
|
||||
return buffer;
|
||||
}
|
||||
|
||||
dir(path: string) {
|
||||
const parentSegments = path.split("/").length;
|
||||
const children = new Set<string>();
|
||||
|
||||
for (const child of this.paths()) {
|
||||
if (!child.startsWith(path) || child === path) continue;
|
||||
|
||||
const childSegments = child.split("/");
|
||||
const nextSegment = childSegments[parentSegments];
|
||||
|
||||
children.add(nextSegment);
|
||||
}
|
||||
|
||||
return Array.from(children);
|
||||
}
|
||||
abstract dir(path: string): Promise<string[]>;
|
||||
|
||||
async text(path: string) {
|
||||
const file = await this.file(path);
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { readdir } from "node:fs/promises";
|
||||
import { AbstractVFS } from "./abstract";
|
||||
|
||||
export class LocalVFS extends AbstractVFS {
|
||||
constructor(private base: string) {
|
||||
super();
|
||||
}
|
||||
|
||||
async init() {
|
||||
return this;
|
||||
}
|
||||
|
||||
async has(path: string) {
|
||||
const file = Bun.file(`${this.base}/${path}`);
|
||||
return await file.exists();
|
||||
}
|
||||
|
||||
async raw(path: string) {
|
||||
const file = Bun.file(`${this.base}/${path}`);
|
||||
const buffer = await file.arrayBuffer();
|
||||
const array = new Uint8Array(buffer);
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
async dir(path: string) {
|
||||
return await readdir(`${this.base}/${path}`);
|
||||
}
|
||||
|
||||
dispose() {}
|
||||
}
|
||||
@@ -76,7 +76,7 @@ export class SteamVFS extends AbstractVFS {
|
||||
return this;
|
||||
}
|
||||
|
||||
has(path: string) {
|
||||
async has(path: string) {
|
||||
return this.manifest.has(path);
|
||||
}
|
||||
|
||||
@@ -89,8 +89,20 @@ export class SteamVFS extends AbstractVFS {
|
||||
return new Uint8Array(downloaded.file);
|
||||
}
|
||||
|
||||
paths() {
|
||||
return Array.from(this.manifest.keys());
|
||||
async dir(path: string) {
|
||||
const parentSegments = path.split("/").length;
|
||||
const children = new Set<string>();
|
||||
|
||||
for (const child of this.manifest.keys()) {
|
||||
if (!child.startsWith(path) || child === path) continue;
|
||||
|
||||
const childSegments = child.split("/");
|
||||
const nextSegment = childSegments[parentSegments];
|
||||
|
||||
children.add(nextSegment);
|
||||
}
|
||||
|
||||
return Array.from(children);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
|
||||
@@ -5,7 +5,10 @@ import { AbstractVFS } from "./abstract";
|
||||
export class ZipVFS extends AbstractVFS {
|
||||
entries = new Map<string, ZipEntry>();
|
||||
|
||||
constructor(private url: string, private root = "app/") {
|
||||
constructor(
|
||||
private url: string,
|
||||
private root = "app/",
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -21,7 +24,7 @@ export class ZipVFS extends AbstractVFS {
|
||||
return this;
|
||||
}
|
||||
|
||||
has(path: string) {
|
||||
async has(path: string) {
|
||||
return this.entries.has(path);
|
||||
}
|
||||
|
||||
@@ -30,8 +33,20 @@ export class ZipVFS extends AbstractVFS {
|
||||
return new Uint8Array(arrayBuffer);
|
||||
}
|
||||
|
||||
paths() {
|
||||
return Array.from(this.entries.keys());
|
||||
async dir(path: string) {
|
||||
const parentSegments = path.split("/").length;
|
||||
const children = new Set<string>();
|
||||
|
||||
for (const child of this.entries.keys()) {
|
||||
if (!child.startsWith(path) || child === path) continue;
|
||||
|
||||
const childSegments = child.split("/");
|
||||
const nextSegment = childSegments[parentSegments];
|
||||
|
||||
children.add(nextSegment);
|
||||
}
|
||||
|
||||
return Array.from(children);
|
||||
}
|
||||
|
||||
dispose() {}
|
||||
|
||||
@@ -97,7 +97,7 @@ const strings = getStrings(locale);
|
||||
window.innerHeight) **
|
||||
2,
|
||||
0,
|
||||
1
|
||||
1,
|
||||
);
|
||||
|
||||
// https://www.desmos.com/calculator/ndz2poxk32
|
||||
@@ -145,11 +145,9 @@ const strings = getStrings(locale);
|
||||
</Flex>
|
||||
</Heading>
|
||||
|
||||
<Text color="gray" align="center" size="4"
|
||||
>{
|
||||
literals(strings.website.home.hero.credit, { name: "TrèsAbhi" })
|
||||
}</Text
|
||||
>
|
||||
<Text color="gray" align="center" size="4">
|
||||
{literals(strings.website.home.hero.credit, { name: "TrèsAbhi" })}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
Reference in new issue
Block a user