Compare commits

..
33 Commits
Author SHA1 Message Date
TrèsAbhi 034429cd47 Merge branch 'main' of https://github.com/tresabhi/blitzkrieg 2024-03-31 17:48:30 -05:00
TrèsAbhi 57867317cc fix: missing info 2024-03-31 17:48:15 -05:00
TrèsAbhi c2e314b193 feat: log 2024-03-31 17:48:01 -05:00
TrèsAbhi 9d2af1de13 fix: quick equipments 2024-03-31 17:45:22 -05:00
TrèsAbhi 232823566b fix: missing info 2024-03-30 12:48:17 -05:00
TrèsAbhi 958f0f2a52 fix: log version 2024-03-30 12:44:46 -05:00
TrèsAbhi b34fef2147 feat: logs 2024-03-30 12:44:34 -05:00
TrèsAbhi fe661124fa Merge branch 'dev' of https://github.com/tresabhi/blitzkrieg into dev 2024-03-30 12:39:19 -05:00
TrèsAbhi 457dcbcad5 fix: 0 battle handling 2024-03-30 12:38:53 -05:00
TrèsAbhi c18ee70eba fix: /rating use midnight leaderboard 2024-03-30 12:38:53 -05:00
TrèsAbhi 095ffc3d6a fix: sync on click inspector 2024-03-30 12:38:53 -05:00
TrèsAbhi e8b91648b1 fix: click through post inputs 2024-03-30 12:38:53 -05:00
TrèsAbhi 1b43f6ba99 fix: use final thickness 2024-03-30 12:38:53 -05:00
TrèsAbhi fc5360363c fix: uniform inv proj mat 2024-03-30 12:38:53 -05:00
TrèsAbhi 47b3c3029e fix: more accurate HE 2024-03-30 12:38:53 -05:00
TrèsAbhi dea7f34ca3 fix: use normal proj mat 2024-03-30 12:38:53 -05:00
TrèsAbhi 1e5026ea4a fix: persistent equipment 2024-03-30 12:38:53 -05:00
TrèsAbhi 98f568ff6a fix: use three js inverse view proj 2024-03-30 12:38:53 -05:00
TrèsAbhi 10a6f8371d fix: 0 battle handling 2024-03-30 12:33:17 -05:00
TrèsAbhi 0933aa6615 fix: /rating use midnight leaderboard 2024-03-30 12:32:48 -05:00
TrèsAbhi 746592418d Update cd-rating-latest.yml 2024-03-30 12:22:20 -05:00
TrèsAbhi e2404b063c fix: sync on click inspector 2024-03-30 12:14:49 -05:00
TrèsAbhi bae1301556 fix: click through post inputs 2024-03-30 12:13:47 -05:00
TrèsAbhi c2de4952ad fix: use final thickness 2024-03-30 11:34:39 -05:00
TrèsAbhi cce61d8075 fix: uniform inv proj mat 2024-03-30 11:31:44 -05:00
TrèsAbhi 34a0388f45 fix: more accurate HE 2024-03-30 11:28:34 -05:00
TrèsAbhi 9d62965fc3 fix: use normal proj mat 2024-03-30 10:39:58 -05:00
TrèsAbhi 892434e28f fix: persistent equipment 2024-03-30 10:30:30 -05:00
TrèsAbhi 2597fe4bcd fix: use three js inverse view proj 2024-03-30 10:23:23 -05:00
TrèsAbhi f85b4b36bd fix: async exec 2024-03-29 16:45:17 -05:00
TrèsAbhi f777dafefc feat: generated list 2024-03-29 16:42:56 -05:00
TrèsAbhi 6ef65ccea3 fix: don't install fixed sharp twice 2024-03-29 16:40:07 -05:00
TrèsAbhi 974b27a313 fix: production flag 2024-03-29 16:38:51 -05:00
14 changed files with 256 additions and 207 deletions

No files matched your search

+1 -1
View File
@@ -1,4 +1,4 @@
name: CD Rating North America
name: CD Rating Latest
on:
schedule:
+1 -4
View File
@@ -33,11 +33,8 @@ jobs:
- name: Install dependencies
run: yarn
- name: Add fixed sharp
run: yarn add [email protected] --ignore-engines
- name: Build
run: yarn build:bot
run: yarn build:bot --production
- name: Archive build
uses: actions/upload-artifact@v4
+11
View File
@@ -0,0 +1,11 @@
# Blitzkrieg 1.18.1
HE gets more accurate and say goodbye to NaNaNaNaNaN.
## Fixes
- Armor highlighting now works on Apple MacBooks
- Applied equipments now persist after switching shells
- The bottom of the tank sandbox panel can now be interacted with as it was previously blocked by an invisible layer
- `/rating` now uses the midnight leaderboard instead of the absolute latest one
- 0-battle stats are now hidden
+7
View File
@@ -0,0 +1,7 @@
# Blitzkrieg 1.18.2
Redefining how micro a micro-path is.
## Fixes
- The pitch and yaw controls, along with the quick equipment buttons, can once again be interacted with
+2 -2
View File
@@ -1,11 +1,11 @@
{
"name": "blitzkrieg",
"version": "1.18.0",
"version": "1.18.2",
"description": "🎉 Tools for everything World of Tanks Blitz",
"main": "src/server.ts",
"type": "module",
"scripts": {
"build:bot": "vite-node --script scripts/build --production",
"build:bot": "vite-node --script scripts/build",
"build:website": "next build && next-sitemap --config next-sitemap.config.cjs",
"build:docs": "vitepress build docs",
"build:ratingleaderboard": "vite-node --script scripts/buildRatingLeaderboard",
+8 -4
View File
@@ -2,10 +2,12 @@ import { exec } from 'child_process';
import { build } from 'esbuild';
import { cp, readdir } from 'fs/promises';
import { argv } from 'process';
import { promisify } from 'util';
const execPromise = promisify(exec);
const isProduction = argv.includes('--production');
readdir('src/workers').then((workers) => {
await readdir('src/workers').then((workers) => {
const files = [
'bot',
'index',
@@ -41,8 +43,8 @@ readdir('src/workers').then((workers) => {
});
// copy schema.prisma
cp('prisma/schema.prisma', 'dist/bot/schema.prisma');
readdir('node_modules/prisma').then((files) =>
await cp('prisma/schema.prisma', 'dist/bot/schema.prisma');
await readdir('node_modules/prisma').then((files) =>
files
.filter((file) => file.endsWith('.node'))
.map((file) => cp(`node_modules/prisma/${file}`, `dist/bot/${file}`)),
@@ -51,7 +53,9 @@ readdir('node_modules/prisma').then((files) =>
if (isProduction) {
// install fixed sharp
console.log('Installing fixed sharp...');
exec('yarn init -y && yarn add [email protected] --ignore-engines', {
await execPromise('yarn init -y && yarn add [email protected] --ignore-engines', {
cwd: 'dist/bot',
});
}
console.log('Generated:', await readdir('dist/bot'));
@@ -419,6 +419,11 @@ export function Characteristics() {
<InfoWithDelta name="Module damage" unit="hp" decimals={0}>
{shell.damage.module * damageCoefficient}
</InfoWithDelta>
{isExplosive(shell.type) && (
<InfoWithDelta name="Splash radius" unit="m" decimals={0}>
{shell.explosionRadius!}
</InfoWithDelta>
)}
<InfoWithDelta name="Shell velocity" unit="m/s" decimals={0}>
{shell.speed * shellVelocityCoefficient}
</InfoWithDelta>
@@ -46,15 +46,11 @@ export function RotationInputs({ isFullScreen }: RotationInputsProps) {
position: 'absolute',
left: 8,
bottom: isFullScreen ? 80 : 8,
pointerEvents: 'none',
}}
gap="4"
>
<Flex
gap="2"
style={{
width: 256,
}}
>
<Flex gap="2" style={{ width: 256, pointerEvents: 'auto' }}>
<TextField.Root
style={{
flex: 1,
@@ -94,13 +90,7 @@ export function RotationInputs({ isFullScreen }: RotationInputsProps) {
ref={turretYawInput}
style={{ textAlign: 'right' }}
/>
<TextField.Slot
style={{
userSelect: 'none',
}}
>
°
</TextField.Slot>
<TextField.Slot style={{ userSelect: 'none' }}>°</TextField.Slot>
</TextField.Root>
<TextField.Root
@@ -155,7 +145,7 @@ export function RotationInputs({ isFullScreen }: RotationInputsProps) {
</Flex>
{mode === 'armor' && (
<Flex gap="4">
<Flex gap="4" style={{ pointerEvents: 'auto' }}>
<QuickEquipmentButton
equipment={103}
active={hasCalibratedShells}
+152 -145
View File
@@ -10,7 +10,7 @@ import getRatingInfo from '../core/blitz/getRatingInfo';
import { getRatingNeighbors } from '../core/blitz/getRatingNeighbors';
import { normalizeLeagueIcon } from '../core/blitz/normalizeLeagueIcon';
import { emblemURL } from '../core/blitzkrieg/emblemURL';
import { getArchivedRatingLeaderboard } from '../core/blitzkrieg/getArchivedRatingLeaderboard';
import getArchivedRatingMidnightLeaderboard from '../core/blitzkrieg/getArchivedRatingMidnightLeaderboard';
import { webpToPng } from '../core/blitzkrieg/iconPng';
import addUsernameChoices from '../core/discord/addUsernameChoices';
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
@@ -42,9 +42,12 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
const { id, region } = await resolvePlayerFromCommand(interaction);
const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan;
const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined;
const leaderboard = await getArchivedRatingLeaderboard(region, 53);
const leaderboard = await getArchivedRatingMidnightLeaderboard(
region,
53,
);
if (leaderboard.format === BkrlFormat.Base) {
if (leaderboard?.format === BkrlFormat.Base) {
throw new Error(
'Encountered bkrl minimal format in latest season. Wait till next cron?',
);
@@ -54,11 +57,11 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
const accountInfo = await getAccountInfo(region, id, [
'statistics.rating',
]);
const statsIndex = leaderboard.entries.findIndex(
const statsIndex = leaderboard?.entries.findIndex(
(entry) => entry.id === id,
);
const statsA = leaderboard.entries[statsIndex];
const numberA = statsIndex + 1;
const statsA = leaderboard ? leaderboard.entries[statsIndex!] : null;
const positionA = leaderboard ? statsIndex! + 1 : undefined;
const statsB1 = accountInfo.statistics.rating!;
const [statsB2] = (await getRatingNeighbors(region, id, 0)).neighbors;
const league = getLeagueFromScore(statsB2.score);
@@ -80,7 +83,9 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
statsB2.number >= reward.from_position &&
statsB2.number <= reward.to_position,
);
const positionDelta = statsB2.number - numberA;
const positionDelta = leaderboard
? statsB2.number - positionA!
: undefined;
const delta = deltaBkrlBlitzStats(
statsA,
accountInfo.statistics.rating!,
@@ -204,7 +209,7 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
>
#{statsB2.number.toLocaleString(interaction.locale)}
</span>
{positionDelta !== 0 && (
{positionDelta !== undefined && positionDelta !== 0 && (
<>
<DeltaCaret delta={positionDelta} />
<span
@@ -336,150 +341,152 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
)}
</div>
<div style={{ padding: '8px 0', display: 'flex' }}>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 4,
flex: 1,
flexDirection: 'column',
}}
>
<span
{delta.battles > 0 && (
<div style={{ padding: '8px 0', display: 'flex' }}>
<div
style={{
fontSize: 24,
color: theme.colors.textHighContrast,
fontWeight: 'bold',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 4,
flex: 1,
flexDirection: 'column',
}}
>
{delta.battles.toLocaleString(interaction.locale)}
</span>
<span
<span
style={{
fontSize: 24,
color: theme.colors.textHighContrast,
fontWeight: 'bold',
}}
>
{delta.battles.toLocaleString(interaction.locale)}
</span>
<span
style={{
fontSize: 16,
color: theme.colors.textLowContrast,
}}
>
Battles
</span>
</div>
<div
style={{
fontSize: 16,
color: theme.colors.textLowContrast,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 4,
flex: 1,
flexDirection: 'column',
}}
>
Battles
</span>
<span
style={{
fontSize: 24,
color: theme.colors.textHighContrast,
fontWeight: 'bold',
}}
>
{(100 * (delta.wins / delta.battles)).toFixed(0)}%
</span>
<span
style={{
fontSize: 16,
color: theme.colors.textLowContrast,
}}
>
Winrate
</span>
</div>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 4,
flex: 1,
flexDirection: 'column',
}}
>
<span
style={{
fontSize: 24,
color: theme.colors.textHighContrast,
fontWeight: 'bold',
}}
>
{Math.round(delta.damage / delta.battles).toLocaleString(
interaction.locale,
)}
</span>
<span
style={{
fontSize: 16,
color: theme.colors.textLowContrast,
}}
>
Damage
</span>
</div>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 4,
flex: 1,
flexDirection: 'column',
}}
>
<span
style={{
fontSize: 24,
color: theme.colors.textHighContrast,
fontWeight: 'bold',
}}
>
{(100 * (delta.survived / delta.battles)).toFixed(0)}%
</span>
<span
style={{
fontSize: 16,
color: theme.colors.textLowContrast,
}}
>
Survival
</span>
</div>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 4,
flex: 1,
flexDirection: 'column',
}}
>
<span
style={{
fontSize: 24,
color: theme.colors.textHighContrast,
fontWeight: 'bold',
}}
>
{(delta.kills / delta.battles).toFixed(2)}
</span>
<span
style={{
fontSize: 16,
color: theme.colors.textLowContrast,
}}
>
Kills
</span>
</div>
</div>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 4,
flex: 1,
flexDirection: 'column',
}}
>
<span
style={{
fontSize: 24,
color: theme.colors.textHighContrast,
fontWeight: 'bold',
}}
>
{(100 * (delta.wins / delta.battles)).toFixed(0)}%
</span>
<span
style={{
fontSize: 16,
color: theme.colors.textLowContrast,
}}
>
Winrate
</span>
</div>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 4,
flex: 1,
flexDirection: 'column',
}}
>
<span
style={{
fontSize: 24,
color: theme.colors.textHighContrast,
fontWeight: 'bold',
}}
>
{Math.round(delta.damage / delta.battles).toLocaleString(
interaction.locale,
)}
</span>
<span
style={{
fontSize: 16,
color: theme.colors.textLowContrast,
}}
>
Damage
</span>
</div>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 4,
flex: 1,
flexDirection: 'column',
}}
>
<span
style={{
fontSize: 24,
color: theme.colors.textHighContrast,
fontWeight: 'bold',
}}
>
{(100 * (delta.survived / delta.battles)).toFixed(0)}%
</span>
<span
style={{
fontSize: 16,
color: theme.colors.textLowContrast,
}}
>
Survival
</span>
</div>
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 4,
flex: 1,
flexDirection: 'column',
}}
>
<span
style={{
fontSize: 24,
color: theme.colors.textHighContrast,
fontWeight: 'bold',
}}
>
{(delta.kills / delta.battles).toFixed(2)}
</span>
<span
style={{
fontSize: 16,
color: theme.colors.textLowContrast,
}}
>
Kills
</span>
</div>
</div>
)}
</CommandWrapper>
);
},
@@ -50,6 +50,7 @@ export function CoreArmorSceneComponent({
opaque: { value: null },
useSpacedArmor: { value: null },
inverseProjectionMatrix: { value: null },
resolution: { value: new Vector2() },
spacedArmorBuffer: { value: null },
spacedArmorDepth: { value: null },
@@ -57,10 +58,7 @@ export function CoreArmorSceneComponent({
});
useEffect(() => {
function handleShellChange(shell: ShellDefinition) {
material.uniforms.penetration.value = resolveNearPenetration(
shell.penetration,
);
async function handleShellChange(shell: ShellDefinition) {
material.uniforms.caliber.value = shell.caliber;
material.uniforms.ricochet.value = degToRad(shell.ricochet ?? 90);
material.uniforms.normalization.value = degToRad(
@@ -70,6 +68,10 @@ export function CoreArmorSceneComponent({
material.uniforms.canSplash.value = canSplash(shell.type);
material.uniforms.damage.value = shell.damage.armor;
material.uniforms.explosionRadius.value = shell.explosionRadius;
const duel = useDuel.getState();
await handleProtagonistEquipmentChange(duel.protagonist!.equipment);
await handleAntagonistEquipmentChange(duel.antagonist!.equipment);
}
function handleVisualChange(
visual: TankopediaPersistent['model']['visual'],
@@ -91,6 +93,7 @@ export function CoreArmorSceneComponent({
const shell = useDuel.getState().antagonist!.shell;
const penetration = resolveNearPenetration(shell.penetration);
const hasCalibratedShells = await hasEquipment(103, true, equipment);
material.uniforms.penetration.value = hasCalibratedShells
? penetration * (isExplosive(shell.type) ? 1.1 : 1.05)
: penetration;
@@ -122,13 +125,15 @@ export function CoreArmorSceneComponent({
};
}, []);
useFrame(({ gl }) => {
useFrame(({ gl, camera }) => {
gl.getSize(material.uniforms.resolution.value).multiplyScalar(
gl.getPixelRatio(),
);
material.uniforms.spacedArmorBuffer.value = spacedArmorRenderTarget.texture;
material.uniforms.spacedArmorDepth.value =
spacedArmorRenderTarget.depthTexture;
material.uniforms.inverseProjectionMatrix.value =
camera.projectionMatrixInverse;
});
return (
@@ -1,6 +1,5 @@
varying vec3 vNormal;
varying vec3 vViewPosition;
varying mat4 vProjectionMatrix;
uniform float thickness;
uniform float penetration;
@@ -15,13 +14,14 @@ uniform float explosionRadius;
uniform bool greenPenetration;
uniform bool opaque;
uniform bool useSpacedArmor;
uniform mat4 inverseProjectionMatrix;
uniform highp sampler2D spacedArmorBuffer;
uniform highp sampler2D spacedArmorDepth;
float depthToDistance(float depth) {
vec4 clipPosition = vec4(gl_FragCoord.xy / resolution * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);
vec4 eyePosition = inverse(vProjectionMatrix) * clipPosition;
vec4 eyePosition = inverseProjectionMatrix * clipPosition;
return length(eyePosition.xyz / eyePosition.w);
}
@@ -42,36 +42,42 @@ void main() {
bool twoCalibersRule = caliber > thickness * 2.0 && thickness > 0.0;
float finalNormalization = twoCalibersRule ? ((1.4 * normalization * caliber) / (2.0 * thickness)) : normalization;
float finalThickness = thickness / cos(max(0.0, angle - finalNormalization));
float remainingPenetration = penetration;
// world space is in meters
// world space in meters
vec4 spacedArmorDepthFragment = texture2D(spacedArmorDepth, screenCoordinates);
float spacedArmorDistance = depthToDistance(spacedArmorDepthFragment.r);
float coreArmorDistance = depthToDistance(gl_FragCoord.z);
float distanceFromSpacedArmor = coreArmorDistance - spacedArmorDistance;
if (useSpacedArmor && isUnderSpacedArmor) {
if (canSplash && useSpacedArmor && isUnderSpacedArmor) {
float spacedArmorThickness = spacedArmorBufferFragment.r * penetration;
remainingPenetration -= spacedArmorThickness;
float finalDamage = 0.5 * damage * (1.0 - distanceFromSpacedArmor / explosionRadius) - 1.1 * (finalThickness + spacedArmorThickness);
if (isExplosive && remainingPenetration > 0.0) {
// there is a 50% penetration loss per meter for HEAT shells
remainingPenetration -= 0.5 * remainingPenetration * distanceFromSpacedArmor;
}
}
remainingPenetration = max(0.0, remainingPenetration);
float delta = finalThickness - remainingPenetration;
float randomization = remainingPenetration * 0.05;
penetrationChance = clamp(1.0 - (delta + randomization) / (2.0 * randomization), 0.0, 1.0);
if (canSplash) {
// only allow splashing if the damage equation deals more than 0 damage
float nominalArmorThickness = finalThickness - remainingPenetration;
float finalDamage = 0.5 * damage * (1.0 - distanceFromSpacedArmor / explosionRadius) - 1.1 * nominalArmorThickness;
splashChance = finalDamage > 0.0 ? 1.0 : 0.0; // TODO: add randomization here?
penetrationChance = 0.0;
splashChance = finalDamage > 0.0 ? 1.0 : 0.0;
} else {
splashChance = 0.0;
float remainingPenetration = penetration;
if (useSpacedArmor && isUnderSpacedArmor) {
float spacedArmorThickness = spacedArmorBufferFragment.r * penetration;
remainingPenetration -= spacedArmorThickness;
if (isExplosive && remainingPenetration > 0.0) {
remainingPenetration -= 0.5 * remainingPenetration * distanceFromSpacedArmor;
}
}
remainingPenetration = max(0.0, remainingPenetration);
float delta = finalThickness - remainingPenetration;
float randomization = remainingPenetration * 0.05;
penetrationChance = clamp(1.0 - (delta + randomization) / (2.0 * randomization), 0.0, 1.0);
if (canSplash) {
float splashDamage = 0.5 * damage - 1.1 * finalThickness;
splashChance = splashDamage > 0.0 ? 1.0 : 0.0;
} else {
splashChance = 0.0;
}
}
}
@@ -1,12 +1,10 @@
varying vec3 vNormal;
varying vec3 vViewPosition;
varying mat4 vProjectionMatrix;
void main() {
vec4 vViewPosition4 = modelViewMatrix * vec4(position, 1.0);
vViewPosition = vViewPosition4.xyz;
vProjectionMatrix = projectionMatrix;
vNormal = normalMatrix * normal;
gl_Position = projectionMatrix * vViewPosition4;
@@ -4,6 +4,7 @@ import { useFrame } from '@react-three/fiber';
import { useEffect, useRef } from 'react';
import { Mesh } from 'three';
import { radToDeg } from 'three/src/math/MathUtils';
import { canSplash } from '../../../core/blitz/canSplash';
import { isExplosive } from '../../../core/blitz/isExplosive';
import { NaNFallback } from '../../../core/math/NaNFallback';
import { normalToEuler } from '../../../core/math/normalToEuler';
@@ -42,6 +43,7 @@ export function ShotDisplay() {
);
const { shell } = useDuel.getState().antagonist!;
const explosive = isExplosive(shell.type);
const splashing = canSplash(shell.type);
const midPointLayer = (shot?.layers.findLast(
(layer) => layer.type !== null && layer.status === 'ricochet',
) ?? shot?.layers.findLast((layer) => layer.type !== null)) as ShotLayerBase;
@@ -64,6 +66,18 @@ export function ShotDisplay() {
return accumulator;
}, 0) + LENGTH_INFINITY
: undefined;
const preMidPointThickness = shot
? preMidPointLayers!.reduce(
(accumulator, layer) =>
accumulator +
(layer.type === null
? 0
: layer.type === ArmorType.External
? layer.thickness
: layer.thicknessAngled),
0,
)
: undefined;
const postMidPointGap = shot
? postMidPointLayers!.reduce((accumulator, layer) => {
if (layer.type === null) return accumulator + layer.distance;
@@ -246,8 +260,9 @@ export function ShotDisplay() {
(1 -
distanceFromSpacedArmor! / shell.explosionRadius!) -
1.1 *
(midPointLayer as ShotLayerNonExternal)
.thicknessAngled,
((midPointLayer as ShotLayerNonExternal)
.thicknessAngled +
preMidPointThickness!),
),
),
0,
@@ -255,7 +270,7 @@ export function ShotDisplay() {
</Text>
{shot.layers.map((layer, index) => {
if (layer.type === null && !explosive) return null;
if (layer.type === null && (!explosive || splashing)) return null;
return (
<Flex key={index} align="center" gap="2">
@@ -179,7 +179,7 @@ export function SpacedArmorSceneComponent({
const distance = (
shot.layers.at(-1) as ShotLayerBase
).point.distanceTo(intersections[0].point);
if (explosive)
if (explosive && !splashing)
remainingPenetration -= 0.5 * remainingPenetration * distance;
const blocked = remainingPenetration < 0;
@@ -205,7 +205,7 @@ export function SpacedArmorSceneComponent({
const previousIntersection = intersections[layerIndex - 1];
const distance =
intersection.distance - previousIntersection.distance;
if (explosive)
if (explosive && !splashing)
remainingPenetration -=
0.5 * remainingPenetration * distance;
const wasted = remainingPenetration < 0;
@@ -216,7 +216,7 @@ export function SpacedArmorSceneComponent({
distance,
});
if (wasted) return;
if (wasted && !splashing) return;
}
if (intersection.object.userData.type === ArmorType.External) {
@@ -233,7 +233,11 @@ export function SpacedArmorSceneComponent({
status: blocked ? 'blocked' : 'penetration',
});
if (blocked) break;
if (splashing) {
remainingPenetration = Math.max(0, remainingPenetration);
} else if (blocked) {
break;
}
} else {
const type = intersection.object.userData.type;
const angle = surfaceNormal.angleTo(shellNormal);