// Copyright 2019-2020 Campbell Crowley. All rights reserved. // Author: Campbell Crowley (dev@campbellcrowley.com) const crypto = require('crypto'); /** * @description Handler function for a generic action. * @typedef HungryGames~Action~ActionHandler * @type {Function} * * @param {HungryGames} hg HG context. * @param {HungryGames~GuildGame} game Game context. */ /** * @description Base for actions to perform in response to certain things that * happen during a hunger games. * * @memberof HungryGames * @inner * @interface */ class Action { /** * @description Create action. * @param {HungryGames~Action~ActionHandler} handler Action handler override. * @param {number} [delay=0] Delay calling the handler by this many * milliseconds after triggered. */ constructor(handler, delay = 0) { if (typeof handler !== 'function') { throw new TypeError('Handler is not a function.'); } /** * @description The unique ID for this action. Probably globally unique, * definitely unique per-trigger in a guild. * @public * @type {string} */ this.id = Action.createID(); /** * @description Passed handler to fire once triggered. * @private * @type {HungryGames~Action~ActionHandler} * @constant */ this._handler = handler; /** * @description Delay handler call by this many milliseconds after * triggered. * @public * @default 0 * @type {number} */ this.delay = delay || 0; /** * @description Data injected into save file that the `create` function uses * to restore data. Must be serializable. * @type {object} * @private * @default */ this._saveData = {}; } /** * @description Convert this object to serializable format for saving to file. * Injects data from `this._saveData`. * @public * @returns {{className: string, data: ?object}} Serializable object that can * be saved to file. */ get serializable() { return { id: this.id, className: this.constructor.name, delay: this.delay, data: this._saveData, }; } /** * @description Trigger the action to be performed. * * @type {HungryGames~Action~ActionHandler} * @public * @param {HungryGames} hg HG context. * @param {HungryGames~GuildGame} game Game context. * @param {...*} [args] Additional arguments to pass. */ trigger(hg, game, ...args) { if (this.delay && !game.options.disableOutput) { setTimeout(() => this._handler(hg, game, ...args), this.delay); } else { this._handler(hg, game, ...args); } } /** * @description Create action from save data. * @public * @static * @abstract * @param {Discord~Client} client Bot client context to get object * references. * @param {string} id Guild ID this action is for. * @param {object} obj The parsed data from file. * @returns {HungryGames~Action} The created action. */ static create(client, id, obj) { id; obj; client; throw new Error('Create function not overridden.'); } /** * @description Generate an ID for an Action. Does not check for collisions. * @public * @static * @returns {string} Generated ID. */ static createID() { return crypto.randomBytes(8).toString('hex').toUpperCase(); } /** * @description Create an Action object from save data. Looks up action from * {@link HungryGames~Action~actionList}. * @public * @static * @param {Discord~Client} client Client reference for obtaining * discord object references. * @param {string} id The Guild ID this action is for. * @param {object} obj The object data from save file. * @returns {?Action} The created action, or null if failed to find the * action. */ static from(client, id, obj) { if (typeof obj.className !== 'string' || obj.className.length === 0) { console.error(obj.className, 'is not an Action name'); return null; } const action = Action[obj.className]; let out = null; if (action) { try { out = action.create(client, id, obj.data); } catch (err) { console.error(err); } if (!out) { console.error('Action.js: Unable to create', obj.className, id, obj); return null; } if (typeof obj.delay === 'number') { out.delay = obj.delay; } if (typeof obj.id === 'string') { out.id = obj.id; } } else { console.error(obj.className, 'is not an Action'); return null; } return out; } } /** * @description List of available actions. * @public * @static * @type {Array.<{path: string, pickable: boolean}>} */ Action.actionList = [ {path: './MemberAction.js'}, {path: './ChannelAction.js'}, { path: './GiveRoleAction.js', type: 'member', args: [{name: 'role', type: 'role'}], }, { path: './TakeRoleAction.js', type: 'member', args: [{name: 'role', type: 'role'}], }, { path: './SendMessageAction.js', type: 'channel', args: [{name: 'message', type: 'text'}], }, { path: './RunCommandAction.js', type: 'channel', args: [{name: 'command', type: 'text'}, {name: 'author', type: 'member'}], }, {path: './SendDayStartMessageAction.js', type: 'channel'}, {path: './SendDayEndMessageAction.js', type: 'channel'}, {path: './SendPlayerRankMessageAction.js', type: 'channel'}, {path: './SendStatusListAction.js', type: 'channel'}, {path: './SendTeamRankMessageAction.js', type: 'channel'}, {path: './SendVictorAction.js', type: 'channel'}, {path: './SendEventMessageAction.js', type: 'channel'}, {path: './SendAutoplayingMessageAlertAction.js', type: 'channel'}, ]; /** * @description Map of metadata for available triggers, to aid with UIs. * @public * @static * @type {object.<{ * order: number, * types: string[], * description: string * }>} */ Action.triggerMeta = { gameStart: { order: 10, types: ['member', 'channel'], description: 'Prior to game starting.', }, dayStart: { order: 20, types: ['member', 'channel'], description: 'Prior to day starting.', }, eventInstant: { order: 30, types: ['member', 'channel'], description: 'Moment the event occurs.', }, eventPlayerDeath: { order: 31, types: ['member'], description: 'Players who died during the event.', }, eventPlayerRevive: { order: 31, types: ['member'], description: 'Players who were revived during the event.', }, eventPlayerWound: { order: 31, types: ['member'], description: 'Players who were wounded during the event.', }, eventPlayerHealed: { order: 31, types: ['member'], description: 'Players who were healed during the event.', }, eventPlayerKilled: { order: 31, types: ['member'], description: 'Players who killed another player during the event.', }, eventPlayerGainWeapon: { order: 31, types: ['member'], description: 'Players who gained a weapon during the event.', }, eventPlayerLoseWeapon: { order: 31, types: ['member'], description: 'Players who lost a weapon during the event.', }, eventPlayerUseWeapon: { order: 31, types: ['member'], description: 'Players who used a weapon during the event, ' + 'and neither gained nor lost consumables.', }, eventPlayerUnAffected: { order: 31, types: ['member'], description: 'Players whose state did not change during the event, ' + 'but took part in the event.', }, eventPlayerAffected: { order: 31, types: ['member'], description: 'All players in the event.', }, dayEnd: { order: 40, types: ['member', 'channel'], description: 'After the day has ended.', }, dayPlayerDead: { order: 41, types: ['member'], description: 'After day has ended, for all players that are dead.', }, dayPlayerAlive: { order: 41, types: ['member'], description: 'After day has ended, for all players that are alive.', }, dayPlayerWounded: { order: 41, types: ['member'], description: 'After day has ended, for all players that are wounded.', }, gameEnd: { order: 50, types: ['member', 'channel'], description: 'After game has ended.', }, gameAbort: { order: 50, types: ['member', 'channel'], description: 'If game is ended early with command.', }, gamePlayerDead: { order: 51, types: ['member', 'channel'], description: 'After game ended, for all players that are dead.', }, gamePlayerAlive: { order: 51, types: ['member', 'channel'], description: 'After game ended, for all players that are alive.', }, gamePlayerWounded: { order: 51, types: ['member', 'channel'], description: 'After game ended, for all players that are wounded.', }, gamePlayerWin: { order: 52, types: ['member'], description: 'After game ended, for all players that won.', }, gamePlayerLose: { order: 52, types: ['member'], description: 'After game ended, for all players that lost.', }, }; module.exports = Action; Action.actionList.forEach( (el) => delete require.cache[require.resolve(el.path)]); Action.actionList.forEach((el) => { try { const obj = require(el.path); Action[obj.name] = obj; el.name = obj.name; } catch (err) { console.error(err); } });