Merge branch 'main' into dev-rinth

This commit is contained in:
TrèsAbhi committed 2024-04-22 12:43:22 -05:00
commit 13a901c839
38 files changed
+876 -308

No files matched your search

+2
View File
@@ -62,4 +62,6 @@ jobs:
$env:GH_TOKEN = "${{ secrets.GH_TOKEN }}"
$env:DISCORD_TOKEN = "${{ secrets.DISCORD_TOKEN }}"
$env:DATABASE_URL = "${{ secrets.DATABASE_URL }}"
$env:VIDEOS_SHEET = "${{ secrets.VIDEOS_SHEET }}"
$env:GOOGLE_APPLICATION_CREDENTIALS = "${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}"
yarn build:assets --all-targets --depot ${{ github.ref == 'refs/heads/main' && '--production' || '' }}
+2 -1
View File
@@ -18,4 +18,5 @@ temp/
/.VSCodeCounter
/.yarn
depots
public/sitemap*
public/sitemap*
/firebase.json
+1
View File
@@ -165,6 +165,7 @@
"xkdkw",
"XMLDVPL",
"YAMLDVPL",
"youtubers",
"zustand"
],
"deno.enable": false,
+19
View File
@@ -0,0 +1,19 @@
# Blitzkrieg 1.19.5
The tankopedia, now more helpful than ever! This update only affects the website.
## Features
- The Tankopedia pages for tanks now include videos from YouTubers
- The selection of tanks that have videos is very small currently but you can help expand it by dropping suggestions on [the official Discord server](https://discord.gg/nDt7AjGJQH)
- Added shell normalization and ricochet angles to the Tankopedia and Compare
## Fixes
- Removed undefined clipping planes
- Added proper keying to all JSX tree nodes, navbar menu items, antagonist bar shells, skill rows and fragments of tank stats in the Tankopedia
- The second half of barrels for tanks like the T-100 LT are now properly included
## Technical Features
- The list of videos is fetched transpiled from a Google Sheets spreadsheet into a CSV file in the asset repository
Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

After

Width:  |  Height:  |  Size: 148 KiB

+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "blitzrinth",
"version": "1.19.4",
"name": "blitzkrieg",
"version": "1.19.5",
"description": "🎉 Tools for everything World of Tanks Blitz",
"main": "src/server.ts",
"type": "module",
@@ -52,6 +52,7 @@
"express": "^4.18.2",
"false": "^0.0.4",
"fuzzysort": "^2.0.4",
"googleapis": "^134.0.0",
"immer": "^10.0.2",
"js-yaml": "^4.1.0",
"lodash": "^4.17.21",
+2
View File
@@ -1 +1,3 @@
google.com, pub-1289406790225052, DIRECT, f08c47fec0942fa0
appnexus.com, 14163, RESELLER
pubcenter.microsoft.com, 253617120, DIRECT
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 921 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

+4 -4
View File
@@ -38,7 +38,7 @@ import { DATA, POI } from './constants';
import { Avatar } from './skillIcons';
import { TankParameters } from './tankIcons';
interface Strings {
export interface BlitzStrings {
[key: string]: string;
}
type ShellKind =
@@ -334,7 +334,7 @@ const blitzShellKindToBlitzrinth: Record<ShellKind, ShellType> = {
const missingStrings: Record<string, string> = {
'#artefacts:tungstentip/name': 'Tungsten Shells',
};
const botPattern = /^.+((tutorial_bot(\d+)?)|(TU(R?)))$/;
export const botPattern = /^.+((tutorial_bot(\d+)?)|(TU(R?)))$/;
export async function definitions(production: boolean) {
console.log('Building definitions...');
@@ -361,12 +361,12 @@ export async function definitions(production: boolean) {
(nations) => nations.filter((nation) => nation !== 'common'),
);
const tankStringIdMap: Record<string, number> = {};
const stringsPreInstalled = await readYAMLDVPL<Strings>(
const stringsPreInstalled = await readYAMLDVPL<BlitzStrings>(
`${DATA}/${POI.strings}/en.yaml.dvpl`,
);
const stringsCache = await fetch(POI.cachedStrings)
.then((response) => response.text())
.then((string) => parseYaml(string) as Strings);
.then((string) => parseYaml(string) as BlitzStrings);
const optionalDevices = await readXMLDVPL<{ root: OptionalDevices }>(
`${DATA}/${POI.optionalDevices}.dvpl`,
);
+7 -5
View File
@@ -24,15 +24,17 @@ import { skillIcons } from './skillIcons';
import { tankArmors } from './tankArmors';
import { tankIcons } from './tankIcons';
import { tankModels } from './tankModels';
import { videos } from './videos';
if (!targets && !allTargets) throw new Error('No target(s) specified');
const methods = [
// definitions,
// scratchedFlags,
// currencies,
// skillIcons,
// circleFlags,
definitions,
videos,
scratchedFlags,
currencies,
skillIcons,
circleFlags,
shellIcons,
moduleIcons,
equipmentIcons,
-3
View File
@@ -28,9 +28,6 @@ export async function tankArmors(production: boolean) {
if (tankKey.includes('tutorial_bot')) return;
const id = toUniqueId(nation, tank.id);
console.log(`Building armor ${id} @ ${nation}/${tankKey}`);
const model = await extractArmor(DATA, `${nation}-${tankKey}`);
changes.push({
-3
View File
@@ -32,9 +32,6 @@ export async function tankModels(production: boolean) {
if (tankKey.includes('tutorial_bot')) return;
const id = toUniqueId(nation, tank.id);
console.log(`Building model ${id} @ ${nation}/${tankKey}`);
const parameters = await readYAMLDVPL<TankParameters>(
`${DATA}/${POI.tankParameters}/${nation}/${tankKey}.yaml.dvpl`,
);
+124
View File
@@ -0,0 +1,124 @@
import { readdir } from 'fs/promises';
import { google } from 'googleapis';
import { cloneDeep, uniqBy } from 'lodash';
import { parse as parseYaml } from 'yaml';
import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL';
import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL';
import { toUniqueId } from '../../src/core/blitz/toUniqueId';
import { commitAssets } from '../../src/core/blitzkrieg/commitAssets';
import {
VideoDefinitions,
videoDefinitions,
} from '../../src/core/blitzkrieg/videos';
import { DATA, POI } from './constants';
import { BlitzStrings, botPattern, VehicleDefinitionList } from './definitions';
const youtubers = [
'UCKBYXp4Xn2I2tL1UL4fpbhw', // droodles
'UCrQ-Dy8lVsm11u8pCJ8W7Tw', // fatness
];
export async function videos(production: boolean) {
console.log('Building videos...');
const currentVideos = await videoDefinitions;
const auth = await google.auth.getClient({
scopes: ['https://www.googleapis.com/auth/youtube.readonly'],
});
const youtube = google.youtube({ version: 'v3', auth });
const nations = await readdir(`${DATA}/${POI.vehicleDefinitions}`).then(
(nations) => nations.filter((nation) => nation !== 'common'),
);
const stringsPreInstalled = await readYAMLDVPL<BlitzStrings>(
`${DATA}/${POI.strings}/en.yaml.dvpl`,
);
const stringsCache = await fetch(POI.cachedStrings)
.then((response) => response.text())
.then((string) => parseYaml(string) as BlitzStrings);
const strings = {
...stringsPreInstalled,
...stringsCache,
};
const tanks: { id: number; name: string; tier: number }[] = [];
await Promise.all(
nations.map(async (nation) => {
const tankList = await readXMLDVPL<{ root: VehicleDefinitionList }>(
`${DATA}/${POI.vehicleDefinitions}/${nation}/list.xml.dvpl`,
);
await Promise.all(
Object.entries(tankList.root).map(async ([tankKey, tank]) => {
if (botPattern.test(tankKey)) return null;
const id = toUniqueId(nation, tank.id);
const name =
(tank.shortUserString
? strings[tank.shortUserString]
: undefined) ?? strings[tank.userString];
const tier = tank.level;
tanks.push({ id, name, tier });
}),
);
}),
);
const tanksSanitized = tanks
.sort((a, b) => b.tier - a.tier)
.sort((a, b) => {
const lastUpdatedA = currentVideos[a.id]?.lastUpdated ?? 0;
const lastUpdatedB = currentVideos[b.id]?.lastUpdated ?? 0;
return lastUpdatedA - lastUpdatedB;
});
const videos: VideoDefinitions = cloneDeep(currentVideos);
let done = 0;
for (const tank of tanksSanitized) {
try {
const results = await youtube.search.list({
part: ['snippet'],
q: `World of Tanks Blitz ${tank.name}`,
});
if (!results.data.items || results.data.items.length === 0) continue;
const items = uniqBy(
results.data.items.filter(
(item) =>
item.snippet?.channelId &&
item.id?.videoId &&
youtubers.includes(item.snippet.channelId),
),
(item) => item.snippet!.channelId,
);
videos[tank.id] = {
lastUpdated: Math.round(Date.now() / 1000),
videos: items.map((item) => item.id!.videoId!),
};
console.log(
`${++done} / ${tanks.length} (${Math.round(100 * (done / tanks.length))}%) done; found ${items.length} for ${tank.name}`,
);
await new Promise((resolve) => setTimeout(resolve, 1000 / 5));
} catch (error) {
console.warn('An error occurred, safely exiting prematurely...', error);
break;
}
}
const content = Object.entries(videos)
.map(
([id, { videos, lastUpdated }]) =>
`${id},${lastUpdated},${videos.join(',')}`,
)
.join('\n');
await commitAssets(
'videos',
[{ content, encoding: 'utf-8', path: 'definitions/videos.csv' }],
production,
);
}
+2 -2
View File
@@ -7,9 +7,9 @@ import { Roboto_Flex } from 'next/font/google';
import { usePathname } from 'next/navigation';
import { ReactNode, Suspense, useEffect, useState } from 'react';
import { Footer } from '../components/Footer';
import { Google } from '../components/Google';
import { Loader } from '../components/Loader';
import Navbar from '../components/Navbar';
import { Party3 } from '../components/Party3';
import isDev from '../core/blitzrinth/isDev';
import { isLocalhost } from '../core/blitzrinth/isLocalhost';
import { useFullScreen } from '../hooks/useFullScreen';
@@ -60,7 +60,7 @@ export default function RootLayout({ children }: RootLayoutProps) {
<meta property="og:site_name" content="Blitzrinth" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<Google />
<Party3 />
</head>
<body
+12 -1
View File
@@ -1233,10 +1233,21 @@ export default function Page() {
<Row name="Shells" value="shells" decimals={0} />
</>
)}
<Row name="Caliber" value="caliber" decimals={0} />
<Row name="Penetration" value="penetration" decimals={0} />
<Row name="Damage" value="damage" decimals={0} />
<Row name="Module damage" value="moduleDamage" decimals={0} />
<Row name="Caliber" value="caliber" decimals={0} />
<Row
name="Normalization"
value="shellNormalization"
decimals={0}
/>
<Row
name="Ricochet"
value="shellRicochet"
deltaType="lowerIsBetter"
decimals={0}
/>
<Row name="Shell velocity" value="shellVelocity" decimals={0} />
<Row
name="Aim time"
@@ -191,6 +191,7 @@ export function AntagonistBar({ floating }: AntagonistBarProps) {
<Flex>
{antagonist.turret.guns.map((gun, index) => (
<ModuleButton
key={gun.id}
rowChild
first={index === 0}
last={
@@ -9,7 +9,7 @@ import {
TextField,
} from '@radix-ui/themes';
import { debounce } from 'lodash';
import { use, useEffect, useRef, useState } from 'react';
import { Fragment, use, useEffect, useRef, useState } from 'react';
import { lerp } from 'three/src/math/MathUtils';
import { ShellButton } from '../../../../../../components/ModuleButtons/ShellButton';
import { applyPitchYawLimits } from '../../../../../../core/blitz/applyPitchYawLimits';
@@ -155,7 +155,6 @@ export function Characteristics() {
})}
</Flex>
</Flex>
<Info name="Gun type">{GUN_TYPE_NAMES[gun.type]}</Info>
<InfoWithDelta name="DPM" decimals={0} unit="hp / min">
{stats.dpm}
@@ -189,7 +188,6 @@ export function Characteristics() {
{gun.type !== 'regular' && (
<InfoWithDelta name="Shells">{stats.shells}</InfoWithDelta>
)}
{gun.type === 'autoReloader' ? (
<>
<Info indent name="Most optimal">
@@ -229,9 +227,6 @@ export function Characteristics() {
{stats.intraClip!}
</InfoWithDelta>
)}
<InfoWithDelta name="Caliber" decimals={0} unit="mm">
{stats.caliber}
</InfoWithDelta>
<InfoWithDelta decimals={0} name="Penetration" unit="mm">
{stats.penetration}
</InfoWithDelta>
@@ -295,6 +290,22 @@ export function Characteristics() {
{stats.explosionRadius!}
</InfoWithDelta>
)}
<InfoWithDelta name="Caliber" decimals={0} unit="mm">
{stats.caliber}
</InfoWithDelta>
<InfoWithDelta name="Normalization" decimals={0} unit="°">
{stats.shellNormalization}
</InfoWithDelta>
{shell.type !== 'he' && (
<InfoWithDelta
name="Ricochet"
decimals={0}
deltaType="lowerIsBetter"
unit="°"
>
{stats.shellRicochet!}
</InfoWithDelta>
)}
<InfoWithDelta name="Shell velocity" unit="m/s" decimals={0}>
{stats.shellVelocity}
</InfoWithDelta>
@@ -516,7 +527,7 @@ export function Characteristics() {
const count = member.count ?? 1;
return (
<>
<Fragment key={member.type}>
<InfoWithDelta
key={`${member.type}-root`}
name={`${CREW_MEMBER_NAMES[member.type]}${count > 1 ? ` x ${count}` : ''}`}
@@ -545,7 +556,7 @@ export function Characteristics() {
}
/>
)}
</>
</Fragment>
);
})}
</Flex>
@@ -124,11 +124,15 @@ export const TankModel = memo(() => {
event.stopPropagation();
}
return jsxTree(node, {
castShadow: true,
receiveShadow: true,
onPointerDown,
});
return jsxTree(
node,
{
castShadow: true,
receiveShadow: true,
onPointerDown,
},
node.uuid,
);
})}
<group ref={turretContainer}>
@@ -189,11 +193,15 @@ export const TankModel = memo(() => {
window.removeEventListener('pointerup', handlePointerUp);
}
return jsxTree(node, {
castShadow: true,
receiveShadow: true,
onPointerDown,
});
return jsxTree(
node,
{
castShadow: true,
receiveShadow: true,
onPointerDown,
},
node.uuid,
);
})}
<group ref={gunContainer}>
@@ -255,11 +263,15 @@ export const TankModel = memo(() => {
window.removeEventListener('pointerup', handlePointerUp);
}
return jsxTree(node, {
castShadow: true,
receiveShadow: true,
onPointerDown,
});
return jsxTree(
node,
{
castShadow: true,
receiveShadow: true,
onPointerDown,
},
node.uuid,
);
})}
</group>
</group>
@@ -0,0 +1,74 @@
import { InfoCircledIcon, PlusIcon } from '@radix-ui/react-icons';
import { Callout, Card, Flex, Heading, Link, Text } from '@radix-ui/themes';
import { use } from 'react';
import { videoDefinitions } from '../../../../../core/blitzkrieg/videos';
import { useFullScreen } from '../../../../../hooks/useFullScreen';
import { useDuel } from '../../../../../stores/duel';
export function Videos() {
const isFullScreen = useFullScreen();
const tank = useDuel((state) => state.protagonist!.tank);
const awaitedVideoDefinitions = use(videoDefinitions);
const videos = awaitedVideoDefinitions[tank.id]?.videos ?? [];
if (isFullScreen) return null;
return (
<Flex direction="column" gap="4" mt="4" align="start">
<Heading size="6">Review videos</Heading>
<Callout.Root>
<Callout.Icon>
<InfoCircledIcon />
</Callout.Icon>
<Callout.Text>
Some YouTubers may not have videos on the {tank.name} leading to
off-topic videos.
</Callout.Text>
</Callout.Root>
<Flex gap="3" wrap="wrap">
{videos.map((video) => (
<Link
key={video}
href={`https://www.youtube.com/watch?v=${video}`}
target="_blank"
>
<img
src={`https://i.ytimg.com/vi/${video}/hqdefault.jpg`}
style={{
aspectRatio: '16 / 9',
objectFit: 'cover',
height: 128,
borderRadius: 8,
}}
/>
</Link>
))}
<Link href="https://discord.gg/nDt7AjGJQH" target="_blank">
<Card
style={{
aspectRatio: '16 / 9',
height: 128,
}}
>
<Flex
align="center"
justify="center"
gap="1"
style={{
width: '100%',
height: '100%',
}}
>
<Text color="purple">
{videos.length === 0 ? 'Suggest' : 'Suggest more'} <PlusIcon />
</Text>
</Flex>
</Card>
</Link>
</Flex>
</Flex>
);
}
+4 -2
View File
@@ -17,6 +17,7 @@ import { Provisions } from './components/Characteristics/components/Provisions';
import { Skills } from './components/Characteristics/components/Skills';
import { TankSandbox } from './components/Model/TankSandbox';
import { Title } from './components/Title';
import { Videos } from './components/Videos';
export default function Page({ params }: { params: { id: string } }) {
const initialId = parseInt(params.id);
@@ -83,7 +84,7 @@ export default function Page({ params }: { params: { id: string } }) {
style={{
flex: 1,
width: '100%',
top: 64,
top: 64 + 16,
position: wideFormat ? 'sticky' : undefined,
}}
>
@@ -91,7 +92,6 @@ export default function Page({ params }: { params: { id: string } }) {
<TankSandbox />
<AntagonistBar />
</Flex>
<Flex gap="5" wrap="wrap">
<Modules />
<Provisions />
@@ -110,6 +110,8 @@ export default function Page({ params }: { params: { id: string } }) {
<Characteristics />
</Flex>
</Flex>
<Videos />
</>
)}
</PageWrapper>
+10 -1
View File
@@ -1,5 +1,8 @@
'use client';
import { Flex, Link } from '@radix-ui/themes';
import { use } from 'react';
import { AdHorizontal } from '../../../../components/Ads/Horizontal';
import PageWrapper from '../../../../components/PageWrapper';
import { tankDefinitions } from '../../../../core/blitzrinth/tankDefinitions';
@@ -8,11 +11,17 @@ export default function Page() {
return (
<PageWrapper>
<title>Super duper secret map for SEO</title>
<AdHorizontal />
<Flex direction="column" align="center">
{Object.values(awaitedTankDefinitions)
.sort((a, b) => b.tier - a.tier)
.map((tank) => (
<Link href={`./${tank.id}`}>{tank.name}</Link>
<Link href={`./${tank.id}`} key={tank.id}>
{tank.name}
</Link>
))}
</Flex>
</PageWrapper>
+24
View File
@@ -0,0 +1,24 @@
export function AdHorizontal() {
return (
<>
<script
async
src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-1289406790225052"
crossOrigin="anonymous"
/>
<ins
className="adsbygoogle"
style={{ display: 'block' }}
data-ad-client="ca-pub-1289406790225052"
data-ad-slot="2391822409"
data-ad-format="auto"
data-full-width-responsive="true"
/>
<script
dangerouslySetInnerHTML={{
__html: `(adsbygoogle = window.adsbygoogle || []).push({});`,
}}
/>
</>
);
}
@@ -153,14 +153,12 @@ export function CoreArmorSceneComponent({
return (
<>
{jsxTree(node, {
renderOrder: 0,
material: excludeMaterial,
})}
{jsxTree(node, {
renderOrder: 1,
material,
})}
{jsxTree(
node,
{ renderOrder: 0, material: excludeMaterial },
`${node.uuid}-exclude`,
)}
{jsxTree(node, { renderOrder: 1, material }, `${node.uuid}-include`)}
</>
);
}
@@ -259,9 +259,9 @@ export const SpacedArmorScene = memo<SpacedArmorSceneProps>(({ scene }) => {
</group>
{modelNodes.map((node) => {
const isCurrentGun =
node.name ===
`gun_${gunModelDefinition.model.toString().padStart(2, '0')}`;
const isCurrentGun = node.name.startsWith(
`gun_${gunModelDefinition.model.toString().padStart(2, '0')}`,
);
const isVisible = isCurrentGun;
if (!isVisible) return null;
@@ -37,8 +37,8 @@ export function SpacedArmorSubExternal({
depthTest: true,
depthWrite: false,
blending: AdditiveBlending,
clippingPlanes: clip ? [clip] : undefined,
clipping: true,
clipping: clip !== undefined,
...(clip ? { clippingPlanes: [clip] } : {}),
uniforms: {
thickness: { value: null },
@@ -107,26 +107,27 @@ export function SpacedArmorSubExternal({
return (
<>
{jsxTree(node, {
renderOrder: 3,
material: new MeshBasicMaterial({
colorWrite: false,
depthTest: true,
depthWrite: true,
clippingPlanes: clip ? [clip] : undefined,
}),
onClick() {},
userData: {
type: ArmorType.External,
thickness,
variant,
} satisfies ArmorUserData,
})}
{jsxTree(
node,
{
renderOrder: 3,
material: new MeshBasicMaterial({
colorWrite: false,
depthTest: true,
depthWrite: true,
...(clip ? { clippingPlanes: [clip] } : {}),
}),
onClick() {},
userData: {
type: ArmorType.External,
thickness,
variant,
} satisfies ArmorUserData,
},
`${node.uuid}-exclude`,
)}
{jsxTree(node, {
renderOrder: 4,
material,
})}
{jsxTree(node, { renderOrder: 4, material }, `${node.uuid}-include`)}
</>
);
}
@@ -113,20 +113,28 @@ export function SpacedArmorSubSpaced({
return (
<>
{jsxTree(node, {
renderOrder: 2,
material,
onClick() {},
userData: {
type: ArmorType.Spaced,
thickness,
} satisfies ArmorUserData,
})}
{jsxTree(
node,
{
renderOrder: 2,
material,
onClick() {},
userData: {
type: ArmorType.Spaced,
thickness,
} satisfies ArmorUserData,
},
`${node.uuid}-exclude`,
)}
{jsxTree(node, {
renderOrder: 5,
material: depthWriteMaterial,
})}
{jsxTree(
node,
{
renderOrder: 5,
material: depthWriteMaterial,
},
`${node.uuid}-include`,
)}
</>
);
}
@@ -81,251 +81,265 @@ export function SpacedArmorSceneComponent({
return (
<>
{props.type === ArmorType.Core &&
jsxTree(node, {
renderOrder: 0,
material: omitMaterial,
userData: {
type: ArmorType.Core,
thickness,
} satisfies ArmorUserData,
jsxTree(
node,
{
renderOrder: 0,
material: omitMaterial,
userData: {
type: ArmorType.Core,
thickness,
} satisfies ArmorUserData,
async onClick(event) {
event.stopPropagation();
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.04 : 1;
const shell = useDuel.getState().antagonist!.shell;
const explosive = isExplosive(shell.type);
const splashing = canSplash(shell.type);
const penetration =
resolveNearPenetration(shell.penetration) *
(hasCalibratedShells
? isExplosive(shell.type)
? 1.1
: 1.05
: 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 duel = useDuel.getState();
const hasCalibratedShells = await hasEquipment(
103,
duel.antagonist!.tank.equipment,
duel.antagonist!.equipmentMatrix,
);
const intersectionsWithDuplicates = intersectionsFiltered.slice(
const hasEnhancedArmor = await hasEquipment(
110,
duel.protagonist!.tank.equipment,
duel.protagonist!.equipmentMatrix,
);
const thicknessCoefficient = hasEnhancedArmor ? 1.04 : 1;
const shell = useDuel.getState().antagonist!.shell;
const explosive = isExplosive(shell.type);
const splashing = canSplash(shell.type);
const penetration =
resolveNearPenetration(shell.penetration) *
(hasCalibratedShells
? isExplosive(shell.type)
? 1.1
: 1.05
: 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,
intersectionsFiltered.findIndex(
({ object }) => object.userData.type === ArmorType.Core,
) + 1,
);
const firstGunIntersectionIndex =
intersectionsWithDuplicates.findIndex(
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 }) =>
object.userData.type === ArmorType.External &&
object.userData.variant === 'gun',
showSpacedArmor
? true
: object.userData.type === ArmorType.Core,
);
const firstTrackIntersectionIndex =
intersectionsWithDuplicates.findIndex(
({ object }) =>
object.userData.type === ArmorType.External &&
object.userData.variant === 'track',
const intersectionsWithDuplicates = intersectionsFiltered.slice(
0,
intersectionsFiltered.findIndex(
({ object }) => object.userData.type === ArmorType.Core,
) + 1,
);
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 (explosive && !splashing)
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 firstGunIntersectionIndex =
intersectionsWithDuplicates.findIndex(
({ object }) =>
object.userData.type === ArmorType.External &&
object.userData.variant === 'gun',
);
const thickness =
thicknessCoefficient * intersection.object.userData.thickness;
const firstTrackIntersectionIndex =
intersectionsWithDuplicates.findIndex(
({ object }) =>
object.userData.type === ArmorType.External &&
object.userData.variant === 'track',
);
let intersections = intersectionsWithDuplicates;
if (layerIndex !== 0 && startingIndex === 0) {
const previousIntersection = intersections[layerIndex - 1];
const distance =
intersection.distance - previousIntersection.distance;
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 (explosive && !splashing)
remainingPenetration -=
0.5 * remainingPenetration * distance;
const wasted = remainingPenetration < 0;
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,
type: null,
status: blocked ? 'blocked' : 'penetration',
distance,
});
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(shell.ricochet ?? 90);
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 (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 (explosive && !splashing)
remainingPenetration -=
0.5 * remainingPenetration * distance;
const wasted = remainingPenetration < 0;
if (!threeCalibersRule && angle >= ricochet) {
shot.layers.push({
type,
index: layerIndex,
shellNormal,
surfaceNormal,
point: intersection.point,
thickness,
thicknessAngled: finalThickness,
angle,
status: 'ricochet',
type: null,
status: wasted ? 'blocked' : 'penetration',
distance,
});
remainingPenetration *= 0.75;
if (wasted && !splashing) return;
}
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;
if (
intersection.object.userData.type === ArmorType.External
) {
remainingPenetration -= thickness;
const blocked = remainingPenetration < 0;
shot.layers.push({
type,
type: ArmorType.External,
index: layerIndex,
shellNormal,
surfaceNormal,
point: intersection.point,
thickness,
thicknessAngled: finalThickness,
angle,
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(shell.ricochet ?? 90);
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++;
}
layerIndex++;
loopIndex++;
}
}
useTankopediaTemporary.setState({ shot });
useTankopediaTemporary.setState({ shot });
},
},
})}
node.uuid,
)}
{props.type === ArmorType.Spaced && (
<SpacedArmorSubSpaced node={node} thickness={thickness} />
+1
View File
@@ -28,6 +28,7 @@ export function CrewSkillManager({
return (
<GenericTankComponentButton
key={skill}
special={!permanentSkills.includes(skill)}
first
last
+1
View File
@@ -131,6 +131,7 @@ export default function Navbar() {
return (
<LinkRadix
key={tool.id}
tabIndex={-1}
href={`/tools/${tool.id}`}
onClick={(event) => event.preventDefault()}
@@ -1,10 +1,16 @@
export function Google() {
import { useEffect } from 'react';
export function Party3() {
useEffect(() => {
(window as any).msAdsQueue = (window as any).msAdsQueue || [];
}, []);
return (
<>
{/* analytics */}
<script
async
src="https://www.googletagmanager.com/gtag/js?id=G-98NTMRHRP9"
src="https://www.googletagmanager.com/gtag/js?id=G-D8DT6EWVFJ"
/>
<script
dangerouslySetInnerHTML={{
@@ -13,7 +19,7 @@ export function Google() {
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-98NTMRHRP9');
gtag('config', 'G-D8DT6EWVFJ');
`,
}}
/>
@@ -24,6 +30,13 @@ export function Google() {
src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-1289406790225052"
crossOrigin="anonymous"
/>
{/* pub center */}
{/* <script
async
src="https://adsdk.microsoft.com/pubcenter/sdk.js?siteId=364477&publisherId=253617120"
crossOrigin="anonymous"
/> */}
</>
);
}
@@ -65,5 +65,6 @@ export default async function commitMultipleFiles(
repo,
ref: `heads/${branch}`,
sha: newCommitData.sha,
force: true,
});
}
@@ -430,8 +430,12 @@ export function tankCharacteristics(
const height = size[0];
const length = size[1];
const volume = width * height * length;
const shellNormalization = shell.normalization ?? 0;
const shellRicochet = shell.ricochet;
return {
shellNormalization,
shellRicochet,
dpm,
dpmMaximum,
dpmEffective,
+30
View File
@@ -0,0 +1,30 @@
import { asset } from './asset';
export interface VideoDefinitions {
[id: number]: {
videos: string[];
lastUpdated: number;
};
}
export const videoDefinitions = fetch(asset('definitions/videos.csv'))
.then((response) => response.text())
.then((text) =>
text
.split('\n')
.map((line) => {
const [idString, lastUpdatedString, ...videos] = line.split(',');
const id = Number(idString);
const lastUpdated = Number(lastUpdatedString);
return { id, videos, lastUpdated };
})
.filter(({ videos }) => videos.length > 0)
.reduce<VideoDefinitions>((accumulator, { id, videos, lastUpdated }) => {
accumulator[id] = {
videos,
lastUpdated,
};
return accumulator;
}, {}),
);
+35
View File
@@ -0,0 +1,35 @@
import { sumBy } from 'lodash';
import { use, useMemo } from 'react';
import {
TankDefinition,
tankDefinitions,
} from '../core/blitzkrieg/tankDefinitions';
import { useDuel } from '../stores/duel';
export enum UseEccentricityMode {
Tier,
Class,
}
export function useEccentricity(mode: UseEccentricityMode) {
const awaitedTankDefinitions = use(tankDefinitions);
const tank = useDuel((state) => state.protagonist!.tank);
const similarTanks = useMemo(
() =>
Object.values(awaitedTankDefinitions).filter(
(tankB) =>
tank.tier === tankB.tier &&
(mode === UseEccentricityMode.Class
? tank.class === tankB.class
: true),
),
[tank.id],
);
function eccentricity(property: (tank: TankDefinition) => number) {
const average = sumBy(similarTanks, property) / similarTanks.length;
return property(tank) / average;
}
return eccentricity;
}
+177 -5
View File
@@ -2590,6 +2590,13 @@ acorn@^8.11.3:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a"
integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==
agent-base@^7.0.2:
version "7.1.1"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.1.tgz#bdbded7dfb096b751a2a087eeeb9664725b2e317"
integrity sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==
dependencies:
debug "^4.3.4"
aggregate-error@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
@@ -2691,7 +2698,7 @@ [email protected]:
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978"
integrity sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==
base64-js@^1.3.1, base64-js@^1.5.1:
base64-js@^1.3.0, base64-js@^1.3.1, base64-js@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
@@ -2718,6 +2725,11 @@ big.js@^5.2.2:
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
bignumber.js@^9.0.0:
version "9.1.2"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c"
integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==
bl@^1.0.0:
version "1.2.3"
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.3.tgz#1e8dd80142eac80d7158c9dccc047fb620e035e7"
@@ -2807,6 +2819,11 @@ buffer-crc32@~0.2.3:
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
[email protected]:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
buffer-fill@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
@@ -2852,7 +2869,7 @@ cac@^6.7.14:
resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959"
integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==
call-bind@^1.0.6:
call-bind@^1.0.6, call-bind@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9"
integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==
@@ -3101,7 +3118,7 @@ [email protected]:
dependencies:
ms "2.0.0"
debug@^4.1.0, debug@^4.3.1, debug@^4.3.4:
debug@4, debug@^4.1.0, debug@^4.3.1, debug@^4.3.4:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@@ -3273,6 +3290,13 @@ [email protected]:
optionalDependencies:
wcwidth "^1.0.1"
[email protected], ecdsa-sig-formatter@^1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
dependencies:
safe-buffer "^5.0.1"
[email protected]:
version "1.1.1"
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
@@ -3450,6 +3474,11 @@ express@^4.18.2:
utils-merge "1.0.1"
vary "~1.1.2"
extend@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
"false@^0.0.4":
version "0.0.4"
resolved "https://registry.yarnpkg.com/false/-/false-0.0.4.tgz#8f55de5af5c03f50bf68aed57ed80e3d2176a517"
@@ -3629,6 +3658,25 @@ fuzzysort@^2.0.4:
resolved "https://registry.yarnpkg.com/fuzzysort/-/fuzzysort-2.0.4.tgz#a21d1ce8947eaf2797dc3b7c28c36db9d1165f84"
integrity sha512-Api1mJL+Ad7W7vnDZnWq5pGaXJjyencT+iKGia2PlHUcSsSzWwIQ3S1isiMpwpavjYtGd2FzhUIhnnhOULZgDw==
gaxios@^6.0.0, gaxios@^6.0.3, gaxios@^6.1.1:
version "6.5.0"
resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-6.5.0.tgz#21bc20e24f21189ce8907079b56205ff9fd2c0d7"
integrity sha512-R9QGdv8j4/dlNoQbX3hSaK/S0rkMijqjVvW3YM06CoBdbU/VdKd159j4hePpng0KuE6Lh6JJ7UdmVGJZFcAG1w==
dependencies:
extend "^3.0.2"
https-proxy-agent "^7.0.1"
is-stream "^2.0.0"
node-fetch "^2.6.9"
uuid "^9.0.1"
gcp-metadata@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-6.1.0.tgz#9b0dd2b2445258e7597f2024332d20611cbd6b8c"
integrity sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==
dependencies:
gaxios "^6.0.0"
json-bigint "^1.0.0"
gensync@^1.0.0-beta.2:
version "1.0.0-beta.2"
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
@@ -3710,6 +3758,38 @@ glsl-noise@^0.0.0:
resolved "https://registry.yarnpkg.com/glsl-noise/-/glsl-noise-0.0.0.tgz#367745f3a33382c0eeec4cb54b7e99cfc1d7670b"
integrity sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==
google-auth-library@^9.0.0, google-auth-library@^9.7.0:
version "9.8.0"
resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-9.8.0.tgz#43ae0f72dd72f49fd6a4a46d8eeffe2236691b20"
integrity sha512-TJJXFzMlVGRlIH27gYZ6XXyPf5Y3OItsKFfefsDAafNNywYRTkei83nEO29IrYj8GtdHWU78YnW+YZdaZaXIJA==
dependencies:
base64-js "^1.3.0"
ecdsa-sig-formatter "^1.0.11"
gaxios "^6.1.1"
gcp-metadata "^6.1.0"
gtoken "^7.0.0"
jws "^4.0.0"
googleapis-common@^7.0.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/googleapis-common/-/googleapis-common-7.1.0.tgz#e97e468a7ef2f230df8255c0f66701cf7e28947c"
integrity sha512-p3KHiWDBBWJEXk6SYauBEvxw5+UmRy7k2scxGtsNv9eHsTbpopJ3/7If4OrNnzJ9XMLg3IlyQXpVp8YPQsStiw==
dependencies:
extend "^3.0.2"
gaxios "^6.0.3"
google-auth-library "^9.7.0"
qs "^6.7.0"
url-template "^2.0.8"
uuid "^9.0.0"
googleapis@^134.0.0:
version "134.0.0"
resolved "https://registry.yarnpkg.com/googleapis/-/googleapis-134.0.0.tgz#1cde101a00916c677384b76bcd0eea57cf3f80bd"
integrity sha512-o8LhD1754W6MHWtpwAPeP1WUHgNxuMxCnLMDFlMKAA5kCMTNqX9/eaTXnkkAIv6YRfoKMQ6D1vyR6/biXuhE9g==
dependencies:
google-auth-library "^9.0.0"
googleapis-common "^7.0.0"
gopd@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
@@ -3722,6 +3802,14 @@ graceful-fs@^4.1.10, graceful-fs@^4.1.15, graceful-fs@^4.1.6, graceful-fs@^4.2.0
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
gtoken@^7.0.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-7.1.0.tgz#d61b4ebd10132222817f7222b1e6064bd463fc26"
integrity sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==
dependencies:
gaxios "^6.0.0"
jws "^4.0.0"
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
@@ -3796,6 +3884,14 @@ [email protected]:
statuses "2.0.1"
toidentifier "1.0.1"
https-proxy-agent@^7.0.1:
version "7.0.4"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz#8e97b841a029ad8ddc8731f26595bad868cb4168"
integrity sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==
dependencies:
agent-base "^7.0.2"
debug "4"
[email protected]:
version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@@ -3917,6 +4013,11 @@ is-stream@^1.1.0:
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==
is-stream@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
@@ -3980,6 +4081,13 @@ jsesc@^2.5.1:
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
json-bigint@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1"
integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==
dependencies:
bignumber.js "^9.0.0"
json-parse-even-better-errors@^2.3.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
@@ -4014,6 +4122,23 @@ jsonfile@^6.0.1:
optionalDependencies:
graceful-fs "^4.1.6"
jwa@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc"
integrity sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==
dependencies:
buffer-equal-constant-time "1.0.1"
ecdsa-sig-formatter "1.0.11"
safe-buffer "^5.0.1"
jws@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4"
integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==
dependencies:
jwa "^2.0.0"
safe-buffer "^5.0.1"
knip@^3.4.0:
version "3.13.2"
resolved "https://registry.yarnpkg.com/knip/-/knip-3.13.2.tgz#34f347a9b3ff1d45f3be7fed6bd3bb1ee5fbbaaa"
@@ -4397,6 +4522,13 @@ [email protected]:
data-uri-to-buffer "^3.0.1"
fetch-blob "^2.1.1"
node-fetch@^2.6.9:
version "2.7.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
dependencies:
whatwg-url "^5.0.0"
node-releases@^2.0.14:
version "2.0.14"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b"
@@ -4772,6 +4904,13 @@ [email protected]:
dependencies:
side-channel "^1.0.4"
qs@^6.7.0:
version "6.12.1"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.1.tgz#39422111ca7cbdb70425541cba20c7d7b216599a"
integrity sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==
dependencies:
side-channel "^1.0.6"
queue-microtask@^1.2.2:
version "1.2.3"
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
@@ -5011,7 +5150,7 @@ run-parallel@^1.1.9:
dependencies:
queue-microtask "^1.2.2"
[email protected], safe-buffer@^5.1.1, safe-buffer@~5.2.0:
[email protected], safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
@@ -5184,6 +5323,16 @@ side-channel@^1.0.4:
get-intrinsic "^1.2.4"
object-inspect "^1.13.1"
side-channel@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2"
integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==
dependencies:
call-bind "^1.0.7"
es-errors "^1.3.0"
get-intrinsic "^1.2.4"
object-inspect "^1.13.1"
signal-exit@^4.0.1:
version "4.1.0"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04"
@@ -5480,6 +5629,11 @@ [email protected]:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
troika-three-text@^0.49.0:
version "0.49.0"
resolved "https://registry.yarnpkg.com/troika-three-text/-/troika-three-text-0.49.0.tgz#bc2dc1924250c477cd39316cd83585dee12550e0"
@@ -5615,6 +5769,11 @@ update-browserslist-db@^1.0.13:
escalade "^3.1.1"
picocolors "^1.0.0"
url-template@^2.0.8:
version "2.0.8"
resolved "https://registry.yarnpkg.com/url-template/-/url-template-2.0.8.tgz#fc565a3cccbff7730c775f5641f9555791439f21"
integrity sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==
use-callback-ref@^1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.1.tgz#9be64c3902cbd72b07fe55e56408ae3a26036fd0"
@@ -5650,7 +5809,7 @@ [email protected]:
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
uuid@^9.0.1:
uuid@^9.0.0, uuid@^9.0.1:
version "9.0.1"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30"
integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==
@@ -5787,6 +5946,19 @@ [email protected]:
resolved "https://registry.yarnpkg.com/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz#3e1b422b3d87cd3cc77f2602c9db63bc0f6accbd"
integrity sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
whatwg-url@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
dependencies:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"
which@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"