mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 10:47:04 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
034429cd47 | ||
|
|
57867317cc | ||
|
|
c2e314b193 | ||
|
|
9d2af1de13 | ||
|
|
232823566b | ||
|
|
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:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
|
|||||||
@@ -33,11 +33,8 @@ jobs:
|
|||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: yarn
|
run: yarn
|
||||||
|
|
||||||
- name: Add fixed sharp
|
|
||||||
run: yarn add [email protected] --ignore-engines
|
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: yarn build:bot
|
run: yarn build:bot --production
|
||||||
|
|
||||||
- name: Archive build
|
- name: Archive build
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "blitzkrieg",
|
"name": "blitzkrieg",
|
||||||
"version": "1.18.0",
|
"version": "1.18.2",
|
||||||
"description": "🎉 Tools for everything World of Tanks Blitz",
|
"description": "🎉 Tools for everything World of Tanks Blitz",
|
||||||
"main": "src/server.ts",
|
"main": "src/server.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"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:website": "next build && next-sitemap --config next-sitemap.config.cjs",
|
||||||
"build:docs": "vitepress build docs",
|
"build:docs": "vitepress build docs",
|
||||||
"build:ratingleaderboard": "vite-node --script scripts/buildRatingLeaderboard",
|
"build:ratingleaderboard": "vite-node --script scripts/buildRatingLeaderboard",
|
||||||
|
|||||||
+8
-4
@@ -2,10 +2,12 @@ import { exec } from 'child_process';
|
|||||||
import { build } from 'esbuild';
|
import { build } from 'esbuild';
|
||||||
import { cp, readdir } from 'fs/promises';
|
import { cp, readdir } from 'fs/promises';
|
||||||
import { argv } from 'process';
|
import { argv } from 'process';
|
||||||
|
import { promisify } from 'util';
|
||||||
|
|
||||||
|
const execPromise = promisify(exec);
|
||||||
const isProduction = argv.includes('--production');
|
const isProduction = argv.includes('--production');
|
||||||
|
|
||||||
readdir('src/workers').then((workers) => {
|
await readdir('src/workers').then((workers) => {
|
||||||
const files = [
|
const files = [
|
||||||
'bot',
|
'bot',
|
||||||
'index',
|
'index',
|
||||||
@@ -41,8 +43,8 @@ readdir('src/workers').then((workers) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// copy schema.prisma
|
// copy schema.prisma
|
||||||
cp('prisma/schema.prisma', 'dist/bot/schema.prisma');
|
await cp('prisma/schema.prisma', 'dist/bot/schema.prisma');
|
||||||
readdir('node_modules/prisma').then((files) =>
|
await readdir('node_modules/prisma').then((files) =>
|
||||||
files
|
files
|
||||||
.filter((file) => file.endsWith('.node'))
|
.filter((file) => file.endsWith('.node'))
|
||||||
.map((file) => cp(`node_modules/prisma/${file}`, `dist/bot/${file}`)),
|
.map((file) => cp(`node_modules/prisma/${file}`, `dist/bot/${file}`)),
|
||||||
@@ -51,7 +53,9 @@ readdir('node_modules/prisma').then((files) =>
|
|||||||
if (isProduction) {
|
if (isProduction) {
|
||||||
// install fixed sharp
|
// install fixed sharp
|
||||||
console.log('Installing 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',
|
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}>
|
<InfoWithDelta name="Module damage" unit="hp" decimals={0}>
|
||||||
{shell.damage.module * damageCoefficient}
|
{shell.damage.module * damageCoefficient}
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
|
{isExplosive(shell.type) && (
|
||||||
|
<InfoWithDelta name="Splash radius" unit="m" decimals={0}>
|
||||||
|
{shell.explosionRadius!}
|
||||||
|
</InfoWithDelta>
|
||||||
|
)}
|
||||||
<InfoWithDelta name="Shell velocity" unit="m/s" decimals={0}>
|
<InfoWithDelta name="Shell velocity" unit="m/s" decimals={0}>
|
||||||
{shell.speed * shellVelocityCoefficient}
|
{shell.speed * shellVelocityCoefficient}
|
||||||
</InfoWithDelta>
|
</InfoWithDelta>
|
||||||
|
|||||||
@@ -46,15 +46,11 @@ export function RotationInputs({ isFullScreen }: RotationInputsProps) {
|
|||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: 8,
|
left: 8,
|
||||||
bottom: isFullScreen ? 80 : 8,
|
bottom: isFullScreen ? 80 : 8,
|
||||||
|
pointerEvents: 'none',
|
||||||
}}
|
}}
|
||||||
gap="4"
|
gap="4"
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex gap="2" style={{ width: 256, pointerEvents: 'auto' }}>
|
||||||
gap="2"
|
|
||||||
style={{
|
|
||||||
width: 256,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TextField.Root
|
<TextField.Root
|
||||||
style={{
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@@ -94,13 +90,7 @@ export function RotationInputs({ isFullScreen }: RotationInputsProps) {
|
|||||||
ref={turretYawInput}
|
ref={turretYawInput}
|
||||||
style={{ textAlign: 'right' }}
|
style={{ textAlign: 'right' }}
|
||||||
/>
|
/>
|
||||||
<TextField.Slot
|
<TextField.Slot style={{ userSelect: 'none' }}>°</TextField.Slot>
|
||||||
style={{
|
|
||||||
userSelect: 'none',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
°
|
|
||||||
</TextField.Slot>
|
|
||||||
</TextField.Root>
|
</TextField.Root>
|
||||||
|
|
||||||
<TextField.Root
|
<TextField.Root
|
||||||
@@ -155,7 +145,7 @@ export function RotationInputs({ isFullScreen }: RotationInputsProps) {
|
|||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{mode === 'armor' && (
|
{mode === 'armor' && (
|
||||||
<Flex gap="4">
|
<Flex gap="4" style={{ pointerEvents: 'auto' }}>
|
||||||
<QuickEquipmentButton
|
<QuickEquipmentButton
|
||||||
equipment={103}
|
equipment={103}
|
||||||
active={hasCalibratedShells}
|
active={hasCalibratedShells}
|
||||||
|
|||||||
+152
-145
@@ -10,7 +10,7 @@ import getRatingInfo from '../core/blitz/getRatingInfo';
|
|||||||
import { getRatingNeighbors } from '../core/blitz/getRatingNeighbors';
|
import { getRatingNeighbors } from '../core/blitz/getRatingNeighbors';
|
||||||
import { normalizeLeagueIcon } from '../core/blitz/normalizeLeagueIcon';
|
import { normalizeLeagueIcon } from '../core/blitz/normalizeLeagueIcon';
|
||||||
import { emblemURL } from '../core/blitzkrieg/emblemURL';
|
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 { webpToPng } from '../core/blitzkrieg/iconPng';
|
||||||
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
import addUsernameChoices from '../core/discord/addUsernameChoices';
|
||||||
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
||||||
@@ -42,9 +42,12 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
const { id, region } = await resolvePlayerFromCommand(interaction);
|
const { id, region } = await resolvePlayerFromCommand(interaction);
|
||||||
const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan;
|
const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan;
|
||||||
const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined;
|
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(
|
throw new Error(
|
||||||
'Encountered bkrl minimal format in latest season. Wait till next cron?',
|
'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, [
|
const accountInfo = await getAccountInfo(region, id, [
|
||||||
'statistics.rating',
|
'statistics.rating',
|
||||||
]);
|
]);
|
||||||
const statsIndex = leaderboard.entries.findIndex(
|
const statsIndex = leaderboard?.entries.findIndex(
|
||||||
(entry) => entry.id === id,
|
(entry) => entry.id === id,
|
||||||
);
|
);
|
||||||
const statsA = leaderboard.entries[statsIndex];
|
const statsA = leaderboard ? leaderboard.entries[statsIndex!] : null;
|
||||||
const numberA = statsIndex + 1;
|
const positionA = leaderboard ? statsIndex! + 1 : undefined;
|
||||||
const statsB1 = accountInfo.statistics.rating!;
|
const statsB1 = accountInfo.statistics.rating!;
|
||||||
const [statsB2] = (await getRatingNeighbors(region, id, 0)).neighbors;
|
const [statsB2] = (await getRatingNeighbors(region, id, 0)).neighbors;
|
||||||
const league = getLeagueFromScore(statsB2.score);
|
const league = getLeagueFromScore(statsB2.score);
|
||||||
@@ -80,7 +83,9 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
statsB2.number >= reward.from_position &&
|
statsB2.number >= reward.from_position &&
|
||||||
statsB2.number <= reward.to_position,
|
statsB2.number <= reward.to_position,
|
||||||
);
|
);
|
||||||
const positionDelta = statsB2.number - numberA;
|
const positionDelta = leaderboard
|
||||||
|
? statsB2.number - positionA!
|
||||||
|
: undefined;
|
||||||
const delta = deltaBkrlBlitzStats(
|
const delta = deltaBkrlBlitzStats(
|
||||||
statsA,
|
statsA,
|
||||||
accountInfo.statistics.rating!,
|
accountInfo.statistics.rating!,
|
||||||
@@ -204,7 +209,7 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
>
|
>
|
||||||
#{statsB2.number.toLocaleString(interaction.locale)}
|
#{statsB2.number.toLocaleString(interaction.locale)}
|
||||||
</span>
|
</span>
|
||||||
{positionDelta !== 0 && (
|
{positionDelta !== undefined && positionDelta !== 0 && (
|
||||||
<>
|
<>
|
||||||
<DeltaCaret delta={positionDelta} />
|
<DeltaCaret delta={positionDelta} />
|
||||||
<span
|
<span
|
||||||
@@ -336,150 +341,152 @@ export const ratingCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ padding: '8px 0', display: 'flex' }}>
|
{delta.battles > 0 && (
|
||||||
<div
|
<div style={{ padding: '8px 0', display: 'flex' }}>
|
||||||
style={{
|
<div
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
gap: 4,
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'column',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
style={{
|
style={{
|
||||||
fontSize: 24,
|
display: 'flex',
|
||||||
color: theme.colors.textHighContrast,
|
alignItems: 'center',
|
||||||
fontWeight: 'bold',
|
justifyContent: 'center',
|
||||||
|
gap: 4,
|
||||||
|
flex: 1,
|
||||||
|
flexDirection: 'column',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{delta.battles.toLocaleString(interaction.locale)}
|
<span
|
||||||
</span>
|
style={{
|
||||||
<span
|
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={{
|
style={{
|
||||||
fontSize: 16,
|
display: 'flex',
|
||||||
color: theme.colors.textLowContrast,
|
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>
|
||||||
<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>
|
</CommandWrapper>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ export function CoreArmorSceneComponent({
|
|||||||
opaque: { value: null },
|
opaque: { value: null },
|
||||||
useSpacedArmor: { value: null },
|
useSpacedArmor: { value: null },
|
||||||
|
|
||||||
|
inverseProjectionMatrix: { value: null },
|
||||||
resolution: { value: new Vector2() },
|
resolution: { value: new Vector2() },
|
||||||
spacedArmorBuffer: { value: null },
|
spacedArmorBuffer: { value: null },
|
||||||
spacedArmorDepth: { value: null },
|
spacedArmorDepth: { value: null },
|
||||||
@@ -57,10 +58,7 @@ export function CoreArmorSceneComponent({
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleShellChange(shell: ShellDefinition) {
|
async function handleShellChange(shell: ShellDefinition) {
|
||||||
material.uniforms.penetration.value = resolveNearPenetration(
|
|
||||||
shell.penetration,
|
|
||||||
);
|
|
||||||
material.uniforms.caliber.value = shell.caliber;
|
material.uniforms.caliber.value = shell.caliber;
|
||||||
material.uniforms.ricochet.value = degToRad(shell.ricochet ?? 90);
|
material.uniforms.ricochet.value = degToRad(shell.ricochet ?? 90);
|
||||||
material.uniforms.normalization.value = degToRad(
|
material.uniforms.normalization.value = degToRad(
|
||||||
@@ -70,6 +68,10 @@ export function CoreArmorSceneComponent({
|
|||||||
material.uniforms.canSplash.value = canSplash(shell.type);
|
material.uniforms.canSplash.value = canSplash(shell.type);
|
||||||
material.uniforms.damage.value = shell.damage.armor;
|
material.uniforms.damage.value = shell.damage.armor;
|
||||||
material.uniforms.explosionRadius.value = shell.explosionRadius;
|
material.uniforms.explosionRadius.value = shell.explosionRadius;
|
||||||
|
|
||||||
|
const duel = useDuel.getState();
|
||||||
|
await handleProtagonistEquipmentChange(duel.protagonist!.equipment);
|
||||||
|
await handleAntagonistEquipmentChange(duel.antagonist!.equipment);
|
||||||
}
|
}
|
||||||
function handleVisualChange(
|
function handleVisualChange(
|
||||||
visual: TankopediaPersistent['model']['visual'],
|
visual: TankopediaPersistent['model']['visual'],
|
||||||
@@ -91,6 +93,7 @@ export function CoreArmorSceneComponent({
|
|||||||
const shell = useDuel.getState().antagonist!.shell;
|
const shell = useDuel.getState().antagonist!.shell;
|
||||||
const penetration = resolveNearPenetration(shell.penetration);
|
const penetration = resolveNearPenetration(shell.penetration);
|
||||||
const hasCalibratedShells = await hasEquipment(103, true, equipment);
|
const hasCalibratedShells = await hasEquipment(103, true, equipment);
|
||||||
|
|
||||||
material.uniforms.penetration.value = hasCalibratedShells
|
material.uniforms.penetration.value = hasCalibratedShells
|
||||||
? penetration * (isExplosive(shell.type) ? 1.1 : 1.05)
|
? penetration * (isExplosive(shell.type) ? 1.1 : 1.05)
|
||||||
: penetration;
|
: penetration;
|
||||||
@@ -122,13 +125,15 @@ export function CoreArmorSceneComponent({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useFrame(({ gl }) => {
|
useFrame(({ gl, camera }) => {
|
||||||
gl.getSize(material.uniforms.resolution.value).multiplyScalar(
|
gl.getSize(material.uniforms.resolution.value).multiplyScalar(
|
||||||
gl.getPixelRatio(),
|
gl.getPixelRatio(),
|
||||||
);
|
);
|
||||||
material.uniforms.spacedArmorBuffer.value = spacedArmorRenderTarget.texture;
|
material.uniforms.spacedArmorBuffer.value = spacedArmorRenderTarget.texture;
|
||||||
material.uniforms.spacedArmorDepth.value =
|
material.uniforms.spacedArmorDepth.value =
|
||||||
spacedArmorRenderTarget.depthTexture;
|
spacedArmorRenderTarget.depthTexture;
|
||||||
|
material.uniforms.inverseProjectionMatrix.value =
|
||||||
|
camera.projectionMatrixInverse;
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
varying vec3 vNormal;
|
varying vec3 vNormal;
|
||||||
varying vec3 vViewPosition;
|
varying vec3 vViewPosition;
|
||||||
varying mat4 vProjectionMatrix;
|
|
||||||
|
|
||||||
uniform float thickness;
|
uniform float thickness;
|
||||||
uniform float penetration;
|
uniform float penetration;
|
||||||
@@ -15,13 +14,14 @@ uniform float explosionRadius;
|
|||||||
uniform bool greenPenetration;
|
uniform bool greenPenetration;
|
||||||
uniform bool opaque;
|
uniform bool opaque;
|
||||||
uniform bool useSpacedArmor;
|
uniform bool useSpacedArmor;
|
||||||
|
uniform mat4 inverseProjectionMatrix;
|
||||||
|
|
||||||
uniform highp sampler2D spacedArmorBuffer;
|
uniform highp sampler2D spacedArmorBuffer;
|
||||||
uniform highp sampler2D spacedArmorDepth;
|
uniform highp sampler2D spacedArmorDepth;
|
||||||
|
|
||||||
float depthToDistance(float depth) {
|
float depthToDistance(float depth) {
|
||||||
vec4 clipPosition = vec4(gl_FragCoord.xy / resolution * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);
|
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);
|
return length(eyePosition.xyz / eyePosition.w);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,36 +42,42 @@ void main() {
|
|||||||
bool twoCalibersRule = caliber > thickness * 2.0 && thickness > 0.0;
|
bool twoCalibersRule = caliber > thickness * 2.0 && thickness > 0.0;
|
||||||
float finalNormalization = twoCalibersRule ? ((1.4 * normalization * caliber) / (2.0 * thickness)) : normalization;
|
float finalNormalization = twoCalibersRule ? ((1.4 * normalization * caliber) / (2.0 * thickness)) : normalization;
|
||||||
float finalThickness = thickness / cos(max(0.0, angle - finalNormalization));
|
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);
|
vec4 spacedArmorDepthFragment = texture2D(spacedArmorDepth, screenCoordinates);
|
||||||
float spacedArmorDistance = depthToDistance(spacedArmorDepthFragment.r);
|
float spacedArmorDistance = depthToDistance(spacedArmorDepthFragment.r);
|
||||||
float coreArmorDistance = depthToDistance(gl_FragCoord.z);
|
float coreArmorDistance = depthToDistance(gl_FragCoord.z);
|
||||||
float distanceFromSpacedArmor = coreArmorDistance - spacedArmorDistance;
|
float distanceFromSpacedArmor = coreArmorDistance - spacedArmorDistance;
|
||||||
|
|
||||||
if (useSpacedArmor && isUnderSpacedArmor) {
|
if (canSplash && useSpacedArmor && isUnderSpacedArmor) {
|
||||||
float spacedArmorThickness = spacedArmorBufferFragment.r * penetration;
|
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) {
|
penetrationChance = 0.0;
|
||||||
// there is a 50% penetration loss per meter for HEAT shells
|
splashChance = finalDamage > 0.0 ? 1.0 : 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) {
|
|
||||||
// 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?
|
|
||||||
} else {
|
} 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 vNormal;
|
||||||
varying vec3 vViewPosition;
|
varying vec3 vViewPosition;
|
||||||
varying mat4 vProjectionMatrix;
|
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec4 vViewPosition4 = modelViewMatrix * vec4(position, 1.0);
|
vec4 vViewPosition4 = modelViewMatrix * vec4(position, 1.0);
|
||||||
|
|
||||||
vViewPosition = vViewPosition4.xyz;
|
vViewPosition = vViewPosition4.xyz;
|
||||||
vProjectionMatrix = projectionMatrix;
|
|
||||||
vNormal = normalMatrix * normal;
|
vNormal = normalMatrix * normal;
|
||||||
|
|
||||||
gl_Position = projectionMatrix * vViewPosition4;
|
gl_Position = projectionMatrix * vViewPosition4;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { useFrame } from '@react-three/fiber';
|
|||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { Mesh } from 'three';
|
import { Mesh } from 'three';
|
||||||
import { radToDeg } from 'three/src/math/MathUtils';
|
import { radToDeg } from 'three/src/math/MathUtils';
|
||||||
|
import { canSplash } from '../../../core/blitz/canSplash';
|
||||||
import { isExplosive } from '../../../core/blitz/isExplosive';
|
import { isExplosive } from '../../../core/blitz/isExplosive';
|
||||||
import { NaNFallback } from '../../../core/math/NaNFallback';
|
import { NaNFallback } from '../../../core/math/NaNFallback';
|
||||||
import { normalToEuler } from '../../../core/math/normalToEuler';
|
import { normalToEuler } from '../../../core/math/normalToEuler';
|
||||||
@@ -42,6 +43,7 @@ export function ShotDisplay() {
|
|||||||
);
|
);
|
||||||
const { shell } = useDuel.getState().antagonist!;
|
const { shell } = useDuel.getState().antagonist!;
|
||||||
const explosive = isExplosive(shell.type);
|
const explosive = isExplosive(shell.type);
|
||||||
|
const splashing = canSplash(shell.type);
|
||||||
const midPointLayer = (shot?.layers.findLast(
|
const midPointLayer = (shot?.layers.findLast(
|
||||||
(layer) => layer.type !== null && layer.status === 'ricochet',
|
(layer) => layer.type !== null && layer.status === 'ricochet',
|
||||||
) ?? shot?.layers.findLast((layer) => layer.type !== null)) as ShotLayerBase;
|
) ?? shot?.layers.findLast((layer) => layer.type !== null)) as ShotLayerBase;
|
||||||
@@ -64,6 +66,18 @@ export function ShotDisplay() {
|
|||||||
return accumulator;
|
return accumulator;
|
||||||
}, 0) + LENGTH_INFINITY
|
}, 0) + LENGTH_INFINITY
|
||||||
: undefined;
|
: 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
|
const postMidPointGap = shot
|
||||||
? postMidPointLayers!.reduce((accumulator, layer) => {
|
? postMidPointLayers!.reduce((accumulator, layer) => {
|
||||||
if (layer.type === null) return accumulator + layer.distance;
|
if (layer.type === null) return accumulator + layer.distance;
|
||||||
@@ -246,8 +260,9 @@ export function ShotDisplay() {
|
|||||||
(1 -
|
(1 -
|
||||||
distanceFromSpacedArmor! / shell.explosionRadius!) -
|
distanceFromSpacedArmor! / shell.explosionRadius!) -
|
||||||
1.1 *
|
1.1 *
|
||||||
(midPointLayer as ShotLayerNonExternal)
|
((midPointLayer as ShotLayerNonExternal)
|
||||||
.thicknessAngled,
|
.thicknessAngled +
|
||||||
|
preMidPointThickness!),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
0,
|
0,
|
||||||
@@ -255,7 +270,7 @@ export function ShotDisplay() {
|
|||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{shot.layers.map((layer, index) => {
|
{shot.layers.map((layer, index) => {
|
||||||
if (layer.type === null && !explosive) return null;
|
if (layer.type === null && (!explosive || splashing)) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex key={index} align="center" gap="2">
|
<Flex key={index} align="center" gap="2">
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ export function SpacedArmorSceneComponent({
|
|||||||
const distance = (
|
const distance = (
|
||||||
shot.layers.at(-1) as ShotLayerBase
|
shot.layers.at(-1) as ShotLayerBase
|
||||||
).point.distanceTo(intersections[0].point);
|
).point.distanceTo(intersections[0].point);
|
||||||
if (explosive)
|
if (explosive && !splashing)
|
||||||
remainingPenetration -= 0.5 * remainingPenetration * distance;
|
remainingPenetration -= 0.5 * remainingPenetration * distance;
|
||||||
const blocked = remainingPenetration < 0;
|
const blocked = remainingPenetration < 0;
|
||||||
|
|
||||||
@@ -205,7 +205,7 @@ export function SpacedArmorSceneComponent({
|
|||||||
const previousIntersection = intersections[layerIndex - 1];
|
const previousIntersection = intersections[layerIndex - 1];
|
||||||
const distance =
|
const distance =
|
||||||
intersection.distance - previousIntersection.distance;
|
intersection.distance - previousIntersection.distance;
|
||||||
if (explosive)
|
if (explosive && !splashing)
|
||||||
remainingPenetration -=
|
remainingPenetration -=
|
||||||
0.5 * remainingPenetration * distance;
|
0.5 * remainingPenetration * distance;
|
||||||
const wasted = remainingPenetration < 0;
|
const wasted = remainingPenetration < 0;
|
||||||
@@ -216,7 +216,7 @@ export function SpacedArmorSceneComponent({
|
|||||||
distance,
|
distance,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (wasted) return;
|
if (wasted && !splashing) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (intersection.object.userData.type === ArmorType.External) {
|
if (intersection.object.userData.type === ArmorType.External) {
|
||||||
@@ -233,7 +233,11 @@ export function SpacedArmorSceneComponent({
|
|||||||
status: blocked ? 'blocked' : 'penetration',
|
status: blocked ? 'blocked' : 'penetration',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (blocked) break;
|
if (splashing) {
|
||||||
|
remainingPenetration = Math.max(0, remainingPenetration);
|
||||||
|
} else if (blocked) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const type = intersection.object.userData.type;
|
const type = intersection.object.userData.type;
|
||||||
const angle = surfaceNormal.angleTo(shellNormal);
|
const angle = surfaceNormal.angleTo(shellNormal);
|
||||||
|
|||||||
Reference in new issue
Block a user