la-cosa-nostra
Version:
A Mafia bot designed to run in Discord - beware the traitors and the lies!
824 lines (559 loc) • 16.8 kB
JavaScript
var logger = process.logger;
var actionables = require("../actionables.js");
var attributes = require("../attributes.js");
var executable = require("../executable.js");
var auxils = require("../auxils.js");
var crypto = require("crypto");
module.exports = class {
constructor () {
}
isVotedAgainstBy (identifier) {
// Check if id is voting against player
for (var i = 0; i < this.votes.length; i++) {
if (this.votes[i].identifier === identifier) {
return true;
};
};
return false;
}
voteAgainst (identifier, magnitude=1) {
// x votes against this player
this.votes.push({identifier: identifier, magnitude: magnitude});
this.game.execute("vote", {target: identifier, voter: this.identifier});
}
toggleVotes (identifier, magnitude=1) {
if (this.isVotedAgainstBy(identifier)) {
this.clearVotesBy(identifier);
return false;
} else {
this.voteAgainst(identifier, magnitude)
return true;
};
}
clearVotesBy (identifier) {
for (var i = 0; i < this.votes.length; i++) {
if (this.votes[i].identifier === identifier) {
this.votes.splice(i, 1);
this.game.execute("unvote", {target: identifier, voter: this.identifier});
return true;
};
};
return false;
}
clearVotes () {
this.votes = new Array();
}
countVotes () {
// Offset the vote count
var votes = new Number();
for (var i = 0; i < this.votes.length; i++) {
// Magnitude
votes += this.votes[i].magnitude;
};
// Offset is calculated elsewhere
return votes;
}
getVoteOffset () {
return this.getStat("vote-offset", (a, b) => a + b);
}
init (id, alphabet, role) {
this.id = id;
this.alphabet = alphabet;
this.role_identifier = role;
this.base_flavour_identifier = undefined;
this.initial_role_identifier = [role];
this.identifier = crypto.randomBytes(8).toString("hex") + "-" + this.id;
this.channel = null;
this.special_channels = new Array();
this.pre_emptive = new Array();
this.votes = new Array();
this.intro_messages = new Array();
// 3x stats - game_stats, permanent_stats, role.stats
this.status = {
"alive": true,
"roleblocked": false,
"controlled": false,
"silenced": false,
"kidnapped": false,
"vote-blocked": false,
"won": false,
"can-win": true
};
this.misc = new Object();
this.visiting = new Array();
this.will = undefined;
this.display_role = undefined;
this.display_secondary = undefined;
this.instantiateRole();
this.see_mafia_chat = this.role["see-mafia-chat"];
this.permanent_stats = {
"basic-defense": 0,
"roleblock-immunity": 0,
"detection-immunity": 0,
"control-immunity": 0,
"redirection-immunity": 0,
"kidnap-immunity": 0,
"priority": 0,
"vote-offset": 0,
"vote-magnitude": this.role.stats["vote-magnitude"]
};
// Initialise stats
// A more than value will cause
// the action to fire
this.game_stats = {
"basic-defense": 0,
"roleblock-immunity": 0,
"detection-immunity": 0,
"control-immunity": 0,
"redirection-immunity": 0,
"kidnap-immunity": 0,
"priority": 0,
"vote-offset": 0,
"vote-magnitude": this.permanent_stats["vote-magnitude"]
};
// Attributes
this.attributes = new Array();
return this;
}
postGameInit () {
this.instantiateFlavour();
}
addPreemptiveVote (identifier) {
this.pre_emptive.push(identifier);
}
clearPreemptiveVotes (runnable) {
if (typeof runnable === "function") {
for (var i = this.pre_emptive.length - 1; i >= 0; i--) {
var identifier = this.pre_emptive[i];
var player = this.game.getPlayerByIdentifier(identifier);
var outcome = runnable(player);
if (outcome === true) {
this.pre_emptive.splice(i, 1);
};
};
} else {
this.pre_emptive = new Array();
};
}
getPreemtiveVotes () {
return this.pre_emptive;
}
resetTemporaryStats () {
this.game_stats = {
"basic-defense": 0,
"roleblock-immunity": 0,
"detection-immunity": 0,
"control-immunity": 0,
"redirection-immunity": 0,
"kidnap-immunity": 0,
"priority": 0,
"vote-offset": 0,
"vote-magnitude": this.permanent_stats["vote-magnitude"]
};
this.setStatus("roleblocked", false);
this.setStatus("controlled", false);
this.setStatus("silenced", false);
this.setStatus("kidnapped", false);
this.setStatus("vote-blocked", false);
}
setGameStat (key, amount, modifier) {
if (modifier === "set") {
modifier = () => amount;
};
if (modifier === undefined) {
modifier = auxils.operations.addition;
};
var final = modifier(this.game_stats[key], amount);
this.game_stats[key] = final;
return final;
}
setPermanentStat (key, amount, modifier) {
if (modifier === "set") {
var final = amount;
} else {
if (modifier === undefined) {
modifier = (a, b) => a + b;
};
var final = modifier(this.permanent_stats[key], amount);
};
this.permanent_stats[key] = final;
return final;
}
getPrivateChannel () {
var guild = this.game.client.guilds.get(this.game.config["server-id"]);
var channel = guild.channels.get(this.channel.id);
return channel;
}
getRoleStats () {
return this.role.stats;
}
getPermanentStats () {
return this.permanent_stats;
}
getTemporaryStats () {
return this.game_stats;
}
getVoteMagnitude () {
return this.getTemporaryStats()["vote-magnitude"];
}
getStat (key, modifier) {
if (modifier === undefined) {
modifier = (a, b) => a + b;
};
if (key === "vote-magnitude") {
var err = "[Vote magnitude] get this number with Player.getVoteMagnitude()";
throw new Error(err);
};
var a = this.game_stats[key];
var b = this.permanent_stats[key];
var c = this.role.stats[key];
return modifier(modifier(a, b), c);
}
getStatus (key) {
return this.status[key];
}
setStatus (key, value) {
this.status[key] = value;
}
setWill (will) {
this.will = will;
}
setPrecedentWill (will) {
this.precedent_will = will;
}
getWill () {
var will = this.precedent_will;
if (will === null) {
return undefined;
};
return this.precedent_will || this.will;
}
getTrueWill () {
// Gets the vanilla will
return this.will;
}
getGuildMember () {
var client = this.game.client;
var config = this.game.config;
var guild = client.guilds.get(config["server-id"]);
var member = guild.members.get(this.id);
return member;
}
getDisplayName () {
var member = this.getGuildMember();
if (member === undefined) {
return "[" + this.alphabet + "] undef'd player";
} else {
return member.displayName;
};
}
lynchable () {
// Set the player to be lynched
// Future special functions may go here
return true;
}
kill () {
this.status.alive = false;
}
async start () {
if (this.role.start !== undefined) {
try {
await this.role.start(this);
} catch (err) {
logger.log(4, "Role start script error with player %s (%s) [%s]", this.identifier, this.getDisplayName(), this.role_identifier);
logger.logError(err);
};
};
// Start attributes
for (var i = 0; i < this.attributes.length; i++) {
var attribute = attributes[this.attributes[i].identifier];
if (attribute.start) {
if (attribute.DO_NOT_RUN_ON_GAME_START === true) {
return null;
};
try {
// Define truestart synchronisation
await attribute.start(this, this.attributes[i], true);
} catch (err) {
logger.log(4, "Attribute start script error with player %s (%s) [%s]", this.identifier, this.getDisplayName(), this.attributes[i].identifier);
logger.logError(err);
};
};
};
await this.postIntro();
this.__routines();
}
async postIntro () {
// Post intro
await executable.roles.postRoleIntroduction(this);
}
getDisplayRole (append_true_role=true) {
// Show display role first
return this.display_role || this.getTrueFlavourRole(append_true_role);
}
getTrueFlavourRole (append_true_role=true) {
// Show display role first
var flavour_role = this.flavour_role;
var flavour = this.game.getGameFlavour();
if (flavour && flavour_role) {
var display_extra = flavour.info["display-role-equivalent-on-death"];
if (display_extra && flavour_role !== this.role["role-name"] && append_true_role) {
flavour_role += " (" + (this.display_secondary || this.role["role-name"]) + ")";
};
};
return flavour_role || this.role["role-name"];
}
setDisplayRole (role_name) {
this.display_role = role_name;
}
clearDisplayRole () {
this.display_role = null;
}
getRole () {
// Give true role
return this.display_secondary || this.role["role-name"];
}
getInitialRole (append_true_role=true) {
var flavour_role = this.flavour_role;
var flavour = this.game.getGameFlavour();
var initial = executable.roles.getRole(this.initial_role_identifier[0])["role-name"];
if (flavour && flavour_role) {
if (flavour_role !== initial && append_true_role) {
flavour_role += " (" + (this.display_secondary || initial) + ")";
};
};
return flavour_role || initial || this.role["role-name"];
}
getInitialRoleDetails () {
return executable.roles.getRole(this.initial_role_identifier[0]);
}
assignChannel (channel) {
this.channel = {
id: channel.id,
name: channel.name,
created_at: channel.createdAt
};
this.addSpecialChannel(channel);
}
addSpecialChannel (channel) {
this.special_channels.push({
id: channel.id,
name: channel.name,
created_at: channel.createdAt
});
}
removeSpecialChannel (channel) {
this.special_channels = this.special_channels.filter(x => x.id !== channel);
}
getSpecialChannels () {
return this.special_channels;
}
reinstantiate (game) {
this.setGame(game);
this.instantiateRole();
}
verifyProperties () {
var incompatible = new Array();
var role = executable.roles.getRole(this.role_identifier, true);
if (!role) {
incompatible.push({type: "role", identifier: this.role_identifier});
};
for (var i = 0; i < this.attributes.length; i++) {
var identifier = this.attributes[i].identifier;
if (!attributes[identifier]) {
incompatible.push({type: "attribute", identifier: identifier});
};
};
return incompatible;
}
setGame (game) {
this.game = game;
}
isAlive () {
return this.getStatus("alive");
}
changeRole (role_identifier, change_vote_magnitude_stat=false, rerun_start=true) {
this.role_identifier = role_identifier;
this.initial_role_identifier.push(role_identifier);
this.instantiateRole();
this.see_mafia_chat = this.see_mafia_chat || this.role["see-mafia-chat"];
if (change_vote_magnitude_stat) {
var current_magnitude = this.getRoleStats()["vote-magnitude"];
this.setPermanentStat("vote-magnitude", current_magnitude, "set");
};
if (rerun_start && this.role.start !== undefined) {
this.role.start(this);
};
}
instantiateRole () {
this.role = executable.roles.getRole(this.role_identifier);
}
instantiateFlavour () {
var flavour = this.game.getGameFlavour();
if (!flavour) {
return null;
};
// Base flavour identifier to override
var identifier = this.base_flavour_identifier || this.role_identifier;
// Open identifier
var current = flavour.roles[identifier];
if (!current) {
// Flavour role not defined
this.flavour_role = null;
return null;
};
var assigned = this.game.findAll(x => x.role_identifier === identifier && !x.flavour_role);
var index = assigned.length % current.length;
// Roles is an array
// Count number of roles assigned before
this.flavour_role = current[index].name;
logger.log(1, "Flavour: %s, Role: %s", this.flavour_role, this.role_identifier);
}
isSame (player) {
// Compare identifiers
return this.identifier === player.identifier;
};
__routines () {
this.resetTemporaryStats();
this.checkAttributeExpiries();
var ret = this.executeRoutine(this.role.routine);
for (var i = 0; i < this.attributes.length; i++) {
var runnable = attributes[this.attributes[i].identifier].routines;
try {
this.executeRoutine(runnable);
} catch (err) {
logger.logError(err);
};
};
return ret;
}
executeRoutine (routine) {
if (routine === undefined) {
return null;
};
// Check if dead
if (!this.isAlive() && !routine.ALLOW_DEAD) {
return null;
};
if (this.game.isDay() && !routine.ALLOW_DAY) {
return null;
};
if (!this.game.isDay() && !routine.ALLOW_NIGHT) {
return null;
};
try {
return routine(this);
} catch (err) {
logger.logError(err);
return null;
}
}
setWin () {
this.status.won = true;
}
hasWon () {
return this.status.won === true;
}
canWin () {
return this.status["can-win"] === true;
}
addIntroMessage (message) {
this.intro_messages.push(message);
}
substitute (id) {
this.id = id;
}
hasAttribute (attribute) {
// Attribute format:
// {identifier: "attribute_name", expiry: 1}
return this.attributes.some(x => x.identifier === attribute);
}
addAttribute (attribute, expiry=Infinity, tags=new Object()) {
if (!attributes[attribute]) {
throw new Error("Invalid attribute identifier " + attribute + "!");
};
var addable = {identifier: attribute, expiry: expiry, tags: tags, attribute: attributes[attribute].attribute};
if (attributes[attribute].start && this.game) {
if (attributes[attribute].DO_NOT_RUN_ON_ADDITION === true) {
return null;
};
try {
// Define truestart synchronisation
attributes[attribute].start(this, addable, false);
} catch (err) {
logger.log(4, "Attribute start script error with player %s (%s) [%s]", this.identifier, this.getDisplayName(), addable.identifier);
logger.logError(err);
};
};
this.attributes.push(addable);
}
checkAttributeExpiries () {
for (var i = this.attributes.length - 1; i >= 0; i--) {
if (this.attributes[i].expiry !== Infinity) {
this.attributes[i].expiry--;
};
if (this.attributes[i].expiry <= 0) {
this.attributes.splice(i, 1);
};
};
}
deleteAttributes (key, value) {
var ret = new Array();
for (var i = this.attributes.length - 1; i >= 0; i--) {
if (!this.attributes[i]) {
continue;
};
if (typeof key === "function") {
var condition = key(this.attributes[i]);
if (condition) {
ret.push(this.attributes[i]);
this.attributes.splice(i, 1);
};
} else {
if (this.attributes[i][key] === value) {
ret.push(this.attributes[i]);
this.attributes.splice(i, 1);
};
};
};
return ret;
}
deleteAttribute (key, value) {
for (var i = this.attributes.length - 1; i >= 0; i--) {
if (!this.attributes[i]) {
continue;
};
if (typeof key === "function") {
var condition = key(this.attributes[i]);
if (condition) {
var ret = this.attributes[i];
this.attributes.splice(i, 1);
return ret;
};
} else {
if (this.attributes[i][key] === value) {
var ret = this.attributes[i];
this.attributes.splice(i, 1);
return ret;
};
};
};
return null;
}
getDiscordUser () {
return this.game.client.users.get(this.id);
}
setBaseFlavourIdentifier (identifier) {
this.base_flavour_identifier = identifier;
}
setDisplaySecondary (identifier) {
this.display_secondary = identifier;
}
};
function dateTimeReviver (key, value) {
var date_format = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/g;
if (typeof value === "string" && date_format.test(value)) {
return new Date(value);
};
return value;
};