mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 02:37:05 +02:00
Merge branch 'main' into equalizer-background-sliver
This commit is contained in:
31 files changed
+170
-55
No files matched your search
+3
-2
@@ -4,12 +4,13 @@
|
||||
"license": "BlitzKit License",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "cd packages/website && bun run dev",
|
||||
"dev": "cd packages/website && bun --bun run dev",
|
||||
"build": "cd packages/website && bun run build",
|
||||
"discover-ids": "cd packages/scripts && bun run discover-ids",
|
||||
"build-protos": "cd packages/scripts && bun run build-protos",
|
||||
"record-performance": "cd packages/scripts && bun run record-performance",
|
||||
"download-client": "cd packages/scripts && bun run download-client"
|
||||
"download-client": "cd packages/scripts && bun run download-client",
|
||||
"clean-astro": "rm -rf packages/website/.astro packages/website/node_modules/.astro packages/website/node_modules/.vite"
|
||||
},
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
|
||||
@@ -25,8 +25,8 @@ import {
|
||||
VehicleDefinitionList,
|
||||
} from "@blitzkit/core";
|
||||
import { AbstractVFS } from "@blitzkit/core/src/blitzkit/vfs/abstract";
|
||||
import { SUPPORTED_LOCALE_BLITZ_MAP } from "@blitzkit/i18n";
|
||||
import locales from "@blitzkit/i18n/locales.json";
|
||||
import { SUPPORTED_LOCALE_BLITZ_MAP } from "@blitzkit/i18n/src/strings";
|
||||
import { parse as parseYaml } from "yaml";
|
||||
import { BlitzKitAPI } from "../base";
|
||||
|
||||
@@ -113,6 +113,9 @@ export abstract class ServerBlitzKitAPI0 extends BlitzKitAPI {
|
||||
}
|
||||
|
||||
async init() {
|
||||
const { existsSync } = await import("fs");
|
||||
const { readFile, writeFile, mkdir } = await import("fs/promises");
|
||||
|
||||
console.log("Initializing virtual file system...");
|
||||
await this.vfs.init();
|
||||
|
||||
@@ -155,31 +158,37 @@ export abstract class ServerBlitzKitAPI0 extends BlitzKitAPI {
|
||||
|
||||
console.log("Fetching game localizations...");
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
locales.supported = [locales.supported[0]];
|
||||
}
|
||||
|
||||
let fetchedLocalizations = 0;
|
||||
await Promise.all(
|
||||
locales.supported.map(async ({ locale }) => {
|
||||
const root = "../../temp/game-strings";
|
||||
const cachePath = `${root}/${locale}.json`;
|
||||
|
||||
if (existsSync(cachePath)) {
|
||||
console.log(` Cache hit for ${locale}`);
|
||||
|
||||
const content = await readFile(cachePath, "utf-8");
|
||||
this.stringsI18n[locale] = JSON.parse(content) as BlitzStrings;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(` Cache miss for ${locale} (this will take a while...)`);
|
||||
|
||||
const blitzLocale = SUPPORTED_LOCALE_BLITZ_MAP[locale];
|
||||
const cache = await fetch(
|
||||
const networkStrings = (await fetch(
|
||||
`https://stufficons.wgcdn.co/localizations/${blitzLocale}.yaml`,
|
||||
)
|
||||
.then((response) => response.text())
|
||||
.then((string) => parseYaml(string) as BlitzStrings);
|
||||
const preInstalled = await this.vfs.yaml<BlitzStrings>(
|
||||
.then(parseYaml)) as BlitzStrings;
|
||||
const localStrings = await this.vfs.yaml<BlitzStrings>(
|
||||
`Data/Strings/${blitzLocale}.yaml`,
|
||||
);
|
||||
const combined = { ...networkStrings, ...localStrings };
|
||||
|
||||
this.stringsI18n[locale] = {
|
||||
...cache,
|
||||
...preInstalled,
|
||||
};
|
||||
await mkdir(root, { recursive: true });
|
||||
await writeFile(cachePath, JSON.stringify(combined));
|
||||
|
||||
console.log(
|
||||
`Fetched localizations for ${locale} (${++fetchedLocalizations}/${locales.supported.length})`,
|
||||
);
|
||||
this.stringsI18n[locale] = combined;
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ export function metaSortTank(
|
||||
gameDefinitions: GameDefinitions,
|
||||
) {
|
||||
return tanks
|
||||
.sort((a, b) => a.id - b.id)
|
||||
.sort((a, b) => b.tier - a.tier)
|
||||
.sort(
|
||||
(a, b) => treeTypeOrder.indexOf(b.type) - treeTypeOrder.indexOf(a.type),
|
||||
|
||||
@@ -54,6 +54,7 @@ export const TIER_ROMAN_NUMERALS: Record<number, string> = {
|
||||
8: "VIII",
|
||||
9: "IX",
|
||||
10: "X",
|
||||
11: "XI",
|
||||
};
|
||||
|
||||
export const flags: Record<string, string> = {
|
||||
|
||||
@@ -215,6 +215,7 @@
|
||||
"title": "Settings",
|
||||
"description": "Change BlitzKit settings",
|
||||
"dev_mode": "Development mode",
|
||||
"auto_load_models": "Auto-load models",
|
||||
"language": "Language"
|
||||
},
|
||||
|
||||
@@ -458,6 +459,8 @@
|
||||
},
|
||||
|
||||
"sandbox": {
|
||||
"load_model": "Load model",
|
||||
|
||||
"poses": {
|
||||
"hull_down": "Hull down",
|
||||
"face_hug": "Face hug",
|
||||
|
||||
@@ -194,6 +194,7 @@
|
||||
"title": "Settings",
|
||||
"description": "Change BlitzKit settings",
|
||||
"dev_mode": "Modo de desarrollador",
|
||||
"auto_load_models": "Auto-load models",
|
||||
"language": "Idioma"
|
||||
},
|
||||
"common": {
|
||||
@@ -412,6 +413,7 @@
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
"load_model": "Load model",
|
||||
"poses": {
|
||||
"hull_down": "Hull down",
|
||||
"face_hug": "Cara a cara",
|
||||
|
||||
@@ -194,6 +194,7 @@
|
||||
"title": "Paramètres",
|
||||
"description": "Modifier les paramètres de BlitzKit",
|
||||
"dev_mode": "Mode développement",
|
||||
"auto_load_models": "Auto-load models",
|
||||
"language": "Langue"
|
||||
},
|
||||
"common": {
|
||||
@@ -412,6 +413,7 @@
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
"load_model": "Load model",
|
||||
"poses": {
|
||||
"hull_down": "Hull down",
|
||||
"face_hug": "Face hug",
|
||||
|
||||
@@ -194,6 +194,7 @@
|
||||
"title": "Settings",
|
||||
"description": "Change BlitzKit settings",
|
||||
"dev_mode": "開発者モード",
|
||||
"auto_load_models": "Auto-load models",
|
||||
"language": "言語 / Language"
|
||||
},
|
||||
"common": {
|
||||
@@ -412,6 +413,7 @@
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
"load_model": "Load model",
|
||||
"poses": {
|
||||
"hull_down": "ハルダウン",
|
||||
"face_hug": "密着",
|
||||
|
||||
@@ -194,6 +194,7 @@
|
||||
"title": "Settings",
|
||||
"description": "Change BlitzKit settings",
|
||||
"dev_mode": "Development mode",
|
||||
"auto_load_models": "Auto-load models",
|
||||
"language": "Language"
|
||||
},
|
||||
"common": {
|
||||
@@ -412,6 +413,7 @@
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
"load_model": "Load model",
|
||||
"poses": {
|
||||
"hull_down": "Hull down",
|
||||
"face_hug": "Face hug",
|
||||
|
||||
@@ -194,6 +194,7 @@
|
||||
"title": "Settings",
|
||||
"description": "Change BlitzKit settings",
|
||||
"dev_mode": "Modo de desenvolvimento",
|
||||
"auto_load_models": "Auto-load models",
|
||||
"language": "Idioma"
|
||||
},
|
||||
"common": {
|
||||
@@ -412,6 +413,7 @@
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
"load_model": "Load model",
|
||||
"poses": {
|
||||
"hull_down": "Casco para baixo",
|
||||
"face_hug": "Abraço facial",
|
||||
|
||||
@@ -194,6 +194,7 @@
|
||||
"title": "Настройки",
|
||||
"description": "Изменить настройки BlitzKit",
|
||||
"dev_mode": "Режим разработчика",
|
||||
"auto_load_models": "Auto-load models",
|
||||
"language": "Язык"
|
||||
},
|
||||
"common": {
|
||||
@@ -412,6 +413,7 @@
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
"load_model": "Load model",
|
||||
"poses": {
|
||||
"hull_down": "За холмом",
|
||||
"face_hug": "В упор",
|
||||
|
||||
@@ -194,6 +194,7 @@
|
||||
"title": "Налаштування",
|
||||
"description": "Змінити налаштування BlitzKit ",
|
||||
"dev_mode": "Режим розробника",
|
||||
"auto_load_models": "Auto-load models",
|
||||
"language": "Мова"
|
||||
},
|
||||
"common": {
|
||||
@@ -412,6 +413,7 @@
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
"load_model": "Load model",
|
||||
"poses": {
|
||||
"hull_down": "Від башти",
|
||||
"face_hug": "Клінч",
|
||||
|
||||
@@ -194,6 +194,7 @@
|
||||
"title": "设置",
|
||||
"description": "更改 BlitzKit 设置",
|
||||
"dev_mode": "开发者模式",
|
||||
"auto_load_models": "Auto-load models",
|
||||
"language": "语言"
|
||||
},
|
||||
"common": {
|
||||
@@ -412,6 +413,7 @@
|
||||
}
|
||||
},
|
||||
"sandbox": {
|
||||
"load_model": "Load model",
|
||||
"poses": {
|
||||
"hull_down": "俯角视角",
|
||||
"face_hug": "顶牛视角",
|
||||
|
||||
@@ -135,7 +135,7 @@ let regionI = 0;
|
||||
|
||||
console.log("Starting discovery loop...");
|
||||
|
||||
atMaxRate(async ({ _break: break1 }) => {
|
||||
await atMaxRate(async ({ _break: break1 }) => {
|
||||
if (regions.length === 0) {
|
||||
console.log("All regions exhausted, exiting discovery loop...");
|
||||
return break1();
|
||||
@@ -166,18 +166,19 @@ atMaxRate(async ({ _break: break1 }) => {
|
||||
fields
|
||||
}&account_id=${accountIds}`;
|
||||
|
||||
atMaxRate(async ({ _break: break2 }) => {
|
||||
if (idRange.length < MAX_IDS_PER_CALL) {
|
||||
console.log(
|
||||
`Removing region ${region.domain} removed due to ID range exhaustion...`,
|
||||
);
|
||||
regions.splice(regionI, 1);
|
||||
}
|
||||
if (idRange.length < MAX_IDS_PER_CALL) {
|
||||
console.log(
|
||||
`Removing region ${region.domain} removed due to ID range exhaustion...`,
|
||||
);
|
||||
regions.splice(regionI, 1);
|
||||
}
|
||||
|
||||
const response = await fetch(url);
|
||||
const data = (await response.json()) as QuickInfo;
|
||||
await atMaxRate(async ({ _break: break2 }) => {
|
||||
const data = await fetch(url)
|
||||
.then((response) => response.json() as Promise<QuickInfo>)
|
||||
.catch(() => null);
|
||||
|
||||
if (data.status !== "ok") {
|
||||
if (data === null || data.status !== "ok") {
|
||||
console.warn("Failed API call; trying again...");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,9 @@ interface MethodContext {
|
||||
let lastT = 0;
|
||||
const maxDelta = 1000 / API_RATE;
|
||||
const DELTA_BUFFER = 0;
|
||||
export async function atMaxRate(method: (context: MethodContext) => void) {
|
||||
export async function atMaxRate(
|
||||
method: (context: MethodContext) => void | Promise<void>,
|
||||
) {
|
||||
let isBroken = false;
|
||||
|
||||
const context: MethodContext = {
|
||||
@@ -39,7 +41,7 @@ export async function atMaxRate(method: (context: MethodContext) => void) {
|
||||
await sleep(maxDelta - delta + DELTA_BUFFER);
|
||||
}
|
||||
|
||||
method(context);
|
||||
await method(context);
|
||||
lastT = t;
|
||||
}
|
||||
}
|
||||
@@ -27,9 +27,10 @@ import {
|
||||
TextField,
|
||||
} from "@radix-ui/themes";
|
||||
import fuzzysort from "fuzzysort";
|
||||
import { debounce, times } from "lodash-es";
|
||||
import { debounce } from "lodash-es";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { awaitableTankNames } from "../core/awaitables/tankNames";
|
||||
import { awaitableTiers } from "../core/awaitables/tiers";
|
||||
import { api } from "../core/blitzkit/api";
|
||||
import { useLocale } from "../hooks/useLocale";
|
||||
import { Guess, GuessState } from "../stores/guess";
|
||||
@@ -38,9 +39,10 @@ import { SearchResults } from "./SearchResults";
|
||||
|
||||
const { go } = fuzzysort;
|
||||
|
||||
const [tankNames, tankDefinitions] = await Promise.all([
|
||||
const [tankNames, tankDefinitions, TIERS] = await Promise.all([
|
||||
awaitableTankNames,
|
||||
api.tankDefinitions(),
|
||||
awaitableTiers,
|
||||
]);
|
||||
|
||||
const ids = Object.keys(tankDefinitions.tanks);
|
||||
@@ -193,8 +195,7 @@ export function Guesser() {
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
<DropdownMenu.Content>
|
||||
{times(10, (index) => {
|
||||
const tier = 10 - index;
|
||||
{TIERS.map((tier) => {
|
||||
const isSelected = tiers.includes(tier);
|
||||
|
||||
return (
|
||||
|
||||
@@ -25,8 +25,8 @@ import {
|
||||
Tooltip,
|
||||
type FlexProps,
|
||||
} from "@radix-ui/themes";
|
||||
import { times } from "lodash-es";
|
||||
import { Fragment, type ComponentProps, type ReactNode } from "react";
|
||||
import { awaitableTiers } from "../../../core/awaitables/tiers";
|
||||
import { api } from "../../../core/blitzkit/api";
|
||||
import { useLocale } from "../../../hooks/useLocale";
|
||||
import { App } from "../../../stores/app";
|
||||
@@ -171,7 +171,7 @@ const GUN_TYPES = Object.keys(
|
||||
|
||||
const MAX_ICONS = 4;
|
||||
|
||||
const TIERS = times(10, (i) => 10 - i);
|
||||
const TIERS = await awaitableTiers;
|
||||
|
||||
export function FilterControl() {
|
||||
return (
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { TIER_ROMAN_NUMERALS } from "@blitzkit/core";
|
||||
import { Flex, IconButton, Text } from "@radix-ui/themes";
|
||||
import { times } from "lodash-es";
|
||||
import { memo } from "react";
|
||||
import { awaitableTiers } from "../../../core/awaitables/tiers";
|
||||
import { TankFilters } from "../../../stores/tankFilters";
|
||||
import { TankSort } from "../../../stores/tankopediaSort";
|
||||
|
||||
const TIERS = await awaitableTiers;
|
||||
|
||||
export const TierFilter = memo(() => {
|
||||
const by = TankSort.use((state) => state.by);
|
||||
const search = TankFilters.use((state) => state.search);
|
||||
@@ -19,8 +21,7 @@ export const TierFilter = memo(() => {
|
||||
overflow="hidden"
|
||||
style={{ borderRadius: "var(--radius-full)" }}
|
||||
>
|
||||
{times(10, (index) => {
|
||||
const tier = 10 - index;
|
||||
{TIERS.map((tier) => {
|
||||
const selected = tiers.includes(tier);
|
||||
|
||||
return (
|
||||
|
||||
+3
-1
@@ -13,6 +13,7 @@ import { modelTransformEvent } from "../../../../../../../core/blitzkit/modelTra
|
||||
import { Var } from "../../../../../../../core/radix/var";
|
||||
import { useLocale } from "../../../../../../../hooks/useLocale";
|
||||
import { Duel } from "../../../../../../../stores/duel";
|
||||
import { Tankopedia } from "../../../../../../../stores/tankopedia";
|
||||
import type { MaybeSkeletonComponentProps } from "../../../../../../../types/maybeSkeletonComponentProps";
|
||||
import { VisualizerCard } from "../VisualizerCard";
|
||||
import { VisualizerCornerStat } from "../VisualizerCornerStat";
|
||||
@@ -43,6 +44,7 @@ export function GunFlexibilityVisualizer({
|
||||
const tank = Duel.use((state) => state.protagonist.tank);
|
||||
const turret = Duel.use((state) => state.protagonist.turret);
|
||||
const gun = Duel.use((state) => state.protagonist.gun);
|
||||
const modelRequested = Tankopedia.use((state) => state.modelRequested);
|
||||
|
||||
const tankModel = modelDefinition.models[tank.id];
|
||||
const turretModel = tankModel.turrets[turret.id];
|
||||
@@ -193,7 +195,7 @@ export function GunFlexibilityVisualizer({
|
||||
top="0"
|
||||
left="0"
|
||||
>
|
||||
{!skeleton && <FlexibilityCanvas />}
|
||||
{!skeleton && modelRequested && <FlexibilityCanvas />}
|
||||
|
||||
<Box
|
||||
position="absolute"
|
||||
|
||||
@@ -77,6 +77,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||
const revealed = Tankopedia.use((state) => state.revealed);
|
||||
const disturbed = Tankopedia.use((state) => state.disturbed);
|
||||
const highGraphics = TankopediaPersistent.use((state) => state.highGraphics);
|
||||
const modelRequested = Tankopedia.use((state) => state.modelRequested);
|
||||
const equalize = Duel.use((state) => state.equalize);
|
||||
const equalizer = Duel.use((state) => state.antagonist.tank.equalizer);
|
||||
|
||||
@@ -283,7 +284,7 @@ export function Options({ thicknessRange, canvas, skeleton }: OptionsProps) {
|
||||
<SpallLinerSwitcher />
|
||||
</Flex>
|
||||
|
||||
{!skeleton && (
|
||||
{!skeleton && modelRequested && (
|
||||
<Suspense>
|
||||
<DynamicArmorSwitcher />
|
||||
</Suspense>
|
||||
|
||||
+38
-4
@@ -1,11 +1,14 @@
|
||||
import { Flex, Heading } from "@radix-ui/themes";
|
||||
import { UpdateIcon } from "@radix-ui/react-icons";
|
||||
import { Button, Flex, Heading } from "@radix-ui/themes";
|
||||
import { useLocale } from "../../../../../../hooks/useLocale";
|
||||
import { Duel } from "../../../../../../stores/duel";
|
||||
import { Tankopedia } from "../../../../../../stores/tankopedia";
|
||||
import type { MaybeSkeletonComponentProps } from "../../../../../../types/maybeSkeletonComponentProps";
|
||||
import { Tracker } from "./components/Tracker";
|
||||
|
||||
export function Title() {
|
||||
const { unwrap } = useLocale();
|
||||
export function Title({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
const { unwrap, strings } = useLocale();
|
||||
const modelRequested = Tankopedia.use((state) => state.modelRequested);
|
||||
const protagonist = Duel.use((state) => state.protagonist.tank);
|
||||
const revealed = Tankopedia.use((state) => state.revealed);
|
||||
const disturbed = Tankopedia.use((state) => state.disturbed);
|
||||
@@ -21,6 +24,8 @@ export function Title() {
|
||||
onPointerDown={(event) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (!modelRequested) return;
|
||||
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.revealed = true;
|
||||
});
|
||||
@@ -60,7 +65,36 @@ export function Title() {
|
||||
{name}
|
||||
</Heading>
|
||||
|
||||
{!revealed && <Tracker fontSize={fontSize} />}
|
||||
{!skeleton && !revealed && modelRequested && (
|
||||
<Tracker fontSize={fontSize} />
|
||||
)}
|
||||
|
||||
{!skeleton && !modelRequested && (
|
||||
<Flex
|
||||
position="absolute"
|
||||
left="50%"
|
||||
top="50%"
|
||||
width="min(100%, 16rem)"
|
||||
justify="center"
|
||||
style={{
|
||||
transform: `translate(-50%, calc(${fontSize} * 0.75 + var(--space-4) - 50%))`,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="solid"
|
||||
highContrast
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
onClick={() => {
|
||||
Tankopedia.mutate((draft) => {
|
||||
draft.modelRequested = true;
|
||||
});
|
||||
}}
|
||||
>
|
||||
<UpdateIcon />
|
||||
{strings.website.tools.tankopedia.sandbox.load_model}
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
+1
@@ -82,6 +82,7 @@ export function Lighting({ hasPbr }: Props) {
|
||||
blur={2 ** 1}
|
||||
resolution={2 ** 9}
|
||||
depthWrite={false}
|
||||
renderOrder={1}
|
||||
/>
|
||||
|
||||
{animate && (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Box, Flex } from "@radix-ui/themes";
|
||||
import { times } from "lodash-es";
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import { Suspense, useEffect, useMemo, useRef } from "react";
|
||||
import { api } from "../../../core/blitzkit/api";
|
||||
import { defaultEqualizer } from "../../../core/blitzkit/tankToDuelMember";
|
||||
import { useFullScreen } from "../../../hooks/useFullScreen";
|
||||
@@ -20,6 +20,7 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
const canvas = useRef<HTMLCanvasElement>(null!);
|
||||
const isFullScreen = useFullScreen();
|
||||
const protagonist = Duel.use((state) => state.protagonist.tank);
|
||||
const modelRequested = Tankopedia.use((state) => state.modelRequested);
|
||||
const thicknessRange = useMemo(() => {
|
||||
const entries = Object.values(tankDefinitions.tanks);
|
||||
const filtered = entries.filter(
|
||||
@@ -131,13 +132,15 @@ export function HeroSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
top="0"
|
||||
left="0"
|
||||
>
|
||||
{!skeleton && (
|
||||
<TankSandbox ref={canvas} thicknessRange={thicknessRange} />
|
||||
{!skeleton && modelRequested && (
|
||||
<Suspense>
|
||||
<TankSandbox ref={canvas} thicknessRange={thicknessRange} />
|
||||
</Suspense>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Title />
|
||||
<Title skeleton={skeleton} />
|
||||
|
||||
<Options
|
||||
skeleton={skeleton}
|
||||
|
||||
@@ -127,7 +127,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
{averages && nextTanks && tank.tier !== 10 && (
|
||||
{averages && nextTanks && (
|
||||
<Text color="gray" size="1" mt="2">
|
||||
{skeleton && <Skeleton height="1em" width="4em" />}
|
||||
{!skeleton &&
|
||||
|
||||
@@ -191,7 +191,7 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
|
||||
);
|
||||
})}
|
||||
|
||||
{master.tier < 10 && (
|
||||
{master.successors.length > 0 && (
|
||||
<>
|
||||
<Arrow />
|
||||
{master.successors!.map((id, index) => (
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { times } from "lodash-es";
|
||||
import { api } from "../blitzkit/api";
|
||||
|
||||
export const awaitableTiers = api.tankDefinitions().then((tankDefinitions) => {
|
||||
let max = 0;
|
||||
|
||||
for (const tank of Object.values(tankDefinitions.tanks)) {
|
||||
if (tank.tier > max) max = tank.tier;
|
||||
}
|
||||
|
||||
return times(max, (index) => max - index);
|
||||
});
|
||||
@@ -3,11 +3,13 @@ import { useRef } from "react";
|
||||
|
||||
export function useStableProgress() {
|
||||
const { loaded, total } = useProgress();
|
||||
const baseline = useRef(loaded);
|
||||
const progress = useRef(0);
|
||||
const pending = total - baseline.current;
|
||||
|
||||
progress.current = Math.max(
|
||||
progress.current,
|
||||
total === 0 ? 0 : (loaded / total) * 100
|
||||
pending <= 0 ? 0 : ((loaded - baseline.current) / pending) * 100
|
||||
);
|
||||
|
||||
return progress.current;
|
||||
|
||||
@@ -18,6 +18,10 @@ export function Page({ locale }: LocaleAcceptorProps) {
|
||||
|
||||
function Content({ locale }: LocaleAcceptorProps) {
|
||||
const developerMode = App.useDeferred((state) => state.developerMode, false);
|
||||
const autoLoadModels = App.useDeferred(
|
||||
(state) => state.autoLoadModels,
|
||||
true,
|
||||
);
|
||||
const { strings } = useLocale();
|
||||
|
||||
return (
|
||||
@@ -36,6 +40,19 @@ function Content({ locale }: LocaleAcceptorProps) {
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Flex align="center" gap="2" justify="between">
|
||||
<Text>{strings.website.settings.auto_load_models}</Text>
|
||||
<Switch
|
||||
variant="classic"
|
||||
checked={autoLoadModels}
|
||||
onCheckedChange={(checked) =>
|
||||
App.mutate((draft) => {
|
||||
draft.autoLoadModels = checked;
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Flex align="center" gap="2" justify="between">
|
||||
<Text>{strings.website.settings.language}</Text>
|
||||
<LocaleSwitcher locale={locale} />
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface WargamingLogin {
|
||||
|
||||
export interface App {
|
||||
developerMode: boolean;
|
||||
autoLoadModels: boolean;
|
||||
policiesAgreementIndex: number;
|
||||
logins: {
|
||||
wargaming?: WargamingLogin;
|
||||
@@ -17,6 +18,7 @@ export interface App {
|
||||
export const App = new Varuna<App>(
|
||||
{
|
||||
developerMode: false,
|
||||
autoLoadModels: true,
|
||||
policiesAgreementIndex: -1,
|
||||
logins: {},
|
||||
},
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { TankDefinition } from "@blitzkit/core";
|
||||
import { times } from "lodash-es";
|
||||
import { Varuna } from "varuna";
|
||||
import { awaitableTiers } from "../core/awaitables/tiers";
|
||||
|
||||
const tiers = await awaitableTiers;
|
||||
|
||||
export enum GuessState {
|
||||
Correct,
|
||||
@@ -25,5 +27,5 @@ export const Guess = new Varuna<Guess, TankDefinition>((tank) => ({
|
||||
correctGuesses: 0,
|
||||
streak: 0,
|
||||
helpingReveal: false,
|
||||
tiers: times(10, (index) => index + 1),
|
||||
tiers,
|
||||
}));
|
||||
@@ -9,6 +9,7 @@ import type { ArmorType } from "../components/Armor/components/SpacedArmorScene"
|
||||
import type { ExternalModuleVariant } from "../components/Armor/components/SpacedArmorSceneComponent";
|
||||
import type { XP_MULTIPLIERS } from "../components/Tankopedia/TechTreeSection";
|
||||
import { api } from "../core/blitzkit/api";
|
||||
import { App } from "./app";
|
||||
import { TankopediaDisplay } from "./tankopediaPersistent/constants";
|
||||
|
||||
export interface ShotLayerBase {
|
||||
@@ -79,6 +80,7 @@ export enum TankopediaRelativeAgainst {
|
||||
interface Tankopedia {
|
||||
disturbed: boolean;
|
||||
revealed: boolean;
|
||||
modelRequested: boolean;
|
||||
shot?: Shot;
|
||||
skills: Record<string, number>;
|
||||
relativeAgainst: TankopediaRelativeAgainst;
|
||||
@@ -110,6 +112,7 @@ const skillDefinitions = await api.skillDefinitions();
|
||||
export const Tankopedia = new Varuna<Tankopedia, ModelDefinition>((model) => ({
|
||||
disturbed: false,
|
||||
revealed: false,
|
||||
modelRequested: App.state.autoLoadModels,
|
||||
relativeAgainst: TankopediaRelativeAgainst.Class,
|
||||
editStatic: false,
|
||||
skills: createDefaultSkills(skillDefinitions),
|
||||
|
||||
Reference in new issue
Block a user