mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 20:19:49 +02:00
feat: dispaly
This commit is contained in:
1 parent
dc50c4a758
commit
3438e51ef1
5 files changed
+139
-325
No files matched your search
@@ -111,6 +111,9 @@ export function Options() {
|
|||||||
mutateDuel((draft) => {
|
mutateDuel((draft) => {
|
||||||
draft.antagonist!.shell = thisShell;
|
draft.antagonist!.shell = thisShell;
|
||||||
});
|
});
|
||||||
|
mutateTankopediaTemporary((draft) => {
|
||||||
|
draft.shot = undefined;
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
@@ -138,6 +141,9 @@ export function Options() {
|
|||||||
? 0
|
? 0
|
||||||
: 1;
|
: 1;
|
||||||
});
|
});
|
||||||
|
mutateTankopediaTemporary((draft) => {
|
||||||
|
draft.shot = undefined;
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
@@ -162,6 +168,9 @@ export function Options() {
|
|||||||
? 0
|
? 0
|
||||||
: -1;
|
: -1;
|
||||||
});
|
});
|
||||||
|
mutateTankopediaTemporary((draft) => {
|
||||||
|
draft.shot = undefined;
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
|
|||||||
-26
@@ -1,26 +0,0 @@
|
|||||||
import { Button } from '@radix-ui/themes';
|
|
||||||
import { ComponentProps } from 'react';
|
|
||||||
import { asset } from '../../../../../../../core/blitzkit/asset';
|
|
||||||
|
|
||||||
interface QuickEquipmentButtonProps extends ComponentProps<typeof Button> {
|
|
||||||
equipment: number;
|
|
||||||
active: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function QuickEquipmentButton({
|
|
||||||
equipment,
|
|
||||||
active,
|
|
||||||
...props
|
|
||||||
}: QuickEquipmentButtonProps) {
|
|
||||||
return (
|
|
||||||
<Button variant="ghost" {...props}>
|
|
||||||
<img
|
|
||||||
alt={`Equipment ${equipment}`}
|
|
||||||
style={{ opacity: active ? 1 : 0.5 }}
|
|
||||||
src={asset(`icons/equipment/${equipment}.webp`)}
|
|
||||||
width={24}
|
|
||||||
height={24}
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,27 +1,9 @@
|
|||||||
import { Card, Flex, Text } from '@radix-ui/themes';
|
import { Card, Flex, Text } from '@radix-ui/themes';
|
||||||
import { Html } from '@react-three/drei';
|
import { Html } from '@react-three/drei';
|
||||||
import { useFrame } from '@react-three/fiber';
|
import { ComponentProps } from 'react';
|
||||||
import { useEffect, useRef } from 'react';
|
import { Shot, useTankopediaTemporary } from '../../../stores/tankopedia';
|
||||||
import { Mesh } from 'three';
|
|
||||||
import { radToDeg } from 'three/src/math/MathUtils';
|
|
||||||
import { canSplash } from '../../../core/blitz/canSplash';
|
|
||||||
import { isExplosive } from '../../../core/blitz/isExplosive';
|
|
||||||
import { NaNFallback } from '../../../core/math/NaNFallback';
|
|
||||||
import { normalToEuler } from '../../../core/math/normalToEuler';
|
|
||||||
import { AngledPenetration } from '../../../icons/AngledPenetration';
|
|
||||||
import { Block } from '../../../icons/Block';
|
|
||||||
import { NominalPenetration } from '../../../icons/NominalPenetration';
|
|
||||||
import { Ricochet } from '../../../icons/Ricochet';
|
|
||||||
import { useDuel } from '../../../stores/duel';
|
|
||||||
import {
|
|
||||||
ShotLayer,
|
|
||||||
ShotLayerBase,
|
|
||||||
ShotLayerGap,
|
|
||||||
ShotLayerNonExternal,
|
|
||||||
mutateTankopediaTemporary,
|
|
||||||
useTankopediaTemporary,
|
|
||||||
} from '../../../stores/tankopedia';
|
|
||||||
import { ArmorType } from './SpacedArmorScene';
|
import { ArmorType } from './SpacedArmorScene';
|
||||||
|
import { ExternalModuleVariant } from './SpacedArmorSceneComponent';
|
||||||
|
|
||||||
const layerTypeNames: Record<ArmorType | 'null', string> = {
|
const layerTypeNames: Record<ArmorType | 'null', string> = {
|
||||||
[ArmorType.Core]: 'Core',
|
[ArmorType.Core]: 'Core',
|
||||||
@@ -30,305 +12,149 @@ const layerTypeNames: Record<ArmorType | 'null', string> = {
|
|||||||
null: 'Gap',
|
null: 'Gap',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const externalLayerNames: Record<ExternalModuleVariant, string> = {
|
||||||
|
gun: 'Gun',
|
||||||
|
track: 'Tracks',
|
||||||
|
};
|
||||||
|
|
||||||
|
const shotStatusNames: Record<Shot['status'], string> = {
|
||||||
|
blocked: 'Blocked',
|
||||||
|
penetration: 'penetration',
|
||||||
|
ricochet: 'ricochet',
|
||||||
|
splash: 'splash',
|
||||||
|
};
|
||||||
|
|
||||||
|
const shotStatusColors: Record<
|
||||||
|
Shot['status'],
|
||||||
|
ComponentProps<typeof Text>['color']
|
||||||
|
> = {
|
||||||
|
blocked: 'red',
|
||||||
|
penetration: 'green',
|
||||||
|
ricochet: 'yellow',
|
||||||
|
splash: 'orange',
|
||||||
|
};
|
||||||
|
|
||||||
const LENGTH_INFINITY = 4;
|
const LENGTH_INFINITY = 4;
|
||||||
const TRACER_THICK = 1 / 64;
|
const TRACER_THICK = 1 / 64;
|
||||||
const TRACER_THIN = TRACER_THICK / 2;
|
const TRACER_THIN = TRACER_THICK / 2;
|
||||||
|
|
||||||
export function ShotDisplay() {
|
export function ShotDisplay() {
|
||||||
return null;
|
|
||||||
|
|
||||||
const shot = useTankopediaTemporary((state) => state.shot);
|
const shot = useTankopediaTemporary((state) => state.shot);
|
||||||
const preMidPointTracer = useRef<Mesh>(null);
|
|
||||||
const postMidPointTracer = useRef<Mesh>(null);
|
|
||||||
const hasMultipleLayers = shot?.layersIn.some(
|
|
||||||
(layer, index) => index !== 0 && layer.type !== null,
|
|
||||||
);
|
|
||||||
const { shell } = useDuel.getState().antagonist!;
|
|
||||||
const explosive = isExplosive(shell.type);
|
|
||||||
const splashing = canSplash(shell.type);
|
|
||||||
const midPointLayer = (shot?.layersIn.findLast(
|
|
||||||
(layer) => layer.type !== null && layer.status === 'ricochet',
|
|
||||||
) ??
|
|
||||||
shot?.layersIn.findLast((layer) => layer.type !== null)) as ShotLayerBase;
|
|
||||||
const firstLayer = shot?.layersIn[0] as Exclude<ShotLayer, ShotLayerGap>;
|
|
||||||
const lastLayer = shot?.layersIn.at(-1)!;
|
|
||||||
const distanceFromSpacedArmor = shot
|
|
||||||
? firstLayer.point.distanceTo(shot.point)
|
|
||||||
: undefined;
|
|
||||||
const preMidPointLayers = shot?.layersIn.slice(
|
|
||||||
0,
|
|
||||||
shot.layersIn.indexOf(midPointLayer as ShotLayer),
|
|
||||||
);
|
|
||||||
const postMidPointLayers = shot?.layersIn.slice(
|
|
||||||
shot.layersIn.indexOf(midPointLayer as ShotLayer),
|
|
||||||
shot.layersIn.length,
|
|
||||||
);
|
|
||||||
const preMidPointGap = shot
|
|
||||||
? preMidPointLayers!.reduce((accumulator, layer) => {
|
|
||||||
if (layer.type === null) return accumulator + layer.distance;
|
|
||||||
return accumulator;
|
|
||||||
}, 0) + LENGTH_INFINITY
|
|
||||||
: undefined;
|
|
||||||
const preMidPointThickness = shot
|
|
||||||
? preMidPointLayers!.reduce(
|
|
||||||
(accumulator, layer) =>
|
|
||||||
accumulator +
|
|
||||||
(layer.type === null
|
|
||||||
? 0
|
|
||||||
: layer.type === ArmorType.External
|
|
||||||
? layer.thickness
|
|
||||||
: layer.thicknessAngled),
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
: undefined;
|
|
||||||
const postMidPointGap = shot
|
|
||||||
? postMidPointLayers!.reduce((accumulator, layer) => {
|
|
||||||
if (layer.type === null) return accumulator + layer.distance;
|
|
||||||
return accumulator;
|
|
||||||
}, 0) || LENGTH_INFINITY
|
|
||||||
: undefined;
|
|
||||||
const spacedBlockedHE =
|
|
||||||
shell.type === 'he' && postMidPointGap && postMidPointGap > 0;
|
|
||||||
|
|
||||||
useFrame(({ clock }) => {
|
|
||||||
if (!shot || !preMidPointTracer.current) return;
|
|
||||||
|
|
||||||
const t1 = clock.elapsedTime % 1;
|
|
||||||
const t2 = (clock.elapsedTime + 0.5) % 1;
|
|
||||||
|
|
||||||
preMidPointTracer.current.position.set(0, (1 - t1) * preMidPointGap!, 0);
|
|
||||||
preMidPointTracer.current.scale.set(1, 1 - 2 * Math.abs(t1 - 0.5), 1);
|
|
||||||
|
|
||||||
if (!postMidPointTracer.current) return;
|
|
||||||
|
|
||||||
postMidPointTracer.current.position.set(0, t2 * postMidPointGap!, 0);
|
|
||||||
postMidPointTracer.current.scale.set(1, 1 - 2 * Math.abs(t2 - 0.5), 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
function resetShot() {
|
|
||||||
mutateTankopediaTemporary((draft) => {
|
|
||||||
draft.shot = undefined;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const unsubscribes = [
|
|
||||||
useDuel.subscribe((state) => state.protagonist?.tank, resetShot),
|
|
||||||
useDuel.subscribe((state) => state.antagonist?.tank, resetShot),
|
|
||||||
];
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
unsubscribes.forEach((unsubscribe) => unsubscribe());
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!shot) return null;
|
if (!shot) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<group>
|
||||||
<group
|
|
||||||
position={midPointLayer.point}
|
|
||||||
rotation={normalToEuler(midPointLayer.shellNormal)}
|
|
||||||
>
|
|
||||||
<mesh position={[0, preMidPointGap! / 2, 0]}>
|
|
||||||
<cylinderGeometry args={[TRACER_THIN, TRACER_THIN, preMidPointGap]} />
|
|
||||||
<meshBasicMaterial depthTest={false} />
|
|
||||||
</mesh>
|
|
||||||
|
|
||||||
<mesh position={[0, preMidPointGap! / 2, 0]} ref={preMidPointTracer}>
|
|
||||||
<cylinderGeometry
|
|
||||||
args={[TRACER_THICK, TRACER_THICK, preMidPointGap]}
|
|
||||||
/>
|
|
||||||
<meshBasicMaterial
|
|
||||||
color={
|
|
||||||
lastLayer.status === 'blocked'
|
|
||||||
? 0xff4040
|
|
||||||
: lastLayer.status === 'penetration'
|
|
||||||
? 0x00ff00
|
|
||||||
: 0xffff00
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</mesh>
|
|
||||||
</group>
|
|
||||||
|
|
||||||
{midPointLayer.status === 'ricochet' && (
|
|
||||||
<group
|
|
||||||
position={midPointLayer.point}
|
|
||||||
rotation={normalToEuler(
|
|
||||||
midPointLayer.shellNormal
|
|
||||||
.clone()
|
|
||||||
.reflect(midPointLayer.surfaceNormal)
|
|
||||||
.multiplyScalar(-1),
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<mesh position={[0, postMidPointGap! / 2, 0]}>
|
|
||||||
<cylinderGeometry
|
|
||||||
args={[TRACER_THIN, TRACER_THIN, postMidPointGap]}
|
|
||||||
/>
|
|
||||||
<meshBasicMaterial depthTest={false} />
|
|
||||||
</mesh>
|
|
||||||
|
|
||||||
<mesh
|
|
||||||
position={[0, postMidPointGap! / 2, 0]}
|
|
||||||
ref={postMidPointTracer}
|
|
||||||
>
|
|
||||||
<cylinderGeometry
|
|
||||||
args={[TRACER_THICK, TRACER_THICK, postMidPointGap]}
|
|
||||||
/>
|
|
||||||
<meshBasicMaterial color={0xffff00} />
|
|
||||||
</mesh>
|
|
||||||
</group>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{shot.layersIn.map((layer, index) => {
|
|
||||||
if (layer.type === null) return;
|
|
||||||
|
|
||||||
const shellRotation = normalToEuler(layer.shellNormal);
|
|
||||||
const nextLayer = shot.layersIn[index + 1] as ShotLayerGap | undefined;
|
|
||||||
const trueStatus =
|
|
||||||
nextLayer?.status === 'blocked' ? 'blocked' : layer.status;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<group
|
|
||||||
key={`${index}-line`}
|
|
||||||
position={layer.point}
|
|
||||||
rotation={shellRotation}
|
|
||||||
>
|
|
||||||
<Html
|
|
||||||
center
|
|
||||||
style={{
|
|
||||||
pointerEvents: 'none',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
backgroundColor:
|
|
||||||
trueStatus === 'penetration'
|
|
||||||
? '#00ff0080'
|
|
||||||
: trueStatus === 'blocked'
|
|
||||||
? '#ff000080'
|
|
||||||
: '#ffff0080',
|
|
||||||
borderRadius: 8,
|
|
||||||
border: 'none',
|
|
||||||
padding: 4,
|
|
||||||
width: hasMultipleLayers ? undefined : 8,
|
|
||||||
height: hasMultipleLayers ? undefined : 8,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{hasMultipleLayers && (
|
|
||||||
<Text
|
|
||||||
size="2"
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
display: 'block',
|
|
||||||
textAlign: 'center',
|
|
||||||
color: trueStatus === 'ricochet' ? 'black' : undefined,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<b>{layer.index + 1}</b>
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</Html>
|
|
||||||
</group>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
<Html
|
<Html
|
||||||
position={midPointLayer.point}
|
position={shot.point}
|
||||||
style={{
|
style={{
|
||||||
transform: 'translateY(-50%)',
|
|
||||||
marginLeft: 32,
|
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Card style={{ width: 256 + 32 }}>
|
<Card ml="9" style={{ width: 300 }}>
|
||||||
<Flex direction="column">
|
<Flex direction="column" gap="1">
|
||||||
<Text>
|
<Text color={shotStatusColors[shot.status]} weight="bold">
|
||||||
{lastLayer.status[0].toUpperCase()}
|
{shot.status !== 'blocked' && (
|
||||||
{lastLayer.status.slice(1)}
|
<>
|
||||||
|
{Math.round(shot.damage).toLocaleString()}
|
||||||
{lastLayer.status === 'penetration' &&
|
<Text size="1">hp</Text>
|
||||||
!spacedBlockedHE &&
|
</>
|
||||||
`: ${Math.round(shell.damage.armor)}hp`}
|
)}
|
||||||
|
{` ${shotStatusNames[shot.status]} `}
|
||||||
{(lastLayer.status === 'blocked' || spacedBlockedHE) &&
|
|
||||||
shell.type === 'he' &&
|
|
||||||
`: ${NaNFallback(
|
|
||||||
Math.max(
|
|
||||||
0,
|
|
||||||
Math.round(
|
|
||||||
0.5 *
|
|
||||||
shell.damage.armor *
|
|
||||||
(1 -
|
|
||||||
distanceFromSpacedArmor! / shell.explosionRadius!) -
|
|
||||||
1.1 *
|
|
||||||
((midPointLayer as ShotLayerNonExternal)
|
|
||||||
.thicknessAngled +
|
|
||||||
preMidPointThickness!),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
0,
|
|
||||||
)}hp`}
|
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{shot.layersIn.map((layer, index) => {
|
<Flex direction="column">
|
||||||
if (layer.type === null && (!explosive || splashing)) return null;
|
{shot.layersIn.map((layer, index) => {
|
||||||
|
const layerName =
|
||||||
|
layer.type === ArmorType.External
|
||||||
|
? externalLayerNames[layer.variant]
|
||||||
|
: layerTypeNames[`${layer.type}`];
|
||||||
|
const layerIndex =
|
||||||
|
layer.type === null
|
||||||
|
? null
|
||||||
|
: `${(shot.containsGaps ? index / 2 : index) + 1}.`;
|
||||||
|
const shotStatusColor = shotStatusColors[layer.status];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex key={index} align="center" gap="2">
|
<Flex align="center" gap="2">
|
||||||
<Flex>
|
<Text
|
||||||
{hasMultipleLayers && (
|
size="1"
|
||||||
<Text size="2" style={{ width: 16 }}>
|
color="gray"
|
||||||
{layer.type === null ? null : `${layer.index + 1}.`}
|
style={{
|
||||||
|
width: 12,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{layerIndex}
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
size="2"
|
||||||
|
color={shotStatusColor}
|
||||||
|
style={{ width: 47 }}
|
||||||
|
>
|
||||||
|
{layerName}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{layer.type === null && (
|
||||||
|
<>
|
||||||
|
<Text
|
||||||
|
size="2"
|
||||||
|
color={shotStatusColor}
|
||||||
|
style={{
|
||||||
|
width: 64,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Math.round(layer.distance * 1000).toLocaleString()}
|
||||||
|
<Text size="1">mm</Text>
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text size="2" color="gray">
|
||||||
|
{Math.max(-100, Math.round(-50 * layer.distance))}%
|
||||||
|
penetration
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{layer.type === ArmorType.External && (
|
||||||
|
<Text
|
||||||
|
size="2"
|
||||||
|
color={shotStatusColor}
|
||||||
|
style={{
|
||||||
|
width: 64,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Math.round(layer.thickness).toLocaleString()}
|
||||||
|
<Text size="1">mm</Text>
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
<Text size="2">{layerTypeNames[`${layer.type}`]}</Text>
|
|
||||||
|
{(layer.type === ArmorType.Core ||
|
||||||
|
layer.type === ArmorType.Spaced) && (
|
||||||
|
<>
|
||||||
|
<Text
|
||||||
|
size="2"
|
||||||
|
color={shotStatusColor}
|
||||||
|
style={{
|
||||||
|
width: 64,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{Math.round(layer.thicknessAngled).toLocaleString()}
|
||||||
|
<Text size="1">mm</Text>
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text size="2" color="gray">
|
||||||
|
{Math.round(layer.thickness).toLocaleString()}
|
||||||
|
<Text size="1">mm</Text> nominal
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
);
|
||||||
{layer.type !== null && (
|
})}
|
||||||
<>
|
</Flex>
|
||||||
<Flex align="center" style={{ width: 64 }}>
|
|
||||||
<NominalPenetration width={16} height={16} />
|
|
||||||
<Text size="2">{Math.round(layer.thickness)}mm</Text>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
{layer.type !== ArmorType.External && (
|
|
||||||
<Flex align="center">
|
|
||||||
{layer.status === 'ricochet' ? (
|
|
||||||
<Ricochet width={16} height={16} />
|
|
||||||
) : layer.status === 'blocked' ? (
|
|
||||||
<Block width={16} height={16} />
|
|
||||||
) : (
|
|
||||||
<AngledPenetration width={16} height={16} />
|
|
||||||
)}
|
|
||||||
<Text size="2">
|
|
||||||
{Math.round(layer.thicknessAngled)}mm (
|
|
||||||
{Math.round(radToDeg(layer.angle))}°)
|
|
||||||
</Text>
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{layer.type === null && (
|
|
||||||
<Text size="2">
|
|
||||||
{Math.round(layer.distance * 1000)}mm (-
|
|
||||||
{Math.round(Math.min(100, layer.distance * 50))}%
|
|
||||||
penetration)
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Card>
|
</Card>
|
||||||
</Html>
|
</Html>
|
||||||
</>
|
</group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -93,14 +93,15 @@ export function SpacedArmorSceneComponent({
|
|||||||
async onClick(event) {
|
async onClick(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
|
const { antagonist, protagonist } = useDuel.getState();
|
||||||
|
const shell = antagonist!.shell;
|
||||||
const shot: Shot = {
|
const shot: Shot = {
|
||||||
status: 'blocked',
|
status: 'blocked',
|
||||||
damage: -1,
|
damage: -1,
|
||||||
|
containsGaps: shell.type === ShellType.HEAT,
|
||||||
layersIn: [],
|
layersIn: [],
|
||||||
point: event.point,
|
point: event.point,
|
||||||
};
|
};
|
||||||
const { antagonist, protagonist } = useDuel.getState();
|
|
||||||
const shell = antagonist!.shell;
|
|
||||||
const cameraNormal = camera.position
|
const cameraNormal = camera.position
|
||||||
.clone()
|
.clone()
|
||||||
.sub(event.point)
|
.sub(event.point)
|
||||||
@@ -185,6 +186,7 @@ export function SpacedArmorSceneComponent({
|
|||||||
point: intersection.point,
|
point: intersection.point,
|
||||||
surfaceNormal: intersection.face!.normal,
|
surfaceNormal: intersection.face!.normal,
|
||||||
status: blocked ? 'blocked' : 'penetration',
|
status: blocked ? 'blocked' : 'penetration',
|
||||||
|
variant: layer.variant,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const thickness = layer.thickness;
|
const thickness = layer.thickness;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { create } from 'zustand';
|
|||||||
import { persist, subscribeWithSelector } from 'zustand/middleware';
|
import { persist, subscribeWithSelector } from 'zustand/middleware';
|
||||||
import { ENVIRONMENTS } from '../app/tools/tankopedia/[id]/components/Lighting';
|
import { ENVIRONMENTS } from '../app/tools/tankopedia/[id]/components/Lighting';
|
||||||
import { ArmorType } from '../components/Armor/components/SpacedArmorScene';
|
import { ArmorType } from '../components/Armor/components/SpacedArmorScene';
|
||||||
|
import { ExternalModuleVariant } from '../components/Armor/components/SpacedArmorSceneComponent';
|
||||||
import { TankClass, TreeType } from '../components/Tanks';
|
import { TankClass, TreeType } from '../components/Tanks';
|
||||||
import { Tier } from '../core/blitzkit/tankDefinitions';
|
import { Tier } from '../core/blitzkit/tankDefinitions';
|
||||||
|
|
||||||
@@ -56,6 +57,7 @@ export interface ShotLayerBase {
|
|||||||
|
|
||||||
interface ShotLayerExternal extends ShotLayerBase {
|
interface ShotLayerExternal extends ShotLayerBase {
|
||||||
type: ArmorType.External;
|
type: ArmorType.External;
|
||||||
|
variant: ExternalModuleVariant;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ShotLayerNonExternal extends ShotLayerBase {
|
export interface ShotLayerNonExternal extends ShotLayerBase {
|
||||||
@@ -74,6 +76,7 @@ export type ShotLayer = ShotLayerExternal | ShotLayerNonExternal | ShotLayerGap;
|
|||||||
|
|
||||||
export type Shot = {
|
export type Shot = {
|
||||||
status: 'penetration' | 'blocked' | 'ricochet' | 'splash';
|
status: 'penetration' | 'blocked' | 'ricochet' | 'splash';
|
||||||
|
containsGaps: boolean;
|
||||||
damage: number;
|
damage: number;
|
||||||
point: Vector3;
|
point: Vector3;
|
||||||
layersIn: ShotLayer[];
|
layersIn: ShotLayer[];
|
||||||
|
|||||||
Reference in new issue
Block a user