mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 20:19:49 +02:00
some script cleanup
This commit is contained in:
1 parent
f1a1c78588
commit
dafa834d28
7 files changed
+7
-705
No files matched your search
+2
-1
@@ -6,7 +6,8 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cd packages/website && bun run dev",
|
"dev": "cd packages/website && bun run dev",
|
||||||
"build": "cd packages/website && bun run build",
|
"build": "cd packages/website && bun run build",
|
||||||
"discover-ids": "cd packages/scripts && bun run discover-ids"
|
"discover-ids": "cd packages/scripts && bun run discover-ids",
|
||||||
|
"build-protos": "cd packages/scripts && bun run build-protos"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "@blitzkit/scripts",
|
"name": "@blitzkit/scripts",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"discover-ids": "bun src/discoverIds/index.ts",
|
||||||
|
"build-protos": "bun src/protos.ts"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blitzkit/closed": "workspace:*",
|
"@blitzkit/closed": "workspace:*",
|
||||||
"@blitzkit/core": "workspace:*",
|
"@blitzkit/core": "workspace:*",
|
||||||
@@ -34,9 +39,5 @@
|
|||||||
"unzipit": "^1.4.3",
|
"unzipit": "^1.4.3",
|
||||||
"yaml": "^2.8.1"
|
"yaml": "^2.8.1"
|
||||||
},
|
},
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"discover-ids": "bun src/discoverIds/index.ts"
|
|
||||||
},
|
|
||||||
"type": "module"
|
"type": "module"
|
||||||
}
|
}
|
||||||
@@ -1,229 +0,0 @@
|
|||||||
import {
|
|
||||||
AverageDefinitions,
|
|
||||||
AverageDefinitionsAllStats,
|
|
||||||
AverageDefinitionsEntry,
|
|
||||||
type IndividualTankStats,
|
|
||||||
REGIONS,
|
|
||||||
type Region,
|
|
||||||
Samples,
|
|
||||||
averageDefinitionsAllStatsKeys,
|
|
||||||
emptySamples,
|
|
||||||
getAccountInfo,
|
|
||||||
getTankStats,
|
|
||||||
idToRegion,
|
|
||||||
} from "@blitzkit/core";
|
|
||||||
import { chunk, times } from "lodash-es";
|
|
||||||
import { AssetUploader } from "./core/github/assetUploader";
|
|
||||||
import { fetchPreDiscoveredIds } from "./core/github/fetchPreDiscoveredIds";
|
|
||||||
|
|
||||||
interface DataPoint {
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
w: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MINUTE = 60 * 1000;
|
|
||||||
const HOUR = 60 * MINUTE;
|
|
||||||
const DAY = 24 * HOUR;
|
|
||||||
// const MAX_ACTIVITY_TIME = 120 * DAY;
|
|
||||||
// const MIN_BATTLES = 5000;
|
|
||||||
const RUN_TIME = 5 * HOUR + 55 * MINUTE;
|
|
||||||
const THREADS = 10;
|
|
||||||
const PLAYER_IDS_PER_CALL = 100;
|
|
||||||
|
|
||||||
const startTime = Date.now();
|
|
||||||
const preDiscoveredIds = await fetchPreDiscoveredIds();
|
|
||||||
const playerIds: Record<Region, number[][]> = {
|
|
||||||
asia: chunk(
|
|
||||||
preDiscoveredIds.filter((id) => idToRegion(id) === "asia"),
|
|
||||||
PLAYER_IDS_PER_CALL,
|
|
||||||
),
|
|
||||||
com: chunk(
|
|
||||||
preDiscoveredIds.filter((id) => idToRegion(id) === "com"),
|
|
||||||
PLAYER_IDS_PER_CALL,
|
|
||||||
),
|
|
||||||
eu: chunk(
|
|
||||||
preDiscoveredIds.filter((id) => idToRegion(id) === "eu"),
|
|
||||||
PLAYER_IDS_PER_CALL,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
let regionIndex = 0;
|
|
||||||
const samples: Samples = { ...emptySamples };
|
|
||||||
const availableRegions = [...REGIONS];
|
|
||||||
const tankIds: number[] = [];
|
|
||||||
const tanksSorted: Record<number, IndividualTankStats[]> = {};
|
|
||||||
let postWorkRequested = false;
|
|
||||||
|
|
||||||
times(THREADS, async () => {
|
|
||||||
while (availableRegions.length > 0 && startTime + RUN_TIME > Date.now()) {
|
|
||||||
const region = availableRegions[regionIndex];
|
|
||||||
const regionIds = playerIds[region];
|
|
||||||
const [ids] = regionIds.splice(
|
|
||||||
Math.floor(Math.random() * regionIds.length),
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
const accountInfo = await getAccountInfo(region, ids, undefined, {
|
|
||||||
fields: "last_battle_time,statistics.all.battles",
|
|
||||||
});
|
|
||||||
|
|
||||||
const filteredIds = ids.filter((_, index) => {
|
|
||||||
const info = accountInfo[index];
|
|
||||||
|
|
||||||
if (info === null) return false;
|
|
||||||
|
|
||||||
const timeSinceLastActivity = Date.now() - info.last_battle_time * 1000;
|
|
||||||
|
|
||||||
samples.total++;
|
|
||||||
if (timeSinceLastActivity <= 120 * DAY) samples.d_120++;
|
|
||||||
if (timeSinceLastActivity <= 90 * DAY) samples.d_90++;
|
|
||||||
if (timeSinceLastActivity <= 60 * DAY) samples.d_60++;
|
|
||||||
if (timeSinceLastActivity <= 30 * DAY) samples.d_30++;
|
|
||||||
if (timeSinceLastActivity <= 7 * DAY) samples.d_7++;
|
|
||||||
if (timeSinceLastActivity <= 1 * DAY) samples.d_1++;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// return (
|
|
||||||
// timeSinceLastActivity <= MAX_ACTIVITY_TIME &&
|
|
||||||
// info.statistics.all.battles > MIN_BATTLES
|
|
||||||
// );
|
|
||||||
});
|
|
||||||
|
|
||||||
const players = await Promise.all(
|
|
||||||
filteredIds.map((id) =>
|
|
||||||
getTankStats(region, id, {
|
|
||||||
fields: "last_battle_time,battle_life_time,all,tank_id",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
players.forEach((tanks) => {
|
|
||||||
tanks?.forEach((tank) => {
|
|
||||||
if (tank.all.battles === 0) return;
|
|
||||||
|
|
||||||
if (!tankIds.includes(tank.tank_id)) {
|
|
||||||
console.log(
|
|
||||||
`Found new tank: ${tank.tank_id} (${tankIds.length + 1})`,
|
|
||||||
);
|
|
||||||
tankIds.push(tank.tank_id);
|
|
||||||
tanksSorted[tank.tank_id] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
tanksSorted[tank.tank_id].push(tank);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (regionIds.length === 0 && availableRegions.includes(region)) {
|
|
||||||
availableRegions.splice(availableRegions.indexOf(region), 1);
|
|
||||||
}
|
|
||||||
regionIndex = (regionIndex + 1) % availableRegions.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!postWorkRequested) {
|
|
||||||
postWorkRequested = true;
|
|
||||||
postWork();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function postWork() {
|
|
||||||
console.log(
|
|
||||||
`Generating statistics based on ${samples.d_120.toLocaleString()} players (${samples.total.toLocaleString()} checked in total) and ${
|
|
||||||
tankIds.length
|
|
||||||
} tanks...`,
|
|
||||||
);
|
|
||||||
|
|
||||||
using uploader = new AssetUploader("averages");
|
|
||||||
const averages: Record<number, AverageDefinitionsEntry> = {};
|
|
||||||
|
|
||||||
tankIds.forEach((id) => {
|
|
||||||
const tanksSortedEntry = tanksSorted[id];
|
|
||||||
// const tanks = tanksSortedEntry.filter(
|
|
||||||
// (tank) => Date.now() - tank.last_battle_time * 1000 <= MAX_ACTIVITY_TIME,
|
|
||||||
// );
|
|
||||||
const tanks = tanksSortedEntry;
|
|
||||||
const entry: {
|
|
||||||
mu: Partial<Record<keyof AverageDefinitionsAllStats, number>>;
|
|
||||||
sigma: Partial<Record<keyof AverageDefinitionsAllStats, number>>;
|
|
||||||
r: Partial<Record<keyof AverageDefinitionsAllStats, number>>;
|
|
||||||
samples: Samples;
|
|
||||||
} = {
|
|
||||||
mu: {},
|
|
||||||
sigma: {},
|
|
||||||
r: {},
|
|
||||||
samples: { ...emptySamples },
|
|
||||||
};
|
|
||||||
|
|
||||||
tanks.forEach((tank) => {
|
|
||||||
const timeSinceLastActivity = Date.now() - tank.last_battle_time * 1000;
|
|
||||||
|
|
||||||
entry.samples!.total++;
|
|
||||||
if (timeSinceLastActivity <= 120 * DAY) entry.samples!.d_120++;
|
|
||||||
if (timeSinceLastActivity <= 90 * DAY) entry.samples!.d_90++;
|
|
||||||
if (timeSinceLastActivity <= 60 * DAY) entry.samples!.d_60++;
|
|
||||||
if (timeSinceLastActivity <= 30 * DAY) entry.samples!.d_30++;
|
|
||||||
if (timeSinceLastActivity <= 7 * DAY) entry.samples!.d_7++;
|
|
||||||
if (timeSinceLastActivity <= 1 * DAY) entry.samples!.d_1++;
|
|
||||||
});
|
|
||||||
|
|
||||||
const dataWY = tanks.map((tank) => ({
|
|
||||||
w: tank.all.battles,
|
|
||||||
y: tank.all.wins / tank.all.battles,
|
|
||||||
}));
|
|
||||||
|
|
||||||
averageDefinitionsAllStatsKeys.forEach((key) => {
|
|
||||||
const data: DataPoint[] = dataWY.map(({ w, y }, index) => ({
|
|
||||||
w,
|
|
||||||
x:
|
|
||||||
(key === "battle_life_time"
|
|
||||||
? tanks[index].battle_life_time
|
|
||||||
: tanks[index].all[key]) / w,
|
|
||||||
y,
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (data.length === 0) return;
|
|
||||||
|
|
||||||
function sum(slicer: (data: DataPoint) => number) {
|
|
||||||
return data.reduce(
|
|
||||||
(accumulator, data) => accumulator + slicer(data),
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const sum_w = sum(({ w }) => w);
|
|
||||||
const sum_wx = sum(({ w, x }) => w * x);
|
|
||||||
const sum_wy = sum(({ w, y }) => w * y);
|
|
||||||
const x_bar = sum_wx / sum_w;
|
|
||||||
const y_bar = sum_wy / sum_w;
|
|
||||||
// undo normalization for mu to get a simple average
|
|
||||||
const mu = sum(({ w, x }) => w * x) / samples.d_120;
|
|
||||||
|
|
||||||
const sigma_numerator = sum(({ w, x }) => w * (x - x_bar) ** 2);
|
|
||||||
const sigma = Math.sqrt(sigma_numerator / sum_w);
|
|
||||||
|
|
||||||
const r_numerator = sum(({ w, x, y }) => w * (x - x_bar) * (y - y_bar));
|
|
||||||
const r_denominator_x = sum(({ w, x }) => w * (x - x_bar) ** 2);
|
|
||||||
const r_denominator_y = sum(({ w, y }) => w * (y - y_bar) ** 2);
|
|
||||||
const r = r_numerator / Math.sqrt(r_denominator_x * r_denominator_y);
|
|
||||||
|
|
||||||
entry.mu[key] = mu;
|
|
||||||
entry.sigma[key] = sigma;
|
|
||||||
entry.r[key] = isNaN(r) ? 0 : r;
|
|
||||||
});
|
|
||||||
|
|
||||||
averages[id] = entry as AverageDefinitionsEntry;
|
|
||||||
});
|
|
||||||
|
|
||||||
const time = Date.now();
|
|
||||||
const averageDefinitions = {
|
|
||||||
averages,
|
|
||||||
samples,
|
|
||||||
time,
|
|
||||||
} satisfies AverageDefinitions;
|
|
||||||
|
|
||||||
await uploader.add({
|
|
||||||
path: "averages.pb",
|
|
||||||
content: AverageDefinitions.encode(averageDefinitions).finish(),
|
|
||||||
});
|
|
||||||
|
|
||||||
await uploader.flush();
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import { readdir, readFile, writeFile } from "fs/promises";
|
|
||||||
|
|
||||||
const SOURCES = "../../packages/closed/protos";
|
|
||||||
const TARGET = "../../packages/closed/src/unreal/catalogItemAccessor.ts";
|
|
||||||
|
|
||||||
const componentPattern = /^message (\w+)Component {/gm;
|
|
||||||
|
|
||||||
const files = await readdir(SOURCES).then((files) =>
|
|
||||||
files.filter(
|
|
||||||
(file) =>
|
|
||||||
file.startsWith("blitz_static_") && file.endsWith("_component.proto")
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const imports: { name: string; file: string }[] = [];
|
|
||||||
|
|
||||||
for (const file of files) {
|
|
||||||
const content = await readFile(`${SOURCES}/${file}`).then((buffer) =>
|
|
||||||
buffer.toString()
|
|
||||||
);
|
|
||||||
const components = content.matchAll(componentPattern);
|
|
||||||
|
|
||||||
for (const match of components) {
|
|
||||||
const name = match[1];
|
|
||||||
const duplicate = imports.find((_import) => _import.name === name);
|
|
||||||
|
|
||||||
if (duplicate) {
|
|
||||||
console.warn(
|
|
||||||
`Duplicate component ${name} across ${duplicate.file}.proto and ${file}`
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const importFile = file.replace(".proto", "");
|
|
||||||
|
|
||||||
imports.push({ name, file: importFile });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let content = "";
|
|
||||||
|
|
||||||
for (const _import of imports) {
|
|
||||||
content += `import { ${_import.name}Component } from "@protos/${_import.file}";\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
content +=
|
|
||||||
'import { BaseCatalogItemAccessor } from "./baseCatalogItemAccessor";\n\n';
|
|
||||||
content +=
|
|
||||||
"export class CatalogItemAccessor extends BaseCatalogItemAccessor {\n";
|
|
||||||
|
|
||||||
for (const _import of imports) {
|
|
||||||
const strippedName = _import.name.replace("BlitzStatic", "");
|
|
||||||
const defaultName = strippedName[0].toLowerCase() + strippedName.slice(1);
|
|
||||||
|
|
||||||
content += ` ${strippedName}(name = "${defaultName}Component") {\n`;
|
|
||||||
content += ` return this.component(name, ${_import.name}Component);\n`;
|
|
||||||
content += " }\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
content += "}\n";
|
|
||||||
|
|
||||||
await writeFile(TARGET, content);
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import { readFile, writeFile } from "fs/promises";
|
|
||||||
|
|
||||||
const IMPORT = "@protos/auto_legacy_proxy_client";
|
|
||||||
const SOURCE = "../../packages/closed/protos/auto_legacy_proxy_client.proto";
|
|
||||||
const TARGET = "../../packages/closed/src/unreal/methodPopulatedProxyClient.ts";
|
|
||||||
|
|
||||||
const requestPattern = /^message (\w+)Request {/gm;
|
|
||||||
const source = await readFile(SOURCE).then((buffer) => buffer.toString());
|
|
||||||
const requests = source.matchAll(requestPattern);
|
|
||||||
|
|
||||||
const imports: string[] = [];
|
|
||||||
const names: string[] = [];
|
|
||||||
|
|
||||||
for (const match of requests) {
|
|
||||||
const name = match[1];
|
|
||||||
|
|
||||||
imports.push(`${name}Request`, `${name}Response`);
|
|
||||||
names.push(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
let content = "";
|
|
||||||
|
|
||||||
content += "import {\n";
|
|
||||||
content += imports.map((i) => ` ${i},`).join("\n");
|
|
||||||
content += `\n} from "${IMPORT}";\n`;
|
|
||||||
|
|
||||||
content += 'import { BaseProxyClient } from "./baseProxyClient";\n\n';
|
|
||||||
|
|
||||||
content +=
|
|
||||||
"export abstract class MethodPopulatedProxyClient extends BaseProxyClient {\n";
|
|
||||||
content += names
|
|
||||||
.map((name) => {
|
|
||||||
return ` ${name}(data: ${name}Request) {
|
|
||||||
return this.request("${name}Request", ${name}Request, data, ${name}Response);
|
|
||||||
}\n`;
|
|
||||||
})
|
|
||||||
.join("");
|
|
||||||
content += "}\n";
|
|
||||||
|
|
||||||
await writeFile(TARGET, content);
|
|
||||||
@@ -1,319 +0,0 @@
|
|||||||
import {
|
|
||||||
availableProvisions,
|
|
||||||
createDefaultProvisions,
|
|
||||||
createDefaultSkills,
|
|
||||||
fetchConsumableDefinitions,
|
|
||||||
fetchEquipmentDefinitions,
|
|
||||||
fetchModelDefinitions,
|
|
||||||
fetchProvisionDefinitions,
|
|
||||||
fetchSkillDefinitions,
|
|
||||||
fetchTankDefinitions,
|
|
||||||
TankDefinition,
|
|
||||||
tankIcon,
|
|
||||||
} from "@blitzkit/core";
|
|
||||||
import { checkConsumableProvisionInclusivity } from "@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity";
|
|
||||||
import strings from "@blitzkit/i18n/strings/en.json";
|
|
||||||
import { writeFile } from "fs/promises";
|
|
||||||
import { isEqual } from "lodash-es";
|
|
||||||
import { tankCharacteristics } from "../../website/src/core/blitzkit/tankCharacteristics";
|
|
||||||
import { genericDefaultEquipmentMatrix } from "../../website/src/stores/duel/constants";
|
|
||||||
|
|
||||||
const domain = "https://opentest.blitzkit.app";
|
|
||||||
|
|
||||||
import.meta.env.PUBLIC_ASSET_BASE = "https://blitzkit.github.io/assets";
|
|
||||||
const currentTankDefinitions = await fetchTankDefinitions();
|
|
||||||
const currentConsumableDefinitions = await fetchConsumableDefinitions();
|
|
||||||
const currentProvisionDefinitions = await fetchProvisionDefinitions();
|
|
||||||
const currentEquipmentDefinitions = await fetchEquipmentDefinitions();
|
|
||||||
const currentSkillDefinitions = await fetchSkillDefinitions();
|
|
||||||
const currentModelDefinitions = await fetchModelDefinitions();
|
|
||||||
|
|
||||||
import.meta.env.PUBLIC_ASSET_BASE = "https://blitzkit.github.io/assets-preview";
|
|
||||||
const previewTankDefinitions = await fetchTankDefinitions();
|
|
||||||
const previewConsumableDefinitions = await fetchConsumableDefinitions();
|
|
||||||
const previewProvisionDefinitions = await fetchProvisionDefinitions();
|
|
||||||
const previewEquipmentDefinitions = await fetchEquipmentDefinitions();
|
|
||||||
const previewSkillDefinitions = await fetchSkillDefinitions();
|
|
||||||
const previewModelDefinitions = await fetchModelDefinitions();
|
|
||||||
|
|
||||||
let notes = "# BlitzKit OpenTest Patch Notes\n\n";
|
|
||||||
|
|
||||||
const newTanks: TankDefinition[] = [];
|
|
||||||
|
|
||||||
for (const id in previewTankDefinitions.tanks) {
|
|
||||||
if (!(id in currentTankDefinitions.tanks)) {
|
|
||||||
newTanks.push(previewTankDefinitions.tanks[id]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newTanks.sort((a, b) => b.tier - a.tier);
|
|
||||||
|
|
||||||
import.meta.env.PUBLIC_ASSET_BASE = "https://blitzkit.github.io/assets-preview";
|
|
||||||
if (newTanks.length > 0) {
|
|
||||||
notes += "## New Tanks\n\n";
|
|
||||||
|
|
||||||
for (const tank of newTanks) {
|
|
||||||
notes += `### [${tank.name!.locales.en}](${domain}/tanks/${tank.slug})\n`;
|
|
||||||
notes += `})\n\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
notes += "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
const changedTanks: [TankDefinition, TankDefinition][] = [];
|
|
||||||
|
|
||||||
for (const stringId in previewTankDefinitions.tanks) {
|
|
||||||
const id = Number(stringId);
|
|
||||||
|
|
||||||
if (newTanks.some((tank) => tank.id === id)) continue;
|
|
||||||
|
|
||||||
const current = currentTankDefinitions.tanks[id];
|
|
||||||
const preview = previewTankDefinitions.tanks[id];
|
|
||||||
|
|
||||||
if (isEqual(current, preview)) continue;
|
|
||||||
|
|
||||||
changedTanks.push([current, preview]);
|
|
||||||
}
|
|
||||||
|
|
||||||
changedTanks.sort((a, b) => b[1].tier - a[1].tier);
|
|
||||||
|
|
||||||
import.meta.env.PUBLIC_ASSET_BASE = "https://blitzkit.github.io/assets";
|
|
||||||
if (changedTanks.length > 0) {
|
|
||||||
notes += "## Changed Tanks\n\n";
|
|
||||||
|
|
||||||
for (const [current, preview] of changedTanks) {
|
|
||||||
const currentTurret = current.turrets.at(-1)!;
|
|
||||||
const previewTurret = preview.turrets.at(-1)!;
|
|
||||||
const currentGun = currentTurret.guns.at(-1)!;
|
|
||||||
const previewGun = previewTurret.guns.at(-1)!;
|
|
||||||
const currentShell = currentGun.shells[0];
|
|
||||||
const previewShell = previewGun.shells[0];
|
|
||||||
const currentEngine = current.engines.at(-1)!;
|
|
||||||
const previewEngine = preview.engines.at(-1)!;
|
|
||||||
const currentTracks = current.tracks.at(-1)!;
|
|
||||||
const previewTracks = preview.tracks.at(-1)!;
|
|
||||||
const currentTankModel = currentModelDefinitions.models[current.id];
|
|
||||||
const previewTankModel = previewModelDefinitions.models[preview.id];
|
|
||||||
|
|
||||||
const currentConsumables = Object.values(
|
|
||||||
currentConsumableDefinitions.consumables,
|
|
||||||
).filter((consumable) =>
|
|
||||||
checkConsumableProvisionInclusivity(consumable, current, currentGun),
|
|
||||||
);
|
|
||||||
const previewConsumables = Object.values(
|
|
||||||
previewConsumableDefinitions.consumables,
|
|
||||||
).filter((consumable) =>
|
|
||||||
checkConsumableProvisionInclusivity(consumable, preview, previewGun),
|
|
||||||
);
|
|
||||||
const addedConsumables = previewConsumables.filter(
|
|
||||||
(previewConsumable) =>
|
|
||||||
!currentConsumables.some(
|
|
||||||
(currentConsumable) => currentConsumable.id === previewConsumable.id,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
const removedConsumables = currentConsumables.filter(
|
|
||||||
(currentConsumable) =>
|
|
||||||
!previewConsumables.some(
|
|
||||||
(previewConsumable) => previewConsumable.id === currentConsumable.id,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const currentProvisions = availableProvisions(
|
|
||||||
current,
|
|
||||||
currentGun,
|
|
||||||
currentProvisionDefinitions,
|
|
||||||
);
|
|
||||||
const previewProvisions = availableProvisions(
|
|
||||||
preview,
|
|
||||||
previewGun,
|
|
||||||
previewProvisionDefinitions,
|
|
||||||
);
|
|
||||||
const addedProvisions = previewProvisions.filter(
|
|
||||||
(previewProvision) =>
|
|
||||||
!currentProvisions.some(
|
|
||||||
(currentProvision) => currentProvision.id === previewProvision.id,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
const removedProvisions = currentProvisions.filter(
|
|
||||||
(currentProvision) =>
|
|
||||||
!previewProvisions.some(
|
|
||||||
(previewProvision) => previewProvision.id === currentProvision.id,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
const currentEquipment = currentEquipmentDefinitions.presets[
|
|
||||||
current.equipment_preset
|
|
||||||
].slots
|
|
||||||
.map(({ left, right }) => [left, right])
|
|
||||||
.flat();
|
|
||||||
const previewEquipment = previewEquipmentDefinitions.presets[
|
|
||||||
preview.equipment_preset
|
|
||||||
].slots
|
|
||||||
.map(({ left, right }) => [left, right])
|
|
||||||
.flat();
|
|
||||||
const addedEquipment = previewEquipment
|
|
||||||
.filter(
|
|
||||||
(previewEquipment) =>
|
|
||||||
!currentEquipment.some(
|
|
||||||
(currentEquipment) => currentEquipment === previewEquipment,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.map((id) => previewEquipmentDefinitions.equipments[id]);
|
|
||||||
const removedEquipment = currentEquipment
|
|
||||||
.filter(
|
|
||||||
(currentEquipment) =>
|
|
||||||
!previewEquipment.some(
|
|
||||||
(previewEquipment) => previewEquipment === currentEquipment,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.map((id) => currentEquipmentDefinitions.equipments[id]);
|
|
||||||
|
|
||||||
const equalize = false;
|
|
||||||
const currentCharacteristics = tankCharacteristics(
|
|
||||||
{
|
|
||||||
tank: current,
|
|
||||||
gun: currentGun,
|
|
||||||
shell: currentShell,
|
|
||||||
applyDynamicArmor: false,
|
|
||||||
applyReactiveArmor: false,
|
|
||||||
applySpallLiner: false,
|
|
||||||
assaultDistance: 0,
|
|
||||||
crewSkills: createDefaultSkills(currentSkillDefinitions),
|
|
||||||
camouflage: true,
|
|
||||||
consumables: [],
|
|
||||||
engine: currentEngine,
|
|
||||||
provisions: createDefaultProvisions(
|
|
||||||
current,
|
|
||||||
currentGun,
|
|
||||||
currentProvisionDefinitions,
|
|
||||||
),
|
|
||||||
equipmentMatrix: genericDefaultEquipmentMatrix,
|
|
||||||
stockEngine: current.engines[0],
|
|
||||||
stockGun: current.turrets[0].guns[0],
|
|
||||||
stockTrack: current.tracks[0],
|
|
||||||
stockTurret: current.turrets[0],
|
|
||||||
track: currentTracks,
|
|
||||||
turret: currentTurret,
|
|
||||||
equalize,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
equipmentDefinitions: currentEquipmentDefinitions,
|
|
||||||
provisionDefinitions: currentProvisionDefinitions,
|
|
||||||
tankModelDefinition: currentTankModel,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const previewCharacteristics = tankCharacteristics(
|
|
||||||
{
|
|
||||||
tank: preview,
|
|
||||||
gun: previewGun,
|
|
||||||
shell: previewShell,
|
|
||||||
applyDynamicArmor: false,
|
|
||||||
applyReactiveArmor: false,
|
|
||||||
applySpallLiner: false,
|
|
||||||
assaultDistance: 0,
|
|
||||||
crewSkills: createDefaultSkills(previewSkillDefinitions),
|
|
||||||
camouflage: true,
|
|
||||||
consumables: [],
|
|
||||||
engine: previewEngine,
|
|
||||||
provisions: createDefaultProvisions(
|
|
||||||
preview,
|
|
||||||
previewGun,
|
|
||||||
previewProvisionDefinitions,
|
|
||||||
),
|
|
||||||
equipmentMatrix: genericDefaultEquipmentMatrix,
|
|
||||||
stockEngine: preview.engines[0],
|
|
||||||
stockGun: preview.turrets[0].guns[0],
|
|
||||||
stockTrack: preview.tracks[0],
|
|
||||||
stockTurret: preview.turrets[0],
|
|
||||||
track: previewTracks,
|
|
||||||
turret: previewTurret,
|
|
||||||
equalize,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
equipmentDefinitions: previewEquipmentDefinitions,
|
|
||||||
provisionDefinitions: previewProvisionDefinitions,
|
|
||||||
tankModelDefinition: previewTankModel,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
const changedCharacteristics: string[] = [];
|
|
||||||
|
|
||||||
for (const key in currentCharacteristics) {
|
|
||||||
const characteristicKey = key as keyof typeof currentCharacteristics;
|
|
||||||
if (
|
|
||||||
currentCharacteristics[characteristicKey] !==
|
|
||||||
previewCharacteristics[characteristicKey]
|
|
||||||
) {
|
|
||||||
changedCharacteristics.push(characteristicKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
notes += `### [${preview.name!.locales.en}](${domain}/tanks/${preview.slug})\n`;
|
|
||||||
notes += `})\n\n`;
|
|
||||||
|
|
||||||
if (addedConsumables.length + removedConsumables.length > 0) {
|
|
||||||
notes += `Consumables: `;
|
|
||||||
notes += [
|
|
||||||
...addedConsumables.map(
|
|
||||||
(consumable) => `🟢 ${consumable.name!.locales.en}`,
|
|
||||||
),
|
|
||||||
...removedConsumables.map(
|
|
||||||
(consumable) => `🔴 ${consumable.name!.locales.en}`,
|
|
||||||
),
|
|
||||||
].join(", ");
|
|
||||||
notes += `\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addedProvisions.length + removedProvisions.length > 0) {
|
|
||||||
notes += `Provisions: `;
|
|
||||||
notes += [
|
|
||||||
...addedProvisions.map(
|
|
||||||
(provision) => `🟢 ${provision.name!.locales.en}`,
|
|
||||||
),
|
|
||||||
...removedProvisions.map(
|
|
||||||
(provision) => `🔴 ${provision.name!.locales.en}`,
|
|
||||||
),
|
|
||||||
].join(", ");
|
|
||||||
notes += `\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addedEquipment.length + removedEquipment.length > 0) {
|
|
||||||
notes += `-Equipment: `;
|
|
||||||
notes += [
|
|
||||||
...addedEquipment.map(
|
|
||||||
(equipment) => `🟢 ${equipment.name!.locales.en}`,
|
|
||||||
),
|
|
||||||
...removedEquipment.map(
|
|
||||||
(equipment) => `🔴 ${equipment.name!.locales.en}`,
|
|
||||||
),
|
|
||||||
].join(", ");
|
|
||||||
notes += `\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changedCharacteristics.length > 0) {
|
|
||||||
notes += `Characteristics:\n`;
|
|
||||||
|
|
||||||
for (const characteristicUntyped of changedCharacteristics) {
|
|
||||||
const characteristic =
|
|
||||||
characteristicUntyped as keyof typeof currentCharacteristics;
|
|
||||||
const name =
|
|
||||||
strings.website.tools.tankopedia.characteristics.values[
|
|
||||||
characteristic
|
|
||||||
];
|
|
||||||
const before = currentCharacteristics[characteristic];
|
|
||||||
const after = previewCharacteristics[characteristic];
|
|
||||||
|
|
||||||
notes += `- ${
|
|
||||||
typeof before === "number" && typeof after === "number"
|
|
||||||
? after > before
|
|
||||||
? "⬆️"
|
|
||||||
: "🔻"
|
|
||||||
: "✏️"
|
|
||||||
} ${name}: \`${
|
|
||||||
typeof before === "number" ? before.toFixed(2) : before
|
|
||||||
}\` → \`${typeof after === "number" ? after.toFixed(2) : after}\`\n`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await writeFile("../../temp/patch-notes.md", notes);
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import { type TranslationTree } from "@blitzkit/i18n";
|
|
||||||
import locales from "@blitzkit/i18n/locales.json";
|
|
||||||
import { writeFile } from "fs/promises";
|
|
||||||
|
|
||||||
const ROOT = "@blitzkit/i18n/strings";
|
|
||||||
const WORKING_ROOT = "../../packages/i18n/strings";
|
|
||||||
|
|
||||||
const defaultStrings = require(`${ROOT}/${locales.default}.json`).default;
|
|
||||||
const nonDefaultLocales = locales.supported.filter(
|
|
||||||
({ locale }) => locale !== locales.default
|
|
||||||
);
|
|
||||||
|
|
||||||
function prune(defaultStrings: TranslationTree, strings: TranslationTree) {
|
|
||||||
for (const key in strings) {
|
|
||||||
if (key in defaultStrings) {
|
|
||||||
if (typeof defaultStrings[key] === typeof strings[key]) {
|
|
||||||
if (typeof strings[key] === "string") {
|
|
||||||
if (
|
|
||||||
// are equivalent regardless of %s flavor
|
|
||||||
(defaultStrings[key] as string).replaceAll(/%s\d/g, "%s") ===
|
|
||||||
(strings[key] as string).replaceAll(/%s\d/g, "%s") ||
|
|
||||||
// string is empty
|
|
||||||
strings[key] === "" ||
|
|
||||||
// string has old flavor of %s
|
|
||||||
strings[key].match(/%s([^\d]|$)/g) !== null
|
|
||||||
) {
|
|
||||||
delete strings[key];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
prune(defaultStrings[key] as TranslationTree, strings[key]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
delete strings[key];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
delete strings[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const locale of nonDefaultLocales) {
|
|
||||||
console.log(`Pruning ${locale}...`);
|
|
||||||
|
|
||||||
const strings = require(`${ROOT}/${locale}.json`).default;
|
|
||||||
const pruned = prune(defaultStrings, strings);
|
|
||||||
|
|
||||||
writeFile(`${WORKING_ROOT}/${locale}.json`, JSON.stringify(pruned, null, 2));
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user