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;
+ });
+ }}
+ >
+
+
+ );
+}
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;
- });
- }}
- >
-
-
- )}
+
)}
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;
- });
- }}
- >
-
-
- )}
+
);
}
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];
}