mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-28 13:09:51 +01:00
Compare commits
11
Commits
d6e37119a8
...
249f62759e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
249f62759e | ||
|
|
7767481633 | ||
|
|
a97b1383d8 | ||
|
|
1b224b3949 | ||
|
|
018aa33a7f | ||
|
|
4b7b8b3392 | ||
|
|
272824a72f | ||
|
|
ac02d65db0 | ||
|
|
9bcaff9fd3 | ||
|
|
7b650bf39b | ||
|
|
ee1ecb83b7 |
No files matched your search
@@ -136,9 +136,10 @@
|
||||
"name": "varuna",
|
||||
"dependencies": {
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/react": "^19.2.14",
|
||||
"immer": "^10.1.3",
|
||||
"lodash-es": "^4.17.21",
|
||||
"react": "^19.1.1",
|
||||
"react": "^19.2.5",
|
||||
},
|
||||
},
|
||||
"packages/website": {
|
||||
@@ -3217,6 +3218,8 @@
|
||||
|
||||
"utif2/pako": ["[email protected]", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
|
||||
|
||||
"varuna/react": ["[email protected]", "", {}, "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA=="],
|
||||
|
||||
"vscode-json-languageservice/jsonc-parser": ["[email protected]", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="],
|
||||
|
||||
"which-builtin-type/isarray": ["[email protected]", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
"main": "src/index.ts",
|
||||
"dependencies": {
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/react": "^19.2.14",
|
||||
"immer": "^10.1.3",
|
||||
"lodash-es": "^4.17.21",
|
||||
"react": "^19.1.1"
|
||||
"react": "^19.2.5"
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ export class Varuna<Type, Arguments = void> {
|
||||
|
||||
constructor(
|
||||
private creator: Type | ((...args: Arguments[]) => Type),
|
||||
private persistence?: string
|
||||
private persistence?: string,
|
||||
) {
|
||||
if (typeof creator !== "function") this.initialize(creator);
|
||||
}
|
||||
@@ -100,7 +100,7 @@ export class Varuna<Type, Arguments = void> {
|
||||
}
|
||||
|
||||
use<Slice = Type>(
|
||||
slicer: (state: Type) => Slice = (state) => state as unknown as Slice
|
||||
slicer: (state: Type) => Slice = (state) => state as unknown as Slice,
|
||||
) {
|
||||
return this.useDeferred(slicer, slicer(this.state));
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
@keyframes skeleton-animation {
|
||||
0% {
|
||||
background-color: var(--black-a8);
|
||||
}
|
||||
50% {
|
||||
background-color: var(--black-a5);
|
||||
}
|
||||
100% {
|
||||
background-color: var(--black-a8);
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton {
|
||||
animation: skeleton-animation var(--duration-subtle) ease infinite;
|
||||
|
||||
border-radius: var(--space-2);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
@keyframes skeleton-animation {
|
||||
0% {
|
||||
background-color: var(--color-1);
|
||||
}
|
||||
50% {
|
||||
background-color: var(--color-2);
|
||||
}
|
||||
100% {
|
||||
background-color: var(--color-1);
|
||||
}
|
||||
}
|
||||
|
||||
.skeleton {
|
||||
--color-1: var(--black-a5);
|
||||
--color-2: var(--black-a4);
|
||||
|
||||
:global(:root.dark) & {
|
||||
--color-1: var(--white-a3);
|
||||
--color-2: var(--white-a2);
|
||||
}
|
||||
|
||||
animation: skeleton-animation var(--duration-subtle) ease infinite;
|
||||
|
||||
border-radius: var(--space-2);
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { ComponentProps } from "react";
|
||||
import { classNames } from "../../core/ui/classNames";
|
||||
import "./index.css";
|
||||
import styles from "./index.module.css";
|
||||
|
||||
export interface SkeletonProps extends ComponentProps<"div"> {}
|
||||
|
||||
export function Skeleton({ className, ...props }: SkeletonProps) {
|
||||
return <div className={classNames("skeleton", className)} {...props} />;
|
||||
return <div className={classNames(styles.skeleton, className)} {...props} />;
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
gap: var(--space-2);
|
||||
|
||||
overflow: hidden;
|
||||
min-width: min-content;
|
||||
|
||||
.icon-wrapper {
|
||||
position: relative;
|
||||
@@ -31,19 +30,29 @@
|
||||
|
||||
background-size: 140%;
|
||||
background-position: 40% center;
|
||||
|
||||
.cover {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(transparent 75%, var(--gray-1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
max-width: 100%;
|
||||
text-overflow: ellipsis;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
|
||||
overflow-x: hidden;
|
||||
|
||||
justify-content: center;
|
||||
|
||||
.text {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&[data-skeleton="true"] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.skeleton {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,10 @@ import { api } from "../../core/api/dynamic";
|
||||
import { useAwait } from "../../hooks/useAwait";
|
||||
import { useGameStrings } from "../../hooks/useGameStrings";
|
||||
import { useLocale } from "../../hooks/useLocale";
|
||||
import type { PropsWithSkeleton } from "../../types/propsWithSkeleton";
|
||||
import { InlineSkeleton } from "../InlineSkeleton";
|
||||
import { LinkI18n } from "../LinkI18n";
|
||||
import { Skeleton } from "../Skeleton";
|
||||
import { Text } from "../Text";
|
||||
import styles from "./index.module.css";
|
||||
|
||||
@@ -11,37 +14,48 @@ interface TankCardProps {
|
||||
subtitle?: string;
|
||||
}
|
||||
|
||||
export function TankCard({ id }: TankCardProps) {
|
||||
const tank = useAwait(() => api.tank(id), `tank-${id}`);
|
||||
const gameStrings = useGameStrings("TankEntity");
|
||||
export function TankCard(props: PropsWithSkeleton<TankCardProps>) {
|
||||
const tank = props.skeleton
|
||||
? undefined
|
||||
: useAwait(() => api.tank(props.id), `tank-${props.id}`);
|
||||
const gameStrings = props.skeleton ? undefined : useGameStrings("TankEntity");
|
||||
const locale = useLocale();
|
||||
|
||||
return (
|
||||
<LinkI18n
|
||||
href={`/tanks/${tank.slug}`}
|
||||
href={props.skeleton ? undefined : `/tanks/${tank!.slug}`}
|
||||
underline="never"
|
||||
locale={locale}
|
||||
className={styles["tank-card"]}
|
||||
>
|
||||
<div className={styles["icon-wrapper"]}>
|
||||
<div
|
||||
className={styles.flag}
|
||||
style={{
|
||||
backgroundImage: `url(/api/nations/${tank.tank!.nation}.webp)`,
|
||||
}}
|
||||
/>
|
||||
{!props.skeleton && (
|
||||
<div
|
||||
className={styles.flag}
|
||||
style={{
|
||||
backgroundImage: `url(/api/nations/${tank!.tank!.nation}/card.webp)`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={styles.icon}
|
||||
style={{
|
||||
backgroundImage: `url(/api/tanks/${id}.webp)`,
|
||||
}}
|
||||
>
|
||||
<div className={styles.cover} />
|
||||
</div>
|
||||
{!props.skeleton && (
|
||||
<div
|
||||
className={styles.icon}
|
||||
style={{
|
||||
backgroundImage: `url(/api/tanks/${props.id}.webp)`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{props.skeleton && <Skeleton className={styles.icon} />}
|
||||
</div>
|
||||
|
||||
<Text className={styles.name}>{gameStrings[tank.tank!.name!.value]}</Text>
|
||||
<div className={styles.name}>
|
||||
<Text className={styles.text} data-skeleton={props.skeleton}>
|
||||
{props.skeleton && <InlineSkeleton className={styles.skeleton} />}
|
||||
{!props.skeleton && gameStrings![tank!.tank!.name!.value]}
|
||||
</Text>
|
||||
</div>
|
||||
</LinkI18n>
|
||||
);
|
||||
}
|
||||
@@ -26,7 +26,7 @@ const toolStrings = strings.tools[tool.id as keyof typeof strings.tools];
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: var(--space-4);
|
||||
padding: var(--space-5) var(--space-5);
|
||||
padding: var(--space-5) var(--space-4);
|
||||
box-sizing: border-box;
|
||||
|
||||
backdrop-filter: var(--blur-tint);
|
||||
@@ -36,7 +36,7 @@ const toolStrings = strings.tools[tool.id as keyof typeof strings.tools];
|
||||
}
|
||||
|
||||
@media (width > 40rem) {
|
||||
padding: var(--space-6) var(--space-5);
|
||||
padding: var(--space-6) var(--space-4);
|
||||
}
|
||||
|
||||
.centered {
|
||||
@@ -86,7 +86,7 @@ const toolStrings = strings.tools[tool.id as keyof typeof strings.tools];
|
||||
</div>
|
||||
|
||||
<div class="centered">
|
||||
<Heading weight="medium" size="2">
|
||||
<Heading weight="bold" size="2">
|
||||
{toolStrings.name}
|
||||
</Heading>
|
||||
<Text>
|
||||
|
||||
+14
-18
@@ -1,4 +1,4 @@
|
||||
import { TankAttributeChange } from "@protos/game/proto/legacy/auto_items";
|
||||
import type { TankCatalogComponent } from "@blitzkit/closed/protos/game/proto/legacy/blitz_static_tank_component";
|
||||
import { StandardSinglePrice } from "@protos/game/proto/legacy/blitz_static_standard_single_price";
|
||||
import { PenetrationGroup } from "@protos/game/proto/legacy/blitz_static_tank_penetration_group";
|
||||
import {
|
||||
@@ -7,13 +7,14 @@ import {
|
||||
ShellUpgrade,
|
||||
ShellUpgradeSingleChange,
|
||||
StageParameters,
|
||||
TankAttributeChange,
|
||||
TankAttributeChange_AttributeName,
|
||||
TankAttributeChange_Modifier,
|
||||
VisualChanges,
|
||||
} from "@protos/game/proto/legacy/blitz_static_tank_upgrade_single_stage";
|
||||
|
||||
function patch(stage0: StageParameters, stage1: StageParameters) {
|
||||
if (stage1.stage_number !== ++stage0.stage_number) {
|
||||
if (stage1.number !== ++stage0.number) {
|
||||
throw new Error("Change stage number must be 1 greater than base");
|
||||
}
|
||||
|
||||
@@ -181,26 +182,21 @@ function patch(stage0: StageParameters, stage1: StageParameters) {
|
||||
}
|
||||
}
|
||||
|
||||
export function aggregateStageParameters(
|
||||
base: StageParameters,
|
||||
stages: StageParameters[],
|
||||
export function aggregateParameters(
|
||||
tank: TankCatalogComponent,
|
||||
upgrades: Record<string, number>,
|
||||
) {
|
||||
const stage0 = StageParameters.create({ ...base, stage_number: 0 });
|
||||
const stage0 = StageParameters.create({});
|
||||
|
||||
/**
|
||||
* Bug in Reforged: base stats are stage 1, so is the first upgrade. Manually
|
||||
* setting it to stage 0 below and if the bug's been fixed, throw an error
|
||||
* in dev mode to allow clean up in the future. Also, update the initial
|
||||
* stage number to -1.
|
||||
*/
|
||||
for (const line of tank.upgrade_lines) {
|
||||
const stage = upgrades[line.name];
|
||||
|
||||
if (base.stage_number === 0 && import.meta.env.DEV) {
|
||||
throw new Error(
|
||||
"Base stats are now stage 0. In-game bug fixed, please remove hack.",
|
||||
);
|
||||
stage0.number = 0;
|
||||
|
||||
for (let i = 0; i <= stage; i++) {
|
||||
patch(stage0, line.stages[i]);
|
||||
}
|
||||
}
|
||||
|
||||
stages.forEach((stage) => patch(stage0, stage));
|
||||
|
||||
return stage0;
|
||||
}
|
||||
@@ -20,7 +20,6 @@ type Characteristic = (helpers: {
|
||||
state: TankState;
|
||||
tank: Tank;
|
||||
parameters: StageParameters;
|
||||
id: string;
|
||||
}) => CharacteristicOutput;
|
||||
|
||||
export type CharacteristicName = keyof typeof characteristics;
|
||||
@@ -409,8 +408,8 @@ export const characteristics = {
|
||||
return tank.tank!.tank_type;
|
||||
},
|
||||
|
||||
id({ id }) {
|
||||
return id;
|
||||
id({ state }) {
|
||||
return state.id;
|
||||
},
|
||||
|
||||
slug({ tank }) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { Tank } from "@blitzkit/closed/protos/blitzkit/tank";
|
||||
import type { Tank } from "@protos/blitzkit/tank";
|
||||
import {
|
||||
ShellUpgradeSingleChange_AttributeName,
|
||||
TankAttributeChange_AttributeName,
|
||||
} from "@protos/game/proto/legacy/blitz_static_tank_upgrade_single_stage";
|
||||
import { aggregateStageParameters } from "./aggregateStageParameters";
|
||||
import { aggregateParameters } from "./aggregateParameters";
|
||||
import {
|
||||
characteristics,
|
||||
type CharacteristicName,
|
||||
@@ -16,15 +16,8 @@ export type ComputedCharacteristics = Record<
|
||||
CharacteristicOutput
|
||||
>;
|
||||
|
||||
export function computeCharacteristics(
|
||||
id: string,
|
||||
tank: Tank,
|
||||
state: TankState,
|
||||
) {
|
||||
const parameters = aggregateStageParameters(
|
||||
tank.tank!.base_stats!,
|
||||
tank.tank!.upgrade_stages.slice(0, state.stage),
|
||||
);
|
||||
export function computeCharacteristics(tank: Tank, state: TankState) {
|
||||
const parameters = aggregateParameters(tank.tank!, state.upgrades);
|
||||
|
||||
const computed: Partial<ComputedCharacteristics> = {};
|
||||
|
||||
@@ -94,7 +87,6 @@ export function computeCharacteristics(
|
||||
state,
|
||||
tank,
|
||||
parameters,
|
||||
id,
|
||||
});
|
||||
computed[name] = value;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
import type { TankCatalogComponent } from "@protos/blitz_static_tank_component";
|
||||
import { TerrainHardness } from "./tankState";
|
||||
import type { Tank } from "@protos/blitzkit/tank";
|
||||
import { TerrainHardness, type TankState } from "./tankState";
|
||||
|
||||
export function createTankState({ tank, id }: Tank) {
|
||||
const upgrades: Record<string, number> = {};
|
||||
|
||||
for (const upgradeLine of tank!.upgrade_lines) {
|
||||
upgrades[upgradeLine.name] = upgradeLine.stages.length - 1;
|
||||
}
|
||||
|
||||
export function createTankState(tank: TankCatalogComponent) {
|
||||
return {
|
||||
stage: tank.upgrade_stages.length,
|
||||
id,
|
||||
|
||||
upgrades,
|
||||
|
||||
shell: 0,
|
||||
isGunDamaged: false,
|
||||
isHullTraversing: false,
|
||||
isShooting: false,
|
||||
isTurretTraversing: false,
|
||||
speed: 0,
|
||||
terrainHardness: TerrainHardness.Hard,
|
||||
|
||||
isShooting: false,
|
||||
isGunDamaged: false,
|
||||
isTurretTraversing: false,
|
||||
isHullTraversing: false,
|
||||
isCaughtOnFire: false,
|
||||
};
|
||||
} satisfies TankState;
|
||||
}
|
||||
@@ -5,13 +5,17 @@ export enum TerrainHardness {
|
||||
}
|
||||
|
||||
export interface TankState {
|
||||
stage: number;
|
||||
id: string;
|
||||
|
||||
upgrades: Record<string, number>;
|
||||
|
||||
shell: number;
|
||||
speed: number;
|
||||
isHullTraversing: boolean;
|
||||
isTurretTraversing: boolean;
|
||||
terrainHardness: TerrainHardness;
|
||||
|
||||
isShooting: boolean;
|
||||
isGunDamaged: boolean;
|
||||
terrainHardness: TerrainHardness;
|
||||
isTurretTraversing: boolean;
|
||||
isHullTraversing: boolean;
|
||||
isCaughtOnFire: boolean;
|
||||
}
|
||||
@@ -1,251 +1,343 @@
|
||||
import { times } from "lodash-es";
|
||||
import { Suspense, useMemo } from "react";
|
||||
import { TankopediaCharacteristic } from "../../../../components/TankopediaCharacteristic";
|
||||
import { TankopediaToy } from "../../../../components/TankopediaToy";
|
||||
import { Grade } from "@blitzkit/closed/protos/game/proto/legacy/blitz_static_standard_grades_enum";
|
||||
import { StageParameters_StageType } from "@blitzkit/closed/protos/game/proto/legacy/blitz_static_tank_upgrade_single_stage";
|
||||
import { useMemo } from "react";
|
||||
import { api } from "../../../../core/api/dynamic";
|
||||
import { characteristicsOrder } from "../../../../core/tankopedia/characteristicsOrder";
|
||||
import { computeCharacteristics } from "../../../../core/tankopedia/computeCharacteristics";
|
||||
import { TerrainHardness } from "../../../../core/tankopedia/tankState";
|
||||
import { useAwait } from "../../../../hooks/useAwait";
|
||||
import { Tankopedia, TankopediaCompare } from "../../../../stores/tankopedia";
|
||||
import { useGameStrings } from "../../../../hooks/useGameStrings";
|
||||
import { LocaleProvider } from "../../../../hooks/useLocale";
|
||||
import { Tankopedia } from "../../../../stores/tankopedia";
|
||||
|
||||
interface PageProps {
|
||||
id: string;
|
||||
locale: string;
|
||||
}
|
||||
|
||||
export function Page({ id, locale }: PageProps) {
|
||||
const tank = useAwait(() => api.tank(id), `tank-${id}`);
|
||||
|
||||
Tankopedia.useInitialization(tank);
|
||||
|
||||
export function Page({ id }: { id: string }) {
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<Content id={id} />
|
||||
</Suspense>
|
||||
<LocaleProvider locale={locale}>
|
||||
<Content />
|
||||
</LocaleProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function Content({ id }: { id: string }) {
|
||||
const tank = useAwait(() => api.tank(id), `tank-${id}`);
|
||||
// const tankList = useAwait(() => api.tankList(), "tank-list");
|
||||
// const otherTanks = useAwait(() => api.tanks(), "tanks");
|
||||
|
||||
Tankopedia.useInitialization(tank.tank!);
|
||||
function Content() {
|
||||
const tankEntityGameStrings = useGameStrings("TankEntity");
|
||||
|
||||
const protagonist = Tankopedia.use((state) => state.protagonist);
|
||||
const compare = Tankopedia.use((state) => state.compare);
|
||||
const characteristics = useMemo(
|
||||
() => computeCharacteristics(id, tank, protagonist),
|
||||
[protagonist]
|
||||
const protagonistTank = useAwait(
|
||||
() => api.tank(protagonist.id),
|
||||
`tank-${protagonist.id}`,
|
||||
);
|
||||
// const otherCharacteristics = useMemo(
|
||||
// () =>
|
||||
// tankList.list
|
||||
// .filter((tank) => tank.id !== id)
|
||||
// .map(({ id }) => {
|
||||
// const state = { ...protagonist };
|
||||
// const tank = otherTanks.tanks[id];
|
||||
|
||||
// return computeCharacteristics(
|
||||
// id,
|
||||
// tank.tank!,
|
||||
// tank.compensation!,
|
||||
// state
|
||||
// );
|
||||
// }),
|
||||
// [protagonist]
|
||||
// );
|
||||
const { characteristics } = useMemo(
|
||||
() => computeCharacteristics(protagonistTank, protagonist),
|
||||
[protagonist],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>{id}</h1>
|
||||
|
||||
<div style={{ display: "flex", flexWrap: "wrap", gap: "1rem" }}>
|
||||
{tank.tank?.feature_screens?.tags.map((tag) => (
|
||||
<span>{tag.value}</span>
|
||||
))}
|
||||
</div>
|
||||
<h1>{protagonist.id}</h1>
|
||||
|
||||
<br />
|
||||
|
||||
<div style={{ display: "flex", flexWrap: "wrap" }}>
|
||||
{times(tank.tank!.upgrade_stages.length + 1, (index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.protagonist.stage = index;
|
||||
});
|
||||
}}
|
||||
>
|
||||
Stage {index} {index === protagonist.stage && "(selected)"}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<h2>upgrades</h2>
|
||||
|
||||
<br />
|
||||
{protagonistTank.tank!.upgrade_lines.map((line) => {
|
||||
return (
|
||||
<>
|
||||
<h3>
|
||||
{line.name} ({protagonist.upgrades[line.name]})
|
||||
</h3>
|
||||
|
||||
<div style={{ display: "flex", flexWrap: "wrap" }}>
|
||||
{characteristics.parameters.shells_upgrades.map((shell) => (
|
||||
<button
|
||||
key={shell.shell_id}
|
||||
onClick={() => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.protagonist.shell = shell.shell_id;
|
||||
});
|
||||
}}
|
||||
>
|
||||
Shell {shell.shell_id}{" "}
|
||||
{shell.shell_id === protagonist.shell && "(selected)"}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "flex-start",
|
||||
gap: 8,
|
||||
}}
|
||||
>
|
||||
{line.stages.map((stage, index) => {
|
||||
if (stage.custom_picture) {
|
||||
throw new Error("Custom picture not supported");
|
||||
}
|
||||
|
||||
<br />
|
||||
return (
|
||||
<button
|
||||
key={stage.tech_name}
|
||||
onClick={() => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.protagonist.upgrades[line.name] = index;
|
||||
|
||||
<div>
|
||||
<span>speed (0 - {characteristics.characteristics.speed_forward})</span>
|
||||
<input
|
||||
type="range"
|
||||
min={0}
|
||||
max={characteristics.characteristics.speed_forward as number}
|
||||
value={protagonist.speed}
|
||||
onChange={(event) => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.protagonist.speed = event.target.valueAsNumber;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
for (const required of stage.required_upgrades) {
|
||||
for (const line of protagonistTank.tank!
|
||||
.upgrade_lines) {
|
||||
let i = 0;
|
||||
|
||||
<br />
|
||||
for (const stage of line.stages) {
|
||||
if (stage.tech_name === required) {
|
||||
draft.protagonist.upgrades[line.name] =
|
||||
Math.max(
|
||||
draft.protagonist.upgrades[line.name],
|
||||
i,
|
||||
);
|
||||
}
|
||||
|
||||
<div>
|
||||
<span>isHullTraversing</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
value={`${protagonist.isHullTraversing}`}
|
||||
onChange={(event) => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.protagonist.isHullTraversing = event.target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<br />
|
||||
for (const line of protagonistTank.tank!
|
||||
.upgrade_lines) {
|
||||
let i = draft.protagonist.upgrades[line.name];
|
||||
|
||||
<div>
|
||||
<span>isTurretTraversing</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
value={`${protagonist.isTurretTraversing}`}
|
||||
onChange={(event) => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.protagonist.isTurretTraversing = event.target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
while (i > 0) {
|
||||
const currentStage = line.stages[i];
|
||||
const valid = currentStage.required_upgrades.every(
|
||||
(required) => {
|
||||
for (const line of protagonistTank.tank!
|
||||
.upgrade_lines) {
|
||||
const idx =
|
||||
draft.protagonist.upgrades[line.name];
|
||||
|
||||
<br />
|
||||
if (
|
||||
line.stages[idx]?.tech_name === required
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
<div>
|
||||
<span>isShooting</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
value={`${protagonist.isShooting}`}
|
||||
onChange={(event) => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.protagonist.isShooting = event.target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
return false;
|
||||
},
|
||||
);
|
||||
|
||||
<br />
|
||||
if (valid) break;
|
||||
|
||||
<div>
|
||||
<span>isGunDamaged</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
value={`${protagonist.isGunDamaged}`}
|
||||
onChange={(event) => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.protagonist.isGunDamaged = event.target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
i--;
|
||||
}
|
||||
|
||||
<br />
|
||||
draft.protagonist.upgrades[line.name] = i;
|
||||
}
|
||||
});
|
||||
}}
|
||||
style={{
|
||||
backgroundColor:
|
||||
protagonist.upgrades[line.name] === index
|
||||
? "green"
|
||||
: undefined,
|
||||
}}
|
||||
>
|
||||
{stage.tech_name}: {Grade[stage.grade]}
|
||||
{StageParameters_StageType[stage.stage_type]}
|
||||
{tankEntityGameStrings[stage.display_name]}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
|
||||
<div style={{ display: "flex", flexWrap: "wrap" }}>
|
||||
{Object.values(TerrainHardness).map(
|
||||
(hardness) =>
|
||||
typeof hardness === "number" && (
|
||||
<button
|
||||
key={hardness}
|
||||
onClick={() => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.protagonist.terrainHardness = hardness;
|
||||
});
|
||||
}}
|
||||
>
|
||||
{TerrainHardness[hardness]}{" "}
|
||||
{hardness === protagonist.terrainHardness && "(selected)"}
|
||||
</button>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
<h2>characteristics</h2>
|
||||
|
||||
<br />
|
||||
|
||||
<div>
|
||||
<span>isCaughtOnFire</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
value={`${protagonist.isCaughtOnFire}`}
|
||||
onChange={(event) => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.protagonist.isCaughtOnFire = event.target.checked;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<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) =>
|
||||
"toy" in config ? (
|
||||
<TankopediaToy key={`toy-${config.toy}`} toy={config.toy} />
|
||||
) : (
|
||||
<TankopediaCharacteristic
|
||||
key={`characteristic-${config.name}`}
|
||||
characteristic={characteristics.characteristics[config.name]}
|
||||
others={[]}
|
||||
config={config}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
{Object.entries(characteristics).map(([key, value]) => (
|
||||
<p>
|
||||
{key}: {value}
|
||||
</p>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
// const parameters = aggregateParameters(
|
||||
// protagonistTank.tank!,
|
||||
// protagonist.upgrades,
|
||||
// );
|
||||
|
||||
// if (parameters.modules_upgrades.length > 0) {
|
||||
// throw new Error("Modules upgrades display not implemented");
|
||||
// }
|
||||
|
||||
// return (
|
||||
// <>
|
||||
// <h1>{protagonist.id}</h1>
|
||||
|
||||
// <br />
|
||||
|
||||
// <h2>upgrades</h2>
|
||||
|
||||
// {protagonistTank.tank!.upgrade_lines.map((line) => {
|
||||
// return (
|
||||
// <>
|
||||
// <h3>
|
||||
// {line.name} ({protagonist.upgrades[line.name]})
|
||||
// </h3>
|
||||
|
||||
// <div
|
||||
// style={{
|
||||
// display: "flex",
|
||||
// flexDirection: "column",
|
||||
// alignItems: "flex-start",
|
||||
// gap: 8,
|
||||
// }}
|
||||
// >
|
||||
// {line.stages.map((stage, index) => {
|
||||
// if (stage.custom_picture) {
|
||||
// throw new Error("Custom picture not supported");
|
||||
// }
|
||||
|
||||
// return (
|
||||
// <button
|
||||
// key={stage.tech_name}
|
||||
// onClick={() => {
|
||||
// Tankopedia.mutate((draft) => {
|
||||
// draft.protagonist.upgrades[line.name] = index;
|
||||
|
||||
// for (const required of stage.required_upgrades) {
|
||||
// for (const line of protagonistTank.tank!
|
||||
// .upgrade_lines) {
|
||||
// let i = 0;
|
||||
|
||||
// for (const stage of line.stages) {
|
||||
// if (stage.tech_name === required) {
|
||||
// draft.protagonist.upgrades[line.name] =
|
||||
// Math.max(
|
||||
// draft.protagonist.upgrades[line.name],
|
||||
// i,
|
||||
// );
|
||||
// }
|
||||
|
||||
// i++;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// for (const line of protagonistTank.tank!
|
||||
// .upgrade_lines) {
|
||||
// let i = draft.protagonist.upgrades[line.name];
|
||||
|
||||
// while (i > 0) {
|
||||
// const currentStage = line.stages[i];
|
||||
// const valid = currentStage.required_upgrades.every(
|
||||
// (required) => {
|
||||
// for (const line of protagonistTank.tank!
|
||||
// .upgrade_lines) {
|
||||
// const idx =
|
||||
// draft.protagonist.upgrades[line.name];
|
||||
|
||||
// if (
|
||||
// line.stages[idx]?.tech_name === required
|
||||
// ) {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
|
||||
// return false;
|
||||
// },
|
||||
// );
|
||||
|
||||
// if (valid) break;
|
||||
|
||||
// i--;
|
||||
// }
|
||||
|
||||
// draft.protagonist.upgrades[line.name] = i;
|
||||
// }
|
||||
// });
|
||||
// }}
|
||||
// style={{
|
||||
// backgroundColor:
|
||||
// protagonist.upgrades[line.name] === index
|
||||
// ? "green"
|
||||
// : undefined,
|
||||
// }}
|
||||
// >
|
||||
// {stage.tech_name}: {Grade[stage.grade]}
|
||||
// {StageParameters_StageType[stage.stage_type]}
|
||||
// {tankEntityGameStrings[stage.display_name]}
|
||||
// </button>
|
||||
// );
|
||||
// })}
|
||||
// </div>
|
||||
// </>
|
||||
// );
|
||||
// })}
|
||||
|
||||
// <h2>characteristics</h2>
|
||||
|
||||
// {}
|
||||
|
||||
// <h2>parameters</h2>
|
||||
|
||||
// <h3>attributes</h3>
|
||||
|
||||
// {parameters.attributes.map((attribute) => {
|
||||
// return (
|
||||
// <span>
|
||||
// {TankAttributeChange_AttributeName[attribute.attribute_name]}:{" "}
|
||||
// {attribute.value}
|
||||
// </span>
|
||||
// );
|
||||
// })}
|
||||
|
||||
// <h3>penetration groups</h3>
|
||||
|
||||
// {parameters.penetration_groups_upgrades.map((upgrade) => {
|
||||
// return (
|
||||
// <>
|
||||
// <h4>{PenetrationGroupUpgrade_TankPart[upgrade.tank_part]}</h4>
|
||||
|
||||
// <span>
|
||||
// primary armor ({upgrade.primary_armor.length}):{" "}
|
||||
// {upgrade.primary_armor}
|
||||
// </span>
|
||||
|
||||
// <br />
|
||||
|
||||
// {upgrade.penetration_groups.map((group) => {
|
||||
// return (
|
||||
// <span>
|
||||
// {group.group_name}: {group.armor} (
|
||||
// {PenetrationGroup_CommonData[group.common_data]})
|
||||
// </span>
|
||||
// );
|
||||
// })}
|
||||
// </>
|
||||
// );
|
||||
// })}
|
||||
|
||||
// <h2>modules ({parameters.modules_upgrades.length})</h2>
|
||||
|
||||
// {parameters.modules_upgrades.map((upgrade) => {
|
||||
// return (
|
||||
// <span>{ModuleUpgrade_AttributeName[upgrade.attribute_name]}</span>
|
||||
// );
|
||||
// })}
|
||||
|
||||
// <h2>shells</h2>
|
||||
|
||||
// {parameters.shells_upgrades.map((upgrade) => {
|
||||
// return (
|
||||
// <>
|
||||
// <h3>
|
||||
// {upgrade.shell_id}: {ShellUpgrade_ShellType[upgrade.shell_type]}
|
||||
// </h3>
|
||||
|
||||
// <pre>
|
||||
// silver_price: {JSON.stringify(upgrade.silver_price, null, 2)}
|
||||
// </pre>
|
||||
|
||||
// <pre>
|
||||
// tray_shells: {JSON.stringify(upgrade.tray_shells, null, 2)}
|
||||
// </pre>
|
||||
// </>
|
||||
// );
|
||||
// })}
|
||||
// </>
|
||||
// );
|
||||
}
|
||||
@@ -19,5 +19,5 @@ const { id, locale } = Astro.props;
|
||||
---
|
||||
|
||||
<BaseLayout {locale} accent="violet">
|
||||
<Page {id} client:load />
|
||||
<Page {locale} {id} client:load />
|
||||
</BaseLayout>
|
||||
@@ -1,5 +1,6 @@
|
||||
import fuzzysort from "fuzzysort";
|
||||
import { useMemo } from "react";
|
||||
import { IncrementalLoader } from "../../../components/IncrementalLoader";
|
||||
import { TankCard } from "../../../components/TankCard";
|
||||
import { TankCards } from "../../../components/TankCards";
|
||||
import { TankSearch } from "../../../components/TankSearch";
|
||||
@@ -34,11 +35,14 @@ function Content() {
|
||||
const tanksOrdered = useMemo(() => {
|
||||
let list = Object.values(tanks.tanks);
|
||||
|
||||
list = list.sort((a, b) => a.tank!.tank_type - b.tank!.tank_type);
|
||||
list = list.sort((a, b) => a.tank!.tank_class - b.tank!.tank_class);
|
||||
list = list.sort((a, b) => a.tank!.nation - b.tank!.nation);
|
||||
list = list.sort((a, b) =>
|
||||
compareTiers(a.tank!.tier_catalog_id, b.tank!.tier_catalog_id),
|
||||
);
|
||||
|
||||
return list;
|
||||
return list.map(({ id }) => id);
|
||||
}, []);
|
||||
const tanksFiltered = useMemo(() => {
|
||||
if (!search) return tanksOrdered;
|
||||
@@ -47,17 +51,32 @@ function Content() {
|
||||
keys: ["id", "name", "deburred"],
|
||||
});
|
||||
|
||||
return results.map(({ obj }) => obj);
|
||||
return results.map(({ obj }) => obj.id);
|
||||
}, [search]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TankSearch />
|
||||
<TankCards>
|
||||
{tanksFiltered.slice(0, 32).map(({ id }) => (
|
||||
<TankCard key={id} id={id} />
|
||||
))}
|
||||
</TankCards>
|
||||
<List tanks={tanksFiltered} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface ListProps {
|
||||
tanks: string[];
|
||||
}
|
||||
|
||||
function List({ tanks }: ListProps) {
|
||||
const data = useMemo(() => tanks.map((id) => ({ key: id, id })), [tanks]);
|
||||
|
||||
return (
|
||||
<TankCards>
|
||||
<IncrementalLoader
|
||||
initial={7 * 5}
|
||||
intermediate={7 * 3}
|
||||
data={data}
|
||||
Component={TankCard}
|
||||
/>
|
||||
</TankCards>
|
||||
);
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { Nation } from "@protos/game/proto/legacy/blitz_static_tank_component";
|
||||
import type { APIContext, GetStaticPaths } from "astro";
|
||||
import { game } from "../../../core/game/game";
|
||||
import { game } from "../../../../core/game/game";
|
||||
|
||||
export const getStaticPaths = (async () => {
|
||||
return Object.values(Nation)
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { TankCatalogComponent } from "@protos/game/proto/legacy/blitz_static_tank_component";
|
||||
import type { Tank } from "@protos/blitzkit/tank";
|
||||
import { Varuna } from "varuna";
|
||||
import { createTankState } from "../core/tankopedia/createTankState";
|
||||
import { type TankState } from "../core/tankopedia/tankState";
|
||||
@@ -14,9 +14,8 @@ export enum TankopediaCompare {
|
||||
All,
|
||||
}
|
||||
|
||||
export const Tankopedia = new Varuna<Tankopedia, TankCatalogComponent>(
|
||||
(tank) => ({
|
||||
compare: TankopediaCompare.TierAndClass,
|
||||
protagonist: createTankState(tank),
|
||||
}),
|
||||
);
|
||||
export const Tankopedia = new Varuna<Tankopedia, Tank>((tank) => ({
|
||||
compare: TankopediaCompare.TierAndClass,
|
||||
|
||||
protagonist: createTankState(tank),
|
||||
}));
|
||||
@@ -1,3 +1,3 @@
|
||||
export type PropsWithSkeleton<Props> =
|
||||
| { skeleton: true }
|
||||
| { skeleton: true; onIntersection?: () => void }
|
||||
| (Props & { skeleton?: false });
|
||||
Reference in new issue
Block a user