diff --git a/docs/changelogs/1.29.10.md b/docs/changelogs/1.29.10.md new file mode 100644 index 000000000..c293c1b4f --- /dev/null +++ b/docs/changelogs/1.29.10.md @@ -0,0 +1,14 @@ +# BlitzKit 1.29.10 + +Thank you to [Mark Greene](https://github.com/Hitjack007) for your contributions this update. + +## Features + +- Combined game mode passive and active abilities into a single dropdown +- [Mark Greene] Added tier filters to the Guess game + - Changing tiers will break your streak + +## Fixes + +- [Mark Greene] module damage is now properly shown when sorting with it in the tank search +- Duplicate abilities in the tank filters are now removed diff --git a/package.json b/package.json index 9f39c56cf..8ee33cf43 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "blitzkit", - "version": "1.29.9", + "version": "1.29.10", "license": "BlitzKit License", "private": true, "scripts": { diff --git a/packages/i18n/strings/en.json b/packages/i18n/strings/en.json index 94def8954..4ecf9071a 100644 --- a/packages/i18n/strings/en.json +++ b/packages/i18n/strings/en.json @@ -762,6 +762,8 @@ "sorting_by": "Sorting by {name}", "clear": "Clear", "tier": "Tier {tier}", + "passive": "{name} (Passive)", + "active": "{name} (Active)", "no_tanks_found": { "body": "No tanks found.", @@ -928,7 +930,10 @@ "description": "Reveals the tank without showing the name. This round will be considered incorrect and break your streak!", "cancel": "Cancel", "reveal": "Reveal" - } + }, + "clear": "Clear", + "select_all": "Select all", + "tier": "Tier {tier}" }, "tankopedia": { diff --git a/packages/website/src/components/Guesser.tsx b/packages/website/src/components/Guesser.tsx index ce512cffb..5c3b1d0a3 100644 --- a/packages/website/src/components/Guesser.tsx +++ b/packages/website/src/components/Guesser.tsx @@ -9,13 +9,17 @@ import { ArrowRightIcon, EyeOpenIcon, MagnifyingGlassIcon, + MixerVerticalIcon, PaperPlaneIcon, + StarFilledIcon, + TrashIcon, } from "@radix-ui/react-icons"; import { AlertDialog, Box, Button, Card, + DropdownMenu, Flex, IconButton, Spinner, @@ -23,7 +27,7 @@ import { TextField, } from "@radix-ui/themes"; import fuzzysort from "fuzzysort"; -import { debounce } from "lodash-es"; +import { debounce, times } from "lodash-es"; import { useCallback, useEffect, useRef, useState } from "react"; import { awaitableTankDefinitions } from "../core/awaitables/tankDefinitions"; import { awaitableTankNames } from "../core/awaitables/tankNames"; @@ -53,6 +57,7 @@ export function Guesser() { const [searching, setSearching] = useState(false); const [results, setResults] = useState(null); const [selected, setSelected] = useState(null); + const tiers = Guess.use((state) => state.tiers); const requestSearch = useCallback(() => { setSelected(null); @@ -97,58 +102,60 @@ export function Guesser() { width="100%" p="4" maxWidth="25rem" - gap="4" + gap="3" > - {results !== null && ( - - - {results.length === 0 && ( - - - {strings.website.tools.guess.search.no_results} - - - )} + + {results !== null && ( + + + {results.length === 0 && ( + + + {strings.website.tools.guess.search.no_results} + + + )} - - {results.map((result) => { - const Icon = classIcons[result.class]; + + {results.map((result) => { + const Icon = classIcons[result.class]; - return ( - { - if (!input.current) return; + return ( + { + if (!input.current) return; - input.current.value = unwrap(result.name); - setSelected(result); - setResults(null); - }} - text={unwrap(result.name)} - prefix={ - - } - discriminator={ - - - {TIER_ROMAN_NUMERALS[result.tier]} - - } - /> - ); - })} - - - - )} + input.current.value = unwrap(result.name); + setSelected(result); + setResults(null); + }} + text={unwrap(result.name)} + prefix={ + + } + discriminator={ + + + {TIER_ROMAN_NUMERALS[result.tier]} + + } + /> + ); + })} + + + + )} + @@ -161,11 +168,78 @@ export function Guesser() { + + + + + + + + + {times(10, (index) => { + const tier = 10 - index; + const isSelected = tiers.includes(tier); + + return ( + { + event.preventDefault(); + + Guess.mutate((draft) => { + if (isSelected) { + if (draft.tiers.length === 1) return; + draft.tiers = draft.tiers.filter((t) => t !== tier); + } else { + draft.tiers = [...draft.tiers, tier]; + } + + draft.streak = 0; + }); + }} + > + {literals(strings.website.tools.guess.tier, { + tier: TIER_ROMAN_NUMERALS[tier], + })} + + ); + })} + + + + { + event.preventDefault(); + + Guess.mutate((draft) => { + draft.tiers = Guess.initial.tiers; + }); + }} + > + {strings.website.tools.guess.select_all} + + + { + event.preventDefault(); + + Guess.mutate((draft) => { + draft.tiers = []; + }); + }} + > + {strings.website.tools.guess.clear} + + + + { + const tank = tankDefinitions.tanks[Number(id)]; + return tiers.includes(tank.tier); + }); + const id = Number( + filteredIds[Math.floor(Math.random() * filteredIds.length)] + ); const tank = tankDefinitions.tanks[id]; Guess.mutate((draft) => { diff --git a/packages/website/src/components/TankSearch/components/Card.tsx b/packages/website/src/components/TankSearch/components/Card.tsx index d15d15a5e..aab5084dc 100644 --- a/packages/website/src/components/TankSearch/components/Card.tsx +++ b/packages/website/src/components/TankSearch/components/Card.tsx @@ -41,7 +41,7 @@ export function TankSearchCard({ tank, onSelect }: TankSearchCardProps) { case 'fire.damage': return shell0.armor_damage.toFixed(0); case 'fire.moduleDamage': - return shell0.armor_damage.toFixed(0); + return shell0.module_damage.toFixed(0); case 'fire.dispersionMoving': return tracks.dispersion_move.toFixed(3); case 'fire.dispersionStill': diff --git a/packages/website/src/components/TankSearch/components/FilterControl.tsx b/packages/website/src/components/TankSearch/components/FilterControl.tsx index fc51ff253..75f50318f 100644 --- a/packages/website/src/components/TankSearch/components/FilterControl.tsx +++ b/packages/website/src/components/TankSearch/components/FilterControl.tsx @@ -83,11 +83,36 @@ for (const gameModeIdString in gameModeRoleSets) { for (const roleId of roles) { const role = gameDefinitions.roles[roleId]; - for (const id of role.consumables) { + outerLoop: for (const id of role.consumables) { + const name = + consumableDefinitions.consumables[id].name.locales[locales.default]; + + for (const otherConsumable of consumables.values()) { + const otherName = + consumableDefinitions.consumables[otherConsumable].name.locales[ + locales.default + ]; + + if (name === otherName) continue outerLoop; + } + consumables.add(id); allGameModeConsumables.add(id); } - for (const id of role.provisions) { + + outerLoop: for (const id of role.provisions) { + const name = + provisionDefinitions.provisions[id].name.locales[locales.default]; + + for (const otherProvision of provisions.values()) { + const otherName = + provisionDefinitions.provisions[otherProvision].name.locales[ + locales.default + ]; + + if (name === otherName) continue outerLoop; + } + provisions.add(id); allGameModeProvisions.add(id); } @@ -155,7 +180,7 @@ const TIERS = times(10, (i) => 10 - i); export function FilterControl() { return ( - + @@ -168,8 +193,7 @@ export function FilterControl() { - - + @@ -1031,22 +1055,33 @@ function ProvisionsFilter() { ); } -function PowersFilter() { +function GameModeAbilitiesFilter() { const { unwrap, strings } = useLocale(); + const rawAbilities = TankFilters.use((state) => state.abilities); const rawPowers = TankFilters.use((state) => state.powers); + + const abilities = + rawAbilities.length === 0 && rawPowers.length === 0 + ? Array.from(allGameModeConsumables.values()) + : rawAbilities; const powers = - rawPowers.length === 0 + rawPowers.length === 0 && rawAbilities.length === 0 ? Array.from(allGameModeProvisions.values()) : rawPowers; + const icons = [ + ...abilities.map((ability) => `icons/consumables/${ability}.webp`), + ...powers.map((power) => `icons/provisions/${power}.webp`), + ]; + return ( - - - - {gameModeRoles.map(({ gameModeId, consumables }) => { - const gameMode = gameDefinitions.gameModes[gameModeId]; - - if (consumables.length === 0) return null; - - return ( - - {unwrap(gameMode.name)} - - {consumables.map((consumable) => { - { - const selected = rawAbilities.includes(consumable); - const abilityDefinition = - consumableDefinitions.consumables[consumable]; - - return ( - { - event.preventDefault(); - - TankFilters.mutate((draft) => { - if (selected) { - draft.abilities = draft.abilities.filter( - (n) => n !== consumable - ); - } else { - draft.abilities = [...draft.abilities, consumable]; - } - }); - }} - checked={selected} - key={consumable} - > - - - {unwrap(abilityDefinition.name)} - - ); - } - })} - - ); - })} - - - - { - event.preventDefault(); - - TankFilters.mutate((draft) => { - draft.abilities = []; - }); - }} - > - - {strings.website.common.tank_search.clear} - - - - ); -} diff --git a/packages/website/src/pages/[...locale]/guess/_index.tsx b/packages/website/src/pages/[...locale]/guess/_index.tsx index 7282e7bbc..d172884ff 100644 --- a/packages/website/src/pages/[...locale]/guess/_index.tsx +++ b/packages/website/src/pages/[...locale]/guess/_index.tsx @@ -1,5 +1,5 @@ import { Box, Flex, Heading } from "@radix-ui/themes"; -import { Suspense } from "react"; +import { Suspense, useEffect } from "react"; import { GuessBackground } from "../../../components/GuessBackground"; import { Guesser } from "../../../components/Guesser"; import { GuessRenderer } from "../../../components/GuessRenderer"; @@ -62,6 +62,31 @@ function Content({ skeleton }: MaybeSkeletonComponentProps) { const name = unwrap(tank.name); const fontSize = `min(48vh, ${55 / name.length}vw)`; const transitionDuration = isRevealed ? "2s" : undefined; + const tiers = Guess.use((state) => state.tiers); + + useEffect(() => { + const isTankAlreadyCompliant = tiers.includes(tank.tier); + + if (isTankAlreadyCompliant) return; + + const filteredIds = ids.filter((id) => { + const tank = tankDefinitions.tanks[Number(id)]; + return tiers.includes(tank.tier); + }); + + if (filteredIds.length === 0) return; + + const id = Number( + filteredIds[Math.floor(Math.random() * filteredIds.length)] + ); + const newTank = tankDefinitions.tanks[id]; + + Guess.mutate((draft) => { + draft.tank = newTank; + draft.guessState = GuessState.NotGuessed; + draft.helpingReveal = false; + }); + }, [tiers]); return ( diff --git a/packages/website/src/stores/guess.ts b/packages/website/src/stores/guess.ts index 22d6a10f0..0a3e94f49 100644 --- a/packages/website/src/stores/guess.ts +++ b/packages/website/src/stores/guess.ts @@ -1,4 +1,5 @@ import type { TankDefinition } from "@blitzkit/core"; +import { times } from "lodash-es"; import { Varuna } from "varuna"; export enum GuessState { @@ -14,6 +15,7 @@ export interface Guess { correctGuesses: number; streak: number; helpingReveal: boolean; + tiers: number[]; } export const Guess = new Varuna((tank) => ({ @@ -23,4 +25,5 @@ export const Guess = new Varuna((tank) => ({ correctGuesses: 0, streak: 0, helpingReveal: false, + tiers: times(10, (index) => index + 1), }));