123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139 |
- const {Document, EmbeddedDocument} = require('camo')
- class Move extends Document {
- constructor() {
- super()
- this.schema({
- // For prompts
- name: {type: String, required: true},
- description: {type: String, default: ''},
- emote: {type: String, required: true},
- // For battle timers
- basePrepareTicks: {type: Number, min: 0, default: 0}, // After choice
- baseCooldownTicks: {type: Number, min: 0, default: 0}, // After action
- // See respective classes
- target: TargetDesc,
- actions: [ActionDesc],
- })
- }
- // Performs (read: acts out) this.actions on a single target.
- async performOn(target, user, battle) {
- for (const action of this.actions) {
- const actionTarget = action.to === 'target' ? target : user
- const targetLabel = await actionTarget.getLabel(battle.game)
- if (action.type === 'damage') {
- // TODO defense stat, etc
- const {amount} = action.data
- const healthState = await actionTarget.modHealth(
- -amount,
- battle.game.guild
- )
- if (healthState === 'dead') {
- await battle.sendMessageToAll(`${targetLabel} died.`)
- } else {
- await battle.sendMessageToAll(`${targetLabel} took ${amount} damage.`)
- }
- } else if (action.type === 'heal') {
- const {amount} = action.data
- if (actionTarget.healthState === 'dead') {
- await battle.sendMessageToAll(
- `${targetLabel} is dead and cannot be healed normally.`
- )
- } else if (actionTarget.healthState === 'healthy') {
- await battle.sendMessageToAll(
- `${targetLabel} is already at max health.`
- )
- } else {
- await actionTarget.modHealth(+amount, battle.game.guild)
- await battle.sendMessageToAll(
- `${targetLabel} recovered ${amount} health.`
- )
- }
- } else {
- throw 'Unknown action descriptor type: ' + action.type
- }
- }
- }
- // Creates a new move, or re-uses if one with the same name is already found.
- static async upsert(
- emote,
- name,
- description,
- {target, actions = [], basePrepareTicks, baseCooldownTicks} = {}
- ) {
- const existing = await Move.findOne({name})
- if (existing) {
- return existing
- } else {
- const move = Move.create({
- name,
- description,
- emote,
- basePrepareTicks,
- baseCooldownTicks,
- target: target instanceof TargetDesc ? target : TargetDesc.of(target),
- actions: actions.map(
- a => (a instanceof ActionDesc ? a : ActionDesc.create(a))
- ),
- })
- return move.save()
- }
- }
- }
- // Target descriptor - describes the number and types of character a move can
- // be used on (TODO: under certain conditions).
- class TargetDesc extends EmbeddedDocument {
- constructor() {
- super()
- this.schema({
- type: {type: String, choices: ['self', 'party', 'enemy', 'any']},
- numberMin: {type: Number, min: 1, default: 1},
- numberMax: {type: Number, min: 1, default: 1},
- //condition: ConditionDesc,
- })
- }
- static of(type, numberMin = 1, numberMax) {
- if (numberMax === undefined) {
- // 'Up to'
- numberMax = numberMin
- numberMin = 1
- }
- return TargetDesc.create({type, numberMin, numberMax})
- }
- }
- // Action descriptor - describes the effect a move has on the target or the
- // user. Includes status-effect inflictions and damage-dealing.
- class ActionDesc extends EmbeddedDocument {
- constructor() {
- super()
- this.schema({
- type: {type: String, required: true, choice: ['damage', 'heal']},
- data: Object, // Depending on type
- to: {type: String, required: 'target', choice: ['user', 'target']},
- //condition: ConditionDesc,
- })
- }
- }
- // TODO: Condition descriptor - eg. carrying Potato AND hp < 10
- // We should make a DSL that lets you easily define these using simple syntax.
- module.exports = Object.assign(Move, {TargetDesc, ActionDesc})
|