mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-23 11:39:50 +02:00
Merge branch 'main' into dev-embed
This commit is contained in:
commit
e99b125517
120 files changed
+3253
-2798
No files matched your search
@@ -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
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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>(
|
||||
|
||||
@@ -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());
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)),
|
||||
);
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}),
|
||||
),
|
||||
);
|
||||
@@ -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})`;
|
||||
}
|
||||
@@ -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;
|
||||
}, {}),
|
||||
);
|
||||
@@ -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}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
Reference in new issue
Block a user