move.js 3.9 KB

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