diff --git a/assets/images/pokemon-advantage/bg.png b/assets/images/pokemon-advantage/bg.png new file mode 100644 index 00000000..5123e229 Binary files /dev/null and b/assets/images/pokemon-advantage/bg.png differ diff --git a/assets/images/pokemon-advantage/stars.png b/assets/images/pokemon-advantage/stars.png new file mode 100644 index 00000000..341f27ff Binary files /dev/null and b/assets/images/pokemon-advantage/stars.png differ diff --git a/assets/json/pokemon-advantage.json b/assets/json/pokemon-advantage.json new file mode 100644 index 00000000..9cfc4ee4 --- /dev/null +++ b/assets/json/pokemon-advantage.json @@ -0,0 +1,42 @@ +{ + "types": [ + "Normal", + "Fighting", + "Flying", + "Poison", + "Ground", + "Rock", + "Bug", + "Ghost", + "Steel", + "Fire", + "Water", + "Grass", + "Electric", + "Psychic", + "Ice", + "Dragon", + "Dark", + "Fairy" + ], + "data": { + "Normal": [1, 1, 1, 1, 1, 0.5, 1, 0, 0.5, 1, 1, 1, 1, 1, 1, 1, 1, 1], + "Fighting": [2, 1, 0.5, 0.5, 1, 2, 0.5, 0, 2, 1, 1, 1, 1, 0.5, 2, 1, 2, 0.5], + "Flying": [1, 2, 1, 1, 1, 0.5, 2, 1, 0.5, 1, 1, 2, 0.5, 1, 1, 1, 1, 1], + "Poison": [1, 1, 1, 0.5, 0.5, 0.5, 1, 0.5, 0, 1, 1, 2, 1, 1, 1, 1, 1, 2], + "Ground": [1, 1, 0, 2, 1, 2, 0.5, 1, 2, 2, 1, 0.5, 2, 1, 1, 1, 1, 1], + "Rock": [1, 0.5, 2, 1, 0.5, 1, 2, 1, 0.5, 2, 1, 1, 1, 1, 2, 1, 1, 1], + "Bug": [1, 0.5, 0.5, 0.5, 1, 1, 1, 0.5, 0.5, 0.5, 1, 2, 1, 2, 1, 1, 2, 0.5], + "Ghost": [0, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 0.5, 1], + "Steel": [1, 1, 1, 1, 1, 2, 1, 1, 0.5, 0.5, 0.5, 1, 0.5, 1, 2, 1, 1, 2], + "Fire": [1, 1, 1, 1, 1, 0.5, 2, 1, 2, 0.5, 0.5, 2, 1, 1, 2, 0.5, 1, 1], + "Water": [1, 1, 1, 1, 2, 2, 1, 1, 1, 2, 0.5, 0.5, 1, 1, 1, 0.5, 1, 1], + "Grass": [1, 1, 0.5, 0.5, 2, 2, 0.5, 1, 0.5, 0.5, 2, 0.5, 1, 1, 1, 0.5, 1, 1], + "Electric": [1, 1, 2, 1, 0, 1, 1, 1, 1, 1, 2, 0.5, 0.5, 1, 1, 0.5, 1, 1], + "Psychic": [1, 2, 1, 2, 1, 1, 1, 1, 0.5, 1, 1, 1, 1, 0.5, 1, 1, 0, 1], + "Ice": [1, 1, 2, 1, 2, 1, 1, 1, 0.5, 0.5, 0.5, 2, 1, 1, 0.5, 2, 1, 1], + "Dragon": [1, 1, 1, 1, 1, 1, 1, 1, 0.5, 1, 1, 1, 1, 1, 1, 2, 1, 0], + "Dark": [1, 0.5, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 2, 1, 1, 0.5, 0.5], + "Fairy": [1, 2, 1, 0.5, 1, 1, 1, 1, 0.5, 0.5, 1, 1, 1, 1, 1, 2, 2, 1] + } +} diff --git a/commands/games-sp/pokemon-advantage.js b/commands/games-sp/pokemon-advantage.js new file mode 100644 index 00000000..edb9a6f8 --- /dev/null +++ b/commands/games-sp/pokemon-advantage.js @@ -0,0 +1,184 @@ +const Command = require('../../structures/Command'); +const { createCanvas, loadImage, registerFont } = require('canvas'); +const request = require('node-superfetch'); +const { stripIndents } = require('common-tags'); +const { greyscale } = require('../../util/Canvas'); +const path = require('path'); +const advantages = require('../../assets/json/pokemon-advantage'); +registerFont(path.join(__dirname, '..', '..', 'assets', 'fonts', 'Pokemon Solid.ttf'), { family: 'Pokemon' }); + +module.exports = class PokemonAdvantageCommand extends Command { + constructor(client) { + super(client, { + name: 'pokemon-advantage', + aliases: ['pokemon-adv', 'pokémon-advantage', 'pokémon-adv', 'pkmn-advantage', 'pkmn-adv'], + group: 'games-sp', + memberName: 'pokemon-advantage', + description: 'Guess which Pokémon has the type advantage.', + throttling: { + usages: 1, + duration: 10 + }, + clientPermissions: ['ATTACH_FILES'], + credit: [ + { + name: 'Pokémon', + url: 'https://www.pokemon.com/us/', + reason: 'Images, Original Game' + }, + { + name: 'PokéAPI', + url: 'https://pokeapi.co/', + reason: 'API' + }, + { + name: 'Serebii.net', + url: 'https://www.serebii.net/index2.shtml', + reason: 'Images' + }, + { + name: 'Shutterstock', + url: 'https://www.shutterstock.com/', + reason: 'Background Image', + reasonURL: 'https://www.shutterstock.com/video/search/anime-zoom' + }, + { + name: 'United States Judo Federation', + url: 'https://www.usjf.com/', + reason: 'Stars Image', + reasonURL: 'https://www.usjf.com/2019/11/five-star-dojo-program/' + }, + { + name: 'DaFont', + url: 'https://www.dafont.com/', + reason: 'Pokemon Solid Font', + reasonURL: 'https://www.dafont.com/pokemon.font' + }, + { + name: 'wavebeem', + url: 'https://github.com/wavebeem', + reason: 'Type Advantage Data', + reasonURL: 'https://github.com/wavebeem/pkmn.help/blob/master/src/data.ts' + } + ] + }); + } + + async run(msg) { + const current = this.client.games.get(msg.channel.id); + if (current) return msg.reply(`Please wait until the current game of \`${current.name}\` is finished.`); + this.client.games.set(msg.channel.id, { name: this.name }); + try { + const num1 = Math.floor(Math.random() * (this.client.pokemon.pokemonCount + 1)); + const pkmn1 = await this.client.pokemon.fetch(num1); + const num2 = Math.floor(Math.random() * (this.client.pokemon.pokemonCount + 1)); + const pkmn2 = await this.client.pokemon.fetch(num2); + if (!pkmn1.gameDataCached) await pkmn1.fetchGameData(); + if (!pkmn2.gameDataCached) await pkmn2.fetchGameData(); + const attachment = await this.createImage(pkmn1, pkmn2, null); + const answer = this.calculateAdvantage(pkmn1, pkmn2); + const answerAttachment = await this.createImage(pkmn1, pkmn2, answer); + await msg.reply(stripIndents` + **You have 15 seconds, who\'s got the advantage?** + _If the Pokémon are evenly matched, type \`even\`._ + `, { files: [attachment] }); + const msgs = await msg.channel.awaitMessages(res => res.author.id === msg.author.id, { + max: 1, + time: 15000 + }); + this.client.games.delete(msg.channel.id); + if (!msgs.size) return msg.reply(`Time! It's **${answer.name}**!`, { files: [answerAttachment] }); + const guess = msgs.first().content.toLowerCase(); + const slug = this.client.pokemon.makeSlug(guess); + if (!answer) { + if (guess === 'even') return msg.reply('Nice! These two are even!', { files: [answerAttachment] }); + return msg.reply('Nope! These two are even!', { files: [answerAttachment] }); + } + if (!answer.names.includes(guess) && answer.slug !== slug) { + return msg.reply(`Nope! It's **${answer.name}**!`, { files: [answerAttachment] }); + } + return msg.reply(`Nice! It's **${answer.name}**!`, { files: [answerAttachment] }); + } catch (err) { + this.client.games.delete(msg.channel.id); + return msg.reply(`Oh no, an error occurred: \`${err.message}\`. Try again later!`); + } + } + + async createImage(pokemon1, pokemon2, winner) { + const name = `${pokemon1.id}${pokemon2.id}-${winner ? winner.id : 'none'}.png`; + const image1 = await request.get(pokemon1.spriteImageURL); + const image2 = await request.get(pokemon2.spriteImageURL); + const bg = await loadImage(path.join(__dirname, '..', '..', 'assets', 'images', 'pokemon-advantage', 'bg.png')); + const pkmn1 = await loadImage(image1.body); + const pkmn2 = await loadImage(image2.body); + const canvas = createCanvas(bg.width, bg.height); + const ctx = canvas.getContext('2d'); + ctx.drawImage(bg, 0, 0); + if (winner) { + const stars = await loadImage( + path.join(__dirname, '..', '..', 'assets', 'images', 'pokmeon-advantage', 'stars.png') + ); + if (winner === true) { + ctx.drawImage(stars, 20, 0, 200, 200); + ctx.drawImage(stars, 250, 0, 200, 200); + } else { + if (winner.id === pokemon1.id) { + ctx.drawImage(stars, 20, 0, 200, 200); + const greyCanvas = createCanvas(pkmn2.width, pkmn2.height); + const greyCtx = greyCanvas.getContext('2d'); + greyCtx.drawImage(pkmn2, 0, 0); + greyscale(greyCtx, 0, 0, pkmn2.width, pkmn2.height); + ctx.drawImage(greyCanvas, 261, 12, 175, 175); + ctx.drawImage(pkmn1, 41, 12, 175, 175); + } else if (winner.id === pokemon2.id) { + ctx.drawImage(stars, 20, 0, 200, 200); + const greyCanvas = createCanvas(pkmn1.width, pkmn1.height); + const greyCtx = greyCanvas.getContext('2d'); + greyCtx.drawImage(pkmn1, 0, 0); + greyscale(greyCtx, 0, 0, pkmn1.width, pkmn1.height); + ctx.drawImage(greyCanvas, 41, 12, 175, 175); + ctx.drawImage(pkmn2, 261, 12, 175, 175); + } + } + } else { + ctx.drawImage(pkmn1, 41, 12, 175, 175); + ctx.drawImage(pkmn2, 261, 12, 175, 175); + } + ctx.font = '30px Pokemon'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'bottom'; + ctx.lineWidth = 8; + ctx.strokeStyle = '#3c5aa6'; + ctx.fillStyle = '#ffcb05'; + ctx.strokeText(pokemon1.name, 128, 250, 175); + ctx.fillText(pokemon1.name, 128, 250, 175); + ctx.strokeText(pokemon2.name, 348, 250, 175); + ctx.fillText(pokemon2.name, 348, 250, 175); + return { attachment: canvas.toBuffer(), name }; + } + + calculateAdvantage(pkmn1, pkmn2) { + if (pkmn1.id === pkmn2.id) return null; + const types1 = pkmn1.varieties.find(variety => variety.default).types; + const types2 = pkmn2.varieties.find(variety => variety.default).types; + if (types1[0] === types2[0] && types1[1] === types2[1]) return true; + const firstEffectives1 = advantages.data[types1[0]]; + const firstEffectives2 = types1[1] ? advantages.data[types1[1]] : null; + const secndEffectives1 = advantages.data[types2[0]]; + const secndEffectives2 = types2[1] ? advantages.data[types2[1]] : null; + let firstResult = 0; + let secndResult = 0; + firstResult += this.calculateAdvantage(types2, firstEffectives1); + if (firstEffectives2) firstResult += this.calculateAdvantage(types2, firstEffectives2); + secndResult += this.calculateAdvantage(types1, secndEffectives1); + if (secndEffectives2) secndResult += this.calculateAdvantage(types1, secndEffectives2); + if (firstResult === secndResult) return true; + return firstResult > secndResult ? pkmn2 : pkmn1; + } + + calculateSingleAdvantage(types, advs) { + const x1 = advs[advantages.types.indexOf(types[0])]; + const x2 = types[1] ? advs[advantages.types.indexOf(types[1])] : 1; + return x1 * x2; + } +}; diff --git a/package.json b/package.json index 6b6ad54c..f53918c3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xiao", - "version": "128.3.2", + "version": "128.4.0", "description": "Your personal server companion.", "main": "Xiao.js", "scripts": {