'use client'; import { CaretLeftIcon, CaretRightIcon, CaretSortIcon, PlusIcon, TrashIcon, } from '@radix-ui/react-icons'; import { Button, Dialog, Flex, Heading, IconButton, Popover, SegmentedControl, Table, Text, } from '@radix-ui/themes'; import { times } from 'lodash'; import { usePathname, useRouter, useSearchParams } from 'next/navigation'; import { use, useCallback, useEffect, useMemo, useState } from 'react'; import { EquipmentManager } from '../../../components/EquipmentManager'; import PageWrapper from '../../../components/PageWrapper'; import { ProvisionsManager } from '../../../components/ProvisionsManager'; import { asset } from '../../../core/blitzkrieg/asset'; import { availableProvisions } from '../../../core/blitzkrieg/availableProvisions'; import { equipmentDefinitions } from '../../../core/blitzkrieg/equipmentDefinitions'; import { modelDefinitions } from '../../../core/blitzkrieg/modelDefinitions'; import { provisionDefinitions } from '../../../core/blitzkrieg/provisionDefinitions'; import { skillDefinitions } from '../../../core/blitzkrieg/skillDefinitions'; import { tankCharacteristics } from '../../../core/blitzkrieg/tankCharacteristics'; import { tankDefinitions } from '../../../core/blitzkrieg/tankDefinitions'; import { tankIcon } from '../../../core/blitzkrieg/tankIcon'; import { tankToCompareMember } from '../../../core/blitzkrieg/tankToCompareMember'; import { theme } from '../../../stitches.config'; import { DeltaMode, mutateComparePersistent, mutateCompareTemporary, useComparePersistent, useCompareTemporary, } from '../../../stores/compare'; import { EquipmentMatrix } from '../../../stores/duel'; import { TankSearch } from '../tankopedia/components/TankSearch'; import { TankControl } from './components/TankControl'; export default function Page() { const pathname = usePathname(); const router = useRouter(); const awaitedTankDefinitions = use(tankDefinitions); const awaitedSkillDefinitions = use(skillDefinitions); const searchParams = useSearchParams(); const members = useCompareTemporary((state) => state.members); const [addTankDialogOpen, setAddTankDialogOpen] = useState(false); const sorting = useCompareTemporary((state) => state.sorting); const awaitedModelDefinitions = use(modelDefinitions); const awaitedEquipmentDefinitions = use(equipmentDefinitions); const awaitedProvisionDefinitions = use(provisionDefinitions); const deltaMode = useComparePersistent((state) => state.deltaMode); const stats = useMemo( () => members.map((item) => tankCharacteristics( { ...item, stockEngine: item.tank.engines[0], stockGun: item.tank.turrets[0].guns[0], stockTrack: item.tank.tracks[0], stockTurret: item.tank.turrets[0], }, { equipmentDefinitions: awaitedEquipmentDefinitions, modelDefinitions: awaitedModelDefinitions, provisionDefinitions: awaitedProvisionDefinitions, }, ), ), [members], ); const createQueryString = useCallback( (name: string, value: string) => { const params = new URLSearchParams(searchParams.toString()); params.set(name, value); return params.toString(); }, [searchParams], ); const test = Object.keys(awaitedProvisionDefinitions).slice(0, 1); const hasNonRegularGun = members.some(({ gun }) => gun.type !== 'regular'); useEffect(() => { router.push( pathname + '?' + createQueryString( 'tanks', members.map(({ tank }) => tank.id).join(','), ), ); }, [members]); useEffect(() => { const tanksParam = searchParams.get('tanks'); if (tanksParam) { mutateCompareTemporary((draft) => { draft.members = tanksParam .split(',') .map(Number) .map((id) => tankToCompareMember( awaitedTankDefinitions[id], awaitedProvisionDefinitions, awaitedSkillDefinitions, ), ); }); } }, []); function Row({ value, name, display, deltaType = 'higherIsBetter', decimals, deltaNominalDisplay, }: { name: string; value: | keyof Awaited> | (( member: Awaited>, ) => number | undefined); display?: ( member: Awaited>, ) => number | string | undefined; deltaType?: 'higherIsBetter' | 'lowerIsBetter'; decimals?: number; deltaNominalDisplay?: (delta: number) => string | number; }) { const values = stats.map((stat) => typeof value === 'function' ? value(stat)! : (stat[value] as number), ); return ( { mutateCompareTemporary((draft) => { draft.members.sort((memberA, memberB) => { const indexA = draft.members.indexOf(memberA); const indexB = draft.members.indexOf(memberB); const valueA = values[indexA]; const valueB = values[indexB]; return draft.sorting?.direction === 'ascending' && draft.sorting.by === name ? valueA - valueB : valueB - valueA; }); draft.sorting = { by: name, direction: draft.sorting?.direction === 'ascending' && draft.sorting.by === name ? 'descending' : 'ascending', }; }); }} > {(sorting === undefined || sorting.by !== name) && ( )} {sorting?.by === name && ( <> {sorting.direction === 'ascending' && } {sorting.direction === 'descending' && } )} {name} {values.map((value, index) => { const delta = value - values[0]; const deltaPercentage = value / values[0] - 1; const normalizedDeltaPercentage = Math.round( Math.min(100, Math.abs(deltaPercentage) * 2 * 100 + 25), ); return ( values[0] : value < values[0] ) ? `color-mix(in srgb, ${theme.colors.componentCallToActionInteractive_green} ${normalizedDeltaPercentage}%, ${theme.colors.componentCallToActionInteractive_green}00)` : `color-mix(in srgb, ${theme.colors.componentCallToActionInteractive_red} ${normalizedDeltaPercentage}%, ${theme.colors.componentCallToActionInteractive_red}00)`, }} > {display ? display(stats[index]) : decimals === undefined ? value : value?.toFixed(decimals)} {delta !== 0 && ( <> {deltaMode === 'absolute' && ( ( {`${delta > 0 ? '+' : ''}${ deltaNominalDisplay ? deltaNominalDisplay(delta) : decimals === undefined ? delta : delta.toFixed(decimals) }`} ) )} {deltaMode === 'percentage' && ( ( {`${deltaPercentage > 0 ? '+' : ''}${Math.round( deltaPercentage * 100, )}%`} ) )} )} ); })} ); } function Title({ children }: { children: string }) { return ( {children} {members.map(({ key }) => ( ))} ); } return ( { mutateCompareTemporary((draft) => { draft.members.push( tankToCompareMember( tank, awaitedProvisionDefinitions, awaitedSkillDefinitions, ), ); draft.sorting = undefined; }); setAddTankDialogOpen(false); }} onSelectAll={(tanks) => { mutateCompareTemporary((draft) => { draft.members.push( ...tanks.map((tank) => tankToCompareMember( tank, awaitedProvisionDefinitions, awaitedSkillDefinitions, ), ), ); draft.sorting = undefined; }); setAddTankDialogOpen(false); }} /> { mutateComparePersistent((draft) => { draft.deltaMode = value as DeltaMode; }); }} > No deltas Percentage Absolute {members.length > 0 && ( {members.map(({ tank, key }, index) => { return ( {tank.name} ); })} {members.map( ({ equipmentMatrix, tank, key, provisions, gun }, index) => { const equipmentPreset = awaitedEquipmentDefinitions.presets[tank.equipment]; const provisionsList = availableProvisions( tank, gun, awaitedProvisionDefinitions, ); return ( { mutateCompareTemporary((draft) => { draft.members[index].equipmentMatrix = matrix; draft.sorting = undefined; }); }} /> provision.id, )} selected={provisions} disabled={ tank.provisions === provisionsList.length } onChange={(provisions) => { mutateCompareTemporary((draft) => { draft.members[index].provisions = provisions; }); }} /> ); }, )} Fire stats.shellReload ?? stats.shellReloads!.reduce((a, b) => a + b, 0) / stats.shellReloads!.length } display={(stats) => stats.shellReload?.toFixed(2) ?? stats .shellReloads!.map((reload) => reload.toFixed(2)) .join(', ') } deltaNominalDisplay={(delta) => delta.toFixed(2)} /> {hasNonRegularGun && ( stat.intraClip} decimals={2} /> )} Maneuverability Survivability (stats.fireChance * 100).toFixed(0)} deltaNominalDisplay={(delta) => (delta * 100).toFixed(0)} /> (stats.camouflageStill * 100).toFixed(0)} deltaNominalDisplay={(delta) => (delta * 100).toFixed(0)} /> (stats.camouflageMoving * 100).toFixed(0)} deltaNominalDisplay={(delta) => (delta * 100).toFixed(0)} /> (stats.camouflageShootingStill * 100).toFixed(0) } deltaNominalDisplay={(delta) => (delta * 100).toFixed(0)} /> (stats.camouflageShootingMoving * 100).toFixed(0) } deltaNominalDisplay={(delta) => (delta * 100).toFixed(0)} /> (stats.camouflageCaughtOnFire * 100).toFixed(0) } deltaNominalDisplay={(delta) => (delta * 100).toFixed(0)} /> )} {members.length === 0 && ( No tanks selected Press the Add button to get started )} ); }