123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153 |
- const { Document } = require('camo')
- const battleAIs = require('../battle/ai')
- const log = require('../util/log')
- const gm = require('gm')
- const request = require('request')
- const tinygradient = require('tinygradient')
- const tinycolor = require('tinycolor2')
- const healthGradient = tinygradient(['#BA0020', '#ffbf00', '#77dd77'])
- const colorDead = '#36454f'
- const BACKUP_AVATAR_URL = 'https://discordapp.com/assets/6debd47ed13483642cf09e832ed0bc1b.png'
- // A Character refers to both a player and an enemy. The difference is in
- // their `ai` that controls their actions (for players, this is DiscordAI).
- class Character extends Document {
- constructor() {
- super()
- this.schema({
- discordID: {type: String, required: false},
- battleAI: {type: String, required: true, choices: Object.keys(battleAIs)},
- partyID: {type: String, required: false}, // Automatically generated
- health: {type: Number, default: 5, min: 0},
- maxHealth: {type: Number, default: 5, min: 1},
- moveIDs: {type: [String]},
- })
- }
- // Rather than a string (this.battleAI), returns the class itself.
- get BattleAI() {
- return battleAIs[this.battleAI]
- }
- async modHealth(by, guild) {
- this.health += by
- if (this.health > this.maxHealth) this.health = this.maxHealth
- if (this.health < 0) this.health = 0
- if (guild) await this.healthUpdate(guild)
- return this.healthState
- }
- async healthUpdate(guild) {
- await this.save()
- if (!this.discordID) return
- const member = guild.members.get(this.discordID)
- // Remove existing health role, if any
- const roleExisting = member.roles.find(role => role.name.endsWith(' health'))
- if (roleExisting) await roleExisting.delete()
- // Reuse/create a health role and grant it
- const roleName = `${this.health}/${this.maxHealth} health`
- const role = member.roles.find(role => role.name === roleName)
- || await guild.createRole({
- name: roleName,
- color: this.health === 0
- ? colorDead
- : tinycolor(healthGradient.rgbAt(this.health / this.maxHealth))
- .toHexString(),
- })
- await member.addRole(role)
- }
- get healthState() {
- if (this.health === this.maxHealth) return 'healthy'
- else if (this.health === 1) return 'peril'
- else if (this.health === 0) return 'dead'
- else return 'injured'
- }
- getName(guild) {
- // FIXME: names for characters without accociated discord users
- return guild.members.get(this.discordID).displayName
- }
- // "Label" of a character, to be displayed in messages.
- async getLabel(game) {
- const emote = await this.getEmote(game)
- const role = await this.getRole(game)
- return `${emote} ${role}`
- }
- // Finds/creates a Discord role with the same name as the character and with
- // the same color as the character's party.
- async getRole(game, forceUpdate = false) {
- if (this._discordRole && !forceUpdate) {
- return this._discordRole
- }
- // Create the role
- const name = this.getName(game.guild)
- const party = await this.getParty()
- const role = await game.guild.createRole({name, color: party.colorHex})
- // Give this member the role
- const member = await game.guild.members.get(this.discordID)
- await member.addRole(role)
- return this._discordRole = role
- }
- // Returns a little icon emote based on the character's avatar.
- // TODO: implement this for characters that aren't discord-backed
- async getEmote(game) {
- if (!this.discordID) throw new Error() // FIXME
- try {
- const member = game.guild.members.get(this.discordID)
- const graphic = gm(request(member.user.avatarURL))
- .resize(48, 48)
- .stream('PNG')
- const emote = await game.emoteStore.get(`char${this._id}`, graphic)
- return emote
- } catch (err) {
- log.warning(`Error while generating character emote:\n${err.message}`)
- const graphic = gm(request(BACKUP_AVATAR_URL))
- .resize(48, 48)
- .stream('PNG')
- const emote = await game.emoteStore.get(`char${this._id}`, graphic)
- return emote
- //return _.sample(['❤️', '💛', '💙', '💜', '💚'])
- }
- }
- async getParty() {
- const Party = require('./party') // I don't like this any more than you do, but recursive requires DESTROY Node.js. Sorry.
- let party
- if (!this.partyID) {
- // Create a one-man party
- party = await Party.of([this])
- }
- return party || Party.findOne({_id: this.partyID})
- }
- }
- module.exports = Character
|