move.js 3.8 KB

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