ai.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. const Move = require('./move')
  2. const discord = require('discord.js')
  3. const _ = require('lodash')
  4. const prompt = require('../util/prompt')
  5. // The base class! Each method is called to request a decision from the AI.
  6. // Instanced on a battle-by-battle, per-character basis.
  7. class AI {
  8. constructor(char, battle, team) {
  9. this.char = char
  10. this.battle = battle
  11. this.team = team
  12. }
  13. // Returns { move: Move, targets: [Character] } to perform next.
  14. async moveChoice(self, battle) {}
  15. }
  16. // For player-controlled characters. Interfaces with Discord.
  17. class DiscordAI extends AI {
  18. async moveChoice() {
  19. // Move
  20. const moves = await Promise.all(this.char.moveIDs.map(id => Move.findOne({_id: id})))
  21. const { move } = await prompt({
  22. channel: this.team.channel,
  23. user: this.char,
  24. title: this.char.getName(this.battle.game.guild),
  25. description: 'Next move:',
  26. choices: moves.map(move => ({emote: move.emote, name: `**${move.name}** - ${move.description}`, move}))
  27. })
  28. // Target(s)
  29. let targetPromptDesc = `Use _${move.name}_ on:`
  30. if (move.target.numberMin === 1 && move.target.numberMax > 1) {
  31. targetPromptDesc += ` (pick up to ${move.target.numberMax})`
  32. } else if (move.target.numberMax > move.target.numberMin) {
  33. targetPromptDesc += ` (pick ${move.target.numberMin} - ${move.target.numberMax})`
  34. }
  35. let targetable
  36. switch (move.target.type) {
  37. // Only yourself.
  38. case 'self':
  39. targetable = [this.char]
  40. break
  41. // Everyone in your party but yourself.
  42. case 'party':
  43. targetable = this.team.party.members
  44. .filter(m => m.discordID !== this.char.discordID)
  45. break
  46. // Anyone in a different party (ie. hostile),
  47. case 'enemy':
  48. targetable = _.flatten(this.battle.teams
  49. .map(({ party }) => party)
  50. .filter(party => party._id !== this.team.party._id)
  51. .map(party => party.members))
  52. break
  53. // Anyone else in the battle.
  54. case 'any':
  55. targetable = this.battle.everyone
  56. .filter(m => m._id !== this.char._id)
  57. break
  58. }
  59. /*if (targetable.length === 1) {
  60. // Only one character can be targeted, so just choose them.
  61. return {move, targets: targetable}
  62. } else */if (targetable.length === 0) {
  63. // No-one can be targeted, so ask for a different move choice.
  64. // TODO: handle this case better?
  65. const msg = await this.team.channel.send('No targets for that move!')
  66. const ret = await this.moveChoice()
  67. await msg.delete()
  68. return ret
  69. }
  70. // TODO: add a back button
  71. const targets = await prompt.multi({
  72. channel: this.team.channel,
  73. user: this.char,
  74. title: this.char.getName(this.battle.game.guild),
  75. description: targetPromptDesc,
  76. choices: await Promise.all(targetable
  77. .map(async (char, i) => ({
  78. emote: await char.getEmote(this.battle.game),
  79. name: char.getName(this.battle.game.guild),
  80. char,
  81. }))),
  82. chooseMin: move.target.numberMin,
  83. chooseMax: move.target.numberMax,
  84. })
  85. return {move, targets: targets.map(choice => choice.char)}
  86. }
  87. }
  88. module.exports = {DiscordAI}