'use client'; import { PlusIcon } from '@radix-ui/react-icons'; import { Button, Dialog, Flex, Heading, 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 { EquipmentButton } from '../../../components/ModuleButtons/EquipmentButton'; import PageWrapper from '../../../components/PageWrapper'; 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 { 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 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 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], 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 ( {name} {values.map((value, index) => { const delta = (value ?? 0) - (values[0] ?? 0); const deltaPercentage = (value ?? 1) / (values[0] ?? 1) - 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 === 'nominal' && ( ( {`${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((_, index) => ( ))} ); } return ( Delta:{' '} { mutateComparePersistent((draft) => { draft.deltaMode = value as DeltaMode; }); }} > None Percentage Nominal { mutateCompareTemporary((draft) => { draft.members.push( tankToCompareMember(tank, awaitedSkillDefinitions), ); }); setAddTankDialogOpen(false); }} /> {members.length > 0 && ( {members.map(({ tank }, index) => { return ( {tank.name} ); })} {members.map(({ equipmentMatrix, tank }) => { const equipmentRows = awaitedEquipmentDefinitions.presets[tank.equipment]; return ( {equipmentRows.map((equipmentRow, rowIndex) => ( {equipmentRow.map( (equipment, columnIndex) => ( { // mutateDuel((draft) => { // if ( // equipmentMatrix[rowIndex][ // columnIndex // ] === -1 // ) { // draft.protagonist!.equipmentMatrix[ // rowIndex // ][columnIndex] = 0; // } else { // draft.protagonist!.equipmentMatrix[ // rowIndex // ][columnIndex] = -1; // } // }); }} /> { // mutateDuel((draft) => { // if ( // equipmentMatrix[rowIndex][ // columnIndex // ] === 1 // ) { // draft.protagonist!.equipmentMatrix[ // rowIndex // ][columnIndex] = 0; // } else { // draft.protagonist!.equipmentMatrix[ // rowIndex // ][columnIndex] = 1; // } // }); }} /> ), )} ))} ); })} 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)} /> )} ); }