feat: start of all tanks

This commit is contained in:
TrèsAbhi committed 2025-12-26 14:59:09 -06:00
1 parent 53d05e73a6
commit fcfc97b964
14 files changed
+174 -58

No files matched your search

@@ -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 (
<p>
{config.name}: {rendered} {config.units && `${config.units}`} (1 / 1)
</p>
);
}
@@ -0,0 +1,7 @@
interface Props {
toy: string;
}
export function TankopediaToy({ toy }: Props) {
return <p>toy: {toy}</p>;
}
@@ -1,9 +1,12 @@
import type { Tank } from "../../protos/tank"; import type { Tank } from "../../protos/tank";
import type { TankList } from "../../protos/tank_list";
import type { Tanks } from "../../protos/tanks"; import type { Tanks } from "../../protos/tanks";
export abstract class AbstractAPI { export abstract class AbstractAPI {
abstract tanks(): Promise<Tanks>; abstract tanks(): Promise<Tanks>;
abstract tankList(): Promise<TankList>;
abstract strings( abstract strings(
locale: string, locale: string,
prefix?: string prefix?: string
+12 -5
View File
@@ -1,12 +1,19 @@
import { fetchPB } from "@blitzkit/core";
import { Tank } from "../../protos/tank"; import { Tank } from "../../protos/tank";
import { TankList } from "../../protos/tank_list";
import { Tanks } from "../../protos/tanks";
import { AbstractAPI } from "./abstract"; import { AbstractAPI } from "./abstract";
export class ClientAPI extends AbstractAPI { export class ClientAPI extends AbstractAPI {
async tank(id: string) { tanks() {
const response = await fetch(`/api/tanks/${id}.pb`); return fetchPB("/api/tanks.pb", Tanks);
const buffer = await response.arrayBuffer(); }
const array = new Uint8Array(buffer);
return Tank.decode(array); tankList() {
return fetchPB("/api/tanks/list.pb", TankList);
}
tank(id: string) {
return fetchPB(`/api/tanks/${id}.pb`, Tank);
} }
} }
+27 -6
View File
@@ -3,7 +3,8 @@ import { sluggify } from "@blitzkit/core";
import type { RemoteStorageComponent } from "@protos/blitz_static_remote_storage_component"; import type { RemoteStorageComponent } from "@protos/blitz_static_remote_storage_component";
import { parse } from "yaml"; import { parse } from "yaml";
import type { Tank } from "../../protos/tank"; 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"; import { AbstractAPI } from "./abstract";
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
@@ -91,9 +92,9 @@ export class ServerAPI extends AbstractAPI {
return strings; return strings;
} }
async tanks() { async tankList() {
const group = this.metadata.group("TankEntity"); const group = this.metadata.group("TankEntity");
const tanks: TanksEntry[] = []; const list: TankListEntry[] = [];
const strings = await this.strings("en"); const strings = await this.strings("en");
for (const item of group) { for (const item of group) {
@@ -108,17 +109,37 @@ export class ServerAPI extends AbstractAPI {
const id = item.name; const id = item.name;
const slug = sluggify(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) { 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 item = this.metadata.item(`TankEntity.${id}`);
const tank = item.TankCatalog(); const tank = item.TankCatalog();
const compensation = item.Compensation(); const compensation = item.Compensation();
return { tank, compensation } satisfies Tank; return { tank, compensation, slug } satisfies Tank;
} }
} }
@@ -12,6 +12,11 @@ import {
} from "./characteristics"; } from "./characteristics";
import type { TankState } from "./tankState"; import type { TankState } from "./tankState";
export type ComputedCharacteristics = Record<
CharacteristicName,
CharacteristicOutput
>;
export function computeCharacteristics( export function computeCharacteristics(
id: string, id: string,
tank: TankCatalogComponent, tank: TankCatalogComponent,
@@ -23,8 +28,7 @@ export function computeCharacteristics(
tank.upgrade_stages.slice(0, state.stage) tank.upgrade_stages.slice(0, state.stage)
); );
const computed: Partial<Record<CharacteristicName, CharacteristicOutput>> = const computed: Partial<ComputedCharacteristics> = {};
{};
function characteristic(name: CharacteristicName) { function characteristic(name: CharacteristicName) {
if (name in computed) return computed[name] as CharacteristicOutput; if (name in computed) return computed[name] as CharacteristicOutput;
@@ -99,10 +103,7 @@ export function computeCharacteristics(
} }
return { return {
characteristics: computed as Record< characteristics: computed as ComputedCharacteristics,
CharacteristicName,
CharacteristicOutput
>,
parameters, parameters,
}; };
} }
@@ -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,
};
}
@@ -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());
}
@@ -3,9 +3,9 @@ import { api } from "../../../core/api/dynamic";
import { Tank } from "../../../protos/tank"; import { Tank } from "../../../protos/tank";
export const getStaticPaths = (async () => { 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; }) satisfies GetStaticPaths;
/** /**
@@ -1,12 +1,13 @@
import { times } from "lodash-es"; import { times } from "lodash-es";
import { Suspense, useMemo } from "react"; import { Suspense, useMemo } from "react";
import { TankopediaCharacteristic } from "../../../components/TankopediaCharacteristic";
import { TankopediaToy } from "../../../components/TankopediaToy";
import { api } from "../../../core/api/dynamic"; import { api } from "../../../core/api/dynamic";
import { characteristicsOrder } from "../../../core/tankopedia/characteristicsOrder"; import { characteristicsOrder } from "../../../core/tankopedia/characteristicsOrder";
import { computeCharacteristics } from "../../../core/tankopedia/computeCharacteristics"; import { computeCharacteristics } from "../../../core/tankopedia/computeCharacteristics";
import { renderCharacteristic } from "../../../core/tankopedia/renderCharacteristic";
import { TerrainHardness } from "../../../core/tankopedia/tankState"; import { TerrainHardness } from "../../../core/tankopedia/tankState";
import { useAwait } from "../../../hooks/useAwait"; import { useAwait } from "../../../hooks/useAwait";
import { Tankopedia } from "../../../stores/tankopedia"; import { Tankopedia, TankopediaCompare } from "../../../stores/tankopedia";
export function Page({ id }: { id: string }) { export function Page({ id }: { id: string }) {
return ( return (
@@ -18,10 +19,12 @@ export function Page({ id }: { id: string }) {
function Content({ id }: { id: string }) { function Content({ id }: { id: string }) {
const { tank, compensation } = useAwait(() => api.tank(id), `tank-${id}`); 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 protagonist = Tankopedia.use((state) => state.protagonist);
const compare = Tankopedia.use((state) => state.compare);
const { characteristics, parameters } = useMemo( const { characteristics, parameters } = useMemo(
() => computeCharacteristics(id, tank!, compensation!, protagonist), () => computeCharacteristics(id, tank!, compensation!, protagonist),
[protagonist] [protagonist]
@@ -181,27 +184,41 @@ function Content({ id }: { id: string }) {
<br /> <br />
<div style={{ display: "flex", flexWrap: "wrap" }}>
{Object.values(TankopediaCompare).map(
(c) =>
typeof c === "number" && (
<button
key={c}
onClick={() => {
Tankopedia.mutate((draft) => {
draft.compare = c;
});
}}
>
{TankopediaCompare[c]} {c === compare && "(selected)"}
</button>
)
)}
</div>
<br />
{characteristicsOrder.map(({ group, order }) => ( {characteristicsOrder.map(({ group, order }) => (
<> <>
<h2 key={group}>{group}</h2> <h2 key={group}>{group}</h2>
{order.map((config) => { {order.map((config) =>
if ("toy" in config) { "toy" in config ? (
return <p key={`toy-${config.toy}`}>toy: {config.toy}</p>; <TankopediaToy key={`toy-${config.toy}`} toy={config.toy} />
} else { ) : (
const characteristic = characteristics[config.name]; <TankopediaCharacteristic
key={`characteristic-${config.name}`}
if (characteristic === null) return null; characteristic={characteristics[config.name]}
config={config}
const rendered = renderCharacteristic(characteristic, config); />
)
return ( )}
<p key={`characteristic-${config.name}`}>
{config.name}: {rendered} {config.units && `${config.units}`}
</p>
);
}
})}
</> </>
))} ))}
</> </>
@@ -8,4 +8,6 @@ import "blitz_static_tank_component.proto";
message Tank { message Tank {
blitz_static_tank_component.TankCatalogComponent tank = 1; blitz_static_tank_component.TankCatalogComponent tank = 1;
blitz_static_compensation_component.CompensationComponent compensation = 2; blitz_static_compensation_component.CompensationComponent compensation = 2;
string slug = 3;
} }
@@ -0,0 +1,12 @@
syntax = "proto3";
package blitzkit;
message TankList {
repeated TankListEntry list = 1;
}
message TankListEntry {
string id = 1;
string slug = 2;
}
+3 -6
View File
@@ -2,11 +2,8 @@ syntax = "proto3";
package blitzkit; package blitzkit;
message Tanks { import "tank.proto";
repeated TanksEntry tanks = 1;
}
message TanksEntry { message Tanks {
string id = 1; map<string, Tank> tanks = 1;
string slug = 2;
} }
+16 -13
View File
@@ -1,19 +1,22 @@
import type { TankCatalogComponent } from "@protos/blitz_static_tank_component";
import { Varuna } from "varuna"; 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 { interface Tankopedia {
compare: TankopediaCompare;
protagonist: TankState; protagonist: TankState;
} }
export const Tankopedia = new Varuna<Tankopedia, number>((maxStage) => ({ export enum TankopediaCompare {
protagonist: { TierAndClass,
stage: maxStage, Tier,
shell: 0, All,
isGunDamaged: false, }
isHullTraversing: false,
isShooting: false, export const Tankopedia = new Varuna<Tankopedia, TankCatalogComponent>(
isTurretTraversing: false, (tank) => ({
speed: 0, compare: TankopediaCompare.TierAndClass,
terrainHardness: TerrainHardness.Hard, protagonist: createTankState(tank),
}, })
})); );