import { asset, emblemURL, equipmentPriceMatrix, getAccountInfo, getClanAccountInfo, getTankStats, TANK_ICONS, TankType, TIER_ROMAN_NUMERALS, } from '@blitzkit/core'; import { literals } from '@blitzkit/i18n'; 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 { strings } = translator(Locale.EnglishUS); resolve({ command: createLocalizedCommand('research') .addStringOption((option) => option .setName(strings.bot.commands.research.options.target_tank.name) .setNameLocalizations( localizationObject( (strings) => strings.bot.commands.research.options.target_tank.name, undefined, true, ), ) .setDescription( strings.bot.commands.research.options.target_tank.description, ) .setDescriptionLocalizations( localizationObject( (strings) => strings.bot.commands.research.options.target_tank.description, ), ) .setAutocomplete(true) .setRequired(true), ) .addStringOption((option) => option .setName(strings.bot.commands.research.options.starting_tank.name) .setNameLocalizations( localizationObject( (strings) => strings.bot.commands.research.options.starting_tank.name, undefined, true, ), ) .setDescription( strings.bot.commands.research.options.starting_tank.description, ) .setDescriptionLocalizations( localizationObject( (strings) => strings.bot.commands.research.options.starting_tank.description, ), ) .setAutocomplete(true) .setRequired(false), ) .addStringOption(addUsernameChoices), async handler(interaction) { const { strings, unwrap } = 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 literals( strings.bot.commands.research.errors.start_end_equal, [escapeMarkdown(unwrap(targetTank.name))], ); } const startingTank = awaitedTankDefinitions.tanks[startingTankId]; if (startingTank.tier > targetTank.tier) { return literals(strings.bot.commands.research.errors.unordered, [ escapeMarkdown(unwrap(startingTank.name)), escapeMarkdown(unwrap(targetTank.name)), escapeMarkdown(unwrap(targetTank.name)), ]); } if (!targetTankAncestry.includes(startingTankId)) { return literals( strings.bot.commands.research.errors.tanks_not_on_line, [ escapeMarkdown(unwrap(targetTank.name)), escapeMarkdown(unwrap(startingTank.name)), ], ); } } else { if (targetTank.type !== TankType.RESEARCHABLE) { return literals(strings.bot.commands.research.errors.non_tech_tree, [ escapeMarkdown(unwrap(targetTank.name)), ]); } if (targetTank.tier === 1) { return literals(strings.bot.commands.research.errors.tier_1, [ escapeMarkdown(unwrap(targetTank.name)), ]); } const tankStats = await getTankStats(region, id); if (tankStats === null) { return strings.bot.common.errors.no_tank_stats; } if (tankStats.some(({ tank_id }) => tank_id === targetTankId)) { return literals( strings.bot.commands.research.errors.already_researched, [escapeMarkdown(unwrap(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.research_cost !== undefined && typeof gun.research_cost.research_cost_type!.value === 'number' ) { gunXps.set(gun.id, gun.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 = (
{strings.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
{unwrap(tank.name)} {unwrap(tank.name)}
XP icon {literals( strings.bot.commands.research.body.research, [ `${costs[index].research?.toLocaleString( interaction.locale, )}`, ], )}
{costs[index].upgrades > 0 && (
Free XP icon {literals( strings.bot.commands.research.body.upgrades, [costs[index].upgrades.toLocaleString()], )}
)}
Silver icon {literals( strings.bot.commands.research.body.purchase, [ costs[index].purchase.toLocaleString( interaction.locale, ), ], )}
Silver icon {literals( strings.bot.commands.research.body.equipment, [ costs[index].equipment.toLocaleString( interaction.locale, ), ], )}
); }), )}
); return startingTankRaw ? image : [strings.bot.commands.research.body.estimation, image]; }, autocomplete(interaction) { autocompleteTanks(interaction, true, ['target-tank', 'starting-tank']); }, }); });