diff --git a/package.json b/package.json index 9af30f596..2ed4ceb5f 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "scripts": { "dev": "cd packages/website && bun run dev", "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", "workspaces": [ diff --git a/packages/scripts/package.json b/packages/scripts/package.json index d72a23bc7..393c95942 100644 --- a/packages/scripts/package.json +++ b/packages/scripts/package.json @@ -1,5 +1,10 @@ { "name": "@blitzkit/scripts", + "private": true, + "scripts": { + "discover-ids": "bun src/discoverIds/index.ts", + "build-protos": "bun src/protos.ts" + }, "dependencies": { "@blitzkit/closed": "workspace:*", "@blitzkit/core": "workspace:*", @@ -34,9 +39,5 @@ "unzipit": "^1.4.3", "yaml": "^2.8.1" }, - "private": true, - "scripts": { - "discover-ids": "bun src/discoverIds/index.ts" - }, "type": "module" } diff --git a/packages/scripts/src/averages.ts b/packages/scripts/src/averages.ts deleted file mode 100644 index 4f2ae28ca..000000000 --- a/packages/scripts/src/averages.ts +++ /dev/null @@ -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 = { - 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 = {}; -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 = {}; - - 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>; - sigma: Partial>; - r: Partial>; - 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(); -} diff --git a/packages/scripts/src/buildCatalogItemAccessor.ts b/packages/scripts/src/buildCatalogItemAccessor.ts deleted file mode 100644 index 8fb850e70..000000000 --- a/packages/scripts/src/buildCatalogItemAccessor.ts +++ /dev/null @@ -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); diff --git a/packages/scripts/src/buildProxyClient.ts b/packages/scripts/src/buildProxyClient.ts deleted file mode 100644 index 2f0cee0ab..000000000 --- a/packages/scripts/src/buildProxyClient.ts +++ /dev/null @@ -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); diff --git a/packages/scripts/src/patchNotes.ts b/packages/scripts/src/patchNotes.ts deleted file mode 100644 index ba02e8fe1..000000000 --- a/packages/scripts/src/patchNotes.ts +++ /dev/null @@ -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 += `![](${tankIcon(tank.id)})\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 += `![](${tankIcon(preview.id)})\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); diff --git a/packages/scripts/src/pruneStrings.ts b/packages/scripts/src/pruneStrings.ts deleted file mode 100644 index ce6e5c8a0..000000000 --- a/packages/scripts/src/pruneStrings.ts +++ /dev/null @@ -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)); -}