mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-24 20:19:49 +02:00
feat: error handling for /research
This commit is contained in:
1 parent
787d3ae6ea
commit
d39ab43e74
6 files changed
+337
-96
No files matched your search
+289
-84
@@ -1,4 +1,4 @@
|
|||||||
import { escapeMarkdown } from 'discord.js';
|
import { Locale, escapeMarkdown } from 'discord.js';
|
||||||
import CommandWrapper from '../components/CommandWrapper';
|
import CommandWrapper from '../components/CommandWrapper';
|
||||||
import TitleBar from '../components/TitleBar';
|
import TitleBar from '../components/TitleBar';
|
||||||
import { equipmentPriceMatrix } from '../constants/equipmentPrice';
|
import { equipmentPriceMatrix } from '../constants/equipmentPrice';
|
||||||
@@ -17,62 +17,207 @@ import {
|
|||||||
TIER_ROMAN_NUMERALS,
|
TIER_ROMAN_NUMERALS,
|
||||||
} from '../core/blitzkrieg/tankDefinitions/constants';
|
} from '../core/blitzkrieg/tankDefinitions/constants';
|
||||||
import { tankIconPng } from '../core/blitzkrieg/tankIconPng';
|
import { tankIconPng } from '../core/blitzkrieg/tankIconPng';
|
||||||
import addTankChoices from '../core/discord/addTankChoices';
|
|
||||||
import autocompleteTanks from '../core/discord/autocompleteTanks';
|
import autocompleteTanks from '../core/discord/autocompleteTanks';
|
||||||
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
import { createLocalizedCommand } from '../core/discord/createLocalizedCommand';
|
||||||
|
import { localizationObject } from '../core/discord/localizationObject';
|
||||||
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
|
import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand';
|
||||||
|
import { translator } from '../core/localization/translator';
|
||||||
import { CommandRegistry } from '../events/interactionCreate';
|
import { CommandRegistry } from '../events/interactionCreate';
|
||||||
import { theme } from '../stitches.config';
|
import { theme } from '../stitches.config';
|
||||||
|
|
||||||
export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
||||||
|
const { t } = translator(Locale.EnglishUS);
|
||||||
|
|
||||||
resolve({
|
resolve({
|
||||||
inProduction: true,
|
inProduction: true,
|
||||||
inPublic: true,
|
inPublic: true,
|
||||||
|
|
||||||
command: createLocalizedCommand('research').addStringOption(addTankChoices),
|
command: createLocalizedCommand('research')
|
||||||
|
.addStringOption((option) =>
|
||||||
|
option
|
||||||
|
.setName(t`bot.commands.research.options.target_tank`)
|
||||||
|
.setNameLocalizations(
|
||||||
|
localizationObject('bot.commands.research.options.target_tank'),
|
||||||
|
)
|
||||||
|
.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'),
|
||||||
|
)
|
||||||
|
.setDescription(
|
||||||
|
t`bot.commands.research.options.starting_tank.description`,
|
||||||
|
)
|
||||||
|
.setDescriptionLocalizations(
|
||||||
|
localizationObject(
|
||||||
|
'bot.commands.research.options.starting_tank.description',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.setAutocomplete(true)
|
||||||
|
.setRequired(false),
|
||||||
|
),
|
||||||
|
|
||||||
async handler(interaction) {
|
async handler(interaction) {
|
||||||
|
const { t, translate } = translator(interaction.locale);
|
||||||
const awaitedTankDefinitions = await tankDefinitions;
|
const awaitedTankDefinitions = await tankDefinitions;
|
||||||
const tankId = await resolveTankId(
|
const targetTankId = await resolveTankId(
|
||||||
interaction.options.getString('tank', true),
|
interaction.options.getString('target-tank', true),
|
||||||
interaction.locale,
|
interaction.locale,
|
||||||
|
true,
|
||||||
);
|
);
|
||||||
const tank = awaitedTankDefinitions[tankId];
|
const startingTankRaw = interaction.options.getString('starting-tank');
|
||||||
|
const targetTank = awaitedTankDefinitions[targetTankId];
|
||||||
if (tank.treeType !== 'researchable') {
|
|
||||||
return `# ${escapeMarkdown(tank.name)} is not researchable\n\nThis tank is not on the tech tree.`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tank.tier === 1) {
|
|
||||||
return `# ${escapeMarkdown(tank.name)} is not researchable\n\nThis tank is a tier I and is always unlocked.`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { id, region } = await resolvePlayerFromCommand(interaction);
|
const { id, region } = await resolvePlayerFromCommand(interaction);
|
||||||
const tankStats = await getTankStats(region, id, interaction.locale);
|
let startingTankId: number;
|
||||||
const ancestry = await resolveAncestry(tankId);
|
const targetTankAncestry = await resolveAncestry(targetTankId);
|
||||||
const firstOwnedTechTree = ancestry.find(
|
|
||||||
(id) =>
|
|
||||||
awaitedTankDefinitions[id].tier === 1 ||
|
|
||||||
tankStats.some(({ tank_id }) => tank_id === id),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (firstOwnedTechTree === undefined) {
|
if (startingTankRaw) {
|
||||||
throw new Error('No first owned tech tree found.');
|
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[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.treeType !== '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, interaction.locale);
|
||||||
|
|
||||||
|
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[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 line = (
|
const techTreeLine = await buildTechTreeLine(
|
||||||
await buildTechTreeLine(firstOwnedTechTree, tankId)
|
startingTankId,
|
||||||
).reverse();
|
targetTankId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const line = [...techTreeLine, startingTankId].reverse();
|
||||||
const { nickname } = await getAccountInfo(region, id);
|
const { nickname } = await getAccountInfo(region, id);
|
||||||
const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan;
|
const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan;
|
||||||
const clanImage = clan ? emblemIdToURL(clan.emblem_set_id) : undefined;
|
const clanImage = clan ? emblemIdToURL(clan.emblem_set_id) : undefined;
|
||||||
|
const costs = await Promise.all(
|
||||||
|
line.map(async (id) => {
|
||||||
|
const tank = awaitedTankDefinitions[id];
|
||||||
|
const turretXps = new Map<number, number>();
|
||||||
|
const gunXps = new Map<number, number>();
|
||||||
|
const engineXps = new Map<number, number>();
|
||||||
|
const trackXps = new Map<number, number>();
|
||||||
|
|
||||||
return (
|
tank.turrets.forEach((turret) => {
|
||||||
|
if (turret.xp !== undefined) {
|
||||||
|
turretXps.set(turret.id, turret.xp);
|
||||||
|
}
|
||||||
|
|
||||||
|
turret.guns.forEach((gun) => {
|
||||||
|
if (gun.xp !== undefined) {
|
||||||
|
gunXps.set(gun.id, gun.xp);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tank.engines.forEach((engine) => {
|
||||||
|
if (engine.xp !== undefined) {
|
||||||
|
engineXps.set(engine.id, engine.xp);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tank.tracks.forEach((track) => {
|
||||||
|
if (track.xp !== undefined) {
|
||||||
|
trackXps.set(track.id, track.xp);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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.xp,
|
||||||
|
upgrades: moduleXp,
|
||||||
|
purchase: tank.price.value,
|
||||||
|
equipment: equipmentPriceMatrix[tank.tier].reduce(
|
||||||
|
(a, b) => a + 3 * b,
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const image = (
|
||||||
<CommandWrapper>
|
<CommandWrapper>
|
||||||
<TitleBar
|
<TitleBar
|
||||||
title={nickname}
|
title={nickname}
|
||||||
image={clanImage}
|
image={clanImage}
|
||||||
description={`Research • ${tank.name}`}
|
description={`${t`bot.commands.research.body.subtitle`} • ${targetTank.name}`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -82,51 +227,98 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
gap: 8,
|
gap: 8,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
borderRadius: 4,
|
||||||
|
backgroundColor: theme.colors.appBackground2,
|
||||||
|
border: theme.borderStyles.subtle,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ width: 48 }}></div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
position: 'relative',
|
||||||
|
right: 24,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
color: theme.colors.textHighContrast,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t`bot.commands.research.body.total`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: 8,
|
||||||
|
padding: '16px 0',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: 4,
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={await iconPng(asset('icons/currencies/xp.webp'))}
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 16,
|
||||||
|
color: theme.colors.textHighContrast,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{costs
|
||||||
|
.reduce((a, b) => a + b.purchase + b.equipment, 0)
|
||||||
|
.toLocaleString(interaction.locale)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: 4,
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={await iconPng(asset('icons/currencies/silver.webp'))}
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: 16,
|
||||||
|
color: theme.colors.textHighContrast,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{costs
|
||||||
|
.reduce((a, b) => a + (b.research ?? 0) + b.upgrades, 0)
|
||||||
|
.toLocaleString(interaction.locale)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{await Promise.all(
|
{await Promise.all(
|
||||||
line.map(async (id) => {
|
line.map(async (id, index) => {
|
||||||
const tank = awaitedTankDefinitions[id];
|
const tank = awaitedTankDefinitions[id];
|
||||||
const turretXps = new Map<number, number>();
|
|
||||||
const gunXps = new Map<number, number>();
|
|
||||||
const engineXps = new Map<number, number>();
|
|
||||||
const trackXps = new Map<number, number>();
|
|
||||||
|
|
||||||
tank.turrets.forEach((turret) => {
|
|
||||||
if (turret.xp !== undefined) {
|
|
||||||
turretXps.set(turret.id, turret.xp);
|
|
||||||
}
|
|
||||||
|
|
||||||
turret.guns.forEach((gun) => {
|
|
||||||
if (gun.xp !== undefined) {
|
|
||||||
gunXps.set(gun.id, gun.xp);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
tank.engines.forEach((engine) => {
|
|
||||||
if (engine.xp !== undefined) {
|
|
||||||
engineXps.set(engine.id, engine.xp);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
tank.tracks.forEach((track) => {
|
|
||||||
if (track.xp !== undefined) {
|
|
||||||
trackXps.set(track.id, track.xp);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let moduleXp = 0;
|
|
||||||
turretXps.forEach((value) => {
|
|
||||||
moduleXp += value;
|
|
||||||
});
|
|
||||||
gunXps.forEach((value) => {
|
|
||||||
moduleXp += value;
|
|
||||||
});
|
|
||||||
engineXps.forEach((value) => {
|
|
||||||
moduleXp += value;
|
|
||||||
});
|
|
||||||
trackXps.forEach((value) => {
|
|
||||||
moduleXp += value;
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -135,7 +327,7 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
backgroundColor: theme.colors.appBackground2,
|
backgroundColor: theme.colors.appBackground2,
|
||||||
border: `1px solid ${theme.colors.componentInteractive}`,
|
border: theme.borderStyles.subtle,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -241,12 +433,15 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
color: theme.colors.textHighContrast,
|
color: theme.colors.textHighContrast,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Research:{' '}
|
{translate('bot.commands.research.body.research', [
|
||||||
{tank.xp?.toLocaleString(interaction.locale)}
|
`${costs[index].research?.toLocaleString(
|
||||||
|
interaction.locale,
|
||||||
|
)}`,
|
||||||
|
])}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{moduleXp > 0 && (
|
{costs[index].upgrades > 0 && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -267,7 +462,9 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
color: theme.colors.textHighContrast,
|
color: theme.colors.textHighContrast,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Upgrades: {moduleXp.toLocaleString()}
|
{translate('bot.commands.research.body.upgrades', [
|
||||||
|
costs[index].upgrades.toLocaleString(),
|
||||||
|
])}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -292,8 +489,11 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
color: theme.colors.textHighContrast,
|
color: theme.colors.textHighContrast,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Purchase:{' '}
|
{translate('bot.commands.research.body.purchase', [
|
||||||
{tank.price.value.toLocaleString(interaction.locale)}
|
costs[index].purchase.toLocaleString(
|
||||||
|
interaction.locale,
|
||||||
|
),
|
||||||
|
])}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -317,10 +517,11 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
color: theme.colors.textHighContrast,
|
color: theme.colors.textHighContrast,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Equipment:{' '}
|
{translate('bot.commands.research.body.equipment', [
|
||||||
{equipmentPriceMatrix[tank.tier]
|
costs[index].equipment.toLocaleString(
|
||||||
.reduce((a, b) => a + 3 * b, 0)
|
interaction.locale,
|
||||||
.toLocaleString(interaction.locale)}
|
),
|
||||||
|
])}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -331,10 +532,14 @@ export const researchCommand = new Promise<CommandRegistry>((resolve) => {
|
|||||||
</div>
|
</div>
|
||||||
</CommandWrapper>
|
</CommandWrapper>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return startingTankRaw
|
||||||
|
? image
|
||||||
|
: [t`bot.commands.research.body.estimation`, image];
|
||||||
},
|
},
|
||||||
|
|
||||||
autocomplete(interaction) {
|
autocomplete(interaction) {
|
||||||
autocompleteTanks(interaction, true);
|
autocompleteTanks(interaction, true, ['target-tank', 'starting-tank']);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -2,24 +2,27 @@ import { Locale } from 'discord.js';
|
|||||||
import { go } from 'fuzzysort';
|
import { go } from 'fuzzysort';
|
||||||
import markdownEscape from 'markdown-escape';
|
import markdownEscape from 'markdown-escape';
|
||||||
import { UserError } from '../../hooks/userError';
|
import { UserError } from '../../hooks/userError';
|
||||||
import {
|
import { tankDefinitions, tankNames } from '../blitzkrieg/tankDefinitions';
|
||||||
tankDefinitions,
|
import { tankNamesTechTreeOnly } from '../discord/autocompleteTanks';
|
||||||
tanksDefinitionsArray,
|
|
||||||
} from '../blitzkrieg/tankDefinitions';
|
|
||||||
import { translator } from '../localization/translator';
|
import { translator } from '../localization/translator';
|
||||||
|
|
||||||
export default async function resolveTankId(
|
export default async function resolveTankId(
|
||||||
tank: string | number,
|
tank: string | number,
|
||||||
locale: Locale,
|
locale: Locale,
|
||||||
|
techTreeOnly = false,
|
||||||
) {
|
) {
|
||||||
const { translate } = translator(locale);
|
const { translate } = translator(locale);
|
||||||
const number = typeof tank === 'string' ? Number(tank) : tank;
|
const number = typeof tank === 'string' ? Number(tank) : tank;
|
||||||
|
|
||||||
if (Number.isNaN(number)) {
|
if (Number.isNaN(number)) {
|
||||||
const searchResult = go(`${tank}`, await tanksDefinitionsArray, {
|
const searchResult = go(
|
||||||
keys: ['name'],
|
`${tank}`,
|
||||||
limit: 1,
|
await (techTreeOnly ? tankNamesTechTreeOnly : tankNames),
|
||||||
});
|
{
|
||||||
|
keys: ['combined'],
|
||||||
|
limit: 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if (searchResult.length === 0) {
|
if (searchResult.length === 0) {
|
||||||
throw new UserError(
|
throw new UserError(
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ export async function buildTechTreeLine(start: number, end: number) {
|
|||||||
if (start === end) return line;
|
if (start === end) return line;
|
||||||
for (const ancestorId of endTank.ancestors) {
|
for (const ancestorId of endTank.ancestors) {
|
||||||
if ((await resolveAncestry(ancestorId)).includes(start)) {
|
if ((await resolveAncestry(ancestorId)).includes(start)) {
|
||||||
line.push(...(await buildTechTreeLine(start, ancestorId)));
|
const ancestorTree = await buildTechTreeLine(start, ancestorId);
|
||||||
|
line.push(...ancestorTree);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,16 +10,17 @@ import {
|
|||||||
OVERFLOW_SUFFIX,
|
OVERFLOW_SUFFIX,
|
||||||
} from './autocompleteClan/constants';
|
} from './autocompleteClan/constants';
|
||||||
|
|
||||||
const tankNamesTechTreeOnly = tankNames.then((names) =>
|
export const tankNamesTechTreeOnly = tankNames.then((names) =>
|
||||||
names.filter((tank) => tank.treeType === 'researchable'),
|
names.filter((tank) => tank.treeType === 'researchable'),
|
||||||
);
|
);
|
||||||
|
|
||||||
export default async function autocompleteTanks(
|
export default async function autocompleteTanks(
|
||||||
interaction: AutocompleteInteraction<CacheType>,
|
interaction: AutocompleteInteraction<CacheType>,
|
||||||
techTreeOnly = false,
|
techTreeOnly = false,
|
||||||
|
fields = ['tank'],
|
||||||
) {
|
) {
|
||||||
const focusedOption = interaction.options.getFocused(true);
|
const focusedOption = interaction.options.getFocused(true);
|
||||||
if (focusedOption.name !== 'tank') return;
|
if (!fields.includes(focusedOption.name)) return;
|
||||||
|
|
||||||
await interaction.respond(
|
await interaction.respond(
|
||||||
focusedOption.value
|
focusedOption.value
|
||||||
|
|||||||
+31
-1
@@ -197,7 +197,37 @@
|
|||||||
"commands": {
|
"commands": {
|
||||||
"research": {
|
"research": {
|
||||||
"$": "research",
|
"$": "research",
|
||||||
"description": "Lists the expenses in researching a line"
|
"description": "Lists the expenses in researching a line",
|
||||||
|
|
||||||
|
"options": {
|
||||||
|
"target_tank": {
|
||||||
|
"$": "target-tank",
|
||||||
|
"description": "The final tank to research in the line"
|
||||||
|
},
|
||||||
|
"starting_tank": {
|
||||||
|
"$": "starting-tank",
|
||||||
|
"description": "The starting tank in the line (inferred automatically if not provided)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"body": {
|
||||||
|
"subtitle": "Research",
|
||||||
|
"total": "Total",
|
||||||
|
"research": "Research: %s",
|
||||||
|
"upgrades": "Upgrades: %s",
|
||||||
|
"purchase": "Purchase: %s",
|
||||||
|
"equipment": "Equipment: %s",
|
||||||
|
"estimation": "# This is an estimation!\n\nThis list may include already researched tanks because of the inaccurate information Wargaming provides. Try providing a `starting-tank` from the line when using the command if you can."
|
||||||
|
},
|
||||||
|
|
||||||
|
"errors": {
|
||||||
|
"non_tech_tree": "# %s is not researchable\n\nThis tank is not on the tech tree.",
|
||||||
|
"tier_1": "# %s is not researchable\n\nThis tank is a tier I and is always unlocked.",
|
||||||
|
"already_researched": "# %s is already researched\n\nTry providing a `starting-tank` when using the command to view the totals for a line you have already researched.",
|
||||||
|
"start_end_equal": "# %s was provided twice\n\nMake sure `starting-tank` and `target-tank` are different.",
|
||||||
|
"tanks_not_on_line": "# %s and %s are not on the same line\n\nMake sure `starting-tank` and `target-tank` are on the same tech-tree line.",
|
||||||
|
"unordered": "# %s higher tiered to %s\n\nMake sure to provide %s as the `starting-tank` instead as it is the lower tiered tank."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"about": {
|
"about": {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export const theme = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
borderStyles: {
|
borderStyles: {
|
||||||
|
subtle: `1px solid ${slateDark.slate2}`,
|
||||||
...createBorderStyles(slateDark),
|
...createBorderStyles(slateDark),
|
||||||
...createBorderStyles(blueDark, undefined, 'blue'),
|
...createBorderStyles(blueDark, undefined, 'blue'),
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in new issue
Block a user