import { asset, emblemURL, equipmentPriceMatrix, getAccountInfo, getClanAccountInfo, getTankStats, TANK_ICONS, TankType, TIER_ROMAN_NUMERALS, } from '@blitzkit/core'; import { escapeMarkdown, Locale } from 'discord.js'; import { CommandWrapper } from '../components/CommandWrapper'; import { TitleBar } from '../components/TitleBar'; import { resolveTankId } from '../core/blitz/resolveTankId'; import { buildTechTreeLine } from '../core/blitzkit/buildTechTreeLine'; import { iconPng } from '../core/blitzkit/iconPng'; import { tankDefinitions } from '../core/blitzkit/nonBlockingPromises'; import { resolveAncestry } from '../core/blitzkit/resolveAncestry'; import { tankIconPng } from '../core/blitzkit/tankIconPng'; import { addUsernameChoices } from '../core/discord/addUsernameChoices'; import { autocompleteTanks } from '../core/discord/autocompleteTanks'; import { createLocalizedCommand } from '../core/discord/createLocalizedCommand'; import { localizationObject } from '../core/discord/localizationObject'; import { resolvePlayerFromCommand } from '../core/discord/resolvePlayerFromCommand'; import { translator } from '../core/localization/translator'; import { CommandRegistry } from '../events/interactionCreate'; import { theme } from '../stitches.config'; export const researchCommand = new Promise((resolve) => { const { t } = translator(Locale.EnglishUS); resolve({ command: createLocalizedCommand('research') .addStringOption((option) => option .setName(t`bot.commands.research.options.target_tank`) .setNameLocalizations( localizationObject( 'bot.commands.research.options.target_tank', undefined, true, ), ) .setDescription( t`bot.commands.research.options.target_tank.description`, ) .setDescriptionLocalizations( localizationObject( 'bot.commands.research.options.target_tank.description', ), ) .setAutocomplete(true) .setRequired(true), ) .addStringOption((option) => option .setName(t`bot.commands.research.options.starting_tank`) .setNameLocalizations( localizationObject( 'bot.commands.research.options.starting_tank', undefined, true, ), ) .setDescription( t`bot.commands.research.options.starting_tank.description`, ) .setDescriptionLocalizations( localizationObject( 'bot.commands.research.options.starting_tank.description', ), ) .setAutocomplete(true) .setRequired(false), ) .addStringOption(addUsernameChoices), async handler(interaction) { const { t, translate } = translator(interaction.locale); const awaitedTankDefinitions = await tankDefinitions; const targetTankId = await resolveTankId( interaction.options.getString('target-tank', true), interaction.locale, true, ); const startingTankRaw = interaction.options.getString('starting-tank'); const targetTank = awaitedTankDefinitions.tanks[targetTankId]; const { id, region } = await resolvePlayerFromCommand(interaction); let startingTankId: number; const targetTankAncestry = await resolveAncestry(targetTankId); if (startingTankRaw) { startingTankId = await resolveTankId( startingTankRaw, interaction.locale, true, ); if (targetTankId === startingTankId) { return translate('bot.commands.research.errors.start_end_equal', [ escapeMarkdown(targetTank.name), ]); } const startingTank = awaitedTankDefinitions.tanks[startingTankId]; if (startingTank.tier > targetTank.tier) { return translate('bot.commands.research.errors.unordered', [ escapeMarkdown(startingTank.name), escapeMarkdown(targetTank.name), escapeMarkdown(targetTank.name), ]); } if (!targetTankAncestry.includes(startingTankId)) { return translate('bot.commands.research.errors.tanks_not_on_line', [ escapeMarkdown(targetTank.name), escapeMarkdown(startingTank.name), ]); } } else { if (targetTank.type !== TankType.RESEARCHABLE) { return translate('bot.commands.research.errors.non_tech_tree', [ escapeMarkdown(targetTank.name), ]); } if (targetTank.tier === 1) { return translate('bot.commands.research.errors.tier_1', [ escapeMarkdown(targetTank.name), ]); } const tankStats = await getTankStats(region, id); if (tankStats === null) { return t`bot.common.errors.no_tank_stats`; } if (tankStats.some(({ tank_id }) => tank_id === targetTankId)) { return translate('bot.commands.research.errors.already_researched', [ escapeMarkdown(targetTank.name), ]); } const foundAncestor = targetTankAncestry.find( (id) => awaitedTankDefinitions.tanks[id].tier === 1 || tankStats.some(({ tank_id }) => tank_id === id), ); if (foundAncestor === undefined) { throw new Error('No first owned tech tree found.'); } startingTankId = foundAncestor; } const techTreeLine = await buildTechTreeLine( startingTankId, targetTankId, ); const line = [...techTreeLine, startingTankId].reverse(); const { nickname } = await getAccountInfo(region, id); const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan; const clanImage = clan ? emblemURL(clan.emblem_set_id) : undefined; const costs = await Promise.all( line.map(async (id) => { const tank = awaitedTankDefinitions.tanks[id]; const turretXps = new Map(); const gunXps = new Map(); const engineXps = new Map(); const trackXps = new Map(); tank.turrets.forEach((turret) => { if ( turret.research_cost !== undefined && typeof turret.research_cost.research_cost_type!.value === 'number' ) { turretXps.set( turret.id, turret.research_cost.research_cost_type!.value, ); } turret.guns.forEach((gun) => { if ( gun.gun_type!.value.base.research_cost !== undefined && typeof gun.gun_type!.value.base.research_cost .research_cost_type!.value === 'number' ) { gunXps.set( gun.gun_type!.value.base.id, gun.gun_type!.value.base.research_cost.research_cost_type! .value, ); } }); }); tank.engines.forEach((engine) => { if ( engine.research_cost !== undefined && typeof engine.research_cost.research_cost_type!.value === 'number' ) { engineXps.set( engine.id, engine.research_cost.research_cost_type!.value, ); } }); tank.tracks.forEach((track) => { if ( track.research_cost !== undefined && typeof track.research_cost.research_cost_type!.value === 'number' ) { trackXps.set( track.id, track.research_cost.research_cost_type!.value, ); } }); let moduleXp = 0; turretXps.forEach((value) => { moduleXp += value; }); gunXps.forEach((value) => { moduleXp += value; }); engineXps.forEach((value) => { moduleXp += value; }); trackXps.forEach((value) => { moduleXp += value; }); return { research: tank.research_cost!.research_cost_type!.value as number, upgrades: moduleXp, purchase: tank.price.value, equipment: equipmentPriceMatrix[tank.tier].reduce( (a, b) => a + 3 * b, 0, ), }; }), ); const image = (
{t`bot.commands.research.body.total`}
XP icon Experience:{' '} {costs .reduce((a, b) => a + (b.research ?? 0) + b.upgrades, 0) .toLocaleString(interaction.locale)}
Silver icon Purchase:{' '} {costs .reduce((a, b) => a + b.purchase, 0) .toLocaleString(interaction.locale)}
Silver icon Equipment:{' '} {costs .reduce((a, b) => a + b.equipment, 0) .toLocaleString(interaction.locale)}
{await Promise.all( line.map(async (id, index) => { const tank = awaitedTankDefinitions.tanks[id]; return (
{TIER_ROMAN_NUMERALS[tank.tier]}
Tank icon
{tank.name} {tank.name}
XP icon {translate('bot.commands.research.body.research', [ `${costs[index].research?.toLocaleString( interaction.locale, )}`, ])}
{costs[index].upgrades > 0 && (
Free XP icon {translate('bot.commands.research.body.upgrades', [ costs[index].upgrades.toLocaleString(), ])}
)}
Silver icon {translate('bot.commands.research.body.purchase', [ costs[index].purchase.toLocaleString( interaction.locale, ), ])}
Silver icon {translate('bot.commands.research.body.equipment', [ costs[index].equipment.toLocaleString( interaction.locale, ), ])}
); }), )}
); return startingTankRaw ? image : [t`bot.commands.research.body.estimation`, image]; }, autocomplete(interaction) { autocompleteTanks(interaction, true, ['target-tank', 'starting-tank']); }, }); });