diff --git a/src/app/tools/tankopedia/[id]/components/Model/components/Options/components/DynamicArmorSwitcher.tsx b/src/app/tools/tankopedia/[id]/components/Model/components/Options/components/DynamicArmorSwitcher.tsx new file mode 100644 index 000000000..a78c31a88 --- /dev/null +++ b/src/app/tools/tankopedia/[id]/components/Model/components/Options/components/DynamicArmorSwitcher.tsx @@ -0,0 +1,53 @@ +import { IconButton } from '@radix-ui/themes'; +import { invalidate } from '@react-three/fiber'; +import { asset } from '../../../../../../../../../core/blitzkit/asset'; +import { useArmor } from '../../../../../../../../../hooks/useArmor'; +import * as Duel from '../../../../../../../../../stores/duel'; +import * as TankopediaEphemeral from '../../../../../../../../../stores/tankopediaEphemeral'; + +export function DynamicArmorSwitcher() { + const tank = Duel.use((state) => state.protagonist.tank); + const mutateDuel = Duel.useMutation(); + const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation(); + const isDynamicArmorActive = Duel.use((state) => + state.protagonist.consumables.includes(73), + ); + const { hasDynamicArmor } = useArmor(tank.id); + + if (!hasDynamicArmor) return null; + + return ( + { + invalidate(); + mutateDuel((draft) => { + if (draft.protagonist.consumables.includes(73)) { + draft.protagonist.consumables = + draft.protagonist.consumables.filter((id) => id !== 73); + } else { + if (draft.protagonist.consumables.length === tank.consumables) { + draft.protagonist.consumables[tank.consumables - 1] = 73; + } else { + draft.protagonist.consumables.push(73); + } + } + }); + mutateTankopediaEphemeral((draft) => { + draft.shot = undefined; + }); + }} + > + Dynamic Armor + + ); +} diff --git a/src/app/tools/tankopedia/[id]/components/Model/components/Options.tsx b/src/app/tools/tankopedia/[id]/components/Model/components/Options/index.tsx similarity index 82% rename from src/app/tools/tankopedia/[id]/components/Model/components/Options.tsx rename to src/app/tools/tankopedia/[id]/components/Model/components/Options/index.tsx index 99c7f07fa..aeb7470ae 100644 --- a/src/app/tools/tankopedia/[id]/components/Model/components/Options.tsx +++ b/src/app/tools/tankopedia/[id]/components/Model/components/Options/index.tsx @@ -20,31 +20,31 @@ import { Tooltip, } from '@radix-ui/themes'; import { invalidate } from '@react-three/fiber'; -import { RefObject, use, useEffect, useState } from 'react'; -import { ThicknessRange } from '../../../../../../../components/Armor/components/StaticArmor'; -import { ModuleButton } from '../../../../../../../components/ModuleButtons/ModuleButton'; -import { SmallTankIcon } from '../../../../../../../components/SmallTankIcon'; -import { resolveNearPenetration } from '../../../../../../../core/blitz/resolveNearPenetration'; -import { resolvePenetrationCoefficient } from '../../../../../../../core/blitz/resolvePenetrationCoefficient'; -import { asset } from '../../../../../../../core/blitzkit/asset'; -import { checkConsumableProvisionInclusivity } from '../../../../../../../core/blitzkit/checkConsumableProvisionInclusivity'; -import { consumableDefinitions } from '../../../../../../../core/blitzkit/consumableDefinitions'; -import { imgur } from '../../../../../../../core/blitzkit/imgur'; -import { Pose, poseEvent } from '../../../../../../../core/blitzkit/pose'; -import { TIER_ROMAN_NUMERALS } from '../../../../../../../core/blitzkit/tankDefinitions/constants'; -import { uniqueGuns } from '../../../../../../../core/blitzkit/uniqueGuns'; -import { useEquipment } from '../../../../../../../hooks/useEquipment'; -import { useFullScreen } from '../../../../../../../hooks/useFullScreen'; -import { useFullscreenAvailability } from '../../../../../../../hooks/useFullscreenAvailability'; -import * as App from '../../../../../../../stores/app'; -import * as Duel from '../../../../../../../stores/duel'; -import * as TankopediaEphemeral from '../../../../../../../stores/tankopediaEphemeral'; -import * as TankopediaPersistent from '../../../../../../../stores/tankopediaPersistent'; -import { TankopediaDisplay } from '../../../../../../../stores/tankopediaPersistent/constants'; -import { TankSearch } from '../../../../components/TankSearch'; -import { ENVIRONMENTS } from '../../Lighting'; -import { QuickInputs } from '../../QuickInputs'; -import { Thicknesses } from '../../Thicknesses'; +import { RefObject, useEffect, useState } from 'react'; +import { ThicknessRange } from '../../../../../../../../components/Armor/components/StaticArmor'; +import { ModuleButton } from '../../../../../../../../components/ModuleButtons/ModuleButton'; +import { SmallTankIcon } from '../../../../../../../../components/SmallTankIcon'; +import { resolveNearPenetration } from '../../../../../../../../core/blitz/resolveNearPenetration'; +import { resolvePenetrationCoefficient } from '../../../../../../../../core/blitz/resolvePenetrationCoefficient'; +import { asset } from '../../../../../../../../core/blitzkit/asset'; +import { imgur } from '../../../../../../../../core/blitzkit/imgur'; +import { Pose, poseEvent } from '../../../../../../../../core/blitzkit/pose'; +import { TIER_ROMAN_NUMERALS } from '../../../../../../../../core/blitzkit/tankDefinitions/constants'; +import { uniqueGuns } from '../../../../../../../../core/blitzkit/uniqueGuns'; +import { useArmor } from '../../../../../../../../hooks/useArmor'; +import { useEquipment } from '../../../../../../../../hooks/useEquipment'; +import { useFullScreen } from '../../../../../../../../hooks/useFullScreen'; +import { useFullscreenAvailability } from '../../../../../../../../hooks/useFullscreenAvailability'; +import * as App from '../../../../../../../../stores/app'; +import * as Duel from '../../../../../../../../stores/duel'; +import * as TankopediaEphemeral from '../../../../../../../../stores/tankopediaEphemeral'; +import * as TankopediaPersistent from '../../../../../../../../stores/tankopediaPersistent'; +import { TankopediaDisplay } from '../../../../../../../../stores/tankopediaPersistent/constants'; +import { TankSearch } from '../../../../../components/TankSearch'; +import { ENVIRONMENTS } from '../../../Lighting'; +import { QuickInputs } from '../../../QuickInputs'; +import { Thicknesses } from '../../../Thicknesses'; +import { DynamicArmorSwitcher } from './components/DynamicArmorSwitcher'; interface OptionsProps { thicknessRange: ThicknessRange; @@ -78,28 +78,14 @@ export function Options({ thicknessRange, canvas }: OptionsProps) { ); const developerMode = App.useDeferred(false, (state) => state.developerMode); const protagonistTank = Duel.use((state) => state.protagonist.tank); - const protagonistGun = Duel.use((state) => state.protagonist.gun); - const protagonistConsumables = Duel.use( - (state) => state.protagonist.consumables, - ); const antagonistGun = Duel.use((state) => state.antagonist.gun); const antagonistShell = Duel.use((state) => state.antagonist.shell); const [antagonistSelectorOpen, setAntagonistSelectorOpen] = useState(false); const antagonistTank = Duel.use((state) => state.antagonist.tank); const hasCalibratedShells = useEquipment(103, true); - const hasDynamicArmor = protagonistConsumables.includes(73); const [tab, setTab] = useState('search'); const mutateDuel = Duel.useMutation(); const hasEnhancedArmor = useEquipment(110); - const awaitedConsumableDefinitions = use(consumableDefinitions); - const consumablesList = Object.values(awaitedConsumableDefinitions).filter( - (consumable) => - checkConsumableProvisionInclusivity( - consumable, - protagonistTank, - protagonistGun, - ), - ); const antagonistUniqueGuns = uniqueGuns(antagonistTank.turrets); useEffect(() => setDisplay(displayRaw), [displayRaw]); @@ -291,45 +277,7 @@ export function Options({ thicknessRange, canvas }: OptionsProps) { - {consumablesList.some((consumable) => consumable.id === 73) && ( - { - invalidate(); - mutateDuel((draft) => { - if (draft.protagonist.consumables.includes(73)) { - draft.protagonist.consumables = - draft.protagonist.consumables.filter((id) => id !== 73); - } else { - if ( - draft.protagonist.consumables.length === - protagonistTank.consumables - ) { - draft.protagonist.consumables[ - protagonistTank.consumables - 1 - ] = 73; - } else { - draft.protagonist.consumables.push(73); - } - } - }); - mutateTankopediaEphemeral((draft) => { - draft.shot = undefined; - }); - }} - > - Calibrated Shells - - )} + )} diff --git a/src/app/tools/tankopedia/[id]/components/Thicknesses.tsx b/src/app/tools/tankopedia/[id]/components/Thicknesses.tsx index 26345f09c..58d170de4 100644 --- a/src/app/tools/tankopedia/[id]/components/Thicknesses.tsx +++ b/src/app/tools/tankopedia/[id]/components/Thicknesses.tsx @@ -1,21 +1,11 @@ import { EyeClosedIcon, EyeOpenIcon, ReloadIcon } from '@radix-ui/react-icons'; -import { - Box, - Button, - Checkbox, - Flex, - IconButton, - Text, -} from '@radix-ui/themes'; -import { invalidate } from '@react-three/fiber'; -import { use } from 'react'; +import { Box, Button, Checkbox, Flex, Text } from '@radix-ui/themes'; import { ThicknessRange } from '../../../../../components/Armor/components/StaticArmor'; -import { asset } from '../../../../../core/blitzkit/asset'; -import { checkConsumableProvisionInclusivity } from '../../../../../core/blitzkit/checkConsumableProvisionInclusivity'; -import { consumableDefinitions } from '../../../../../core/blitzkit/consumableDefinitions'; +import { useArmor } from '../../../../../hooks/useArmor'; import * as Duel from '../../../../../stores/duel'; import * as TankopediaEphemeral from '../../../../../stores/tankopediaEphemeral'; import * as TankopediaPersistent from '../../../../../stores/tankopediaPersistent'; +import { DynamicArmorSwitcher } from './Model/components/Options/components/DynamicArmorSwitcher'; interface ThicknessesProps { thicknessRange: ThicknessRange; @@ -36,21 +26,8 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) { const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation(); const editStatic = TankopediaEphemeral.use((state) => state.editStatic); const protagonistTank = Duel.use((state) => state.protagonist.tank); - const protagonistGun = Duel.use((state) => state.protagonist.gun); const tankopediaEphemeralStore = TankopediaEphemeral.useStore(); - const awaitedConsumableDefinitions = use(consumableDefinitions); - const protagonistConsumables = Duel.use( - (state) => state.protagonist.consumables, - ); - const hasDynamicArmor = protagonistConsumables.includes(73); - const consumablesList = Object.values(awaitedConsumableDefinitions).filter( - (consumable) => - checkConsumableProvisionInclusivity( - consumable, - protagonistTank, - protagonistGun, - ), - ); + const { hasDynamicArmor } = useArmor(protagonistTank.id); return ( - {consumablesList.some((consumable) => consumable.id === 73) && ( - { - invalidate(); - mutateDuel((draft) => { - if (draft.protagonist.consumables.includes(73)) { - draft.protagonist.consumables = - draft.protagonist.consumables.filter((id) => id !== 73); - } else { - if ( - draft.protagonist.consumables.length === - protagonistTank.consumables - ) { - draft.protagonist.consumables[ - protagonistTank.consumables - 1 - ] = 73; - } else { - draft.protagonist.consumables.push(73); - } - } - }); - mutateTankopediaEphemeral((draft) => { - draft.shot = undefined; - }); - }} - > - Calibrated Shells - - )} + ); } diff --git a/src/components/Armor/components/PrimaryArmorScene.tsx b/src/components/Armor/components/PrimaryArmorScene.tsx index 7b2dc2160..378e54227 100644 --- a/src/components/Armor/components/PrimaryArmorScene.tsx +++ b/src/components/Armor/components/PrimaryArmorScene.tsx @@ -19,7 +19,7 @@ export const PrimaryArmorScene = memo(() => { const track = Duel.use((state) => state.protagonist.track); const turret = Duel.use((state) => state.protagonist.turret); const gun = Duel.use((state) => state.protagonist.gun); - const armorGltf = useArmor(tank.id); + const armorGltf = useArmor(tank.id).gltf; const armorNodes = Object.values(armorGltf.nodes); const tankModelDefinition = useTankModelDefinition(); const trackModelDefinition = tankModelDefinition.tracks[track.id]; @@ -28,10 +28,9 @@ export const PrimaryArmorScene = memo(() => { const hullOrigin = correctZYTuple(trackModelDefinition.origin); const turretOrigin = correctZYTuple(tankModelDefinition.turretOrigin); const gunOrigin = correctZYTuple(turretModelDefinition.gunOrigin); - const protagonistConsumables = Duel.use( - (state) => state.protagonist.consumables, + const isDynamicArmorActive = Duel.use((state) => + state.protagonist.consumables.includes(73), ); - const hasDynamicArmor = protagonistConsumables.includes(73); useTankTransform(protagonist, turretContainer, gunContainer); @@ -51,8 +50,8 @@ export const PrimaryArmorScene = memo(() => { !isVisible || spaced || thickness === undefined || - (hasDynamicArmor && node.name.includes('state_01')) || - (!hasDynamicArmor && node.name.includes('state_00')) + (isDynamicArmorActive && node.name.includes('state_01')) || + (!isDynamicArmorActive && node.name.includes('state_00')) ) return null; @@ -82,8 +81,8 @@ export const PrimaryArmorScene = memo(() => { !isVisible || spaced || thickness === undefined || - (hasDynamicArmor && node.name.includes('state_01')) || - (!hasDynamicArmor && node.name.includes('state_00')) + (isDynamicArmorActive && node.name.includes('state_01')) || + (!isDynamicArmorActive && node.name.includes('state_00')) ) return null; @@ -116,8 +115,8 @@ export const PrimaryArmorScene = memo(() => { !isVisible || spaced || thickness === undefined || - (hasDynamicArmor && node.name.includes('state_01')) || - (!hasDynamicArmor && node.name.includes('state_00')) + (isDynamicArmorActive && node.name.includes('state_01')) || + (!isDynamicArmorActive && node.name.includes('state_00')) ) return null; diff --git a/src/components/Armor/components/SpacedArmorScene.tsx b/src/components/Armor/components/SpacedArmorScene.tsx index d432d92d4..a0080f10e 100644 --- a/src/components/Armor/components/SpacedArmorScene.tsx +++ b/src/components/Armor/components/SpacedArmorScene.tsx @@ -30,7 +30,7 @@ export const SpacedArmorScene = memo(({ scene }) => { const track = Duel.use((state) => state.protagonist.track); const turret = Duel.use((state) => state.protagonist.turret); const gun = Duel.use((state) => state.protagonist.gun); - const armorGltf = useArmor(tank.id); + const { gltf: armorGltf } = useArmor(tank.id); const { gltf: modelGltf } = useModel(tank.id); const armorNodes = Object.values(armorGltf.nodes); const modelNodes = Object.values(modelGltf.nodes); @@ -45,10 +45,9 @@ export const SpacedArmorScene = memo(({ scene }) => { gunModelDefinition.mask === undefined ? undefined : gunModelDefinition.mask + hullOrigin.y + turretOrigin.y + gunOrigin.y; - const protagonistConsumables = Duel.use( - (state) => state.protagonist.consumables, + const isDynamicArmorActive = Duel.use((state) => + state.protagonist.consumables.includes(73), ); - const hasDynamicArmor = protagonistConsumables.includes(73); useTankTransform(protagonist, turretContainer, gunContainer); @@ -67,8 +66,8 @@ export const SpacedArmorScene = memo(({ scene }) => { if ( !isVisible || thickness === undefined || - (hasDynamicArmor && node.name.includes('state_01')) || - (!hasDynamicArmor && node.name.includes('state_00')) + (isDynamicArmorActive && node.name.includes('state_00')) || + (!isDynamicArmorActive && node.name.includes('state_01')) ) return null; @@ -118,8 +117,8 @@ export const SpacedArmorScene = memo(({ scene }) => { if ( !isVisible || thickness === undefined || - (hasDynamicArmor && node.name.includes('state_01')) || - (!hasDynamicArmor && node.name.includes('state_00')) + (isDynamicArmorActive && node.name.includes('state_00')) || + (!isDynamicArmorActive && node.name.includes('state_01')) ) return null; @@ -153,8 +152,8 @@ export const SpacedArmorScene = memo(({ scene }) => { if ( !isVisible || thickness === undefined || - (hasDynamicArmor && node.name.includes('state_01')) || - (!hasDynamicArmor && node.name.includes('state_00')) + (isDynamicArmorActive && node.name.includes('state_00')) || + (!isDynamicArmorActive && node.name.includes('state_01')) ) return null; diff --git a/src/components/Armor/components/StaticArmor.tsx b/src/components/Armor/components/StaticArmor.tsx index 61da9c600..505010e1d 100644 --- a/src/components/Armor/components/StaticArmor.tsx +++ b/src/components/Armor/components/StaticArmor.tsx @@ -36,7 +36,7 @@ export const StaticArmor = memo(({ thicknessRange }) => { const track = Duel.use((state) => state.protagonist.track); const turret = Duel.use((state) => state.protagonist.turret); const gun = Duel.use((state) => state.protagonist.gun); - const armorGltf = useArmor(tank.id); + const { gltf: armorGltf } = useArmor(tank.id); const { gltf: modelGltf } = useModel(tank.id); const armorNodes = Object.values(armorGltf.nodes); const modelNodes = Object.values(modelGltf.nodes); @@ -64,10 +64,9 @@ export const StaticArmor = memo(({ thicknessRange }) => { const showExternalModules = TankopediaPersistent.use( (state) => state.model.visual.showExternalModules, ); - const protagonistConsumables = Duel.use( - (state) => state.protagonist.consumables, + const isDynamicArmorActive = Duel.use((state) => + state.protagonist.consumables.includes(73), ); - const hasDynamicArmor = protagonistConsumables.includes(73); useTankTransform(protagonist, turretContainer, gunContainer); @@ -88,8 +87,8 @@ export const StaticArmor = memo(({ thicknessRange }) => { thickness === undefined || (!showPrimaryArmor && !spaced) || (!showSpacedArmor && spaced) || - (hasDynamicArmor && node.name.includes('state_01')) || - (!hasDynamicArmor && node.name.includes('state_00')) + (isDynamicArmorActive && node.name.includes('state_00')) || + (!isDynamicArmorActive && node.name.includes('state_01')) ) return null; @@ -145,8 +144,8 @@ export const StaticArmor = memo(({ thicknessRange }) => { thickness === undefined || (!showPrimaryArmor && !spaced) || (!showSpacedArmor && spaced) || - (hasDynamicArmor && node.name.includes('state_01')) || - (!hasDynamicArmor && node.name.includes('state_00')) + (isDynamicArmorActive && node.name.includes('state_00')) || + (!isDynamicArmorActive && node.name.includes('state_01')) ) { return null; } @@ -240,8 +239,8 @@ export const StaticArmor = memo(({ thicknessRange }) => { thickness === undefined || (!showPrimaryArmor && !spaced) || (!showSpacedArmor && spaced) || - (hasDynamicArmor && node.name.includes('state_01')) || - (!hasDynamicArmor && node.name.includes('state_00')) + (isDynamicArmorActive && node.name.includes('state_00')) || + (!isDynamicArmorActive && node.name.includes('state_01')) ) return null; diff --git a/src/components/Armor/components/StaticArmorSceneComponent.tsx b/src/components/Armor/components/StaticArmorSceneComponent.tsx index 054d3272a..ff60bfc44 100644 --- a/src/components/Armor/components/StaticArmorSceneComponent.tsx +++ b/src/components/Armor/components/StaticArmorSceneComponent.tsx @@ -71,7 +71,6 @@ export function StaticArmorSceneComponent({ const mutateTankopediaEphemeralStore = TankopediaEphemeral.useMutation(); const tankopediaEphemeralStore = TankopediaEphemeral.useStore(); const camera = useThree((state) => state.camera); - const scene = useThree((state) => state.scene); const x = thickness / thicknessRange.value; const xClamped = clamp(x, 0, 1); let color: Color; diff --git a/src/hooks/useArmor.ts b/src/hooks/useArmor.ts index 9d000e247..da4ccc870 100644 --- a/src/hooks/useArmor.ts +++ b/src/hooks/useArmor.ts @@ -2,10 +2,24 @@ import { ObjectMap, useLoader } from '@react-three/fiber'; import { GLTF, GLTFLoader } from 'three-stdlib'; import { asset } from '../core/blitzkit/asset'; -const cache: Record = {}; +const cache: Record< + number, + { gltf: GLTF & ObjectMap; hasDynamicArmor: boolean } +> = {}; export function useArmor(id: number) { const gltf = useLoader(GLTFLoader, asset(`3d/tanks/armor/${id}.glb`)); - if (!cache[id]) cache[id] = gltf; + + console.log(Object.values(gltf.nodes).map((node) => node.name)); + + if (!cache[id]) { + cache[id] = { + gltf, + hasDynamicArmor: Object.values(gltf.nodes).some( + (node) => + node.name.includes('state_00') || node.name.includes('state_01'), + ), + }; + } return cache[id]; }