mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-25 03:29:51 +01:00
feat: list
This commit is contained in:
1 parent
fa8922e0a6
commit
c0a3628b3b
3 files changed
+138
-35
No files matched your search
@@ -1,11 +1,19 @@
|
|||||||
import { Card, Inset, Text } from '@radix-ui/themes';
|
import { Card, Flex, Inset, Text } from '@radix-ui/themes';
|
||||||
import { Html } from '@react-three/drei';
|
import { Html } from '@react-three/drei';
|
||||||
import { Euler, Quaternion, Vector3 } from 'three';
|
import { Euler, Quaternion, Vector3 } from 'three';
|
||||||
|
import { radToDeg } from 'three/src/math/MathUtils';
|
||||||
import { J_HAT } from '../../../constants/axis';
|
import { J_HAT } from '../../../constants/axis';
|
||||||
|
import { isExplosive } from '../../../core/blitz/isExplosive';
|
||||||
|
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 {
|
import {
|
||||||
ShotLayerGap,
|
ShotLayerGap,
|
||||||
useTankopediaTemporary,
|
useTankopediaTemporary,
|
||||||
} from '../../../stores/tankopedia';
|
} from '../../../stores/tankopedia';
|
||||||
|
import { ArmorType } from './SpacedArmorScene';
|
||||||
|
|
||||||
// export const SHOT_NAMES: Record<Shot['type'], string> = {
|
// export const SHOT_NAMES: Record<Shot['type'], string> = {
|
||||||
// ricochet: 'Ricochet',
|
// ricochet: 'Ricochet',
|
||||||
@@ -13,12 +21,19 @@ import {
|
|||||||
// block: 'Blocked',
|
// block: 'Blocked',
|
||||||
// };
|
// };
|
||||||
|
|
||||||
// export const SHOT_ICONS: Record<Shot['type'], ReactNode> = {
|
// export const SHOT_ICONS: Record<ShotLayerBase[''], ReactNode> = {
|
||||||
// ricochet: <Ricochet width={24} height={24} />,
|
// ricochet: <Ricochet width={24} height={24} />,
|
||||||
// block: <Block width={24} height={24} />,
|
// block: <Block width={24} height={24} />,
|
||||||
// penetration: <NominalPenetration width={24} height={24} />,
|
// penetration: <NominalPenetration width={24} height={24} />,
|
||||||
// };
|
// };
|
||||||
|
|
||||||
|
const layerTypeNames: Record<ArmorType | 'null', string> = {
|
||||||
|
[ArmorType.Core]: 'Core',
|
||||||
|
[ArmorType.Spaced]: 'Spaced',
|
||||||
|
[ArmorType.External]: 'External',
|
||||||
|
null: 'Gap',
|
||||||
|
};
|
||||||
|
|
||||||
// const THICKNESS = 0.05;
|
// const THICKNESS = 0.05;
|
||||||
// const LENGTH = 4;
|
// const LENGTH = 4;
|
||||||
|
|
||||||
@@ -26,15 +41,15 @@ const LENGTH_INFINITY = 4;
|
|||||||
|
|
||||||
export function ShotDisplay() {
|
export function ShotDisplay() {
|
||||||
const shot = useTankopediaTemporary((state) => state.shot);
|
const shot = useTankopediaTemporary((state) => state.shot);
|
||||||
const hasMultipleLayers = shot?.some(
|
const hasMultipleLayers = shot?.layers.some(
|
||||||
(layer, index) => index !== 0 && layer.type !== null,
|
(layer, index) => index !== 0 && layer.type !== null,
|
||||||
);
|
);
|
||||||
|
const { shell } = useDuel.getState().antagonist!;
|
||||||
console.log(shot);
|
const explosive = isExplosive(shell.type);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{shot?.map((layer, index) => {
|
{shot?.layers.map((layer, index) => {
|
||||||
if (layer.type === null) return;
|
if (layer.type === null) return;
|
||||||
|
|
||||||
const shellRotation = new Euler().setFromQuaternion(
|
const shellRotation = new Euler().setFromQuaternion(
|
||||||
@@ -43,9 +58,9 @@ export function ShotDisplay() {
|
|||||||
J_HAT.angleTo(layer.shellNormal),
|
J_HAT.angleTo(layer.shellNormal),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const nextLayer = shot[index + 1] as ShotLayerGap | undefined;
|
const nextLayer = shot.layers[index + 1] as ShotLayerGap | undefined;
|
||||||
const trueStatus =
|
const trueStatus =
|
||||||
nextLayer?.status === 'wasted' ? 'blocked' : layer.status;
|
nextLayer?.status === 'blocked' ? 'blocked' : layer.status;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -54,7 +69,12 @@ export function ShotDisplay() {
|
|||||||
position={layer.point}
|
position={layer.point}
|
||||||
rotation={shellRotation}
|
rotation={shellRotation}
|
||||||
>
|
>
|
||||||
<Html center>
|
<Html
|
||||||
|
center
|
||||||
|
style={{
|
||||||
|
pointerEvents: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Card
|
<Card
|
||||||
style={{
|
style={{
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
@@ -86,7 +106,7 @@ export function ShotDisplay() {
|
|||||||
let length = LENGTH_INFINITY;
|
let length = LENGTH_INFINITY;
|
||||||
|
|
||||||
if (layer.index !== 0) {
|
if (layer.index !== 0) {
|
||||||
const gap = shot[index - 1] as ShotLayerGap;
|
const gap = shot.layers[index - 1] as ShotLayerGap;
|
||||||
length = gap.distance;
|
length = gap.distance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +120,10 @@ export function ShotDisplay() {
|
|||||||
</group>
|
</group>
|
||||||
|
|
||||||
{(() => {
|
{(() => {
|
||||||
if (layer.status !== 'ricochet' || index !== shot.length - 1)
|
if (
|
||||||
|
layer.status !== 'ricochet' ||
|
||||||
|
index !== shot.layers.length - 1
|
||||||
|
)
|
||||||
return null;
|
return null;
|
||||||
const ricochetShellNormal = layer.shellNormal
|
const ricochetShellNormal = layer.shellNormal
|
||||||
.clone()
|
.clone()
|
||||||
@@ -129,6 +152,88 @@ export function ShotDisplay() {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
{shot &&
|
||||||
|
(() => {
|
||||||
|
const lastLayer = shot.layers.at(-1)!;
|
||||||
|
const lastStatus = lastLayer.status;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Html
|
||||||
|
position={shot.point}
|
||||||
|
style={{
|
||||||
|
transform: 'translateY(-50%)',
|
||||||
|
marginLeft: 16,
|
||||||
|
pointerEvents: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Card style={{ width: 256 }}>
|
||||||
|
<Flex direction="column">
|
||||||
|
<Text>
|
||||||
|
{lastStatus[0].toUpperCase()}
|
||||||
|
{lastStatus.slice(1)}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{shot.layers.map((layer) => {
|
||||||
|
if (layer.type === null && !explosive) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex align="center" gap="2">
|
||||||
|
<Flex>
|
||||||
|
{hasMultipleLayers && (
|
||||||
|
<Text size="2" style={{ width: 16 }}>
|
||||||
|
{layer.type === null
|
||||||
|
? null
|
||||||
|
: `${layer.index + 1}.`}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
<Text size="2">
|
||||||
|
{layerTypeNames[`${layer.type}`]}
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{layer.type !== null && (
|
||||||
|
<>
|
||||||
|
<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>
|
||||||
|
</Card>
|
||||||
|
</Html>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,10 @@ export function SpacedArmorSceneComponent({
|
|||||||
? 1.1
|
? 1.1
|
||||||
: 1.05
|
: 1.05
|
||||||
: 1);
|
: 1);
|
||||||
const shot: Shot = []; // TODO: type this
|
const shot: Shot = {
|
||||||
|
layers: [],
|
||||||
|
point: event.point,
|
||||||
|
}; // TODO: type this
|
||||||
let remainingPenetration = penetration;
|
let remainingPenetration = penetration;
|
||||||
let penetrationChance = -1;
|
let penetrationChance = -1;
|
||||||
let splashChance = -1;
|
let splashChance = -1;
|
||||||
@@ -109,14 +112,8 @@ export function SpacedArmorSceneComponent({
|
|||||||
.clone()
|
.clone()
|
||||||
.sub(event.point)
|
.sub(event.point)
|
||||||
.normalize();
|
.normalize();
|
||||||
const cameraNormal = shellNormal.clone().multiplyScalar(-1);
|
|
||||||
|
|
||||||
pushLogs(
|
pushLogs(shellNormal, filterIntersections(event.intersections), 0);
|
||||||
shellNormal,
|
|
||||||
cameraNormal,
|
|
||||||
filterIntersections(event.intersections),
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
|
|
||||||
function filterIntersections(intersectionsRaw: Intersection[]) {
|
function filterIntersections(intersectionsRaw: Intersection[]) {
|
||||||
const intersectionsAll = intersectionsRaw.filter(
|
const intersectionsAll = intersectionsRaw.filter(
|
||||||
@@ -164,7 +161,6 @@ export function SpacedArmorSceneComponent({
|
|||||||
|
|
||||||
function pushLogs(
|
function pushLogs(
|
||||||
shellNormal: Vector3,
|
shellNormal: Vector3,
|
||||||
cameraNormal: Vector3,
|
|
||||||
intersections: ArmorMeshIntersection[],
|
intersections: ArmorMeshIntersection[],
|
||||||
startingIndex: number,
|
startingIndex: number,
|
||||||
) {
|
) {
|
||||||
@@ -173,15 +169,15 @@ export function SpacedArmorSceneComponent({
|
|||||||
|
|
||||||
if (layerIndex !== 0 && intersections.length > 0) {
|
if (layerIndex !== 0 && intersections.length > 0) {
|
||||||
const distance = (
|
const distance = (
|
||||||
shot.at(-1) as ShotLayerBase
|
shot.layers.at(-1) as ShotLayerBase
|
||||||
).point.distanceTo(intersections[0].point);
|
).point.distanceTo(intersections[0].point);
|
||||||
if (explosive)
|
if (explosive)
|
||||||
remainingPenetration -= 0.5 * remainingPenetration * distance;
|
remainingPenetration -= 0.5 * remainingPenetration * distance;
|
||||||
const wasted = remainingPenetration < 0;
|
const wasted = remainingPenetration < 0;
|
||||||
|
|
||||||
shot.push({
|
shot.layers.push({
|
||||||
type: null,
|
type: null,
|
||||||
status: wasted ? 'wasted' : 'nominal',
|
status: wasted ? 'blocked' : 'penetration',
|
||||||
distance,
|
distance,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -206,9 +202,9 @@ export function SpacedArmorSceneComponent({
|
|||||||
0.5 * remainingPenetration * distance;
|
0.5 * remainingPenetration * distance;
|
||||||
const wasted = remainingPenetration < 0;
|
const wasted = remainingPenetration < 0;
|
||||||
|
|
||||||
shot.push({
|
shot.layers.push({
|
||||||
type: null,
|
type: null,
|
||||||
status: wasted ? 'wasted' : 'nominal',
|
status: wasted ? 'blocked' : 'penetration',
|
||||||
distance,
|
distance,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -219,7 +215,7 @@ export function SpacedArmorSceneComponent({
|
|||||||
remainingPenetration -= thickness;
|
remainingPenetration -= thickness;
|
||||||
const blocked = remainingPenetration < 0;
|
const blocked = remainingPenetration < 0;
|
||||||
|
|
||||||
shot.push({
|
shot.layers.push({
|
||||||
type: ArmorType.External,
|
type: ArmorType.External,
|
||||||
index: layerIndex,
|
index: layerIndex,
|
||||||
shellNormal,
|
shellNormal,
|
||||||
@@ -242,11 +238,13 @@ export function SpacedArmorSceneComponent({
|
|||||||
? (1.4 * normalization * shell.caliber) / (2.0 * thickness)
|
? (1.4 * normalization * shell.caliber) / (2.0 * thickness)
|
||||||
: normalization;
|
: normalization;
|
||||||
const finalThickness =
|
const finalThickness =
|
||||||
thickness / Math.cos(angle - finalNormalization);
|
thickness === 0
|
||||||
|
? 0
|
||||||
|
: thickness / Math.cos(angle - finalNormalization);
|
||||||
remainingPenetration -= finalThickness;
|
remainingPenetration -= finalThickness;
|
||||||
|
|
||||||
if (!threeCalibersRule && angle >= ricochet) {
|
if (!threeCalibersRule && angle >= ricochet) {
|
||||||
shot.push({
|
shot.layers.push({
|
||||||
type,
|
type,
|
||||||
index: layerIndex,
|
index: layerIndex,
|
||||||
shellNormal,
|
shellNormal,
|
||||||
@@ -271,7 +269,6 @@ export function SpacedArmorSceneComponent({
|
|||||||
raycaster.set(intersection.point, newCameraNormal);
|
raycaster.set(intersection.point, newCameraNormal);
|
||||||
pushLogs(
|
pushLogs(
|
||||||
newShellNormal,
|
newShellNormal,
|
||||||
newCameraNormal,
|
|
||||||
filterIntersections(
|
filterIntersections(
|
||||||
raycaster.intersectObjects(scene.children, true),
|
raycaster.intersectObjects(scene.children, true),
|
||||||
),
|
),
|
||||||
@@ -281,7 +278,7 @@ export function SpacedArmorSceneComponent({
|
|||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
const blocked = remainingPenetration < 0;
|
const blocked = remainingPenetration < 0;
|
||||||
shot.push({
|
shot.layers.push({
|
||||||
type,
|
type,
|
||||||
index: layerIndex,
|
index: layerIndex,
|
||||||
shellNormal,
|
shellNormal,
|
||||||
@@ -300,8 +297,6 @@ export function SpacedArmorSceneComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('setting', shot);
|
|
||||||
|
|
||||||
useTankopediaTemporary.setState({ shot });
|
useTankopediaTemporary.setState({ shot });
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -65,13 +65,16 @@ interface ShotLayerNonExternal extends ShotLayerBase {
|
|||||||
|
|
||||||
export interface ShotLayerGap {
|
export interface ShotLayerGap {
|
||||||
type: null;
|
type: null;
|
||||||
status: 'nominal' | 'wasted';
|
status: 'penetration' | 'blocked';
|
||||||
distance: number;
|
distance: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShotLayer = ShotLayerExternal | ShotLayerNonExternal | ShotLayerGap;
|
export type ShotLayer = ShotLayerExternal | ShotLayerNonExternal | ShotLayerGap;
|
||||||
|
|
||||||
export type Shot = ShotLayer[];
|
export type Shot = {
|
||||||
|
point: Vector3;
|
||||||
|
layers: ShotLayer[];
|
||||||
|
};
|
||||||
|
|
||||||
export type ArmorPiercingLayer =
|
export type ArmorPiercingLayer =
|
||||||
| {
|
| {
|
||||||
|
|||||||
Reference in new issue
Block a user