fix: deterministic and correct boolean dynamic armor

This commit is contained in:
TrèsAbhi committed 2024-09-04 21:15:57 -05:00
1 parent 319c865ef0
commit 952523d3f9
8 files changed
+127 -177

No files matched your search

@@ -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 (
<IconButton
color={isDynamicArmorActive ? undefined : 'gray'}
variant="soft"
size={{ initial: '2', sm: '3' }}
onClick={() => {
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;
});
}}
>
<img
alt="Dynamic Armor"
src={asset('icons/consumables/73.webp')}
style={{
width: '50%',
height: '50%',
}}
/>
</IconButton>
);
}
@@ -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) {
</IconButton>
</Flex>
{consumablesList.some((consumable) => consumable.id === 73) && (
<IconButton
color={hasDynamicArmor ? undefined : 'gray'}
variant="soft"
size={{ initial: '2', sm: '3' }}
onClick={() => {
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;
});
}}
>
<img
alt="Calibrated Shells"
src={asset('icons/consumables/73.webp')}
style={{
width: '50%',
height: '50%',
}}
/>
</IconButton>
)}
<DynamicArmorSwitcher />
</Flex>
)}
@@ -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 (
<Flex
@@ -199,45 +176,7 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
</Flex>
</Flex>
{consumablesList.some((consumable) => consumable.id === 73) && (
<IconButton
color={hasDynamicArmor ? undefined : 'gray'}
variant="soft"
size={{ initial: '2', sm: '3' }}
onClick={() => {
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;
});
}}
>
<img
alt="Calibrated Shells"
src={asset('icons/consumables/73.webp')}
style={{
width: '50%',
height: '50%',
}}
/>
</IconButton>
)}
<DynamicArmorSwitcher />
</Flex>
);
}
@@ -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;
@@ -30,7 +30,7 @@ export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ 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<SpacedArmorSceneProps>(({ 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<SpacedArmorSceneProps>(({ 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<SpacedArmorSceneProps>(({ 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<SpacedArmorSceneProps>(({ 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;
@@ -36,7 +36,7 @@ export const StaticArmor = memo<ArmorSceneProps>(({ 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<ArmorSceneProps>(({ 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<ArmorSceneProps>(({ 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<ArmorSceneProps>(({ 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<ArmorSceneProps>(({ 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;
@@ -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;
+16 -2
View File
@@ -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<number, GLTF & ObjectMap> = {};
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];
}