game.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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. // Clean dead roles
  48. await Party.cleanDeadRoles(this.guild, this.log)
  49. // TEMP: Create a couple moves to give to new characters.
  50. const pokeMove = await Move.upsert('👉', 'Poke', 'Deals a tiny amount of damage to a single enemy.', {
  51. target: 'enemy',
  52. actions: [{type: 'damage', data: {amount: 2}, to: 'target'}],
  53. })
  54. const kissMove = await Move.upsert('💋', 'Kiss', 'Heals a party member by a tiny amount.', {
  55. target: 'party',
  56. actions: [{type: 'heal', data: {amount: 3}, to: 'target'}],
  57. basePrepareTicks: 1,
  58. })
  59. const multipokeMove = await Move.upsert('👏', 'Multipoke', 'Deals a tiny amount of damage to up to three enemies at once.', {
  60. target: Move.TargetDesc.of('enemy', 3),
  61. actions: [{type: 'damage', data: {amount: 2}, to: 'target'}],
  62. baseCooldownTicks: 2,
  63. })
  64. // Add all players to the database (if they don't exist already)
  65. // This could take a while on large servers
  66. //
  67. // TODO: add new users (memberadd event) as characters when they join if the
  68. // server is already running
  69. await Promise.all(this.guild.members.filter(m => !m.user.bot).map(async member => {
  70. let char = await Character.findOne({discordID: member.id})
  71. if (!char) {
  72. char = await Character.create({
  73. discordID: member.id,
  74. battleAI: 'DiscordAI',
  75. moveIDs: [pokeMove._id, multipokeMove._id, kissMove._id],
  76. }).save()
  77. this.log.info(`Created player data for new user ${chalk.blue(member.user.tag)}`)
  78. await char.healthUpdate(this.guild)
  79. }
  80. }))
  81. // TEMP
  82. this.clientPool[0].on('message', async msg => {
  83. if (msg.guild.id !== config.get('discord_server_id')) return
  84. if (msg.author.bot) return
  85. const self = await Character.findOne({discordID: msg.author.id})
  86. // Usage: .fight @opponent#0001 @opponent#0002 [...]
  87. // Initiates a fight between your party and the parties of the specified
  88. // opponents.
  89. if (msg.content.startsWith('.fight ')) {
  90. const battle = new Battle(this)
  91. // FIXME: this crashes if the battle starts off completed (eg. one team
  92. // is comprised of only dead players).
  93. const characters = await Promise.all(msg.mentions.users.map(user => Character.findOne({discordID: user.id})))
  94. const parties = _.uniq([
  95. await self.getParty(), ...await Promise.all(characters.map(character => character && character.getParty()))
  96. ])
  97. for (const party of parties) {
  98. await battle.addTeam(party)
  99. }
  100. while (await battle.tick()) {
  101. // Use this to mess with the battle as it runs.
  102. }
  103. }
  104. // Usage: .revive
  105. if (msg.content === '.revive') {
  106. self.health = self.maxHealth
  107. await self.healthUpdate(this.guild)
  108. }
  109. // Usage: .suicide
  110. if (msg.content === '.suicide') {
  111. self.health = 0
  112. await self.healthUpdate(this.guild)
  113. }
  114. })
  115. }
  116. get guild() {
  117. return _.sample(this.clientPool).guilds.get(config.get('discord_server_id'))
  118. }
  119. }
  120. // Let's go!!
  121. const game = new Game()
  122. game.boot()
  123. .then(() => game.log.ok('Game started'))
  124. .catch(err => {
  125. // :(
  126. game.log.critical('Unhandled error during boot:\n' + (err.message || err))
  127. process.exit(1)
  128. })