index.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. const { Document } = require('camo')
  2. const battleAIs = require('../battle/ai')
  3. const Move = require('../battle/move')
  4. const gm = require('gm')
  5. const request = require('request')
  6. const tinygradient = require('tinygradient')
  7. const tinycolor = require('tinycolor2')
  8. const healthGradient = tinygradient(['#BA0020', '#ffbf00', '#77dd77'])
  9. const colorDead = '#36454f'
  10. const BACKUP_AVATAR_URL = 'https://discordapp.com/assets/6debd47ed13483642cf09e832ed0bc1b.png'
  11. // A Character refers to both a player and an enemy. The difference is in
  12. // their `ai` that controls their actions (for players, this is DiscordAI).
  13. class Character extends Document {
  14. constructor() {
  15. super()
  16. this.schema({
  17. discordID: {type: String, required: false},
  18. battleAI: {type: String, required: true, choices: Object.keys(battleAIs)},
  19. partyID: {type: String, required: false}, // Automatically generated
  20. health: {type: Number, default: 5, min: 0},
  21. maxHealth: {type: Number, default: 5, min: 1},
  22. moveIDs: {type: [String]},
  23. })
  24. }
  25. // Rather than a string (this.battleAI), returns the class itself.
  26. get BattleAI() {
  27. return battleAIs[this.battleAI]
  28. }
  29. // Returns a little icon emote based on the character's avatar.
  30. // TODO: implement this for characters that aren't discord-backed
  31. async getEmote(game) {
  32. if (!this.discordID) throw new Error() // FIXME
  33. try {
  34. const member = game.guild.members.get(this.discordID)
  35. const graphic = gm(request(member.user.avatarURL))
  36. .resize(48, 48)
  37. .stream()
  38. const emote = await game.emoteStore.get('char' + this._id, graphic)
  39. return emote
  40. } catch (err) {
  41. game.log.warning('Error while generating character emote:\n' + err.message)
  42. const graphic = gm(request(BACKUP_AVATAR_URL))
  43. .resize(48, 48)
  44. .stream()
  45. const emote = await game.emoteStore.get('char' + this._id, graphic)
  46. return emote
  47. //return _.sample(['❤️', '💛', '💙', '💜', '💚'])
  48. }
  49. }
  50. async modHealth(by, guild) {
  51. this.health += by
  52. if (this.health > this.maxHealth) this.health = this.maxHealth
  53. if (this.health < 0) this.health = 0
  54. if (guild) await this.healthUpdate(guild)
  55. return this.healthState
  56. }
  57. async healthUpdate(guild) {
  58. await this.save()
  59. if (!this.discordID) return
  60. const member = guild.members.get(this.discordID)
  61. // Remove existing health role, if any
  62. const roleExisting = member.roles.find(role => role.name.endsWith(' health'))
  63. if (roleExisting) await roleExisting.delete()
  64. // Reuse/create a health role and grant it
  65. const roleName = `${this.health}/${this.maxHealth} health`
  66. const role = member.roles.find(role => role.name === roleName)
  67. || await guild.createRole({
  68. name: roleName,
  69. color: this.health === 0
  70. ? colorDead
  71. : tinycolor(healthGradient.rgbAt(this.health / this.maxHealth)).toHexString()
  72. })
  73. await member.addRole(role)
  74. }
  75. get healthState() {
  76. if (this.health === this.maxHealth) return 'healthy'
  77. else if (this.health === 1) return 'peril'
  78. else if (this.health === 0) return 'dead'
  79. else return 'injured'
  80. }
  81. getName(guild) {
  82. // FIXME: names for characters without accociated discord users (eg. ai enemies)
  83. return guild.members.get(this.discordID).displayName
  84. }
  85. async getParty() {
  86. const Party = require('./party') // I don't like this any more than you do, but recursive requires DESTROY Node.js. Sorry.
  87. if (!this.partyID) {
  88. const party = Party.of([this])
  89. await party.save()
  90. this.partyID = party._id
  91. }
  92. return await Party.findOne({_id: this.partyID})
  93. }
  94. }
  95. module.exports = Character