diff --git a/commands/barter.mjs b/commands/barter.mjs index e1b7e90..95cca0a 100644 --- a/commands/barter.mjs +++ b/commands/barter.mjs @@ -78,10 +78,16 @@ const defaultFunction = { title += " (" + barter.rewardItems[0].count + ")"; } - const locked = prog.traders[barter.trader.id] < barter.level ? '🔒' : ''; - title += `\r\n ${traders.find(tr => tr.id === barter.trader.id).name} ${t('LL')}${barter.level}${locked}`; + //title += `\r\n ${traders.find(tr => tr.id === barter.trader.id).name} ${t('LL')}${barter.level}${locked}`; embed.setTitle(title); - embed.setURL(`${rewardItem.link}#${i}`); + embed.setURL(rewardItem.link); + const locked = prog.traders[barter.trader.id] < barter.level ? '🔒' : ''; + const trader = traders.find(tr => tr.id === barter.trader.id); + embed.setAuthor({ + name: `${trader.name} ${t('LL')}${barter.level}${locked}`, + iconURL: trader.imageLink, + url: `https://tarkov.dev/trader/${trader.normalizedName}`, + }); if (rewardItem.iconLink) { embed.setThumbnail(rewardItem.iconLink); diff --git a/commands/craft.mjs b/commands/craft.mjs index fccdd8d..a0287f8 100644 --- a/commands/craft.mjs +++ b/commands/craft.mjs @@ -81,13 +81,19 @@ const defaultFunction = { title += " (" + craft.rewardItems[0].count + ")"; } + embed.setTitle(title); + embed.setURL(rewardItem.link); + const measuredTime = new Date(null); let timeDiscount = prog.skills['crafting']*0.0075*craft.duration; measuredTime.setSeconds(craft.duration - timeDiscount); const locked = prog.hideout[craft.station.id] < craft.level ? '🔒' : ''; - title += `\n${stations.find(st => st.id === craft.station.id).name} ${t('level')} ${craft.level} (${measuredTime.toISOString().substr(11, 8)})${locked}`; - embed.setTitle(title); - embed.setURL(`${rewardItem.link}#${i}`); + const station = stations.find(st => st.id === craft.station.id); + embed.setAuthor({ + name: `${station.name} ${t('level')} ${craft.level} (${measuredTime.toISOString().substring(11, 19)})${locked}`, + iconURL: station.imageLink, + url: `https://tarkov.dev/hideout-profit/?all=true&station=${station.normalizedName}&search=${encodeURIComponent(rewardItem.name)}`, + }); if (rewardItem.iconLink) { embed.setThumbnail(rewardItem.iconLink); diff --git a/commands/quest.mjs b/commands/quest.mjs new file mode 100644 index 0000000..87d8a7a --- /dev/null +++ b/commands/quest.mjs @@ -0,0 +1,93 @@ +import { EmbedBuilder, SlashCommandBuilder } from 'discord.js'; + +import gameData from '../modules/game-data.mjs'; +import { getFixedT, getCommandLocalizations } from '../modules/translations.mjs'; +import progress from '../modules/progress-shard.mjs'; + +const defaultFunction = { + data: new SlashCommandBuilder() + .setName('quest') + .setDescription('Get detailed information about a quest') + .setNameLocalizations(getCommandLocalizations('quest')) + .setDescriptionLocalizations(getCommandLocalizations('quest_desc')) + .addStringOption(option => option + .setName('name') + .setDescription('Quest name to search for') + .setNameLocalizations(getCommandLocalizations('name')) + .setDescriptionLocalizations(getCommandLocalizations('quest_seach_desc')) + .setAutocomplete(true) + .setRequired(true) + ), + + async execute(interaction) { + await interaction.deferReply(); + const locale = await progress.getServerLanguage(interaction.guildId) || interaction.locale; + const t = getFixedT(locale); + const searchString = interaction.options.getString('name'); + + const [tasks, traders] = await Promise.all([ + gameData.tasks.getAll(locale), + gameData.traders.getAll(locale), + ]); + const matchedTasks = tasks.filter(t => t.name.toLowerCase().includes(searchString.toLowerCase())); + + if (matchedTasks.length === 0) { + return interaction.editReply({ + content: t('Found no results for "{{searchString}}"', { + searchString: searchString + }), + ephemeral: true, + }); + } + + let task = matchedTasks.find(t => t.name.toLowerCase() === searchString.toLowerCase()); + + if (!task) { + task = matchedTasks[0]; + } + + const trader = traders.find(t => t.id === task.trader.id); + + const embed = new EmbedBuilder(); + + // Construct the embed + embed.setTitle(task.name); + if (task.taskImageLink) { + embed.setImage(task.taskImageLink); + } + embed.setAuthor({ + name: trader.name, + iconURL: trader.imageLink, + url: `https://tarkov.dev/trader/${trader.normalizedName}`, + }); + embed.setURL(`https://tarkov.dev/task/${task.normalizedName}`); + const descriptionParts = [`[${t('Wiki Link')}](${task.wikiLink})`]; + if (task.minPlayerLevel) { + descriptionParts.push(`${t('Minimum Level')}: ${task.minPlayerLevel}`); + } + embed.setDescription(descriptionParts.join('\n')); + + embed.addFields( + { name: t('Objectives'), value: task.objectives.map(obj => `${obj.description}${obj.count > 1 ? ` (x${obj.count})` : ''}`).join('\n'), inline: false }, + ); + + const footerParts = [`${task.experience} EXP`]; + for (const repReward of task.finishRewards.traderStanding) { + const repTrader = traders.find(t => t.id === repReward.trader.id); + const sign = repReward.standing >= 0 ? '+' : ''; + footerParts.push(`${repTrader.name} ${sign}${repReward.standing}`); + } + + embed.setFooter({ text: footerParts.join(' | ') }); + + return interaction.editReply({ + embeds: [embed], + }); + }, + examples: [ + '/$t(map) Woods', + '/$t(map) customs' + ] +}; + +export default defaultFunction; diff --git a/modules/autocomplete.mjs b/modules/autocomplete.mjs index e85280f..b941d8d 100644 --- a/modules/autocomplete.mjs +++ b/modules/autocomplete.mjs @@ -36,7 +36,10 @@ const caches = { const allOption = allLocalizations[lang] || allLocalizations['en-US']; stations.push(allOption); return stations; - } + }, + quest: async lang => { + return gameData.tasks.getAll(lang).then(tasks => tasks.map(t => t.name).sort()); + }, }; const allLocalizations = getCommandLocalizations('all_desc'); diff --git a/modules/game-data.mjs b/modules/game-data.mjs index 8e0e31f..fcbae65 100644 --- a/modules/game-data.mjs +++ b/modules/game-data.mjs @@ -15,6 +15,7 @@ const gameData = { crafts: false, items: false, itemNames: {}, + tasks: {}, flea: false, skills: [ { @@ -363,6 +364,7 @@ export async function updateTraders() { normalizedName resetTime discount + imageLink levels { id level @@ -421,6 +423,8 @@ export async function updateHideout() { id tarkovDataId name + normalizedName + imageLink levels { id tarkovDataId @@ -852,6 +856,94 @@ export async function getStims(lang = 'en') { }); } +export async function updateTasks() { + const taskQueries = []; + for (const langCode of gameData.languages) { + taskQueries.push(`${langCode}: tasks(lang: ${langCode}) { + ...TaskFields + }`); + } + const query = `query StashTasks { + ${taskQueries.join('\n')} + } + fragment TaskFields on Task { + id + name + normalizedName + taskImageLink + objectives { + id + description + __typename + ...on TaskObjectiveBasic { + requiredKeys { + id + } + } + ...on TaskObjectiveItem { + count + requiredKeys { + id + } + } + ...on TaskObjectivePlayerLevel { + playerLevel + } + ...on TaskObjectiveShoot { + count + } + ...on TaskObjectiveSkill { + skillLevel { + name + level + } + } + ...on TaskObjectiveTraderLevel { + trader { + id + } + level + } + ...on TaskObjectiveUseItem { + count + } + } + trader { + id + } + minPlayerLevel + wikiLink + experience + finishRewards { + traderStanding { + trader { + id + } + standing + } + } + }`; + const response = await graphqlRequest({ graphql: query }).then(response => response.data); + + for (const lang in response) { + gameData.tasks[lang] = response[lang]; + } + + eventEmitter.emit('updatedTasks'); + return gameData.tasks; +}; + +export async function getTasks(lang = 'en') { + if (process.env.IS_SHARD) { + return getParentReply({data: 'gameData', function: 'tasks.getAll', args: lang}); + } + lang = validateLanguage(lang); + if (gameData.tasks[lang]) { + return gameData.tasks[lang]; + } + return updateTasks().then(ts => ts[lang]); +}; + export async function updateAll(rejectOnError = false) { try { await updateLanguages(); @@ -869,6 +961,7 @@ export async function updateAll(rejectOnError = false) { updateTraders(), updateHideout(), updateItems(), + updateTasks(), ]).then(results => { const taskNames = [ 'barters', @@ -878,6 +971,7 @@ export async function updateAll(rejectOnError = false) { 'traders', 'hideout', 'items', + 'tasks', ]; let reject = false; results.forEach((result, index) => { @@ -1017,6 +1111,16 @@ export default { getAmmo: getAmmo, getStims: getStims, }, + tasks: { + getAll: getTasks, + get: async (id, lang = 'en') => { + if (process.env.IS_SHARD) { + return getParentReply({data: 'gameData', function: 'tasks.get', args: [id, lang]}); + } + const tasks = await getTasks(lang); + return tasks.find(task => task.id === id); + }, + }, events: eventEmitter, updateAll: updateAll, validateLanguage, diff --git a/translations/en.json b/translations/en.json index 5071d9d..ba46b02 100644 --- a/translations/en.json +++ b/translations/en.json @@ -68,6 +68,7 @@ "Loot tiers are divided primarily by the per-slot value of the item": "Loot tiers are divided primarily by the per-slot value of the item", "Map": "Map", "Map made by {{author}}": "Map made by {{author}}", + "Minimum Level": "Minimum Level", "minutes": "minutes", "more": "more", "Name": "Name", @@ -79,6 +80,7 @@ "No prices available.": "No prices available.", "Note: Progress synced via [TarkovTracker](https://tarkovtracker.io/settings/) will overwrite your hideout settings. \nUse `/progress unlink` to stop syncing from TarkovTracker.": "Note: Progress synced via [TarkovTracker](https://tarkovtracker.io/settings/) will overwrite your hideout settings. \nUse `/progress unlink` to stop syncing from TarkovTracker.", "Net": "Net", + "Objectives": "Objectives", "Pen": "Pen", "Players": "Players", "Please visit {{url}} for more information": "Please visit {{url}} for more information", @@ -201,6 +203,9 @@ "progress_trader_desc": "Set trader level", "progress_trader_level_select_desc": "The trader's level", "progress_unlink_desc": "Unlink your TarkovTracker account", + "quest": "quest", + "quest_desc": "Get detailed information about a quest", + "quest_seach_desc": "Quest name to search for", "restock": "restock", "restock_alert_desc": "Set alerts for trader restocks", "restock_alert_send_desc": "Whether to send an alert",