feat: start of more attrs

This commit is contained in:
TrèsAbhi committed 2025-12-18 16:12:01 -06:00
1 parent 6ce1135835
commit 7b830f5a71
6 files changed
+128 -49

No files matched your search

@@ -3,50 +3,83 @@ import {
TankAttributeChange_AttributeName, TankAttributeChange_AttributeName,
} from "@protos/blitz_static_tank_upgrade_single_stage"; } from "@protos/blitz_static_tank_upgrade_single_stage";
export type CharacteristicOutput = number | string | null; export type CharacteristicOutput = number | null;
interface Characteristic { type Characteristic = (helpers: {
value: (helpers: { characteristic: (name: CharacteristicName) => CharacteristicOutput;
characteristic: (name: CharacteristicName) => CharacteristicOutput; attribute: (name: TankAttributeChange_AttributeName) => number;
attribute: (name: TankAttributeChange_AttributeName) => number; shell: (name: ShellUpgrageSingleChange_AttributeName) => number;
shell: (name: ShellUpgrageSingleChange_AttributeName) => number; }) => CharacteristicOutput;
}) => CharacteristicOutput;
}
export type CharacteristicName = keyof typeof characteristics; export type CharacteristicName = keyof typeof characteristics;
export enum GunType {
Regular,
AutoLoader,
AutoReloader,
}
export const characteristics = { export const characteristics = {
gun_type: { gun_type({ attribute }) {
value({ attribute }) { const isPump = attribute(
const isPump = attribute( TankAttributeChange_AttributeName.ATTRIBUTE_NAME_IS_PUMP
TankAttributeChange_AttributeName.ATTRIBUTE_NAME_IS_PUMP );
);
if (isPump === 1) return "auto_reloader"; if (isPump === 1) return GunType.AutoReloader;
const clipSize = attribute( const clipSize = attribute(
TankAttributeChange_AttributeName.ATTRIBUTE_NAME_CLIP_SIZE TankAttributeChange_AttributeName.ATTRIBUTE_NAME_CLIP_SIZE
); );
if (clipSize === 1) return "regular"; if (clipSize === 1) return GunType.Regular;
return "auto_loader"; return GunType.AutoLoader;
},
}, },
damage: { damage({ shell }) {
value({ shell }) { return shell(
return shell( ShellUpgrageSingleChange_AttributeName.ATTRIBUTE_NAME_ARMOR_DAMAGE
ShellUpgrageSingleChange_AttributeName.ATTRIBUTE_NAME_ARMOR_DAMAGE );
);
},
}, },
dpm: { module_damage({ shell }) {
value({ characteristic }) { return shell(
const damage = characteristic("damage"); ShellUpgrageSingleChange_AttributeName.ATTRIBUTE_NAME_MODULE_DAMAGE
);
},
return 1; penetration({ shell }) {
}, return shell(
ShellUpgrageSingleChange_AttributeName.ATTRIBUTE_NAME_PIERCING_POWER
);
},
reload({ characteristic, attribute }) {
const gunType = characteristic("gun_type")!;
if (gunType !== GunType.Regular) return null;
return attribute(
TankAttributeChange_AttributeName.ATTRIBUTE_NAME_RELOAD_TIME
);
},
dpm({ characteristic }) {
let dps: number;
const gunType = characteristic("gun_type")!;
const damage = characteristic("damage")!;
switch (gunType) {
case GunType.Regular: {
const reload = characteristic("reload")!;
dps = damage / reload;
break;
}
default:
throw new Error("Unknown gun type");
}
return 60 * dps;
}, },
} satisfies Record<string, Characteristic>; } satisfies Record<string, Characteristic>;
@@ -1,14 +1,28 @@
import type { CharacteristicName } from "./characteristics"; import type { CharacteristicName } from "./characteristics";
export enum BetterDirection {
Higher,
Lower,
}
type CharacteristicsOrder = { type CharacteristicsOrder = {
group: string; group: string;
order: CharacteristicName[]; order: {
name: CharacteristicName;
decimals?: number;
}[];
}[]; }[];
export const characteristicsGroups = [ export const characteristicsGroups = [
{ {
group: "firepower", group: "firepower",
order: ["gun_type", "dpm"], order: [
{ name: "gun_type" },
{ name: "dpm", decimals: 0 },
{ name: "damage" },
{ name: "module_damage" },
{ name: "penetration" },
],
}, },
{ {
group: "maneuverability", group: "maneuverability",
@@ -8,10 +8,11 @@ import {
type CharacteristicName, type CharacteristicName,
type CharacteristicOutput, type CharacteristicOutput,
} from "./characteristics"; } from "./characteristics";
import type { TankState } from "./tankState";
export function renderCharacteristics( export function computeCharacteristics(
parameters: StageParameters, parameters: StageParameters,
shellId: number state: TankState
) { ) {
const computed: Partial<Record<CharacteristicName, CharacteristicOutput>> = const computed: Partial<Record<CharacteristicName, CharacteristicOutput>> =
{}; {};
@@ -34,7 +35,7 @@ export function renderCharacteristics(
function shell(name: ShellUpgrageSingleChange_AttributeName) { function shell(name: ShellUpgrageSingleChange_AttributeName) {
const shell = parameters.shells_upgrades.find( const shell = parameters.shells_upgrades.find(
(shell) => shell.shell_id === name (shell) => shell.shell_id === state.shell
); );
if (!shell) throw new Error(`Shell ${name} not found`); if (!shell) throw new Error(`Shell ${name} not found`);
@@ -50,7 +51,9 @@ export function renderCharacteristics(
for (const _name in characteristics) { for (const _name in characteristics) {
const name = _name as CharacteristicName; const name = _name as CharacteristicName;
const _characteristic = characteristics[name]; const value = characteristics[name]({ characteristic, attribute, shell });
const value = _characteristic.value({ characteristic, attribute, shell }); computed[name] = value;
} }
return computed as Record<CharacteristicName, CharacteristicOutput>;
} }
@@ -0,0 +1,5 @@
export interface TankState {
stage: number;
shell: number;
}
@@ -2,7 +2,8 @@ import { times } from "lodash-es";
import { Suspense, useMemo } from "react"; import { Suspense, useMemo } from "react";
import { api } from "../../../core/api/dynamic"; import { api } from "../../../core/api/dynamic";
import { aggregateStageParameters } from "../../../core/tankopedia/aggregateStageParameters"; import { aggregateStageParameters } from "../../../core/tankopedia/aggregateStageParameters";
import { characteristics } from "../../../core/tankopedia/characteristics"; import { characteristicsGroups } from "../../../core/tankopedia/characteristicsGroups";
import { computeCharacteristics } from "../../../core/tankopedia/computeCharacteristics";
import { useAwait } from "../../../hooks/useAwait"; import { useAwait } from "../../../hooks/useAwait";
import { Tankopedia } from "../../../stores/tankopedia"; import { Tankopedia } from "../../../stores/tankopedia";
@@ -17,11 +18,19 @@ export function Page({ id }: { id: string }) {
function Content({ id }: { id: string }) { function Content({ id }: { id: string }) {
const tank = useAwait(() => api.tank(id), `tank-${id}`); const tank = useAwait(() => api.tank(id), `tank-${id}`);
const stage = Tankopedia.use((state) => state.stage); const protagonist = Tankopedia.use((state) => state.protagonist);
const parameters = useMemo( const parameters = useMemo(
() => () =>
aggregateStageParameters(tank.base_stats!, tank.upgrade_stages, stage), aggregateStageParameters(
[stage] tank.base_stats!,
tank.upgrade_stages,
protagonist.shell
),
[protagonist]
);
const characteristics = useMemo(
() => computeCharacteristics(parameters, protagonist),
[protagonist]
); );
return ( return (
@@ -32,23 +41,35 @@ function Content({ id }: { id: string }) {
key={index} key={index}
onClick={() => { onClick={() => {
Tankopedia.mutate((draft) => { Tankopedia.mutate((draft) => {
draft.stage = index; draft.protagonist.stage = index;
}); });
}} }}
> >
Stage {index} {index === stage && "(selected)"} Stage {index} {index === protagonist.stage && "(selected)"}
</button> </button>
))} ))}
</div> </div>
{characteristicsOrder.map(({ group, order }) => ( {characteristicsGroups.map(({ group, order }) => (
<> <>
<h2>{group}</h2> <h2>{group}</h2>
{order.map((name) => { {order.map(({ name, decimals }) => {
const characteristic = characteristics[name]; const characteristic = characteristics[name];
return <span>{}</span>; if (characteristic === null) return null;
let rendered: string | number = characteristic;
if (typeof rendered === "number") {
if (decimals !== undefined) rendered = rendered.toFixed(decimals);
}
return (
<p>
{name}: {rendered}
</p>
);
})} })}
</> </>
))} ))}
+5 -2
View File
@@ -1,7 +1,10 @@
import { Varuna } from "varuna"; import { Varuna } from "varuna";
import type { TankState } from "../core/tankopedia/tankState";
interface Tankopedia { interface Tankopedia {
stage: number; protagonist: TankState;
} }
export const Tankopedia = new Varuna<Tankopedia>({ stage: 0 }); export const Tankopedia = new Varuna<Tankopedia>({
protagonist: { stage: 0, shell: 0 },
});