move.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. const { Document, EmbeddedDocument } = require('camo')
  2. class Move extends Document {
  3. constructor() {
  4. super()
  5. this.schema({
  6. // For prompts
  7. name: {type: String, required: true},
  8. description: {type: String, default: ''},
  9. emote: {type: String, required: true},
  10. // For battle timers
  11. basePrepareTicks: {type: Number, min: 0, default: 0}, // After choice
  12. baseCooldownTicks: {type: Number, min: 0, default: 0}, // After action
  13. // See respective classes
  14. target: TargetDesc,
  15. actions: [ActionDesc],
  16. })
  17. }
  18. // Performs (read: acts out) this.actions on a single target.
  19. async performOn(target, user, battle) {
  20. for (const action of this.actions) {
  21. const actionTarget = action.to === 'target' ? target : user
  22. if (action.type === 'damage') {
  23. // TODO defense stat, etc
  24. const { amount } = action.data
  25. const healthState = await actionTarget.modHealth(-amount, battle.game.guild)
  26. if (healthState === 'dead') {
  27. await battle.sendMessageToAll(`**${actionTarget.getName(battle.game.guild)}** died.`)
  28. } else {
  29. await battle.sendMessageToAll(`**${actionTarget.getName(battle.game.guild)}** took ${amount} damage.`)
  30. }
  31. } else if (action.type === 'heal') {
  32. const { amount } = action.data
  33. if (actionTarget.healthState === 'dead') {
  34. await battle.sendMessageToAll(`**${actionTarget.getName(battle.game.guild)}** is dead and cannot be healed normally.`)
  35. } else if (actionTarget.healthState === 'healthy') {
  36. await battle.sendMessageToAll(`**${actionTarget.getName(battle.game.guild)}** is already at max health.`)
  37. } else {
  38. await actionTarget.modHealth(+amount, battle.game.guild)
  39. await battle.sendMessageToAll(`**${actionTarget.getName(battle.game.guild)}** recovered ${amount} health.`)
  40. }
  41. } else {
  42. throw 'Unknown action descriptor type: ' + action.type
  43. }
  44. }
  45. }
  46. // Creates a new move, or re-uses if one with the same name is already found.
  47. static async upsert(emote, name, description, {target, actions=[], basePrepareTicks, baseCooldownTicks}={}) {
  48. const existing = await Move.findOne({name})
  49. if (existing) {
  50. return existing
  51. } else {
  52. const move = Move.create({
  53. name, description, emote,
  54. basePrepareTicks, baseCooldownTicks,
  55. target: target instanceof TargetDesc ? target : TargetDesc.of(target),
  56. actions: actions.map(a => a instanceof ActionDesc ? a : ActionDesc.create(a)),
  57. })
  58. return move.save()
  59. }
  60. }
  61. }
  62. // Target descriptor - describes the number and types of character a move can
  63. // be used on (TODO: under certain conditions).
  64. class TargetDesc extends EmbeddedDocument {
  65. constructor() {
  66. super()
  67. this.schema({
  68. type: {type: String, choices: ['self', 'party', 'enemy', 'any']},
  69. numberMin: {type: Number, min: 1, default: 1},
  70. numberMax: {type: Number, min: 1, default: 1},
  71. //condition: ConditionDesc,
  72. })
  73. }
  74. static of(type, numberMin=1, numberMax) {
  75. if (numberMax === undefined) {
  76. // 'Up to'
  77. numberMax = numberMin
  78. numberMin = 1
  79. }
  80. return TargetDesc.create({type, numberMin, numberMax})
  81. }
  82. }
  83. // Action descriptor - describes the effect a move has on the target or the
  84. // user. Includes status-effect inflictions and damage-dealing.
  85. class ActionDesc extends EmbeddedDocument {
  86. constructor() {
  87. super()
  88. this.schema({
  89. type: {type: String, required: true, choice: ['damage', 'heal']},
  90. data: Object, // Depending on type
  91. to: {type: String, required: 'target', choice: ['user', 'target']}
  92. //condition: ConditionDesc,
  93. })
  94. }
  95. }
  96. // TODO: Condition descriptor - eg. carrying Potato AND hp < 10
  97. // We should make a DSL that lets you easily define these using simple syntax.
  98. module.exports = Object.assign(Move, {TargetDesc, ActionDesc})