From fcfc97b9647e6863fa8852365c1cdde9c17b424b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Fri, 26 Dec 2025 14:59:09 -0600 Subject: [PATCH] feat: start of all tanks --- .../components/TankopediaCharacteristic.tsx | 20 +++++++ .../src/components/TankopediaToy.tsx | 7 +++ packages/website-ue/src/core/api/abstract.ts | 3 + packages/website-ue/src/core/api/client.ts | 17 ++++-- packages/website-ue/src/core/api/server.ts | 33 +++++++++-- .../core/tankopedia/computeCharacteristics.ts | 13 +++-- .../src/core/tankopedia/createTankState.ts | 16 ++++++ packages/website-ue/src/pages/api/tanks.pb.ts | 10 ++++ .../website-ue/src/pages/api/tanks/[id].pb.ts | 4 +- .../src/pages/tanks/[slug]/_index.tsx | 57 ++++++++++++------- packages/website-ue/src/protos/tank.proto | 2 + .../website-ue/src/protos/tank_list.proto | 12 ++++ packages/website-ue/src/protos/tanks.proto | 9 +-- packages/website-ue/src/stores/tankopedia.ts | 29 +++++----- 14 files changed, 174 insertions(+), 58 deletions(-) create mode 100644 packages/website-ue/src/components/TankopediaCharacteristic.tsx create mode 100644 packages/website-ue/src/components/TankopediaToy.tsx create mode 100644 packages/website-ue/src/core/tankopedia/createTankState.ts create mode 100644 packages/website-ue/src/pages/api/tanks.pb.ts create mode 100644 packages/website-ue/src/protos/tank_list.proto diff --git a/packages/website-ue/src/components/TankopediaCharacteristic.tsx b/packages/website-ue/src/components/TankopediaCharacteristic.tsx new file mode 100644 index 000000000..7499c7e12 --- /dev/null +++ b/packages/website-ue/src/components/TankopediaCharacteristic.tsx @@ -0,0 +1,20 @@ +import type { CharacteristicOutput } from "../core/tankopedia/characteristics"; +import type { CharacteristicRenderConfig } from "../core/tankopedia/characteristicsOrder"; +import { renderCharacteristic } from "../core/tankopedia/renderCharacteristic"; + +interface Props { + characteristic: CharacteristicOutput; + config: CharacteristicRenderConfig; +} + +export function TankopediaCharacteristic({ characteristic, config }: Props) { + if (characteristic === null) return null; + + const rendered = renderCharacteristic(characteristic, config); + + return ( +

+ {config.name}: {rendered} {config.units && `${config.units}`} (1 / 1) +

+ ); +} diff --git a/packages/website-ue/src/components/TankopediaToy.tsx b/packages/website-ue/src/components/TankopediaToy.tsx new file mode 100644 index 000000000..5411fb87e --- /dev/null +++ b/packages/website-ue/src/components/TankopediaToy.tsx @@ -0,0 +1,7 @@ +interface Props { + toy: string; +} + +export function TankopediaToy({ toy }: Props) { + return

toy: {toy}

; +} diff --git a/packages/website-ue/src/core/api/abstract.ts b/packages/website-ue/src/core/api/abstract.ts index 111be5b0e..32eaf287e 100644 --- a/packages/website-ue/src/core/api/abstract.ts +++ b/packages/website-ue/src/core/api/abstract.ts @@ -1,9 +1,12 @@ import type { Tank } from "../../protos/tank"; +import type { TankList } from "../../protos/tank_list"; import type { Tanks } from "../../protos/tanks"; export abstract class AbstractAPI { abstract tanks(): Promise; + abstract tankList(): Promise; + abstract strings( locale: string, prefix?: string diff --git a/packages/website-ue/src/core/api/client.ts b/packages/website-ue/src/core/api/client.ts index 65d054ccb..487c1b16d 100644 --- a/packages/website-ue/src/core/api/client.ts +++ b/packages/website-ue/src/core/api/client.ts @@ -1,12 +1,19 @@ +import { fetchPB } from "@blitzkit/core"; import { Tank } from "../../protos/tank"; +import { TankList } from "../../protos/tank_list"; +import { Tanks } from "../../protos/tanks"; import { AbstractAPI } from "./abstract"; export class ClientAPI extends AbstractAPI { - async tank(id: string) { - const response = await fetch(`/api/tanks/${id}.pb`); - const buffer = await response.arrayBuffer(); - const array = new Uint8Array(buffer); + tanks() { + return fetchPB("/api/tanks.pb", Tanks); + } - return Tank.decode(array); + tankList() { + return fetchPB("/api/tanks/list.pb", TankList); + } + + tank(id: string) { + return fetchPB(`/api/tanks/${id}.pb`, Tank); } } diff --git a/packages/website-ue/src/core/api/server.ts b/packages/website-ue/src/core/api/server.ts index 65847544e..68771c57c 100644 --- a/packages/website-ue/src/core/api/server.ts +++ b/packages/website-ue/src/core/api/server.ts @@ -3,7 +3,8 @@ import { sluggify } from "@blitzkit/core"; import type { RemoteStorageComponent } from "@protos/blitz_static_remote_storage_component"; import { parse } from "yaml"; import type { Tank } from "../../protos/tank"; -import type { TanksEntry } from "../../protos/tanks"; +import type { TankListEntry } from "../../protos/tank_list"; +import type { Tanks } from "../../protos/tanks"; import { AbstractAPI } from "./abstract"; if (typeof window !== "undefined") { @@ -91,9 +92,9 @@ export class ServerAPI extends AbstractAPI { return strings; } - async tanks() { + async tankList() { const group = this.metadata.group("TankEntity"); - const tanks: TanksEntry[] = []; + const list: TankListEntry[] = []; const strings = await this.strings("en"); for (const item of group) { @@ -108,17 +109,37 @@ export class ServerAPI extends AbstractAPI { const id = item.name; const slug = sluggify(name); - tanks.push({ id, slug }); + list.push({ id, slug }); } - return { tanks }; + return { list }; + } + + async tanks() { + const tankList = await this.tankList(); + const data: Tanks = { tanks: {} }; + + for (const { id } of tankList.list) { + console.log(id); + data.tanks[id] = await this.tank(id); + } + + return data; } async tank(id: string) { + const tankList = await this.tankList(); + const tankListEntry = tankList.list.find((tank) => tank.id === id); + + if (!tankListEntry) { + throw new Error(`Tank with id ${id} not found`); + } + + const { slug } = tankListEntry; const item = this.metadata.item(`TankEntity.${id}`); const tank = item.TankCatalog(); const compensation = item.Compensation(); - return { tank, compensation } satisfies Tank; + return { tank, compensation, slug } satisfies Tank; } } diff --git a/packages/website-ue/src/core/tankopedia/computeCharacteristics.ts b/packages/website-ue/src/core/tankopedia/computeCharacteristics.ts index 6bd9ecabc..5c7ae136c 100644 --- a/packages/website-ue/src/core/tankopedia/computeCharacteristics.ts +++ b/packages/website-ue/src/core/tankopedia/computeCharacteristics.ts @@ -12,6 +12,11 @@ import { } from "./characteristics"; import type { TankState } from "./tankState"; +export type ComputedCharacteristics = Record< + CharacteristicName, + CharacteristicOutput +>; + export function computeCharacteristics( id: string, tank: TankCatalogComponent, @@ -23,8 +28,7 @@ export function computeCharacteristics( tank.upgrade_stages.slice(0, state.stage) ); - const computed: Partial> = - {}; + const computed: Partial = {}; function characteristic(name: CharacteristicName) { if (name in computed) return computed[name] as CharacteristicOutput; @@ -99,10 +103,7 @@ export function computeCharacteristics( } return { - characteristics: computed as Record< - CharacteristicName, - CharacteristicOutput - >, + characteristics: computed as ComputedCharacteristics, parameters, }; } diff --git a/packages/website-ue/src/core/tankopedia/createTankState.ts b/packages/website-ue/src/core/tankopedia/createTankState.ts new file mode 100644 index 000000000..0d6bcb507 --- /dev/null +++ b/packages/website-ue/src/core/tankopedia/createTankState.ts @@ -0,0 +1,16 @@ +import type { TankCatalogComponent } from "@protos/blitz_static_tank_component"; +import { TerrainHardness } from "./tankState"; + +export function createTankState(tank: TankCatalogComponent) { + return { + stage: tank.upgrade_stages.length, + shell: 0, + isGunDamaged: false, + isHullTraversing: false, + isShooting: false, + isTurretTraversing: false, + speed: 0, + terrainHardness: TerrainHardness.Hard, + isCaughtOnFire: false, + }; +} diff --git a/packages/website-ue/src/pages/api/tanks.pb.ts b/packages/website-ue/src/pages/api/tanks.pb.ts new file mode 100644 index 000000000..241a6f65f --- /dev/null +++ b/packages/website-ue/src/pages/api/tanks.pb.ts @@ -0,0 +1,10 @@ +import { api } from "../../core/api/dynamic"; +import { Tanks } from "../../protos/tanks"; + +/** + * Literally all the tanks in one place lmao. + */ +export async function GET() { + const tanks = await api.tanks(); + return new Response(Tanks.encode(tanks).finish()); +} diff --git a/packages/website-ue/src/pages/api/tanks/[id].pb.ts b/packages/website-ue/src/pages/api/tanks/[id].pb.ts index b24ace9e0..9c001dfbc 100644 --- a/packages/website-ue/src/pages/api/tanks/[id].pb.ts +++ b/packages/website-ue/src/pages/api/tanks/[id].pb.ts @@ -3,9 +3,9 @@ import { api } from "../../../core/api/dynamic"; import { Tank } from "../../../protos/tank"; export const getStaticPaths = (async () => { - const { tanks } = await api.tanks(); + const { list } = await api.tankList(); - return tanks.map(({ id }) => ({ params: { id } })); + return list.map(({ id }) => ({ params: { id } })); }) satisfies GetStaticPaths; /** diff --git a/packages/website-ue/src/pages/tanks/[slug]/_index.tsx b/packages/website-ue/src/pages/tanks/[slug]/_index.tsx index 115d7fcba..3c8aae6ac 100644 --- a/packages/website-ue/src/pages/tanks/[slug]/_index.tsx +++ b/packages/website-ue/src/pages/tanks/[slug]/_index.tsx @@ -1,12 +1,13 @@ import { times } from "lodash-es"; import { Suspense, useMemo } from "react"; +import { TankopediaCharacteristic } from "../../../components/TankopediaCharacteristic"; +import { TankopediaToy } from "../../../components/TankopediaToy"; import { api } from "../../../core/api/dynamic"; import { characteristicsOrder } from "../../../core/tankopedia/characteristicsOrder"; import { computeCharacteristics } from "../../../core/tankopedia/computeCharacteristics"; -import { renderCharacteristic } from "../../../core/tankopedia/renderCharacteristic"; import { TerrainHardness } from "../../../core/tankopedia/tankState"; import { useAwait } from "../../../hooks/useAwait"; -import { Tankopedia } from "../../../stores/tankopedia"; +import { Tankopedia, TankopediaCompare } from "../../../stores/tankopedia"; export function Page({ id }: { id: string }) { return ( @@ -18,10 +19,12 @@ export function Page({ id }: { id: string }) { function Content({ id }: { id: string }) { const { tank, compensation } = useAwait(() => api.tank(id), `tank-${id}`); + const otherTanks = useAwait(() => api.tanks(), "tanks"); - Tankopedia.useInitialization(tank!.upgrade_stages.length); + Tankopedia.useInitialization(tank!); const protagonist = Tankopedia.use((state) => state.protagonist); + const compare = Tankopedia.use((state) => state.compare); const { characteristics, parameters } = useMemo( () => computeCharacteristics(id, tank!, compensation!, protagonist), [protagonist] @@ -181,27 +184,41 @@ function Content({ id }: { id: string }) {
+
+ {Object.values(TankopediaCompare).map( + (c) => + typeof c === "number" && ( + + ) + )} +
+ +
+ {characteristicsOrder.map(({ group, order }) => ( <>

{group}

- {order.map((config) => { - if ("toy" in config) { - return

toy: {config.toy}

; - } else { - const characteristic = characteristics[config.name]; - - if (characteristic === null) return null; - - const rendered = renderCharacteristic(characteristic, config); - - return ( -

- {config.name}: {rendered} {config.units && `${config.units}`} -

- ); - } - })} + {order.map((config) => + "toy" in config ? ( + + ) : ( + + ) + )} ))} diff --git a/packages/website-ue/src/protos/tank.proto b/packages/website-ue/src/protos/tank.proto index d0319e335..c4f980327 100644 --- a/packages/website-ue/src/protos/tank.proto +++ b/packages/website-ue/src/protos/tank.proto @@ -8,4 +8,6 @@ import "blitz_static_tank_component.proto"; message Tank { blitz_static_tank_component.TankCatalogComponent tank = 1; blitz_static_compensation_component.CompensationComponent compensation = 2; + + string slug = 3; } \ No newline at end of file diff --git a/packages/website-ue/src/protos/tank_list.proto b/packages/website-ue/src/protos/tank_list.proto new file mode 100644 index 000000000..2ecfd18b8 --- /dev/null +++ b/packages/website-ue/src/protos/tank_list.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package blitzkit; + +message TankList { + repeated TankListEntry list = 1; +} + +message TankListEntry { + string id = 1; + string slug = 2; +} \ No newline at end of file diff --git a/packages/website-ue/src/protos/tanks.proto b/packages/website-ue/src/protos/tanks.proto index 8642d30fc..f33e7f182 100644 --- a/packages/website-ue/src/protos/tanks.proto +++ b/packages/website-ue/src/protos/tanks.proto @@ -2,11 +2,8 @@ syntax = "proto3"; package blitzkit; -message Tanks { - repeated TanksEntry tanks = 1; -} +import "tank.proto"; -message TanksEntry { - string id = 1; - string slug = 2; +message Tanks { + map tanks = 1; } \ No newline at end of file diff --git a/packages/website-ue/src/stores/tankopedia.ts b/packages/website-ue/src/stores/tankopedia.ts index deccca892..6d56241fc 100644 --- a/packages/website-ue/src/stores/tankopedia.ts +++ b/packages/website-ue/src/stores/tankopedia.ts @@ -1,19 +1,22 @@ +import type { TankCatalogComponent } from "@protos/blitz_static_tank_component"; import { Varuna } from "varuna"; -import { TerrainHardness, type TankState } from "../core/tankopedia/tankState"; +import { createTankState } from "../core/tankopedia/createTankState"; +import { type TankState } from "../core/tankopedia/tankState"; interface Tankopedia { + compare: TankopediaCompare; protagonist: TankState; } -export const Tankopedia = new Varuna((maxStage) => ({ - protagonist: { - stage: maxStage, - shell: 0, - isGunDamaged: false, - isHullTraversing: false, - isShooting: false, - isTurretTraversing: false, - speed: 0, - terrainHardness: TerrainHardness.Hard, - }, -})); +export enum TankopediaCompare { + TierAndClass, + Tier, + All, +} + +export const Tankopedia = new Varuna( + (tank) => ({ + compare: TankopediaCompare.TierAndClass, + protagonist: createTankState(tank), + }) +);