mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 18:57:03 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
958f0f2a52 | ||
|
|
b34fef2147 | ||
|
|
fe661124fa | ||
|
|
457dcbcad5 | ||
|
|
c18ee70eba | ||
|
|
095ffc3d6a | ||
|
|
e8b91648b1 | ||
|
|
1b43f6ba99 | ||
|
|
fc5360363c | ||
|
|
47b3c3029e | ||
|
|
dea7f34ca3 | ||
|
|
1e5026ea4a | ||
|
|
98f568ff6a | ||
|
|
10a6f8371d | ||
|
|
0933aa6615 | ||
|
|
746592418d | ||
|
|
e2404b063c | ||
|
|
bae1301556 | ||
|
|
c2de4952ad | ||
|
|
cce61d8075 | ||
|
|
34a0388f45 | ||
|
|
9d62965fc3 | ||
|
|
892434e28f | ||
|
|
2597fe4bcd | ||
|
|
f85b4b36bd | ||
|
|
f777dafefc | ||
|
|
6ef65ccea3 | ||
|
|
974b27a313 |
No files matched your search
@@ -1,4 +1,4 @@
|
||||
name: CD Rating North America
|
||||
name: CD Rating Latest
|
||||
|
||||
on:
|
||||
schedule:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# 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
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "blitzkrieg",
|
||||
"version": "1.18.0",
|
||||
"version": "1.18.1",
|
||||
"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
@@ -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: 'none' }}>
|
||||
<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
|
||||
|
||||
+152
-145
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user