mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
feat: start rewrite on onClick armor
This commit is contained in:
1 parent
82933aaf1e
commit
542145467c
10 files changed
+476
-282
No files matched your search
@@ -13,6 +13,7 @@ import { Fragment, use, useEffect, useRef, useState } from 'react';
|
||||
import { lerp } from 'three/src/math/MathUtils';
|
||||
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
|
||||
import { isExplosive } from '../../../../../../core/blitz/isExplosive';
|
||||
import { resolvePenetrationCoefficient } from '../../../../../../core/blitz/resolvePenetrationCoefficient';
|
||||
import { asset } from '../../../../../../core/blitzkit/asset';
|
||||
import { coefficient } from '../../../../../../core/blitzkit/coefficient';
|
||||
import { equipmentDefinitions } from '../../../../../../core/blitzkit/equipmentDefinitions';
|
||||
@@ -101,13 +102,7 @@ export function Characteristics() {
|
||||
const hasImprovedVerticalStabilizer = useEquipment(122);
|
||||
const penetrationCoefficient = coefficient([
|
||||
hasCalibratedShells,
|
||||
shell.type === 'ap'
|
||||
? 0.08
|
||||
: shell.type === 'ap_cr'
|
||||
? 0.05
|
||||
: shell.type === 'hc'
|
||||
? 0.13
|
||||
: 0.08,
|
||||
resolvePenetrationCoefficient(true, shell.type) - 1,
|
||||
]);
|
||||
const penetrationLossOverDistanceCoefficient = coefficient([
|
||||
hasSupercharger,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { degToRad } from 'three/src/math/MathUtils';
|
||||
import { canSplash } from '../../../../core/blitz/canSplash';
|
||||
import { isExplosive } from '../../../../core/blitz/isExplosive';
|
||||
import { resolveNearPenetration } from '../../../../core/blitz/resolveNearPenetration';
|
||||
import { resolvePenetrationCoefficient } from '../../../../core/blitz/resolvePenetrationCoefficient';
|
||||
import { hasEquipment } from '../../../../core/blitzkit/hasEquipment';
|
||||
import { jsxTree } from '../../../../core/blitzkit/jsxTree';
|
||||
import { ShellDefinition } from '../../../../core/blitzkit/tankDefinitions';
|
||||
@@ -98,25 +99,17 @@ export function CoreArmorSceneComponent({
|
||||
}
|
||||
async function handleAntagonistEquipmentChange(equipment: EquipmentMatrix) {
|
||||
const duel = useDuel.getState();
|
||||
const penetration = resolveNearPenetration(
|
||||
duel.antagonist!.shell.penetration,
|
||||
);
|
||||
const shell = duel.antagonist!.shell;
|
||||
const penetration = resolveNearPenetration(shell.penetration);
|
||||
const hasCalibratedShells = await hasEquipment(
|
||||
103,
|
||||
duel.antagonist!.tank.equipment,
|
||||
equipment,
|
||||
);
|
||||
|
||||
material.uniforms.penetration.value = hasCalibratedShells
|
||||
? penetration *
|
||||
(duel.antagonist!.shell.type === 'ap'
|
||||
? 1.08
|
||||
: duel.antagonist!.shell.type === 'ap_cr'
|
||||
? 1.05
|
||||
: duel.antagonist!.shell.type === 'hc'
|
||||
? 1.13
|
||||
: 1.08)
|
||||
: penetration;
|
||||
material.uniforms.penetration.value =
|
||||
penetration *
|
||||
resolvePenetrationCoefficient(hasCalibratedShells, shell.type);
|
||||
}
|
||||
|
||||
handleShellChange(useDuel.getState().antagonist!.shell);
|
||||
|
||||
@@ -35,30 +35,33 @@ const TRACER_THICK = 1 / 64;
|
||||
const TRACER_THIN = TRACER_THICK / 2;
|
||||
|
||||
export function ShotDisplay() {
|
||||
return null;
|
||||
|
||||
const shot = useTankopediaTemporary((state) => state.shot);
|
||||
const preMidPointTracer = useRef<Mesh>(null);
|
||||
const postMidPointTracer = useRef<Mesh>(null);
|
||||
const hasMultipleLayers = shot?.layers.some(
|
||||
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?.layers.findLast(
|
||||
const midPointLayer = (shot?.layersIn.findLast(
|
||||
(layer) => layer.type !== null && layer.status === 'ricochet',
|
||||
) ?? shot?.layers.findLast((layer) => layer.type !== null)) as ShotLayerBase;
|
||||
const firstLayer = shot?.layers[0] as Exclude<ShotLayer, ShotLayerGap>;
|
||||
const lastLayer = shot?.layers.at(-1)!;
|
||||
) ??
|
||||
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?.layers.slice(
|
||||
const preMidPointLayers = shot?.layersIn.slice(
|
||||
0,
|
||||
shot.layers.indexOf(midPointLayer as ShotLayer),
|
||||
shot.layersIn.indexOf(midPointLayer as ShotLayer),
|
||||
);
|
||||
const postMidPointLayers = shot?.layers.slice(
|
||||
shot.layers.indexOf(midPointLayer as ShotLayer),
|
||||
shot.layers.length,
|
||||
const postMidPointLayers = shot?.layersIn.slice(
|
||||
shot.layersIn.indexOf(midPointLayer as ShotLayer),
|
||||
shot.layersIn.length,
|
||||
);
|
||||
const preMidPointGap = shot
|
||||
? preMidPointLayers!.reduce((accumulator, layer) => {
|
||||
@@ -84,6 +87,8 @@ export function ShotDisplay() {
|
||||
return accumulator;
|
||||
}, 0) || LENGTH_INFINITY
|
||||
: undefined;
|
||||
const spacedBlockedHE =
|
||||
shell.type === 'he' && postMidPointGap && postMidPointGap > 0;
|
||||
|
||||
useFrame(({ clock }) => {
|
||||
if (!shot || !preMidPointTracer.current) return;
|
||||
@@ -175,11 +180,11 @@ export function ShotDisplay() {
|
||||
</group>
|
||||
)}
|
||||
|
||||
{shot.layers.map((layer, index) => {
|
||||
{shot.layersIn.map((layer, index) => {
|
||||
if (layer.type === null) return;
|
||||
|
||||
const shellRotation = normalToEuler(layer.shellNormal);
|
||||
const nextLayer = shot.layers[index + 1] as ShotLayerGap | undefined;
|
||||
const nextLayer = shot.layersIn[index + 1] as ShotLayerGap | undefined;
|
||||
const trueStatus =
|
||||
nextLayer?.status === 'blocked' ? 'blocked' : layer.status;
|
||||
|
||||
@@ -247,9 +252,12 @@ export function ShotDisplay() {
|
||||
<Text>
|
||||
{lastLayer.status[0].toUpperCase()}
|
||||
{lastLayer.status.slice(1)}
|
||||
|
||||
{lastLayer.status === 'penetration' &&
|
||||
!spacedBlockedHE &&
|
||||
`: ${Math.round(shell.damage.armor)}hp`}
|
||||
{lastLayer.status === 'blocked' &&
|
||||
|
||||
{(lastLayer.status === 'blocked' || spacedBlockedHE) &&
|
||||
shell.type === 'he' &&
|
||||
`: ${NaNFallback(
|
||||
Math.max(
|
||||
@@ -269,7 +277,7 @@ export function ShotDisplay() {
|
||||
)}hp`}
|
||||
</Text>
|
||||
|
||||
{shot.layers.map((layer, index) => {
|
||||
{shot.layersIn.map((layer, index) => {
|
||||
if (layer.type === null && (!explosive || splashing)) return null;
|
||||
|
||||
return (
|
||||
|
||||
+7
-13
@@ -8,6 +8,7 @@ import {
|
||||
} from 'three';
|
||||
import { ArmorUserData, ExternalModuleVariant } from '../..';
|
||||
import { resolveNearPenetration } from '../../../../../../core/blitz/resolveNearPenetration';
|
||||
import { resolvePenetrationCoefficient } from '../../../../../../core/blitz/resolvePenetrationCoefficient';
|
||||
import { hasEquipment } from '../../../../../../core/blitzkit/hasEquipment';
|
||||
import { jsxTree } from '../../../../../../core/blitzkit/jsxTree';
|
||||
import { ShellDefinition } from '../../../../../../core/blitzkit/tankDefinitions';
|
||||
@@ -66,24 +67,17 @@ export function SpacedArmorSubExternal({
|
||||
}
|
||||
async function handleAntagonistEquipmentChange(equipment: EquipmentMatrix) {
|
||||
const duel = useDuel.getState();
|
||||
const penetration = resolveNearPenetration(
|
||||
duel.antagonist!.shell.penetration,
|
||||
);
|
||||
const shell = duel.antagonist!.shell;
|
||||
const penetration = resolveNearPenetration(shell.penetration);
|
||||
const hasCalibratedShells = await hasEquipment(
|
||||
103,
|
||||
duel.antagonist!.tank.equipment,
|
||||
equipment,
|
||||
);
|
||||
material.uniforms.penetration.value = hasCalibratedShells
|
||||
? penetration *
|
||||
(duel.antagonist!.shell.type === 'ap'
|
||||
? 1.08
|
||||
: duel.antagonist!.shell.type === 'ap_cr'
|
||||
? 1.05
|
||||
: duel.antagonist!.shell.type === 'hc'
|
||||
? 1.13
|
||||
: 1.08)
|
||||
: penetration;
|
||||
|
||||
material.uniforms.penetration.value =
|
||||
penetration *
|
||||
resolvePenetrationCoefficient(hasCalibratedShells, shell.type);
|
||||
}
|
||||
|
||||
handleShellChange(useDuel.getState().antagonist!.shell);
|
||||
|
||||
+4
-10
@@ -16,6 +16,8 @@ import { EquipmentMatrix, useDuel } from '../../../../../../stores/duel';
|
||||
import { ArmorType } from '../../../SpacedArmorScene';
|
||||
import fragmentShader from './shaders/fragment.glsl';
|
||||
import vertexShader from './shaders/vertex.glsl';
|
||||
import { resolvePenetrationCoefficient } from '../../../../../../core/blitz/resolvePenetrationCoefficient';
|
||||
import { hasColorSpace } from '@react-three/fiber/dist/declarations/src/core/utils';
|
||||
|
||||
interface SpacedArmorSubSpacedProps {
|
||||
node: Object3D;
|
||||
@@ -83,16 +85,8 @@ export function SpacedArmorSubSpaced({
|
||||
duel.antagonist!.tank.equipment,
|
||||
equipment,
|
||||
);
|
||||
material.uniforms.penetration.value = hasCalibratedShells
|
||||
? penetration *
|
||||
(duel.antagonist!.shell.type === 'ap'
|
||||
? 1.08
|
||||
: duel.antagonist!.shell.type === 'ap_cr'
|
||||
? 1.05
|
||||
: duel.antagonist!.shell.type === 'hc'
|
||||
? 1.13
|
||||
: 1.08)
|
||||
: penetration;
|
||||
|
||||
material.uniforms.penetration.value=penetration*resolvePenetrationCoefficient(hasCalibratedShells, shell.type)
|
||||
}
|
||||
|
||||
handleShellChange(useDuel.getState().antagonist!.shell);
|
||||
|
||||
@@ -5,21 +5,19 @@ import {
|
||||
Object3D,
|
||||
Plane,
|
||||
Quaternion,
|
||||
Raycaster,
|
||||
Scene,
|
||||
Vector3,
|
||||
} from 'three';
|
||||
import { degToRad } from 'three/src/math/MathUtils';
|
||||
import { canSplash } from '../../../../core/blitz/canSplash';
|
||||
import { isExplosive } from '../../../../core/blitz/isExplosive';
|
||||
import { resolveNearPenetration } from '../../../../core/blitz/resolveNearPenetration';
|
||||
import { resolvePenetrationCoefficient } from '../../../../core/blitz/resolvePenetrationCoefficient';
|
||||
import { hasEquipment } from '../../../../core/blitzkit/hasEquipment';
|
||||
import { jsxTree } from '../../../../core/blitzkit/jsxTree';
|
||||
import { ShellType } from '../../../../core/blitzkit/tankDefinitions';
|
||||
import { useDuel } from '../../../../stores/duel';
|
||||
import {
|
||||
Shot,
|
||||
ShotLayerBase,
|
||||
useTankopediaPersistent,
|
||||
useTankopediaTemporary,
|
||||
} from '../../../../stores/tankopedia';
|
||||
import { ArmorType } from '../SpacedArmorScene';
|
||||
@@ -94,254 +92,448 @@ export function SpacedArmorSceneComponent({
|
||||
async onClick(event) {
|
||||
event.stopPropagation();
|
||||
|
||||
const duel = useDuel.getState();
|
||||
const hasCalibratedShells = await hasEquipment(
|
||||
103,
|
||||
duel.antagonist!.tank.equipment,
|
||||
duel.antagonist!.equipmentMatrix,
|
||||
);
|
||||
const hasEnhancedArmor = await hasEquipment(
|
||||
110,
|
||||
duel.protagonist!.tank.equipment,
|
||||
duel.protagonist!.equipmentMatrix,
|
||||
);
|
||||
const thicknessCoefficient = hasEnhancedArmor ? 1.03 : 1;
|
||||
const shell = useDuel.getState().antagonist!.shell;
|
||||
const explosive = isExplosive(shell.type);
|
||||
const splashing = canSplash(shell.type);
|
||||
const penetration =
|
||||
resolveNearPenetration(shell.penetration) *
|
||||
(hasCalibratedShells
|
||||
? duel.antagonist!.shell.type === 'ap'
|
||||
? 1.08
|
||||
: duel.antagonist!.shell.type === 'ap_cr'
|
||||
? 1.05
|
||||
: duel.antagonist!.shell.type === 'hc'
|
||||
? 1.13
|
||||
: 1.08
|
||||
: 1);
|
||||
const shot: Shot = {
|
||||
layers: [],
|
||||
status: 'blocked',
|
||||
damage: -1,
|
||||
layersIn: [],
|
||||
point: event.point,
|
||||
}; // TODO: type this
|
||||
let remainingPenetration = penetration;
|
||||
|
||||
const shellNormal = camera.position
|
||||
};
|
||||
const { antagonist, protagonist } = useDuel.getState();
|
||||
const shell = antagonist!.shell;
|
||||
const cameraNormal = camera.position
|
||||
.clone()
|
||||
.sub(event.point)
|
||||
.normalize();
|
||||
|
||||
pushLogs(
|
||||
shellNormal,
|
||||
filterIntersections(event.intersections),
|
||||
0,
|
||||
const hasCalibratedShells = await hasEquipment(
|
||||
103,
|
||||
protagonist!.tank.equipment,
|
||||
protagonist!.equipmentMatrix,
|
||||
);
|
||||
const hasEnhancedArmor = await hasEquipment(
|
||||
110,
|
||||
antagonist!.tank.equipment,
|
||||
antagonist!.equipmentMatrix,
|
||||
);
|
||||
const penetration =
|
||||
resolveNearPenetration(shell.penetration) *
|
||||
resolvePenetrationCoefficient(hasCalibratedShells, shell.type);
|
||||
const thicknessCoefficient = hasEnhancedArmor ? 1.03 : 1;
|
||||
const filteredIntersections = event.intersections.filter(
|
||||
(intersection) => 'type' in intersection.object.userData,
|
||||
) as unknown as Intersection<
|
||||
Object3D & { userData: ArmorUserData }
|
||||
>[];
|
||||
const encounteredExternalModuleVariants: ExternalModuleVariant[] =
|
||||
[];
|
||||
const noDuplicateIntersections: typeof filteredIntersections = [];
|
||||
|
||||
function filterIntersections(intersectionsRaw: Intersection[]) {
|
||||
const showSpacedArmor =
|
||||
useTankopediaPersistent.getState().model.visual
|
||||
.showSpacedArmor;
|
||||
const intersectionsTyped = intersectionsRaw.filter(
|
||||
({ object }) => object.userData.type !== undefined,
|
||||
) as ArmorMeshIntersection[];
|
||||
const intersectionsFiltered = intersectionsTyped.filter(
|
||||
({ object }) =>
|
||||
showSpacedArmor
|
||||
? true
|
||||
: object.userData.type === ArmorType.Core,
|
||||
);
|
||||
const intersectionsWithDuplicates = intersectionsFiltered.slice(
|
||||
0,
|
||||
intersectionsFiltered.findIndex(
|
||||
({ object }) => object.userData.type === ArmorType.Core,
|
||||
) + 1,
|
||||
);
|
||||
const firstGunIntersectionIndex =
|
||||
intersectionsWithDuplicates.findIndex(
|
||||
({ object }) =>
|
||||
object.userData.type === ArmorType.External &&
|
||||
object.userData.variant === 'gun',
|
||||
);
|
||||
const firstTrackIntersectionIndex =
|
||||
intersectionsWithDuplicates.findIndex(
|
||||
({ object }) =>
|
||||
object.userData.type === ArmorType.External &&
|
||||
object.userData.variant === 'track',
|
||||
);
|
||||
let intersections = intersectionsWithDuplicates;
|
||||
for (const intersection of filteredIntersections) {
|
||||
if (intersection.object.userData.type === ArmorType.External) {
|
||||
if (
|
||||
!encounteredExternalModuleVariants.includes(
|
||||
intersection.object.userData.variant,
|
||||
)
|
||||
) {
|
||||
noDuplicateIntersections.push(intersection);
|
||||
encounteredExternalModuleVariants.push(
|
||||
intersection.object.userData.variant,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
noDuplicateIntersections.push(intersection);
|
||||
|
||||
if (firstGunIntersectionIndex !== -1) {
|
||||
intersections = intersections.filter(({ object }, index) => {
|
||||
return object.userData.type === ArmorType.External &&
|
||||
object.userData.variant === 'gun'
|
||||
? index === firstGunIntersectionIndex
|
||||
: true;
|
||||
});
|
||||
if (intersection.object.userData.type === ArmorType.Core) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (firstTrackIntersectionIndex !== -1) {
|
||||
intersections = intersections.filter(({ object }, index) => {
|
||||
return object.userData.type === ArmorType.External &&
|
||||
object.userData.variant === 'track'
|
||||
? index === firstTrackIntersectionIndex
|
||||
: true;
|
||||
});
|
||||
}
|
||||
|
||||
return intersections;
|
||||
}
|
||||
|
||||
function pushLogs(
|
||||
shellNormal: Vector3,
|
||||
intersections: ArmorMeshIntersection[],
|
||||
startingIndex: number,
|
||||
) {
|
||||
let layerIndex = startingIndex;
|
||||
let loopIndex = 0;
|
||||
let remainingPenetration = penetration;
|
||||
let index = 0;
|
||||
for (const intersection of noDuplicateIntersections) {
|
||||
const layer = intersection.object.userData;
|
||||
|
||||
if (layerIndex !== 0 && intersections.length > 0) {
|
||||
const distance = (
|
||||
shot.layers.at(-1) as ShotLayerBase
|
||||
).point.distanceTo(intersections[0].point);
|
||||
if (explosive && !splashing)
|
||||
remainingPenetration -=
|
||||
0.5 * remainingPenetration * distance;
|
||||
const blocked = remainingPenetration < 0;
|
||||
if (shell.type === ShellType.HEAT && index > 0) {
|
||||
const lastLayer = shot.layersIn.at(-1) as ShotLayerBase;
|
||||
const distance = lastLayer.point.distanceTo(
|
||||
intersection.point,
|
||||
);
|
||||
remainingPenetration -= 0.5 * remainingPenetration * distance;
|
||||
const blocked = remainingPenetration <= 0;
|
||||
|
||||
shot.layers.push({
|
||||
shot.layersIn.push({
|
||||
type: null,
|
||||
status: blocked ? 'blocked' : 'penetration',
|
||||
distance,
|
||||
status: blocked ? 'blocked' : 'penetration',
|
||||
});
|
||||
|
||||
if (blocked) return;
|
||||
if (blocked) break;
|
||||
}
|
||||
|
||||
for (const intersection of intersections) {
|
||||
if (layer.type === ArmorType.External) {
|
||||
const thickness = layer.thickness * thicknessCoefficient;
|
||||
remainingPenetration -= thickness;
|
||||
const blocked =
|
||||
remainingPenetration <= 0 || shell.type === ShellType.HE;
|
||||
|
||||
shot.layersIn.push({
|
||||
type: ArmorType.External,
|
||||
index,
|
||||
thickness,
|
||||
point: intersection.point,
|
||||
surfaceNormal: intersection.face!.normal,
|
||||
status: blocked ? 'blocked' : 'penetration',
|
||||
});
|
||||
} else {
|
||||
const ricochet = degToRad(
|
||||
isExplosive(shell.type) ? 90 : shell.ricochet!,
|
||||
);
|
||||
const surfaceNormal = intersection
|
||||
.normal!.clone()
|
||||
.applyQuaternion(
|
||||
event.object.getWorldQuaternion(new Quaternion()),
|
||||
);
|
||||
const thickness =
|
||||
thicknessCoefficient *
|
||||
intersection.object.userData.thickness;
|
||||
const angle = surfaceNormal.angleTo(cameraNormal);
|
||||
const threeCalibersRule =
|
||||
shell.caliber > thickness * 3 || index > 0;
|
||||
const twoCalibersRule = shell.caliber > thickness * 2;
|
||||
const normalization = degToRad(shell.normalization ?? 0);
|
||||
const finalNormalization = twoCalibersRule
|
||||
? (1.4 * normalization * shell.caliber) / (2.0 * thickness)
|
||||
: normalization;
|
||||
const finalThickness =
|
||||
thickness /
|
||||
Math.cos(Math.max(0, angle - finalNormalization));
|
||||
|
||||
if (layerIndex !== 0 && startingIndex === 0) {
|
||||
const previousIntersection = intersections[layerIndex - 1];
|
||||
const distance =
|
||||
intersection.distance - previousIntersection.distance;
|
||||
if (explosive && !splashing)
|
||||
remainingPenetration -=
|
||||
0.5 * remainingPenetration * distance;
|
||||
const wasted = remainingPenetration < 0;
|
||||
if (!threeCalibersRule && angle >= ricochet) {
|
||||
remainingPenetration *= 0.75;
|
||||
|
||||
shot.layers.push({
|
||||
type: null,
|
||||
status: wasted ? 'blocked' : 'penetration',
|
||||
distance,
|
||||
});
|
||||
|
||||
if (wasted && !splashing) return;
|
||||
}
|
||||
|
||||
if (
|
||||
intersection.object.userData.type === ArmorType.External
|
||||
) {
|
||||
remainingPenetration -= thickness;
|
||||
const blocked = remainingPenetration < 0;
|
||||
|
||||
shot.layers.push({
|
||||
type: ArmorType.External,
|
||||
index: layerIndex,
|
||||
shellNormal,
|
||||
surfaceNormal,
|
||||
shot.layersIn.push({
|
||||
type: layer.type,
|
||||
index,
|
||||
thickness,
|
||||
thicknessAngled: finalThickness,
|
||||
point: intersection.point,
|
||||
thickness: intersection.object.userData.thickness,
|
||||
status: blocked ? 'blocked' : 'penetration',
|
||||
surfaceNormal: intersection.face!.normal,
|
||||
status: 'ricochet',
|
||||
angle,
|
||||
});
|
||||
|
||||
if (splashing) {
|
||||
remainingPenetration = Math.max(0, remainingPenetration);
|
||||
} else if (blocked) {
|
||||
break;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
const type = intersection.object.userData.type;
|
||||
const angle = surfaceNormal.angleTo(shellNormal);
|
||||
const ricochet = degToRad(
|
||||
isExplosive(shell.type) ? 90 : shell.ricochet!,
|
||||
);
|
||||
const normalization = degToRad(shell.normalization ?? 0);
|
||||
const threeCalibersRule =
|
||||
shell.caliber > thickness * 3 || layerIndex > 0;
|
||||
const twoCalibersRule = shell.caliber > thickness * 2;
|
||||
const finalNormalization = twoCalibersRule
|
||||
? (1.4 * normalization * shell.caliber) /
|
||||
(2.0 * thickness)
|
||||
: normalization;
|
||||
const finalThickness =
|
||||
thickness === 0
|
||||
? 0
|
||||
: thickness /
|
||||
Math.cos(Math.max(0, angle - finalNormalization));
|
||||
remainingPenetration -= finalThickness;
|
||||
const blocked = remainingPenetration <= 0;
|
||||
|
||||
if (!threeCalibersRule && angle >= ricochet) {
|
||||
shot.layers.push({
|
||||
type,
|
||||
index: layerIndex,
|
||||
shellNormal,
|
||||
surfaceNormal,
|
||||
point: intersection.point,
|
||||
thickness,
|
||||
thicknessAngled: finalThickness,
|
||||
angle,
|
||||
status: 'ricochet',
|
||||
});
|
||||
|
||||
remainingPenetration *= 0.75;
|
||||
|
||||
const raycaster = new Raycaster();
|
||||
const newShellNormal = shellNormal
|
||||
.clone()
|
||||
.reflect(surfaceNormal);
|
||||
const newCameraNormal = newShellNormal
|
||||
.clone()
|
||||
.multiplyScalar(-1);
|
||||
|
||||
raycaster.set(intersection.point, newCameraNormal);
|
||||
pushLogs(
|
||||
newShellNormal,
|
||||
filterIntersections(
|
||||
raycaster.intersectObjects(scene.children, true),
|
||||
),
|
||||
layerIndex + 1,
|
||||
);
|
||||
|
||||
break;
|
||||
} else {
|
||||
remainingPenetration -= finalThickness;
|
||||
const blocked = remainingPenetration < 0;
|
||||
|
||||
shot.layers.push({
|
||||
type,
|
||||
index: layerIndex,
|
||||
shellNormal,
|
||||
surfaceNormal,
|
||||
point: intersection.point,
|
||||
thickness,
|
||||
thicknessAngled: finalThickness,
|
||||
angle,
|
||||
status: blocked ? 'blocked' : 'penetration',
|
||||
});
|
||||
}
|
||||
shot.layersIn.push({
|
||||
type: layer.type,
|
||||
index,
|
||||
thickness,
|
||||
thicknessAngled: finalThickness,
|
||||
point: intersection.point,
|
||||
surfaceNormal: intersection.face!.normal,
|
||||
status: blocked ? 'blocked' : 'penetration',
|
||||
angle,
|
||||
});
|
||||
}
|
||||
|
||||
layerIndex++;
|
||||
loopIndex++;
|
||||
}
|
||||
|
||||
if (remainingPenetration <= 0) break;
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
const lastLayer = shot.layersIn.at(-1) as ShotLayerBase;
|
||||
const firstLayer = shot.layersIn[0] as ShotLayerBase;
|
||||
|
||||
if (shell.type === ShellType.HE) {
|
||||
const totalThickness = shot.layersIn.reduce((acc, layer) => {
|
||||
if (layer.type === null) return acc;
|
||||
|
||||
if (layer.type === ArmorType.External) {
|
||||
return acc + layer.thickness;
|
||||
}
|
||||
|
||||
return acc + layer.thicknessAngled;
|
||||
}, 0);
|
||||
const distanceFromSpacedArmor = firstLayer.point.distanceTo(
|
||||
lastLayer.point,
|
||||
);
|
||||
const finalDamage = Math.max(
|
||||
0,
|
||||
0.5 *
|
||||
shell.damage.armor *
|
||||
(1 - distanceFromSpacedArmor / shell.explosionRadius!) -
|
||||
1.1 * totalThickness,
|
||||
);
|
||||
|
||||
if (
|
||||
shot.layersIn.length > 1 ||
|
||||
lastLayer.status === 'blocked'
|
||||
) {
|
||||
shot.status = finalDamage > 0 ? 'splash' : 'blocked';
|
||||
shot.damage = finalDamage;
|
||||
} else {
|
||||
shot.status = 'penetration';
|
||||
shot.damage = shell.damage.armor;
|
||||
}
|
||||
} else {
|
||||
shot.status = lastLayer.status;
|
||||
}
|
||||
|
||||
console.log(shot);
|
||||
useTankopediaTemporary.setState({ shot });
|
||||
|
||||
// const duel = useDuel.getState();
|
||||
// const hasCalibratedShells = await hasEquipment(
|
||||
// 103,
|
||||
// duel.antagonist!.tank.equipment,
|
||||
// duel.antagonist!.equipmentMatrix,
|
||||
// );
|
||||
// const hasEnhancedArmor = await hasEquipment(
|
||||
// 110,
|
||||
// duel.protagonist!.tank.equipment,
|
||||
// duel.protagonist!.equipmentMatrix,
|
||||
// );
|
||||
// const thicknessCoefficient = hasEnhancedArmor ? 1.03 : 1;
|
||||
// const shell = useDuel.getState().antagonist!.shell;
|
||||
// const explosive = isExplosive(shell.type);
|
||||
// const splashing = canSplash(shell.type);
|
||||
// const penetration =
|
||||
// resolveNearPenetration(shell.penetration) *
|
||||
// (hasCalibratedShells
|
||||
// ? duel.antagonist!.shell.type === 'ap'
|
||||
// ? 1.08
|
||||
// : duel.antagonist!.shell.type === 'ap_cr'
|
||||
// ? 1.05
|
||||
// : duel.antagonist!.shell.type === 'hc'
|
||||
// ? 1.13
|
||||
// : 1.08
|
||||
// : 1);
|
||||
// const shot: Shot = {
|
||||
// layers: [],
|
||||
// point: event.point,
|
||||
// }; // TODO: type this
|
||||
// let remainingPenetration = penetration;
|
||||
|
||||
// const shellNormal = camera.position
|
||||
// .clone()
|
||||
// .sub(event.point)
|
||||
// .normalize();
|
||||
|
||||
// pushLogs(
|
||||
// shellNormal,
|
||||
// filterIntersections(event.intersections),
|
||||
// 0,
|
||||
// );
|
||||
|
||||
// function filterIntersections(intersectionsRaw: Intersection[]) {
|
||||
// const showSpacedArmor =
|
||||
// useTankopediaPersistent.getState().model.visual
|
||||
// .showSpacedArmor;
|
||||
// const intersectionsTyped = intersectionsRaw.filter(
|
||||
// ({ object }) => object.userData.type !== undefined,
|
||||
// ) as ArmorMeshIntersection[];
|
||||
// const intersectionsFiltered = intersectionsTyped.filter(
|
||||
// ({ object }) =>
|
||||
// showSpacedArmor
|
||||
// ? true
|
||||
// : object.userData.type === ArmorType.Core,
|
||||
// );
|
||||
// const intersectionsWithDuplicates = intersectionsFiltered.slice(
|
||||
// 0,
|
||||
// intersectionsFiltered.findIndex(
|
||||
// ({ object }) => object.userData.type === ArmorType.Core,
|
||||
// ) + 1,
|
||||
// );
|
||||
// const firstGunIntersectionIndex =
|
||||
// intersectionsWithDuplicates.findIndex(
|
||||
// ({ object }) =>
|
||||
// object.userData.type === ArmorType.External &&
|
||||
// object.userData.variant === 'gun',
|
||||
// );
|
||||
// const firstTrackIntersectionIndex =
|
||||
// intersectionsWithDuplicates.findIndex(
|
||||
// ({ object }) =>
|
||||
// object.userData.type === ArmorType.External &&
|
||||
// object.userData.variant === 'track',
|
||||
// );
|
||||
// let intersections = intersectionsWithDuplicates;
|
||||
|
||||
// if (firstGunIntersectionIndex !== -1) {
|
||||
// intersections = intersections.filter(({ object }, index) => {
|
||||
// return object.userData.type === ArmorType.External &&
|
||||
// object.userData.variant === 'gun'
|
||||
// ? index === firstGunIntersectionIndex
|
||||
// : true;
|
||||
// });
|
||||
// }
|
||||
// if (firstTrackIntersectionIndex !== -1) {
|
||||
// intersections = intersections.filter(({ object }, index) => {
|
||||
// return object.userData.type === ArmorType.External &&
|
||||
// object.userData.variant === 'track'
|
||||
// ? index === firstTrackIntersectionIndex
|
||||
// : true;
|
||||
// });
|
||||
// }
|
||||
|
||||
// return intersections;
|
||||
// }
|
||||
|
||||
// function pushLogs(
|
||||
// shellNormal: Vector3,
|
||||
// intersections: ArmorMeshIntersection[],
|
||||
// startingIndex: number,
|
||||
// ) {
|
||||
// let layerIndex = startingIndex;
|
||||
// let loopIndex = 0;
|
||||
|
||||
// if (layerIndex !== 0 && intersections.length > 0) {
|
||||
// const distance = (
|
||||
// shot.layers.at(-1) as ShotLayerBase
|
||||
// ).point.distanceTo(intersections[0].point);
|
||||
// if (shell.type === 'hc') {
|
||||
// remainingPenetration -=
|
||||
// 0.5 * remainingPenetration * distance;
|
||||
// }
|
||||
// const blocked = remainingPenetration < 0;
|
||||
|
||||
// shot.layers.push({
|
||||
// type: null,
|
||||
// status: blocked ? 'blocked' : 'penetration',
|
||||
// distance,
|
||||
// });
|
||||
|
||||
// if (blocked) return;
|
||||
// }
|
||||
|
||||
// for (const intersection of intersections) {
|
||||
// const surfaceNormal = intersection
|
||||
// .normal!.clone()
|
||||
// .applyQuaternion(
|
||||
// event.object.getWorldQuaternion(new Quaternion()),
|
||||
// );
|
||||
// const thickness =
|
||||
// thicknessCoefficient *
|
||||
// intersection.object.userData.thickness;
|
||||
|
||||
// if (layerIndex !== 0 && startingIndex === 0) {
|
||||
// const previousIntersection = intersections[layerIndex - 1];
|
||||
// const distance =
|
||||
// intersection.distance - previousIntersection.distance;
|
||||
// if (shell.type === 'hc') {
|
||||
// remainingPenetration -=
|
||||
// 0.5 * remainingPenetration * distance;
|
||||
// }
|
||||
// const wasted = remainingPenetration < 0 || splashing;
|
||||
|
||||
// shot.layers.push({
|
||||
// type: null,
|
||||
// status: wasted ? 'blocked' : 'penetration',
|
||||
// distance,
|
||||
// });
|
||||
|
||||
// if (wasted && !splashing) return;
|
||||
// }
|
||||
|
||||
// if (
|
||||
// intersection.object.userData.type === ArmorType.External
|
||||
// ) {
|
||||
// remainingPenetration -= thickness;
|
||||
// const blocked = remainingPenetration < 0;
|
||||
|
||||
// shot.layers.push({
|
||||
// type: ArmorType.External,
|
||||
// index: layerIndex,
|
||||
// shellNormal,
|
||||
// surfaceNormal,
|
||||
// point: intersection.point,
|
||||
// thickness: intersection.object.userData.thickness,
|
||||
// status: blocked ? 'blocked' : 'penetration',
|
||||
// });
|
||||
|
||||
// if (splashing) {
|
||||
// remainingPenetration = Math.max(0, remainingPenetration);
|
||||
// } else if (blocked) {
|
||||
// break;
|
||||
// }
|
||||
// } else {
|
||||
// const type = intersection.object.userData.type;
|
||||
// const angle = surfaceNormal.angleTo(shellNormal);
|
||||
// const ricochet = degToRad(
|
||||
// isExplosive(shell.type) ? 90 : shell.ricochet!,
|
||||
// );
|
||||
// const normalization = degToRad(shell.normalization ?? 0);
|
||||
// const threeCalibersRule =
|
||||
// shell.caliber > thickness * 3 || layerIndex > 0;
|
||||
// const twoCalibersRule = shell.caliber > thickness * 2;
|
||||
// const finalNormalization = twoCalibersRule
|
||||
// ? (1.4 * normalization * shell.caliber) /
|
||||
// (2.0 * thickness)
|
||||
// : normalization;
|
||||
// const finalThickness =
|
||||
// thickness === 0
|
||||
// ? 0
|
||||
// : thickness /
|
||||
// Math.cos(Math.max(0, angle - finalNormalization));
|
||||
|
||||
// if (!threeCalibersRule && angle >= ricochet) {
|
||||
// shot.layers.push({
|
||||
// type,
|
||||
// index: layerIndex,
|
||||
// shellNormal,
|
||||
// surfaceNormal,
|
||||
// point: intersection.point,
|
||||
// thickness,
|
||||
// thicknessAngled: finalThickness,
|
||||
// angle,
|
||||
// status: 'ricochet',
|
||||
// });
|
||||
|
||||
// remainingPenetration *= 0.75;
|
||||
|
||||
// const raycaster = new Raycaster();
|
||||
// const newShellNormal = shellNormal
|
||||
// .clone()
|
||||
// .reflect(surfaceNormal);
|
||||
// const newCameraNormal = newShellNormal
|
||||
// .clone()
|
||||
// .multiplyScalar(-1);
|
||||
|
||||
// raycaster.set(intersection.point, newCameraNormal);
|
||||
// pushLogs(
|
||||
// newShellNormal,
|
||||
// filterIntersections(
|
||||
// raycaster.intersectObjects(scene.children, true),
|
||||
// ),
|
||||
// layerIndex + 1,
|
||||
// );
|
||||
|
||||
// break;
|
||||
// } else {
|
||||
// remainingPenetration -= finalThickness;
|
||||
// const blocked = remainingPenetration < 0;
|
||||
|
||||
// shot.layers.push({
|
||||
// type,
|
||||
// index: layerIndex,
|
||||
// shellNormal,
|
||||
// surfaceNormal,
|
||||
// point: intersection.point,
|
||||
// thickness,
|
||||
// thicknessAngled: finalThickness,
|
||||
// angle,
|
||||
// status: blocked ? 'blocked' : 'penetration',
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// layerIndex++;
|
||||
// loopIndex++;
|
||||
// }
|
||||
// }
|
||||
|
||||
// useTankopediaTemporary.setState({ shot });
|
||||
},
|
||||
},
|
||||
node.uuid,
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { ShellType } from '../blitzkit/tankDefinitions';
|
||||
|
||||
export function resolvePenetrationCoefficient(
|
||||
calibratedShells: boolean,
|
||||
type: ShellType,
|
||||
) {
|
||||
if (!calibratedShells) return 1;
|
||||
|
||||
return type === 'ap'
|
||||
? 1.08
|
||||
: type === 'ap_cr'
|
||||
? 1.05
|
||||
: type === 'hc'
|
||||
? 1.13
|
||||
: 1.08;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { EquipmentMatrix } from '../../stores/duel';
|
||||
import { resolveNearPenetration } from '../blitz/resolveNearPenetration';
|
||||
import { resolvePenetrationCoefficient } from '../blitz/resolvePenetrationCoefficient';
|
||||
import { coefficient } from './coefficient';
|
||||
import { degressiveStat } from './degressiveStat';
|
||||
import { EquipmentDefinitions } from './equipmentDefinitions';
|
||||
@@ -151,13 +152,7 @@ export function tankCharacteristics(
|
||||
coefficient([hasAdrenaline && gun.type === 'regular', 0.17]);
|
||||
const penetrationCoefficient = coefficient([
|
||||
hasCalibratedShells,
|
||||
shell.type === 'ap'
|
||||
? 0.08
|
||||
: shell.type === 'ap_cr'
|
||||
? 0.05
|
||||
: shell.type === 'hc'
|
||||
? 0.13
|
||||
: 0.08,
|
||||
resolvePenetrationCoefficient(true, shell.type) - 1,
|
||||
]);
|
||||
const healthCoefficient = coefficient(
|
||||
[hasSandbagArmor, 0.03],
|
||||
|
||||
@@ -5,7 +5,12 @@ import { asset } from '../asset';
|
||||
import { fetchCdonLz4 } from '../fetchCdonLz4';
|
||||
import { TIERS } from './constants';
|
||||
|
||||
export type ShellType = 'ap' | 'ap_cr' | 'hc' | 'he';
|
||||
export enum ShellType {
|
||||
AP = 'ap',
|
||||
APCR = 'ap_cr',
|
||||
HEAT = 'hc',
|
||||
HE = 'he',
|
||||
}
|
||||
|
||||
export type CrewMember =
|
||||
| 'commander'
|
||||
|
||||
@@ -50,7 +50,6 @@ export interface ShotLayerBase {
|
||||
index: number;
|
||||
thickness: number;
|
||||
point: Vector3;
|
||||
shellNormal: Vector3;
|
||||
surfaceNormal: Vector3;
|
||||
status: 'blocked' | 'penetration' | 'ricochet';
|
||||
}
|
||||
@@ -74,8 +73,11 @@ export interface ShotLayerGap {
|
||||
export type ShotLayer = ShotLayerExternal | ShotLayerNonExternal | ShotLayerGap;
|
||||
|
||||
export type Shot = {
|
||||
status: 'penetration' | 'blocked' | 'ricochet' | 'splash';
|
||||
damage: number;
|
||||
point: Vector3;
|
||||
layers: ShotLayer[];
|
||||
layersIn: ShotLayer[];
|
||||
layersOut?: ShotLayer[];
|
||||
};
|
||||
|
||||
export type ArmorPiercingLayer =
|
||||
|
||||
Reference in new issue
Block a user