diff --git a/src/app/tools/compare/page.tsx b/src/app/tools/compare/page.tsx index 4a4f454df..f42edc05f 100644 --- a/src/app/tools/compare/page.tsx +++ b/src/app/tools/compare/page.tsx @@ -2,31 +2,63 @@ import { PlusIcon } from '@radix-ui/react-icons'; import { Button, Dialog, Flex, Table, Text } from '@radix-ui/themes'; -import { usePathname, useRouter, useSearchParams } from 'next/navigation'; -import { use, useCallback, useState } from 'react'; +import { useSearchParams } from 'next/navigation'; +import { use, useState } from 'react'; import PageWrapper from '../../../components/PageWrapper'; -import { tankDefinitions } from '../../../core/blitzkrieg/tankDefinitions'; +import { tankCharacteristics } from '../../../core/blitzkrieg/tankCharacteristics'; import { tankIcon } from '../../../core/blitzkrieg/tankIcon'; +import mutateCompare, { useCompare } from '../../../stores/compare'; +import { genericDefaultEquipmentMatrix } from '../../../stores/duel/constants'; import { TankSearch } from '../tankopedia/components/TankSearch'; export default function Page() { - const router = useRouter(); - const pathname = usePathname(); const searchParams = useSearchParams(); - const tanks = searchParams.get('tanks')?.split(',').map(Number) ?? []; - const awaitedTankDefinitions = use(tankDefinitions); + const tanks = useCompare((state) => state.tanks); const [addTankDialogOpen, setAddTankDialogOpen] = useState(false); - - const createQueryString = useCallback( - (name: string, value: string) => { - const params = new URLSearchParams(searchParams.toString()); - params.set(name, value); - - return params.toString(); - }, - [searchParams], + const stats = use( + Promise.all( + tanks.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], + }), + ), + ), ); + function Row({ + value, + decimals, + name, + }: { + name: string; + value: (member: Awaited>) => number; + decimals?: number; + }) { + const values = stats.map(value); + + return ( + + {name} + + {values.map((value, index) => ( + + values[0] ? 'green' : 'red' + } + > + {decimals === undefined ? value : value.toFixed(decimals)} + + + ))} + + ); + } + return ( @@ -51,12 +83,26 @@ export default function Page() { { - tanks.push(tank.id); - router.push( - pathname + - '?' + - createQueryString('tanks', tanks.join(',')), - ); + mutateCompare((draft) => { + draft.tanks.push({ + camouflage: true, + consumables: [], + equipmentMatrix: genericDefaultEquipmentMatrix, + cooldownBooster: 0, + crewMastery: 1, + provisions: [], + engine: tank.engines.at(-1)!, + gun: tank.turrets.at(-1)!.guns.at(-1)!, + shell: tank.turrets.at(-1)!.guns.at(-1)!.shells[0], + tank, + track: tank.tracks.at(-1)!, + yaw: 0, + pitch: 0, + turret: tank.turrets.at(-1)!, + crewSkills: {}, + }); + }); + setAddTankDialogOpen(false); }} /> @@ -76,30 +122,21 @@ export default function Page() { > - - - Statistics - - - {tanks.map((id) => { - const tank = awaitedTankDefinitions[id]; + + {tanks.map(({ tank }) => { return ( - + - {tank.name} + {tank.name} ); @@ -108,23 +145,7 @@ export default function Page() { - - Stat 1 - {tanks.map((id) => { - const tank = awaitedTankDefinitions[id]; - - return {Math.random()}; - })} - - - - Stat 2 - {tanks.map((id) => { - const tank = awaitedTankDefinitions[id]; - - return {Math.random()}; - })} - + stats.dpm} decimals={0} /> diff --git a/src/app/tools/tankopedia/[id]/components/Characteristics/components/Equipment.tsx b/src/app/tools/tankopedia/[id]/components/Characteristics/components/Equipment.tsx index bb01136df..36c8433e4 100644 --- a/src/app/tools/tankopedia/[id]/components/Characteristics/components/Equipment.tsx +++ b/src/app/tools/tankopedia/[id]/components/Characteristics/components/Equipment.tsx @@ -10,7 +10,9 @@ export function Equipment() { const awaitedEquipmentDefinitions = use(equipmentDefinitions); const equipmentRows = awaitedEquipmentDefinitions.presets[protagonist.tank.equipment]; - const equipmentMatrix = useDuel((state) => state.protagonist!.equipment); + const equipmentMatrix = useDuel( + (state) => state.protagonist!.equipmentMatrix, + ); return ( @@ -21,7 +23,7 @@ export function Equipment() { color="red" onClick={() => { mutateDuel((draft) => { - draft.protagonist!.equipment.forEach((row) => { + draft.protagonist!.equipmentMatrix.forEach((row) => { row.forEach((_, index) => { row[index] = 0; }); @@ -46,10 +48,13 @@ export function Equipment() { onClick={() => { mutateDuel((draft) => { if (equipmentMatrix[rowIndex][columnIndex] === -1) { - draft.protagonist!.equipment[rowIndex][columnIndex] = 0; + draft.protagonist!.equipmentMatrix[rowIndex][ + columnIndex + ] = 0; } else { - draft.protagonist!.equipment[rowIndex][columnIndex] = - -1; + draft.protagonist!.equipmentMatrix[rowIndex][ + columnIndex + ] = -1; } }); }} @@ -62,9 +67,13 @@ export function Equipment() { onClick={() => { mutateDuel((draft) => { if (equipmentMatrix[rowIndex][columnIndex] === 1) { - draft.protagonist!.equipment[rowIndex][columnIndex] = 0; + draft.protagonist!.equipmentMatrix[rowIndex][ + columnIndex + ] = 0; } else { - draft.protagonist!.equipment[rowIndex][columnIndex] = 1; + draft.protagonist!.equipmentMatrix[rowIndex][ + columnIndex + ] = 1; } }); }} diff --git a/src/app/tools/tankopedia/[id]/components/Characteristics/index.tsx b/src/app/tools/tankopedia/[id]/components/Characteristics/index.tsx index 83ee39d11..0c9cd3258 100644 --- a/src/app/tools/tankopedia/[id]/components/Characteristics/index.tsx +++ b/src/app/tools/tankopedia/[id]/components/Characteristics/index.tsx @@ -17,6 +17,7 @@ import { isExplosive } from '../../../../../../core/blitz/isExplosive'; import { coefficient } from '../../../../../../core/blitzkrieg/coefficient'; import { modelDefinitions } from '../../../../../../core/blitzkrieg/modelDefinitions'; import { provisionDefinitions } from '../../../../../../core/blitzkrieg/provisionDefinitions'; +import { tankCharacteristics } from '../../../../../../core/blitzkrieg/tankCharacteristics'; import { CREW_MEMBER_NAMES, GUN_TYPE_NAMES, @@ -24,7 +25,6 @@ import { } from '../../../../../../core/blitzkrieg/tankDefinitions/constants'; import { useEquipment } from '../../../../../../hooks/useEquipment'; import { useFullScreen } from '../../../../../../hooks/useFullScreen'; -import { useTankCharacteristics } from '../../../../../../hooks/useTankCharacteristics'; import { mutateDuel, useDuel } from '../../../../../../stores/duel'; import { useTankopediaTemporary } from '../../../../../../stores/tankopedia'; import { Info } from './components/Info'; @@ -53,7 +53,9 @@ export function Characteristics() { const crewMemberMastery = commanderMastery * 1.1; const consumables = useDuel((state) => state.protagonist!.consumables); const camouflage = useDuel((state) => state.protagonist!.camouflage); - const equipmentMatrix = useDuel((state) => state.protagonist!.equipment); + const equipmentMatrix = useDuel( + (state) => state.protagonist!.equipmentMatrix, + ); const { tank, turret, gun, engine, track, shell } = useDuel( (state) => state.protagonist!, ); @@ -64,24 +66,26 @@ export function Characteristics() { const tankModelDefinition = awaitedModelDefinitions[tank.id]; const turretModelDefinition = tankModelDefinition.turrets[turret.id]; const gunModelDefinition = turretModelDefinition.guns[gun.id]; - const stats = useTankCharacteristics({ - tank, - camouflage, - consumables, - crewMastery, - crewSkills, - engine, - equipmentMatrix, - gun, - provisions, - shell, - turret, - track, - stockEngine, - stockGun, - stockTrack, - stockTurret, - }); + const stats = use( + tankCharacteristics({ + tank, + camouflage, + consumables, + crewMastery, + crewSkills, + engine, + equipmentMatrix, + gun, + provisions, + shell, + turret, + track, + stockEngine, + stockGun, + stockTrack, + stockTurret, + }), + ); const hasSupercharger = useEquipment(107); const hasCalibratedShells = useEquipment(103); diff --git a/src/app/tools/tankopedia/[id]/components/Control.tsx b/src/app/tools/tankopedia/[id]/components/Control.tsx index 919be6fc9..b9383188a 100644 --- a/src/app/tools/tankopedia/[id]/components/Control.tsx +++ b/src/app/tools/tankopedia/[id]/components/Control.tsx @@ -73,7 +73,7 @@ export function Controls() { const hasImprovedVerticalStabilizer = await hasEquipment( 122, duel.protagonist!.tank.equipment, - duel.protagonist!.equipment, + duel.protagonist!.equipmentMatrix, ); switch (event) { diff --git a/src/app/tools/tankopedia/[id]/components/Model/components/TankModel.tsx b/src/app/tools/tankopedia/[id]/components/Model/components/TankModel.tsx index 24e2e193e..32c0fde34 100644 --- a/src/app/tools/tankopedia/[id]/components/Model/components/TankModel.tsx +++ b/src/app/tools/tankopedia/[id]/components/Model/components/TankModel.tsx @@ -166,7 +166,7 @@ export const TankModel = memo(() => { const hasImprovedVerticalStabilizer = await hasEquipment( 122, duel.protagonist!.tank.equipment, - duel.protagonist!.equipment, + duel.protagonist!.equipmentMatrix, ); [pitch, yaw] = applyPitchYawLimits( pitch, @@ -232,7 +232,7 @@ export const TankModel = memo(() => { const hasImprovedVerticalStabilizer = await hasEquipment( 122, duel.protagonist!.tank.equipment, - duel.protagonist!.equipment, + duel.protagonist!.equipmentMatrix, ); [pitch, yaw] = applyPitchYawLimits( pitch - event.movementY * (Math.PI / canvas.height), diff --git a/src/app/tools/tankopedia/[id]/components/QuickInputs/index.tsx b/src/app/tools/tankopedia/[id]/components/QuickInputs/index.tsx index 0ce8e87f4..a61f5c196 100644 --- a/src/app/tools/tankopedia/[id]/components/QuickInputs/index.tsx +++ b/src/app/tools/tankopedia/[id]/components/QuickInputs/index.tsx @@ -159,7 +159,9 @@ export function RotationInputs({ isFullScreen }: RotationInputsProps) { active={hasCalibratedShells} onClick={() => { mutateDuel((draft) => { - draft.antagonist!.equipment[0][0] = hasCalibratedShells ? 0 : 1; + draft.antagonist!.equipmentMatrix[0][0] = hasCalibratedShells + ? 0 + : 1; }); }} /> @@ -168,7 +170,9 @@ export function RotationInputs({ isFullScreen }: RotationInputsProps) { active={hasEnhancedArmor} onClick={() => { mutateDuel((draft) => { - draft.protagonist!.equipment[1][1] = hasEnhancedArmor ? 0 : -1; + draft.protagonist!.equipmentMatrix[1][1] = hasEnhancedArmor + ? 0 + : -1; }); }} /> diff --git a/src/components/Armor/components/CoreArmorSceneComponent/index.tsx b/src/components/Armor/components/CoreArmorSceneComponent/index.tsx index 28bafad51..079f04dca 100644 --- a/src/components/Armor/components/CoreArmorSceneComponent/index.tsx +++ b/src/components/Armor/components/CoreArmorSceneComponent/index.tsx @@ -70,8 +70,8 @@ export function CoreArmorSceneComponent({ material.uniforms.explosionRadius.value = shell.explosionRadius; const duel = useDuel.getState(); - await handleProtagonistEquipmentChange(duel.protagonist!.equipment); - await handleAntagonistEquipmentChange(duel.antagonist!.equipment); + await handleProtagonistEquipmentChange(duel.protagonist!.equipmentMatrix); + await handleAntagonistEquipmentChange(duel.antagonist!.equipmentMatrix); } function handleVisualChange( visual: TankopediaPersistent['model']['visual'], @@ -112,8 +112,12 @@ export function CoreArmorSceneComponent({ handleShellChange(useDuel.getState().antagonist!.shell); handleVisualChange(useTankopediaPersistent.getState().model.visual); - handleProtagonistEquipmentChange(useDuel.getState().protagonist!.equipment); - handleAntagonistEquipmentChange(useDuel.getState().antagonist!.equipment); + handleProtagonistEquipmentChange( + useDuel.getState().protagonist!.equipmentMatrix, + ); + handleAntagonistEquipmentChange( + useDuel.getState().antagonist!.equipmentMatrix, + ); const unsubscribes = [ useDuel.subscribe((state) => state.antagonist!.shell, handleShellChange), @@ -122,11 +126,11 @@ export function CoreArmorSceneComponent({ handleVisualChange, ), useDuel.subscribe( - (state) => state.protagonist!.equipment, + (state) => state.protagonist!.equipmentMatrix, handleProtagonistEquipmentChange, ), useDuel.subscribe( - (state) => state.antagonist!.equipment, + (state) => state.antagonist!.equipmentMatrix, handleAntagonistEquipmentChange, ), ]; diff --git a/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubExternal/index.tsx b/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubExternal/index.tsx index 81d4c510b..2db0bb49b 100644 --- a/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubExternal/index.tsx +++ b/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubExternal/index.tsx @@ -76,17 +76,21 @@ export function SpacedArmorSubExternal({ } handleShellChange(useDuel.getState().antagonist!.shell); - handleProtagonistEquipmentChange(useDuel.getState().protagonist!.equipment); - handleAntagonistEquipmentChange(useDuel.getState().antagonist!.equipment); + handleProtagonistEquipmentChange( + useDuel.getState().protagonist!.equipmentMatrix, + ); + handleAntagonistEquipmentChange( + useDuel.getState().antagonist!.equipmentMatrix, + ); const unsubscribes = [ useDuel.subscribe((state) => state.antagonist!.shell, handleShellChange), useDuel.subscribe( - (state) => state.protagonist!.equipment, + (state) => state.protagonist!.equipmentMatrix, handleProtagonistEquipmentChange, ), useDuel.subscribe( - (state) => state.antagonist!.equipment, + (state) => state.antagonist!.equipmentMatrix, handleAntagonistEquipmentChange, ), ]; diff --git a/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubSpaced/index.tsx b/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubSpaced/index.tsx index 0563515e8..73f63049d 100644 --- a/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubSpaced/index.tsx +++ b/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubSpaced/index.tsx @@ -87,17 +87,21 @@ export function SpacedArmorSubSpaced({ } handleShellChange(useDuel.getState().antagonist!.shell); - handleProtagonistEquipmentChange(useDuel.getState().protagonist!.equipment); - handleAntagonistEquipmentChange(useDuel.getState().antagonist!.equipment); + handleProtagonistEquipmentChange( + useDuel.getState().protagonist!.equipmentMatrix, + ); + handleAntagonistEquipmentChange( + useDuel.getState().antagonist!.equipmentMatrix, + ); const unsubscribes = [ useDuel.subscribe((state) => state.antagonist!.shell, handleShellChange), useDuel.subscribe( - (state) => state.protagonist!.equipment, + (state) => state.protagonist!.equipmentMatrix, handleProtagonistEquipmentChange, ), useDuel.subscribe( - (state) => state.antagonist!.equipment, + (state) => state.antagonist!.equipmentMatrix, handleAntagonistEquipmentChange, ), ]; diff --git a/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx b/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx index 385e14d93..28c0c82a2 100644 --- a/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx +++ b/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx @@ -93,12 +93,12 @@ export function SpacedArmorSceneComponent({ const hasCalibratedShells = await hasEquipment( 103, duel.antagonist!.tank.equipment, - duel.antagonist!.equipment, + duel.antagonist!.equipmentMatrix, ); const hasEnhancedArmor = await hasEquipment( 110, duel.protagonist!.tank.equipment, - duel.protagonist!.equipment, + duel.protagonist!.equipmentMatrix, ); const thicknessCoefficient = hasEnhancedArmor ? 1.04 : 1; const shell = useDuel.getState().antagonist!.shell; diff --git a/src/core/blitzkrieg/assignDuelMember.ts b/src/core/blitzkrieg/assignDuelMember.ts index e7329823b..1b201a2b1 100644 --- a/src/core/blitzkrieg/assignDuelMember.ts +++ b/src/core/blitzkrieg/assignDuelMember.ts @@ -32,7 +32,7 @@ export async function assignDuelMember( gun, shell, track, - equipment: genericDefaultEquipmentMatrix, + equipmentMatrix: genericDefaultEquipmentMatrix, pitch: degToRad(tankModelDefinition.turretRotation?.pitch ?? 0), yaw: 0, camouflage: true, diff --git a/src/hooks/useTankCharacteristics.ts b/src/core/blitzkrieg/tankCharacteristics.ts similarity index 92% rename from src/hooks/useTankCharacteristics.ts rename to src/core/blitzkrieg/tankCharacteristics.ts index c9c750681..75be0517b 100644 --- a/src/hooks/useTankCharacteristics.ts +++ b/src/core/blitzkrieg/tankCharacteristics.ts @@ -1,15 +1,15 @@ -import { use } from 'react'; -import { isExplosive } from '../core/blitz/isExplosive'; -import { resolveNearPenetration } from '../core/blitz/resolveNearPenetration'; -import { coefficient } from '../core/blitzkrieg/coefficient'; -import { degressiveStat } from '../core/blitzkrieg/degressiveStat'; -import { equipmentDefinitions } from '../core/blitzkrieg/equipmentDefinitions'; -import { modelDefinitions } from '../core/blitzkrieg/modelDefinitions'; -import { normalizeBoundingBox } from '../core/blitzkrieg/normalizeBoundingBox'; -import { progressiveStat } from '../core/blitzkrieg/progressiveStat'; -import { provisionDefinitions } from '../core/blitzkrieg/provisionDefinitions'; -import { resolveDpm } from '../core/blitzkrieg/resolveDpm'; -import { sum } from '../core/blitzkrieg/sum'; +import { EquipmentMatrix } from '../../stores/duel'; +import { isExplosive } from '../blitz/isExplosive'; +import { resolveNearPenetration } from '../blitz/resolveNearPenetration'; +import { coefficient } from './coefficient'; +import { degressiveStat } from './degressiveStat'; +import { equipmentDefinitions } from './equipmentDefinitions'; +import { modelDefinitions } from './modelDefinitions'; +import { normalizeBoundingBox } from './normalizeBoundingBox'; +import { progressiveStat } from './progressiveStat'; +import { provisionDefinitions } from './provisionDefinitions'; +import { resolveDpm } from './resolveDpm'; +import { sum } from './sum'; import { EngineDefinition, GunDefinition, @@ -17,11 +17,10 @@ import { TankDefinition, TrackDefinition, TurretDefinition, -} from '../core/blitzkrieg/tankDefinitions'; -import { unionBoundingBox } from '../core/blitzkrieg/unionBoundingBox'; -import { EquipmentMatrix } from '../stores/duel'; +} from './tankDefinitions'; +import { unionBoundingBox } from './unionBoundingBox'; -export function useTankCharacteristics({ +export async function tankCharacteristics({ tank, turret, gun, @@ -56,9 +55,9 @@ export function useTankCharacteristics({ crewSkills: Record; camouflage: boolean; }) { - const awaitedModelDefinitions = use(modelDefinitions); - const awaitedEquipmentDefinitions = use(equipmentDefinitions); - const awaitedProvisionDefinitions = use(provisionDefinitions); + const awaitedModelDefinitions = await modelDefinitions; + const awaitedEquipmentDefinitions = await equipmentDefinitions; + const awaitedProvisionDefinitions = await provisionDefinitions; const presetRows = awaitedEquipmentDefinitions.presets[tank.equipment]; const tankModelDefinition = awaitedModelDefinitions[tank.id]; const turretModelDefinition = tankModelDefinition.turrets[turret.id]; diff --git a/src/hooks/useEquipment.ts b/src/hooks/useEquipment.ts index 6c568c18b..cc2bc3ec2 100644 --- a/src/hooks/useEquipment.ts +++ b/src/hooks/useEquipment.ts @@ -10,7 +10,8 @@ export function useEquipment(id: number, antagonist = false) { const equipmentRows = awaitedEquipmentDefinitions.presets[member.tank.equipment]; const equipmentMatrix = useDuel( - (state) => state[antagonist ? 'antagonist' : 'protagonist']!.equipment, + (state) => + state[antagonist ? 'antagonist' : 'protagonist']!.equipmentMatrix, ); const value = useMemo( () => diff --git a/src/stores/compare.ts b/src/stores/compare.ts new file mode 100644 index 000000000..0abba2f2f --- /dev/null +++ b/src/stores/compare.ts @@ -0,0 +1,19 @@ +import { produce } from 'immer'; +import { create } from 'zustand'; +import { DuelMember } from './duel'; + +export interface CompareMember extends DuelMember { + crewSkills: Record; +} + +export interface Compare { + tanks: CompareMember[]; +} + +export const useCompare = create()(() => ({ + tanks: [], +})); + +export default function mutateCompare(recipe: (draft: Compare) => void) { + useCompare.setState(produce(recipe)); +} diff --git a/src/stores/duel/index.ts b/src/stores/duel/index.ts index b1897ffe4..936f36382 100644 --- a/src/stores/duel/index.ts +++ b/src/stores/duel/index.ts @@ -29,7 +29,7 @@ export interface DuelMember { gun: GunDefinition; shell: ShellDefinition; track: TrackDefinition; - equipment: EquipmentMatrix; + equipmentMatrix: EquipmentMatrix; pitch: number; yaw: number; consumables: number[];