From 542145467ce9c5574fcf9aa761cb12b68e520d90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Fri, 24 May 2024 20:32:39 -0500 Subject: [PATCH] feat: start rewrite on onClick armor --- .../[id]/components/Characteristics/index.tsx | 9 +- .../CoreArmorSceneComponent/index.tsx | 19 +- .../Armor/components/ShotDisplay.tsx | 36 +- .../SpacedArmorSubExternal/index.tsx | 20 +- .../components/SpacedArmorSubSpaced/index.tsx | 14 +- .../SpacedArmorSceneComponent/index.tsx | 622 ++++++++++++------ .../blitz/resolvePenetrationCoefficient.ts | 16 + src/core/blitzkit/tankCharacteristics.ts | 9 +- src/core/blitzkit/tankDefinitions/index.ts | 7 +- src/stores/tankopedia.ts | 6 +- 10 files changed, 476 insertions(+), 282 deletions(-) create mode 100644 src/core/blitz/resolvePenetrationCoefficient.ts diff --git a/src/app/tools/tankopedia/[id]/components/Characteristics/index.tsx b/src/app/tools/tankopedia/[id]/components/Characteristics/index.tsx index 088514414..23ee719bf 100644 --- a/src/app/tools/tankopedia/[id]/components/Characteristics/index.tsx +++ b/src/app/tools/tankopedia/[id]/components/Characteristics/index.tsx @@ -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, diff --git a/src/components/Armor/components/CoreArmorSceneComponent/index.tsx b/src/components/Armor/components/CoreArmorSceneComponent/index.tsx index 0b7d727e7..06b00bbf5 100644 --- a/src/components/Armor/components/CoreArmorSceneComponent/index.tsx +++ b/src/components/Armor/components/CoreArmorSceneComponent/index.tsx @@ -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); diff --git a/src/components/Armor/components/ShotDisplay.tsx b/src/components/Armor/components/ShotDisplay.tsx index 7244f1f45..c8ea82b80 100644 --- a/src/components/Armor/components/ShotDisplay.tsx +++ b/src/components/Armor/components/ShotDisplay.tsx @@ -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(null); const postMidPointTracer = useRef(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; - const lastLayer = shot?.layers.at(-1)!; + ) ?? + shot?.layersIn.findLast((layer) => layer.type !== null)) as ShotLayerBase; + const firstLayer = shot?.layersIn[0] as Exclude; + 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() { )} - {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() { {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`} - {shot.layers.map((layer, index) => { + {shot.layersIn.map((layer, index) => { if (layer.type === null && (!explosive || splashing)) return null; return ( diff --git a/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubExternal/index.tsx b/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubExternal/index.tsx index b7afe7e51..676865f40 100644 --- a/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubExternal/index.tsx +++ b/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubExternal/index.tsx @@ -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); diff --git a/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubSpaced/index.tsx b/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubSpaced/index.tsx index 4c45704f7..09689392a 100644 --- a/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubSpaced/index.tsx +++ b/src/components/Armor/components/SpacedArmorSceneComponent/components/SpacedArmorSubSpaced/index.tsx @@ -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); diff --git a/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx b/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx index c080f0188..f8ccc64b8 100644 --- a/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx +++ b/src/components/Armor/components/SpacedArmorSceneComponent/index.tsx @@ -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, diff --git a/src/core/blitz/resolvePenetrationCoefficient.ts b/src/core/blitz/resolvePenetrationCoefficient.ts new file mode 100644 index 000000000..6f2f6acd0 --- /dev/null +++ b/src/core/blitz/resolvePenetrationCoefficient.ts @@ -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; +} diff --git a/src/core/blitzkit/tankCharacteristics.ts b/src/core/blitzkit/tankCharacteristics.ts index a6a15203f..bdd2db848 100644 --- a/src/core/blitzkit/tankCharacteristics.ts +++ b/src/core/blitzkit/tankCharacteristics.ts @@ -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], diff --git a/src/core/blitzkit/tankDefinitions/index.ts b/src/core/blitzkit/tankDefinitions/index.ts index a80ab8cd6..55e9d6922 100644 --- a/src/core/blitzkit/tankDefinitions/index.ts +++ b/src/core/blitzkit/tankDefinitions/index.ts @@ -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' diff --git a/src/stores/tankopedia.ts b/src/stores/tankopedia.ts index 81b87113b..9a4322d6b 100644 --- a/src/stores/tankopedia.ts +++ b/src/stores/tankopedia.ts @@ -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 =