Added ability to filter tanks by available provisions

This commit is contained in:
Mark Greene committed 2025-12-30 20:45:44 +13:00
1 parent b479c09cf8
commit b6204b1c2c
3 files changed
+186 -2

No files matched your search

@@ -18,8 +18,10 @@ import {
type ButtonProps,
type FlexProps,
} from "@radix-ui/themes";
import { isEqual, times } from "lodash-es";
import { isEqual, times, chunk } from "lodash-es";
import React from "react";
import { awaitableGameDefinitions } from "../../../core/awaitables/gameDefinitions";
import { awaitableConsumableDefinitions } from "../../../core/awaitables/consumableDefinitions";
import { Var } from "../../../core/radix/var";
import { useLocale } from "../../../hooks/useLocale";
import { App } from "../../../stores/app";
@@ -35,6 +37,7 @@ import { ScienceIcon } from "../../ScienceIcon";
import { ScienceOffIcon } from "../../ScienceOffIcon";
const gameDefinitions = await awaitableGameDefinitions;
const consumableDefinitions = await awaitableConsumableDefinitions;
const shellTypeIcons: Record<ShellType, string> = {
[ShellType.AP]: "ap",
@@ -43,6 +46,25 @@ const shellTypeIcons: Record<ShellType, string> = {
[ShellType.HEAT]: "hc",
};
// Get consumables
const regularConsumables = Object.values(
consumableDefinitions.consumables
).filter(
(consumable) =>
!consumable.game_mode_exclusive && consumable.name.locales.en !== ""
);
// Split consumables into chunks of 5 for columns
const consumableColumns = chunk(regularConsumables, 5);
// Get game mode exclusive abilities
const gameModeAbilities = Object.values(
consumableDefinitions.consumables
).filter((consumable) => consumable.game_mode_exclusive);
// Split abilities into chunks of 5 for columns
const abilityColumns = chunk(gameModeAbilities, 5);
const size: ButtonProps["size"] = { initial: "1", xs: "2" };
function ShellFilter({ index, premium }: { index: number; premium?: boolean }) {
@@ -337,6 +359,151 @@ export function FilterControl() {
</Flex>
</Flex>
{/* Consumables Filter - RIGHT AFTER NATIONS */}
<Flex
flexShrink="0"
overflow="hidden"
style={{ borderRadius: "var(--radius-4)" }}
>
<Flex direction="column">
{consumableColumns[0]?.map((consumable) => {
const selected = tankFilters.consumables?.includes(consumable.id);
return (
<IconButton
size={size}
key={consumable.id}
variant={selected ? "solid" : "soft"}
color={selected ? undefined : "gray"}
highContrast
radius="none"
onClick={() => {
TankFilters.mutate((draft) => {
if (tankFilters.consumables.includes(consumable.id)) {
draft.consumables = draft.consumables.filter(
(c) => c !== consumable.id
);
} else {
draft.consumables = [
...draft.consumables,
consumable.id,
];
}
});
}}
>
<img
style={{ width: "1em", height: "1em" }}
src={asset(`icons/consumables/${consumable.id}.webp`)}
/>
</IconButton>
);
})}
</Flex>
{consumableColumns[1] && (
<>
<Box
display={{ initial: "none", md: "block" }}
style={{
height: Var("space-1"),
backgroundColor: Var("gray-a3"),
}}
/>
<Flex direction="column">
{consumableColumns[1].map((consumable) => {
const selected = tankFilters.consumables?.includes(
consumable.id
);
return (
<IconButton
size={size}
key={consumable.id}
style={{ flex: 1 }}
variant={selected ? "solid" : "soft"}
color={selected ? undefined : "gray"}
highContrast
radius="none"
onClick={() => {
TankFilters.mutate((draft) => {
if (tankFilters.consumables.includes(consumable.id)) {
draft.consumables = draft.consumables.filter(
(c) => c !== consumable.id
);
} else {
draft.consumables = [
...draft.consumables,
consumable.id,
];
}
});
}}
>
<img
style={{ width: "1em", height: "1em" }}
src={asset(`icons/consumables/${consumable.id}.webp`)}
/>
</IconButton>
);
})}
</Flex>
</>
)}
{consumableColumns[2] && (
<>
<Box
display={{ initial: "none", md: "block" }}
style={{
height: Var("space-1"),
backgroundColor: Var("gray-a3"),
}}
/>
<Flex direction="column">
{consumableColumns[2].map((consumable) => {
const selected = tankFilters.consumables?.includes(
consumable.id
);
return (
<IconButton
size={size}
key={consumable.id}
style={{ flex: 1 }}
variant={selected ? "solid" : "soft"}
color={selected ? undefined : "gray"}
highContrast
radius="none"
onClick={() => {
TankFilters.mutate((draft) => {
if (tankFilters.consumables.includes(consumable.id)) {
draft.consumables = draft.consumables.filter(
(c) => c !== consumable.id
);
} else {
draft.consumables = [
...draft.consumables,
consumable.id,
];
}
});
}}
>
<img
style={{ width: "1em", height: "1em" }}
src={asset(`icons/consumables/${consumable.id}.webp`)}
/>
</IconButton>
);
})}
</Flex>
</>
)}
</Flex>
<Flex
overflow="hidden"
style={{ borderRadius: "var(--radius-full)" }}
@@ -1,8 +1,11 @@
import type { TankDefinition } from "@blitzkit/core";
import { checkConsumableProvisionInclusivity } from "@blitzkit/core/src/blitzkit/checkConsumableProvisionInclusivity";
import { times } from "lodash-es";
import { awaitableConsumableDefinitions } from "../../core/awaitables/consumableDefinitions";
import type { TankFilters } from "../../stores/tankFilters";
const SHELLS = times(3, (index) => index);
const consumableDefinitions = await awaitableConsumableDefinitions;
export async function filterTank(
filters: TankFilters,
@@ -40,6 +43,18 @@ export async function filterTank(
gun.shells[index].type === filters.shells[index]
)
)
)
) &&
(filters.consumables.length === 0 ||
filters.consumables.every((consumableId) =>
tank.turrets.some((turret) =>
turret.guns.some((gun) =>
checkConsumableProvisionInclusivity(
consumableDefinitions.consumables[consumableId],
tank,
gun
)
)
)
))
);
}
@@ -21,6 +21,7 @@ export interface TankFilters {
searching: boolean;
gunType: CaseType<GunDefinition>[];
shells: [ShellType | null, ShellType | null, ShellType | null];
consumables: number[];
showTesting: boolean;
showNonTesting: boolean;
@@ -37,6 +38,7 @@ export const TankFilters = new Varuna<TankFilters>({
searching: false,
gunType: [],
shells: [null, null, null],
consumables: [], // Added this line
showTesting: true,
showNonTesting: true,