diff --git a/resources/i18n/en/companion.json b/resources/i18n/en/companion.json index 7cf5438..959d24a 100644 --- a/resources/i18n/en/companion.json +++ b/resources/i18n/en/companion.json @@ -9,6 +9,13 @@ "Are you certain you'd like to purchase it for {{amount}} credits?\n", "To confirm, please write {{code}}, or anything else to exit." ], + "food": [ + "{{author}}, are you sure you'd like to feed your {{animal}} {{amount}} food?\n", + "To confirm, please write {{code}}, or anything else to exit." + ], + "petFed": "{{author}}, you have just fed your {{animal}}, restoring their **hunger** and **mood** by {{amount}}", + "tooHungry": "Your companion is too full to feed it {{amount}} food!", + "notEnoughFood": "You can't feed your {{animal}} {{amount}} food as you only have {{inv}} in your inventory!", "invalidCode": "you have entered an invalid code! The transaction was cancelled.", "error": "an error has occurred! The transaction was cancelled. Try again later.", "result": [ @@ -21,7 +28,9 @@ "wins": "Wins", "losses": "Losses", "info": "Companion Info", - "info2": "{{author}}'s Companion" + "info2": "{{author}}'s Companion", + "mood": "Mood", + "hunger": "Food" }, "errors": { "self": "you can't battle yourself!", @@ -42,7 +51,11 @@ "plsBetProperly": "you can't bet less than one credit!", "notBetting": "the battle has not entered the betting phase!", "afterBetting": "the betting phase has already concluded for this battle!", - "notOnline": "that user is not online yet!" + "notOnline": "that user is not online yet!", + "hungry": "your companion is too hungry to battle", + "moody": "your companion's mood is too low to battle", + "hungryOpponent": "your opponent's companion is too hungry to battle", + "moodyOpponent": "your opponent's companion's mood is too low to battle" }, "challenge": [ "you have challenged {{mention}} to a pet battle!\n", diff --git a/resources/i18n/en/shop.json b/resources/i18n/en/shop.json new file mode 100644 index 0000000..3bf586b --- /dev/null +++ b/resources/i18n/en/shop.json @@ -0,0 +1,24 @@ +{ + "buyDialog": "Items for purchase", + "sellDialog": "Items for sale", + "menu": { + "food": "Bag of generic pet food. Raises food by 1", + "checkInv": "Check your current inventory" + }, + "howMuch": "{{author}}, how many {{selection}} would you like to buy?", + "purchase": [ + "{{author}}, are you sure you'd like to purchase {{amount}} {{selection}}?\n", + "To confirm, please write {{code}}, or anything else to exit." + ], + "result": [ + "**Food Purchased**\n", + "{{author}}, you have successfully purchased {{amount}} {{selection}}.", + "__Current Balance__: {{balance}} credits\n" + ], + "error": "an error has occurred! The transaction was cancelled. Try again later.", + "cannotAfford": "I'm sorry, you need {{ammount}} credits to buy that but you only have {{balance}}", + "info": "Current Inventory", + "amounts": { + "petFood": "Pet food" + } +} diff --git a/src/commands/games/battle.js b/src/commands/games/battle.js index 28d1861..7a66174 100644 --- a/src/commands/games/battle.js +++ b/src/commands/games/battle.js @@ -47,9 +47,24 @@ class Battle extends Command { balance: `**${userProfile.credits}**` }) } + + if (userProfile.companion.hunger <= 1) { + return responder.error('{{errors.hungry}}') + } + + if (userProfile.companion.mood <= 1) { + return responder.error('{{errors.moody}}') + } + const oppProfile = await data.User.fetch(opp.id) if (!oppProfile.companion) return responder.error('{{errors.opponentNoCompanion}}') if (oppProfile.credits < this.entryFee) return responder.error('{{errors.cantChallenge}}') + if (oppProfile.companion.hunger === 1) { + return responder.error('{{errors.hungryOpponent}}') + } + if (oppProfile.companion.mood === 1) { + return responder.error('{{errors.moodyOpponent}}') + } try { await companions.initBattle(msg.author, opp, msg.channel, settings, responder, this.respondTime, this.entryFee) diff --git a/src/commands/games/companions.js b/src/commands/games/companions.js index 8a8dbc5..97b4c83 100644 --- a/src/commands/games/companions.js +++ b/src/commands/games/companions.js @@ -6,7 +6,7 @@ class Companions extends Command { super(...args, { name: 'companion', description: 'Animal companion system', - usage: [{ name: 'action', displayName: 'buy | rename | peek', type: 'string', optional: true }], + usage: [{ name: 'action', displayName: 'buy | rename | peek | feed', type: 'string', optional: true }], aliases: ['pet'], cooldown: 5, subcommands: { @@ -15,7 +15,10 @@ class Companions extends Command { usage: [{ name: 'user', type: 'member', optional: false }], options: { guildOnly: true } }, - rename: 'rename' + rename: 'rename', + feed: { + usage: [{ name: 'amount', type: 'int', optional: true }] + } }, options: { botPerms: ['embedLinks'] } }) @@ -34,11 +37,66 @@ class Companions extends Command { description: `**\`LVL ${Math.floor(Math.cbrt(companion.xp)) || 0}\`** :${companion.type}: ${companion.name}`, fields: [ { name: responder.t('{{definitions.wins}}'), value: stats.wins || 0, inline: true }, - { name: responder.t('{{definitions.losses}}'), value: stats.losses || 0, inline: true } + { name: responder.t('{{definitions.losses}}'), value: stats.losses || 0, inline: true }, + { name: responder.t('{{definitions.mood}}'), value: companion.mood || 10, inline: true}, + { name: responder.t('{{definitions.hunger}}'), value: companion.hunger || 10, inline: true} ] }).send() } + async feed ({ msg, args, data }, responder) { + const user = await data.User.fetch(msg.author.id) + const companion = (await data.User.fetchJoin(msg.author.id, { companion: true })).companion + if (!companion) { + responder.error('{{noPet}}', { command: `**\`${settings.prefix}${trigger} buy\`**` }) + return + } + const amount = args.amount || 1 + if ((companion.hunger + amount) > 10) { + responder.error('{{tooHungry}}', {amount: `**${amount}**`}) + return + } + if (user.petfood < amount) { + responder.error('{{notEnoughFood}}', { + amount: `**${amount}**`, + inv: `**${user.petfood}**`, + animal: `:${companion.type}:` + }) + return + } + const code = ~~(Math.random() * 8999) + 1000 + const arg = await responder.format('emoji:info').dialog([{ + prompt: '{{food}}', + input: { type: 'string', name: 'code' } + }], { + author: `**${msg.author.username}**`, + animal: `:${companion.type}:`, + amount: `**${amount}**`, + code: `**\`${code}\`**` + }) + if (parseInt(arg.code, 10) !== code) { + return responder.error('{{invalidCode}}') + } + if ((companion.mood + amount) > 10) { + companion.mood = 10 + } else { + companion.mood += amount + } + companion.hunger += amount + try { + await user.saveAll() + await data.User.update(user.id, user) + } catch (err) { + logger.error(`Could not save after companion feeding: ${err}`) + return responder.error('{{error}}') + } + responder.format('emoji:success').send('{{petFed}}', { + author: `**${msg.author.username}**`, + animal: `:${companion.type}:`, + amount: `**${amount}**` + }) + } + async peek ({ args, data }, responder) { const [member] = await responder.selection(args.user, { mapFunc: m => `${m.user.username}#${m.user.discriminator}` }) if (!member) return @@ -55,7 +113,9 @@ class Companions extends Command { description: `**\`LVL ${Math.floor(Math.cbrt(companion.xp)) || 0}\`** :${companion.type}: ${companion.name}`, fields: [ { name: responder.t('{{definitions.wins}}'), value: stats.wins || 0, inline: true }, - { name: responder.t('{{definitions.losses}}'), value: stats.losses || 0, inline: true } + { name: responder.t('{{definitions.losses}}'), value: stats.losses || 0, inline: true }, + { name: responder.t('{{definitions.mood}}'), value: companion.mood || 10, inline: true}, + { name: responder.t('{{definitions.hunger}}'), value: companion.hunger || 10, inline: true} ] }).send() } @@ -220,4 +280,17 @@ class PetBuy extends Companions { } } -module.exports = [ Companions, PetBuy ] +class PetFeed extends Companions { + constructor (...args) { + super(...args, { + name: 'feedpet', + description: 'Feeds your personal companion', + options: { localeKey: 'companion' }, + aliases: [], + usage: [{ name: 'amount', type: 'int', optional: true }], + subcommand: 'feed' + }) + } +} + +module.exports = [ Companions, PetBuy, PetFeed ] diff --git a/src/commands/games/shop.js b/src/commands/games/shop.js new file mode 100644 index 0000000..8875a99 --- /dev/null +++ b/src/commands/games/shop.js @@ -0,0 +1,82 @@ +const logger = require('winston') +const { Command } = require('../../core') + +class Shop extends Command { + constructor (...args) { + super(...args, { + name: 'shop', + description: 'A small shop to buy things with credits', + options: { localeKey: 'shop' } + }) + } + + handle (container, responder) { + const { msg, data, settings } = container + return responder.selection(['food', 'checkInv'], { + title: '{{buyDialog}}', + mapFunc: ch => responder.t(`{{menu.${ch}}}`) + }).then(arg => arg.length ? this[arg[0]](container, responder) : false) + } + + async food ({ msg, data, settings }, responder) { + const user = await data.User.fetch(msg.author.id) + const arg = await responder.format('emoji:info').dialog([{ + prompt: '{{howMuch}}', + input: { type: 'int', name: 'howMuch' } + }], { + author: `**${msg.author.username}**`, + selection: `food` + }) + const amount = arg.howMuch + const price = (100 * amount) + if (user.credits < price) { + responder.error('{{cannotAfford}}', { + amount: `**${price}**`, + balance: `**${user.credits}**` + }) + return + } + const code = ~~(Math.random() * 8999) + 1000 + const argCode = await responder.format('emoji:info').dialog([{ + prompt: '{{purchase}}', + input: { type: 'string', name: 'code' } + }], { + author: `**${msg.author.username}**`, + selection: `food`, + amount: `**${amount}**`, + code: `**\`${code}\`**` + }) + if (parseInt(argCode.code, 10) !== code) { + return responder.error('{{invalidCode}}') + } + user.petfood += amount + user.credits -= price + try { + await user.saveAll() + await data.User.update(user.id, user) + } catch (err) { + logger.error(`Could not save after food purchase: ${err}`) + return responder.error('{{error}}') + } + responder.format('emoji:success').send('{{result}}', { + author: `**${msg.author.username}**`, + amount: `**${amount}**`, + selection: `food`, + balance: `:credits: **${user.credits}**` + }) + } + + async checkInv ({ msg, data, settings }, responder){ + const user = await data.User.fetch(msg.author.id) + responder.embed({ + color: this.colours.blue, + author: { name: responder.t('{{info}}'), icon_url: msg.author.avatarURL }, + description: `:credit_card: ${user.credits}`, + fields: [ + { name: responder.t('{{amounts.petFood}}'), value: user.petfood || 0, inline: true } + ] + }).send() + } + +} +module.exports = [Shop] diff --git a/src/commands/moderation/autorole.js b/src/commands/moderation/autorole.js index 6498c5c..3fbd519 100644 --- a/src/commands/moderation/autorole.js +++ b/src/commands/moderation/autorole.js @@ -7,7 +7,7 @@ class Autorole extends Command { name: 'autorole', description: 'Add a role to a user on join', usage: [ - { name: 'action', displayName: ' add | remove', type: 'string', optional: true }], + { name: 'action', displayName: ' add <@role> | remove', type: 'string', optional: true }], subcommands: { add: { usage: [ diff --git a/src/commands/moderation/ban.js b/src/commands/moderation/ban.js index 89f9da9..8b93eea 100644 --- a/src/commands/moderation/ban.js +++ b/src/commands/moderation/ban.js @@ -42,13 +42,6 @@ class Ban extends Command { return responder.error('{{ban.exit}}') } try { - const channel = await this.bot.getDMChannel(member.id) - await this.send(channel, [ - `🔨 | You have been banned from **\`${msg.channel.guild.name}\`**\n`, - `**Reason**: ${args.reason}` - ].join('\n')) - await msg.channel.guild.banMember(member.id, 0, args.reason) - client.emit('haruMemberBanned', msg.channel.guild, member.user, args.reason) return responder.format('emoji:hammer').reply('{{ban.msg}}', { member: `**${member.user.username}#${member.user.discriminator}**`, deleteDelay: 5000 diff --git a/src/commands/moderation/kick.js b/src/commands/moderation/kick.js index 2418a76..b95fe43 100644 --- a/src/commands/moderation/kick.js +++ b/src/commands/moderation/kick.js @@ -42,12 +42,6 @@ class Kick extends Command { return responder.error('{{kick.exit}}') } try { - const channel = await this.bot.getDMChannel(member.id) - await this.send(channel, [ - `👢 | You have been kicked from **\`${msg.channel.guild.name}\`**\n`, - `**Reason**: ${args.reason}` - ].join('\n')) - await msg.channel.guild.kickMember(member.id, args.reason) client.emit('haruMemberKicked', msg.channel.guild, member.user, args.reason) return responder.format('emoji:boot').reply('{{kick.msg}}', { member: `**${member.user.username}#${member.user.discriminator}**`, diff --git a/src/models/Companion.js b/src/models/Companion.js index a6007b3..8ec5308 100644 --- a/src/models/Companion.js +++ b/src/models/Companion.js @@ -19,6 +19,9 @@ module.exports = function () { hp: number().default(10), crit: number().default(1), atk: number().default(1), + heal: number().default(1), + mood: number().default(10), + hunger: number().default(10), inventory: array().default([]) }, relations: { diff --git a/src/models/User.js b/src/models/User.js index f5557b9..9e6ebb2 100644 --- a/src/models/User.js +++ b/src/models/User.js @@ -11,6 +11,7 @@ module.exports = function () { id: string(), credits: number().default(0), exp: number().default(0), + petfood: number().default(0), deleted: bool().default(false), title: string().default('Commoner'), description: string().default('A simple wandering soul'), diff --git a/src/modules/games/Companions.js b/src/modules/games/Companions.js index fe50d37..ab57953 100644 --- a/src/modules/games/Companions.js +++ b/src/modules/games/Companions.js @@ -151,6 +151,7 @@ class Companions extends Module { hp: p1.hp || 10, crit: p1.crit || 1, atk: p1.atk || 1, + heal: p1.heal || 1, type: p1.type }, p2: { @@ -160,6 +161,7 @@ class Companions extends Module { hp: p2.hp || 10, crit: p2.crit || 1, atk: p2.atk || 1, + heal: p2.heal || 1, type: p2.type } } @@ -184,11 +186,13 @@ class Companions extends Module { const res = this.outcomes[~~(Math.random() * 100)] if (!stats[attacker] || !stats[receiver]) return - if (battle._turn < 0) battle._actions.push(':info: **Match begins!**') + if (battle._turn < 0) battle._actions.push(':information_source: **Match begins!**') battle._turn = turn const crit = stats[attacker].crit - const multiplier = Array(100).fill(2, 0, crit).fill(1, crit)[~~(Math.random() * 100)] + const critmultiplier = Array(100).fill(2, 0, crit).fill(1, crit)[~~(Math.random() * 100)] + const heal = stats[attacker].heal + const healmultiplier = Array(100).fill(2, 0, heal).fill(1, heal)[~~(Math.random() * 100)] const damagenum = Math.floor(Math.random() * (6 - 1 + 1)) + 1; const actionnum = Math.floor(Math.random() * (4 - 1 + 1)) + 1; switch (res) { @@ -197,13 +201,13 @@ class Companions extends Module { break } case 1: { - const dmg = (stats[attacker].atk * multiplier) - battle._actions.push(`${multiplier > 1 ? ':anger:' : ':punch:'} **${responder.t(`{{script.HIT_${damagenum}}}`, {p1: stats[attacker].name, p2: stats[receiver].name, dmg: dmg}) }**`) + const dmg = (stats[attacker].atk * critmultiplier) + battle._actions.push(`${critmultiplier > 1 ? ':anger:' : ':punch:'} **${responder.t(`{{script.HIT_${damagenum}}}`, {p1: stats[attacker].name, p2: stats[receiver].name, dmg: dmg}) }**`) battle._stats[receiver].hp -= dmg break } case 2: { - const heal = (1 * multiplier) + const heal = (1 * healmultiplier) battle._actions.push(`:sparkles: **${responder.t(`{{script.HEAL_${actionnum}}}`, {p1: stats[attacker].name, heal: heal}) }**`) battle._stats[attacker].hp += heal break @@ -253,10 +257,16 @@ class Companions extends Module { winUser.credits += battle.fee * 2 winUser.companion.xp = (winUser.companion.xp || 0) + ~~(Math.random() * 5) + 2 winUser.companion.stats.wins += 1 + winUser.companion.hunger -= 1 + if (winUser.companion.mood < 10) { + winUser.companion.mood += 1 + } const loseUser = await this.db.User.fetch(battle[loser], { companion: true }) loseUser.companion.xp = (loseUser.companion.xp || 0) + ~~(Math.random() * 3) + 1 loseUser.companion.stats.losses += 1 + loseUser.companion.hunger -= 1 + loseUser.companion.mood -= 1 await loseUser.saveAll({ companion: true }) await this.send(battle.channel, [ diff --git a/src/modules/stats/Auditor.js b/src/modules/stats/Auditor.js index de93fdd..83ac6ca 100644 --- a/src/modules/stats/Auditor.js +++ b/src/modules/stats/Auditor.js @@ -6,7 +6,7 @@ class Auditor extends Module { super(...args, { name: 'guilds:settings', events: { - haruMemberBanned: 'onBan', + guildBanAdd: 'onBan', haruMemberKicked: 'onKick', guildMemberUpdate: 'memberUpdate', guildMemberAdd: 'onJoin', @@ -47,15 +47,14 @@ class Auditor extends Module { }) } - onBan (guild, user, reason) { + onBan (guild, user) { this.data.Guild.fetch(guild.id).then(settings => { if (typeof settings.events !== 'object') return if (!settings.events.hasOwnProperty('ban')) return for (const id of settings.events['ban']) { this.send(id, '', { embed: { color: this.colours.red, - description: `🔨 **Member Banned**: ${user.username}#${user.discriminator} (ID: ${user.id})` + - (reason ? `\n\n**Reason**: ${reason}` : ''), + description: `🔨 **Member Banned**: ${user.username}#${user.discriminator} (ID: ${user.id})`, footer: { text: moment().locale(settings.lang).tz(settings.tz).format('ddd Do MMM, YYYY [at] hh:mm:ss a') } }}) }