diff --git a/README.md b/README.md index 3ffe1da7..925a0236 100644 --- a/README.md +++ b/README.md @@ -261,7 +261,7 @@ in the appropriate channel's topic to use it. ## Commands -Total: 583 +Total: 584 ### Utility: @@ -608,6 +608,7 @@ Total: 583 * **balloon-pop:** Don't let yourself be the last one to pump the balloon before it pops! * **battle:** Engage in a turn-based battle against another user or the AI. * **bingo:** Play bingo with up to 99 other users. +* **car-race:** Race a car against another user. * **chess-delete:** Deletes your saved Chess game. * **chess:** Play a game of Chess with another user or the AI. * **connect-four:** Play a game of Connect Four with another user or the AI. diff --git a/assets/images/car-race/bg.png b/assets/images/car-race/bg.png new file mode 100644 index 00000000..b7c1a2f7 Binary files /dev/null and b/assets/images/car-race/bg.png differ diff --git a/assets/images/car-race/cars/ae86.png b/assets/images/car-race/cars/ae86.png new file mode 100644 index 00000000..ac08c4ee Binary files /dev/null and b/assets/images/car-race/cars/ae86.png differ diff --git a/assets/images/car-race/cars/anakin.png b/assets/images/car-race/cars/anakin.png new file mode 100644 index 00000000..5991671a Binary files /dev/null and b/assets/images/car-race/cars/anakin.png differ diff --git a/assets/images/car-race/cars/delorean.png b/assets/images/car-race/cars/delorean.png new file mode 100644 index 00000000..91a23f86 Binary files /dev/null and b/assets/images/car-race/cars/delorean.png differ diff --git a/assets/images/car-race/cars/earnhardt.png b/assets/images/car-race/cars/earnhardt.png new file mode 100644 index 00000000..1e514851 Binary files /dev/null and b/assets/images/car-race/cars/earnhardt.png differ diff --git a/assets/images/car-race/cars/elise.png b/assets/images/car-race/cars/elise.png new file mode 100644 index 00000000..fc0d521e Binary files /dev/null and b/assets/images/car-race/cars/elise.png differ diff --git a/assets/images/car-race/cars/f1.png b/assets/images/car-race/cars/f1.png new file mode 100644 index 00000000..d9b20bc5 Binary files /dev/null and b/assets/images/car-race/cars/f1.png differ diff --git a/assets/images/car-race/cars/kitano.png b/assets/images/car-race/cars/kitano.png new file mode 100644 index 00000000..4ea0174b Binary files /dev/null and b/assets/images/car-race/cars/kitano.png differ diff --git a/assets/images/car-race/cars/mario.png b/assets/images/car-race/cars/mario.png new file mode 100644 index 00000000..c1b3b8b7 Binary files /dev/null and b/assets/images/car-race/cars/mario.png differ diff --git a/assets/images/car-race/cars/mcqueen.png b/assets/images/car-race/cars/mcqueen.png new file mode 100644 index 00000000..209a842b Binary files /dev/null and b/assets/images/car-race/cars/mcqueen.png differ diff --git a/assets/images/car-race/cars/reverb.png b/assets/images/car-race/cars/reverb.png new file mode 100644 index 00000000..3321c2de Binary files /dev/null and b/assets/images/car-race/cars/reverb.png differ diff --git a/assets/images/car-race/cars/sonic.png b/assets/images/car-race/cars/sonic.png new file mode 100644 index 00000000..e2cf0ec3 Binary files /dev/null and b/assets/images/car-race/cars/sonic.png differ diff --git a/commands/games-mp/car-race.js b/commands/games-mp/car-race.js new file mode 100644 index 00000000..767a7b1d --- /dev/null +++ b/commands/games-mp/car-race.js @@ -0,0 +1,223 @@ +const Command = require('../../structures/Command'); +const { createCanvas, loadImage } = require('canvas'); +const { stripIndents } = require('common-tags'); +const path = require('path'); +const { verify, list, delay } = require('../../util/Util'); +const fs = require('fs'); +const cars = fs.readdirSync(path.join(__dirname, '..', '..', 'assets', 'images', 'car-race', 'cars')) + .map(car => car.replace('.png', '')); +const words = ['go', 'zoom', 'drive', 'advance', 'pedal', 'vroom']; + +module.exports = class CarRaceCommand extends Command { + constructor(client) { + super(client, { + name: 'car-race', + aliases: ['cars'], + group: 'games-mp', + memberName: 'car-race', + description: 'Race a car against another user.', + credit: [ + { + name: 'iStock', + url: 'https://www.istockphoto.com/', + reason: 'Background Image', + // eslint-disable-next-line max-len + reasonURL: 'https://www.istockphoto.com/vector/side-view-of-a-road-with-a-crash-barrier-roadside-green-meadow-and-clear-blue-sky-gm1081596948-290039955' + }, + { + name: 'PNGkit', + url: 'https://www.pngkit.com/', + reason: 'Earnhardt Car Image', + reasonURL: 'https://www.pngkit.com/bigpic/u2r5r5o0a9y3w7q8/' + }, + { + name: 'Disneyclips.com', + url: 'https://www.disneyclips.com/main.html', + reason: 'McQueen Car Image', + reasonURL: 'https://www.disneyclips.com/images2/cars2.html' + }, + { + name: 'NicolasDavila', + url: 'https://www.deviantart.com/nicolasdavila', + reason: 'Reverb Car Image', + reasonURL: 'https://www.deviantart.com/nicolasdavila/art/Reverb-Wireframe-Blueprint-777342814' + }, + { + name: 'Sherif Saad', + url: 'https://www.behance.net/SherifSaad', + reason: 'AE86 Car Image', + reasonURL: 'https://www.behance.net/gallery/62672149/AE86-Tattoo' + }, + { + name: 'ClipArtBest', + url: 'http://www.clipartbest.com/', + reason: 'Kitano Car Image', + reasonURL: 'http://www.clipartbest.com/clipart-KinXey4XT' + }, + { + name: 'Marien Bierhuizen', + url: 'https://www.racedepartment.com/members/marien-bierhuizen.280739/', + reason: 'F1 Car Image', + reasonURL: 'https://www.racedepartment.com/downloads/f2018-car-sideviews.22450/updates' + }, + { + name: 'La Linea', + url: 'https://www.lalinea.de/', + reason: 'Elise Car Image', + reasonURL: 'https://www.lalinea.de/pkw/neuwagen/lotus/elise/roadster-2-tuerer/2011/' + }, + { + name: 'PNGkey.com', + url: 'https://www.pngkey.com/', + reason: 'Sonic Car Image', + reasonURL: 'https://www.pngkey.com/maxpic/u2e6y3t4a9o0a9a9/' + }, + { + name: 'MinionFan1024', + url: 'https://www.deviantart.com/minionfan1024', + reason: 'Anakin Car Image', + reasonURL: 'https://www.deviantart.com/minionfan1024/art/Anakin-s-podracer-829694073' + }, + { + name: 'theraymachine', + url: 'https://www.gran-turismo.com/ch/gtsport/user/profile/1679092/overview', + reason: 'DeLorean Car Image', + // eslint-disable-next-line max-len + reasonURL: 'https://www.gran-turismo.com/ch/gtsport/user/profile/1679092/gallery/all/decal/1679092/7359459034929333784' + }, + { + name: 'Kevin Zino', + url: 'https://codepen.io/natefr0st', + reason: 'Mario Car Image', + reasonURL: 'https://codepen.io/natefr0st/pen/GrMrZV' + } + ], + args: [ + { + key: 'opponent', + prompt: 'What user would you like to challenge?', + type: 'user' + }, + { + key: 'car', + prompt: `What car do you want to use? Either ${list(cars, 'or')}.`, + type: 'string', + oneOf: cars, + parse: car => car.toLowerCase() + } + ] + }); + } + + async run(msg, { opponent, car }) { + if (opponent.bot) return msg.reply('Bots may not be played against.'); + if (opponent.id === msg.author.id) return msg.reply('You may not play against yourself.'); + 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 }); + const bg = await loadImage(path.join(__dirname, '..', '..', 'assets', 'images', 'car-race', 'bg.png')); + const userCar = await loadImage( + path.join(__dirname, '..', '..', 'assets', 'images', 'car-race', 'cars', `${car}.png`) + ); + let oppoCar; + try { + const available = cars.filter(car2 => car !== car2); + await msg.say(`${opponent}, do you accept this challenge?`); + const verification = await verify(msg.channel, opponent); + if (!verification) { + this.client.games.delete(msg.channel.id); + return msg.say('Looks like they declined...'); + } + await msg.say(`${opponent}, what car do you want to be? Either ${list(available, 'or')}.`); + const filter = res => { + if (res.author.id !== opponent.id) return false; + return available.includes(res.content.toLowerCase()); + }; + const p2Car = await msg.channel.awaitMessages(filter, { + max: 1, + time: 30000 + }); + let car2; + if (p2Car.size) { + const choice = p2Color.first().content.toLowerCase(); + car2 = choice; + oppoCar = await loadImage( + path.join(__dirname, '..', '..', 'assets', 'images', 'car-race', 'cars', `${choice}.png`) + ); + } else { + const chosen = cars[Math.floor(Math.random() * cars.length)]; + car2 = chosen; + oppoCar = await loadImage( + path.join(__dirname, '..', '..', 'assets', 'images', 'car-race', 'cars', `${chosen}.png`) + ); + } + let userCarSpaces = 0; + let oppoCarSpaces = 0; + let lastRoundWinner; + while (userCarSpaces < 10 && oppoCarSpaces < 10) { + const board = this.generateBoard(bg, userCar, oppoCar, userCarSpaces, oppoCarSpaces); + let text; + if (lastRoundWinner) { + text = `${lastRoundWinner} pulls ahead!`; + } else { + text = stripIndents` + Welcome to \`car-race\`! Whenever a message pops up, type the word provided. + Whoever types the word first advances their car! + Either play can type \`end\` at any time to end the game. + `; + } + await msg.say(`${text}\n\nGet Ready...`, { files: [{ attachment: board, name: 'car-race.png' }] }); + await delay(randomRange(1000, 30000)); + const word = words[Math.floor(Math.random() * words.length)]; + await msg.say(`TYPE \`${word.toUpperCase()}\` NOW!`); + const filter = res => { + if (![opponent.id, msg.author.id].includes(res.author.id)) return false; + if (res.content.toLowerCase() === 'end') return true; + return res.content.toLowerCase() === word; + }; + const winner = await msg.channel.awaitMessages(filter, { + max: 1, + time: 30000 + }); + if (!winner.size) { + if (lastTurnTimeout) { + this.client.games.delete(msg.channel.id); + return msg.say('Game ended due to inactivity.'); + } else { + await msg.say('Come on, get your head in the game!'); + lastTurnTimeout = true; + continue; + } + } + const win = winner.first(); + if (win.content.toLowerCase() === 'end') { + if (win.author.id === msg.author.id) oppoCarSpaces = 10; + else if (win.author.id === opponent.id) userCarSpaces = 10; + break; + } + if (win.author.id === msg.author.id) userCarSpaces += 1; + else if (win.author.id === opponent.id) oppoCarSpaces += 1; + lastRoundWinner = win.author; + if (lastTurnTimeout) lastTurnTimeout = false; + } + this.client.games.delete(msg.channel.id); + const winner = userCarSpaces > oppoCarSpaces ? msg.author : opponent; + const car = winner.id === msg.author.id ? car : car2; + return msg.say(`Congrats, ${winner}!`, { files: [car2] }); + } catch (err) { + this.client.games.delete(msg.channel.id); + throw err; + } + } + + generateBoard(bg, userCar, oppoCar, userCarSpaces, oppoCarSpaces) { + const canvas = createCanvas(bg.width, bg.height); + const ctx = canvas.getContext('2d'); + ctx.drawImage(bg, 0, 0); + const userCarX = -155 + (92 * userCarSpaces); + ctx.drawImage(userCar, userCarX, 254); + const oppoCarX = -155 + (92 * oppoCarSpaces); + ctx.drawImage(oppoCar, oppoCarX, 208); + return canvas.toBuffer(); + } +}; diff --git a/package.json b/package.json index 0e045c0e..46260e7c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xiao", - "version": "127.0.1", + "version": "127.1.0", "description": "Your personal server companion.", "main": "Xiao.js", "scripts": {