mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02: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 { Euler, Quaternion, Vector3 } from 'three';
|
||||
import { radToDeg } from 'three/src/math/MathUtils';
|
||||
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 {
|
||||
ShotLayerGap,
|
||||
useTankopediaTemporary,
|
||||
} from '../../../stores/tankopedia';
|
||||
import { ArmorType } from './SpacedArmorScene';
|
||||
|
||||
// export const SHOT_NAMES: Record<Shot['type'], string> = {
|
||||
// ricochet: 'Ricochet',
|
||||
@@ -13,12 +21,19 @@ import {
|
||||
// block: 'Blocked',
|
||||
// };
|
||||
|
||||
// export const SHOT_ICONS: Record<Shot['type'], ReactNode> = {
|
||||
// export const SHOT_ICONS: Record<ShotLayerBase[''], ReactNode> = {
|
||||
// ricochet: <Ricochet width={24} height={24} />,
|
||||
// block: <Block 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 LENGTH = 4;
|
||||
|
||||
@@ -26,15 +41,15 @@ const LENGTH_INFINITY = 4;
|
||||
|
||||
export function ShotDisplay() {
|
||||
const shot = useTankopediaTemporary((state) => state.shot);
|
||||
const hasMultipleLayers = shot?.some(
|
||||
const hasMultipleLayers = shot?.layers.some(
|
||||
(layer, index) => index !== 0 && layer.type !== null,
|
||||
);
|
||||
|
||||
console.log(shot);
|
||||
const { shell } = useDuel.getState().antagonist!;
|
||||
const explosive = isExplosive(shell.type);
|
||||
|
||||
return (
|
||||
<>
|
||||
{shot?.map((layer, index) => {
|
||||
{shot?.layers.map((layer, index) => {
|
||||
if (layer.type === null) return;
|
||||
|
||||
const shellRotation = new Euler().setFromQuaternion(
|
||||
@@ -43,9 +58,9 @@ export function ShotDisplay() {
|
||||
J_HAT.angleTo(layer.shellNormal),
|
||||
),
|
||||
);
|
||||
const nextLayer = shot[index + 1] as ShotLayerGap | undefined;
|
||||
const nextLayer = shot.layers[index + 1] as ShotLayerGap | undefined;
|
||||
const trueStatus =
|
||||
nextLayer?.status === 'wasted' ? 'blocked' : layer.status;
|
||||
nextLayer?.status === 'blocked' ? 'blocked' : layer.status;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -54,7 +69,12 @@ export function ShotDisplay() {
|
||||
position={layer.point}
|
||||
rotation={shellRotation}
|
||||
>
|
||||
<Html center>
|
||||
<Html
|
||||
center
|
||||
style={{
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
>
|
||||
<Card
|
||||
style={{
|
||||
backgroundColor:
|
||||
@@ -86,7 +106,7 @@ export function ShotDisplay() {
|
||||
let length = LENGTH_INFINITY;
|
||||
|
||||
if (layer.index !== 0) {
|
||||
const gap = shot[index - 1] as ShotLayerGap;
|
||||
const gap = shot.layers[index - 1] as ShotLayerGap;
|
||||
length = gap.distance;
|
||||
}
|
||||
|
||||
@@ -100,7 +120,10 @@ export function ShotDisplay() {
|
||||
</group>
|
||||
|
||||
{(() => {
|
||||
if (layer.status !== 'ricochet' || index !== shot.length - 1)
|
||||
if (
|
||||
layer.status !== 'ricochet' ||
|
||||
index !== shot.layers.length - 1
|
||||
)
|
||||
return null;
|
||||
const ricochetShellNormal = layer.shellNormal
|
||||
.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.05
|
||||
: 1);
|
||||
const shot: Shot = []; // TODO: type this
|
||||
const shot: Shot = {
|
||||
layers: [],
|
||||
point: event.point,
|
||||
}; // TODO: type this
|
||||
let remainingPenetration = penetration;
|
||||
let penetrationChance = -1;
|
||||
let splashChance = -1;
|
||||
@@ -109,14 +112,8 @@ export function SpacedArmorSceneComponent({
|
||||
.clone()
|
||||
.sub(event.point)
|
||||
.normalize();
|
||||
const cameraNormal = shellNormal.clone().multiplyScalar(-1);
|
||||
|
||||
pushLogs(
|
||||
shellNormal,
|
||||
cameraNormal,
|
||||
filterIntersections(event.intersections),
|
||||
0,
|
||||
);
|
||||
pushLogs(shellNormal, filterIntersections(event.intersections), 0);
|
||||
|
||||
function filterIntersections(intersectionsRaw: Intersection[]) {
|
||||
const intersectionsAll = intersectionsRaw.filter(
|
||||
@@ -164,7 +161,6 @@ export function SpacedArmorSceneComponent({
|
||||
|
||||
function pushLogs(
|
||||
shellNormal: Vector3,
|
||||
cameraNormal: Vector3,
|
||||
intersections: ArmorMeshIntersection[],
|
||||
startingIndex: number,
|
||||
) {
|
||||
@@ -173,15 +169,15 @@ export function SpacedArmorSceneComponent({
|
||||
|
||||
if (layerIndex !== 0 && intersections.length > 0) {
|
||||
const distance = (
|
||||
shot.at(-1) as ShotLayerBase
|
||||
shot.layers.at(-1) as ShotLayerBase
|
||||
).point.distanceTo(intersections[0].point);
|
||||
if (explosive)
|
||||
remainingPenetration -= 0.5 * remainingPenetration * distance;
|
||||
const wasted = remainingPenetration < 0;
|
||||
|
||||
shot.push({
|
||||
shot.layers.push({
|
||||
type: null,
|
||||
status: wasted ? 'wasted' : 'nominal',
|
||||
status: wasted ? 'blocked' : 'penetration',
|
||||
distance,
|
||||
});
|
||||
|
||||
@@ -206,9 +202,9 @@ export function SpacedArmorSceneComponent({
|
||||
0.5 * remainingPenetration * distance;
|
||||
const wasted = remainingPenetration < 0;
|
||||
|
||||
shot.push({
|
||||
shot.layers.push({
|
||||
type: null,
|
||||
status: wasted ? 'wasted' : 'nominal',
|
||||
status: wasted ? 'blocked' : 'penetration',
|
||||
distance,
|
||||
});
|
||||
|
||||
@@ -219,7 +215,7 @@ export function SpacedArmorSceneComponent({
|
||||
remainingPenetration -= thickness;
|
||||
const blocked = remainingPenetration < 0;
|
||||
|
||||
shot.push({
|
||||
shot.layers.push({
|
||||
type: ArmorType.External,
|
||||
index: layerIndex,
|
||||
shellNormal,
|
||||
@@ -242,11 +238,13 @@ export function SpacedArmorSceneComponent({
|
||||
? (1.4 * normalization * shell.caliber) / (2.0 * thickness)
|
||||
: normalization;
|
||||
const finalThickness =
|
||||
thickness / Math.cos(angle - finalNormalization);
|
||||
thickness === 0
|
||||
? 0
|
||||
: thickness / Math.cos(angle - finalNormalization);
|
||||
remainingPenetration -= finalThickness;
|
||||
|
||||
if (!threeCalibersRule && angle >= ricochet) {
|
||||
shot.push({
|
||||
shot.layers.push({
|
||||
type,
|
||||
index: layerIndex,
|
||||
shellNormal,
|
||||
@@ -271,7 +269,6 @@ export function SpacedArmorSceneComponent({
|
||||
raycaster.set(intersection.point, newCameraNormal);
|
||||
pushLogs(
|
||||
newShellNormal,
|
||||
newCameraNormal,
|
||||
filterIntersections(
|
||||
raycaster.intersectObjects(scene.children, true),
|
||||
),
|
||||
@@ -281,7 +278,7 @@ export function SpacedArmorSceneComponent({
|
||||
break;
|
||||
} else {
|
||||
const blocked = remainingPenetration < 0;
|
||||
shot.push({
|
||||
shot.layers.push({
|
||||
type,
|
||||
index: layerIndex,
|
||||
shellNormal,
|
||||
@@ -300,8 +297,6 @@ export function SpacedArmorSceneComponent({
|
||||
}
|
||||
}
|
||||
|
||||
console.log('setting', shot);
|
||||
|
||||
useTankopediaTemporary.setState({ shot });
|
||||
},
|
||||
})}
|
||||
|
||||
@@ -65,13 +65,16 @@ interface ShotLayerNonExternal extends ShotLayerBase {
|
||||
|
||||
export interface ShotLayerGap {
|
||||
type: null;
|
||||
status: 'nominal' | 'wasted';
|
||||
status: 'penetration' | 'blocked';
|
||||
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 =
|
||||
| {
|
||||
|
||||
Reference in new issue
Block a user