mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 19:49:50 +02:00
feat: start of dynamic armor
This commit is contained in:
1 parent
86e369af19
commit
cd0769675e
13 files changed
+300
-45
No files matched your search
@@ -46,6 +46,11 @@ import { DATA } from './constants';
|
|||||||
import { Avatar } from './skillIcons';
|
import { Avatar } from './skillIcons';
|
||||||
import { TankParameters } from './tankIcons';
|
import { TankParameters } from './tankIcons';
|
||||||
|
|
||||||
|
function ensureDynamicArmor(armor: ModelArmor) {
|
||||||
|
if (!armor.dynamic) armor.dynamic = { false: [], true: [] };
|
||||||
|
return armor as ModelArmor & Required<Pick<ModelArmor, 'dynamic'>>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface BlitzStrings {
|
export interface BlitzStrings {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
}
|
}
|
||||||
@@ -210,6 +215,27 @@ interface VehicleDefinitions {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
extras?: {
|
||||||
|
armorsStatesController?: {
|
||||||
|
state: [
|
||||||
|
{
|
||||||
|
id: 0;
|
||||||
|
armors: string;
|
||||||
|
type: 'default';
|
||||||
|
enabled: true;
|
||||||
|
factorsModifiers: '';
|
||||||
|
default: '';
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 1;
|
||||||
|
armors: string;
|
||||||
|
type: 'default';
|
||||||
|
enabled: false;
|
||||||
|
factorsModifiers: '';
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
export interface VehicleDefinitionList {
|
export interface VehicleDefinitionList {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
@@ -1242,6 +1268,55 @@ export async function definitions() {
|
|||||||
Object.values(tankDefinitions[tankId].tracks).forEach((track) => {
|
Object.values(tankDefinitions[tankId].tracks).forEach((track) => {
|
||||||
track.xp = trackXps.get(track.id);
|
track.xp = trackXps.get(track.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (tankDefinition.root.extras?.armorsStatesController) {
|
||||||
|
const [falsePlates, truePlates] =
|
||||||
|
tankDefinition.root.extras.armorsStatesController.state.map(
|
||||||
|
(state) => state.armors.split(' '),
|
||||||
|
);
|
||||||
|
|
||||||
|
function applyDynamicArmor(plates: string[], type: `${boolean}`) {
|
||||||
|
plates.forEach((plate) => {
|
||||||
|
const [name, armorIndexString] = plate.split('.');
|
||||||
|
const armorIndex = Number(armorIndexString.split('_')[1]);
|
||||||
|
|
||||||
|
if (name === 'hull') {
|
||||||
|
ensureDynamicArmor(modelDefinitions[tankId].armor).dynamic[
|
||||||
|
type
|
||||||
|
].push(armorIndex);
|
||||||
|
} else {
|
||||||
|
const [module, moduleIndexString] = name.split('_');
|
||||||
|
const moduleIndex = Number(moduleIndexString);
|
||||||
|
|
||||||
|
if (module === 'turret') {
|
||||||
|
const turret = Object.values(
|
||||||
|
modelDefinitions[tankId].turrets,
|
||||||
|
).find((turret) => turret.model === moduleIndex);
|
||||||
|
if (!turret) throw new Error('Turret not found');
|
||||||
|
ensureDynamicArmor(turret.armor).dynamic[type].push(
|
||||||
|
armorIndex,
|
||||||
|
);
|
||||||
|
} else if (module === 'gun') {
|
||||||
|
Object.values(modelDefinitions[tankId].turrets).forEach(
|
||||||
|
(turret) => {
|
||||||
|
const gun = Object.values(turret.guns).find(
|
||||||
|
(gun) => gun.model === moduleIndex,
|
||||||
|
);
|
||||||
|
if (!gun) throw new Error('Gun not found');
|
||||||
|
ensureDynamicArmor(gun.armor).dynamic[type].push(
|
||||||
|
armorIndex,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else
|
||||||
|
throw new Error(`Unknown dynamic armor module ${module}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
applyDynamicArmor(falsePlates, 'false');
|
||||||
|
applyDynamicArmor(truePlates, 'true');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { ChevronLeftIcon, MixIcon } from '@radix-ui/react-icons';
|
|||||||
import { Box, Button, Flex, Heading } from '@radix-ui/themes';
|
import { Box, Button, Flex, Heading } from '@radix-ui/themes';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Suspense, use, useEffect, useMemo, useRef, useState } from 'react';
|
import { Suspense, use, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { ThicknessRange } from '../../../../../../components/Armor/components/StaticArmor';
|
||||||
import { classIcons } from '../../../../../../components/ClassIcon';
|
import { classIcons } from '../../../../../../components/ClassIcon';
|
||||||
import { ThicknessRange } from '../../../../../../components/StaticArmor';
|
|
||||||
import { resolveNearPenetration } from '../../../../../../core/blitz/resolveNearPenetration';
|
import { resolveNearPenetration } from '../../../../../../core/blitz/resolveNearPenetration';
|
||||||
import { tankDefinitions } from '../../../../../../core/blitzkit/tankDefinitions';
|
import { tankDefinitions } from '../../../../../../core/blitzkit/tankDefinitions';
|
||||||
import { useFullScreen } from '../../../../../../hooks/useFullScreen';
|
import { useFullScreen } from '../../../../../../hooks/useFullScreen';
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { ShotDisplay } from '../../../../../../components/Armor/components/ShotD
|
|||||||
import {
|
import {
|
||||||
StaticArmor,
|
StaticArmor,
|
||||||
ThicknessRange,
|
ThicknessRange,
|
||||||
} from '../../../../../../components/StaticArmor';
|
} from '../../../../../../components/Armor/components/StaticArmor';
|
||||||
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
||||||
import { modelTransformEvent } from '../../../../../../core/blitzkit/modelTransform';
|
import { modelTransformEvent } from '../../../../../../core/blitzkit/modelTransform';
|
||||||
import { Pose, poseEvent } from '../../../../../../core/blitzkit/pose';
|
import { Pose, poseEvent } from '../../../../../../core/blitzkit/pose';
|
||||||
@@ -48,8 +48,6 @@ export const TankSandbox = forwardRef<HTMLCanvasElement, TankSandboxProps>(
|
|||||||
const display = TankopediaPersistent.use((state) => state.display);
|
const display = TankopediaPersistent.use((state) => state.display);
|
||||||
const onScreen = useOnScreen(canvas);
|
const onScreen = useOnScreen(canvas);
|
||||||
|
|
||||||
console.log(onScreen);
|
|
||||||
|
|
||||||
useImperativeHandle(ref, () => canvas.current!, []);
|
useImperativeHandle(ref, () => canvas.current!, []);
|
||||||
|
|
||||||
function handlePointerDown() {
|
function handlePointerDown() {
|
||||||
|
|||||||
@@ -22,13 +22,15 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
} from '@radix-ui/themes';
|
} from '@radix-ui/themes';
|
||||||
import { invalidate } from '@react-three/fiber';
|
import { invalidate } from '@react-three/fiber';
|
||||||
import { RefObject, useEffect, useState } from 'react';
|
import { RefObject, use, useEffect, useState } from 'react';
|
||||||
|
import { ThicknessRange } from '../../../../../../../components/Armor/components/StaticArmor';
|
||||||
import { ModuleButton } from '../../../../../../../components/ModuleButtons/ModuleButton';
|
import { ModuleButton } from '../../../../../../../components/ModuleButtons/ModuleButton';
|
||||||
import { SmallTankIcon } from '../../../../../../../components/SmallTankIcon';
|
import { SmallTankIcon } from '../../../../../../../components/SmallTankIcon';
|
||||||
import { ThicknessRange } from '../../../../../../../components/StaticArmor';
|
|
||||||
import { resolveNearPenetration } from '../../../../../../../core/blitz/resolveNearPenetration';
|
import { resolveNearPenetration } from '../../../../../../../core/blitz/resolveNearPenetration';
|
||||||
import { resolvePenetrationCoefficient } from '../../../../../../../core/blitz/resolvePenetrationCoefficient';
|
import { resolvePenetrationCoefficient } from '../../../../../../../core/blitz/resolvePenetrationCoefficient';
|
||||||
import { asset } from '../../../../../../../core/blitzkit/asset';
|
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 { imgur } from '../../../../../../../core/blitzkit/imgur';
|
||||||
import { Pose, poseEvent } from '../../../../../../../core/blitzkit/pose';
|
import { Pose, poseEvent } from '../../../../../../../core/blitzkit/pose';
|
||||||
import { TIER_ROMAN_NUMERALS } from '../../../../../../../core/blitzkit/tankDefinitions/constants';
|
import { TIER_ROMAN_NUMERALS } from '../../../../../../../core/blitzkit/tankDefinitions/constants';
|
||||||
@@ -77,15 +79,29 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
|||||||
(state) => state.model.visual.environment,
|
(state) => state.model.visual.environment,
|
||||||
);
|
);
|
||||||
const developerMode = App.useDeferred(false, (state) => state.developerMode);
|
const developerMode = App.useDeferred(false, (state) => state.developerMode);
|
||||||
const protagonist = Duel.use((state) => state.protagonist.tank);
|
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 antagonistGun = Duel.use((state) => state.antagonist.gun);
|
||||||
const antagonistShell = Duel.use((state) => state.antagonist.shell);
|
const antagonistShell = Duel.use((state) => state.antagonist.shell);
|
||||||
const [antagonistSelectorOpen, setAntagonistSelectorOpen] = useState(false);
|
const [antagonistSelectorOpen, setAntagonistSelectorOpen] = useState(false);
|
||||||
const antagonistTank = Duel.use((state) => state.antagonist.tank);
|
const antagonistTank = Duel.use((state) => state.antagonist.tank);
|
||||||
const hasCalibratedShells = useEquipment(103, true);
|
const hasCalibratedShells = useEquipment(103, true);
|
||||||
|
const hasDynamicArmor = protagonistConsumables.includes(73);
|
||||||
const [tab, setTab] = useState('search');
|
const [tab, setTab] = useState('search');
|
||||||
const mutateDuel = Duel.useMutation();
|
const mutateDuel = Duel.useMutation();
|
||||||
const hasEnhancedArmor = useEquipment(110);
|
const hasEnhancedArmor = useEquipment(110);
|
||||||
|
const awaitedConsumableDefinitions = use(consumableDefinitions);
|
||||||
|
const consumablesList = Object.values(awaitedConsumableDefinitions).filter(
|
||||||
|
(consumable) =>
|
||||||
|
checkConsumableProvisionInclusivity(
|
||||||
|
consumable,
|
||||||
|
protagonistTank,
|
||||||
|
protagonistGun,
|
||||||
|
),
|
||||||
|
);
|
||||||
const antagonistUniqueGuns = uniqueGuns(antagonistTank.turrets);
|
const antagonistUniqueGuns = uniqueGuns(antagonistTank.turrets);
|
||||||
|
|
||||||
useEffect(() => setDisplay(displayRaw), [displayRaw]);
|
useEffect(() => setDisplay(displayRaw), [displayRaw]);
|
||||||
@@ -141,7 +157,6 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
|||||||
).toFixed(0)}
|
).toFixed(0)}
|
||||||
mm
|
mm
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{antagonistUniqueGuns.length > 1 && (
|
{antagonistUniqueGuns.length > 1 && (
|
||||||
<Popover.Root>
|
<Popover.Root>
|
||||||
<Popover.Trigger>
|
<Popover.Trigger>
|
||||||
@@ -201,7 +216,6 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
|||||||
</Popover.Content>
|
</Popover.Content>
|
||||||
</Popover.Root>
|
</Popover.Root>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Flex
|
<Flex
|
||||||
direction="column"
|
direction="column"
|
||||||
style={{
|
style={{
|
||||||
@@ -303,6 +317,46 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
|||||||
/>
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</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>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -323,7 +377,10 @@ export function Options({ thicknessRange, canvas }: OptionsProps) {
|
|||||||
|
|
||||||
const anchor = document.createElement('a');
|
const anchor = document.createElement('a');
|
||||||
|
|
||||||
anchor.setAttribute('download', `${protagonist.name}.png`);
|
anchor.setAttribute(
|
||||||
|
'download',
|
||||||
|
`${protagonistTank.name}.png`,
|
||||||
|
);
|
||||||
anchor.setAttribute(
|
anchor.setAttribute(
|
||||||
'href',
|
'href',
|
||||||
canvas.current.toDataURL('image/png'),
|
canvas.current.toDataURL('image/png'),
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ export const TankModel = memo(() => {
|
|||||||
|
|
||||||
if (!isVisible) return null;
|
if (!isVisible) return null;
|
||||||
|
|
||||||
|
console.log(node.name);
|
||||||
|
|
||||||
function translateTexture(offset: number) {
|
function translateTexture(offset: number) {
|
||||||
const mesh = node.children[0] as Mesh;
|
const mesh = node.children[0] as Mesh;
|
||||||
const material = mesh?.material as MeshStandardMaterial;
|
const material = mesh?.material as MeshStandardMaterial;
|
||||||
|
|||||||
@@ -1,6 +1,19 @@
|
|||||||
import { EyeClosedIcon, EyeOpenIcon, ReloadIcon } from '@radix-ui/react-icons';
|
import { EyeClosedIcon, EyeOpenIcon, ReloadIcon } from '@radix-ui/react-icons';
|
||||||
import { Box, Button, Checkbox, Flex, Text } from '@radix-ui/themes';
|
import {
|
||||||
import { ThicknessRange } from '../../../../../components/StaticArmor';
|
Box,
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
Flex,
|
||||||
|
IconButton,
|
||||||
|
Text,
|
||||||
|
} from '@radix-ui/themes';
|
||||||
|
import { invalidate } from '@react-three/fiber';
|
||||||
|
import { use } from 'react';
|
||||||
|
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 * as Duel from '../../../../../stores/duel';
|
||||||
import * as TankopediaEphemeral from '../../../../../stores/tankopediaEphemeral';
|
import * as TankopediaEphemeral from '../../../../../stores/tankopediaEphemeral';
|
||||||
import * as TankopediaPersistent from '../../../../../stores/tankopediaPersistent';
|
import * as TankopediaPersistent from '../../../../../stores/tankopediaPersistent';
|
||||||
|
|
||||||
@@ -18,10 +31,26 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
|
|||||||
const showPrimaryArmor = TankopediaPersistent.use(
|
const showPrimaryArmor = TankopediaPersistent.use(
|
||||||
(state) => state.model.visual.showPrimaryArmor,
|
(state) => state.model.visual.showPrimaryArmor,
|
||||||
);
|
);
|
||||||
|
const mutateDuel = Duel.useMutation();
|
||||||
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
|
const mutateTankopediaPersistent = TankopediaPersistent.useMutation();
|
||||||
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
const mutateTankopediaEphemeral = TankopediaEphemeral.useMutation();
|
||||||
const editStatic = TankopediaEphemeral.use((state) => state.editStatic);
|
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 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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
@@ -169,6 +198,46 @@ export function Thicknesses({ thicknessRange }: ThicknessesProps) {
|
|||||||
<Checkbox size="2" checked={editStatic} />
|
<Checkbox size="2" checked={editStatic} />
|
||||||
</Flex>
|
</Flex>
|
||||||
</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>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -28,6 +28,10 @@ export const PrimaryArmorScene = memo(() => {
|
|||||||
const hullOrigin = correctZYTuple(trackModelDefinition.origin);
|
const hullOrigin = correctZYTuple(trackModelDefinition.origin);
|
||||||
const turretOrigin = correctZYTuple(tankModelDefinition.turretOrigin);
|
const turretOrigin = correctZYTuple(tankModelDefinition.turretOrigin);
|
||||||
const gunOrigin = correctZYTuple(turretModelDefinition.gunOrigin);
|
const gunOrigin = correctZYTuple(turretModelDefinition.gunOrigin);
|
||||||
|
const protagonistConsumables = Duel.use(
|
||||||
|
(state) => state.protagonist.consumables,
|
||||||
|
);
|
||||||
|
const hasDynamicArmor = protagonistConsumables.includes(73);
|
||||||
|
|
||||||
useTankTransform(protagonist, turretContainer, gunContainer);
|
useTankTransform(protagonist, turretContainer, gunContainer);
|
||||||
|
|
||||||
@@ -43,7 +47,14 @@ export const PrimaryArmorScene = memo(() => {
|
|||||||
armorId,
|
armorId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isVisible || spaced || thickness === undefined) return null;
|
if (
|
||||||
|
!isVisible ||
|
||||||
|
spaced ||
|
||||||
|
thickness === undefined ||
|
||||||
|
(hasDynamicArmor && node.name.includes('state_01')) ||
|
||||||
|
(!hasDynamicArmor && node.name.includes('state_00'))
|
||||||
|
)
|
||||||
|
return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PrimaryArmorSceneComponent
|
<PrimaryArmorSceneComponent
|
||||||
@@ -67,7 +78,14 @@ export const PrimaryArmorScene = memo(() => {
|
|||||||
armorId,
|
armorId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isVisible || spaced || thickness === undefined) return null;
|
if (
|
||||||
|
!isVisible ||
|
||||||
|
spaced ||
|
||||||
|
thickness === undefined ||
|
||||||
|
(hasDynamicArmor && node.name.includes('state_01')) ||
|
||||||
|
(!hasDynamicArmor && node.name.includes('state_00'))
|
||||||
|
)
|
||||||
|
return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group position={hullOrigin}>
|
<group position={hullOrigin}>
|
||||||
@@ -94,7 +112,14 @@ export const PrimaryArmorScene = memo(() => {
|
|||||||
armorId,
|
armorId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isVisible || spaced || thickness === undefined) return null;
|
if (
|
||||||
|
!isVisible ||
|
||||||
|
spaced ||
|
||||||
|
thickness === undefined ||
|
||||||
|
(hasDynamicArmor && node.name.includes('state_01')) ||
|
||||||
|
(!hasDynamicArmor && node.name.includes('state_00'))
|
||||||
|
)
|
||||||
|
return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group position={hullOrigin}>
|
<group position={hullOrigin}>
|
||||||
|
|||||||
@@ -45,6 +45,10 @@ export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ scene }) => {
|
|||||||
gunModelDefinition.mask === undefined
|
gunModelDefinition.mask === undefined
|
||||||
? undefined
|
? undefined
|
||||||
: gunModelDefinition.mask + hullOrigin.y + turretOrigin.y + gunOrigin.y;
|
: gunModelDefinition.mask + hullOrigin.y + turretOrigin.y + gunOrigin.y;
|
||||||
|
const protagonistConsumables = Duel.use(
|
||||||
|
(state) => state.protagonist.consumables,
|
||||||
|
);
|
||||||
|
const hasDynamicArmor = protagonistConsumables.includes(73);
|
||||||
|
|
||||||
useTankTransform(protagonist, turretContainer, gunContainer);
|
useTankTransform(protagonist, turretContainer, gunContainer);
|
||||||
|
|
||||||
@@ -60,7 +64,13 @@ export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ scene }) => {
|
|||||||
armorId,
|
armorId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isVisible || thickness === undefined) return null;
|
if (
|
||||||
|
!isVisible ||
|
||||||
|
thickness === undefined ||
|
||||||
|
(hasDynamicArmor && node.name.includes('state_01')) ||
|
||||||
|
(!hasDynamicArmor && node.name.includes('state_00'))
|
||||||
|
)
|
||||||
|
return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SpacedArmorSceneComponent
|
<SpacedArmorSceneComponent
|
||||||
@@ -105,7 +115,13 @@ export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ scene }) => {
|
|||||||
armorId,
|
armorId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isVisible || thickness === undefined) return null;
|
if (
|
||||||
|
!isVisible ||
|
||||||
|
thickness === undefined ||
|
||||||
|
(hasDynamicArmor && node.name.includes('state_01')) ||
|
||||||
|
(!hasDynamicArmor && node.name.includes('state_00'))
|
||||||
|
)
|
||||||
|
return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group position={hullOrigin}>
|
<group position={hullOrigin}>
|
||||||
@@ -134,7 +150,13 @@ export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ scene }) => {
|
|||||||
armorId,
|
armorId,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!isVisible || thickness === undefined) return null;
|
if (
|
||||||
|
!isVisible ||
|
||||||
|
thickness === undefined ||
|
||||||
|
(hasDynamicArmor && node.name.includes('state_01')) ||
|
||||||
|
(!hasDynamicArmor && node.name.includes('state_00'))
|
||||||
|
)
|
||||||
|
return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group position={hullOrigin}>
|
<group position={hullOrigin}>
|
||||||
|
|||||||
+31
-22
@@ -1,23 +1,23 @@
|
|||||||
import { ThreeEvent, useThree } from '@react-three/fiber';
|
import { ThreeEvent, useThree } from '@react-three/fiber';
|
||||||
import { memo, useRef } from 'react';
|
import { memo, useRef } from 'react';
|
||||||
import { Group, Plane, Vector2, Vector3 } from 'three';
|
import { Group, Plane, Vector2, Vector3 } from 'three';
|
||||||
import { applyPitchYawLimits } from '../../core/blitz/applyPitchYawLimits';
|
import { applyPitchYawLimits } from '../../../core/blitz/applyPitchYawLimits';
|
||||||
import { correctZYTuple } from '../../core/blitz/correctZYTuple';
|
import { correctZYTuple } from '../../../core/blitz/correctZYTuple';
|
||||||
import { hasEquipment } from '../../core/blitzkit/hasEquipment';
|
import { hasEquipment } from '../../../core/blitzkit/hasEquipment';
|
||||||
import { modelTransformEvent } from '../../core/blitzkit/modelTransform';
|
import { modelTransformEvent } from '../../../core/blitzkit/modelTransform';
|
||||||
import { nameToArmorId } from '../../core/blitzkit/nameToArmorId';
|
import { nameToArmorId } from '../../../core/blitzkit/nameToArmorId';
|
||||||
import { resolveArmor } from '../../core/blitzkit/resolveThickness';
|
import { resolveArmor } from '../../../core/blitzkit/resolveThickness';
|
||||||
import { normalizeAngleRad } from '../../core/math/normalizeAngleRad';
|
import { normalizeAngleRad } from '../../../core/math/normalizeAngleRad';
|
||||||
import { useArmor } from '../../hooks/useArmor';
|
import { useArmor } from '../../../hooks/useArmor';
|
||||||
import { useModel } from '../../hooks/useModel';
|
import { useModel } from '../../../hooks/useModel';
|
||||||
import { useTankModelDefinition } from '../../hooks/useTankModelDefinition';
|
import { useTankModelDefinition } from '../../../hooks/useTankModelDefinition';
|
||||||
import { useTankTransform } from '../../hooks/useTankTransform';
|
import { useTankTransform } from '../../../hooks/useTankTransform';
|
||||||
import * as Duel from '../../stores/duel';
|
import * as Duel from '../../../stores/duel';
|
||||||
import * as TankopediaEphemeral from '../../stores/tankopediaEphemeral';
|
import * as TankopediaEphemeral from '../../../stores/tankopediaEphemeral';
|
||||||
import * as TankopediaPersistent from '../../stores/tankopediaPersistent';
|
import * as TankopediaPersistent from '../../../stores/tankopediaPersistent';
|
||||||
import { ModelTankWrapper } from '../Armor/components/ModelTankWrapper';
|
import { ModelTankWrapper } from './ModelTankWrapper';
|
||||||
import { ArmorType } from '../Armor/components/SpacedArmorScene';
|
import { ArmorType } from './SpacedArmorScene';
|
||||||
import { StaticArmorSceneComponent } from '../Armor/components/StaticArmorSceneComponent';
|
import { StaticArmorSceneComponent } from './StaticArmorSceneComponent';
|
||||||
|
|
||||||
interface ArmorSceneProps {
|
interface ArmorSceneProps {
|
||||||
thicknessRange: ThicknessRange;
|
thicknessRange: ThicknessRange;
|
||||||
@@ -64,6 +64,10 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
|||||||
const showExternalModules = TankopediaPersistent.use(
|
const showExternalModules = TankopediaPersistent.use(
|
||||||
(state) => state.model.visual.showExternalModules,
|
(state) => state.model.visual.showExternalModules,
|
||||||
);
|
);
|
||||||
|
const protagonistConsumables = Duel.use(
|
||||||
|
(state) => state.protagonist.consumables,
|
||||||
|
);
|
||||||
|
const hasDynamicArmor = protagonistConsumables.includes(73);
|
||||||
|
|
||||||
useTankTransform(protagonist, turretContainer, gunContainer);
|
useTankTransform(protagonist, turretContainer, gunContainer);
|
||||||
|
|
||||||
@@ -83,7 +87,9 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
|||||||
!isVisible ||
|
!isVisible ||
|
||||||
thickness === undefined ||
|
thickness === undefined ||
|
||||||
(!showPrimaryArmor && !spaced) ||
|
(!showPrimaryArmor && !spaced) ||
|
||||||
(!showSpacedArmor && spaced)
|
(!showSpacedArmor && spaced) ||
|
||||||
|
(hasDynamicArmor && node.name.includes('state_01')) ||
|
||||||
|
(!hasDynamicArmor && node.name.includes('state_00'))
|
||||||
)
|
)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
@@ -138,7 +144,9 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
|||||||
!isVisible ||
|
!isVisible ||
|
||||||
thickness === undefined ||
|
thickness === undefined ||
|
||||||
(!showPrimaryArmor && !spaced) ||
|
(!showPrimaryArmor && !spaced) ||
|
||||||
(!showSpacedArmor && spaced)
|
(!showSpacedArmor && spaced) ||
|
||||||
|
(hasDynamicArmor && node.name.includes('state_01')) ||
|
||||||
|
(!hasDynamicArmor && node.name.includes('state_00'))
|
||||||
) {
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -231,10 +239,11 @@ export const StaticArmor = memo<ArmorSceneProps>(({ thicknessRange }) => {
|
|||||||
!isVisible ||
|
!isVisible ||
|
||||||
thickness === undefined ||
|
thickness === undefined ||
|
||||||
(!showPrimaryArmor && !spaced) ||
|
(!showPrimaryArmor && !spaced) ||
|
||||||
(!showSpacedArmor && spaced)
|
(!showSpacedArmor && spaced) ||
|
||||||
) {
|
(hasDynamicArmor && node.name.includes('state_01')) ||
|
||||||
|
(!hasDynamicArmor && node.name.includes('state_00'))
|
||||||
|
)
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
const position = new Vector2();
|
const position = new Vector2();
|
||||||
const delta = new Vector2();
|
const delta = new Vector2();
|
||||||
@@ -23,12 +23,12 @@ import {
|
|||||||
} from '../../../core/blitzkit/modelTransform';
|
} from '../../../core/blitzkit/modelTransform';
|
||||||
import { discardClippingPlane } from '../../../core/three/discardClippingPlane';
|
import { discardClippingPlane } from '../../../core/three/discardClippingPlane';
|
||||||
import * as TankopediaEphemeral from '../../../stores/tankopediaEphemeral';
|
import * as TankopediaEphemeral from '../../../stores/tankopediaEphemeral';
|
||||||
import { ThicknessRange } from '../../StaticArmor';
|
|
||||||
import { ArmorType } from './SpacedArmorScene';
|
import { ArmorType } from './SpacedArmorScene';
|
||||||
import {
|
import {
|
||||||
ArmorUserData,
|
ArmorUserData,
|
||||||
ExternalModuleVariant,
|
ExternalModuleVariant,
|
||||||
} from './SpacedArmorSceneComponent';
|
} from './SpacedArmorSceneComponent';
|
||||||
|
import { ThicknessRange } from './StaticArmor';
|
||||||
|
|
||||||
type StaticArmorSceneComponentProps = {
|
type StaticArmorSceneComponentProps = {
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export function ConsumablesManager({
|
|||||||
}: ConsumablesManagerProps) {
|
}: ConsumablesManagerProps) {
|
||||||
return (
|
return (
|
||||||
<Flex wrap="wrap" gap="2" {...props}>
|
<Flex wrap="wrap" gap="2" {...props}>
|
||||||
{consumables.map((consumable, index) => {
|
{consumables.map((consumable) => {
|
||||||
const isSelected = selected.some((id) => id === consumable.id);
|
const isSelected = selected.some((id) => id === consumable.id);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export type ModelDefinitions = Record<number, ModelDefinition>;
|
|||||||
export interface ModelArmor {
|
export interface ModelArmor {
|
||||||
thickness: Record<number, number>;
|
thickness: Record<number, number>;
|
||||||
spaced?: number[];
|
spaced?: number[];
|
||||||
|
// dynamic?: Record<`${boolean}`, number[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BoundingBox {
|
export interface BoundingBox {
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
export function useConsumable(id: number, consumables: number[]) {
|
|
||||||
return consumables.includes(id);
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user