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 { TankList } from "../../protos/tank_list";
import type { Tanks } from "../../protos/tanks";
export abstract class AbstractAPI {
abstract tanks(): Promise<Tanks>;
abstract tankList(): Promise<TankList>;
abstract strings(
locale: string,
prefix?: string
+12 -5
View File
@@ -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);
}
}
+27 -6
View File
@@ -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;
}
}
@@ -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<Record<CharacteristicName, CharacteristicOutput>> =
{};
const computed: Partial<ComputedCharacteristics> = {};
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,
};
}
@@ -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";
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;
/**
@@ -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 }) {
<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 }) => (
<>
<h2 key={group}>{group}</h2>
{order.map((config) => {
if ("toy" in config) {
return <p key={`toy-${config.toy}`}>toy: {config.toy}</p>;
} else {
const characteristic = characteristics[config.name];
if (characteristic === null) return null;
const rendered = renderCharacteristic(characteristic, config);
return (
<p key={`characteristic-${config.name}`}>
{config.name}: {rendered} {config.units && `${config.units}`}
</p>
);
}
})}
{order.map((config) =>
"toy" in config ? (
<TankopediaToy key={`toy-${config.toy}`} toy={config.toy} />
) : (
<TankopediaCharacteristic
key={`characteristic-${config.name}`}
characteristic={characteristics[config.name]}
config={config}
/>
)
)}
</>
))}
</>
@@ -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;
}
@@ -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;
message Tanks {
repeated TanksEntry tanks = 1;
}
import "tank.proto";
message TanksEntry {
string id = 1;
string slug = 2;
message Tanks {
map<string, Tank> tanks = 1;
}
+16 -13
View File
@@ -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<Tankopedia, number>((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<Tankopedia, TankCatalogComponent>(
(tank) => ({
compare: TankopediaCompare.TierAndClass,
protagonist: createTankState(tank),
})
);