game.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. #!/usr/bin/env node
  2. const camo = require('camo')
  3. const discord = require('discord.js')
  4. const _ = require('lodash')
  5. const config = require('./config')
  6. const Log = require('./util/log')
  7. const chalk = require('chalk')
  8. const Character = require('./character')
  9. const Battle = require('./battle/battle')
  10. const Move = require('./battle/move')
  11. const Party = require('./character/party')
  12. const { DiscordAI } = require('./battle/ai')
  13. class Game {
  14. // Starts the game. Should only run once per process!
  15. async boot() {
  16. // Create an instance of the logging tool
  17. this.log = new Log()
  18. // Load the config file
  19. await config.load()
  20. // Connect to the database (nedb or mongodb)
  21. await camo.connect(config.get('database_uri'))
  22. // Create the bot pool
  23. this.clientPool = await Promise.all(config.get('discord_bot_tokens').map(async token => {
  24. const client = new discord.Client()
  25. try {
  26. await client.login(token)
  27. } catch (err) {
  28. this.log.critical('Bot login failure (is the token correct?)')
  29. return null
  30. }
  31. if (client.guilds.find(g => g.id === config.get('discord_server_id'))) {
  32. this.log.ok(`Bot ${chalk.blue(client.user.tag)} logged in successfully`)
  33. return client
  34. } else {
  35. const url = `https://discordapp.com/oauth2/authorize?&client_id=${client.user.id}&scope=bot&permissions=${0x00000008}&response_type=code`
  36. this.log.warning('Bot not connected to configured Discord server - add it using the following URL:\n' + url)
  37. return null
  38. }
  39. })).then(pool => pool.filter(client => client !== null))
  40. if (this.clientPool.length === 0) {
  41. throw 'No bots connected, cannot start'
  42. }
  43. // Cleanup temporary channels (incase of crash last time)
  44. await Promise.all(this.guild.channels
  45. .filter(chnl => chnl.name === 'battle')
  46. .map(chnl => chnl.delete()))
  47. // TEMP: Create a couple moves to give to new characters.
  48. const pokeMove = await Move.upsert('👉', 'Poke', 'Deals a tiny amount of damage to a single enemy.', 1, {
  49. target: 'enemy',
  50. actions: [{type: 'damage', data: {amount: 2}, to: 'target'}],
  51. })
  52. const kissMove = await Move.upsert('💋', 'Kiss', 'Heals a party member by a tiny amount.', 5, {
  53. target: 'party',
  54. actions: [{type: 'heal', data: {amount: 3}, to: 'target'}],
  55. })
  56. const multipokeMove = await Move.upsert('👏', 'Multipoke', 'Deals a tiny amount of damage to up to three enemies at once.', 3, {
  57. target: Move.TargetDesc.of('enemy', 3),
  58. actions: [{type: 'damage', data: {amount: 2}, to: 'target'}],
  59. })
  60. // Add all players to the database (if they don't exist already)
  61. // This could take a while on large servers
  62. //
  63. // TODO: add new users (memberadd event) as characters when they join if the
  64. // server is already running
  65. await Promise.all(this.guild.members.filter(m => !m.user.bot).map(async member => {
  66. let char = await Character.findOne({discordID: member.id})
  67. if (!char) {
  68. char = await Character.create({
  69. discordID: member.id,
  70. battleAI: 'DiscordAI',
  71. moves: [pokeMove, multipokeMove, kissMove],
  72. }).save()
  73. this.log.info(`Created player data for new user ${chalk.blue(member.user.tag)}`)
  74. await char.healthUpdate(this.guild)
  75. }
  76. }))
  77. // TEMP
  78. this.clientPool[0].on('message', async msg => {
  79. if (msg.guild.id !== config.get('discord_server_id')) return
  80. if (msg.author.bot) return
  81. const self = await Character.findOne({discordID: msg.author.id})
  82. // Usage: .fight @opponent#0001 @opponent#0002 [...]
  83. if (msg.content.startsWith('.fight ')) {
  84. const battle = new Battle(this)
  85. // FIXME: this crashes if the battle starts off completed (eg. one team
  86. // is comprised of only dead players.
  87. await battle.addTeam(Party.of([self]))
  88. await battle.addTeam(Party.of(await Promise.all(
  89. msg.mentions.users.map(user =>
  90. Character.findOne({discordID: user.id})
  91. )
  92. )))
  93. while (await battle.tick()) {
  94. // Use this to mess with the battle as it runs.
  95. }
  96. }
  97. // Usage: .revive
  98. if (msg.content === '.revive') {
  99. self.health = self.maxHealth
  100. await self.healthUpdate(this.guild)
  101. }
  102. // Usage: .suicide
  103. if (msg.content === '.suicide') {
  104. self.health = 0
  105. await self.healthUpdate(this.guild)
  106. }
  107. })
  108. }
  109. get guild() {
  110. return _.sample(this.clientPool).guilds.get(config.get('discord_server_id'))
  111. }
  112. }
  113. // Let's go!!
  114. const game = new Game()
  115. game.boot()
  116. .then(() => game.log.ok('Game started'))
  117. .catch(err => {
  118. // :(
  119. game.log.critical('Unhandled error during boot:\n' + (err.message || err))
  120. process.exit(1)
  121. })