Merge branch 'main' into dev-embed

This commit is contained in:
TrèsAbhi committed 2024-08-26 22:02:10 -05:00
commit e99b125517
120 files changed
+3253 -2798

No files matched your search

+3 -2
View File
@@ -2,7 +2,8 @@ import { Locale } from 'discord.js';
import { go } from 'fuzzysort';
import markdownEscape from 'markdown-escape';
import { UserError } from '../../hooks/userError';
import { tankDefinitions, tankNames } from '../blitzkit/tankDefinitions';
import { tankDefinitions } from '../blitzkit/tankDefinitions';
import { tankNames } from '../blitzkit/tankDefinitions/tankNames';
import { tankNamesTechTreeOnly } from '../discord/autocompleteTanks';
import { translator } from '../localization/translator';
@@ -19,7 +20,7 @@ export default async function resolveTankId(
`${tank}`,
await (techTreeOnly ? tankNamesTechTreeOnly : tankNames),
{
keys: ['combined'],
keys: ['searchableName', 'searchableNameDeburr', 'camouflages'],
limit: 1,
},
);
@@ -52,7 +52,7 @@ export const averageDefinitions = fetch(asset('averages/manifest.json'), {
.then((response) => response.arrayBuffer())
.then((buffer) =>
decode<AverageDefinitions>(
'blitzkit.AverageDefinitions',
'blitzkit/blitzkit.AverageDefinitions',
new Uint8Array(buffer),
),
);
@@ -0,0 +1,16 @@
import { asset } from './asset';
import { fetchCdonLz4 } from './fetchCdonLz4';
export type CamouflageDefinitions = Record<
number,
{
id: number;
name: string;
tankName?: string;
tankNameFull?: string;
}
>;
export const camouflageDefinitions = fetchCdonLz4<CamouflageDefinitions>(
asset('definitions/camouflages.cdon.lz4'),
);
@@ -7,41 +7,50 @@ export function checkConsumableProvisionInclusivity(
tank: TankDefinition,
gun: GunDefinition,
) {
const included = consumableProvision.include.every((rule) => {
switch (rule.type) {
case 'tier':
return rule.min <= tank.tier && tank.tier <= rule.max;
const included =
!consumableProvision.gameMode &&
consumableProvision.include?.every((rule) => {
switch (rule.type) {
case 'tier':
return rule.min <= tank.tier && tank.tier <= rule.max;
case 'ids':
return rule.ids.includes(tank.id);
case 'ids':
return rule.ids.includes(tank.id);
case 'nation':
return rule.nations.includes(tank.nation);
case 'nation':
return rule.nations.includes(tank.nation);
case 'category':
throw new SyntaxError('Category filtering found in include rule');
}
});
const excluded = consumableProvision.exclude?.some((rule) => {
switch (rule.type) {
case 'tier':
return rule.min <= tank.tier && tank.tier <= rule.max;
case 'category':
throw new SyntaxError('Category filtering found in include rule');
}
});
const excluded =
consumableProvision.gameMode ||
consumableProvision.exclude?.some((rule) => {
switch (rule.type) {
case 'tier':
return rule.min <= tank.tier && tank.tier <= rule.max;
case 'ids':
return rule.ids.includes(tank.id);
case 'ids':
return rule.ids.includes(tank.id);
case 'nation':
return rule.nations.includes(tank.nation);
case 'nation':
return rule.nations.includes(tank.nation);
case 'category':
return rule.categories.some((category) => {
switch (category) {
case 'clip':
return gun.type !== 'regular';
}
});
}
});
case 'category':
return rule.categories.some((category) => {
switch (category) {
case 'clip':
return gun.type !== 'regular';
}
});
}
});
return consumableProvision.include.length > 0 && included && !excluded;
return (
!consumableProvision.gameMode &&
consumableProvision.include?.length > 0 &&
included &&
!excluded
);
}
+11 -4
View File
@@ -22,14 +22,21 @@ export type TankFilterDefinition =
nations: string[];
};
export interface ConsumableEntry {
export type ConsumableEntry = {
id: number;
name: string;
cooldown: number;
duration?: number;
include: TankFilterDefinition[];
exclude?: TankFilterDefinition[];
}
} & (
| {
gameMode: false;
include: TankFilterDefinition[];
exclude?: TankFilterDefinition[];
}
| {
gameMode: true;
}
);
export interface ConsumableDefinitions {
[key: string]: ConsumableEntry;
+12
View File
@@ -1,9 +1,21 @@
import { asset } from './asset';
import { fetchCdonLz4 } from './fetchCdonLz4';
interface GameMode {
name: string;
}
export interface GameDefinitions {
version: string;
nations: string[];
gameModes: Record<number, GameMode>;
roles: Record<
number,
{
provisions: number[];
consumables: number[];
}
>;
}
export const gameDefinitions = fetchCdonLz4<GameDefinitions>(
+2
View File
@@ -1,3 +1,4 @@
import { invalidate } from '@react-three/fiber';
import { EventManager } from './eventManager';
export interface ModelTransformEventData {
@@ -6,3 +7,4 @@ export interface ModelTransformEventData {
}
export const modelTransformEvent = new EventManager<ModelTransformEventData>();
modelTransformEvent.on(() => invalidate());
+11 -4
View File
@@ -2,13 +2,20 @@ import { asset } from './asset';
import { TankFilterDefinition } from './consumableDefinitions';
import { fetchCdonLz4 } from './fetchCdonLz4';
export interface ProvisionEntry {
export type ProvisionEntry = {
id: number;
name: string;
include: TankFilterDefinition[];
exclude?: TankFilterDefinition[];
crew?: number;
}
} & (
| {
gameMode: false;
include: TankFilterDefinition[];
exclude?: TankFilterDefinition[];
}
| {
gameMode: true;
}
);
export interface ProvisionDefinitions {
[key: string]: ProvisionEntry;
+23 -12
View File
@@ -8,21 +8,32 @@ export function resolveDpm(
intraClipCoefficient = 1,
) {
const alpha = shell.damage.armor * damageCoefficient;
let dps: number;
if (gun.type === 'regular') {
return (alpha / (reloadCoefficient * gun.reload)) * 60;
dps = alpha / (reloadCoefficient * gun.reload);
} else if (gun.type === 'autoLoader') {
return (
((alpha * gun.count) /
(gun.reload + (gun.count - 1) * gun.intraClip * intraClipCoefficient)) *
60
);
dps =
(alpha * gun.count) /
(gun.reload * reloadCoefficient +
(gun.count - 1) * gun.intraClip * intraClipCoefficient);
} else {
return (
((alpha * gun.count) /
(reloadCoefficient * gun.reload.reduce((a, b) => a + b, 0) +
(gun.count - 1) * gun.intraClip * intraClipCoefficient)) *
60
);
const mostOptimalShell = gun.reload.reduce<null | {
index: number;
reload: number;
}>((current, reloadRaw, index) => {
const reload =
reloadRaw * reloadCoefficient +
(index > 0 ? gun.intraClip * intraClipCoefficient : 0);
if (current === null || reload < current.reload) {
return { index, reload };
}
return current;
}, null)!;
dps = alpha / mostOptimalShell?.reload;
}
return dps * 60;
}
+22
View File
@@ -0,0 +1,22 @@
import { decode } from '../protobuf/decode';
import { asset } from './asset';
export interface Reviews {
reviews: Record<number, Review>;
}
interface Review {
last_updated: number;
videos?: Video[];
}
export interface Video {
id: string;
author: string;
}
export const reviews = fetch(asset('definitions/reviews.pb'))
.then((response) => response.arrayBuffer())
.then((buffer) =>
decode<Reviews>('reviews/blitzkit.Reviews', new Uint8Array(buffer)),
);
+58 -42
View File
@@ -138,8 +138,15 @@ export function tankCharacteristics(
0,
) + (hasImprovedVentilation ? 0.08 : 0);
const commanderMastery = crewMastery + provisionCrewBonus;
const crewMemberMastery = commanderMastery * 1.1;
const loaderMastery =
commanderMastery *
(tank.crew.some(({ type }) => type === 'loader') ? 1.1 : 1.05);
const gunnerMastery =
commanderMastery *
(tank.crew.some(({ type }) => type === 'gunner') ? 1.1 : 1.05);
const driverMastery =
commanderMastery *
(tank.crew.some(({ type }) => type === 'driver') ? 1.1 : 1.05);
const intraClipCoefficient = coefficient([hasShellReloadBoost, -0.3]);
const damageCoefficient =
coefficient([hasTungsten, 0.15]) *
@@ -150,7 +157,7 @@ export function tankCharacteristics(
);
const reloadCoefficient =
(coefficient([hasGunRammer, -0.05]) *
coefficient([true, degressiveStat(crewMemberMastery)])) /
coefficient([true, degressiveStat(loaderMastery)])) /
coefficient([hasAdrenaline && gun.type === 'regular', 0.17]);
const penetrationCoefficient = coefficient([
hasCalibratedShells,
@@ -173,10 +180,10 @@ export function tankCharacteristics(
coefficient(
[hasEnhancedGunLayingDrive, -0.15],
[hasReticleCalibration, -0.3],
) * coefficient([true, degressiveStat(crewMemberMastery)]);
) * coefficient([true, degressiveStat(gunnerMastery)]);
const dispersionStillCoefficient =
coefficient([hasRefinedGun, -0.15], [hasReticleCalibration, -0.3]) *
coefficient([true, degressiveStat(crewMemberMastery)]);
coefficient([true, degressiveStat(gunnerMastery)]);
const dispersionDamagedCoefficient = coefficient([
hasVerticalStabilizer,
-0.12,
@@ -212,13 +219,13 @@ export function tankCharacteristics(
);
const turretTraverseCoefficient =
coefficient([hasStandardFuel, 0.03], [hasImprovedFuel, 0.1]) *
coefficient([true, progressiveStat(crewMemberMastery)]);
coefficient([true, progressiveStat(gunnerMastery)]);
const hullTraverseCoefficient =
coefficient(
[hasImprovedControl, 0.11],
[hasImprovedEnginePowerBoost, 0.01],
[true, crewSkills.virtuoso / 100],
) * coefficient([true, progressiveStat(crewMemberMastery)]);
) * coefficient([true, progressiveStat(driverMastery)]);
const resistanceHardCoefficient = coefficient([hasImprovedSuspension, -0.2]);
const resistanceMediumCoefficient = coefficient([
hasImprovedSuspension,
@@ -248,7 +255,18 @@ export function tankCharacteristics(
[hasGearOil, 2],
[hasImprovedGearOil, 4],
);
const camouflageCoefficientMoving = coefficient(
const camouflageCoefficientMoving = sum(
[
true,
coefficient([
camouflage,
tank.class === 'AT-SPG'
? 0.04
: tank.class === 'heavyTank'
? 0.03
: 0.02,
]),
],
[
hasCamouflageNet,
tank.class === 'heavyTank'
@@ -259,12 +277,22 @@ export function tankCharacteristics(
? 0.07
: 0.1,
],
[
camouflage,
tank.class === 'AT-SPG' ? 0.04 : tank.class === 'heavyTank' ? 0.03 : 0.02,
],
);
const camouflageCoefficientStill = coefficient(
const camouflageCoefficientStill = sum(
[
true,
coefficient(
[
camouflage,
tank.class === 'AT-SPG'
? 0.04
: tank.class === 'heavyTank'
? 0.03
: 0.02,
],
[true, crewSkills.camouflage * (3 / 100)],
),
],
[
hasCamouflageNet,
tank.class === 'heavyTank'
@@ -275,11 +303,6 @@ export function tankCharacteristics(
? 0.14
: 0.2,
],
[
camouflage,
tank.class === 'AT-SPG' ? 0.04 : tank.class === 'heavyTank' ? 0.03 : 0.02,
],
[true, crewSkills.camouflage * (3 / 100)],
);
const size = normalizeBoundingBox(
@@ -305,24 +328,27 @@ export function tankCharacteristics(
reloadCoefficient,
intraClipCoefficient,
);
const dpmMaximum =
const intraClip =
gun.type === 'regular' ? undefined : gun.intraClip * intraClipCoefficient;
const mostOptimalShellIndex =
gun.type === 'autoReloader'
? (
gun.count > 2
? gun.reload.at(-1)! < gun.reload.at(-2)!
: gun.reload.at(-1)! + gun.intraClip < gun.reload.at(-2)!
)
? ((damageCoefficient * shell.damage.armor) /
(gun.reload.at(-1)! * reloadCoefficient +
gun.intraClip * intraClipCoefficient)) *
60
: ((damageCoefficient * shell.damage.armor) /
(gun.reload[0] * reloadCoefficient)) *
60
? gun.reload.reduce<null | { index: number; reload: number }>(
(current, reloadRaw, index) => {
const reload =
reloadRaw * reloadCoefficient + (index > 0 ? intraClip! : 0);
if (current === null || reload < current.reload) {
return { index, reload };
}
return current;
},
null,
)!.index
: undefined;
const damage = shell.damage.armor * damageCoefficient;
const dpmEffective =
gun.type === 'autoReloader'
? gun.reload.at(-1)! < gun.reload.at(-2)!
? gun.reload[0] > gun.reload[1] + gun.intraClip
? ((damageCoefficient * shell.damage.armor) /
(gun.reload.at(-1)! * reloadCoefficient +
gun.intraClip * intraClipCoefficient)) *
@@ -343,24 +369,15 @@ export function tankCharacteristics(
damageCoefficient * shell.damage.armor * gun.reload.slice(1).length
: undefined;
const shells = gun.type === 'regular' ? 1 : gun.count;
const mostOptimalShellIndex =
gun.type === 'autoReloader'
? gun.reload.at(-1)! < gun.reload.at(-2)!
? gun.reload.length
: 1
: undefined;
const shellReloads =
gun.type === 'autoReloader'
? gun.reload.map((reload) => reload * reloadCoefficient)
: undefined;
const shellReload =
gun.type === 'autoReloader' ? undefined : gun.reload * reloadCoefficient;
const intraClip =
gun.type === 'regular' ? undefined : gun.intraClip * intraClipCoefficient;
const caliber = shell.caliber;
const penetration =
resolveNearPenetration(shell.penetration) * penetrationCoefficient;
const damage = shell.damage.armor * damageCoefficient;
const clipDamage = gun.type === 'regular' ? undefined : gun.count * damage;
const moduleDamage = shell.damage.module * damageCoefficient;
const explosionRadius = shell.explosionRadius;
@@ -470,7 +487,6 @@ export function tankCharacteristics(
shellNormalization,
shellRicochet,
dpm,
dpmMaximum,
dpmEffective,
shells,
mostOptimalShellIndex,
+6 -20
View File
@@ -1,4 +1,3 @@
import { deburr } from 'lodash';
import { Vector2Tuple } from 'three';
import { TankClass, TreeType } from '../../../components/Tanks';
import { asset } from '../asset';
@@ -41,8 +40,10 @@ export type ModuleDefinition =
| GunDefinition;
export interface TankDefinition {
id: number;
roles: Record<number, number>;
description?: string;
fixedCamouflage?: boolean;
camouflages?: number[];
ancestors?: number[];
successors?: number[];
crew: Crew[];
@@ -59,7 +60,7 @@ export interface TankDefinition {
turrets: TurretDefinition[];
engines: EngineDefinition[];
tracks: TrackDefinition[];
price: TankDefinitionPrice;
price: TankPrice;
xp?: number;
speed: {
forwards: number;
@@ -101,7 +102,7 @@ export interface EngineDefinition {
weight: number;
unlocks?: Unlock[];
}
export type TankDefinitionPrice =
export type TankPrice =
| { type: 'credits'; value: number }
| { type: 'gold'; value: number };
export interface TurretDefinition {
@@ -172,24 +173,9 @@ export const tankDefinitions = fetchCdonLz4<TankDefinitions>(
asset('definitions/tanks.cdon.lz4'),
);
export const tankDefinitionsArray = tankDefinitions.then((tanks) =>
Object.values(tanks).map((tank) => tank),
export const tanksDefinitionsArray = tankDefinitions.then((tanks) =>
Object.values(tanks),
);
export const tankNames = tankDefinitionsArray.then((tanks) =>
Promise.all(
tanks.map(async (tank, index) => {
const { id } = (await tankDefinitionsArray)[index];
return {
id,
original: tank.name,
combined: `${tank.name}${deburr(tank.name)}${tank.nameFull ? `${tank.nameFull}${deburr(tank.nameFull)}` : ''}`,
treeType: tank.treeType,
};
}),
),
);
export type Tier = (typeof TIERS)[number];
export const flags: Record<string, string> = {
@@ -0,0 +1,26 @@
import { deburr } from 'lodash';
import { tanksDefinitionsArray } from '.';
import { camouflageDefinitions } from '../camouflageDefinitions';
export const tankNames = Promise.all([
tanksDefinitionsArray,
camouflageDefinitions,
]).then(([tanks, camouflages]) =>
Promise.all(
tanks.map(async (tank, index) => {
const { id } = (await tanksDefinitionsArray)[index];
return {
id,
name: tank.name,
nameFull: tank.nameFull,
searchableName: tank.nameFull ?? tank.name,
searchableNameDeburr: deburr(tank.nameFull ?? tank.name),
camouflages: tank.camouflages
?.map((id) => camouflages[id].name)
.join(' '),
treeType: tank.treeType,
};
}),
),
);
+7
View File
@@ -0,0 +1,7 @@
type Name =
| `color-${'background' | `panel-${'solid' | 'translucent'}` | 'surface' | 'overlay'}`
| `radius-${1 | 2 | 3 | 4 | 5 | 6 | 'full'}`;
export function Var(name: Name) {
return `var(--${name})`;
}
-32
View File
@@ -1,32 +0,0 @@
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(',')
.filter(Boolean);
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;
}, {}),
);
+7 -5
View File
@@ -4,7 +4,7 @@ import {
CacheType,
} from 'discord.js';
import { go } from 'fuzzysort';
import { tankNames } from '../blitzkit/tankDefinitions';
import { tankNames } from '../blitzkit/tankDefinitions/tankNames';
import {
DISCORD_CHOICES_MAX_NAME_SIZE,
OVERFLOW_SUFFIX,
@@ -29,18 +29,20 @@ export default async function autocompleteTanks(
focusedOption.value,
await (techTreeOnly ? tankNamesTechTreeOnly : tankNames),
{
keys: ['combined'],
keys: ['searchableName', 'searchableNameDeburr', 'camouflages'],
limit: 10,
},
).map(async (item) => {
let name = item.obj.original;
let name = item.obj.searchableName;
if (name.length > DISCORD_CHOICES_MAX_NAME_SIZE) {
const overSize = name.length - DISCORD_CHOICES_MAX_NAME_SIZE;
name = `${item.obj.original.slice(
name = `${item.obj.searchableName.slice(
0,
item.obj.original.length - overSize - OVERFLOW_SUFFIX.length,
item.obj.searchableName.length -
overSize -
OVERFLOW_SUFFIX.length,
)}${OVERFLOW_SUFFIX}`;
}
+4
View File
@@ -2,5 +2,9 @@ import { lookup } from './lookup';
export async function encode(proto: string, data: any) {
const Message = await lookup(proto);
const verification = Message.verify(data);
if (verification !== null) throw new Error(verification);
return Message.encode(Message.create(data)).finish();
}
+4 -4
View File
@@ -2,13 +2,13 @@ import { Root } from 'protobufjs';
import { context } from '../blitzkit/context';
export async function lookup(proto: string) {
const name = proto.split('.')[0];
const [file, message] = proto.split('/');
const root = await new Root().load(
context === 'server'
? `public/protos/${name}.proto`
: `/protos/${name}.proto`,
? `public/protos/${file}.proto`
: `/protos/${file}.proto`,
{ keepCase: true },
);
return root.lookupType(proto);
return root.lookupType(message);
}
+23 -2
View File
@@ -1,5 +1,12 @@
import { Draft, produce } from 'immer';
import { createContext, ReactNode, useContext, useRef } from 'react';
import {
createContext,
ReactNode,
useContext,
useEffect,
useRef,
useState,
} from 'react';
import { StoreApi, useStore as useStoreZustand } from 'zustand';
type StoreProviderProps<InitData> = {
@@ -49,6 +56,20 @@ export function createNextSafeStore<Store extends StoreApi<unknown>, InitData>(
);
}
function useDeferred<Slice = Type>(
initial: Slice,
selector: (store: Type) => Slice = (state) => state as Slice,
) {
const [state, setState] = useState(initial);
const trueState = use(selector);
useEffect(() => {
setState(trueState);
}, [trueState]);
return state;
}
function useMutation() {
const counterStoreContext = useStore();
@@ -61,5 +82,5 @@ export function createNextSafeStore<Store extends StoreApi<unknown>, InitData>(
};
}
return { Provider, use, useMutation, useStore };
return { Provider, use, useMutation, useStore, useDeferred };
}