Feature: tier XI compatibility

Prepares BlitzKit for tier XI tanks ahead of their release. Tier X was hardcoded as the maximum in the tank search filters, the guesser and the tech tree, and had no roman numeral beyond X.

- Added `XI` to `TIER_ROMAN_NUMERALS`
- Added an awaitable tier list derived from the highest tier in the tank definitions, so nothing changes until tier XI tanks exist
- Used it for the tank search tier filters and the guesser tier picker and defaults
- Made the tech tree check for successors instead of assuming tier X is the last tier

Fixes #302
This commit is contained in:
Mark Greene committed 2026-10-05 18:18:19 +13:00
1 parent 116c30be88
commit e4e92d76b4
8 files changed
+28 -13

No files matched your search

@@ -54,6 +54,7 @@ export const TIER_ROMAN_NUMERALS: Record<number, string> = {
8: "VIII", 8: "VIII",
9: "IX", 9: "IX",
10: "X", 10: "X",
11: "XI",
}; };
export const flags: Record<string, string> = { export const flags: Record<string, string> = {
+5 -4
View File
@@ -27,9 +27,10 @@ import {
TextField, TextField,
} from "@radix-ui/themes"; } from "@radix-ui/themes";
import fuzzysort from "fuzzysort"; import fuzzysort from "fuzzysort";
import { debounce, times } from "lodash-es"; import { debounce } from "lodash-es";
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { awaitableTankNames } from "../core/awaitables/tankNames"; import { awaitableTankNames } from "../core/awaitables/tankNames";
import { awaitableTiers } from "../core/awaitables/tiers";
import { api } from "../core/blitzkit/api"; import { api } from "../core/blitzkit/api";
import { useLocale } from "../hooks/useLocale"; import { useLocale } from "../hooks/useLocale";
import { Guess, GuessState } from "../stores/guess"; import { Guess, GuessState } from "../stores/guess";
@@ -38,9 +39,10 @@ import { SearchResults } from "./SearchResults";
const { go } = fuzzysort; const { go } = fuzzysort;
const [tankNames, tankDefinitions] = await Promise.all([ const [tankNames, tankDefinitions, TIERS] = await Promise.all([
awaitableTankNames, awaitableTankNames,
api.tankDefinitions(), api.tankDefinitions(),
awaitableTiers,
]); ]);
const ids = Object.keys(tankDefinitions.tanks); const ids = Object.keys(tankDefinitions.tanks);
@@ -193,8 +195,7 @@ export function Guesser() {
</DropdownMenu.Trigger> </DropdownMenu.Trigger>
<DropdownMenu.Content> <DropdownMenu.Content>
{times(10, (index) => { {TIERS.map((tier) => {
const tier = 10 - index;
const isSelected = tiers.includes(tier); const isSelected = tiers.includes(tier);
return ( return (
@@ -25,8 +25,8 @@ import {
Tooltip, Tooltip,
type FlexProps, type FlexProps,
} from "@radix-ui/themes"; } from "@radix-ui/themes";
import { times } from "lodash-es";
import { Fragment, type ComponentProps, type ReactNode } from "react"; import { Fragment, type ComponentProps, type ReactNode } from "react";
import { awaitableTiers } from "../../../core/awaitables/tiers";
import { api } from "../../../core/blitzkit/api"; import { api } from "../../../core/blitzkit/api";
import { useLocale } from "../../../hooks/useLocale"; import { useLocale } from "../../../hooks/useLocale";
import { App } from "../../../stores/app"; import { App } from "../../../stores/app";
@@ -171,7 +171,7 @@ const GUN_TYPES = Object.keys(
const MAX_ICONS = 4; const MAX_ICONS = 4;
const TIERS = times(10, (i) => 10 - i); const TIERS = await awaitableTiers;
export function FilterControl() { export function FilterControl() {
return ( return (
@@ -1,10 +1,12 @@
import { TIER_ROMAN_NUMERALS } from "@blitzkit/core"; import { TIER_ROMAN_NUMERALS } from "@blitzkit/core";
import { Flex, IconButton, Text } from "@radix-ui/themes"; import { Flex, IconButton, Text } from "@radix-ui/themes";
import { times } from "lodash-es";
import { memo } from "react"; import { memo } from "react";
import { awaitableTiers } from "../../../core/awaitables/tiers";
import { TankFilters } from "../../../stores/tankFilters"; import { TankFilters } from "../../../stores/tankFilters";
import { TankSort } from "../../../stores/tankopediaSort"; import { TankSort } from "../../../stores/tankopediaSort";
const TIERS = await awaitableTiers;
export const TierFilter = memo(() => { export const TierFilter = memo(() => {
const by = TankSort.use((state) => state.by); const by = TankSort.use((state) => state.by);
const search = TankFilters.use((state) => state.search); const search = TankFilters.use((state) => state.search);
@@ -19,8 +21,7 @@ export const TierFilter = memo(() => {
overflow="hidden" overflow="hidden"
style={{ borderRadius: "var(--radius-full)" }} style={{ borderRadius: "var(--radius-full)" }}
> >
{times(10, (index) => { {TIERS.map((tier) => {
const tier = 10 - index;
const selected = tiers.includes(tier); const selected = tiers.includes(tier);
return ( return (
@@ -127,7 +127,7 @@ export function Node({ id, highlight, nextIds, skeleton }: NodeProps) {
</Text> </Text>
</Flex> </Flex>
{averages && nextTanks && tank.tier !== 10 && ( {averages && nextTanks && nextTanks.length > 0 && (
<Text color="gray" size="1" mt="2"> <Text color="gray" size="1" mt="2">
{skeleton && <Skeleton height="1em" width="4em" />} {skeleton && <Skeleton height="1em" width="4em" />}
{!skeleton && {!skeleton &&
@@ -191,7 +191,7 @@ export function TechTreeSection({ skeleton }: MaybeSkeletonComponentProps) {
); );
})} })}
{master.tier < 10 && ( {master.successors.length > 0 && (
<> <>
<Arrow /> <Arrow />
{master.successors!.map((id, index) => ( {master.successors!.map((id, index) => (
@@ -0,0 +1,10 @@
import { times } from "lodash-es";
import { api } from "../blitzkit/api";
export const awaitableTiers = api.tankDefinitions().then((tankDefinitions) => {
const maxTier = Math.max(
...Object.values(tankDefinitions.tanks).map((tank) => tank.tier),
);
return times(maxTier, (index) => maxTier - index);
});
+4 -2
View File
@@ -1,6 +1,8 @@
import type { TankDefinition } from "@blitzkit/core"; import type { TankDefinition } from "@blitzkit/core";
import { times } from "lodash-es";
import { Varuna } from "varuna"; import { Varuna } from "varuna";
import { awaitableTiers } from "../core/awaitables/tiers";
const tiers = await awaitableTiers;
export enum GuessState { export enum GuessState {
Correct, Correct,
@@ -25,5 +27,5 @@ export const Guess = new Varuna<Guess, TankDefinition>((tank) => ({
correctGuesses: 0, correctGuesses: 0,
streak: 0, streak: 0,
helpingReveal: false, helpingReveal: false,
tiers: times(10, (index) => index + 1), tiers,
})); }));