diff --git a/docs/commands.md b/docs/commands.md index 9dfd6ab22..a24806097 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -22,6 +22,7 @@ Slash Command | Description [**/map**](commands.md#map) | Get the currently connected server map image. [**/market**](commands.md#market) | Operations for In-Game Vending Machines. [**/players**](commands.md#players) | Get player/players information based on battlemetrics. +[**/raid**](commands.md#raid) | Display the raid costs for an item. [**/recycle**](commands.md#recycle) | Display the output of recycling an item. [**/research**](commands.md#research) | Display the cost to research an item. [**/reset**](commands.md#reset) | Reset Discord channels. @@ -224,6 +225,17 @@ Subcommand | Options | Description | Required ![Discord Slash Command players specific user Image](images/slash_commands/players_specific_user.png) +## **/raid** + +> **Display the raid costs for an item.** + +Subcommand | Options | Description | Required +---------- | ------- | ----------- | -------- +  | `name` | The name of the item to raid. | `False` +  | `id` | The id of the item to raid. | `False` + +![Discord Slash Command raid Image](images/slash_commands/raid.png) + ## **/recycle** > **Display the output of recycling an item.** diff --git a/docs/images/slash_commands/raid.png b/docs/images/slash_commands/raid.png new file mode 100644 index 000000000..6295ea1bc Binary files /dev/null and b/docs/images/slash_commands/raid.png differ diff --git a/src/commands/durability.js b/src/commands/durability.js new file mode 100644 index 000000000..d6510bdde --- /dev/null +++ b/src/commands/durability.js @@ -0,0 +1,155 @@ +/* + Copyright (C) 2023 Alexander Emanuelsson (alexemanuelol) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + https://github.com/alexemanuelol/rustplusplus + +*/ + +const Builder = require('@discordjs/builders'); + +const DiscordEmbeds = require('../discordTools/discordEmbeds.js'); +const DiscordMessages = require('../discordTools/discordMessages.js'); + +module.exports = { + name: 'raid', + + getData(client, guildId) { + return new Builder.SlashCommandBuilder() + .setName('raid') + .setDescription(client.intlGet(guildId, 'commandsRaidDesc')) + .addStringOption(option => option + .setName('name') + .setDescription(client.intlGet(guildId, 'theNameOfTheItem')) + .setRequired(false)) + .addStringOption(option => option + .setName('id') + .setDescription(client.intlGet(guildId, 'theIdOfTheItem')) + .setRequired(false)); + }, + + + + /** + * Group durability data by group and toolId. + * @param {array} data The array of durability data objects. + * @return {object} The grouped durability data. + */ + groupDurabilityData(client, data) { + const groupedData = {}; + + for (const item of data) { + if (item.which === 'soft') continue; + + if (!groupedData[item.group]) { + groupedData[item.group] = {}; + } + + if (!groupedData[item.group][item.toolId]) { + groupedData[item.group][item.toolId] = []; + } + + groupedData[item.group][item.toolId].push({ + ...item, + 'itemName': client.items.getName(item.toolId) + }); + } + + return groupedData; + }, + + getDurabilityData(raidItemName, raidItemId, client, guildId) { + let itemId = null; + if (raidItemName !== null) { + let item = null + if (!item) { + item = client.rustlabs.getClosestOtherNameByName(raidItemName); + } + + if (!item) { + item = client.items.getClosestItemIdByName(raidItemName); + if(item !== null) { + item = client.items.getName(item); + } + } + + if (!item) { + item = client.rustlabs.getClosestBuildingBlockNameByName(raidItemName); + } + + if (item === null) { + return null; + } + else { + itemId = item; + } + } + else if (raidItemId !== null) { + if (client.items.itemExist(raidItemId)) { + itemId = raidItemId; + } + else { + return null; + } + } + const itemName = client.items.getName(itemId); + + const raidDetails = client.rustlabs.getDurabilityDetailsByName(raidItemName); + if (raidDetails === null) { + return null; + } + + raidDetails[3] = this.groupDurabilityData(client, raidDetails[3]); + return raidDetails; + }, + + async execute(client, interaction) { + const guildId = interaction.guildId; + + const verifyId = Math.floor(100000 + Math.random() * 900000); + client.logInteraction(interaction, verifyId, 'slashCommand'); + + if (!await client.validatePermissions(interaction)) return; + await interaction.deferReply({ ephemeral: true }); + + const raidItemName = interaction.options.getString('name'); + const raidItemId = interaction.options.getString('id'); + if (raidItemName === null && raidItemId === null) { + const str = client.intlGet(guildId, 'noNameIdGiven'); + await client.interactionEditReply(interaction, DiscordEmbeds.getActionInfoEmbed(1, str)); + client.log(client.intlGet(guildId, 'warningCap'), str); + return null; + } + + client.log(client.intlGet(null, 'infoCap'), client.intlGet(null, 'slashCommandValueChange', { + id: `${verifyId}`, + value: `${raidItemName} ${raidItemId}` + })); + + const raidDetails = this.getDurabilityData(raidItemName, raidItemId, client, interaction); + + if (raidDetails === null) { + const str = client.intlGet(guildId, 'noItemWithNameFound', { + name: raidItemName + }); + await client.interactionEditReply(interaction, DiscordEmbeds.getActionInfoEmbed(1, str)); + client.log(client.intlGet(guildId, 'warningCap'), str); + return; + } + + await DiscordMessages.sendRaidMessage(interaction, raidDetails); + client.log(client.intlGet(null, 'infoCap'), client.intlGet(guildId, 'commandsRaidDesc')); + }, +}; diff --git a/src/discordTools/discordEmbeds.js b/src/discordTools/discordEmbeds.js index ece33295b..e93414d84 100644 --- a/src/discordTools/discordEmbeds.js +++ b/src/discordTools/discordEmbeds.js @@ -1126,6 +1126,36 @@ module.exports = { }); }, + getRaidEmbed: function (guildId, raidDetails) { + const raidCosts = { + name: '', + time: '', + sulfur: '' + }; + + const sortedItems = Object.values(raidDetails[3].explosive).sort((a, b) => { + const sulfurA = a[0].sulfur === null ? Infinity : Number(a[0].sulfur); + const sulfurB = b[0].sulfur === null ? Infinity : Number(b[0].sulfur); + return sulfurA - sulfurB; + }); + for (const item of sortedItems) { + raidCosts.name += `${item[0].itemName}\n`; + raidCosts.time += `${item[0].timeString} (${item[0].quantity})\n`; + raidCosts.sulfur += `${item[0].sulfur}\n`; + } + + return module.exports.getEmbed({ + title: `${raidDetails[1]}`, + color: Constants.COLOR_DEFAULT, + timestamp: true, + fields: [ + { name: Client.client.intlGet(guildId, 'name'), value: raidCosts.name.trim(), inline: true }, + { name: Client.client.intlGet(guildId, 'time'), value: raidCosts.time.trim(), inline: true }, + { name: Client.client.intlGet(guildId, 'sulfur'), value: raidCosts.sulfur.trim(), inline: true } + ] + }); + }, + getResearchEmbed: function (guildId, researchDetails) { let typeString = '', scrapString = ''; if (researchDetails[2].researchTable !== null) { diff --git a/src/discordTools/discordMessages.js b/src/discordTools/discordMessages.js index dcc2e0ee5..dbace5ab1 100644 --- a/src/discordTools/discordMessages.js +++ b/src/discordTools/discordMessages.js @@ -560,6 +560,15 @@ module.exports = { await Client.client.interactionEditReply(interaction, content); }, + sendRaidMessage: async function (interaction, raidDetails) { + const content = { + embeds: [DiscordEmbeds.getRaidEmbed(interaction.guildId, raidDetails)], + ephemeral: true + } + + await Client.client.interactionEditReply(interaction, content); + }, + sendResearchMessage: async function (interaction, researchDetails) { const content = { embeds: [DiscordEmbeds.getResearchEmbed(interaction.guildId, researchDetails)], diff --git a/src/external/process_rustlabs.js b/src/external/process_rustlabs.js index c12a68b40..e7b5f7a86 100644 --- a/src/external/process_rustlabs.js +++ b/src/external/process_rustlabs.js @@ -594,7 +594,10 @@ function processItemDurability(rustlabsName, shortname, name, data, type = 'item } if (toolShortname === null || toolName === null) exit(); toolId = Object.keys(ITEMS).find(e => ITEMS[e].shortname === toolShortname && ITEMS[e].name === toolName); - if (!toolId) exit(); + if (!toolId) { + console.error(`Tool ID not found for ${toolShortname} - ${toolName}`); + continue; // Skip this iteration and continue with the next match + } /* Caption in tool name */ let captionInTool = null; diff --git a/src/handlers/discordCommandHandler.js b/src/handlers/discordCommandHandler.js index baf9f6bc4..89f4053cb 100644 --- a/src/handlers/discordCommandHandler.js +++ b/src/handlers/discordCommandHandler.js @@ -204,6 +204,10 @@ module.exports = { commandLowerCase === `${prefix}${client.intlGet(guildId, 'commandSyntaxTravelingVendor')}`) { response = rustplus.getCommandTravelingVendor(); } + else if (commandLowerCase === `${prefix}${client.intlGet('en', 'commandSyntaxRaid')}` || + commandLowerCase === `${prefix}${client.intlGet(guildId, 'commandSyntaxRaid')}`) { + response = rustplus.getCommandRaidCost(command); + } else { /* Smart Switches/ Group Switches are not currently supported through discord. */ return false; diff --git a/src/handlers/inGameCommandHandler.js b/src/handlers/inGameCommandHandler.js index 8d606ef74..6ea2d1699 100644 --- a/src/handlers/inGameCommandHandler.js +++ b/src/handlers/inGameCommandHandler.js @@ -211,6 +211,10 @@ module.exports = { commandLowerCase === `${prefix}${client.intlGet(guildId, 'commandSyntaxTravelingVendor')}`) { rustplus.sendInGameMessage(rustplus.getCommandTravelingVendor()); } + else if (commandLowerCase.startsWith(`${prefix}${client.intlGet('en', 'commandSyntaxRaid')}`) || + commandLowerCase.startsWith(`${prefix}${client.intlGet(guildId, 'commandSyntaxRaid')}`)) { + rustplus.sendInGameMessage(rustplus.getCommandRaidCost(command)); + } else { /* Maybe a custom command? */ diff --git a/src/languages/en.json b/src/languages/en.json index bb5fb961d..bc51149bb 100644 --- a/src/languages/en.json +++ b/src/languages/en.json @@ -142,6 +142,7 @@ "commandSyntaxPlayers": "players", "commandSyntaxPop": "pop", "commandSyntaxProx": "prox", + "commandSyntaxRaid": "raid", "commandSyntaxRecycle": "recycle", "commandSyntaxRemove": "remove", "commandSyntaxResearch": "research", @@ -225,6 +226,7 @@ "commandsRecycleQuantityDesc": "The quantity of items to recycle.", "commandsRecycleRecyclerTypeDesc": "The recycler type (recycler, shredder, safe-zone-recycler).", "commandsResearchDesc": "Display the cost to research an item.", + "commandsRaidDesc": "Display the cost to destroy an item.", "commandsResetAlarmsDesc": "Reset alarms channel.", "commandsResetDesc": "Reset Discord channels.", "commandsResetInformationDesc": "Reset information channel.", @@ -297,6 +299,7 @@ "couldNotFindPlayerId": "Could not find player with id {id}.", "couldNotFindRecycleDetails": "Could not find recycle details for {name}.", "couldNotFindResearchDetails": "Could not find research details for {name}.", + "couldNotFindRaidDetails": "Could not find raid details for {name}.", "couldNotFindRole": "Could not find role: {roleId}", "couldNotFindStackDetails": "Could not find stack details for {name}.", "couldNotFindTeammate": "Could not find teammate: {name}.", @@ -682,6 +685,7 @@ "subscribeToChangesBattlemetrics": "Subscribe to different changes on Battlemetrics.", "subscriptionList": "Subscription list", "subscriptionListEmpty": "Item subscription list is empty.", + "sulfur": "Sulfur", "sulfurQuarry": "Sulfur Quarry", "switches": "Switches", "teamMember": "Team Member", diff --git a/src/structures/RustLabs.js b/src/structures/RustLabs.js index cf2a2bb6d..7db9ce46f 100644 --- a/src/structures/RustLabs.js +++ b/src/structures/RustLabs.js @@ -469,10 +469,10 @@ class RustLabs { } if (!foundName) { - foundName = this.getClosestBuildingBlockNameByName(name); + foundName = this.items.getClosestItemIdByName(name); if (foundName) { - if (this.durabilityData['buildingBlocks'].hasOwnProperty(foundName)) { - type = 'buildingBlocks'; + if (this.durabilityData['items'].hasOwnProperty(foundName)) { + return this.getDurabilityDetailsById(foundName, group, which, orderedBy); } else { foundName = null; @@ -481,10 +481,10 @@ class RustLabs { } if (!foundName) { - foundName = this.items.getClosestItemIdByName(name); + foundName = this.getClosestBuildingBlockNameByName(name); if (foundName) { - if (this.durabilityData['items'].hasOwnProperty(foundName)) { - return this.getDurabilityDetailsById(foundName, group, which, orderedBy); + if (this.durabilityData['buildingBlocks'].hasOwnProperty(foundName)) { + type = 'buildingBlocks'; } else { foundName = null; @@ -529,7 +529,7 @@ class RustLabs { content = this.getArrayOrderedByChoice(content, orderedBy); - return ['items', id, this.items.items[id], content]; + return ['items', this.items.items[id].name, this.items.items[id], content]; } diff --git a/src/structures/RustPlus.js b/src/structures/RustPlus.js index 39820fa6a..0461d9fe4 100644 --- a/src/structures/RustPlus.js +++ b/src/structures/RustPlus.js @@ -38,6 +38,7 @@ const Map = require('../util/map.js'); const RustPlusLite = require('../structures/RustPlusLite'); const TeamHandler = require('../handlers/teamHandler.js'); const Timer = require('../util/timer.js'); +const Durability = require('../commands/durability.js'); const TOKENS_LIMIT = 24; /* Per player */ const TOKENS_REPLENISH = 3; /* Per second */ @@ -2746,6 +2747,38 @@ class RustPlus extends RustPlusLib { return strings; } + + getCommandRaidCost(command) { + const prefix = this.generalSettings.prefix; + const commandRaid = `${prefix}${Client.client.intlGet(this.guildId, 'commandSyntaxRaid')}`; + const commandRaidEn = `${prefix}${Client.client.intlGet('en', 'commandSyntaxRaid')}`; + if (command.toLowerCase().startsWith(`${commandRaid} `)) { + command = command.slice(`${commandRaid} `.length).trim(); + } + else { + command = command.slice(`${commandRaidEn} `.length).trim(); + } + + const durability = Durability.getDurabilityData(command, null, Client.client, this.guildId); + let raidCosts = `${durability[1]}: `; + + if(!durability) { + return Client.client.intlGet(this.guildId, 'noItemWithNameFound', { + name: command + }); + } + + const sortedItems = Object.values(durability[3].explosive).sort((a, b) => { + const sulfurA = a[0].sulfur === null ? Infinity : Number(a[0].sulfur); + const sulfurB = b[0].sulfur === null ? Infinity : Number(b[0].sulfur); + return sulfurA - sulfurB; + }).slice(0,3); + for (const item of sortedItems) { + raidCosts += `${item[0].itemName} ${item[0].timeString} (${item[0].quantity}) ${item[0].sulfur}, `; + } + + return raidCosts.trim().trim(","); + } } module.exports = RustPlus;