UNPKG

la-cosa-nostra

Version:

A Mafia bot designed to run in Discord - beware the traitors and the lies!

529 lines (352 loc) 11 kB
// Stores all the executable actions var logger = process.logger; var actionables = require("../actionables.js"); // for now var crypto = require("crypto"); var Player = require("./Player.js"); var auxils = require("../auxils.js"); module.exports = class { constructor () { } init (game) { this.actions = new Array(); this.previous = new Array(); this.visit_log = new Array(); this.previous_visit_log = new Array(); this.game = game; return this; } add (identifier, triggers, options, rearrange=true) { // Actions are calculated relative to the step var allowed = ["cycle", "chat", "lynch", "attacked", "killed", "visit", "roleblock", "postcycle", "instant", "outvisit", "retrooutvisit", "retrovisit", "retrocycle", "vote", "unvote", "arbitrary", "miscellaneous"]; for (var i = 0; i < triggers.length; i++) { if (!allowed.includes(triggers[i])) { var err = "Unknown trigger " + triggers[i] + "."; throw new Error(err); }; }; var actionable = options; if (triggers.includes("retrooutvisit")) { if (triggers.length > 1) { var err = "Retrooutvisit trigger cannot be combined."; throw new Error(err); }; }; if (triggers.includes("retrovisit")) { if (triggers.length > 1) { var err = "Retrovisit trigger cannot be combined."; throw new Error(err); }; }; if (triggers.includes("retrocycle")) { if (triggers.length > 1) { var err = "Retrocycle trigger cannot be combined."; throw new Error(err); }; }; actionable.id = crypto.randomBytes(4).toString("hex"); actionable.identifier = identifier; actionable.triggers = triggers; actionable.tags = actionable.tags || new Array(); actionable.meta = actionable.meta || new Object(); if (actionable.tags.includes("system")) { actionable.from = "*"; actionable.to = "*"; }; if (actionable.from !== "*") { var from = this.game.getPlayer(actionable.from); var implicit_priority = from.getStat("priority", Math.max); actionable.from = from.identifier; } else { var implicit_priority = 0; }; if (actionable.to !== "*") { var to = this.game.getPlayer(actionable.to); actionable.to = to.identifier; }; actionable.priority = actionable.priority || implicit_priority; // Number of "hits" before execution // Defaults to zero - immediately execute when hit actionable.execution = actionable.execution || 0; actionable.cycles = 0; actionable._scan = new Array(); // Append new tags to array var runnable = actionables[actionable.identifier]; if (typeof runnable === "function" && Array.isArray(runnable.TAGS)) { actionable.tags = actionable.tags.concat(runnable.TAGS); }; /* var actionable = { id: crypto.randomBytes(4).toString("hex"), name: name, from: from, to: to, priority: priority, identifier: identifier, expiry: expiry, execution: execution, trigger: ["cycle"] };*/ // triggers: cycle, chat, lynch, arbitrary, attacked this.actions.push(actionable); if (rearrange) { this.sortByPriority(true); }; if (triggers.includes("instant")) { // Execute immediately this.execute("instant"); }; this.game.tentativeSave(); return actionable; } sortByPriority (shuffle_first=false) { if (shuffle_first) { this.actions = auxils.shuffle(this.actions); }; this.actions.sort(function (a, b) { if (!a || !b) { return -1; }; return a.priority - b.priority; }); } findAllTagged (tag) { var ret = new Array(); for (var i = 0; i < this.actions.length; i++) { if (!this.actions[i]) { continue; }; if (this.actions[i].tags.includes(tag)) { ret.push(this.actions[i]); }; }; return ret; } find (key, value) { for (var i = 0; i < this.actions.length; i++) { if (!this.actions[i]) { continue; }; if (typeof key === "function") { var condition = key(this.actions[i]); if (condition) { return this.actions[i]; }; } else { if (this.actions[i][key] === value) { return this.actions[i]; }; }; }; return undefined; } findAll (key, value) { var ret = new Array(); for (var i = 0; i < this.actions.length; i++) { if (!this.actions[i]) { continue; }; if (typeof key === "function") { var condition = key(this.actions[i]); if (condition) { ret.push(this.actions[i]); }; } else if (this.actions[i][key] === value) { ret.push(this.actions[i]); }; }; return ret; } delete (key, value) { var ret = new Array(); for (var i = this.actions.length - 1; i >= 0; i--) { if (!this.actions[i]) { continue; }; if (typeof key === "function") { var condition = key(this.actions[i]); if (condition) { ret.push(this.actions[i]); this.actions.splice(i, 1); }; } else { if (this.actions[i][key] === value) { ret.push(this.actions[i]); this.actions.splice(i, 1); }; }; }; return ret; } exists (key, value) { return this.find(key, value) !== undefined; } get () { return this.actions; } step () { // Iterate through actions this.execute("cycle"); } // "params" is optional execute (type, params, check_expiries=true) { // Actions: [from, to, game] // Returns: boolean // If true for chat, lynch, arbitrary types, subtract one // from expiration // Create loop identifier var loop_id = crypto.randomBytes(8).toString("hex"); var game = this.game; if (type === "chat") { var sender = game.getPlayerById(params.message.author.id); if (!sender) { return null; }; params.target = sender.identifier; }; if (type === "visit") { this.visit_log.push(params); this.execute("outvisit", params); }; var i = 0; while (i < this.actions.length) { var action = this.actions[i]; if (!action) { i++; continue; }; if (action._scan.includes(loop_id)) { i++; continue; }; action._scan.push(loop_id); if (["cycle"].includes(type) && !action.triggers.includes("retrooutvisit") && !action.triggers.includes("retrovisit") && !action.triggers.includes("retrocycle")) { action.execution--; action.cycles++; if (action.expiry !== Infinity && !action.tags.includes("permanent")) { action.expiry--; }; }; if (["retrocycle"].includes(type) && (action.triggers.includes("retrooutvisit") || action.triggers.includes("retrovisit") || action.triggers.includes("retrocycle"))) { action.execution--; action.cycles++; if (action.expiry !== Infinity && !action.tags.includes("permanent")) { action.expiry--; }; }; if (!action.triggers.includes(type)) { i++; continue; }; var run = actionables[action.identifier]; if (!run) { logger.log(3, "Bad undefined function in actions: " + action.identifier + "!"); i++; continue; }; var rerun = false; function execute () { rerun = true; try { var result = run(action, game, params); return result; } catch (err) { logger.logError(err); logger.log(4, "[Error follow-up] attempted to destroy action %s to prevent snowballing.\nFrom: %s\nTo: %s", action.identifier, action.from, action.to); // Attempts to destroy action in event of failure return true; }; }; // Non-routine triggers if (["chat", "lynch", "attacked", "killed", "visit", "roleblock", "outvisit", "retrooutvisit", "retrovisit", "vote", "unvote", "arbitrary", "miscellaneous"].includes(type)) { var target = action.target || action.to; var check = params.target; if (["outvisit"].includes(type)) { check = params.visitor; }; if (check === target || target === "*") { if (action.execution <= 0) { var result = execute(); if (result === true) { // Immediately mark for deletion action.expiry = 0; }; }; }; }; // Periodic-triggers if (["cycle", "postcycle", "instant", "retrocycle"].includes(type)) { if (action.execution <= 0) { var result = execute(); if (result === true) { // Immediately mark for deletion action.expiry = 0; }; }; }; /* Had to shift this in; yes, yes, I know it slows stuff down; but dang it there's no easier way out */ if (check_expiries) { this.nullExpiries(type); }; if (rerun) { i = 0; } else { i++; }; }; // Remove loop ID for (var i = 0; i < this.actions.length; i++) { var action = this.actions[i]; if (!action) { continue; }; action._scan = this.actions[i]._scan.filter(x => x !== loop_id); }; if (type === "cycle") { for (var i = 0; i < this.visit_log.length; i++) { var visit_log = this.visit_log[i]; this.execute("retrovisit", visit_log); var inverse_log = Object.assign(new Object(), visit_log); inverse_log.visited = inverse_log.target; inverse_log.target = inverse_log.visitor; delete inverse_log.visitor; this.execute("retrooutvisit", inverse_log); }; this.execute("retrocycle"); this.previous_visit_log = this.previous_visit_log.concat(this.visit_log); this.visit_log = new Array(); }; // Decrement those outside cycle if (check_expiries) { this.nullExpiries(); this.removeUndefinedActionables(); }; } reinstantiate (game) { this.game = game; } nullExpiries (trigger=null) { // Check expiries, remove for (var i = 0; i < this.actions.length; i++) { if (!this.actions[i]) { continue; }; if (this.actions[i].expiry < 1 && (this.actions[i].triggers.includes(trigger) || trigger === null)) { // Remove this.previous.push(this.actions[i]); delete this.actions[i]; }; }; } removeUndefinedActionables () { for (var i = this.actions.length - 1; i >= 0; i--) { if (!this.actions[i]) { this.actions.splice(i, 1); }; }; } };