@@ -547,8 +562,8 @@ export const replayCommand = new Promise
((resolve) => {
@@ -569,8 +584,8 @@ export const replayCommand = new Promise((resolve) => {
,
- buttonLink(submissionData.data.view_url, 'WoT Inspector'),
- buttonLink(submissionData.data.download_url, 'Download'),
+ buttonLink(submissionData.data.view_url, "WoT Inspector"),
+ buttonLink(submissionData.data.download_url, "Download"),
];
},
});
diff --git a/packages/bot/src/core/blitzkit/nonBlockingPromises.ts b/packages/bot/src/core/blitzkit/nonBlockingPromises.ts
index 72695ae8b..647a955d0 100644
--- a/packages/bot/src/core/blitzkit/nonBlockingPromises.ts
+++ b/packages/bot/src/core/blitzkit/nonBlockingPromises.ts
@@ -1,11 +1,13 @@
import {
+ fetchAverageDefinitions,
fetchDiscoveredIdsDefinitions,
fetchGameDefinitions,
fetchMapDefinitions,
fetchTankDefinitions,
fetchTankNames,
-} from '@blitzkit/core';
+} from "@blitzkit/core";
+export const averageDefinitions = fetchAverageDefinitions();
export const tankDefinitions = fetchTankDefinitions();
export const gameDefinitions = fetchGameDefinitions();
export const tankNames = fetchTankNames();
diff --git a/packages/bot/src/core/blitzkit/processes.ts b/packages/bot/src/core/blitzkit/processes.ts
index 096fbdb25..a639e25cc 100644
--- a/packages/bot/src/core/blitzkit/processes.ts
+++ b/packages/bot/src/core/blitzkit/processes.ts
@@ -1,29 +1,37 @@
-import { usersDatabase } from '../db/users';
-import { client } from '../discord/client';
+import { usersDatabase } from "../db/users";
+import { client } from "../discord/client";
-const EXIT_EVENTS = [
- 'beforeExit',
- 'exit',
- 'SIGINT',
- 'SIGTERM',
- 'SIGUSR1',
- 'SIGUSR2',
-];
+const EXIT_EVENTS = ["SIGINT", "SIGTERM", "SIGUSR1", "SIGUSR2"];
-async function cleanup() {
- console.log('Cleaning up...');
+let cleaningUp = false;
- await Promise.all([usersDatabase.$disconnect(), client.destroy()]);
+async function cleanup(signal: string) {
+ if (cleaningUp) return;
+ cleaningUp = true;
- console.log('Gracefully exiting...');
+ console.log(`Cleaning up... (${signal})`);
+
+ try {
+ await Promise.all([usersDatabase.$disconnect(), client.destroy()]);
+ } catch (err) {
+ console.error("Cleanup error:", err);
+ }
+
+ console.log("Gracefully exiting...");
- EXIT_EVENTS.forEach((event) => process.off(event, cleanup));
process.exit(0);
}
export function registerProcesses() {
- process.on('uncaughtException', console.error);
- client.on('error', console.error);
+ process.on("uncaughtException", (err) => {
+ console.error("uncaughtException:", err);
+ });
- EXIT_EVENTS.forEach((event) => process.on(event, cleanup));
+ process.on("unhandledRejection", (err) => {
+ console.error("unhandledRejection:", err);
+ });
+
+ client.on("error", console.error);
+
+ EXIT_EVENTS.forEach((event) => process.on(event, () => cleanup(event)));
}
diff --git a/packages/bot/src/core/blitzstars/filterStats.ts b/packages/bot/src/core/blitzstars/filterStats.ts
index 91c9ad4c1..9f936d87d 100644
--- a/packages/bot/src/core/blitzstars/filterStats.ts
+++ b/packages/bot/src/core/blitzstars/filterStats.ts
@@ -3,8 +3,10 @@ import type { SupplementaryStats } from "@blitzkit/core/src/blitz/getAccountInfo
import { calculateWN8 } from "@blitzkit/core/src/statistics/calculateWN8";
import type { BlitzStats } from "@blitzkit/core/src/statistics/compositeStats";
import { sumAllStats } from "@blitzkit/core/src/statistics/sumAllStats";
-import { tankDefinitions } from "../blitzkit/nonBlockingPromises";
-import { blitzStarsTankAverages } from "./tankAverages";
+import {
+ averageDefinitions,
+ tankDefinitions,
+} from "../blitzkit/nonBlockingPromises";
export interface StatFilters {
nation?: string;
@@ -16,13 +18,13 @@ export interface StatFilters {
export async function filterStats(
{ diff, order }: DiffedTankStats,
- filters: StatFilters
+ filters: StatFilters,
) {
// tank, if provided, takes priority over all other filters
if (filters.tank) filters = { tank: filters.tank };
const awaitedTankDefinitions = await tankDefinitions;
- const awaitedTankAverages = await blitzStarsTankAverages;
+ const awaitedTankAverages = await averageDefinitions;
const filteredOrder = order.filter((id) => {
const entry = awaitedTankDefinitions.tanks[id];
@@ -44,26 +46,28 @@ export async function filterStats(
const stats = sumAllStats(filteredOrder.map((id) => diff[id]));
const battlesOfTanksWithAverages = filteredOrder.reduce
(
(accumulator, id) =>
- awaitedTankAverages[id] ? accumulator + diff[id].battles : accumulator,
- 0
+ awaitedTankAverages.averages[id]
+ ? accumulator + diff[id].battles
+ : accumulator,
+ 0,
);
const battlesOfTanksWithTankDefinition = filteredOrder.reduce(
(accumulator, id) =>
awaitedTankDefinitions.tanks[id]
? accumulator + diff[id].battles
: accumulator,
- 0
+ 0,
);
const supplementary = {
WN8:
filteredOrder.reduce(
(accumulator, id) =>
- awaitedTankAverages[id]
+ awaitedTankAverages.averages[id]
? accumulator +
- calculateWN8(awaitedTankAverages[id].all, diff[id]) *
+ calculateWN8(awaitedTankAverages.averages[id].mu, diff[id]) *
diff[id].battles
: accumulator,
- 0
+ 0,
) / battlesOfTanksWithAverages,
tier:
filteredOrder.reduce(
@@ -72,7 +76,7 @@ export async function filterStats(
? accumulator +
awaitedTankDefinitions.tanks[id]!.tier * diff[id].battles
: accumulator,
- 0
+ 0,
) / battlesOfTanksWithTankDefinition,
} satisfies SupplementaryStats;
diff --git a/packages/bot/src/core/blitzstars/tankAverages.ts b/packages/bot/src/core/blitzstars/tankAverages.ts
deleted file mode 100644
index 66c6edd36..000000000
--- a/packages/bot/src/core/blitzstars/tankAverages.ts
+++ /dev/null
@@ -1,101 +0,0 @@
-import { BlitzStats } from '@blitzkit/core';
-import { context } from '@blitzkit/core/src/blitzkit/context';
-
-interface SpecialStats {
- winrate: number;
- damageRatio: number;
- kdr: number;
- damagePerBattle: number;
- killsPerBattle: number;
- hitsPerBattle: number;
- spotsPerBattle: number;
- wpm: number;
- dpm: number;
- kpm: number;
- hitRate: number;
- survivalRate: number;
-}
-
-export type Percentiles = [
- number,
- number,
- number,
- number,
- number,
- number,
- number,
- number,
- number,
-];
-
-export interface SpecialPercentiles {
- winRate: Percentiles;
- damageRatio: Percentiles;
- kdr: Percentiles;
- damagePerBattle: Percentiles;
- killsPerBattle: Percentiles;
- hitsPerBattle: Percentiles;
- spotsPerBattle: Percentiles;
- wpm: Percentiles;
- dpm: Percentiles;
- kpm: Percentiles;
- hitRate: Percentiles;
- survivalRate: Percentiles;
-}
-
-export interface MeanSd {
- winrateMean: number;
- winrateSd: number;
- hitRateMean: number;
- hitRateSd: number;
- survivalRateMean: number;
- survivalRateSd: number;
- damageRatioMean: number;
- damageRatioSd: number;
- kdrMean: number;
- kdrSd: number;
- dpbMean: number;
- dpbSd: number;
- kpbMean: number;
- kpbSd: number;
- hpbMean: number;
- hpbSd: number;
- spbMean: number;
- spbSd: number;
-}
-
-export interface IndividualTankAverage {
- _id: number;
- battle_life_time: number;
- total_battle_life_time: number;
- number_of_players: number;
- tank_id: number;
- all: BlitzStats;
- special: SpecialStats;
- percentiles: SpecialPercentiles;
- percentiles_player_count: number;
- meanSd: MeanSd;
- shotEff: number;
- last_update: number;
-}
-
-export type BlitzStarsTankAverages = Record;
-
-export const blitzStarsTankAverages = fetch(
- 'https://www.blitzstars.com/api/tankaverages.json',
-)
- .then((response) => response.json())
- .then((json) => {
- if (context === 'server') {
- const partialTankAverages: BlitzStarsTankAverages = {};
-
- (json as IndividualTankAverage[]).forEach((individualTankAverage) => {
- partialTankAverages[individualTankAverage.tank_id] =
- individualTankAverage;
- });
-
- return partialTankAverages;
- } else {
- return json as BlitzStarsTankAverages;
- }
- });
diff --git a/packages/bot/src/core/discord/client.ts b/packages/bot/src/core/discord/client.ts
index 5e9931914..a83fb3822 100644
--- a/packages/bot/src/core/discord/client.ts
+++ b/packages/bot/src/core/discord/client.ts
@@ -1,9 +1,9 @@
-import { Client, GatewayIntentBits } from 'discord.js';
-import { interactionCreate } from '../../events/interactionCreate';
-import { ready } from '../../events/ready';
+import { Client, GatewayIntentBits } from "discord.js";
+import { interactionCreate } from "../../events/interactionCreate";
+import { ready } from "../../events/ready";
export const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers],
})
- .on('ready', ready)
- .on('interactionCreate', interactionCreate);
+ .on("clientReady", ready)
+ .on("interactionCreate", interactionCreate);
diff --git a/packages/bot/src/core/discord/createLocalizedCommand.ts b/packages/bot/src/core/discord/createLocalizedCommand.ts
index b0f03e0e7..acb00ca49 100644
--- a/packages/bot/src/core/discord/createLocalizedCommand.ts
+++ b/packages/bot/src/core/discord/createLocalizedCommand.ts
@@ -22,11 +22,11 @@ export function createLocalizedCommand(
command: string,
sub?:
| CreateLocalizedCommandSubcommand[]
- | CreateLocalizedCommandSubcommandGroup[]
+ | CreateLocalizedCommandSubcommandGroup[],
) {
const commandPathItem = command.replaceAll(
"-",
- "_"
+ "_",
) as keyof BlitzKitStrings["bot"]["commands"];
const { strings } = translator(Locale.EnglishUS);
const slashCommand = new SlashCommandBuilder()
@@ -35,14 +35,14 @@ export function createLocalizedCommand(
localizationObject(
(strings) => strings.bot.commands[commandPathItem].name,
undefined,
- true
- )
+ true,
+ ),
)
.setDescription(strings.bot.commands[commandPathItem].description)
.setDescriptionLocalizations(
localizationObject(
- (strings) => strings.bot.commands[commandPathItem].description
- )
+ (strings) => strings.bot.commands[commandPathItem].description,
+ ),
);
if (sub) {
@@ -56,7 +56,7 @@ export function createLocalizedCommand(
}
).subcommands[subItem.subcommand].name,
undefined,
- true
+ true,
);
const subcommandDescriptionLocalizations = localizationObject(
(strings) =>
@@ -64,7 +64,7 @@ export function createLocalizedCommand(
strings.bot.commands[commandPathItem] as unknown as {
subcommands: Record;
}
- ).subcommands[subItem.subcommand].description
+ ).subcommands[subItem.subcommand].description,
);
slashCommand.addSubcommand((option) => {
@@ -78,7 +78,7 @@ export function createLocalizedCommand(
strings.bot.commands[commandPathItem] as unknown as {
subcommands: Record;
}
- ).subcommands[subItem.subcommand].description
+ ).subcommands[subItem.subcommand].description,
)
.setDescriptionLocalizations(subcommandDescriptionLocalizations);
});
@@ -91,7 +91,7 @@ export function createLocalizedCommand(
}
).groups[subItem.group].name,
undefined,
- true
+ true,
);
const subcommandDescriptionLocalizations = localizationObject(
(strings) =>
@@ -99,7 +99,7 @@ export function createLocalizedCommand(
strings.bot.commands[commandPathItem] as unknown as {
groups: Record;
}
- ).groups[subItem.group].description
+ ).groups[subItem.group].description,
);
slashCommand.addSubcommandGroup((option) => {
@@ -113,7 +113,7 @@ export function createLocalizedCommand(
strings.bot.commands[commandPathItem] as unknown as {
groups: Record;
}
- ).groups[subItem.group].description
+ ).groups[subItem.group].description,
)
.setDescriptionLocalizations(subcommandDescriptionLocalizations);
});
diff --git a/packages/bot/src/events/interactionCreate/handlers/autocomplete.ts b/packages/bot/src/events/interactionCreate/handlers/autocomplete.ts
index 78b9f7afb..59ec8f072 100644
--- a/packages/bot/src/events/interactionCreate/handlers/autocomplete.ts
+++ b/packages/bot/src/events/interactionCreate/handlers/autocomplete.ts
@@ -1,10 +1,10 @@
-import { AutocompleteInteraction, CacheType } from 'discord.js';
-import { commands } from '..';
+import { AutocompleteInteraction, CacheType } from "discord.js";
+import { commands } from "..";
export async function handleAutocomplete(
interaction: AutocompleteInteraction,
) {
- const command = (await commands)[interaction.commandName];
+ const command = commands[interaction.commandName];
if (!command) {
console.error(`No command matching ${interaction.commandName} was found.`);
diff --git a/packages/bot/src/events/interactionCreate/handlers/button.tsx b/packages/bot/src/events/interactionCreate/handlers/button.tsx
index 96b78e4a6..8ac4b7269 100644
--- a/packages/bot/src/events/interactionCreate/handlers/button.tsx
+++ b/packages/bot/src/events/interactionCreate/handlers/button.tsx
@@ -1,13 +1,13 @@
-import { ButtonInteraction, CacheType } from 'discord.js';
-import { commands } from '..';
-import { normalizeInteractionReturnable } from '../../../core/discord/normalizeInteractionReturnable';
+import { ButtonInteraction, CacheType } from "discord.js";
+import { commands } from "..";
+import { normalizeInteractionReturnable } from "../../../core/discord/normalizeInteractionReturnable";
export async function handleButton(interaction: ButtonInteraction) {
await interaction.deferUpdate();
const url = new URL(`https://example.com/${interaction.customId}`);
- const commandName = url.pathname.split('/')[1];
- const button = (await commands)[commandName]?.button;
+ const commandName = url.pathname.split("/")[1];
+ const button = commands[commandName]?.button;
if (!button) {
throw new Error(`Button handler not found for "${commandName}"`);
@@ -16,7 +16,7 @@ export async function handleButton(interaction: ButtonInteraction) {
if (interaction.message.interaction?.user.id !== interaction.user.id) {
interaction.followUp({
ephemeral: true,
- content: 'You are not the author of this command.',
+ content: "You are not the author of this command.",
});
return;
}
diff --git a/packages/bot/src/events/interactionCreate/handlers/chatInputCommand.ts b/packages/bot/src/events/interactionCreate/handlers/chatInputCommand.ts
index 5c2197c59..d385a036a 100644
--- a/packages/bot/src/events/interactionCreate/handlers/chatInputCommand.ts
+++ b/packages/bot/src/events/interactionCreate/handlers/chatInputCommand.ts
@@ -17,12 +17,11 @@ import { translator } from "../../../core/localization/translator";
import type { Writeable } from "../../../types/writable";
export async function handleChatInputCommand(
- interaction: ChatInputCommandInteraction
+ interaction: ChatInputCommandInteraction,
) {
await interaction.deferReply();
- const awaitedCommands = await commands;
- const registry = awaitedCommands[interaction.commandName];
+ const registry = commands[interaction.commandName];
try {
const returnable = await registry.handler(interaction);
@@ -36,7 +35,7 @@ export async function handleChatInputCommand(
if (typeof object === "string") {
chunks.push([object]);
} else chunks.at(-1)?.push(object!);
- }
+ },
);
let index = 0;
@@ -59,7 +58,7 @@ export async function handleChatInputCommand(
} else if (psa.data.type === "image") {
if (!reply.files) reply.files = [];
(reply.files as Writeable).push(
- new AttachmentBuilder(psa.data.image)
+ new AttachmentBuilder(psa.data.image),
);
reply.content = reply.content ?? psa.data.title;
}
@@ -72,7 +71,7 @@ export async function handleChatInputCommand(
(
reply.components[0] as ActionRowBuilder
).addComponents(
- psa.data.links.map(({ title, url }) => buttonLink(url, title))
+ psa.data.links.map(({ title, url }) => buttonLink(url, title)),
);
}
}
@@ -103,7 +102,7 @@ export async function handleChatInputCommand(
} else if (psa.data.type === "image") {
if (!followUp.files) followUp.files = [];
(followUp.files as Writeable).push(
- new AttachmentBuilder(psa.data.image)
+ new AttachmentBuilder(psa.data.image),
);
followUp.content = followUp.content ?? psa.data.title;
}
@@ -116,7 +115,7 @@ export async function handleChatInputCommand(
(
followUp.components[0] as ActionRowBuilder
).addComponents(
- psa.data.links.map(({ title, url }) => buttonLink(url, title))
+ psa.data.links.map(({ title, url }) => buttonLink(url, title)),
);
}
@@ -133,7 +132,7 @@ export async function handleChatInputCommand(
new ButtonBuilder()
.setLabel(strings.bot.common.errors.get_help)
.setURL("https://discord.gg/nDt7AjGJQH")
- .setStyle(ButtonStyle.Link)
+ .setStyle(ButtonStyle.Link),
),
];
if (error instanceof UserError) {
diff --git a/packages/bot/src/events/interactionCreate/index.ts b/packages/bot/src/events/interactionCreate/index.ts
index b1d13a0a3..9eef8b024 100644
--- a/packages/bot/src/events/interactionCreate/index.ts
+++ b/packages/bot/src/events/interactionCreate/index.ts
@@ -72,13 +72,13 @@ interface CommandRegistryImplicit extends CommandRegistryBase {
interface CommandRegistryExplicit extends CommandRegistryBase {
handlesInteraction?: false;
handler: (
- interaction: ChatInputCommandInteraction
+ interaction: ChatInputCommandInteraction,
) => InteractionReturnable;
}
export type CommandRegistry = CommandRegistryImplicit | CommandRegistryExplicit;
-export const COMMANDS_RAW: Promise[] = [
+export const commandsArray: Promise[] = [
// auctionCommand,
permissionsCommand,
debugCommand,
@@ -101,34 +101,28 @@ export const COMMANDS_RAW: Promise[] = [
replayCommand,
];
-export const commands = Promise.allSettled(COMMANDS_RAW).then((rawCommands) => {
- return rawCommands.reduce>(
- (commands, registry, index) => {
- if (registry.status === "rejected") {
- console.warn(
- `Command ${index} failed to load; skipping...`,
- registry.reason
- );
- return commands;
- }
- return { ...commands, [registry.value.command.name]: registry.value };
- },
- {}
- );
-});
+export const commands: Record = {};
+const commandsFulfilled: CommandRegistry[] = [];
+
+let index = 0;
+for (const registry of await Promise.allSettled(commandsArray)) {
+ if (registry.status === "rejected") {
+ console.warn(`Command ${index} rejected`, registry.reason);
+ continue;
+ }
+
+ commandsFulfilled.push(registry.value);
+ commands[registry.value.command.name] = registry.value;
+ index++;
+}
const rest = new REST().setToken(assertSecret(import.meta.env.DISCORD_TOKEN));
+const body = commandsFulfilled.map((registry) => registry.command.toJSON());
+const applicationCommandsRoute = Routes.applicationCommands(
+ assertSecret(import.meta.env.DISCORD_CLIENT_ID),
+);
-commands.then((awaitedCommands) => {
- const body = Object.values(awaitedCommands).map((registry) =>
- registry.command.toJSON()
- );
-
- rest.put(
- Routes.applicationCommands(assertSecret(import.meta.env.DISCORD_CLIENT_ID)),
- { body }
- );
-});
+await rest.put(applicationCommandsRoute, { body });
export async function interactionCreate(interaction: Interaction) {
if (interaction.isAutocomplete()) {
diff --git a/packages/bot/src/main.ts b/packages/bot/src/main.ts
index c494ba6ad..b4f4f3268 100644
--- a/packages/bot/src/main.ts
+++ b/packages/bot/src/main.ts
@@ -1,9 +1,9 @@
-import { assertSecret } from '@blitzkit/core';
-import { ActivityType, ShardingManager } from 'discord.js';
+import { assertSecret } from "@blitzkit/core";
+import { ActivityType, ShardingManager } from "discord.js";
import {
discoveredIdsDefinitions,
tankDefinitions,
-} from './core/blitzkit/nonBlockingPromises';
+} from "./core/blitzkit/nonBlockingPromises";
let iteration = 0;
const interval = setInterval(async () => {
@@ -11,15 +11,15 @@ const interval = setInterval(async () => {
console.log(`Attempting launch on iteration ${iteration}`);
- const manager = new ShardingManager('dist/bot/workers/bot.js', {
+ const manager = new ShardingManager("dist/bot/workers/bot.js", {
token: assertSecret(import.meta.env.DISCORD_TOKEN),
});
const shards = await manager
- .on('shardCreate', (shard) => {
+ .on("shardCreate", (shard) => {
clearInterval(interval);
console.log(`🟡 Launching shard ${shard.id}`);
- shard.on('ready', () => console.log(`🟢 Launched shard ${shard.id}`));
+ shard.on("ready", () => console.log(`🟢 Launched shard ${shard.id}`));
})
.spawn();
@@ -29,7 +29,7 @@ const interval = setInterval(async () => {
let users = 0;
for (const [, shard] of shards) {
- const guilds = (await shard.fetchClientValue('guilds.cache')) as any;
+ const guilds = (await shard.fetchClientValue("guilds.cache")) as any;
servers += guilds.length;
@@ -66,7 +66,7 @@ const interval = setInterval(async () => {
for (const guild of client.guilds.cache.values()) {
try {
const botMember = await guild.members.fetch(client.user.id);
- await botMember.setNickname('BlitzKit');
+ await botMember.setNickname("BlitzKit");
} catch {}
}
},
@@ -74,7 +74,7 @@ const interval = setInterval(async () => {
);
} catch (error) {
console.warn(
- 'An error occurred when setting up presence, ignoring to keep the process alive',
+ "An error occurred when setting up presence, ignoring to keep the process alive",
error,
);
}
diff --git a/packages/bot/src/workers/bot.ts b/packages/bot/src/workers/bot.ts
index 65f3a4dee..b3af429ca 100644
--- a/packages/bot/src/workers/bot.ts
+++ b/packages/bot/src/workers/bot.ts
@@ -1,6 +1,6 @@
-import { assertSecret } from '@blitzkit/core';
-import { registerProcesses } from '../core/blitzkit/processes';
-import { client } from '../core/discord/client';
+import { assertSecret } from "@blitzkit/core";
+import { registerProcesses } from "../core/blitzkit/processes";
+import { client } from "../core/discord/client";
console.log(`🟡 Launching bot ${client.shard?.ids[0]}`);
diff --git a/packages/i18n/src/strings.ts b/packages/i18n/src/strings.ts
index 386979308..ace172be0 100644
--- a/packages/i18n/src/strings.ts
+++ b/packages/i18n/src/strings.ts
@@ -35,7 +35,7 @@ for (const locale in stringsRaw) {
const localeStrings = stringsRaw[locale];
STRINGS[locale] = merge(
stringsRaw[locales.default],
- localeStrings
+ localeStrings,
) as BlitzKitStrings;
}
diff --git a/packages/i18n/strings/en.json b/packages/i18n/strings/en.json
index 4ecf9071a..00c03109a 100644
--- a/packages/i18n/strings/en.json
+++ b/packages/i18n/strings/en.json
@@ -359,7 +359,14 @@
"commands": {
"replay": {
"name": "replay",
- "description": "Display replay results"
+ "description": "Display replay results",
+
+ "options": {
+ "file": {
+ "name": "file",
+ "description": "Replay file"
+ }
+ }
},
"research": {
diff --git a/packages/scripts/src/buildBot.ts b/packages/scripts/src/buildBot.ts
index 25641a4d3..ae01b217f 100644
--- a/packages/scripts/src/buildBot.ts
+++ b/packages/scripts/src/buildBot.ts
@@ -44,9 +44,9 @@ for (const module of await readdir(`${modulesRoot}/@resvg`)) {
.map((file) =>
cp(
`${modulesRoot}/@resvg/${module}/${file}`,
- `${distRoot}/workers/${file}`
- )
- )
+ `${distRoot}/workers/${file}`,
+ ),
+ ),
);
}
@@ -54,7 +54,7 @@ if (!dev) {
await cp(`${projectDir}/prisma`, `${distRoot}/prisma`, { recursive: true });
for (const file of await readdir(`${modulesRoot}/prisma`).then((files) =>
- files.filter((file) => file.endsWith(".node"))
+ files.filter((file) => file.endsWith(".node")),
)) {
cp(`${modulesRoot}/prisma/${file}`, `${distRoot}/prisma/${file}`);
}
diff --git a/packages/scripts/src/core/blitz/extractArmor.ts b/packages/scripts/src/core/blitz/extractArmor.ts
index 33dd87095..d30b38433 100644
--- a/packages/scripts/src/core/blitz/extractArmor.ts
+++ b/packages/scripts/src/core/blitz/extractArmor.ts
@@ -6,7 +6,7 @@ import {
} from "@blitzkit/core";
import { Accessor, Document, Node, Scene } from "@gltf-transform/core";
import { times } from "lodash-es";
-import { SteamVFS } from "../vfs/vfs";
+import { SteamVFS } from "../vfs/steam";
import {
vertexAttributeGLTFName,
vertexAttributeGltfVectorSizes,
@@ -16,10 +16,10 @@ export async function extractArmor(vfs: SteamVFS, fileName: string) {
const sc2Path = `Data/3d/Tanks/CollisionMeshes/${fileName}.sc2`;
const scgPath = `Data/3d/Tanks/CollisionMeshes/${fileName}.scg`;
const sc2 = new Sc2ReadStream(
- (await vfs.file(sc2Path)).buffer as ArrayBuffer
+ (await vfs.file(sc2Path)).buffer as ArrayBuffer,
).sc2();
const scg = new ScgReadStream(
- (await vfs.file(scgPath)).buffer as ArrayBuffer
+ (await vfs.file(scgPath)).buffer as ArrayBuffer,
).scg();
const document = new Document();
const scene = document.createScene();
@@ -29,7 +29,7 @@ export async function extractArmor(vfs: SteamVFS, fileName: string) {
hierarchies.forEach((hierarchy) => {
const components = times(
hierarchy.components.count,
- (index) => hierarchy.components[index.toString().padStart(4, "0")]
+ (index) => hierarchy.components[index.toString().padStart(4, "0")],
);
components.forEach((component) => {
@@ -43,7 +43,7 @@ export async function extractArmor(vfs: SteamVFS, fileName: string) {
if (!polygonGroup) {
console.warn(
- `Missing polygon group ${batch["rb.datasource"]} (${hierarchy.name}); skipping...`
+ `Missing polygon group ${batch["rb.datasource"]} (${hierarchy.name}); skipping...`,
);
break;
@@ -73,7 +73,7 @@ export async function extractArmor(vfs: SteamVFS, fileName: string) {
VertexAttribute.TANGENT,
attributes
.get(VertexAttribute.TANGENT)!
- .map((tangent) => [...tangent, 1])
+ .map((tangent) => [...tangent, 1]),
);
}
@@ -94,7 +94,7 @@ export async function extractArmor(vfs: SteamVFS, fileName: string) {
hardJointIndices.forEach((hardJointIndex) => {
const node = document.createNode(
- `${hierarchy.name}_armor_${hardJointIndex}`
+ `${hierarchy.name}_armor_${hardJointIndex}`,
);
const mesh = document.createMesh(batch["##name"]);
const indicesAccessor = document
@@ -104,9 +104,9 @@ export async function extractArmor(vfs: SteamVFS, fileName: string) {
new Uint16Array(
polygonGroup.indices.filter(
(index) =>
- vertexHardJointIndices.get(index) === hardJointIndex
- )
- )
+ vertexHardJointIndices.get(index) === hardJointIndex,
+ ),
+ ),
)
.setBuffer(buffer);
const primitive = document
@@ -127,7 +127,7 @@ export async function extractArmor(vfs: SteamVFS, fileName: string) {
default:
throw new TypeError(
- `Unhandled component type: ${component["comp.typename"]}`
+ `Unhandled component type: ${component["comp.typename"]}`,
);
}
});
diff --git a/packages/scripts/src/core/blitz/extractModel/index.ts b/packages/scripts/src/core/blitz/extractModel/index.ts
index 14a14e4f2..d514cedbb 100644
--- a/packages/scripts/src/core/blitz/extractModel/index.ts
+++ b/packages/scripts/src/core/blitz/extractModel/index.ts
@@ -9,7 +9,7 @@ import { Document, Material, Node, Scene } from "@gltf-transform/core";
import { dedup, prune } from "@gltf-transform/functions";
import { times } from "lodash-es";
import { dirname } from "path";
-import { SteamVFS } from "../../vfs/vfs";
+import { SteamVFS } from "../../vfs/steam";
import { readBaseColor } from "../readBaseColor";
import { readNormal } from "../readNormal";
import { readRoughnessMetallic } from "../readRoughnessMetallic";
@@ -29,10 +29,10 @@ export async function extractModel(vfs: SteamVFS, path: string) {
const sc2Path = `Data/3d/${path}.sc2`;
const scgPath = `Data/3d/${path}.scg`;
const sc2 = new Sc2ReadStream(
- (await vfs.file(sc2Path)).buffer as ArrayBuffer
+ (await vfs.file(sc2Path)).buffer as ArrayBuffer,
).sc2();
const scg = new ScgReadStream(
- (await vfs.file(scgPath)).buffer as ArrayBuffer
+ (await vfs.file(scgPath)).buffer as ArrayBuffer,
).scg();
const document = new Document();
const scene = document.createScene();
@@ -72,9 +72,9 @@ export async function extractModel(vfs: SteamVFS, path: string) {
}`,
textures.miscMap
? `Data/3d/${dirname(path)}/${textures.miscMap}`
- : undefined
- )
- )
+ : undefined,
+ ),
+ ),
);
if (node.configCount) {
@@ -103,9 +103,9 @@ export async function extractModel(vfs: SteamVFS, path: string) {
.setMimeType("image/webp")
.setImage(
await readRoughnessMetallic(
- `Data/3d/${dirname(path)}/${textures.baseRMMap}`
- )
- )
+ `Data/3d/${dirname(path)}/${textures.baseRMMap}`,
+ ),
+ ),
);
}
@@ -121,9 +121,9 @@ export async function extractModel(vfs: SteamVFS, path: string) {
`Data/3d/${dirname(path)}/${
textures.baseNormalMap ?? textures.normalmap
}`,
- isBase
- )
- )
+ isBase,
+ ),
+ ),
);
}
@@ -157,7 +157,7 @@ export async function extractModel(vfs: SteamVFS, path: string) {
const node = document.createNode(hierarchy.name);
const components = times(
hierarchy.components.count,
- (index) => hierarchy.components[index.toString().padStart(4, "0")]
+ (index) => hierarchy.components[index.toString().padStart(4, "0")],
);
components.forEach((component) => {
@@ -194,7 +194,7 @@ export async function extractModel(vfs: SteamVFS, path: string) {
}
if (polygonGroup === undefined) {
throw new Error(
- `Missing polygon group ${batch["rb.datasource"]}`
+ `Missing polygon group ${batch["rb.datasource"]}`,
);
}
@@ -255,7 +255,7 @@ export async function extractModel(vfs: SteamVFS, path: string) {
default: {
if (ERROR_ON_UNKNOWN_COMPONENT) {
throw new Error(
- `Unhandled component type: ${component["comp.typename"]}`
+ `Unhandled component type: ${component["comp.typename"]}`,
);
}
}
From bf61cb17de9b1c647f772bdabe9353cdb38f60ba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com>
Date: Wed, 18 Feb 2026 16:36:42 -0600
Subject: [PATCH 10/27] typo
---
docs/changelogs/1.29.12.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/changelogs/1.29.12.md b/docs/changelogs/1.29.12.md
index e0f0ac395..8ef1bdeaa 100644
--- a/docs/changelogs/1.29.12.md
+++ b/docs/changelogs/1.29.12.md
@@ -10,5 +10,5 @@
- Special Chinese characters are now properly handled
- Commands are now registered synchronously, allowing errors to reveal their exact origins
- Error handling now properly exists the process after logging the error
-- The bot now uses Discord's new `clientReady` instead of the ambagious `ready` event
+- The bot now uses Discord's new `clientReady` instead of the ambiguous `ready` event
- Both the armor and model extractors now use the new BlitzKit `SteamVFS`
From f39d1bab4e19c2c74bf93296bf46255e7165f8f0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com>
Date: Wed, 18 Feb 2026 16:45:06 -0600
Subject: [PATCH 11/27] wait wait wait no no no
---
packages/bot/src/main.ts | 2 +-
packages/scripts/src/buildBot.ts | 25 +++++++++----------------
2 files changed, 10 insertions(+), 17 deletions(-)
diff --git a/packages/bot/src/main.ts b/packages/bot/src/main.ts
index b4f4f3268..c3dd9e107 100644
--- a/packages/bot/src/main.ts
+++ b/packages/bot/src/main.ts
@@ -78,4 +78,4 @@ const interval = setInterval(async () => {
error,
);
}
-}, 5000);
+}, 15000);
diff --git a/packages/scripts/src/buildBot.ts b/packages/scripts/src/buildBot.ts
index ae01b217f..379bfc72a 100644
--- a/packages/scripts/src/buildBot.ts
+++ b/packages/scripts/src/buildBot.ts
@@ -1,15 +1,10 @@
import { build } from "esbuild";
import { cp, readdir, rm } from "fs/promises";
-import { argv } from "process";
-
-const dev = argv.includes("--dev");
const projectDir = "../..";
const srcRoot = `${projectDir}/packages/bot`;
const distRoot = `${projectDir}/dist/bot`;
-const modulesRoot = dev
- ? "../../packages/bot/node_modules"
- : "../../node_modules";
+const modulesRoot = "../../node_modules";
const workers = await readdir(`${srcRoot}/src/workers`);
await rm(distRoot, { recursive: true, force: true });
@@ -30,9 +25,9 @@ build({
},
external: ["sharp"],
minifyIdentifiers: false, // causes goofy ahh issues
- minifySyntax: !dev,
- minifyWhitespace: !dev,
- sourcemap: !dev,
+ minifySyntax: true,
+ minifyWhitespace: true,
+ sourcemap: true,
jsxImportSource: "react",
jsx: "automatic",
});
@@ -50,12 +45,10 @@ for (const module of await readdir(`${modulesRoot}/@resvg`)) {
);
}
-if (!dev) {
- await cp(`${projectDir}/prisma`, `${distRoot}/prisma`, { recursive: true });
+await cp(`${projectDir}/prisma`, `${distRoot}/prisma`, { recursive: true });
- for (const file of await readdir(`${modulesRoot}/prisma`).then((files) =>
- files.filter((file) => file.endsWith(".node")),
- )) {
- cp(`${modulesRoot}/prisma/${file}`, `${distRoot}/prisma/${file}`);
- }
+for (const file of await readdir(`${modulesRoot}/prisma`).then((files) =>
+ files.filter((file) => file.endsWith(".node")),
+)) {
+ cp(`${modulesRoot}/prisma/${file}`, `${distRoot}/prisma/${file}`);
}
From 629e9f814e091c8ce74df316801d511a122f233b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com>
Date: Wed, 18 Feb 2026 16:49:08 -0600
Subject: [PATCH 12/27] bruh
---
packages/bot/src/main.ts | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/packages/bot/src/main.ts b/packages/bot/src/main.ts
index c3dd9e107..4dc54f3cc 100644
--- a/packages/bot/src/main.ts
+++ b/packages/bot/src/main.ts
@@ -5,13 +5,13 @@ import {
tankDefinitions,
} from "./core/blitzkit/nonBlockingPromises";
+let manager: ShardingManager;
let iteration = 0;
const interval = setInterval(async () => {
- iteration++;
+ console.log(`Attempting launch on iteration ${++iteration}`);
- console.log(`Attempting launch on iteration ${iteration}`);
-
- const manager = new ShardingManager("dist/bot/workers/bot.js", {
+ manager?.respawnAll();
+ manager = new ShardingManager("dist/bot/workers/bot.js", {
token: assertSecret(import.meta.env.DISCORD_TOKEN),
});
From a0b81bfb835bcc7d6b1c16770f2abf61b274915d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com>
Date: Wed, 18 Feb 2026 16:52:47 -0600
Subject: [PATCH 13/27] bruh
---
packages/bot/src/main.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/bot/src/main.ts b/packages/bot/src/main.ts
index 4dc54f3cc..8bac09c3a 100644
--- a/packages/bot/src/main.ts
+++ b/packages/bot/src/main.ts
@@ -21,7 +21,7 @@ const interval = setInterval(async () => {
console.log(`🟡 Launching shard ${shard.id}`);
shard.on("ready", () => console.log(`🟢 Launched shard ${shard.id}`));
})
- .spawn();
+ .spawn({ timeout: 60000 });
try {
let servers = 0;
From bae7ca272042f90a2e833e5dbebd0f014de86039 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com>
Date: Wed, 18 Feb 2026 18:09:05 -0600
Subject: [PATCH 14/27] fix: turret angles round back to \pm 180deg
---
packages/website/src/core/blitz/applyPitchYawLimits.ts | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/packages/website/src/core/blitz/applyPitchYawLimits.ts b/packages/website/src/core/blitz/applyPitchYawLimits.ts
index 420a78323..5176c1ff9 100644
--- a/packages/website/src/core/blitz/applyPitchYawLimits.ts
+++ b/packages/website/src/core/blitz/applyPitchYawLimits.ts
@@ -2,9 +2,9 @@ import {
normalizeAngleRad,
type PitchLimits,
type YawLimits,
-} from '@blitzkit/core';
-import { clamp } from 'lodash-es';
-import { degToRad } from 'three/src/math/MathUtils.js';
+} from "@blitzkit/core";
+import { clamp } from "lodash-es";
+import { degToRad } from "three/src/math/MathUtils.js";
export const DEFAULT_PITCH_TRANSITION = 15;
@@ -87,5 +87,5 @@ export function applyPitchYawLimits(
newPitch = clamp(newPitch, lowerPitch, upperPitch);
- return [newPitch, newYaw] as const;
+ return [newPitch, normalizeAngleRad(newYaw)] as const;
}
From d5269e763b1b71ddb4c1037edb6a36bdfd8c7a56 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com>
Date: Wed, 18 Feb 2026 18:20:37 -0600
Subject: [PATCH 15/27] fix: transparency checks and depth writing
---
.../src/core/blitz/extractModel/index.ts | 27 ++++++-------------
.../scripts/src/core/blitz/readBaseColor.ts | 8 ++++--
.../website/src/core/blitzkit/jsxTree.tsx | 12 ++++++---
3 files changed, 22 insertions(+), 25 deletions(-)
diff --git a/packages/scripts/src/core/blitz/extractModel/index.ts b/packages/scripts/src/core/blitz/extractModel/index.ts
index d514cedbb..f42a018bf 100644
--- a/packages/scripts/src/core/blitz/extractModel/index.ts
+++ b/packages/scripts/src/core/blitz/extractModel/index.ts
@@ -61,20 +61,17 @@ export async function extractModel(vfs: SteamVFS, path: string) {
}
if (textures) {
+ const { image: baseColor, hasAlpha } = await readBaseColor(
+ `Data/3d/${dirname(path)}/${textures.baseColorMap ?? textures.albedo}`,
+ textures.miscMap
+ ? `Data/3d/${dirname(path)}/${textures.miscMap}`
+ : undefined,
+ );
material.setBaseColorTexture(
document
.createTexture(node.materialName)
.setMimeType("image/webp")
- .setImage(
- await readBaseColor(
- `Data/3d/${dirname(path)}/${
- textures.baseColorMap ?? textures.albedo
- }`,
- textures.miscMap
- ? `Data/3d/${dirname(path)}/${textures.miscMap}`
- : undefined,
- ),
- ),
+ .setImage(baseColor),
);
if (node.configCount) {
@@ -83,15 +80,7 @@ export async function extractModel(vfs: SteamVFS, path: string) {
configIndex < node.configCount;
configIndex++
) {
- const archive = node[`configArchive_${configIndex}`];
-
- if (
- archive.configName !== "Default" ||
- !archive.enabledPresets?.AlphaTest
- ) {
- continue;
- }
-
+ if (!hasAlpha) continue;
material.setAlphaMode("BLEND").setDoubleSided(true);
}
}
diff --git a/packages/scripts/src/core/blitz/readBaseColor.ts b/packages/scripts/src/core/blitz/readBaseColor.ts
index 0081b9cdc..50647add3 100644
--- a/packages/scripts/src/core/blitz/readBaseColor.ts
+++ b/packages/scripts/src/core/blitz/readBaseColor.ts
@@ -2,6 +2,7 @@ import sharp from "sharp";
import { readTexture } from "./readTexture";
export async function readBaseColor(path: string, occlusionPath?: string) {
+ let hasAlpha = false;
const baseRaw = await readTexture(path);
const occlusionRaw = occlusionPath
? await readTexture(occlusionPath)
@@ -30,10 +31,13 @@ export async function readBaseColor(path: string, occlusionPath?: string) {
c = occlusion![occlusionI] / 255;
}
+ const alpha = base[i * 4 + 3];
+ hasAlpha ||= alpha < 255;
+
combined[i * 4 + 0] = Math.round(base[i * 4 + 0] * c);
combined[i * 4 + 1] = Math.round(base[i * 4 + 1] * c);
combined[i * 4 + 2] = Math.round(base[i * 4 + 2] * c);
- combined[i * 4 + 3] = base[i * 4 + 3];
+ combined[i * 4 + 3] = alpha;
}
const image = await sharp(combined, {
@@ -42,5 +46,5 @@ export async function readBaseColor(path: string, occlusionPath?: string) {
.webp()
.toBuffer();
- return image;
+ return { hasAlpha, image };
}
diff --git a/packages/website/src/core/blitzkit/jsxTree.tsx b/packages/website/src/core/blitzkit/jsxTree.tsx
index 518108fc9..cac9d9385 100644
--- a/packages/website/src/core/blitzkit/jsxTree.tsx
+++ b/packages/website/src/core/blitzkit/jsxTree.tsx
@@ -1,5 +1,5 @@
import type { ComponentProps, ReactNode } from "react";
-import { Mesh, Object3D } from "three";
+import { Mesh, MeshStandardMaterial, Object3D } from "three";
export function jsxTree(
node: Object3D,
@@ -7,14 +7,14 @@ export function jsxTree(
mesh?: (
mesh: Mesh,
props: ComponentProps<"mesh">,
- key: string
+ key: string,
) => ReactNode;
group?: (
object3d: Object3D,
props: ComponentProps<"group">,
- key: string
+ key: string,
) => ReactNode;
- }
+ },
): ReactNode {
if (node instanceof Mesh) {
const props = {
@@ -25,6 +25,10 @@ export function jsxTree(
scale: node.scale,
};
+ if (props.material instanceof MeshStandardMaterial) {
+ props.material.depthWrite = true;
+ }
+
return mergers?.mesh ? (
mergers.mesh(node, props, node.uuid)
) : (
From 4cc4e664dfa8ecc339ab209eb80103c8226ebed6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com>
Date: Wed, 18 Feb 2026 18:22:56 -0600
Subject: [PATCH 16/27] fix: high end consumables gives 33% extra not 70%
---
packages/website/src/components/ConsumablesManager.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/website/src/components/ConsumablesManager.tsx b/packages/website/src/components/ConsumablesManager.tsx
index 8ddff5c06..28b6a812d 100644
--- a/packages/website/src/components/ConsumablesManager.tsx
+++ b/packages/website/src/components/ConsumablesManager.tsx
@@ -38,7 +38,7 @@ export function ConsumablesManager({
timers
? consumable.duration === undefined
? undefined
- : consumable.duration * (hasHighEndConsumables ? 1.7 : 1)
+ : consumable.duration * (hasHighEndConsumables ? 1.33 : 1)
: undefined
}
cooldown={
From 10d7fe1e75b4e043f57a59d8e9d330e8b2736dbc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com>
Date: Wed, 18 Feb 2026 18:38:40 -0600
Subject: [PATCH 17/27] feat: auto crop image
---
.vscode/settings.json | 1 +
bun.lock | 145 ++++++++++++++++--
packages/website/package.json | 1 +
.../src/components/ScreenshotButton.tsx | 78 ++++++----
4 files changed, 184 insertions(+), 41 deletions(-)
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 8c0be114b..93c9586ed 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -17,6 +17,7 @@
"astrojs",
"astrolib",
"atgm",
+ "autocrop",
"Autoloader",
"Autoreloader",
"AYUV",
diff --git a/bun.lock b/bun.lock
index 937a04cf6..eff663c87 100644
--- a/bun.lock
+++ b/bun.lock
@@ -27,26 +27,26 @@
"@blitzkit/core": "workspace:*",
"@blitzkit/i18n": "workspace:*",
"@radix-ui/colors": "^3.0.0",
- "@radix-ui/themes": "^3.2.1",
+ "@radix-ui/themes": "^3.3.0",
"@resvg/resvg-js": "^2.6.2",
"@types/adm-zip": "^0.5.7",
"@types/bun": "^1.3.9",
"@types/lodash-es": "^4.17.12",
"@types/markdown-escape": "^1.1.3",
- "@types/react": "^19.2.2",
+ "@types/react": "^19.2.14",
"adm-zip": "^0.5.16",
"bepaint": "^0.1.0-beta.3",
- "discord.js": "^14.22.1",
- "dotenv": "^17.2.2",
+ "discord.js": "^14.25.1",
+ "dotenv": "^17.3.1",
"fuzzysort": "^3.1.0",
- "lodash-es": "^4.17.21",
+ "lodash-es": "^4.17.23",
"markdown-escape": "^2.0.0",
"pickleparser": "^0.2.1",
- "prisma": "^6.17.1",
+ "prisma": "^6.19.2",
"protobufjs": "^7.5.4",
- "react": "^19.2.0",
- "satori": "^0.18.3",
- "sharp": "^0.34.4",
+ "react": "^19.2.4",
+ "satori": "^0.18.4",
+ "sharp": "^0.34.5",
},
},
"packages/closed": {
@@ -164,6 +164,7 @@
"compare-versions": "^6.1.1",
"fuzzysort": "^3.1.0",
"immer": "^10.1.3",
+ "jimp": "^1.6.0",
"lodash-es": "^4.17.21",
"markdown-to-jsx": "^7.7.13",
"pwa-install-handler": "^2.6.3",
@@ -565,6 +566,62 @@
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
+ "@jimp/core": ["@jimp/core@1.6.0", "", { "dependencies": { "@jimp/file-ops": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "await-to-js": "^3.0.0", "exif-parser": "^0.1.12", "file-type": "^16.0.0", "mime": "3" } }, "sha512-EQQlKU3s9QfdJqiSrZWNTxBs3rKXgO2W+GxNXDtwchF3a4IqxDheFX1ti+Env9hdJXDiYLp2jTRjlxhPthsk8w=="],
+
+ "@jimp/diff": ["@jimp/diff@1.6.0", "", { "dependencies": { "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "pixelmatch": "^5.3.0" } }, "sha512-+yUAQ5gvRC5D1WHYxjBHZI7JBRusGGSLf8AmPRPCenTzh4PA+wZ1xv2+cYqQwTfQHU5tXYOhA0xDytfHUf1Zyw=="],
+
+ "@jimp/file-ops": ["@jimp/file-ops@1.6.0", "", {}, "sha512-Dx/bVDmgnRe1AlniRpCKrGRm5YvGmUwbDzt+MAkgmLGf+jvBT75hmMEZ003n9HQI/aPnm/YKnXjg/hOpzNCpHQ=="],
+
+ "@jimp/js-bmp": ["@jimp/js-bmp@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "bmp-ts": "^1.0.9" } }, "sha512-FU6Q5PC/e3yzLyBDXupR3SnL3htU7S3KEs4e6rjDP6gNEOXRFsWs6YD3hXuXd50jd8ummy+q2WSwuGkr8wi+Gw=="],
+
+ "@jimp/js-gif": ["@jimp/js-gif@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "gifwrap": "^0.10.1", "omggif": "^1.0.10" } }, "sha512-N9CZPHOrJTsAUoWkWZstLPpwT5AwJ0wge+47+ix3++SdSL/H2QzyMqxbcDYNFe4MoI5MIhATfb0/dl/wmX221g=="],
+
+ "@jimp/js-jpeg": ["@jimp/js-jpeg@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "jpeg-js": "^0.4.4" } }, "sha512-6vgFDqeusblf5Pok6B2DUiMXplH8RhIKAryj1yn+007SIAQ0khM1Uptxmpku/0MfbClx2r7pnJv9gWpAEJdMVA=="],
+
+ "@jimp/js-png": ["@jimp/js-png@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "pngjs": "^7.0.0" } }, "sha512-AbQHScy3hDDgMRNfG0tPjL88AV6qKAILGReIa3ATpW5QFjBKpisvUaOqhzJ7Reic1oawx3Riyv152gaPfqsBVg=="],
+
+ "@jimp/js-tiff": ["@jimp/js-tiff@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "utif2": "^4.1.0" } }, "sha512-zhReR8/7KO+adijj3h0ZQUOiun3mXUv79zYEAKvE0O+rP7EhgtKvWJOZfRzdZSNv0Pu1rKtgM72qgtwe2tFvyw=="],
+
+ "@jimp/plugin-blit": ["@jimp/plugin-blit@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-M+uRWl1csi7qilnSK8uxK4RJMSuVeBiO1AY0+7APnfUbQNZm6hCe0CCFv1Iyw1D/Dhb8ph8fQgm5mwM0eSxgVA=="],
+
+ "@jimp/plugin-blur": ["@jimp/plugin-blur@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/utils": "1.6.0" } }, "sha512-zrM7iic1OTwUCb0g/rN5y+UnmdEsT3IfuCXCJJNs8SZzP0MkZ1eTvuwK9ZidCuMo4+J3xkzCidRwYXB5CyGZTw=="],
+
+ "@jimp/plugin-circle": ["@jimp/plugin-circle@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-xt1Gp+LtdMKAXfDp3HNaG30SPZW6AQ7dtAtTnoRKorRi+5yCJjKqXRgkewS5bvj8DEh87Ko1ydJfzqS3P2tdWw=="],
+
+ "@jimp/plugin-color": ["@jimp/plugin-color@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "tinycolor2": "^1.6.0", "zod": "^3.23.8" } }, "sha512-J5q8IVCpkBsxIXM+45XOXTrsyfblyMZg3a9eAo0P7VPH4+CrvyNQwaYatbAIamSIN1YzxmO3DkIZXzRjFSz1SA=="],
+
+ "@jimp/plugin-contain": ["@jimp/plugin-contain@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-oN/n+Vdq/Qg9bB4yOBOxtY9IPAtEfES8J1n9Ddx+XhGBYT1/QTU/JYkGaAkIGoPnyYvmLEDqMz2SGihqlpqfzQ=="],
+
+ "@jimp/plugin-cover": ["@jimp/plugin-cover@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-Iow0h6yqSC269YUJ8HC3Q/MpCi2V55sMlbkkTTx4zPvd8mWZlC0ykrNDeAy9IJegrQ7v5E99rJwmQu25lygKLA=="],
+
+ "@jimp/plugin-crop": ["@jimp/plugin-crop@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-KqZkEhvs+21USdySCUDI+GFa393eDIzbi1smBqkUPTE+pRwSWMAf01D5OC3ZWB+xZsNla93BDS9iCkLHA8wang=="],
+
+ "@jimp/plugin-displace": ["@jimp/plugin-displace@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-4Y10X9qwr5F+Bo5ME356XSACEF55485j5nGdiyJ9hYzjQP9nGgxNJaZ4SAOqpd+k5sFaIeD7SQ0Occ26uIng5Q=="],
+
+ "@jimp/plugin-dither": ["@jimp/plugin-dither@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0" } }, "sha512-600d1RxY0pKwgyU0tgMahLNKsqEcxGdbgXadCiVCoGd6V6glyCvkNrnnwC0n5aJ56Htkj88PToSdF88tNVZEEQ=="],
+
+ "@jimp/plugin-fisheye": ["@jimp/plugin-fisheye@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-E5QHKWSCBFtpgZarlmN3Q6+rTQxjirFqo44ohoTjzYVrDI6B6beXNnPIThJgPr0Y9GwfzgyarKvQuQuqCnnfbA=="],
+
+ "@jimp/plugin-flip": ["@jimp/plugin-flip@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-/+rJVDuBIVOgwoyVkBjUFHtP+wmW0r+r5OQ2GpatQofToPVbJw1DdYWXlwviSx7hvixTWLKVgRWQ5Dw862emDg=="],
+
+ "@jimp/plugin-hash": ["@jimp/plugin-hash@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/js-bmp": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/js-tiff": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "any-base": "^1.1.0" } }, "sha512-wWzl0kTpDJgYVbZdajTf+4NBSKvmI3bRI8q6EH9CVeIHps9VWVsUvEyb7rpbcwVLWYuzDtP2R0lTT6WeBNQH9Q=="],
+
+ "@jimp/plugin-mask": ["@jimp/plugin-mask@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-Cwy7ExSJMZszvkad8NV8o/Z92X2kFUFM8mcDAhNVxU0Q6tA0op2UKRJY51eoK8r6eds/qak3FQkXakvNabdLnA=="],
+
+ "@jimp/plugin-print": ["@jimp/plugin-print@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/types": "1.6.0", "parse-bmfont-ascii": "^1.0.6", "parse-bmfont-binary": "^1.0.6", "parse-bmfont-xml": "^1.1.6", "simple-xml-to-json": "^1.2.2", "zod": "^3.23.8" } }, "sha512-zarTIJi8fjoGMSI/M3Xh5yY9T65p03XJmPsuNet19K/Q7mwRU6EV2pfj+28++2PV2NJ+htDF5uecAlnGyxFN2A=="],
+
+ "@jimp/plugin-quantize": ["@jimp/plugin-quantize@1.6.0", "", { "dependencies": { "image-q": "^4.0.0", "zod": "^3.23.8" } }, "sha512-EmzZ/s9StYQwbpG6rUGBCisc3f64JIhSH+ncTJd+iFGtGo0YvSeMdAd+zqgiHpfZoOL54dNavZNjF4otK+mvlg=="],
+
+ "@jimp/plugin-resize": ["@jimp/plugin-resize@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/types": "1.6.0", "zod": "^3.23.8" } }, "sha512-uSUD1mqXN9i1SGSz5ov3keRZ7S9L32/mAQG08wUwZiEi5FpbV0K8A8l1zkazAIZi9IJzLlTauRNU41Mi8IF9fA=="],
+
+ "@jimp/plugin-rotate": ["@jimp/plugin-rotate@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-JagdjBLnUZGSG4xjCLkIpQOZZ3Mjbg8aGCCi4G69qR+OjNpOeGI7N2EQlfK/WE8BEHOW5vdjSyglNqcYbQBWRw=="],
+
+ "@jimp/plugin-threshold": ["@jimp/plugin-threshold@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-hash": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0", "zod": "^3.23.8" } }, "sha512-M59m5dzLoHOVWdM41O8z9SyySzcDn43xHseOH0HavjsfQsT56GGCC4QzU1banJidbUrePhzoEdS42uFE8Fei8w=="],
+
+ "@jimp/types": ["@jimp/types@1.6.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-7UfRsiKo5GZTAATxm2qQ7jqmUXP0DxTArztllTcYdyw6Xi5oT4RaoXynVtCD4UyLK5gJgkZJcwonoijrhYFKfg=="],
+
+ "@jimp/utils": ["@jimp/utils@1.6.0", "", { "dependencies": { "@jimp/types": "1.6.0", "tinycolor2": "^1.6.0" } }, "sha512-gqFTGEosKbOkYF/WFj26jMHOI5OH2jeP1MmC/zbK6BF6VJBf8rIC5898dPfSzZEbSA0wbbV5slbntWVc5PKLFA=="],
+
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
@@ -919,6 +976,8 @@
"@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.0", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-B0LShOYae4CZjN8leiNDbnfjSrTwoZakqKaWpfoH6nXiJwt6Rgj6RnVIffG3DoJiKsffRhMkjmBV9VeilSb4TA=="],
+ "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
+
"@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="],
"@tweenjs/tween.js": ["@tweenjs/tween.js@23.1.3", "", {}, "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA=="],
@@ -1033,6 +1092,8 @@
"@webgpu/types": ["@webgpu/types@0.1.65", "", {}, "sha512-cYrHab4d6wuVvDW5tdsfI6/o6vcLMDe6w2Citd1oS51Xxu2ycLCnVo4fqwujfKWijrZMInTJIKcXxteoy21nVA=="],
+ "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
+
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
"adm-zip": ["adm-zip@0.5.16", "", {}, "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ=="],
@@ -1047,6 +1108,8 @@
"ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
+ "any-base": ["any-base@1.1.0", "", {}, "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg=="],
+
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
@@ -1075,6 +1138,8 @@
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
+ "await-to-js": ["await-to-js@3.0.0", "", {}, "sha512-zJAaP9zxTcvTHRlejau3ZOY4V7SRpiByf3/dxx2uyKxxor19tpmpV2QRsTKikckwhaPmr2dVpxxMr7jOCYVp5g=="],
+
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
"b4a": ["b4a@1.7.3", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q=="],
@@ -1121,6 +1186,8 @@
"bl": ["bl@1.2.3", "", { "dependencies": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" } }, "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww=="],
+ "bmp-ts": ["bmp-ts@1.0.9", "", {}, "sha512-cTEHk2jLrPyi+12M3dhpEbnnPOsaZuq7C45ylbbQIiWgDFZq4UVYPEY5mlqjvsj/6gJv9qX5sa+ebDzLXT28Vw=="],
+
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
"bottleneck": ["bottleneck@2.19.5", "", {}, "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="],
@@ -1423,10 +1490,16 @@
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
+ "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
+
"eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="],
+ "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
+
"events-universal": ["events-universal@1.0.1", "", { "dependencies": { "bare-events": "^2.7.0" } }, "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="],
+ "exif-parser": ["exif-parser@0.1.12", "", {}, "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw=="],
+
"exsolve": ["exsolve@1.0.7", "", {}, "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw=="],
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
@@ -1521,6 +1594,8 @@
"get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="],
+ "gifwrap": ["gifwrap@0.10.1", "", { "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" } }, "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw=="],
+
"giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="],
"github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
@@ -1605,6 +1680,8 @@
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
+ "image-q": ["image-q@4.0.0", "", { "dependencies": { "@types/node": "16.9.1" } }, "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw=="],
+
"immediate": ["immediate@3.0.6", "", {}, "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="],
"immer": ["immer@10.1.3", "", {}, "sha512-tmjF/k8QDKydUlm3mZU+tjM6zeq9/fFpPqH9SzWmBnVVKsPBg/V66qsMwb3/Bo90cgUN+ghdVBess+hPsxUyRw=="],
@@ -1709,8 +1786,12 @@
"jake": ["jake@10.9.4", "", { "dependencies": { "async": "^3.2.6", "filelist": "^1.0.4", "picocolors": "^1.1.1" }, "bin": { "jake": "bin/cli.js" } }, "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA=="],
+ "jimp": ["jimp@1.6.0", "", { "dependencies": { "@jimp/core": "1.6.0", "@jimp/diff": "1.6.0", "@jimp/js-bmp": "1.6.0", "@jimp/js-gif": "1.6.0", "@jimp/js-jpeg": "1.6.0", "@jimp/js-png": "1.6.0", "@jimp/js-tiff": "1.6.0", "@jimp/plugin-blit": "1.6.0", "@jimp/plugin-blur": "1.6.0", "@jimp/plugin-circle": "1.6.0", "@jimp/plugin-color": "1.6.0", "@jimp/plugin-contain": "1.6.0", "@jimp/plugin-cover": "1.6.0", "@jimp/plugin-crop": "1.6.0", "@jimp/plugin-displace": "1.6.0", "@jimp/plugin-dither": "1.6.0", "@jimp/plugin-fisheye": "1.6.0", "@jimp/plugin-flip": "1.6.0", "@jimp/plugin-hash": "1.6.0", "@jimp/plugin-mask": "1.6.0", "@jimp/plugin-print": "1.6.0", "@jimp/plugin-quantize": "1.6.0", "@jimp/plugin-resize": "1.6.0", "@jimp/plugin-rotate": "1.6.0", "@jimp/plugin-threshold": "1.6.0", "@jimp/types": "1.6.0", "@jimp/utils": "1.6.0" } }, "sha512-YcwCHw1kiqEeI5xRpDlPPBGL2EOpBKLwO4yIBJcXWHPj5PnA5urGq0jbyhM5KoNpypQ6VboSoxc9D8HyfvngSg=="],
+
"jiti": ["jiti@2.6.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ=="],
+ "jpeg-js": ["jpeg-js@0.4.4", "", {}, "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="],
+
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
@@ -1881,6 +1962,8 @@
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
+ "mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="],
+
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
"mime-types": ["mime-types@3.0.1", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="],
@@ -1941,6 +2024,8 @@
"ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
+ "omggif": ["omggif@1.0.10", "", {}, "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="],
+
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
@@ -1967,6 +2052,12 @@
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
+ "parse-bmfont-ascii": ["parse-bmfont-ascii@1.0.6", "", {}, "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA=="],
+
+ "parse-bmfont-binary": ["parse-bmfont-binary@1.0.6", "", {}, "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA=="],
+
+ "parse-bmfont-xml": ["parse-bmfont-xml@1.1.6", "", { "dependencies": { "xml-parse-from-string": "^1.0.0", "xml2js": "^0.5.0" } }, "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA=="],
+
"parse-css-color": ["parse-css-color@0.2.1", "", { "dependencies": { "color-name": "^1.1.4", "hex-rgb": "^4.1.0" } }, "sha512-bwS/GGIFV3b6KS4uwpzCFj4w297Yl3uqnSgIPsoQkx7GMLROXfMnWvxfNkL0oh8HVhZA4hvJoEoEIqonfJ3BWg=="],
"parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="],
@@ -1989,6 +2080,8 @@
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
+ "peek-readable": ["peek-readable@4.1.0", "", {}, "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg=="],
+
"pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="],
"perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="],
@@ -2007,8 +2100,12 @@
"pinkie-promise": ["pinkie-promise@2.0.1", "", { "dependencies": { "pinkie": "^2.0.0" } }, "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw=="],
+ "pixelmatch": ["pixelmatch@5.3.0", "", { "dependencies": { "pngjs": "^6.0.0" }, "bin": { "pixelmatch": "bin/pixelmatch" } }, "sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q=="],
+
"pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="],
+ "pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="],
+
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
@@ -2025,6 +2122,8 @@
"prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="],
+ "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
+
"process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
"progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="],
@@ -2093,6 +2192,8 @@
"readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
+ "readable-web-to-node-stream": ["readable-web-to-node-stream@3.0.4", "", { "dependencies": { "readable-stream": "^4.7.0" } }, "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw=="],
+
"readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="],
"reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
@@ -2211,6 +2312,8 @@
"simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="],
+ "simple-xml-to-json": ["simple-xml-to-json@1.2.3", "", {}, "sha512-kWJDCr9EWtZ+/EYYM5MareWj2cRnZGF93YDNpH4jQiHB+hBIZnfPFSQiVMzZOdk+zXWqTZ/9fTeQNu2DqeiudA=="],
+
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
"smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
@@ -2279,6 +2382,8 @@
"strnum": ["strnum@2.1.1", "", {}, "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw=="],
+ "strtok3": ["strtok3@6.3.0", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "peek-readable": "^4.1.0" } }, "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw=="],
+
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
"suspend-react": ["suspend-react@0.1.3", "", { "peerDependencies": { "react": ">=17.0" } }, "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ=="],
@@ -2307,6 +2412,8 @@
"tiny-typed-emitter": ["tiny-typed-emitter@2.1.0", "", {}, "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA=="],
+ "tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="],
+
"tinyexec": ["tinyexec@1.0.1", "", {}, "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw=="],
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
@@ -2317,6 +2424,8 @@
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
+ "token-types": ["token-types@4.2.1", "", { "dependencies": { "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ=="],
+
"tr46": ["tr46@1.0.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA=="],
"trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
@@ -2437,6 +2546,8 @@
"use-sync-external-store": ["use-sync-external-store@1.5.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A=="],
+ "utif2": ["utif2@4.1.0", "", { "dependencies": { "pako": "^1.0.11" } }, "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w=="],
+
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"utility-types": ["utility-types@3.11.0", "", {}, "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw=="],
@@ -2565,6 +2676,8 @@
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
+ "xml-parse-from-string": ["xml-parse-from-string@1.0.1", "", {}, "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g=="],
+
"xml2js": ["xml2js@0.6.2", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA=="],
"xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
@@ -2639,6 +2752,8 @@
"@discordjs/ws/discord-api-types": ["discord-api-types@0.38.26", "", {}, "sha512-xpmPviHjIJ6dFu1eNwNDIGQ3N6qmPUUYFVAx/YZ64h7ZgPkTcKjnciD8bZe8Vbeji7yS5uYljyciunpq0J5NSw=="],
+ "@jimp/core/file-type": ["file-type@16.5.4", "", { "dependencies": { "readable-web-to-node-stream": "^3.0.0", "strtok3": "^6.2.4", "token-types": "^4.1.1" } }, "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw=="],
+
"@octokit/endpoint/@octokit/types": ["@octokit/types@14.1.0", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="],
"@octokit/graphql/@octokit/types": ["@octokit/types@14.1.0", "", { "dependencies": { "@octokit/openapi-types": "^25.1.0" } }, "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g=="],
@@ -2715,6 +2830,8 @@
"https-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
+ "image-q/@types/node": ["@types/node@16.9.1", "", {}, "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g=="],
+
"its-fine/@types/react-reconciler": ["@types/react-reconciler@0.28.9", "", { "peerDependencies": { "@types/react": "*" } }, "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg=="],
"linebreak/base64-js": ["base64-js@0.0.8", "", {}, "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw=="],
@@ -2729,8 +2846,12 @@
"pac-proxy-agent/socks-proxy-agent": ["socks-proxy-agent@8.0.5", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" } }, "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw=="],
+ "parse-bmfont-xml/xml2js": ["xml2js@0.5.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA=="],
+
"parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
+ "pixelmatch/pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="],
+
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
"proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
@@ -2745,6 +2866,8 @@
"readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
+ "readable-web-to-node-stream/readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="],
+
"safe-array-concat/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
"safe-push-apply/isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
@@ -2783,6 +2906,8 @@
"unicode-trie/pako": ["pako@0.2.9", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="],
+ "utif2/pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
+
"volar-service-typescript/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"vscode-json-languageservice/jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="],
@@ -2979,6 +3104,8 @@
"ndarray-pixels/sharp/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
+ "readable-web-to-node-stream/readable-stream/string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
+
"steam-appticket/protobufjs/@types/long": ["@types/long@4.0.2", "", {}, "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA=="],
"steam-appticket/protobufjs/long": ["long@4.0.0", "", {}, "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="],
diff --git a/packages/website/package.json b/packages/website/package.json
index bc6f67e1d..9bbd91bff 100644
--- a/packages/website/package.json
+++ b/packages/website/package.json
@@ -32,6 +32,7 @@
"compare-versions": "^6.1.1",
"fuzzysort": "^3.1.0",
"immer": "^10.1.3",
+ "jimp": "^1.6.0",
"lodash-es": "^4.17.21",
"markdown-to-jsx": "^7.7.13",
"pwa-install-handler": "^2.6.3",
diff --git a/packages/website/src/components/ScreenshotButton.tsx b/packages/website/src/components/ScreenshotButton.tsx
index c5be06dc8..b86be58b9 100644
--- a/packages/website/src/components/ScreenshotButton.tsx
+++ b/packages/website/src/components/ScreenshotButton.tsx
@@ -6,7 +6,8 @@ import {
Popover,
type ButtonProps,
} from "@radix-ui/themes";
-import type { RefObject } from "react";
+import { Jimp } from "jimp";
+import { useCallback, type RefObject } from "react";
import { useLocale } from "../hooks/useLocale";
import { screenshotReadyEvent } from "./Tankopedia/HeroSection/components/TankSandbox/components/SceneProps";
@@ -18,6 +19,35 @@ interface Props extends ButtonProps {
export function ScreenshotButton({ canvas, name, children, ...props }: Props) {
const { strings } = useLocale();
+ const screenshot = useCallback(async () => {
+ if (!canvas.current) {
+ throw new Error("Canvas not found");
+ }
+
+ screenshotReadyEvent.dispatch(true);
+
+ const blob = await new Promise((resolve, reject) => {
+ requestAnimationFrame(() => {
+ canvas.current.toBlob((blob) => {
+ screenshotReadyEvent.dispatch(false);
+
+ if (!blob) {
+ reject(new Error("Failed to capture screenshot"));
+ } else {
+ resolve(blob);
+ }
+ });
+ });
+ });
+
+ const buffer = await blob.arrayBuffer();
+ const image = await Jimp.fromBuffer(buffer);
+
+ image.autocrop();
+
+ return image;
+ }, [canvas]);
+
return (
@@ -37,23 +67,13 @@ export function ScreenshotButton({ canvas, name, children, ...props }: Props) {