diff --git a/src/app/tools/tankopedia/[id]/components/Characteristics/index.tsx b/src/app/tools/tankopedia/[id]/components/Characteristics/index.tsx index be696b587..0e0a4bde0 100644 --- a/src/app/tools/tankopedia/[id]/components/Characteristics/index.tsx +++ b/src/app/tools/tankopedia/[id]/components/Characteristics/index.tsx @@ -343,7 +343,7 @@ export function Characteristics() { {stats.dispersion} mutateTankopediaFilters((draft) => { - draft.tier = tier; + draft.tier = draft.tier === tier ? undefined : tier; }) } > @@ -65,7 +65,7 @@ export function FilterControl() { highContrast onClick={() => mutateTankopediaFilters((draft) => { - draft.tier = tier; + draft.tier = draft.tier === tier ? undefined : tier; }) } > diff --git a/src/app/tools/tankopedia/components/Results.tsx b/src/app/tools/tankopedia/components/Results.tsx index 440ab02ad..c1c48b989 100644 --- a/src/app/tools/tankopedia/components/Results.tsx +++ b/src/app/tools/tankopedia/components/Results.tsx @@ -3,7 +3,9 @@ import { go } from 'fuzzysort'; import { times } from 'lodash'; import { memo, use, useMemo } from 'react'; import { ExperimentIcon } from '../../../../components/ExperimentIcon'; +import { TANK_CLASSES } from '../../../../components/Tanks/components/Item/constants'; import { resolveNearPenetration } from '../../../../core/blitz/resolveNearPenetration'; +import { gameDefinitions } from '../../../../core/blitzkit/gameDefinitions'; import { modelDefinitions } from '../../../../core/blitzkit/modelDefinitions'; import { normalizeBoundingBox } from '../../../../core/blitzkit/normalizeBoundingBox'; import { resolveDpm } from '../../../../core/blitzkit/resolveDpm'; @@ -14,6 +16,7 @@ import { tankNames, tanksDefinitionsArray, } from '../../../../core/blitzkit/tankDefinitions'; +import { tankopediaFilterTank } from '../../../../core/blitzkit/tankopediaFilterTank'; import { unionBoundingBox } from '../../../../core/blitzkit/unionBoundingBox'; import { useTankopediaFilters } from '../../../../stores/tankopediaFilters'; import { FilterControl } from './FilterControl'; @@ -22,35 +25,41 @@ import { SearchBar } from './SearchBar'; import { SkeletonTankCard } from './SkeletonTankCard'; import { TankCard } from './TankCard'; import { TankCardWrapper } from './TankCardWrapper'; -import { TierCard } from './TierCard'; +import { treeTypeOrder } from './TankSearch/constants'; export const Results = memo(() => { + const awaitedGameDefinitions = use(gameDefinitions); const awaitedModelDefinitions = use(modelDefinitions); const awaitedTankDefinitions = use(tankDefinitions); const awaitedTanksDefinitionsArray = use(tanksDefinitionsArray); const awaitedTankNames = use(tankNames); - const testing = useTankopediaFilters((state) => state.testing); - const search = useTankopediaFilters((state) => state.search); - const tier = useTankopediaFilters((state) => state.tier); - const sort = useTankopediaFilters((state) => state.sort); - const searching = useTankopediaFilters((state) => state.searching); - const searchedTanks = useMemo(() => { - if (search !== undefined) { - const searchedRaw = go(search, awaitedTankNames, { - key: 'combined', - limit: 25, - }); - const searchedTanks = searchedRaw.map( - (result) => awaitedTankDefinitions[result.obj.id], + const filters = useTankopediaFilters(); + const tanks = useMemo(() => { + if (filters.search === undefined) { + const filtered = awaitedTanksDefinitionsArray.filter((tank) => + tankopediaFilterTank(filters, tank), ); - return searchedTanks; - } - - if (sort.by !== 'meta.tier') { - const filtered = awaitedTanksDefinitionsArray; let sorted: TankDefinition[]; - switch (sort.by) { + switch (filters.sort.by) { + case 'meta.none': + sorted = filtered + .sort( + (a, b) => + treeTypeOrder.indexOf(b.treeType) - + treeTypeOrder.indexOf(a.treeType), + ) + .sort( + (a, b) => + TANK_CLASSES.indexOf(b.class) - TANK_CLASSES.indexOf(a.class), + ) + .sort( + (a, b) => + awaitedGameDefinitions.nations.indexOf(b.nation) - + awaitedGameDefinitions.nations.indexOf(a.nation), + ); + break; + case 'survivability.health': { sorted = filtered.sort((a, b) => { const aHealth = a.health + a.turrets.at(-1)!.health; @@ -82,8 +91,10 @@ export const Results = memo(() => { case 'survivability.camouflageShooting': sorted = filtered.sort( (a, b) => - a.turrets.at(-1)!.guns.at(-1)!.camouflageLoss - - b.turrets.at(-1)!.guns.at(-1)!.camouflageLoss, + a.camouflage.still * + a.turrets.at(-1)!.guns.at(-1)!.camouflageLoss - + b.camouflage.still * + b.turrets.at(-1)!.guns.at(-1)!.camouflageLoss, ); break; @@ -187,16 +198,10 @@ export const Results = memo(() => { sorted = filtered.sort( (a, b) => resolveNearPenetration( - ( - a.turrets.at(-1)!.guns.at(-1)!.shells[1] ?? - a.turrets.at(-1)!.guns.at(-1)!.shells[0] - ).penetration, + a.turrets.at(-1)!.guns.at(-1)!.shells[1]?.penetration ?? 0, ) - resolveNearPenetration( - ( - b.turrets.at(-1)!.guns.at(-1)!.shells[1] ?? - b.turrets.at(-1)!.guns.at(-1)!.shells[0] - ).penetration, + b.turrets.at(-1)!.guns.at(-1)!.shells[1]?.penetration ?? 0, ), ); break; @@ -246,23 +251,26 @@ export const Results = memo(() => { (a, b) => awaitedModelDefinitions[a.id].turrets[a.turrets.at(-1)!.id].guns[ a.turrets.at(-1)!.guns.at(-1)!.id - ].pitch.max - + ].pitch.max + + (awaitedModelDefinitions[a.id].turretRotation?.pitch ?? 0) - awaitedModelDefinitions[b.id].turrets[b.turrets.at(-1)!.id].guns[ b.turrets.at(-1)!.guns.at(-1)!.id - ].pitch.max, + ].pitch.max - + (awaitedModelDefinitions[b.id].turretRotation?.pitch ?? 0), ); break; case 'fire.gunElevation': sorted = filtered.sort( (a, b) => - // reversed because of negative values awaitedModelDefinitions[b.id].turrets[b.turrets.at(-1)!.id].guns[ b.turrets.at(-1)!.guns.at(-1)!.id - ].pitch.min - + ].pitch.min + + (awaitedModelDefinitions[b.id].turretRotation?.pitch ?? 0) - awaitedModelDefinitions[a.id].turrets[a.turrets.at(-1)!.id].guns[ a.turrets.at(-1)!.guns.at(-1)!.id - ].pitch.min, + ].pitch.min - + (awaitedModelDefinitions[a.id].turretRotation?.pitch ?? 0), ); break; @@ -324,19 +332,26 @@ export const Results = memo(() => { break; } - return sort.direction === 'ascending' ? sorted : sorted.reverse(); + return filters.sort.direction === 'ascending' ? sorted : sorted.reverse(); + } else { + const searchedRaw = go(filters.search, awaitedTankNames, { + key: 'combined', + limit: 25, + }); + const searchedTanks = searchedRaw.map( + (result) => awaitedTankDefinitions[result.obj.id], + ); + return searchedTanks; } - - return undefined; - }, [search, sort]); + }, [filters]); return ( - + - {!search && !searching && } + {!filters.search && !filters.searching && } - {testing === 'only' && ( + {filters.testing === 'only' && ( @@ -350,25 +365,21 @@ export const Results = memo(() => { )} - {!search && sort.by === 'meta.tier' && !searching && ( - - )} - - {(search || sort.by !== 'meta.tier') && !searching && searchedTanks && ( + {!filters.searching && ( <> - {searchedTanks.length > 0 && ( + {tanks.length > 0 && ( - {searchedTanks.map((tank) => ( + {tanks.map((tank) => ( ))} )} - {searchedTanks.length === 0 && } + {tanks.length === 0 && } )} - {searching && ( + {filters.searching && ( {times(Math.round(10 + 10 * Math.random()), (index) => ( diff --git a/src/app/tools/tankopedia/components/Sort.tsx b/src/app/tools/tankopedia/components/Sort.tsx index ea1e52b4f..e0a50f4a8 100644 --- a/src/app/tools/tankopedia/components/Sort.tsx +++ b/src/app/tools/tankopedia/components/Sort.tsx @@ -19,23 +19,18 @@ export function Sort() { - Sort by + { + mutateTankopediaFilters((draft) => { + draft.sort.by = 'meta.none'; + }); + }} + > + No sort + - - Meta - - { - mutateTankopediaFilters((draft) => { - draft.sort.by = 'meta.tier'; - }); - }} - > - Tier - - - + Sort by Fire @@ -305,8 +300,6 @@ export function Sort() { - - Direction (null); - // const sortBy = useTankopediaFilters((state) => state.sort.by); - // const discriminator = useMemo(() => { - // if (sortBy.startsWith('meta')) return undefined; + const sortBy = useTankopediaFilters((state) => state.sort.by); + const discriminator = useMemo(() => { + if (sortBy.startsWith('meta')) return undefined; - // const turret = tank.turrets.at(-1)!; - // const gun = turret.guns.at(-1)!; - // const shell = gun.shells[0]; - // const tracks = tank.tracks.at(-1)!; - // const tankModelDefinition = awaitedModelDefinitions[tank.id]; + const turret = tank.turrets.at(-1)!; + const gun = turret.guns.at(-1)!; + const shell0 = gun.shells[0]; + const shell1 = gun.shells[1]; + const tracks = tank.tracks.at(-1)!; + const engine = tank.engines.at(-1)!; + const tankModelDefinition = awaitedModelDefinitions[tank.id]; + const turretModelDefinition = tankModelDefinition.turrets[turret.id]; + const gunModelDefinition = turretModelDefinition.guns[gun.id]; - // switch (sortBy) { - // case 'fire.aimTime': - // return `${gun.aimTime.toFixed(2)}s`; - // case 'fire.caliber': - // return `${shell.caliber.toFixed(0)}mm`; - // case 'fire.damage': - // return `${shell.damage.armor.toFixed(0)}hp`; - // case 'fire.dispersionMoving': - // return `+ ${tracks.dispersion.move.toFixed(3)}`; - // case 'fire.dispersionStill': - // return gun.dispersion.base.toFixed(3); - // case 'fire.dpm': - // return resolveDpm(gun, shell).toFixed(0); - // case 'fire.gunDepression': - // return awaitedModelDefinitions; - // } - // }, [sortBy]); + switch (sortBy) { + case 'fire.aimTime': + return gun.aimTime.toFixed(2); + case 'fire.caliber': + return shell0.caliber.toFixed(0); + case 'fire.damage': + return shell0.damage.armor.toFixed(0); + case 'fire.dispersionMoving': + return tracks.dispersion.move.toFixed(3); + case 'fire.dispersionStill': + return gun.dispersion.base.toFixed(3); + case 'fire.dpm': + return resolveDpm(gun, shell0).toFixed(0); + case 'fire.reload': + return resolveReload(gun).toFixed(2); + case 'fire.standardPenetration': + return resolveNearPenetration(shell0.penetration).toFixed(0); + case 'fire.premiumPenetration': + return shell1 + ? resolveNearPenetration(shell1.penetration).toFixed(0) + : '--'; + case 'fire.shellVelocity': + return shell0.speed.toFixed(0); + case 'fire.gunDepression': + return ( + gunModelDefinition.pitch.max + + (tankModelDefinition.turretRotation?.pitch ?? 0) + ).toFixed(1); + case 'fire.gunElevation': + return ( + -gunModelDefinition.pitch.min - + (tankModelDefinition.turretRotation?.pitch ?? 0) + ).toFixed(1); + case 'maneuverability.forwardsSpeed': + return tank.speed.forwards.toFixed(0); + case 'maneuverability.backwardsSpeed': + return tank.speed.backwards.toFixed(0); + case 'maneuverability.power': + return engine.power.toFixed(0); + case 'maneuverability.powerToWeight': + return ( + (1000 * engine.power) / + (tank.weight + + engine.weight + + tracks.weight + + turret.weight + + gun.weight) + ).toFixed(1); + case 'maneuverability.weight': + return ( + (tank.weight + + engine.weight + + tracks.weight + + turret.weight + + gun.weight) / + 1000 + ).toFixed(1); + case 'maneuverability.traverseSpeed': + return tracks.traverseSpeed.toFixed(1); + case 'survivability.health': + return (tank.health + turret.health).toFixed(0); + case 'survivability.viewRange': + return turret.viewRange.toFixed(0); + case 'survivability.camouflageStill': + return (tank.camouflage.still * 100).toFixed(0); + case 'survivability.camouflageMoving': + return (tank.camouflage.moving * 100).toFixed(0); + case 'survivability.camouflageShooting': + return (tank.camouflage.still * gun.camouflageLoss * 100).toFixed(0); + case 'survivability.volume': { + const dimensions = normalizeBoundingBox( + unionBoundingBox( + tankModelDefinition.boundingBox, + turretModelDefinition.boundingBox, + ), + ); - // useEffect(() => { - // const unsubscribe = useTankopediaFilters.subscribe((filters) => { - // if (!link.current) return; + return (dimensions[0] * dimensions[1] * dimensions[2]).toFixed(0); + } - // const visible = tankopediaFilterTank(filters, tank); - // link.current.style.display = visible ? 'flex' : 'none'; - // }); - - // return unsubscribe; - // }, []); + case 'survivability.length': + return Math.max( + ...normalizeBoundingBox( + unionBoundingBox( + tankModelDefinition.boundingBox, + turretModelDefinition.boundingBox, + ), + ), + ).toFixed(0); + } + }, [sortBy]); return ( - {/* {discriminator && ( + {discriminator && ( {discriminator} - )} */} + )} ); } diff --git a/src/app/tools/tankopedia/components/TankSearch/index.tsx b/src/app/tools/tankopedia/components/TankSearch/index.tsx index 951c088c1..c0a0cbb4a 100644 --- a/src/app/tools/tankopedia/components/TankSearch/index.tsx +++ b/src/app/tools/tankopedia/components/TankSearch/index.tsx @@ -101,7 +101,7 @@ export function TankSearch({ let sorted: TankDefinition[] = []; switch (sort.by) { - case 'meta.tier': + case 'meta.none': sorted = filtered.sort((a, b) => a.tier - b.tier); break; @@ -475,7 +475,7 @@ export function TankSearch({ page: 0, }, sort: { - by: 'meta.tier', + by: 'meta.none', direction: 'descending', }, }) diff --git a/src/app/tools/tankopedia/components/TierCard.tsx b/src/app/tools/tankopedia/components/TierCard.tsx deleted file mode 100644 index 92f2e1778..000000000 --- a/src/app/tools/tankopedia/components/TierCard.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { memo, use, useMemo } from 'react'; -import { TANK_CLASSES } from '../../../../components/Tanks/components/Item/constants'; -import { gameDefinitions } from '../../../../core/blitzkit/gameDefinitions'; -import { - Tier, - tanksDefinitionsArray, -} from '../../../../core/blitzkit/tankDefinitions'; -import { tankopediaFilterTank } from '../../../../core/blitzkit/tankopediaFilterTank'; -import { useTankopediaFilters } from '../../../../stores/tankopediaFilters'; -import { NoResults } from './NoResults'; -import { TankCard } from './TankCard'; -import { TankCardWrapper } from './TankCardWrapper'; -import { treeTypeOrder } from './TankSearch/constants'; - -interface TierCardProps { - tier: Tier; -} - -export const TierCard = memo( - ({ tier }: TierCardProps) => { - const awaitedGameDefinitions = use(gameDefinitions); - const awaitedTanksDefinitionsArray = use(tanksDefinitionsArray); - const filters = useTankopediaFilters(); - const tanks = useMemo( - () => - awaitedTanksDefinitionsArray - .filter((tank) => tankopediaFilterTank(filters, tank)) - .sort( - (a, b) => - treeTypeOrder.indexOf(a.treeType) - - treeTypeOrder.indexOf(b.treeType), - ) - .sort( - (a, b) => - TANK_CLASSES.indexOf(a.class) - TANK_CLASSES.indexOf(b.class), - ) - .sort( - (a, b) => - awaitedGameDefinitions.nations.indexOf(a.nation) - - awaitedGameDefinitions.nations.indexOf(b.nation), - ), - [tier, filters], - ); - - if (tanks.length === 0) return ; - - return ( - - {tanks.map((tank) => ( - - ))} - - ); - }, - (prev, next) => prev.tier === next.tier, -); diff --git a/src/app/tools/tankopedia/components/TierFilter.tsx b/src/app/tools/tankopedia/components/TierFilter.tsx index 3123a209c..5398883b7 100644 --- a/src/app/tools/tankopedia/components/TierFilter.tsx +++ b/src/app/tools/tankopedia/components/TierFilter.tsx @@ -13,7 +13,7 @@ export const TierFilter = memo(() => { const sort = useTankopediaFilters((state) => state.sort); const search = useTankopediaFilters((state) => state.search); - if (sort.by !== 'meta.tier' || search) return null; + if (sort.by !== 'meta.none' || search) return null; return ( diff --git a/src/stores/tankopedia.ts b/src/stores/tankopedia.ts index fb70c111b..8c86eeffa 100644 --- a/src/stores/tankopedia.ts +++ b/src/stores/tankopedia.ts @@ -106,7 +106,7 @@ export type ArmorPiercingLayer = }; export const SORT_NAMES = { - 'meta.tier': 'tier', + 'meta.none': 'none', 'survivability.health': 'health', 'survivability.viewRange': 'view range', 'survivability.camouflageStill': 'camouflage still', @@ -150,7 +150,7 @@ export const useTankopediaPersistent = create()( }, }, sort: { - by: 'meta.tier', + by: 'meta.none', direction: 'descending', }, filters: { diff --git a/src/stores/tankopediaFilters.ts b/src/stores/tankopediaFilters.ts index ffcaeb958..4d05ace64 100644 --- a/src/stores/tankopediaFilters.ts +++ b/src/stores/tankopediaFilters.ts @@ -5,7 +5,7 @@ import { Tier } from '../core/blitzkit/tankDefinitions'; import { TankopediaSortBy, TankopediaSortDirection } from './tankopedia'; export interface TankopediaFilters { - tier: Tier; + tier?: Tier; nation?: string; class?: TankClass; type?: TreeType; @@ -21,7 +21,7 @@ export interface TankopediaFilters { export const useTankopediaFilters = create()(() => ({ testing: 'include', sort: { - by: 'meta.tier', + by: 'meta.none', direction: 'descending', }, tier: 10,