From 56f70acb3e472af83583940ee2aa789462e52825 Mon Sep 17 00:00:00 2001 From: WubzyGD Date: Thu, 30 Dec 2021 03:03:45 -0700 Subject: [PATCH] n?stats image generation --- commands/leveling/stats.js | 88 +++++++++++++++++++++++++++++++++----- util/lxp/gainxp.js | 2 +- 2 files changed, 79 insertions(+), 11 deletions(-) diff --git a/commands/leveling/stats.js b/commands/leveling/stats.js index d7895c6..7255a88 100644 --- a/commands/leveling/stats.js +++ b/commands/leveling/stats.js @@ -1,4 +1,38 @@ const Discord = require('discord.js'); +const Canvas = require('canvas'); +Canvas.registerFont('./resources/fonts/Nunito-Regular.ttf', {family: "Nunito"}); + +const applyText = (base, canvas, text) => { + const ctx = canvas.getContext('2d'); + let fontSize = base; + + do {ctx.font = `${fontSize -= 2}px "Nunito"`;} + while (ctx.measureText(text).width > canvas.width - 460); + + return ctx.font; +}; + +function roundRect(ctx, x, y, width, height, radius=5, fill=false, stroke=true, clip=false) { + if (typeof radius === 'number') {radius = {tl: radius, tr: radius, br: radius, bl: radius}; + } else { + var defaultRadius = {tl: 0, tr: 0, br: 0, bl: 0}; + for (let side in defaultRadius) {radius[side] = radius[side] || defaultRadius[side];} + } + ctx.beginPath(); + ctx.moveTo(x + radius.tl, y); + ctx.lineTo(x + width - radius.tr, y); + ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr); + ctx.lineTo(x + width, y + height - radius.br); + ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height); + ctx.lineTo(x + radius.bl, y + height); + ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl); + ctx.lineTo(x, y + radius.tl); + ctx.quadraticCurveTo(x, y, x + radius.tl, y); + ctx.closePath(); + if (fill) {ctx.fill();} + if (stroke) {ctx.stroke();} + if (clip) {ctx.clip();} +} const LXP = require('../../models/localxp'); @@ -27,15 +61,49 @@ module.exports = { if (!txp.xp[u.id]) {return message.channel.send(`${u.id === message.author.id ? "You" : "That user"} doesn't have any leveling info available!`);} xp = {xp: txp.xp[u.id][0], level: txp.xp[u.id][1]}; } else {xp = client.misc.cache.lxp.xp[message.guild.id][u.id];} - return message.channel.send({embeds: [new Discord.MessageEmbed() - .setTitle(`${u.displayName}${u.displayName.toLowerCase().endsWith('s') ? "'" : "'s"} Stats`) - .setDescription("Local leveling stats") - .addField("Level", `${xp.level}`, true) - .addField("XP", `**${xp.xp}** of **${Math.ceil(100 + (((xp.level / 3) ** 2) * 2))}** needed to level up`, true) - .setThumbnail(client.users.cache.get(u.id).avatarURL({size: 2048})) - .setColor("c375f0") - .setFooter({text: "Natsuki"}) - .setTimestamp() - ]}) + if (!message.channel.permissionsFor(message.guild.me.id).has("ATTACH_FILES")) { + return message.channel.send({embeds: [new Discord.MessageEmbed() + .setTitle(`${u.displayName}${u.displayName.toLowerCase().endsWith('s') ? "'" : "'s"} Stats`) + .setDescription("Local leveling stats") + .addField("Level", `${xp.level}`, true) + .addField("XP", `**${xp.xp}** of **${Math.ceil(100 + (((xp.level / 3) ** 2) * 2))}** needed to level up`, true) + .setThumbnail(client.users.cache.get(u.id).avatarURL({size: 2048})) + .setColor("c375f0") + .setFooter({text: "Natsuki"}) + .setTimestamp() + ]}); + } else { + const canvas = Canvas.createCanvas(1193, 411); + const ctx = canvas.getContext('2d'); + + const background = await Canvas.loadImage('./resources/images/nat-lvl.jpg'); + ctx.drawImage(background, 0, 0, canvas.width, canvas.height); + + ctx.fillStyle = '#13131337'; //darken img + ctx.fillRect(0, 0, canvas.width, canvas.height); + + const avatar = await Canvas.loadImage(u.displayAvatarURL({format: 'jpg', size: 2048})); + ctx.drawImage(avatar, 40, 40, canvas.height - 80, canvas.height - 80); //draw avatar + + ctx.font = applyText(50, canvas, `${u.displayName}${u.displayName.toLowerCase().endsWith('s') ? "'" : "'s"} Stats`); //center text + ctx.fillStyle = '#ffffff'; + ctx.fillText(`${u.displayName}${u.displayName.toLowerCase().endsWith('s') ? "'" : "'s"} Stats`, canvas.width / 2.8, canvas.height / 2); + + ctx.font = applyText(120, canvas, `${xp.xp} / ${Math.ceil(100 + (((xp.level / 3) ** 2) * 2))} | Level ${xp.level}`); //top text + ctx.fillStyle = '#ffffff'; + ctx.fillText(`${xp.xp} / ${Math.ceil(100 + (((xp.level / 3) ** 2) * 2))} | Level ${xp.level}`, canvas.width / 2.8, canvas.height / 3.2); + + //draw the bar borders + ctx.strokeStyle = '#ffffff'; + ctx.strokeWidth = 6; + roundRect(ctx, canvas.width / 2.8, canvas.height / 1.53, canvas.width - (canvas.width / 2.8) - 80, 40, 10, false, true, false); + //set a clipping area to keep the bar filler inside the rounded borders + roundRect(ctx, canvas.width / 2.8, canvas.height / 1.53, canvas.width - (canvas.width / 2.8) - 80, 40, 10, false, false, true); + ctx.fillStyle = '#4aa4e0c8'; + //draw the bar filler + ctx.fillRect(canvas.width / 2.8, canvas.height / 1.53, (xp.xp / Math.ceil(100 + (((xp.level / 3) ** 2) * 2))) * (canvas.width - (canvas.width / 2.8) - 80), 40); + + message.channel.send({files: [new Discord.MessageAttachment(canvas.toBuffer(), 'xp-stats.png')]}); + } } }; \ No newline at end of file diff --git a/util/lxp/gainxp.js b/util/lxp/gainxp.js index 32ac6a4..30d5ca8 100644 --- a/util/lxp/gainxp.js +++ b/util/lxp/gainxp.js @@ -10,7 +10,7 @@ const applyText = (canvas, text) => { let fontSize = 120; do {context.font = `${fontSize -= 5}px "Nunito"`;} - while (context.measureText(text).width > canvas.width - 250); + while (context.measureText(text).width > canvas.width - 460); return context.font; };