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),
+ })
+);