diff --git a/Games/types/LiarsDice/Game.js b/Games/types/LiarsDice/Game.js index 294d5e706..f07947479 100644 --- a/Games/types/LiarsDice/Game.js +++ b/Games/types/LiarsDice/Game.js @@ -100,13 +100,22 @@ module.exports = class LiarsDiceGame extends Game { this.sendAlert(`Good luck... You'll probably need it.`); //start of game - randomizes player order, and gives dice to everyone. - this.randomizedPlayers = Random.randomizeArray(this.players.array()); + this.hasHost = this.setup.roles[0]["Host:"]; + if (this.hasHost) { + let hostPlayer = this.players.array()[0]; + this.randomizedPlayers = Random.randomizeArray( + this.players.array() + ).filter((p) => p != hostPlayer); + } else { + this.randomizedPlayers = Random.randomizeArray(this.players.array()); + } this.randomizedPlayersCopy = this.randomizedPlayers; this.randomizedPlayers.forEach((player) => { player.diceNum = this.startingDice; }); + // super.start(); this.rollDice(); this.startRoundRobin(); @@ -923,7 +932,7 @@ module.exports = class LiarsDiceGame extends Game { }, }); - if (player.alive) { + if (player.alive && player != this.hostPlayer) { this.sendAlert( `${player.name} left, but their ${player.rolledDice.length} dice will still count towards this round's total.` ); diff --git a/Games/types/LiarsDice/roles/Host/Host.js b/Games/types/LiarsDice/roles/Host/Host.js new file mode 100644 index 000000000..3fc98c2e0 --- /dev/null +++ b/Games/types/LiarsDice/roles/Host/Host.js @@ -0,0 +1,10 @@ +const Role = require("../../Role"); + +module.exports = class Host extends Role { + constructor(player, data) { + super("Host", player, data); + + this.alignment = "Host"; + this.cards = ["TownCore"]; + } +}; diff --git a/Games/types/LiarsDice/roles/cards/WinIfLastAlive.js b/Games/types/LiarsDice/roles/cards/WinIfLastAlive.js index f5079c0a4..9aa6d46fa 100644 --- a/Games/types/LiarsDice/roles/cards/WinIfLastAlive.js +++ b/Games/types/LiarsDice/roles/cards/WinIfLastAlive.js @@ -7,7 +7,11 @@ module.exports = class WinIfLastAlive extends Card { this.winCheck = { priority: 0, check: function (counts, winners, aliveCount) { - if (aliveCount <= 1 && this.player.alive) + let nonHostAlive = this.game + .alivePlayers() + .filter((p) => p.role.name != "Host"); + + if (nonHostAlive.length <= 1 && this.player.alive) winners.addPlayer(this.player, this.name); }, }; diff --git a/Games/types/Mafia/roles/cards/Ascetic.js b/Games/types/Mafia/roles/cards/Ascetic.js index 10589fe53..3cc33302f 100644 --- a/Games/types/Mafia/roles/cards/Ascetic.js +++ b/Games/types/Mafia/roles/cards/Ascetic.js @@ -1,10 +1,11 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_UNTARGETABLE } = require("../../const/Priority"); module.exports = class Ascetic extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_UNTARGETABLE, @@ -16,5 +17,26 @@ module.exports = class Ascetic extends Card { }, }, ]; +*/ + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + if (!stateInfo.name.match(/Night/)) { + return; + } + var action = new Action({ + actor: this.player, + priority: PRIORITY_UNTARGETABLE, + game: this.player.game, + labels: ["stop", "hidden"], + run: function () { + this.makeUntargetable(this.actor, "kill"); + }, + }); + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/AskDeadQuestion.js b/Games/types/Mafia/roles/cards/AskDeadQuestion.js index 4fa300437..8ff281adf 100644 --- a/Games/types/Mafia/roles/cards/AskDeadQuestion.js +++ b/Games/types/Mafia/roles/cards/AskDeadQuestion.js @@ -1,4 +1,5 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_DAY_DEFAULT, PRIORITY_INVESTIGATIVE_DEFAULT, @@ -25,10 +26,23 @@ module.exports = class AskDeadQuestion extends Card { 'Answer Mourner asking "' + this.actor.role.data.question + '"'; this.actor.role.mournerYes = 0; this.actor.role.mournerNo = 0; + if (!this.actor.role.data.question) { + return; + } + for (let player of this.game.players) { + if (!player.alive) { + player.holdItem("Mourned", { + mourner: this.actor, + question: this.actor.role.data.question, + meetingName: this.actor.role.data.meetingName, + }); + } + } }, }, }, }; + /* this.actions = [ // give mourned item to dead { @@ -39,7 +53,10 @@ module.exports = class AskDeadQuestion extends Card { return; } - if (this.game.getStateName() !== "Day") { + if ( + this.game.getStateName() !== "Day" && + this.game.getStateName() !== "Dusk" + ) { return; } @@ -67,7 +84,10 @@ module.exports = class AskDeadQuestion extends Card { return; } - if (this.game.getStateName() !== "Night") { + if ( + this.game.getStateName() !== "Night" && + this.game.getStateName() !== "Dawn" + ) { return; } @@ -104,5 +124,66 @@ module.exports = class AskDeadQuestion extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (stateInfo.name.match(/Day/)) { + } + if (!stateInfo.name.match(/Night/)) { + return; + } + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_INVESTIGATIVE_DEFAULT, + run: function () { + if (!this.actor.alive) { + return; + } + + if ( + this.game.getStateName() !== "Night" && + this.game.getStateName() !== "Dawn" + ) { + return; + } + + if (!this.actor.role.data.question) { + return; + } + + let numYes = this.actor.role.mournerYes; + let numNo = this.actor.role.mournerNo; + + let totalResponses = numYes + numNo; + + let percentNo = Math.round((numNo / totalResponses) * 100); + let percentYes = Math.round((numYes / totalResponses) * 100); + + if (this.actor.hasEffect("FalseMode")) { + if (totalResponses === 0) { + percentYes = 100; + percentNo = 0; + totalResponses = totalResponses + 1; + } else { + let temp = percentNo; + percentNo = percentYes; + percentYes = temp; + } + } + + if (totalResponses === 0) + this.actor.queueAlert(`You receive no responses from the dead.`); + else + this.actor.queueAlert( + `The dead has replied with ${percentYes}% Yes's and ${percentNo}% No's to your question "${this.actor.role.data.question}".` + ); + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/Astral.js b/Games/types/Mafia/roles/cards/Astral.js index da4994253..38511cc6a 100644 --- a/Games/types/Mafia/roles/cards/Astral.js +++ b/Games/types/Mafia/roles/cards/Astral.js @@ -1,10 +1,11 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_MODIFY_ACTION_LABELS } = require("../../const/Priority"); module.exports = class Astral extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_MODIFY_ACTION_LABELS, @@ -23,5 +24,30 @@ module.exports = class Astral extends Card { }, }, ]; +*/ + this.listeners = { + state: function (stateInfo) { + if (!stateInfo.name.match(/Night/)) { + return; + } + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_MODIFY_ACTION_LABELS, + labels: ["absolute", "hidden"], + run: function () { + for (let action of this.game.actions[0]) { + if ( + action.priority > this.priority && + action.actors.includes(this.actor) + ) { + action.labels = [...action.labels, "hidden"]; + } + } + }, + }); + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/BecomeAlignmentOfVisitors.js b/Games/types/Mafia/roles/cards/BecomeAlignmentOfVisitors.js index 1f4aeadbc..3305aa102 100644 --- a/Games/types/Mafia/roles/cards/BecomeAlignmentOfVisitors.js +++ b/Games/types/Mafia/roles/cards/BecomeAlignmentOfVisitors.js @@ -1,4 +1,5 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_BLOCK_VISITORS, PRIORITY_WIN_CHECK_DEFAULT, @@ -7,7 +8,7 @@ const { module.exports = class BecomeAlignmentOfVisitors extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_BLOCK_VISITORS - 1, @@ -27,25 +28,43 @@ module.exports = class BecomeAlignmentOfVisitors extends Card { this.actor.faction = visit.faction; return; } - /* - for (let action of this.game.actions[0]) { - if (action.target == this.actor && !action.hasLabel("hidden")) { + }, + }, + ]; + */ + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + if (!stateInfo.name.match(/Night/)) { + return; + } + var action = new Action({ + actor: this.player, + target: target, + game: this.player.game, + priority: PRIORITY_BLOCK_VISITORS - 1, + labels: ["block", "hidden"], + run: function () { + if (!this.actor.alive) return; - if ( - action.priority > this.priority - ) { - if (this.dominates(action.actor)) { - this.blockWithMindRot(action.actor); - } + for (let visit of this.getVisitors(this.actor)) { + if (this.dominates(visit)) { + this.blockWithMindRot(visit); } - this.actor.queueAlert(`After Hitchhiking with a player you feel like Supporting the ${action.actors [0].role.alignment}.`); - this.actor.role.alignment = action.actor.role.alignment; - return; + + this.actor.queueAlert( + `After Hitchhiking with a player you feel like Supporting the ${visit.faction}.` + ); + this.actor.faction = visit.faction; + return; } - } - */ - }, + }, + }); + + this.game.queueAction(action); }, - ]; + }; } }; diff --git a/Games/types/Mafia/roles/cards/BecomeExcessRole.js b/Games/types/Mafia/roles/cards/BecomeExcessRole.js index 7295f3fe8..e8b9481ec 100644 --- a/Games/types/Mafia/roles/cards/BecomeExcessRole.js +++ b/Games/types/Mafia/roles/cards/BecomeExcessRole.js @@ -1,12 +1,13 @@ const Card = require("../../Card"); const Random = require("../../../../../lib/Random"); const AllRoles = require("../../../../../data/roles"); +const Action = require("../../Action"); const { PRIORITY_BECOME_DEAD_ROLE } = require("../../const/Priority"); module.exports = class BecomeExcessRole extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_BECOME_DEAD_ROLE, @@ -64,5 +65,69 @@ module.exports = class BecomeExcessRole extends Card { }, }, ]; +*/ + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_BECOME_DEAD_ROLE, + labels: ["convert"], + run: function () { + if (this.game.getStateName() != "Night") return; + + if (!this.actor.alive) return; + + let roles = this.game.PossibleRoles.filter((r) => r); + let players = this.game.players.filter((p) => p.role); + let currentRoles = []; + + for (let x = 0; x < players.length; x++) { + currentRoles.push(players[x].role); + } + for (let y = 0; y < currentRoles.length; y++) { + roles = roles.filter( + (r) => + r.split(":")[0] != currentRoles[y].name && + (this.game.getRoleAlignment(r) == + this.game.getRoleAlignment(this.actor.role.name) || + this.game.getRoleAlignment(this.actor.role.name) == + "Independent") + ); + } + if (roles.length <= 0) { + roles = Object.entries(AllRoles.Mafia) + .filter( + (roleData) => + roleData[1].alignment === + this.game.getRoleAlignment(this.actor.role.name) + ) + .map((roleData) => roleData[0]); + } + + let newRole = Random.randArrayVal(roles); + + this.actor.setRole( + newRole, + undefined, + false, + false, + false, + "No Change" + ); + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/BegumsSenses.js b/Games/types/Mafia/roles/cards/BegumsSenses.js index d96cbd4e2..a1154ebf7 100644 --- a/Games/types/Mafia/roles/cards/BegumsSenses.js +++ b/Games/types/Mafia/roles/cards/BegumsSenses.js @@ -1,4 +1,5 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const Random = require("../../../../../lib/Random"); const { PRIORITY_INVESTIGATIVE_DEFAULT } = require("../../const/Priority"); const { PRIORITY_DAY_DEFAULT } = require("../../const/Priority"); @@ -42,7 +43,7 @@ module.exports = class BegumsSenses extends Card { }, }, }; - + /* this.actions = [ { priority: PRIORITY_INVESTIGATIVE_DEFAULT, @@ -93,7 +94,7 @@ module.exports = class BegumsSenses extends Card { }, }, ]; - +*/ this.listeners = { roleAssigned: function (player) { if (player !== this.player) return; @@ -102,6 +103,67 @@ module.exports = class BegumsSenses extends Card { .filter((p) => p != this.player); this.begumTarget = Random.randArrayVal(possibleTargets); }, + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_INVESTIGATIVE_DEFAULT, + labels: ["investigate"], + run: function () { + if (!this.actor.alive) return; + if (this.game.getStateName() != "Night") return; + if (!this.actor.role.begumTarget) return; + + let begumTarget = this.actor.role.begumTarget; + let visits = this.getVisits(begumTarget); + let visitNames = visits.map((p) => p.name); + let visitors = this.getVisitors(begumTarget); + let visitorNames = visitors.map((p) => p.name); + + if (this.actor.hasEffect("FalseMode")) { + let players = this.game + .alivePlayers() + .filter((p) => p != this.actor); + let playerNames = players.map((p) => p.name); + if (visitNames.length == 0) { + visitNames.push(Random.randArrayVal(playerNames)); + } else { + visitNames = []; + } + + if (visitorNames.length == 0) { + visitorNames.push(Random.randArrayVal(playerNames)); + } else { + visitorNames = []; + } + } + + if (visitNames.length == 0) visitNames.push("no one"); + if (visitorNames.length === 0) visitorNames.push("no one"); + + this.actor.queueAlert( + `:watch: Your target was visited by ${visitorNames.join( + ", " + )} during the night.` + ); + + this.actor.queueAlert( + `:watch: Your target visited ${visitNames.join( + ", " + )} during the night.` + ); + }, + }); + + this.game.queueAction(action); + }, }; } }; diff --git a/Games/types/Mafia/roles/cards/BlockIfVisited.js b/Games/types/Mafia/roles/cards/BlockIfVisited.js index 7136b3ff2..36f8091a2 100644 --- a/Games/types/Mafia/roles/cards/BlockIfVisited.js +++ b/Games/types/Mafia/roles/cards/BlockIfVisited.js @@ -1,10 +1,11 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_NIGHT_ROLE_BLOCKER } = require("../../const/Priority"); module.exports = class BlockIfVisited extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_NIGHT_ROLE_BLOCKER, @@ -18,5 +19,26 @@ module.exports = class BlockIfVisited extends Card { }, }, ]; +*/ + this.listeners = { + state: function (stateInfo) { + if (!stateInfo.name.match(/Night/)) { + return; + } + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_NIGHT_ROLE_BLOCKER, + labels: ["block", "hidden"], + run: function () { + if (this.hasVisitors() === true) { + this.blockActions(this.actor); + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/BlockTargets.js b/Games/types/Mafia/roles/cards/BlockTargets.js index b31516f96..bd9dde55e 100644 --- a/Games/types/Mafia/roles/cards/BlockTargets.js +++ b/Games/types/Mafia/roles/cards/BlockTargets.js @@ -1,10 +1,11 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_NIGHT_ROLE_BLOCKER } = require("../../const/Priority"); module.exports = class BlockTargets extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_NIGHT_ROLE_BLOCKER, @@ -17,5 +18,25 @@ module.exports = class BlockTargets extends Card { }, }, ]; + */ + this.listeners = { + state: function (stateInfo) { + if (!stateInfo.name.match(/Night/)) { + return; + } + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_NIGHT_ROLE_BLOCKER, + labels: ["block", "hidden"], + run: function () { + let visits = this.getVisits(this.actor); + visits.map((v) => this.blockActions(v)); + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/Bouncy.js b/Games/types/Mafia/roles/cards/Bouncy.js index 5b01c542f..b476f2e33 100644 --- a/Games/types/Mafia/roles/cards/Bouncy.js +++ b/Games/types/Mafia/roles/cards/Bouncy.js @@ -1,11 +1,12 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const Random = require("../../../../../lib/Random"); const { PRIORITY_MODIFY_ACTION } = require("../../const/Priority"); module.exports = class Bouncy extends Card { constructor(role) { super(role); - + /* this.actions = [ { labels: ["redirect"], @@ -30,5 +31,43 @@ module.exports = class Bouncy extends Card { }, }, ]; +*/ + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + labels: ["redirect"], + priority: PRIORITY_MODIFY_ACTION, + run: function () { + if (this.game.getStateName() != "Night") return; + if (!this.actor.alive) return; + var alive = this.game.players.filter( + (p) => + p.alive && + p != this.actor && + p.role.alignment == this.actor.role.alignment + ); + if (alive.length > 0) { + var randomTarget = Random.randArrayVal(alive); + for (const action of this.game.actions[0]) { + if (action.target === this.actor && action.hasLabel("kill")) { + action.target = randomTarget; + } + } + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/CheckSuccessfulVisit.js b/Games/types/Mafia/roles/cards/CheckSuccessfulVisit.js index 6daee9e05..ede6b88c9 100644 --- a/Games/types/Mafia/roles/cards/CheckSuccessfulVisit.js +++ b/Games/types/Mafia/roles/cards/CheckSuccessfulVisit.js @@ -1,10 +1,11 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_INVESTIGATIVE_DEFAULT } = require("../../const/Priority"); module.exports = class CheckSuccessfulVisit extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_INVESTIGATIVE_DEFAULT, @@ -29,5 +30,38 @@ module.exports = class CheckSuccessfulVisit extends Card { }, }, ]; +*/ + this.listeners = { + state: function (stateInfo) { + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + priority: PRIORITY_INVESTIGATIVE_DEFAULT, + labels: ["investigate", "hidden"], + game: this.player.game, + run: function () { + let targets = this.getVisits(this.actor); + let targetNames = targets.map((t) => t.name); + + if (this.actor.hasEffect("FalseMode")) { + if (targetNames.length >= 1) return; + } + + if (targetNames.length >= 1) { + this.actor.queueAlert( + `:invest: You learn that your visit to ${targetNames.join( + ", " + )} was successful.` + ); + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/ChoirOfRoles.js b/Games/types/Mafia/roles/cards/ChoirOfRoles.js index 86ff45fa1..2f38db5c0 100644 --- a/Games/types/Mafia/roles/cards/ChoirOfRoles.js +++ b/Games/types/Mafia/roles/cards/ChoirOfRoles.js @@ -1,12 +1,13 @@ const Card = require("../../Card"); const Random = require("../../../../../lib/Random"); +const Action = require("../../Action"); const { PRIORITY_EFFECT_GIVER_DEFAULT } = require("../../const/Priority"); const { PRIORITY_OVERTHROW_VOTE } = require("../../const/Priority"); module.exports = class ChoirOfRoles extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_EFFECT_GIVER_DEFAULT, @@ -39,7 +40,7 @@ module.exports = class ChoirOfRoles extends Card { }, }, ]; - +*/ this.meetings = { "Guess Wailer": { actionName: "Guess", @@ -78,6 +79,46 @@ module.exports = class ChoirOfRoles extends Card { }, }, }; + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_EFFECT_GIVER_DEFAULT, + labels: ["effect"], + run: function () { + if (!this.actor.alive) return; + + let roles = this.game.PossibleRoles.filter((r) => r); + let players = this.game + .alivePlayers() + .filter((p) => p.role.alignment != "Cult"); + + let role = Random.randArrayVal(roles, true) + .split(":")[0] + .toLowerCase(); + let victim = Random.randArrayVal(players, true); + + victim.queueAlert( + `From your bedroom window you heard the Banshee's wailing about the ${role}. You must say ${role} today or you will be condenmed! If the Banshee guesses your name as their target you will be condenmed anyway so be sneaky!` + ); + victim.giveEffect("ChoirSong", this.actor, role, 1); //,this.actor,role,1 + this.actor.role.data.singer = victim; + this.actor.role.data.singAbout = role; + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/CleanseVisitors.js b/Games/types/Mafia/roles/cards/CleanseVisitors.js index 1ff0112ab..f840ea4b0 100644 --- a/Games/types/Mafia/roles/cards/CleanseVisitors.js +++ b/Games/types/Mafia/roles/cards/CleanseVisitors.js @@ -1,4 +1,5 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_EFFECT_REMOVER_DEFAULT, PRIORITY_CLEANSE_LYCAN_VISITORS, @@ -9,6 +10,7 @@ const { module.exports = class CleanseVisitors extends Card { constructor(role) { super(role); + /* this.actions = [ { priority: PRIORITY_EFFECT_REMOVER_DEFAULT, @@ -108,5 +110,126 @@ module.exports = class CleanseVisitors extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_EFFECT_REMOVER_DEFAULT, + labels: ["cleanse", "hidden"], + run: function () { + let visitors = this.getVisitors(); + + for (let visitor of visitors) { + this.heal(1, visitor); + } + + for (let action of this.game.actions[0]) { + if (action.target == this.actor && !action.hasLabel("hidden")) { + this.cleanse(1, action.actor); + } + } + }, + }); + + var action2 = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_CLEANSE_LYCAN_VISITORS, + labels: ["cleanse", "lycan", "hidden"], + run: function () { + var cleansedWolves = {}; + for (let action of this.game.actions[0]) { + if ( + action.target == this.actor && + action.actor.hasEffect("Lycan") && + action.priority > this.priority && + !action.hasLabel("hidden") + ) { + action.actor.removeEffect("Lycan", true); + cleansedWolves[action.actor.id] = true; + } + } + if (Object.keys(cleansedWolves).length == 0) return; + for (let action of this.game.actions[0]) { + if ( + action.actor && + cleansedWolves[action.actor.id] && + action.hasLabels(["kill", "lycan"]) + ) { + action.cancel(); + } + } + }, + }); + + var action3 = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_CANCEL_ROLEBLOCK_ACTIONS, + labels: ["cleanse", "hidden"], + run: function () { + const alcoholicVisitors = this.getVisitors().filter((p) => + p.hasEffect("Alcoholic") + ); + for (const v of alcoholicVisitors) { + v.removeEffect("Alcoholic", true); + this.blockActions(v, "alcoholic"); + } + }, + }); + + var action4 = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_KILL_WEREWOLF_VISITORS_ENQUEUE, + run: function () { + for (let action of this.game.actions[0]) + if ( + action.target == this.actor && + action.actor.role.name == "Werewolf" && + action.priority > this.priority && + !action.hasLabel("hidden") + ) { + if (!this.actor.role.data.werewolfVisitors) + this.actor.role.data.werewolfVisitors = []; + this.actor.role.data.werewolfVisitors.push(action.actor); + } + }, + }); + + var action5 = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_KILL_DEFAULT, + power: 2, + labels: ["kill", "hidden"], + run: function () { + var werewolfVisitors = this.actor.role.data.werewolfVisitors; + if (werewolfVisitors) { + for (let visitor of werewolfVisitors) + if (this.dominates(visitor)) visitor.kill("basic", this.actor); + this.actor.role.data.werewolfVisitors = []; + } + }, + }); + + this.game.queueAction(action); + this.game.queueAction(action2); + this.game.queueAction(action3); + this.game.queueAction(action4); + this.game.queueAction(action5); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/Commuting.js b/Games/types/Mafia/roles/cards/Commuting.js index 2aaf1cc34..d1e5b0f79 100644 --- a/Games/types/Mafia/roles/cards/Commuting.js +++ b/Games/types/Mafia/roles/cards/Commuting.js @@ -1,10 +1,11 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_BLOCK_VISITORS } = require("../../const/Priority"); module.exports = class Commuting extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_BLOCK_VISITORS, @@ -27,5 +28,39 @@ module.exports = class Commuting extends Card { }, }, ]; +*/ + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_BLOCK_VISITORS, + labels: ["block", "hidden"], + run: function () { + for (let action of this.game.actions[0]) { + if (action.target == this.actor && !action.hasLabel("hidden")) { + for (let _action of this.game.actions[0]) { + if ( + _action.priority > this.priority && + !_action.hasLabel("absolute") + ) { + _action.cancelActor(action.actor); + } + } + } + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/Complex.js b/Games/types/Mafia/roles/cards/Complex.js index ac23f4ae2..c6ad8e447 100644 --- a/Games/types/Mafia/roles/cards/Complex.js +++ b/Games/types/Mafia/roles/cards/Complex.js @@ -1,11 +1,12 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const Player = require("../../../../core/Player"); const { PRIORITY_NIGHT_ROLE_BLOCKER } = require("../../const/Priority"); module.exports = class Complex extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_NIGHT_ROLE_BLOCKER, @@ -57,5 +58,60 @@ module.exports = class Complex extends Card { }, }, ]; +*/ + this.listeners = { + state: function (stateInfo) { + if (!stateInfo.name.match(/Night/)) { + return; + } + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_NIGHT_ROLE_BLOCKER, + labels: ["block", "hidden"], + run: function () { + if (!this.actor.alive) return; + + for (let action of this.game.actions[0]) { + if (action.hasLabel("absolute")) { + continue; + } + if (action.hasLabel("mafia")) { + continue; + } + if (action.hasLabel("hidden")) { + continue; + } + + let toCheck = action.target; + if (!Array.isArray(action.target)) { + toCheck = [action.target]; + } + + if ( + action.actors.indexOf(this.actor) != -1 && + !action.hasLabel("hidden") && + action.target && + toCheck[0] instanceof Player + ) { + for (let y = 0; y < toCheck.length; y++) { + if (this.isVanillaRole(toCheck[y])) { + if ( + action.priority > this.priority && + !action.hasLabel("absolute") + ) { + action.cancelActor(this.actor); + break; + } + } + } + } + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/CondemnReveal.js b/Games/types/Mafia/roles/cards/CondemnReveal.js index cf4190333..ded602d70 100644 --- a/Games/types/Mafia/roles/cards/CondemnReveal.js +++ b/Games/types/Mafia/roles/cards/CondemnReveal.js @@ -28,8 +28,12 @@ module.exports = class CondemnReveal extends Card { let wrongPlayer = Random.randArrayVal(wrongPlayers); this.target.setTempAppearance("reveal", wrongPlayer.role.name); } - - this.target.role.revealToAll(); + for (let player of this.game.players) { + if (player.faction == this.actor.faction) { + this.target.role.revealToPlayer(player); + } + } + //this.target.role.revealToAll(); }, }, }, diff --git a/Games/types/Mafia/roles/cards/ConvertVisitors.js b/Games/types/Mafia/roles/cards/ConvertVisitors.js index 632193862..681f134b5 100644 --- a/Games/types/Mafia/roles/cards/ConvertVisitors.js +++ b/Games/types/Mafia/roles/cards/ConvertVisitors.js @@ -1,10 +1,11 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_CONVERT_DEFAULT } = require("../../const/Priority"); module.exports = class ConvertVisitors extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_CONVERT_DEFAULT, @@ -21,5 +22,27 @@ module.exports = class ConvertVisitors extends Card { }, }, ]; +*/ + this.listeners = { + state: function (stateInfo) { + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_CONVERT_DEFAULT, + labels: ["convert", "hidden"], + run: function () { + let visitors = this.getVisitors(); + for (let visitor of visitors) + if (this.dominates(visitor)) visitor.setRole("Cultist"); + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/CountEvilVotes.js b/Games/types/Mafia/roles/cards/CountEvilVotes.js index e8249b9ba..724e08c3e 100644 --- a/Games/types/Mafia/roles/cards/CountEvilVotes.js +++ b/Games/types/Mafia/roles/cards/CountEvilVotes.js @@ -1,12 +1,13 @@ const Card = require("../../Card"); -const Action = require("../../../../core/Action"); +//const Action = require("../../../../core/Action"); +const Action = require("../../Action"); const { PRIORITY_DAY_DEFAULT } = require("../../const/Priority"); const { PRIORITY_INVESTIGATIVE_DEFAULT } = require("../../const/Priority"); module.exports = class CountEvilVotes extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_DAY_DEFAULT + 1, @@ -110,13 +111,116 @@ module.exports = class CountEvilVotes extends Card { }, }, ]; - +*/ this.listeners = { state: function (stateInfo) { if (!this.player.alive) return; if (stateInfo.name.match(/Day/)) { this.player.role.data.VotingLog = []; + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_DAY_DEFAULT + 1, + labels: ["hidden", "absolute"], + run: function () { + let villageMeeting = this.game.getMeetingByName("Village"); + if (this.game.RoomOne.includes(this.actor)) { + villageMeeting = this.game.getMeetingByName("Room 1"); + } else if (this.game.RoomTwo.includes(this.actor)) { + villageMeeting = this.game.getMeetingByName("Room 2"); + } + if (!villageMeeting) return; + //New code + const voteCounts = Object.values(villageMeeting.votes).reduce( + (acc, vote) => { + acc[vote] = (acc[vote] || 0) + 1; + return acc; + }, + {} + ); + + const minVotes = Math.min(...Object.values(voteCounts)); + const maxVotes = Math.max(...Object.values(voteCounts)); + let villageVotes = this.actor.role.data.VotingLog; + this.actor.role.data.evilVoted = false; + let maxTarget; + let tied = false; + //this.actor.queueAlert(`${maxVotes}`); + + for (let x = 0; x < villageVotes.length; x++) { + if ( + this.game.getRoleAlignment( + villageVotes[x].voter.getRoleAppearance().split(" (")[0] + ) == "Cult" || + this.game.getRoleAlignment( + villageVotes[x].voter.getRoleAppearance().split(" (")[0] + ) == "Mafia" + ) { + if (voteCounts[villageVotes[x].target] == maxVotes) { + if (maxTarget == null) { + maxTarget = villageVotes[x].target; + } else if (villageVotes[x].target != maxTarget) { + tied = true; + } + this.actor.role.data.evilVoted = true; + this.actor.role.data.voteTied = tied; + } + } + } + }, + }); + + this.game.queueAction(action); + } + + if (stateInfo.name.match(/Night/)) { + var action2 = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_INVESTIGATIVE_DEFAULT, + labels: ["investigate"], + run: function () { + let outcome = "No"; + var alert; + + if (this.actor.role.data.voteTied == true) { + alert = `:invest: Their was no Majority Vote yesterday!`; + this.actor.queueAlert(alert); + return; + } + + if (this.actor.role.data.VotingLog.length <= 0) return; + if (this.actor.hasEffect("FalseMode")) { + if (this.actor.role.data.evilVoted) { + this.actor.role.data.evilVoted = false; + } else { + this.actor.role.data.evilVoted = true; + } + } + + if (this.actor.role.data.evilVoted == true) { + alert = `:invest: You learn that Evil Players voted with the Majority yesterday!`; + } else { + alert = `:invest: You learn that no evil players voted with the Majority yesterday!`; + } + + if ( + this.game.RoomOne.length > 0 && + this.game.RoomTwo.length > 0 + ) { + if (this.actor.role.data.evilVoted == true) { + alert = `:invest: You learn that Evil Players voted with the Majority in the Room you were in yesterday!`; + } else { + alert = `:invest: You learn that no evil players voted with the Majority in the Room you were in yesterday!`; + } + } + + this.actor.queueAlert(alert); + }, + }); + this.game.queueAction(action2); } }, vote: function (vote) { diff --git a/Games/types/Mafia/roles/cards/CountVisitors.js b/Games/types/Mafia/roles/cards/CountVisitors.js index 864550556..69323e2ea 100644 --- a/Games/types/Mafia/roles/cards/CountVisitors.js +++ b/Games/types/Mafia/roles/cards/CountVisitors.js @@ -1,11 +1,12 @@ const Card = require("../../Card"); const Random = require("../../../../../lib/Random"); +const Action = require("../../Action"); const { PRIORITY_INVESTIGATIVE_DEFAULT } = require("../../const/Priority"); module.exports = class CountVisitors extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_INVESTIGATIVE_DEFAULT, @@ -34,5 +35,45 @@ module.exports = class CountVisitors extends Card { }, }, ]; +*/ + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_INVESTIGATIVE_DEFAULT, + labels: ["hidden", "absolute"], + run: function () { + let visitors = this.actor.role.data.visitors; + if (visitors) { + let unique = new Set(visitors); + + if (this.actor.hasEffect("FalseMode")) { + let num = 0; + if (unique.size == 0) num = 1; + else num = 0; + this.actor.queueAlert( + `:visited: You were visited by ${num} people last night.` + ); + return; + } + + this.actor.queueAlert( + `:visited: You were visited by ${unique.size} people last night.` + ); + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/CountWrongReveals.js b/Games/types/Mafia/roles/cards/CountWrongReveals.js index 8090c8732..de58292ce 100644 --- a/Games/types/Mafia/roles/cards/CountWrongReveals.js +++ b/Games/types/Mafia/roles/cards/CountWrongReveals.js @@ -1,4 +1,5 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT, } = require("../../const/Priority"); @@ -6,7 +7,7 @@ const { module.exports = class CountWrongReveals extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT - 1, @@ -59,5 +60,69 @@ module.exports = class CountWrongReveals extends Card { }, }, ]; +*/ + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + if (!stateInfo.name.match(/Night/)) { + return; + } + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT - 1, + labels: ["investigate"], + run: function () { + if (!this.actor.alive) return; + //let visitors; + let problemCount = 0; + let role; + let appearRole; + let isFalse; + + let players = this.game.players.filter((p) => p.role); + for (let x = 0; x < players.length; x++) { + //visitors = this.getVisits(players[x]); + role = players[x].role.name; + appearRole = players[x].getRoleAppearance().split(" (")[0]; + isFalse = false; + + for (let action of this.game.actions[0]) { + if ( + action.actor == players[x] && + action.hasLabel("investigate") && + players[x].hasEffect("FalseMode") + ) { + isFalse = true; + } + } + + if (role != appearRole) { + problemCount = problemCount + 1; + } + if (isFalse) { + problemCount = problemCount + 1; + } + } + + if (this.actor.hasEffect("FalseMode")) { + if (problemCount == 0) { + problemCount = 1; + } else { + problemCount = 0; + } + } + + this.actor.queueAlert( + `You learn that ${problemCount} problems occured during the night.` + ); + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/CultWinsIfNoCondemn.js b/Games/types/Mafia/roles/cards/CultWinsIfNoCondemn.js index 77ecca07c..fb8f136bd 100644 --- a/Games/types/Mafia/roles/cards/CultWinsIfNoCondemn.js +++ b/Games/types/Mafia/roles/cards/CultWinsIfNoCondemn.js @@ -10,7 +10,10 @@ module.exports = class CultWinsIfNoCondemn extends Card { priority: PRIORITY_DAY_EFFECT_DEFAULT + 1, run: function () { if (!this.actor.alive) return; - if (this.game.getStateName() == "Day") { + if ( + this.game.getStateName() == "Day" || + this.game.getStateName() == "Dusk" + ) { let alivePlayers = this.game.players.filter((p) => p.role); for (let x = 0; x < alivePlayers.length; x++) { diff --git a/Games/types/Mafia/roles/cards/DaySlasher.js b/Games/types/Mafia/roles/cards/DaySlasher.js index 878f166ae..79c9c133d 100644 --- a/Games/types/Mafia/roles/cards/DaySlasher.js +++ b/Games/types/Mafia/roles/cards/DaySlasher.js @@ -1,10 +1,11 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_ITEM_GIVER_DEFAULT } = require("../../const/Priority"); module.exports = class DaySlasher extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_ITEM_GIVER_DEFAULT, @@ -20,5 +21,31 @@ module.exports = class DaySlasher extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_ITEM_GIVER_DEFAULT, + labels: ["hidden", "absolute"], + run: function () { + if (!this.actor.alive) return; + + if (!this.hasVisitors()) { + this.actor.holdItem("Knife", { reveal: false }); + this.actor.queueGetItemAlert("Knife"); + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/DisguiseAsTarget.js b/Games/types/Mafia/roles/cards/DisguiseAsTarget.js index 8ac17ae5c..886c67fbf 100644 --- a/Games/types/Mafia/roles/cards/DisguiseAsTarget.js +++ b/Games/types/Mafia/roles/cards/DisguiseAsTarget.js @@ -1,11 +1,12 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_ITEM_GIVER_DEFAULT } = require("../../const/Priority"); const { addArticle } = require("../../../../core/Utils"); module.exports = class DisguiseAsTarget extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_ITEM_GIVER_DEFAULT, @@ -28,5 +29,41 @@ module.exports = class DisguiseAsTarget extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_ITEM_GIVER_DEFAULT, + labels: ["giveItem", "suit", "hidden", "absolute"], + run: function () { + if (!this.actor.alive) return; + + let targets = this.getVisits(this.actor); + for (let target of targets) { + let role = target.getAppearance("investigate", true); + this.actor.queueAlert( + `:mask: After studying ${ + target.name + }, you learn to act like ${addArticle(role)}.` + ); + this.actor.holdItem("Suit", { type: role }); + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/Disloyal.js b/Games/types/Mafia/roles/cards/Disloyal.js index bb348b674..4b357c64f 100644 --- a/Games/types/Mafia/roles/cards/Disloyal.js +++ b/Games/types/Mafia/roles/cards/Disloyal.js @@ -1,11 +1,12 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const Player = require("../../../../core/Player"); const { PRIORITY_NIGHT_ROLE_BLOCKER } = require("../../const/Priority"); module.exports = class Disloyal extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_NIGHT_ROLE_BLOCKER - 1, @@ -57,5 +58,62 @@ module.exports = class Disloyal extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_NIGHT_ROLE_BLOCKER - 1, + labels: ["block", "hidden", "absolute"], + run: function () { + if (!this.actor.alive) return; + + for (let action of this.game.actions[0]) { + if (action.hasLabel("absolute")) { + continue; + } + if (action.hasLabel("mafia")) { + continue; + } + if (action.hasLabel("hidden")) { + continue; + } + + let toCheck = action.target; + if (!Array.isArray(action.target)) { + toCheck = [action.target]; + } + + if ( + action.actors.indexOf(this.actor) != -1 && + !action.hasLabel("hidden") && + action.target && + toCheck[0] instanceof Player + ) { + for (let y = 0; y < toCheck.length; y++) { + if (toCheck[y].role.alignment == this.actor.role.alignment) { + if ( + action.priority > this.priority && + !action.hasLabel("absolute") + ) { + action.cancelActor(this.actor); + break; + } + } + } + } + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/Dream.js b/Games/types/Mafia/roles/cards/Dream.js index 9afef5eb1..aa6bfc3f6 100644 --- a/Games/types/Mafia/roles/cards/Dream.js +++ b/Games/types/Mafia/roles/cards/Dream.js @@ -1,4 +1,5 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const Random = require("../../../../../lib/Random"); const { PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT, @@ -7,7 +8,7 @@ const { module.exports = class Dream extends Card { constructor(role) { super(role); - + /* this.actions = [ { labels: ["dream", "hidden"], @@ -63,5 +64,74 @@ module.exports = class Dream extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + labels: ["dream", "hidden"], + priority: PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT, + run: function () { + if (!this.actor.alive) return; + + var aliveExceptSelf = this.game.players.filter( + (p) => p.alive && p != this.actor + ); + if (aliveExceptSelf.length < 3) return; + + if (this.hasVisitors()) return; + + var dream; + let evilPlayers = aliveExceptSelf.filter( + (p) => p.role.alignment == "Mafia" || p.role.alignment == "Cult" + ); + let village = aliveExceptSelf.filter( + (p) => p.role.alignment == "Village" + ); + + if (this.actor.hasEffect("FalseMode")) { + let temp = evilPlayers; + evilPlayers = village; + village = temp; + } + + if (village.length == 0) { + dream = `:dream: You had a dream that you can trust no one but yourself…`; + } else if (evilPlayers.length == 0 || Random.randInt(0, 1) == 0) { + const chosenOne = Random.randArrayVal(village); + dream = `:dream: You had a dream that you can trust ${chosenOne.name}…`; + } else { + // guarantee no repeats in dream + var chosenThree = [Random.randArrayVal(evilPlayers)]; + aliveExceptSelf = aliveExceptSelf.filter( + (p) => p !== chosenThree[0] + ); + aliveExceptSelf = Random.randomizeArray(aliveExceptSelf); + chosenThree.push(aliveExceptSelf[0]); + chosenThree.push(aliveExceptSelf[1]); + chosenThree = Random.randomizeArray(chosenThree); + dream = `:dream: You had a dream where at least one of ${chosenThree[0].name}, ${chosenThree[1].name}, and ${chosenThree[2].name} is evil…`; + if (this.actor.hasEffect("FalseMode")) { + let wrongPlayers = Random.randomizeArray(evilPlayers); + dream = `:dream: You had a dream where at least one of ${wrongPlayers[0].name}, ${wrongPlayers[1].name}, and ${wrongPlayers[2].name} is evil…`; + } + } + + this.actor.queueAlert(dream); + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/Eavesdrop.js b/Games/types/Mafia/roles/cards/Eavesdrop.js index e0daa8d9d..a472fdd82 100644 --- a/Games/types/Mafia/roles/cards/Eavesdrop.js +++ b/Games/types/Mafia/roles/cards/Eavesdrop.js @@ -30,7 +30,10 @@ module.exports = class Eavesdrop extends Card { { labels: ["hidden", "absolute"], run: function () { - if (this.game.getStateName() === "Night") + if ( + this.game.getStateName() === "Night" || + this.game.getStateName() === "Dawn" + ) delete this.actor.role.data.stalk; }, }, diff --git a/Games/types/Mafia/roles/cards/EnqueueVisitors.js b/Games/types/Mafia/roles/cards/EnqueueVisitors.js index c176935d5..ea071b37f 100644 --- a/Games/types/Mafia/roles/cards/EnqueueVisitors.js +++ b/Games/types/Mafia/roles/cards/EnqueueVisitors.js @@ -1,9 +1,11 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_VISITORS_ENQUEUE } = require("../../const/Priority"); module.exports = class EnqueueVisitors extends Card { constructor(role) { super(role); + /* this.actions = [ { priority: PRIORITY_VISITORS_ENQUEUE, @@ -30,5 +32,41 @@ module.exports = class EnqueueVisitors extends Card { }, }, ]; +*/ + this.listeners = { + state: function (stateInfo) { + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_VISITORS_ENQUEUE, + labels: ["absolute", "hidden"], + run: function () { + this.actor.role.data.visitors = []; + + for (let action of this.game.actions[0]) { + let toCheck = action.target; + if (!Array.isArray(action.target)) { + toCheck = [action.target]; + } + + for (let target of toCheck) { + if (target == this.actor && !action.hasLabel("hidden")) { + if (!this.actor.role.data.visitors) + this.actor.role.data.visitors = []; + + this.actor.role.data.visitors.push(action.actor); + } + } + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/EvilDirection.js b/Games/types/Mafia/roles/cards/EvilDirection.js index d10a10fca..cd3c2bfd6 100644 --- a/Games/types/Mafia/roles/cards/EvilDirection.js +++ b/Games/types/Mafia/roles/cards/EvilDirection.js @@ -1,5 +1,6 @@ const Card = require("../../Card"); const Random = require("../../../../../lib/Random"); +const Action = require("../../Action"); const { PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT, } = require("../../const/Priority"); @@ -7,7 +8,7 @@ const { module.exports = class EvilDirection extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT - 10, @@ -85,5 +86,96 @@ module.exports = class EvilDirection extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT - 10, + labels: ["investigate"], + run: function () { + if (this.actor.role.hasInfo) return; + if (!this.actor.alive) return; + + let alive = this.game.alivePlayers(); + var evilPlayers = alive.filter( + (p) => + this.game.getRoleAlignment( + p.getRoleAppearance().split(" (")[0] + ) == "Cult" || + this.game.getRoleAlignment( + p.getRoleAppearance().split(" (")[0] + ) == "Mafia" + ); + + if (evilPlayers.length <= 0) { + this.actor.queueAlert( + `There wasn't enough evil players for your abilty to work!` + ); + return; + } + + var evilTarget = Random.randArrayVal(evilPlayers); + var indexOfTarget = alive.indexOf(this.actor); + var rightIdx; + var leftIdx; + var leftAlign; + var rightAlign; + var distance = 0; + var found = false; + let info = ""; + + for (let x = 0; x < alive.length; x++) { + leftIdx = + (indexOfTarget - distance - 1 + alive.length) % alive.length; + rightIdx = (indexOfTarget + distance + 1) % alive.length; + leftAlign = this.game.getRoleAlignment( + alive[leftIdx].getRoleAppearance().split(" (")[0] + ); + rightAlign = this.game.getRoleAlignment( + alive[rightIdx].getRoleAppearance().split(" (")[0] + ); + + if (rightAlign == "Cult" || rightAlign == "Mafia") { + found = true; + info = "Below"; + break; + } else if (leftAlign == "Cult" || leftAlign == "Mafia") { + found = true; + info = "Above"; + break; + } else { + distance = x; + } + } + + if (this.actor.hasEffect("FalseMode")) { + if (distance == "Above") { + distance = "Below"; + } else { + distance = "Above"; + } + } + + this.actor.queueAlert( + `You learn that the closest Evil player to you is ${info} you on the player list!` + ); + + this.actor.role.hasInfo = true; + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/EvilDistance.js b/Games/types/Mafia/roles/cards/EvilDistance.js index cf6ffd490..ebb8b9f0f 100644 --- a/Games/types/Mafia/roles/cards/EvilDistance.js +++ b/Games/types/Mafia/roles/cards/EvilDistance.js @@ -1,4 +1,5 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const Random = require("../../../../../lib/Random"); const { PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT, @@ -7,7 +8,7 @@ const { module.exports = class EvilDistance extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT - 10, @@ -82,5 +83,94 @@ module.exports = class EvilDistance extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT - 10, + labels: ["investigate"], + run: function () { + if (this.actor.role.hasInfo) return; + if (!this.actor.alive) return; + + let alive = this.game.alivePlayers(); + var evilPlayers = alive.filter( + (p) => + this.game.getRoleAlignment( + p.getRoleAppearance().split(" (")[0] + ) == "Cult" || + this.game.getRoleAlignment( + p.getRoleAppearance().split(" (")[0] + ) == "Mafia" + ); + + if (evilPlayers.length <= 1) { + this.actor.queueAlert( + `There wasn't enough evil players for your abilty to work!` + ); + return; + } + + var evilTarget = Random.randArrayVal(evilPlayers); + var indexOfTarget = alive.indexOf(evilTarget); + var rightIdx; + var leftIdx; + var leftAlign; + var rightAlign; + var distance = 0; + var found = false; + + for (let x = 0; x < alive.length; x++) { + leftIdx = + (indexOfTarget - distance - 1 + alive.length) % alive.length; + rightIdx = (indexOfTarget + distance + 1) % alive.length; + leftAlign = this.game.getRoleAlignment( + alive[leftIdx].getRoleAppearance().split(" (")[0] + ); + rightAlign = this.game.getRoleAlignment( + alive[rightIdx].getRoleAppearance().split(" (")[0] + ); + + if (rightAlign == "Cult" || rightAlign == "Mafia") { + found = true; + break; + } else if (leftAlign == "Cult" || leftAlign == "Mafia") { + found = true; + break; + } else { + distance = x; + } + } + + if (this.actor.hasEffect("FalseMode")) { + if (distance == 0) { + distance = 1; + } else { + distance = distance - 1; + } + } + + this.actor.queueAlert( + `You learn that there is ${distance} players between 2 of the evil players in the game!` + ); + + this.actor.role.hasInfo = true; + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/EvilPairs.js b/Games/types/Mafia/roles/cards/EvilPairs.js index 1849d2ca2..d42736270 100644 --- a/Games/types/Mafia/roles/cards/EvilPairs.js +++ b/Games/types/Mafia/roles/cards/EvilPairs.js @@ -1,4 +1,5 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT, } = require("../../const/Priority"); @@ -6,7 +7,7 @@ const { module.exports = class EvilPairs extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT - 10, @@ -58,5 +59,71 @@ module.exports = class EvilPairs extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT - 10, + labels: ["investigate"], + run: function () { + if (this.actor.role.hasInfo) return; + if (!this.actor.alive) return; + + let alive = this.game.alivePlayers(); + var evilPlayers = alive.filter( + (p) => + this.game.getRoleAlignment( + p.getRoleAppearance().split(" (")[0] + ) == "Cult" || + this.game.getRoleAlignment( + p.getRoleAppearance().split(" (")[0] + ) == "Mafia" + ); + + var evilPair = 0; + var index; + var rightIdx; + var neighborAlignment; + for (let x = 0; x < evilPlayers.length; x++) { + index = alive.indexOf(evilPlayers[x]); + rightIdx = (index + 1) % alive.length; + neighborAlignment = this.game.getRoleAlignment( + alive[rightIdx].getRoleAppearance().split(" (")[0] + ); + + if (neighborAlignment == "Cult" || neighborAlignment == "Mafia") { + evilPair = evilPair + 1; + } + } + + if (this.actor.hasEffect("FalseMode")) { + if (evilPair == 0) { + evilPair = 1; + } else { + evilPair = evilPair - 1; + } + } + + this.actor.queueAlert( + `After Evaluating the neighborhood you learn that there is ${evilPair} pairs of evil players!` + ); + this.actor.role.hasInfo = true; + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/ForageItem.js b/Games/types/Mafia/roles/cards/ForageItem.js index 9822248ce..643b8b8e1 100644 --- a/Games/types/Mafia/roles/cards/ForageItem.js +++ b/Games/types/Mafia/roles/cards/ForageItem.js @@ -1,11 +1,12 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const Random = require("../../../../../lib/Random"); const { PRIORITY_ITEM_GIVER_DEFAULT } = require("../../const/Priority"); module.exports = class ForageItem extends Card { constructor(role) { super(role); - + /* this.actions = [ { labels: ["giveItem"], @@ -40,6 +41,7 @@ module.exports = class ForageItem extends Card { }, }, ]; + */ this.listeners = { death: function (player, killer, deathType) { if (player === this.player && killer && deathType != "condemn") { @@ -47,6 +49,52 @@ module.exports = class ForageItem extends Card { killer.holdItem("Gun", { reveal: true }); } }, + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + labels: ["giveItem"], + priority: PRIORITY_ITEM_GIVER_DEFAULT, + run: function () { + if (!this.actor.alive) return; + + //if (this.game.getStateName() != "Night") return; + + if (this.getVisitors().length > 0) { + return; + } + + var items = [ + "Gun", + "Armor", + "Bomb", + "Knife", + "Whiskey", + "Crystal", + "Key", + "Candle", + "Falcon", + "Tract", + "Syringe", + "Envelope", + ]; + var itemToGet = Random.randArrayVal(items); + + this.actor.holdItem(itemToGet); + this.actor.queueGetItemAlert(itemToGet); + }, + }); + + this.game.queueAction(action); + }, }; } }; diff --git a/Games/types/Mafia/roles/cards/FrustratedCondemnation.js b/Games/types/Mafia/roles/cards/FrustratedCondemnation.js index 5046a08d0..370c7234c 100644 --- a/Games/types/Mafia/roles/cards/FrustratedCondemnation.js +++ b/Games/types/Mafia/roles/cards/FrustratedCondemnation.js @@ -1,6 +1,6 @@ const Card = require("../../Card"); const Action = require("../../../../core/Action"); -const { PRIORITY_DAY_DEFAULT } = require("../../const/Priority"); +const { PRIORITY_OVERTHROW_VOTE } = require("../../const/Priority"); module.exports = class FrustratedCondemnation extends Card { constructor(role) { @@ -11,12 +11,13 @@ module.exports = class FrustratedCondemnation extends Card { }; this.immunity["condemn"] = 3; + /* this.actions = [ { priority: PRIORITY_DAY_DEFAULT + 1, labels: ["hidden", "absolute"], run: function () { - if (this.game.getStateName() != "Day") return; + if (this.game.getStateName() != "Day" && this.game.getStateName() != "Dusk") return; let villageMeeting = this.game.getMeetingByName("Village"); @@ -57,7 +58,7 @@ module.exports = class FrustratedCondemnation extends Card { if (!targeted) { return; } - */ + let action = new Action({ actor: this.actor, @@ -76,5 +77,93 @@ module.exports = class FrustratedCondemnation extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Day/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_OVERTHROW_VOTE - 3, + labels: ["hidden", "absolute"], + run: function () { + //if (this.game.getStateName() != "Day" && this.game.getStateName() != "Dusk") return; + + let villageMeeting = this.game.getMeetingByName("Village"); + + //New code + const voteCounts = Object.values(villageMeeting.votes).reduce( + (acc, vote) => { + acc[vote] = (acc[vote] || 0) + 1; + return acc; + }, + {} + ); + + const minVotes = Math.min(...Object.values(voteCounts)); + const maxVotes = Math.max(...Object.values(voteCounts)); + + if ( + voteCounts[this.actor.id] !== minVotes || + voteCounts[this.actor.id] === maxVotes || + voteCounts[this.actor.id] === 0 + ) { + return; + } + + /* Old code in case new causes any problems + if (villageMeeting.finalTarget === this.actor) { + return; + } + + // check if it was a target + let targeted = false; + for (let key in villageMeeting.votes) { + let target = villageMeeting.votes[key]; + if (target === this.actor.id) { + targeted = true; + break; + } + } + if (!targeted) { + return; + } + */ + for (let action of this.game.actions[0]) { + if (action.hasLabel("condemn") && !action.hasLabel("overthrow")) { + // Only one village vote can be overthrown + action.cancel(true); + break; + } + } + + let action = new Action({ + actor: this.actor, + target: this.actor, + game: this.game, + labels: ["kill", "frustration", "hidden"], + power: 3, + run: function () { + this.game.sendAlert( + `${this.target.name} feels immensely frustrated!` + ); + if (this.dominates()) this.target.kill("basic", this.actor); + }, + }); + action.do(); + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/FungalSpores.js b/Games/types/Mafia/roles/cards/FungalSpores.js index 049218308..c94820d0f 100644 --- a/Games/types/Mafia/roles/cards/FungalSpores.js +++ b/Games/types/Mafia/roles/cards/FungalSpores.js @@ -9,11 +9,41 @@ module.exports = class FungalSpores extends Card { Spore: { states: ["Night"], flags: ["voting"], + labels: ["giveEffect"], action: { priority: PRIORITY_EFFECT_GIVER_DEFAULT, run: function () { // set target this.actor.role.data.currentTarget = this.target; + + if (this.actor.role.data.currentFungus == null) return; + else if (this.actor.role.data.currentFungus == "Thrush") { + if (this.dominates(this.target)) { + this.target.giveEffect("Silenced", 1); + } + } else if (this.actor.role.data.currentFungus == "Aspergillus") { + if (this.dominates(this.target)) { + this.target.giveEffect("Fiddled", 1); + } + } else if (this.actor.role.data.currentFungus == "Cataracts") { + if (this.dominates(this.target)) { + this.target.giveEffect("Blind", 1); + } + } else if (this.actor.role.data.currentFungus == "Hallucinogens") { + if (this.dominates(this.target)) { + this.target.giveEffect("Scrambled", 1); + } + } + + // set cooldown + var fungus = this.actor.role.data.currentFungus; + if (this.actor.role.data.fungusCounter) { + this.actor.role.data.fungusCounter[fungus] = + this.actor.role.data.fungusCooldown; + } + + delete this.actor.role.data.currentFungus; + delete this.actor.role.data.currentTarget; }, }, }, @@ -24,14 +54,14 @@ module.exports = class FungalSpores extends Card { // needs to insert every state // targets: currentFungusList, action: { - priority: PRIORITY_EFFECT_GIVER_DEFAULT, + priority: PRIORITY_EFFECT_GIVER_DEFAULT - 1, run: function () { this.actor.role.data.currentFungus = this.target; }, }, }, }; - + /* this.actions = [ { priority: PRIORITY_EFFECT_GIVER_DEFAULT, @@ -164,7 +194,7 @@ module.exports = class FungalSpores extends Card { }, }, ]; - +*/ this.listeners = { roleAssigned: function (player) { if (player !== this.player) { diff --git a/Games/types/Mafia/roles/cards/GainGunIfMafiaAbstained.js b/Games/types/Mafia/roles/cards/GainGunIfMafiaAbstained.js index 80caa6c24..726bcf164 100644 --- a/Games/types/Mafia/roles/cards/GainGunIfMafiaAbstained.js +++ b/Games/types/Mafia/roles/cards/GainGunIfMafiaAbstained.js @@ -1,10 +1,11 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_ITEM_GIVER_DEFAULT } = require("../../const/Priority"); module.exports = class GainGunIfMafiaAbstained extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_ITEM_GIVER_DEFAULT, @@ -32,6 +33,7 @@ module.exports = class GainGunIfMafiaAbstained extends Card { }, }, ]; + */ this.listeners = { roleAssigned: function (player) { if (player !== this.player) { @@ -40,6 +42,41 @@ module.exports = class GainGunIfMafiaAbstained extends Card { this.player.data.gainedGun = false; }, + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + if (!stateInfo.name.match(/Night/)) { + return; + } + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_ITEM_GIVER_DEFAULT, + labels: ["hidden", "absolute"], + run: function () { + if (!this.actor.alive) return; + + if (this.actor.role.data.gainedGun) return; + + let mafiaKilled = false; + for (let action of this.game.actions[0]) { + if (action.hasLabels(["kill", "mafia"])) { + mafiaKilled = true; + break; + } + } + + if (!mafiaKilled) { + this.actor.holdItem("Gun", { reveal: true }); + this.actor.queueGetItemAlert("Gun"); + this.actor.role.data.gainedGun = true; + } + }, + }); + + this.game.queueAction(action); + }, }; } }; diff --git a/Games/types/Mafia/roles/cards/GainKnifeIfVisitedNonCult.js b/Games/types/Mafia/roles/cards/GainKnifeIfVisitedNonCult.js index db244bbbf..cdada016d 100644 --- a/Games/types/Mafia/roles/cards/GainKnifeIfVisitedNonCult.js +++ b/Games/types/Mafia/roles/cards/GainKnifeIfVisitedNonCult.js @@ -1,10 +1,11 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_ITEM_GIVER_DEFAULT } = require("../../const/Priority"); module.exports = class GainKnifeIfVisitedNonCult extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_ITEM_GIVER_DEFAULT, @@ -27,5 +28,40 @@ module.exports = class GainKnifeIfVisitedNonCult extends Card { }, }, ]; +*/ + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_ITEM_GIVER_DEFAULT, + labels: ["hidden", "absolute"], + run: function () { + if (!this.actor.alive) return; + + let visitors = this.getVisitors(this.actor); + let hasNonCultVisitors = + visitors.filter((v) => v.role.alignment !== "Cult")?.length > 0; + + if (!hasNonCultVisitors) { + return; + } + + this.actor.holdItem("Knife"); + this.actor.queueGetItemAlert("Knife"); + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/GiveVisitorsGuns.js b/Games/types/Mafia/roles/cards/GiveVisitorsGuns.js index 32d026532..073b4afc8 100644 --- a/Games/types/Mafia/roles/cards/GiveVisitorsGuns.js +++ b/Games/types/Mafia/roles/cards/GiveVisitorsGuns.js @@ -1,10 +1,11 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_EFFECT_GIVER_DEFAULT } = require("../../const/Priority"); module.exports = class GiveVisitorsGuns extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_EFFECT_GIVER_DEFAULT, @@ -26,5 +27,37 @@ module.exports = class GiveVisitorsGuns extends Card { }, }, ]; +*/ + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_EFFECT_GIVER_DEFAULT, + labels: ["giveItem", "gun"], + run: function () { + if (!this.actor.alive) { + return; + } + + let visitors = this.getVisitors(); + visitors.map((p) => { + p.holdItem("Gun"); + p.queueGetItemAlert("Gun"); + }); + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/HideBehindPlayer.js b/Games/types/Mafia/roles/cards/HideBehindPlayer.js index 0d6fbb79e..10be5981c 100644 --- a/Games/types/Mafia/roles/cards/HideBehindPlayer.js +++ b/Games/types/Mafia/roles/cards/HideBehindPlayer.js @@ -1,4 +1,5 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_NIGHT_SAVER, PRIORITY_KILL_DEFAULT, @@ -29,14 +30,37 @@ module.exports = class HideBehindPlayer extends Card { return; } - if (!stateInfo.name.match(/Day/)) { - return; + if (stateInfo.name.match(/Day/)) { + delete this.hideBehind; } + if (stateInfo.name.match(/Night/)) { + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_KILL_DEFAULT, + labels: ["kill", "hidden", "absolute"], + run: function () { + if (!this.actor.alive) return; + + let visitors = this.getVisitors(); + for (let v of visitors) { + if (v == this.actor.role.hideBehind) { + // skip the dominates check, this kill is absolute + this.actor.kill("eaten", v); + this.actor.giveEffect("ExtraLife", this.actor); + this.actor.queueAlert( + "You gained an extra life from hiding correctly." + ); + } + } + }, + }); - delete this.hideBehind; + this.game.queueAction(action); + } }, }; - + /* this.actions = [ { priority: PRIORITY_KILL_DEFAULT, @@ -60,5 +84,6 @@ module.exports = class HideBehindPlayer extends Card { }, }, ]; +*/ } }; diff --git a/Games/types/Mafia/roles/cards/IdentityStealer.js b/Games/types/Mafia/roles/cards/IdentityStealer.js index f1ce3253e..c6d801f16 100644 --- a/Games/types/Mafia/roles/cards/IdentityStealer.js +++ b/Games/types/Mafia/roles/cards/IdentityStealer.js @@ -1,4 +1,5 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_IDENTITY_STEALER, PRIORITY_IDENTITY_STEALER_BLOCK, @@ -33,6 +34,7 @@ module.exports = class IdentityStealer extends Card { }, }, }; + /* this.actions = [ { priority: PRIORITY_IDENTITY_STEALER_BLOCK, @@ -54,6 +56,7 @@ module.exports = class IdentityStealer extends Card { }, }, ]; + */ this.listeners = { death: function (player, killer, deathType) { if (player == this.player) resetIdentities.bind(this)(); @@ -61,6 +64,39 @@ module.exports = class IdentityStealer extends Card { aboutToFinish: function () { resetIdentities.bind(this)(); }, + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_IDENTITY_STEALER_BLOCK, + run: function () { + if (this.game.getStateName() != "Night") return; + + var stealing = false; + var killing = false; + + for (let action of this.game.actions[0]) { + if (action.hasLabel("stealIdentity") && action.target == "Yes") + stealing = true; + else if (action.hasLabels(["kill", "mafia"])) killing = true; + } + + if (stealing && killing) + for (let action of this.game.actions[0]) + if (action.target == this.actor) action.cancel(true); + }, + }); + + this.game.queueAction(action); + }, }; } }; diff --git a/Games/types/Mafia/roles/cards/IfTrueKill.js b/Games/types/Mafia/roles/cards/IfTrueKill.js index bbf25b07e..3238631a9 100644 --- a/Games/types/Mafia/roles/cards/IfTrueKill.js +++ b/Games/types/Mafia/roles/cards/IfTrueKill.js @@ -1,5 +1,6 @@ const Card = require("../../Card"); const { PRIORITY_INVESTIGATIVE_DEFAULT } = require("../../const/Priority"); +const Action = require("../../Action"); const Random = require("../../../../../lib/Random"); const { PRIORITY_KILL_DEFAULT } = require("../../const/Priority"); @@ -144,6 +145,7 @@ module.exports = class LearnAboutPlayerAndRole extends Card { }, }, }), + /* (this.actions = [ { priority: PRIORITY_INVESTIGATIVE_DEFAULT, @@ -192,27 +194,79 @@ module.exports = class LearnAboutPlayerAndRole extends Card { }, }, ]); +*/ + (this.listeners = { + roleAssigned: function (player) { + if (player !== this.player) { + return; + } - this.listeners = { - roleAssigned: function (player) { - if (player !== this.player) { - return; - } + this.data.ConvertOptions = this.game.PossibleRoles.map( + (r) => r.split(":")[0] + ); + }, + // refresh cooldown + state: function (stateInfo) { + if (stateInfo.name.match(/Day/)) { + var ConvertOptions = this.data.ConvertOptions; + ConvertOptions.push("None"); + this.meetings["Select Role"].targets = ConvertOptions; + } + if (stateInfo.name.match(/Night/)) { + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_INVESTIGATIVE_DEFAULT, + labels: ["hidden", "absolute", "investigate"], + run: function () { + if (!this.actor.alive) return; - this.data.ConvertOptions = this.game.PossibleRoles.map( - (r) => r.split(":")[0] - ); - }, - // refresh cooldown - state: function (stateInfo) { - if (!stateInfo.name.match(/Day/)) { - return; - } - var ConvertOptions = this.data.ConvertOptions; - ConvertOptions.push("None"); - - this.meetings["Select Role"].targets = ConvertOptions; - }, - }; + let alivePlayers = this.game.players.filter((p) => p.role); + let allVisits = []; + let allVisitors = []; + + for (let x = 0; x < alivePlayers.length; x++) { + let visits = this.getVisits(alivePlayers[x]); + let visitNames = visits.map((p) => p.role); + let visitors = this.getVisitors(alivePlayers[x]); + let visitorNames = visitors.map((p) => p.role); + allVisits.push(visitNames); + allVisitors.push(visitorNames); + } + + this.actor.role.data.LastNightVisits = allVisits; + this.actor.role.data.LastNightVisitors = allVisitors; + this.actor.role.data.LastNightPlayers = alivePlayers; + }, + }); + + var action2 = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_KILL_DEFAULT - 2, + labels: ["hidden", "kill"], + run: function () { + if (!this.actor.alive) return; + if (this.actor.role.data.WasStatementTrue != true) { + return; + } + + let alivePlayers = this.game.players.filter((p) => p.role); + let goodPlayers = alivePlayers.filter( + (p) => + p.role.alignment == "Village" || + p.role.alignment == "Independent" + ); + let shuffledPlayers = Random.randomizeArray(goodPlayers); + + shuffledPlayers[0].kill("basic", this.actor); + }, + }); + + this.game.queueAction(action); + this.game.queueAction(action2); + } + }, + }); } }; diff --git a/Games/types/Mafia/roles/cards/IfVotedForceCondemn.js b/Games/types/Mafia/roles/cards/IfVotedForceCondemn.js index b6c11927a..e4a1d694b 100644 --- a/Games/types/Mafia/roles/cards/IfVotedForceCondemn.js +++ b/Games/types/Mafia/roles/cards/IfVotedForceCondemn.js @@ -1,10 +1,11 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_OVERTHROW_VOTE } = require("../../const/Priority"); module.exports = class IfVotedForceCondemn extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_OVERTHROW_VOTE - 1, @@ -34,44 +35,10 @@ module.exports = class IfVotedForceCondemn extends Card { this.actor.role.data.playerVoter.kill("condemn", this.actor); } this.actor.role.data.playerVoter = 0; - - /* Old code - if (villageMeeting.finalTarget === this.actor) { - return; - } - - // check if it was a target - let targeted = false; - for (let key in villageMeeting.votes) { - let target = villageMeeting.votes[key]; - if (target === this.actor.id) { - targeted = true; - break; - } - } - if (!targeted) { - return; - } - - let action = new Action({ - actor: this.actor, - target: this.actor, - game: this.game, - labels: ["kill", "frustration", "hidden"], - power: 3, - run: function () { - this.game.sendAlert( - `${this.target.name} feels immensely frustrated!` - ); - if (this.dominates()) this.target.kill("basic", this.actor); - }, - }); - action.do(); - */ }, }, ]; - +*/ this.listeners = { vote: function (vote) { if (vote.meeting.name === "Village" && vote.target === this.player.id) { @@ -98,6 +65,49 @@ module.exports = class IfVotedForceCondemn extends Card { */ } }, + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + if (!stateInfo.name.match(/Day/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_OVERTHROW_VOTE - 1, + labels: ["hidden", "absolute", "condemn", "overthrow"], + run: function () { + if (this.game.getStateName() != "Day") return; + + //let villageMeeting = this.game.getMeetingByName("Village"); + + if ( + !this.actor.role.data.hasBeenVoted || + this.actor.role.data.playerVoter == 0 + ) { + return; + } + + //New code + for (let action of this.game.actions[0]) { + if (action.hasLabel("condemn") && !action.hasLabel("overthrow")) { + // Only one village vote can be overthrown + action.cancel(true); + break; + } + } + + if (this.dominates(this.actor.role.data.playerVoter)) { + this.actor.role.data.playerVoter.kill("condemn", this.actor); + } + this.actor.role.data.playerVoter = 0; + }, + }); + + this.game.queueAction(action); + }, }; } }; diff --git a/Games/types/Mafia/roles/cards/ImmortalUnlessBlocked.js b/Games/types/Mafia/roles/cards/ImmortalUnlessBlocked.js index eae9c26a9..ec9551ce5 100644 --- a/Games/types/Mafia/roles/cards/ImmortalUnlessBlocked.js +++ b/Games/types/Mafia/roles/cards/ImmortalUnlessBlocked.js @@ -1,11 +1,12 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const Random = require("../../../../../lib/Random"); const { PRIORITY_NIGHT_SAVER } = require("../../const/Priority"); module.exports = class ImmortalUnlessBlocked extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_NIGHT_SAVER, @@ -19,5 +20,31 @@ module.exports = class ImmortalUnlessBlocked extends Card { }, }, ]; +*/ + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_NIGHT_SAVER, + labels: ["save"], + run: function () { + if (!this.actor.alive) return; + + this.actor.giveEffect("Immortal", 5, 1); + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/KillConverters.js b/Games/types/Mafia/roles/cards/KillConverters.js index c0af1e6da..32e042a7f 100644 --- a/Games/types/Mafia/roles/cards/KillConverters.js +++ b/Games/types/Mafia/roles/cards/KillConverters.js @@ -1,4 +1,5 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_KILL_DEFAULT } = require("../../const/Priority"); module.exports = class KillConverters extends Card { @@ -6,7 +7,7 @@ module.exports = class KillConverters extends Card { super(role); this.role.killLimit = 2; - + /* this.actions = [ { priority: PRIORITY_KILL_DEFAULT, @@ -31,5 +32,43 @@ module.exports = class KillConverters extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_KILL_DEFAULT, + labels: ["kill", "hidden"], + run: function () { + if (!this.actor.alive) return; + + if (this.actor.role.killLimit <= 0) { + return; + } + + var convertingVisitors = this.getVisitors(this.actor, "convert"); + + for (let visitor of convertingVisitors) { + if (this.actor.role.killLimit > 0 && this.dominates(visitor)) { + visitor.kill("basic", this.actor); + this.actor.role.killLimit--; + } + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/KillVillagePlayerOnDeath.js b/Games/types/Mafia/roles/cards/KillVillagePlayerOnDeath.js index 787c9aa49..6a2882fae 100644 --- a/Games/types/Mafia/roles/cards/KillVillagePlayerOnDeath.js +++ b/Games/types/Mafia/roles/cards/KillVillagePlayerOnDeath.js @@ -1,4 +1,5 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_DAY_DEFAULT } = require("../../const/Priority"); const { PRIORITY_KILL_DEFAULT } = require("../../const/Priority"); const { @@ -31,7 +32,7 @@ module.exports = class KillVillagePlayerOnDeath extends Card { }, }, }; - + /* this.actions = [ { priority: PRIORITY_KILL_DEFAULT, @@ -42,10 +43,40 @@ module.exports = class KillVillagePlayerOnDeath extends Card { if (!this.actor.role.SelectedPlayer) return; if (this.actor.role.SelectedPlayer.role.alignment != "Village") return; - + this.dominates(this.target){ this.actor.role.SelectedPlayer.kill("basic", this.actor); + } }, }, ]; +*/ + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_KILL_DEFAULT, + labels: ["hidden", "kill"], + run: function () { + if (!this.actor.role.SelectedPlayer) return; + if (this.actor.role.SelectedPlayer.role.alignment != "Village") + return; + if (this.dominates(this.target)) { + this.actor.role.SelectedPlayer.kill("basic", this.actor); + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/KillVisitors.js b/Games/types/Mafia/roles/cards/KillVisitors.js index 15a34db8f..6b9cd6312 100644 --- a/Games/types/Mafia/roles/cards/KillVisitors.js +++ b/Games/types/Mafia/roles/cards/KillVisitors.js @@ -1,10 +1,11 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_KILL_DEFAULT } = require("../../const/Priority"); module.exports = class KillVisitors extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_KILL_DEFAULT, @@ -21,5 +22,34 @@ module.exports = class KillVisitors extends Card { }, }, ]; +*/ + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_KILL_DEFAULT, + labels: ["kill", "hidden", "absolute"], + run: function () { + if (!this.actor.alive) return; + + let visitors = this.getVisitors(); + + for (let visitor of visitors) + if (this.dominates(visitor)) visitor.kill("basic", this.actor); + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/KillVisitorsWhileDead.js b/Games/types/Mafia/roles/cards/KillVisitorsWhileDead.js index 3cf60ddae..4dd44fe59 100644 --- a/Games/types/Mafia/roles/cards/KillVisitorsWhileDead.js +++ b/Games/types/Mafia/roles/cards/KillVisitorsWhileDead.js @@ -1,10 +1,11 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_KILL_DEFAULT } = require("../../const/Priority"); module.exports = class KillVisitorsWhileDead extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_KILL_DEFAULT - 1, @@ -19,5 +20,34 @@ module.exports = class KillVisitorsWhileDead extends Card { }, }, ]; +*/ + this.listeners = { + state: function (stateInfo) { + if (this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_KILL_DEFAULT, + labels: ["kill", "hidden", "absolute"], + run: function () { + if (this.actor.alive) return; + + let visitors = this.getVisitors(); + + for (let visitor of visitors) + if (this.dominates(visitor)) visitor.kill("basic", this.actor); + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/Learn3ExcessRoles.js b/Games/types/Mafia/roles/cards/Learn3ExcessRoles.js index 5915a6711..97e2f515a 100644 --- a/Games/types/Mafia/roles/cards/Learn3ExcessRoles.js +++ b/Games/types/Mafia/roles/cards/Learn3ExcessRoles.js @@ -5,7 +5,7 @@ const { PRIORITY_INVESTIGATIVE_DEFAULT } = require("../../const/Priority"); module.exports = class Learn3ExcessRoles extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_INVESTIGATIVE_DEFAULT, @@ -45,7 +45,7 @@ module.exports = class Learn3ExcessRoles extends Card { roles.slice(roles.indexOf(roles[x]), 1); } } - */ + } if (roles.length <= 0) { @@ -77,5 +77,72 @@ module.exports = class Learn3ExcessRoles extends Card { }, }, ]; +*/ + this.listeners = { + roleAssigned: function (player) { + if (player !== this.player) { + return; + } + if ( + this.player.role.data.hasExcessRoles != false && + this.player.role.data.hasExcessRoles != null + ) + return; + if (!this.player.alive) return; + if (this.player.role.data.hasExcessRoles == true) return; + this.player.role.data.hasExcessRoles = true; + + let roles = this.game.PossibleRoles.filter((r) => r); + let players = this.game.players.filter((p) => p.role); + let currentRoles = []; + + for (let x = 0; x < players.length; x++) { + currentRoles.push(players[x].role); + } + for (let y = 0; y < currentRoles.length; y++) { + roles = roles.filter((r) => r.split(":")[0] != currentRoles[y].name); + } + + if (this.player.hasEffect("FalseMode")) { + roles = currentRoles.map((r) => r.name); + } + + if ( + this.player.role.alignment == "Mafia" || + this.player.role.alignment == "Cult" + ) { + roles = roles.filter( + (r) => this.game.getRoleAlignment(r) == "Village" + ); + } + + if (roles.length <= 0) { + this.player.queueAlert(`There are 0 excess roles.`); + } else if (roles.length == 1) { + var role1 = roles[0]; + this.player.queueAlert( + `There is only 1 excess role. The Excess role is ${role1}` + ); + } else if (roles.length == 2) { + var role1 = roles[0]; + var role2 = roles[1]; + this.player.queueAlert( + `There is only 2 excess roles. The Excess roles are ${role1} and ${role2}` + ); + } else { + var roleIndexes = roles.map((r, i) => i); + var roleIndex1 = Random.randArrayVal(roleIndexes, true); + var roleIndex2 = Random.randArrayVal(roleIndexes, true); + var roleIndex3 = Random.randArrayVal(roleIndexes, true); + var role1 = roles[roleIndex1]; + var role2 = roles[roleIndex2]; + var role3 = roles[roleIndex3]; + + this.player.queueAlert( + `3 excess roles are ${role1}, ${role2}, and ${role3}.` + ); + } + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/LearnAboutPlayerAndRole.js b/Games/types/Mafia/roles/cards/LearnAboutPlayerAndRole.js index e9d06481d..04f7dc064 100644 --- a/Games/types/Mafia/roles/cards/LearnAboutPlayerAndRole.js +++ b/Games/types/Mafia/roles/cards/LearnAboutPlayerAndRole.js @@ -1,4 +1,5 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_INVESTIGATIVE_DEFAULT } = require("../../const/Priority"); module.exports = class LearnAboutPlayerAndRole extends Card { @@ -167,6 +168,7 @@ module.exports = class LearnAboutPlayerAndRole extends Card { }, }, }), + /* (this.actions = [ { priority: PRIORITY_INVESTIGATIVE_DEFAULT, @@ -194,27 +196,56 @@ module.exports = class LearnAboutPlayerAndRole extends Card { }, }, ]); +*/ + (this.listeners = { + roleAssigned: function (player) { + if (player !== this.player) { + return; + } + + this.data.ConvertOptions = this.game.PossibleRoles.map( + (r) => r.split(":")[0] + ); + }, + // refresh cooldown + state: function (stateInfo) { + if (stateInfo.name.match(/Day/)) { + var ConvertOptions = this.data.ConvertOptions; + ConvertOptions.push("None"); + this.meetings["Select Role"].targets = ConvertOptions; + } + if (stateInfo.name.match(/Night/)) { + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_INVESTIGATIVE_DEFAULT, + labels: ["hidden", "absolute", "investigate"], + run: function () { + if (!this.actor.alive) return; + + let alivePlayers = this.game.players.filter((p) => p.role); + let allVisits = []; + let allVisitors = []; + + for (let x = 0; x < alivePlayers.length; x++) { + let visits = this.getVisits(alivePlayers[x]); + let visitNames = visits.map((p) => p.role); + let visitors = this.getVisitors(alivePlayers[x]); + let visitorNames = visitors.map((p) => p.role); + allVisits.push(visitNames); + allVisitors.push(visitorNames); + } - this.listeners = { - roleAssigned: function (player) { - if (player !== this.player) { - return; - } + this.actor.role.data.LastNightVisits = allVisits; + this.actor.role.data.LastNightVisitors = allVisitors; + this.actor.role.data.LastNightPlayers = alivePlayers; + }, + }); - this.data.ConvertOptions = this.game.PossibleRoles.map( - (r) => r.split(":")[0] - ); - }, - // refresh cooldown - state: function (stateInfo) { - if (!stateInfo.name.match(/Day/)) { - return; - } - var ConvertOptions = this.data.ConvertOptions; - ConvertOptions.push("None"); - - this.meetings["Select Role"].targets = ConvertOptions; - }, - }; + this.game.queueAction(action); + //this.game.queueAction(action2); + } + }, + }); } }; diff --git a/Games/types/Mafia/roles/cards/LearnAndLifeLinkToPlayer.js b/Games/types/Mafia/roles/cards/LearnAndLifeLinkToPlayer.js index 4965f8d69..3b063d205 100644 --- a/Games/types/Mafia/roles/cards/LearnAndLifeLinkToPlayer.js +++ b/Games/types/Mafia/roles/cards/LearnAndLifeLinkToPlayer.js @@ -1,11 +1,12 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const Random = require("../../../../../lib/Random"); const { PRIORITY_INVESTIGATIVE_DEFAULT } = require("../../const/Priority"); module.exports = class LearnAndLifeLinkToPlayer extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_INVESTIGATIVE_DEFAULT, @@ -51,7 +52,7 @@ module.exports = class LearnAndLifeLinkToPlayer extends Card { }, }, ]; - +*/ this.listeners = { roleAssigned: function (player) { if (player !== this.player) { @@ -75,6 +76,63 @@ module.exports = class LearnAndLifeLinkToPlayer extends Card { this.player.kill("basic", this.player); } }, + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_INVESTIGATIVE_DEFAULT, + labels: ["investigate", "role"], + run: function () { + if (this.actor.role.hasInfo) return; + if (!this.actor.alive) return; + + if (this.actor.role.targetPlayer) { + let learnPlayer = this.actor.role.targetPlayer; + let learnRole = learnPlayer.getRoleAppearance(); + + if (this.actor.hasEffect("FalseMode")) { + var alive = this.game.players.filter( + (p) => p.alive && p != this.actor && p != learnPlayer + ); + alive = Random.randomizeArray(alive); + //learnPlayer = alive[0]; + if ( + alive.filter( + (p) => + p.getRoleAppearance() != + learnPlayer.getRoleAppearance() && + p.role.alignment == this.actor.role.alignment + ).length > 0 + ) { + alive = alive.filter( + (p) => + p.getRoleAppearance() != + learnPlayer.getRoleAppearance() && + p.role.alignment == this.actor.role.alignment + ); + } + alive = Random.randomizeArray(alive); + learnRole = alive[0].getRoleAppearance(); + } + + this.actor.queueAlert( + `You are Married to ${learnPlayer.name} who is a ${learnRole}. If they die during the night to another alignment, You will die as well.` + ); + } + + this.actor.role.hasInfo = true; + }, + }); + + this.game.queueAction(action); + }, }; } }; diff --git a/Games/types/Mafia/roles/cards/LearnEvilDeadCount.js b/Games/types/Mafia/roles/cards/LearnEvilDeadCount.js index bee3588af..60f4ded57 100644 --- a/Games/types/Mafia/roles/cards/LearnEvilDeadCount.js +++ b/Games/types/Mafia/roles/cards/LearnEvilDeadCount.js @@ -1,4 +1,5 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT, } = require("../../const/Priority"); @@ -6,7 +7,7 @@ const { module.exports = class LearnEvilDeadCount extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT - 2, @@ -43,5 +44,56 @@ module.exports = class LearnEvilDeadCount extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT - 2, + labels: ["investigate"], + run: function () { + if (!this.actor.alive) return; + let evilCount; + let players = this.game.deadPlayers(); + + if (players.length <= 0) { + return; + } + + var evilPlayers = players.filter( + (p) => + this.game.getRoleAlignment( + p.getRoleAppearance().split(" (")[0] + ) == "Cult" || + this.game.getRoleAlignment( + p.getRoleAppearance().split(" (")[0] + ) == "Mafia" + ); + evilCount = evilPlayers.length; + + if (this.actor.hasEffect("FalseMode")) { + if (evilCount == 0) evilCount = 1; + else evilCount = evilCount - 1; + } + + this.actor.queueAlert( + `You learn that ${evilCount} dead players are Evil.` + ); + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/LearnIfRoleChanged.js b/Games/types/Mafia/roles/cards/LearnIfRoleChanged.js index 9cc1a2f96..c1718506b 100644 --- a/Games/types/Mafia/roles/cards/LearnIfRoleChanged.js +++ b/Games/types/Mafia/roles/cards/LearnIfRoleChanged.js @@ -1,4 +1,5 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const Random = require("../../../../../lib/Random"); const { PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT, @@ -7,7 +8,7 @@ const { module.exports = class LearnIfRoleChanged extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT - 2, @@ -18,5 +19,30 @@ module.exports = class LearnIfRoleChanged extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT - 2, + run: function () { + if (!this.actor.alive) return; + this.actor.queueAlert(`Your role is ${this.actor.role.name}`); + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/LearnOneOfTwoPlayers.js b/Games/types/Mafia/roles/cards/LearnOneOfTwoPlayers.js index cfdb7127f..74bfa06e5 100644 --- a/Games/types/Mafia/roles/cards/LearnOneOfTwoPlayers.js +++ b/Games/types/Mafia/roles/cards/LearnOneOfTwoPlayers.js @@ -1,11 +1,12 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const Random = require("../../../../../lib/Random"); const { PRIORITY_INVESTIGATIVE_DEFAULT } = require("../../const/Priority"); module.exports = class LearnOneOfTwoPlayers extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_INVESTIGATIVE_DEFAULT, @@ -55,5 +56,69 @@ module.exports = class LearnOneOfTwoPlayers extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_INVESTIGATIVE_DEFAULT, + labels: ["investigate"], + run: function () { + if (!this.actor.alive) return; + if (this.actor.role.hasInfo) return; + + var alive = this.game.players.filter( + (p) => p.alive && p != this.actor + ); + + if (alive.length < 3) { + this.actor.queueAlert( + ` You learn nothing because only 2 players are alive.` + ); + return; + } else { + const chosenPlayer = Random.randArrayVal(alive); + var aliveRemoveTarget = alive.filter((p) => p != chosenPlayer); + const chosenRandom = Random.randArrayVal(aliveRemoveTarget); + let chosenRole = chosenPlayer.getRoleAppearance(); + let chosenNames = [chosenPlayer, chosenRandom]; + let chosenNamesRan = Random.randomizeArray(chosenNames); + + if (this.actor.hasEffect("FalseMode")) { + aliveRemoveTarget = aliveRemoveTarget.filter( + (p) => + p.getRoleAppearance().split(" (")[0] != + chosenNamesRan[0].role.name + ); + aliveRemoveTarget = aliveRemoveTarget.filter( + (p) => + p.getRoleAppearance().split(" (")[0] != + chosenNamesRan[1].role.name + ); + chosenRole = + Random.randArrayVal(aliveRemoveTarget).getRoleAppearance(); + } + + this.actor.queueAlert( + ` You learn that ${chosenNamesRan[0].name} or ${chosenNamesRan[1].name} is a ${chosenRole}.` + ); + this.actor.role.hasInfo = true; + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/LearnVisitors.js b/Games/types/Mafia/roles/cards/LearnVisitors.js index 2c6255246..6b8016c23 100644 --- a/Games/types/Mafia/roles/cards/LearnVisitors.js +++ b/Games/types/Mafia/roles/cards/LearnVisitors.js @@ -1,4 +1,5 @@ const { addArticle } = require("../../../../core/Utils"); +const Action = require("../../Action"); const Random = require("../../../../../lib/Random"); const Card = require("../../Card"); const { PRIORITY_INVESTIGATIVE_DEFAULT } = require("../../const/Priority"); @@ -6,7 +7,7 @@ const { PRIORITY_INVESTIGATIVE_DEFAULT } = require("../../const/Priority"); module.exports = class LearnVisitors extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_INVESTIGATIVE_DEFAULT, @@ -45,5 +46,57 @@ module.exports = class LearnVisitors extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_INVESTIGATIVE_DEFAULT, + labels: ["investigate", "role", "hidden", "absolute"], + run: function () { + if (!this.actor.alive) return; + + let visitors = this.getVisitors(this.actor); + + if (this.actor.hasEffect("FalseMode")) { + let players = this.game + .alivePlayers() + .filter((p) => p != this.actor); + let playerNames = players.map((p) => p.name); + let playerFake = Random.randArrayVal(players, true); + let roleFake = Random.randArrayVal(players, true); + if (visitors.length <= 0) { + this.actor.queueAlert( + `:invest: You learn that ${ + playerFake.name + }'s role is ${addArticle(roleFake.getRoleAppearance())}.` + ); + } + return; + } + + for (let visitor of visitors) { + this.actor.queueAlert( + `:invest: You learn that ${visitor.name}'s role is ${addArticle( + visitor.getRoleAppearance() + )}.` + ); + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/LearnVisitorsAndArm.js b/Games/types/Mafia/roles/cards/LearnVisitorsAndArm.js index ca9ea94e1..af1c5998d 100644 --- a/Games/types/Mafia/roles/cards/LearnVisitorsAndArm.js +++ b/Games/types/Mafia/roles/cards/LearnVisitorsAndArm.js @@ -1,11 +1,12 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const Random = require("../../../../../lib/Random"); const { PRIORITY_ITEM_GIVER_DEFAULT } = require("../../const/Priority"); module.exports = class LearnVisitorsAndArm extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_ITEM_GIVER_DEFAULT, @@ -39,5 +40,52 @@ module.exports = class LearnVisitorsAndArm extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_ITEM_GIVER_DEFAULT, + labels: ["giveItem", "gun", "hidden", "absolute"], + run: function () { + if (!this.actor.alive) return; + + let visitors = this.getVisitors(this.actor); + + for (let visitor of visitors) { + if (this.actor.hasEffect("FalseMode")) { + let players = this.game + .alivePlayers() + .filter((p) => p != this.actor); + for (let v of visitors) { + players = players.filter((p) => p != v); + } + let ranPlayer = Random.randArrayVal(players); + this.actor.queueAlert( + `:gun2: You still feel apprehensive about ${ranPlayer.name} after their visit last night but with this new gun, you feel more safe.` + ); + } else { + this.actor.queueAlert( + `:gun2: You still feel apprehensive about ${visitor.name} after their visit last night but with this new gun, you feel more safe.` + ); + } + this.actor.holdItem("Gun", { reveal: false }); + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/LearnVisitorsPerson.js b/Games/types/Mafia/roles/cards/LearnVisitorsPerson.js index 3d83a1490..fc3265acc 100644 --- a/Games/types/Mafia/roles/cards/LearnVisitorsPerson.js +++ b/Games/types/Mafia/roles/cards/LearnVisitorsPerson.js @@ -1,11 +1,12 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const Random = require("../../../../../lib/Random"); const { PRIORITY_INVESTIGATIVE_DEFAULT } = require("../../const/Priority"); module.exports = class LearnVisitorsPerson extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_INVESTIGATIVE_DEFAULT, @@ -42,5 +43,54 @@ module.exports = class LearnVisitorsPerson extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_INVESTIGATIVE_DEFAULT, + labels: ["investigate", "role", "hidden", "absolute"], + run: function () { + if (!this.actor.alive) return; + + let visitors = this.getVisitors(this.actor); + let visitorNames = visitors.map((v) => v.name); + + if (this.actor.hasEffect("FalseMode")) { + let players = this.game + .alivePlayers() + .filter((p) => p != this.actor); + let playerNames = players.map((p) => p.name); + if (visitorNames.length == 0) { + visitorNames.push(Random.randArrayVal(playerNames)); + } else { + visitorNames = []; + } + } + + if (visitors.length === 0) { + visitorNames = ["no one"]; + } + + this.actor.queueAlert( + `:watch: You were visited by ${visitorNames.join( + ", " + )} during the night.` + ); + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/LearnVisitorsRole.js b/Games/types/Mafia/roles/cards/LearnVisitorsRole.js index aa243fec0..25bbaa1ee 100644 --- a/Games/types/Mafia/roles/cards/LearnVisitorsRole.js +++ b/Games/types/Mafia/roles/cards/LearnVisitorsRole.js @@ -1,4 +1,5 @@ const { addArticle } = require("../../../../core/Utils"); +const Action = require("../../Action"); const Random = require("../../../../../lib/Random"); const Card = require("../../Card"); const { PRIORITY_INVESTIGATIVE_DEFAULT } = require("../../const/Priority"); @@ -6,7 +7,7 @@ const { PRIORITY_INVESTIGATIVE_DEFAULT } = require("../../const/Priority"); module.exports = class LearnVisitorsRole extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_INVESTIGATIVE_DEFAULT, @@ -39,5 +40,51 @@ module.exports = class LearnVisitorsRole extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_INVESTIGATIVE_DEFAULT, + labels: ["investigate", "role", "hidden", "absolute"], + run: function () { + if (!this.actor.alive) return; + + let visitors = this.getVisitors(this.actor); + + if (this.actor.hasEffect("FalseMode")) { + let players = this.game + .alivePlayers() + .filter((p) => p != this.actor); + if (visitors.length == 0) { + visitors.push(Random.randArrayVal(players)); + } else { + visitors = []; + } + } + + for (let visitor of visitors) { + this.actor.queueAlert( + `Last night, ${addArticle( + visitor.getRoleAppearance() + )} visited you and confessed their sins.` + ); + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/Loyal.js b/Games/types/Mafia/roles/cards/Loyal.js index fc1a4c549..1c196c26d 100644 --- a/Games/types/Mafia/roles/cards/Loyal.js +++ b/Games/types/Mafia/roles/cards/Loyal.js @@ -1,11 +1,12 @@ const Card = require("../../Card"); const Player = require("../../../../core/Player"); +const Action = require("../../Action"); const { PRIORITY_NIGHT_ROLE_BLOCKER } = require("../../const/Priority"); module.exports = class Loyal extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_NIGHT_ROLE_BLOCKER - 1, @@ -57,5 +58,66 @@ module.exports = class Loyal extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_NIGHT_ROLE_BLOCKER - 1, + labels: ["block", "hidden", "absolute"], + run: function () { + if (!this.actor.alive) return; + + for (let action of this.game.actions[0]) { + if (action.hasLabel("absolute")) { + continue; + } + if (action.hasLabel("mafia")) { + continue; + } + if (action.hasLabel("hidden")) { + continue; + } + + let toCheck = action.target; + if (!Array.isArray(action.target)) { + toCheck = [action.target]; + } + + if ( + action.actors.indexOf(this.actor) != -1 && + !action.hasLabel("hidden") && + action.target && + toCheck[0] instanceof Player + ) { + for (let y = 0; y < toCheck.length; y++) { + if (toCheck[y].role.alignment != this.actor.role.alignment) { + if ( + action.priority > this.priority && + !action.hasLabel("absolute") + ) { + action.cancelActor(this.actor); + break; + } + } + } + } + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/MakeAllVillageInfoFalse.js b/Games/types/Mafia/roles/cards/MakeAllVillageInfoFalse.js index 0fdc8d89b..bd803831b 100644 --- a/Games/types/Mafia/roles/cards/MakeAllVillageInfoFalse.js +++ b/Games/types/Mafia/roles/cards/MakeAllVillageInfoFalse.js @@ -1,4 +1,5 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_MODIFY_INVESTIGATIVE_RESULT_DEFAULT, } = require("../../const/Priority"); @@ -6,7 +7,7 @@ const { module.exports = class MakeAllVillageInfoFalse extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_MODIFY_INVESTIGATIVE_RESULT_DEFAULT, @@ -23,5 +24,36 @@ module.exports = class MakeAllVillageInfoFalse extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_MODIFY_INVESTIGATIVE_RESULT_DEFAULT, + labels: ["effect"], + run: function () { + if (!this.actor.alive) return; + let players = this.game.players.filter( + (p) => p.role.alignment == "Village" + ); + for (let x = 0; x < players.length; x++) { + players[x].giveEffect("FalseMode", 1); + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/MakeKillHidden.js b/Games/types/Mafia/roles/cards/MakeKillHidden.js index 100f9c9d4..aa1193338 100644 --- a/Games/types/Mafia/roles/cards/MakeKillHidden.js +++ b/Games/types/Mafia/roles/cards/MakeKillHidden.js @@ -1,10 +1,11 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_NIGHT_ROLE_BLOCKER } = require("../../const/Priority"); module.exports = class MakeKillHidden extends Card { constructor(role) { super(role); - + /* this.actions = [ { labels: ["hidden"], @@ -23,5 +24,38 @@ module.exports = class MakeKillHidden extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_NIGHT_ROLE_BLOCKER + 5, + run: function () { + if (!this.actor.alive) return; + + for (let action of this.game.actions[0]) { + if ( + action.actors.includes(this.actor) && + action.hasLabel("kill") + ) { + action.labels = [...action.labels, "hidden"]; + } + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/MakeVisitorsInsane.js b/Games/types/Mafia/roles/cards/MakeVisitorsInsane.js index ee27cc892..cd58e04dd 100644 --- a/Games/types/Mafia/roles/cards/MakeVisitorsInsane.js +++ b/Games/types/Mafia/roles/cards/MakeVisitorsInsane.js @@ -1,10 +1,11 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_EFFECT_GIVER_DEFAULT } = require("../../const/Priority"); module.exports = class MakeVisitorsInsane extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_EFFECT_GIVER_DEFAULT, @@ -26,5 +27,35 @@ module.exports = class MakeVisitorsInsane extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_EFFECT_GIVER_DEFAULT, + labels: ["hidden", "absolute", "giveEffect", "insanity"], + run: function () { + let visitors = this.getVisitors(); + for (let visitor of visitors) { + if (this.dominates(visitor)) { + visitor.giveEffect("Insanity"); + } + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/MayorWin.js b/Games/types/Mafia/roles/cards/MayorWin.js index dcc72b915..639ea9fc1 100644 --- a/Games/types/Mafia/roles/cards/MayorWin.js +++ b/Games/types/Mafia/roles/cards/MayorWin.js @@ -14,7 +14,10 @@ module.exports = class MayorWin extends Card { this.actor.role.data.MayorWin = false; return; } - if (this.game.getStateName() == "Day") { + if ( + this.game.getStateName() == "Day" || + this.game.getStateName() == "Dusk" + ) { let alivePlayers = this.game.players.filter((p) => p.role); for (let x = 0; x < alivePlayers.length; x++) { diff --git a/Games/types/Mafia/roles/cards/MindRot50Percent.js b/Games/types/Mafia/roles/cards/MindRot50Percent.js index d380b4049..c11f3cf3e 100644 --- a/Games/types/Mafia/roles/cards/MindRot50Percent.js +++ b/Games/types/Mafia/roles/cards/MindRot50Percent.js @@ -1,11 +1,12 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const Random = require("../../../../../lib/Random"); const { PRIORITY_NIGHT_ROLE_BLOCKER } = require("../../const/Priority"); module.exports = class MindRot50Percent extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_NIGHT_ROLE_BLOCKER, @@ -19,5 +20,32 @@ module.exports = class MindRot50Percent extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_NIGHT_ROLE_BLOCKER, + labels: ["block", "hidden", "absolute"], + run: function () { + if (Random.randInt(0, 1) == 0) { + this.blockWithMindRot(this.actor); + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/MindRotEveryoneOnEvilCondemn.js b/Games/types/Mafia/roles/cards/MindRotEveryoneOnEvilCondemn.js index 49db9b69c..2fccfde1f 100644 --- a/Games/types/Mafia/roles/cards/MindRotEveryoneOnEvilCondemn.js +++ b/Games/types/Mafia/roles/cards/MindRotEveryoneOnEvilCondemn.js @@ -1,4 +1,5 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_NIGHT_ROLE_BLOCKER } = require("../../const/Priority"); module.exports = class MindRotEveryoneOnEvilCondemn extends Card { @@ -6,7 +7,7 @@ module.exports = class MindRotEveryoneOnEvilCondemn extends Card { super(role); //role.evilDied = false; - + /* this.actions = [ { priority: PRIORITY_NIGHT_ROLE_BLOCKER - 1, @@ -33,7 +34,7 @@ module.exports = class MindRotEveryoneOnEvilCondemn extends Card { }, }, ]; - +*/ this.listeners = { state: function (stateInfo) { if (!this.player.alive) { @@ -44,6 +45,32 @@ module.exports = class MindRotEveryoneOnEvilCondemn extends Card { this.player.role.evilDied = false; return; } + + if (stateInfo.name.match(/Night/)) { + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_NIGHT_ROLE_BLOCKER - 1, + labels: ["block"], + run: function () { + if (!this.actor.role.evilDied) return; + + if (!this.actor.alive) return; + + let players = this.game.players.filter((p) => p != this.actor); + + let victims = players; + + for (let x = 0; x < victims.length; x++) { + if (this.dominates(victims[x])) { + this.blockWithMindRot(victims[x]); + } + } + }, + }); + + this.game.queueAction(action); + } }, death: function (player, killer, deathType) { if ( diff --git a/Games/types/Mafia/roles/cards/MindRotNeighbors.js b/Games/types/Mafia/roles/cards/MindRotNeighbors.js index efbfebb37..25387a665 100644 --- a/Games/types/Mafia/roles/cards/MindRotNeighbors.js +++ b/Games/types/Mafia/roles/cards/MindRotNeighbors.js @@ -1,11 +1,12 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const Random = require("../../../../../lib/Random"); const { PRIORITY_NIGHT_ROLE_BLOCKER } = require("../../const/Priority"); module.exports = class MindRotNeighbors extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_NIGHT_ROLE_BLOCKER, @@ -63,5 +64,76 @@ module.exports = class MindRotNeighbors extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_NIGHT_ROLE_BLOCKER, + labels: ["block"], + run: function () { + if (!this.actor.alive) return; + + let players = this.game.alivePlayers(); + var indexOfActor = players.indexOf(this.actor); + var rightIdx; + var leftIdx; + var leftAlign; + var rightAlign; + var distance = 0; + var foundUp = 0; + var foundDown = 0; + + for (let x = 0; x < players.length; x++) { + leftIdx = + (indexOfActor - distance - 1 + players.length) % players.length; + rightIdx = (indexOfActor + distance + 1) % players.length; + leftAlign = players[leftIdx].role.alignment; + rightAlign = players[rightIdx].role.alignment; + + if ( + rightAlign == "Village" && + !players[rightIdx].role.data.banished && + foundUp == 0 + ) { + foundUp = players[rightIdx]; + } + if ( + leftAlign == "Village" && + !players[leftIdx].role.data.banished && + foundDown == 0 + ) { + foundDown = players[leftIdx]; + } + if (foundUp == 0 || foundDown == 0) { + distance = x; + } else { + break; + } + } + + let victims = [foundUp, foundDown]; + + for (let x = 0; x < victims.length; x++) { + if (this.dominates(victims[x])) { + this.blockWithMindRot(victims[x]); + } + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/MindRotRoleFor3Nights.js b/Games/types/Mafia/roles/cards/MindRotRoleFor3Nights.js index 5f88ad39c..67cb1eb4d 100644 --- a/Games/types/Mafia/roles/cards/MindRotRoleFor3Nights.js +++ b/Games/types/Mafia/roles/cards/MindRotRoleFor3Nights.js @@ -1,5 +1,6 @@ const Card = require("../../Card"); const { PRIORITY_NIGHT_ROLE_BLOCKER } = require("../../const/Priority"); +const Action = require("../../Action"); const { addArticle } = require("../../../../core/Utils"); module.exports = class MindRotRoleFor3Nights extends Card { constructor(role) { @@ -59,7 +60,10 @@ module.exports = class MindRotRoleFor3Nights extends Card { if (victims.length <= 0) return; if (this.actor.role.blockCounter <= 0) return; if (!this.actor.alive) return; - if (this.game.getStateName() == "Day") + if ( + this.game.getStateName() == "Day" || + this.game.getStateName() == "Dusk" + ) this.actor.role.blockCounter = this.actor.role.blockCounter - 1; for (let x = 0; x < victims.length; x++) { diff --git a/Games/types/Mafia/roles/cards/MindShifter.js b/Games/types/Mafia/roles/cards/MindShifter.js index 049dd257f..2eb9952b0 100644 --- a/Games/types/Mafia/roles/cards/MindShifter.js +++ b/Games/types/Mafia/roles/cards/MindShifter.js @@ -1,4 +1,5 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_EFFECT_GIVER_DEFAULT } = require("../../const/Priority"); module.exports = class MindShifter extends Card { @@ -22,7 +23,7 @@ module.exports = class MindShifter extends Card { }, }, }; - + /* this.actions = [ { labels: ["giveEffect", "insanity"], @@ -48,5 +49,40 @@ module.exports = class MindShifter extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + labels: ["giveEffect", "insanity"], + priority: PRIORITY_EFFECT_GIVER_DEFAULT - 1, + run: function () { + let target = this.actor.role.data.insane; + if (!target) { + return; + } + + var visitors = this.getVisitors(this.actor.role.data.insane); + var becomesInsane = !visitors.find( + (visitor) => visitor.role.alignment != "Cult" + ); + + if (becomesInsane && this.dominates(target)) { + target.giveEffect("Insanity"); + } + + delete this.actor.role.data.insane; + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/ModifierBloodthirsty.js b/Games/types/Mafia/roles/cards/ModifierBloodthirsty.js index cda83e6f3..6b29e4d8b 100644 --- a/Games/types/Mafia/roles/cards/ModifierBloodthirsty.js +++ b/Games/types/Mafia/roles/cards/ModifierBloodthirsty.js @@ -1,10 +1,11 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_KILL_DEFAULT } = require("../../const/Priority"); module.exports = class ModifierBloodthirsty extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_KILL_DEFAULT, @@ -17,5 +18,27 @@ module.exports = class ModifierBloodthirsty extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_KILL_DEFAULT, + labels: ["kill"], + run: function () { + let visits = this.getVisits(this.actor); + visits.map((v) => this.kill(v)); + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/ModifierLazy.js b/Games/types/Mafia/roles/cards/ModifierLazy.js index 8297e2aee..41bf4cdf5 100644 --- a/Games/types/Mafia/roles/cards/ModifierLazy.js +++ b/Games/types/Mafia/roles/cards/ModifierLazy.js @@ -1,10 +1,11 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_MODIFY_ACTION_DELAY } = require("../../const/Priority"); module.exports = class ModifierLazy extends Card { constructor(role) { super(role); - + /* this.actions = [ { labels: ["delayAction"], @@ -22,5 +23,45 @@ module.exports = class ModifierLazy extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + labels: ["delayAction"], + priority: PRIORITY_MODIFY_ACTION_DELAY, + run: function () { + for (let action of this.game.actions[0]) { + if ( + action.actors.includes(this.actor) && + !action.hasLabel("delayAction") + ) { + //let newAction = action; + action.cancel(true); + this.game.dequeueAction(action, true); + //newAction.delay = 1; + //newAction.labels.push("delayAction"); + + action.delay = 1; + action.labels.push("delayAction"); + this.game.queueAction(newAction); + } + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/ModifierLoud.js b/Games/types/Mafia/roles/cards/ModifierLoud.js index 5906bc8a5..45550f546 100644 --- a/Games/types/Mafia/roles/cards/ModifierLoud.js +++ b/Games/types/Mafia/roles/cards/ModifierLoud.js @@ -1,4 +1,5 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const Random = require("../../../../../lib/Random"); const { PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT, @@ -10,6 +11,7 @@ module.exports = class ModifierLoud extends Card { super(role); this.startEffects = ["Leak Whispers"]; + /* this.actions = [ { priority: PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT + 2, @@ -72,5 +74,76 @@ module.exports = class ModifierLoud extends Card { }, }, ]; +*/ + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT + 2, + labels: [ + "investigate", + "alerts", + "hidden", + "absolute", + "uncontrollable", + ], + run: function () { + let visitors = this.getVisitors(); + let MafiaKill = this.getVisitors(this.actor, "mafia"); + + if (MafiaKill && MafiaKill.length > 1) { + for (let x = 1; x < MafiaKill.length; x++) { + visitors.splice(visitors.indexOf(MafiaKill[x]), 1); + } + } + + if (visitors?.length) { + let names = visitors?.map((visitor) => visitor.name); + + if (this.actor.hasEffect("FalseMode")) { + let players = this.game + .alivePlayers() + .filter((p) => p != this.actor); + + for (let v of visitors) { + players = players.filter((p) => p != v); + } + names = []; + for (let x = 0; x < visitors.length; x++) { + let randomPlayer = Random.randArrayVal(players).name; + names.push(randomPlayer); + } + } + + this.game.queueAlert( + `:loud: Someone shouts during the night: ` + + `Curses! ${names.join(", ")} disturbed my slumber!` + ); + this.actor.role.data.visitors = []; + } + + let reports = this.getReports(this.actor); + for (let report of reports) { + this.game.queueAlert( + `:loud: ${addArticle( + this.actor.getRoleAppearance() + )} is overheard reading: ${report}` + ); + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/NeighborAlignment.js b/Games/types/Mafia/roles/cards/NeighborAlignment.js index cdaac3b56..29533495d 100644 --- a/Games/types/Mafia/roles/cards/NeighborAlignment.js +++ b/Games/types/Mafia/roles/cards/NeighborAlignment.js @@ -1,4 +1,5 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT, } = require("../../const/Priority"); @@ -6,7 +7,7 @@ const { module.exports = class NeighborAlignment extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT - 10, @@ -44,5 +45,56 @@ module.exports = class NeighborAlignment extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT - 10, + labels: ["investigate"], + run: function () { + if (!this.actor.alive) return; + + const neighbors = this.getAliveNeighbors(); + + let evilCount = neighbors.filter( + (p) => + this.game.getRoleAlignment( + p.getRoleAppearance().split(" (")[0] + ) !== "Village" && + this.game.getRoleAlignment( + p.getRoleAppearance().split(" (")[0] + ) !== "Independent" + ).length; + + if (this.actor.hasEffect("FalseMode")) { + if (evilCount == 0) { + evilCount = 1; + } else if (evilCount == 2) { + evilCount = 0; + } else { + evilCount = 2; + } + } + + this.actor.queueAlert( + `You can feel the intent of those around you… you learn that you have ${evilCount} evil neighbors!` + ); + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/NightBodyguard.js b/Games/types/Mafia/roles/cards/NightBodyguard.js index eba813043..3c389c630 100644 --- a/Games/types/Mafia/roles/cards/NightBodyguard.js +++ b/Games/types/Mafia/roles/cards/NightBodyguard.js @@ -1,4 +1,5 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const Random = require("../../../../../lib/Random"); const { PRIORITY_NIGHT_SAVER, @@ -31,7 +32,7 @@ module.exports = class NightBodyguard extends Card { }, }, }; - + /* this.actions = [ { priority: PRIORITY_KILL_DEFAULT, @@ -68,5 +69,55 @@ module.exports = class NightBodyguard extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_KILL_DEFAULT, + labels: ["kill", "hidden", "absolute"], + run: function () { + // target was not attacked + let killers = this.actor.role.killers; + if (!killers) { + return; + } + + // checks how many to kill + let killsAllAttackers = false; + if (this.actor.role.savedRole === "Celebrity") { + killsAllAttackers = true; + } + + // kill attackers first + if (!killsAllAttackers) { + killers = [Random.randArrayVal(killers)]; + } + for (let k of killers) { + if (this.dominates(k)) { + k.kill("basic", this.actor); + } + } + + // bodyguard did not survive the fight + if (this.dominates(this.actor)) { + this.actor.kill("basic", killers[0]); + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/NightMatron.js b/Games/types/Mafia/roles/cards/NightMatron.js index 736dba29a..2c2c837bc 100644 --- a/Games/types/Mafia/roles/cards/NightMatron.js +++ b/Games/types/Mafia/roles/cards/NightMatron.js @@ -1,4 +1,5 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { MEETING_PRIORITY_MATRON } = require("../../const/MeetingPriority"); const { PRIORITY_ITEM_GIVER_DEFAULT } = require("../../const/Priority"); @@ -17,6 +18,34 @@ module.exports = class NightMatron extends Card { this.meetings["CommonRoomPlaceholder"]; delete this.meetings["CommonRoomPlaceholder"]; }, + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_ITEM_GIVER_DEFAULT, + labels: ["giveItem", "hidden"], + run: function () { + let visitors = this.getVisitors(this.actor); + visitors.map((v) => + v.holdItem("CommonRoomPassword", this.actor.role.data.meetingName) + ); + this.actor.holdItem( + "CommonRoomPassword", + this.actor.role.data.meetingName + ); + }, + }); + + this.game.queueAction(action); + }, }; this.meetings = { @@ -43,6 +72,7 @@ module.exports = class NightMatron extends Card { }, }, }; + /* this.actions = [ { priority: PRIORITY_ITEM_GIVER_DEFAULT, @@ -61,5 +91,6 @@ module.exports = class NightMatron extends Card { }, }, ]; + */ } }; diff --git a/Games/types/Mafia/roles/cards/Omniscient.js b/Games/types/Mafia/roles/cards/Omniscient.js index 85bb21abc..a3ab0d43e 100644 --- a/Games/types/Mafia/roles/cards/Omniscient.js +++ b/Games/types/Mafia/roles/cards/Omniscient.js @@ -1,11 +1,12 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const Random = require("../../../../../lib/Random"); const { PRIORITY_INVESTIGATIVE_DEFAULT } = require("../../const/Priority"); module.exports = class Omiscient extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_INVESTIGATIVE_DEFAULT, @@ -38,5 +39,52 @@ module.exports = class Omiscient extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_INVESTIGATIVE_DEFAULT, + labels: ["investigate"], + run: function () { + if (!this.actor.alive) return; + let visits; + let visitNames; + let role; + let name; + let players = this.game.alivePlayers(); + for (let x = 0; x < players.length; x++) { + visits = this.getVisits(players[x]); + visitNames = visits.map((p) => p.name); + role = players[x].role.name; + if (this.actor.hasEffect("FalseMode")) { + visits = this.getVisits(Random.randArrayVal(players)); + visitNames = visits.map((p) => p.name); + role = Random.randArrayVal(players).role.name; + } + name = players[x].name; + if (visitNames.length == 0) visitNames.push("no one"); + this.actor.queueAlert( + `:track: ${name}'s role is ${role} and they visited ${visitNames.join( + ", " + )} during the night.` + ); + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/OpenTheDoor.js b/Games/types/Mafia/roles/cards/OpenTheDoor.js index 39c9effa7..cea96de6c 100644 --- a/Games/types/Mafia/roles/cards/OpenTheDoor.js +++ b/Games/types/Mafia/roles/cards/OpenTheDoor.js @@ -1,4 +1,5 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const Random = require("../../../../../lib/Random"); const { PRIORITY_DAY_DEFAULT } = require("../../const/Priority"); const { PRIORITY_KILL_DEFAULT } = require("../../const/Priority"); @@ -42,7 +43,7 @@ module.exports = class OpenTheDoor extends Card { }, }, }; - + /* this.actions = [ { priority: PRIORITY_KILL_DEFAULT + 1, @@ -64,5 +65,41 @@ module.exports = class OpenTheDoor extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_KILL_DEFAULT + 1, + run: function () { + if (!this.actor.role.openedDoorLastNight) return; + + var visitors = this.getVisitors(); + var imminentDeath = !visitors.find( + (visitor) => visitor.role.alignment == "Village" + ); + + // death is absolute + if (imminentDeath) { + this.actor.kill("mistress", this.actor); + } + + delete this.actor.role.openedDoorLastNight; + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/PaintPortraits.js b/Games/types/Mafia/roles/cards/PaintPortraits.js index 70fbe25e0..ff4ef97c3 100644 --- a/Games/types/Mafia/roles/cards/PaintPortraits.js +++ b/Games/types/Mafia/roles/cards/PaintPortraits.js @@ -1,11 +1,12 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const Random = require("../../../../../lib/Random"); const { PRIORITY_INVESTIGATIVE_DEFAULT } = require("../../const/Priority"); module.exports = class PaintPortraits extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_INVESTIGATIVE_DEFAULT, @@ -22,6 +23,8 @@ module.exports = class PaintPortraits extends Card { }, }, ]; + */ + this.listeners = { roleAssigned: function (player) { if (player !== this.player) { @@ -30,6 +33,32 @@ module.exports = class PaintPortraits extends Card { this.player.data.portraits = []; }, + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_INVESTIGATIVE_DEFAULT, + labels: ["investigate", "role", "hidden", "absolute"], + run: function () { + if (!this.actor.alive) return; + + let visitors = this.getVisitors(this.actor); + for (let visitor of visitors) { + this.actor.data.portraits.push(visitor.name); + } + }, + }); + + this.game.queueAction(action); + }, death: function (player, killer, deathType) { if (player === this.player) { let portraits = this.player.data.portraits; diff --git a/Games/types/Mafia/roles/cards/PotionCaster.js b/Games/types/Mafia/roles/cards/PotionCaster.js index 76fcec135..3bc51cddb 100644 --- a/Games/types/Mafia/roles/cards/PotionCaster.js +++ b/Games/types/Mafia/roles/cards/PotionCaster.js @@ -1,5 +1,6 @@ const Card = require("../../Card"); const Random = require("../../../../../lib/Random"); +const Action = require("../../Action"); const { PRIORITY_KILL_DEFAULT, PRIORITY_NIGHT_SAVER, @@ -36,7 +37,7 @@ module.exports = class PotionCaster extends Card { }, }, }; - + /* this.actions = [ { priority: PRIORITY_KILL_DEFAULT, @@ -137,7 +138,7 @@ module.exports = class PotionCaster extends Card { }, }, ]; - +*/ this.listeners = { roleAssigned: function (player) { if (player !== this.player) { @@ -179,6 +180,106 @@ module.exports = class PotionCaster extends Card { } this.meetings["Choose Potion"].targets = currentPotionList; + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_NIGHT_SAVER, + labels: ["save"], + run: function () { + if (this.actor.role.data.currentPotion !== "Restoring") return; + + let target = this.actor.role.data.currentTarget; + if (!target) { + return; + } + + this.heal(1, target); + + // set cooldown + var potion = this.actor.role.data.currentPotion; + if (this.actor.role.data.potionCounter) { + this.actor.role.data.potionCounter[potion] = + this.actor.role.data.potionCooldown; + } + + delete this.actor.role.data.currentPotion; + delete this.actor.role.data.currentTarget; + }, + }); + + var action2 = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_KILL_DEFAULT, + labels: ["kill"], + run: function () { + if (this.actor.role.data.currentPotion !== "Damaging") return; + + let target = this.actor.role.data.currentTarget; + if (!target) { + return; + } + + if (this.dominates(target)) { + target.kill("basic", this.actor); + } + + // set cooldown + var potion = this.actor.role.data.currentPotion; + if (this.actor.role.data.potionCounter) { + this.actor.role.data.potionCounter[potion] = + this.actor.role.data.potionCooldown; + } + + delete this.actor.role.data.currentPotion; + delete this.actor.role.data.currentTarget; + }, + }); + + var action3 = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_INVESTIGATIVE_DEFAULT, + labels: ["investigate"], + run: function () { + if (this.actor.role.data.currentPotion !== "Elucidating") return; + + let target = this.actor.role.data.currentTarget; + if (!target) { + return; + } + + let role = target.getRoleAppearance(); + + if (this.actor.hasEffect("FalseMode")) { + let wrongPlayers = this.game + .alivePlayers() + .filter( + (p) => + p.getRoleAppearance().split(" (")[0] != target.role.name + ); + role = Random.randArrayVal(wrongPlayers).getRoleAppearance(); + } + + this.actor.queueAlert( + `:invest: You learn that ${target.name}'s role is ${role}.` + ); + + // set cooldown + var potion = this.actor.role.data.currentPotion; + if (this.actor.role.data.potionCounter) { + this.actor.role.data.potionCounter[potion] = + this.actor.role.data.potionCooldown; + } + delete this.actor.role.data.currentPotion; + delete this.actor.role.data.currentTarget; + }, + }); + + this.game.queueAction(action); + this.game.queueAction(action2); + this.game.queueAction(action3); }, }; } diff --git a/Games/types/Mafia/roles/cards/ProtectNeighborsIfBothTown.js b/Games/types/Mafia/roles/cards/ProtectNeighborsIfBothTown.js index 0b2b15c82..120b07a67 100644 --- a/Games/types/Mafia/roles/cards/ProtectNeighborsIfBothTown.js +++ b/Games/types/Mafia/roles/cards/ProtectNeighborsIfBothTown.js @@ -1,4 +1,5 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_NIGHT_SAVER } = require("../../const/Priority"); module.exports = class ProtectNeighborsIfBothTown extends Card { diff --git a/Games/types/Mafia/roles/cards/ReceiveBread.js b/Games/types/Mafia/roles/cards/ReceiveBread.js index 99dd61086..09864164c 100644 --- a/Games/types/Mafia/roles/cards/ReceiveBread.js +++ b/Games/types/Mafia/roles/cards/ReceiveBread.js @@ -1,10 +1,11 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_ITEM_GIVER_DEFAULT } = require("../../const/Priority"); module.exports = class ReceiveBread extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_ITEM_GIVER_DEFAULT, @@ -21,5 +22,35 @@ module.exports = class ReceiveBread extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_ITEM_GIVER_DEFAULT, + labels: ["giveItem", "bread", "hidden"], + run: function () { + for (let action of this.game.actions[0]) { + if (action.target == this.actor && !action.hasLabel("hidden")) { + action.actor.holdItem("bread"); + action.actor.queueGetItemAlert("Bread"); + } + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/Regretful.js b/Games/types/Mafia/roles/cards/Regretful.js index d94d7aca9..6688fb0d3 100644 --- a/Games/types/Mafia/roles/cards/Regretful.js +++ b/Games/types/Mafia/roles/cards/Regretful.js @@ -1,10 +1,11 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_KILL_DEFAULT } = require("../../const/Priority"); module.exports = class Regretful extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_KILL_DEFAULT, @@ -23,5 +24,37 @@ module.exports = class Regretful extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_KILL_DEFAULT, + labels: ["kill", "hidden", "absolute"], + run: function () { + let visits = this.getVisits(this.actor); + let killers = visits.map((v) => this.getVisitors(v, "kill")); + + if (killers.length == 0) { + return; + } else if (this.dominates(this.actor)) { + this.actor.kill("basic", this.actor); + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/Resolute.js b/Games/types/Mafia/roles/cards/Resolute.js index bc97ec601..f95585e52 100644 --- a/Games/types/Mafia/roles/cards/Resolute.js +++ b/Games/types/Mafia/roles/cards/Resolute.js @@ -1,10 +1,11 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_MODIFY_ACTION_LABELS } = require("../../const/Priority"); module.exports = class Resolute extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_MODIFY_ACTION_LABELS, @@ -20,5 +21,34 @@ module.exports = class Resolute extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_MODIFY_ACTION_LABELS, + labels: ["absolute", "hidden"], + run: function () { + for (let action of this.game.actions[0]) { + if (action.actors.includes(this.actor)) { + action.labels = [...action.labels, "absolute"]; + } + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/RevealNameToTarget.js b/Games/types/Mafia/roles/cards/RevealNameToTarget.js index 0f994c47f..80ba31ab8 100644 --- a/Games/types/Mafia/roles/cards/RevealNameToTarget.js +++ b/Games/types/Mafia/roles/cards/RevealNameToTarget.js @@ -1,4 +1,5 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const Random = require("../../../../../lib/Random"); const { PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT, @@ -7,7 +8,7 @@ const { module.exports = class RevealNameToTarget extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT, @@ -31,5 +32,42 @@ module.exports = class RevealNameToTarget extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT, + labels: ["investigate", "hidden"], + run: function () { + var alert = `:mask: You learn that you were visited by ${this.actor.name}.`; + + if (this.actor.hasEffect("FalseMode")) { + let players = this.game + .alivePlayers() + .filter((p) => p != this.actor); + alert = `:mask: You learn that you were visited by ${ + Random.randArrayVal(players).name + }.`; + } + + let visits = this.getVisits(this.actor); + visits.map((v) => v.queueAlert(alert)); + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/RevealRoleToTarget.js b/Games/types/Mafia/roles/cards/RevealRoleToTarget.js index 1c38daa11..f387e0f71 100644 --- a/Games/types/Mafia/roles/cards/RevealRoleToTarget.js +++ b/Games/types/Mafia/roles/cards/RevealRoleToTarget.js @@ -1,4 +1,5 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const Random = require("../../../../../lib/Random"); const { PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT, @@ -7,7 +8,7 @@ const { module.exports = class RevealRoleToTarget extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT, @@ -35,5 +36,45 @@ module.exports = class RevealRoleToTarget extends Card { }, }, ]; +*/ + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT, + labels: ["investigate", "hidden"], + run: function () { + var alert = `:mask: You learn that you were targeted by ${this.actor.getRoleAppearance()}.`; + + if (this.actor.hasEffect("FalseMode")) { + let players = this.game + .alivePlayers() + .filter( + (p) => + p.getRoleAppearance("condemn").split(" (")[0] != + this.actor.role.name + ); + alert = `:mask: You learn that you were visited by ${Random.randArrayVal( + players + ).getRoleAppearance()}.`; + } + + let visits = this.getVisits(this.actor); + visits.map((v) => v.queueAlert(alert)); + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/Rotten.js b/Games/types/Mafia/roles/cards/Rotten.js index 15b42885e..5cbecc209 100644 --- a/Games/types/Mafia/roles/cards/Rotten.js +++ b/Games/types/Mafia/roles/cards/Rotten.js @@ -1,11 +1,12 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const Random = require("../../../../../lib/Random"); const { PRIORITY_NIGHT_ROLE_BLOCKER } = require("../../const/Priority"); module.exports = class Rotten extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_NIGHT_ROLE_BLOCKER, @@ -19,5 +20,35 @@ module.exports = class Rotten extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + const target_list = this.game.players.filter((p) => p.alive); + const target = Random.randArrayVal(target_list); + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_NIGHT_ROLE_BLOCKER, + labels: ["block", "hidden", "absolute"], + run: function () { + //if (!this.actor.alive) return; + + this.blockWithMindRot(this.actor); + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/Sacrificial.js b/Games/types/Mafia/roles/cards/Sacrificial.js index def764bc1..28b8d8c59 100644 --- a/Games/types/Mafia/roles/cards/Sacrificial.js +++ b/Games/types/Mafia/roles/cards/Sacrificial.js @@ -1,10 +1,11 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_KILL_DEFAULT } = require("../../const/Priority"); module.exports = class Sacrificial extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_KILL_DEFAULT, @@ -18,5 +19,32 @@ module.exports = class Sacrificial extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_KILL_DEFAULT, + labels: ["kill", "hidden"], + run: function () { + if (this.hasVisits() === true && this.dominates(this.actor)) { + this.player.kill("sacrifice", this.actor, true); + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/Simple.js b/Games/types/Mafia/roles/cards/Simple.js index 124714eba..0e9e6eb6e 100644 --- a/Games/types/Mafia/roles/cards/Simple.js +++ b/Games/types/Mafia/roles/cards/Simple.js @@ -1,11 +1,12 @@ const Card = require("../../Card"); const Player = require("../../../../core/Player"); +const Action = require("../../Action"); const { PRIORITY_NIGHT_ROLE_BLOCKER } = require("../../const/Priority"); module.exports = class Simple extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_NIGHT_ROLE_BLOCKER, @@ -57,5 +58,66 @@ module.exports = class Simple extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_NIGHT_ROLE_BLOCKER, + labels: ["block", "hidden"], + run: function () { + if (!this.actor.alive) return; + + for (let action of this.game.actions[0]) { + if (action.hasLabel("absolute")) { + continue; + } + if (action.hasLabel("mafia")) { + continue; + } + if (action.hasLabel("hidden")) { + continue; + } + + let toCheck = action.target; + if (!Array.isArray(action.target)) { + toCheck = [action.target]; + } + + if ( + action.actors.indexOf(this.actor) != -1 && + !action.hasLabel("hidden") && + action.target && + toCheck[0] instanceof Player + ) { + for (let y = 0; y < toCheck.length; y++) { + if (!this.isVanillaRole(toCheck[y])) { + if ( + action.priority > this.priority && + !action.hasLabel("absolute") + ) { + action.cancelActor(this.actor); + break; + } + } + } + } + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/StealFromTargets.js b/Games/types/Mafia/roles/cards/StealFromTargets.js index c90d95afc..f3b5b4073 100644 --- a/Games/types/Mafia/roles/cards/StealFromTargets.js +++ b/Games/types/Mafia/roles/cards/StealFromTargets.js @@ -1,10 +1,11 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_ITEM_TAKER_DEFAULT } = require("../../const/Priority"); module.exports = class StealFromTargets extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_ITEM_TAKER_DEFAULT, @@ -19,5 +20,33 @@ module.exports = class StealFromTargets extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_ITEM_TAKER_DEFAULT, + labels: ["stealItem"], + run: function () { + if (!this.actor.alive) return; + + let visits = this.getVisits(this.actor); + visits.map((v) => this.stealAllItems(v)); + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/StrongModifier.js b/Games/types/Mafia/roles/cards/StrongModifier.js index 370132ec0..8316b9357 100644 --- a/Games/types/Mafia/roles/cards/StrongModifier.js +++ b/Games/types/Mafia/roles/cards/StrongModifier.js @@ -1,10 +1,11 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_MODIFY_ACTION_LABELS } = require("../../const/Priority"); module.exports = class StrongModifier extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_MODIFY_ACTION_LABELS, @@ -19,5 +20,38 @@ module.exports = class StrongModifier extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_MODIFY_ACTION_LABELS, + run: function () { + for (let action of this.game.actions[0]) { + if ( + action.actors.includes(this.actor) && + action.hasLabel("kill") + ) { + action.power = Infinity; + action.labels = [...action.labels, "absolute", "strong"]; + action.target.removeEffect("Extra Life", true); + } + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/TargetSelf50Percent.js b/Games/types/Mafia/roles/cards/TargetSelf50Percent.js index 1cc7474a3..e8733b54b 100644 --- a/Games/types/Mafia/roles/cards/TargetSelf50Percent.js +++ b/Games/types/Mafia/roles/cards/TargetSelf50Percent.js @@ -1,11 +1,12 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const Random = require("../../../../../lib/Random"); const { PRIORITY_REDIRECT_ACTION } = require("../../const/Priority"); module.exports = class TargetSelf50Percent extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_REDIRECT_ACTION, @@ -19,5 +20,32 @@ module.exports = class TargetSelf50Percent extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_REDIRECT_ACTION, + labels: ["block", "hidden", "absolute"], + run: function () { + if (Random.randInt(0, 1) == 0) { + this.redirectAllActions(this.actor, this.actor); + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/UnluckyDeath.js b/Games/types/Mafia/roles/cards/UnluckyDeath.js index 036d79471..a37826de5 100644 --- a/Games/types/Mafia/roles/cards/UnluckyDeath.js +++ b/Games/types/Mafia/roles/cards/UnluckyDeath.js @@ -12,7 +12,11 @@ module.exports = class UnluckyDeath extends Card { priority: PRIORITY_KILL_DEFAULT + 1, labels: ["kill"], run: function () { - if (this.game.getStateName() != "Night") return; + if ( + this.game.getStateName() != "Night" && + this.game.getStateName() != "Dawn" + ) + return; const alivePlayers = this.game .alivePlayers() diff --git a/Games/types/Mafia/roles/cards/Vain.js b/Games/types/Mafia/roles/cards/Vain.js index 9408f21d0..4a7d018d4 100644 --- a/Games/types/Mafia/roles/cards/Vain.js +++ b/Games/types/Mafia/roles/cards/Vain.js @@ -6,7 +6,7 @@ const { PRIORITY_KILL_DEFAULT } = require("../../const/Priority"); module.exports = class Vain extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_KILL_DEFAULT, @@ -54,5 +54,61 @@ module.exports = class Vain extends Card { }, }, ]; +*/ + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_KILL_DEFAULT, + labels: ["kill", "hidden"], + run: function () { + if (!this.actor.alive) return; + + for (let action of this.game.actions[0]) { + if (action.hasLabel("absolute")) { + continue; + } + if (action.hasLabel("mafia")) { + continue; + } + if (action.hasLabel("hidden")) { + continue; + } + + let toCheck = action.target; + if (!Array.isArray(action.target)) { + toCheck = [action.target]; + } + + if ( + action.actors.indexOf(this.actor) != -1 && + !action.hasLabel("hidden") && + action.target && + toCheck[0] instanceof Player + ) { + for (let y = 0; y < toCheck.length; y++) { + if (toCheck[y].role.alignment == this.actor.role.alignment) { + if (this.dominates(this.actor)) { + this.actor.kill("basic", this.actor); + } + } + } + } + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/VillageMightSurviveCondemn.js b/Games/types/Mafia/roles/cards/VillageMightSurviveCondemn.js index 1786eeab4..615c182c8 100644 --- a/Games/types/Mafia/roles/cards/VillageMightSurviveCondemn.js +++ b/Games/types/Mafia/roles/cards/VillageMightSurviveCondemn.js @@ -1,11 +1,12 @@ const Card = require("../../Card"); +const Action = require("../../Action"); const { PRIORITY_EFFECT_GIVER_DEFAULT } = require("../../const/Priority"); const Random = require("../../../../../lib/Random"); module.exports = class VillageMightSurviveCondemn extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_EFFECT_GIVER_DEFAULT, @@ -27,5 +28,40 @@ module.exports = class VillageMightSurviveCondemn extends Card { }, }, ]; +*/ + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_EFFECT_GIVER_DEFAULT, + labels: ["save"], + run: function () { + if (!this.actor.alive) return; + + const villagePlayers = this.game + .alivePlayers() + .filter( + (p) => + p.role.alignment == "Village" || p.role.winCount == "Village" + ); + + let shuffledPlayers = Random.randomizeArray(villagePlayers); + + shuffledPlayers[0].giveEffect("Condemn Immune", 5, 1); + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/Mafia/roles/cards/Weak.js b/Games/types/Mafia/roles/cards/Weak.js index 68adb3d6d..2ff6d5a57 100644 --- a/Games/types/Mafia/roles/cards/Weak.js +++ b/Games/types/Mafia/roles/cards/Weak.js @@ -6,7 +6,7 @@ const { PRIORITY_KILL_DEFAULT } = require("../../const/Priority"); module.exports = class Weak extends Card { constructor(role) { super(role); - + /* this.actions = [ { priority: PRIORITY_KILL_DEFAULT, @@ -54,5 +54,68 @@ module.exports = class Weak extends Card { }, }, ]; +*/ + + this.listeners = { + state: function (stateInfo) { + if (!this.player.alive) { + return; + } + + if (!stateInfo.name.match(/Night/)) { + return; + } + + var action = new Action({ + actor: this.player, + game: this.player.game, + priority: PRIORITY_KILL_DEFAULT, + labels: ["kill", "hidden"], + run: function () { + if ( + this.game.getStateName() != "Night" && + this.game.getStateName() != "Dawn" + ) + return; + + if (!this.actor.alive) return; + + for (let action of this.game.actions[0]) { + if (action.hasLabel("absolute")) { + continue; + } + if (action.hasLabel("mafia")) { + continue; + } + if (action.hasLabel("hidden")) { + continue; + } + + let toCheck = action.target; + if (!Array.isArray(action.target)) { + toCheck = [action.target]; + } + + if ( + action.actors.indexOf(this.actor) != -1 && + !action.hasLabel("hidden") && + action.target && + toCheck[0] instanceof Player + ) { + for (let y = 0; y < toCheck.length; y++) { + if (toCheck[y].role.alignment != this.actor.role.alignment) { + if (this.dominates(this.actor)) { + this.actor.kill("basic", this.actor); + } + } + } + } + } + }, + }); + + this.game.queueAction(action); + }, + }; } }; diff --git a/Games/types/WackyWords/Game.js b/Games/types/WackyWords/Game.js index f30dde1de..f531d9ff6 100644 --- a/Games/types/WackyWords/Game.js +++ b/Games/types/WackyWords/Game.js @@ -35,18 +35,44 @@ module.exports = class WackyWordsGame extends Game { // game settings this.roundAmt = options.settings.roundAmt; + this.acronymSize = options.settings.acronymSize; + this.enablePunctuation = options.settings.enablePunctuation; + this.standardiseCapitalisation = options.settings.standardiseCapitalisation; + this.turnOnCaps = options.settings.turnOnCaps; + this.hasAlien = this.setup.roles[0]["Alien:"]; this.hasNeighbor = this.setup.roles[0]["Neighbor:"]; - - if (this.hasAlien && this.hasNeighbor) { - // cannot be both game modes - choice = Random.randInt(0, 1); - if (choice == 0) { + this.hasGovernor = this.setup.roles[0]["Governor:"]; + this.hasHost = this.setup.roles[0]["Host:"]; + // cannot be both game modes + let possible = []; + if (this.hasAlien) { + possible.push("Alien"); + } + if (this.hasNeighbor) { + possible.push("Neighbor"); + } + if (this.hasGovernor) { + possible.push("Governor"); + } + if (possible.length > 1) { + possible = possible.randomizeArray(currentResponseHistory); + if (possible[0] == "Alien") { + this.hasNeighbor = false; + this.hasGovernor = false; + } + if (possible[0] == "Neighbor") { + this.hasAlien = false; + this.hasGovernor = false; + } + if (possible[0] == "Governor") { this.hasNeighbor = false; - } else { this.hasAlien = false; } } + if (this.hasAlien || this.hasNeighbor) { + this.hasHost = false; + } this.currentRound = 0; this.currentResponse = ""; @@ -67,6 +93,11 @@ module.exports = class WackyWordsGame extends Game { } start() { + if (this.hasHost) { + this.hostChoosePrompts = true; + this.promptMode = true; + this.shuffledQuestions = []; + } if (this.hasAlien) { this.shuffledQuestions = []; this.promptMode = true; @@ -90,8 +121,15 @@ module.exports = class WackyWordsGame extends Game { this.emptyResponseHistory(); if (this.shuffledQuestions.length > 0) { this.promptMode = false; + this.hostChoosePrompts = false; } - if (this.promptMode) { + if (this.hostChoosePrompts) { + if (this.hasGovernor) { + this.queueAlert(`Host is choosing Acronyms!`); + } else { + this.queueAlert(`Host is choosing Prompts!`); + } + } else if (this.promptMode) { // if generating questions round if (this.hasAlien) { this.generateNewPrompt(); @@ -99,7 +137,11 @@ module.exports = class WackyWordsGame extends Game { this.generatePlayerQuestions(); } } else { - this.generateNewQuestion(); + if (this.hasGovernor && !this.hasHost) { + this.generateNewAcronym(); + } else { + this.generateNewQuestion(); + } } return; } @@ -145,7 +187,11 @@ module.exports = class WackyWordsGame extends Game { this.shuffledQuestions.shift(); this.currentQuestion = question; - this.queueAlert(`The prompt is "${question}".`); + if (this.hasGovernor) { + this.queueAlert(`The acronym is "${question}".`); + } else { + this.queueAlert(`The prompt is "${question}".`); + } if (this.hasNeighbor) { for (let player of this.players) { @@ -163,12 +209,36 @@ module.exports = class WackyWordsGame extends Game { this.queueAlert( `Create a question that the prompt given is responding to. Go wild!` ); + } else if (this.hasGovernor) { + this.queueAlert( + `Create a word phrase starting with these letters. Go wild!` + ); } else { this.queueAlert(`Give a response to the prompt given. Go wild!`); } } } + generateNewAcronym() { + this.promptMode = false; + // JQVXZ are less likely to appear + const characters = "ABCDEFGHIKLMNOPRSTUWYABCDEFGHIJKLMNOPQRSTUVWXYZ"; + let acronym = ""; + for (var i = 0; i < this.acronymSize; i++) { + acronym += characters.charAt( + Math.floor(Math.random() * characters.length) + ); + } + this.currentQuestion = acronym; + this.queueAlert(`The acronym is ${acronym}.`); + + if (this.currentRound == 0) { + this.queueAlert( + `Give a ${this.acronymSize}-word phrase starting with these letters. Go wild!` + ); + } + } + generateNewPrompt() { var alive = this.players.filter((p) => p.alive); for (let player of alive) { @@ -415,6 +485,10 @@ module.exports = class WackyWordsGame extends Game { getGameTypeOptions() { return { roundAmt: this.roundAmt, + acronymSize: this.acronymSize, + enablePunctuation: this.enablePunctuation, + standardiseCapitalisation: this.standardiseCapitalisation, + turnOnCaps: this.turnOnCaps, }; } }; diff --git a/Games/types/WackyWords/roles/Host/Host.js b/Games/types/WackyWords/roles/Host/Host.js new file mode 100644 index 000000000..28c69bfd9 --- /dev/null +++ b/Games/types/WackyWords/roles/Host/Host.js @@ -0,0 +1,10 @@ +const Role = require("../../Role"); + +module.exports = class Host extends Role { + constructor(player, data) { + super("Host", player, data); + + this.alignment = "Host"; + this.cards = ["TownCore", "PromptMaker"]; + } +}; diff --git a/Games/types/WackyWords/roles/Town/Governor.js b/Games/types/WackyWords/roles/Town/Governor.js new file mode 100644 index 000000000..c9a3816a3 --- /dev/null +++ b/Games/types/WackyWords/roles/Town/Governor.js @@ -0,0 +1,10 @@ +const Role = require("../../Role"); + +module.exports = class Governor extends Role { + constructor(player, data) { + super("Governor", player, data); + + this.alignment = "Town"; + this.cards = ["TownCore", "ResponseGiver"]; + } +}; diff --git a/Games/types/WackyWords/roles/cards/PromptMaker.js b/Games/types/WackyWords/roles/cards/PromptMaker.js new file mode 100644 index 000000000..85c8710dc --- /dev/null +++ b/Games/types/WackyWords/roles/cards/PromptMaker.js @@ -0,0 +1,75 @@ +const Card = require("../../Card"); + +module.exports = class PromptMaker extends Card { + constructor(role) { + super(role); + + for (let x = 0; x < role.game.roundAmt; x++) { + this.meetings[`Create Prompt ${x}`] = { + actionName: "Create Prompt (1-200)", + states: ["Night"], + flags: ["voting"], + inputType: "text", + textOptions: { + minLength: 1, + maxLength: 200, + enforceAcronym: "", + submit: "Confirm", + }, + action: { + item: this, + run: function () { + if (this.game.hasGovernor) { + this.target = this.target.toUpperCase(); + for (let x = 0; x < this.target.length; x++) { + this.target = this.target.replace(" ", ""); + this.target = this.target.replace("[^a-zA-Z]", ""); + } + } + this.game.addResponse(this.target); + }, + }, + shouldMeet: function (meetingName) { + return this.game.hasHost && this.game.hostChoosePrompts; + }, + }; + } + /* + this.meetings = { + "Create Prompt": { + actionName: "Respond to Prompt (1-200)", + states: ["Night"], + flags: ["voting"], + inputType: "text", + textOptions: { + minLength: 1, + maxLength: 200, + enforceAcronym: "", + submit: "Confirm", + }, + action: { + item: this, + run: function () { + if (this.game.promptMode) { + if (this.game.hasAlien) { + this.game.addResponse(this.target); + } + if (this.game.hasNeighbor) { + //this.game.addResponse(this.game.questionNeighbor[this.actor]); + this.game.responseNeighbor[this.actor.name] = this.target; + } + } else { + this.game.recordResponse(this.actor, this.target); + } + }, + }, + shouldMeet: function () { + return ( + !this.game.hasNeighbor || this.player.name != this.game.realAnswerer + ); + }, + }, + }; + */ + } +}; diff --git a/Games/types/WackyWords/roles/cards/ResponseGiver.js b/Games/types/WackyWords/roles/cards/ResponseGiver.js index 76a70af19..5093e2808 100644 --- a/Games/types/WackyWords/roles/cards/ResponseGiver.js +++ b/Games/types/WackyWords/roles/cards/ResponseGiver.js @@ -33,11 +33,32 @@ module.exports = class ResponseGiver extends Card { }, }, shouldMeet: function () { + if (this.game.hasHost && this.game.hostChoosePrompts) { + return false; + } return ( !this.game.hasNeighbor || this.player.name != this.game.realAnswerer ); }, }, }; + + this.listeners = { + start: function () { + if (!this.game.hasGovernor) return; + if (!this.game.enablePunctuation) { + this.meetings["Give Response"].textOptions.alphaOnlyWithSpaces = true; + } + }, + state: function (stateInfo) { + if (!stateInfo.name.match(/Night/)) { + return; + } + if (!this.game.hasGovernor) return; + + this.meetings["Give Response"].textOptions.enforceAcronym = + this.game.currentQuestion; + }, + }; } }; diff --git a/data/modifiers.js b/data/modifiers.js index 9957b8cff..94a6f7cc8 100644 --- a/data/modifiers.js +++ b/data/modifiers.js @@ -2,13 +2,13 @@ const modifierData = { Mafia: { Armed: { internal: ["StartWithGun"], - tags: ["Items", "Killing", "Gun"], + tags: ["Items", "Killing", "Gun", "Day Killer"], description: "Starts with a gun.", allowDuplicate: true, }, Rifled: { internal: ["StartWithRifle"], - tags: ["Items", "Killing", "Gun", "Alignments"], + tags: ["Items", "Killing", "Gun", "Alignments", "Day Killer"], description: "Starts with a rifle.", allowDuplicate: true, }, @@ -32,7 +32,7 @@ const modifierData = { }, Steeled: { internal: ["StartWithKnife"], - tags: ["Bleeding", "Items", "Knife", "Killing"], + tags: ["Bleeding", "Items", "Knife", "Killing", "Day Killer"], description: "Starts with a knife.", allowDuplicate: true, }, @@ -213,14 +213,14 @@ const modifierData = { }, Vain: { internal: ["Vain"], - tags: ["Visits", "Killing", "Alignments"], + tags: ["Visits", "Killing", "Alignments", "Self Kill"], description: "If this player visits a player of the same alignment, they die.", incompatible: ["Weak"], }, Weak: { internal: ["Weak"], - tags: ["Visits", "Killing", "Alignments"], + tags: ["Visits", "Killing", "Alignments", "Self Kill"], description: "If this player visits a player of the opposite alignment, they die.", incompatible: ["Vain"], @@ -371,12 +371,12 @@ const modifierData = { }, Regretful: { internal: ["Regretful"], - tags: ["Killing", "Visits"], + tags: ["Killing", "Visits", "Self Kill"], description: "Will be killed if their target was killed.", }, Sacrificial: { internal: ["Sacrificial"], - tags: ["Sacrificial", "Killing"], + tags: ["Sacrificial", "Killing", "Self Kill"], description: "Will sacrifice themselves and die, if they ever visit another player.", }, @@ -460,7 +460,7 @@ const modifierData = { internal: ["Learn3ExcessRoles"], tags: ["Investigative", "Roles", "Excess Roles"], description: - "On night 1 learn 3 Excess Roles. Mafia/Cult roles will Always learn Village-Aligned Excess roles.", + "Starts knowing 3 Excess Roles. Mafia/Cult roles will Always learn Village-Aligned Excess roles.", }, Verrucose: { internal: ["GivePermaMindRot"], diff --git a/data/roles.js b/data/roles.js index 4d8bba2ce..393a08ddf 100644 --- a/data/roles.js +++ b/data/roles.js @@ -6,7 +6,7 @@ const roleData = { Villager: { alignment: "Village", category: "Basic", - tags: ["Villager", "Vanilla", "Basic"], + tags: ["Vanilla", "Basic"], description: [ "Wins when no Mafia, Cult, or Hostile Independents remain.", "Other roles appear as Villager to investigative roles, upon death, and to themself.", @@ -23,7 +23,7 @@ const roleData = { Celebrity: { alignment: "Village", category: "Basic", - tags: ["Reveal Self", "Basic"], + tags: ["Exposed", "Basic"], description: [ "Identity is publicly revealed to all players at the start of the game.", ], @@ -40,7 +40,7 @@ const roleData = { alignment: "Village", newlyAdded: true, category: "Basic", - tags: ["Items", "Basic", "Killing", "Alignments"], + tags: ["Items", "Killing", "Alignments", "Day Killer"], description: [ "Starts with a stake.", "Stakes can only kill players who appear as Cult or Mafia-aligned.", @@ -50,7 +50,7 @@ const roleData = { Deputy: { alignment: "Village", category: "Basic", - tags: ["Items", "Basic", "Killing", "Gun"], + tags: ["Items", "Basic", "Killing", "Gun", "Day Killer"], description: [ "Starts with a gun.", "This gun never reveals the deputy when shot.", @@ -68,7 +68,7 @@ const roleData = { Miller: { alignment: "Village", category: "Basic", - tags: ["Villager", "Basic", "Deception"], + tags: ["Humble", "Basic", "Deception"], description: [ "Appears as Villager to self.", "Appears as a random Mafia/Cult role to investigative roles.", @@ -79,7 +79,7 @@ const roleData = { "Party Host": { alignment: "Village", category: "Basic", - tags: ["Meetings", "Basic", "Party"], + tags: ["Meetings", "Basic"], description: [ "Chooses to host a party during day meeting for everyone to attend once per game on the following night.", "Everyone will share a party meeting at night.", @@ -88,7 +88,7 @@ const roleData = { Sapling: { alignment: "Village", category: "Basic", - tags: ["Tree", "Basic", "Voting", "Immortal", "Condemn Immune"], + tags: ["Tree", "Basic", "Voting", "Unkillable", "Condemn Immunity"], description: [ "Chooses whether or not to grow into a tree at night.", "Tree is immune to most ways of dying.", @@ -98,7 +98,7 @@ const roleData = { Sheriff: { alignment: "Village", category: "Basic", - tags: ["Items", "Basic", "Killing", "Gun"], + tags: ["Items", "Basic", "Killing", "Gun", "Day Killer"], description: [ "Starts with a gun.", "This gun always reveals the sheriff when shot.", @@ -106,7 +106,7 @@ const roleData = { }, Sleepwalker: { alignment: "Village", - tags: ["Visits", "Basic"], + tags: ["Visiting", "Basic"], category: "Basic", description: ["Visits a random player each night."], }, @@ -115,7 +115,7 @@ const roleData = { alignment: "Village", newlyAdded: true, category: "Protective", - tags: ["Protective", "Night Saver", "Mind Rot"], + tags: ["Protective", "Night Saver", "Mind Rot", "Visiting"], description: [ "Protects two players every night.", "One of the players being protected in inflicted with Mind Rot.", @@ -124,7 +124,7 @@ const roleData = { Bodyguard: { alignment: "Village", category: "Protective", - tags: ["Protective", "Killing"], + tags: ["Protective", "Killing", "Self Kill", "Visiting"], description: [ "Guards one player every night", "If the target was attacked, the Bodyguard will kill one attacker and die.", @@ -134,13 +134,13 @@ const roleData = { Doctor: { alignment: "Village", category: "Protective", - tags: ["Protective", "Night Saver"], + tags: ["Protective", "Night Saver", "Visiting"], description: ["Saves another player from dying each night."], }, Martyr: { alignment: "Village", category: "Protective", - tags: ["Protective", "Condemn"], + tags: ["Protective", "Condemn", "Visiting"], description: [ "Can choose to sacrifice themself and be condemned in the place of the player currently being condemned.", ], @@ -148,7 +148,7 @@ const roleData = { Medic: { alignment: "Village", category: "Protective", - tags: ["Protective", "Extra Lives"], + tags: ["Protective", "Extra Lives", "Visiting"], description: [ "Visits two players each night.", "If the first player is targeted for a night kill and dies, the second player gains an extra life.", @@ -157,7 +157,7 @@ const roleData = { Nurse: { alignment: "Village", category: "Protective", - tags: ["Protective", "Malicious Effects"], + tags: ["Protective", "Cleanse", "Visiting"], description: [ "Visits one player each night and cleanses them of malicious effects.", "Malicious effects include poison, bleeding, insanity, and polarization.", @@ -166,7 +166,7 @@ const roleData = { Resurrectionist: { alignment: "Village", category: "Protective", - tags: ["Revive", "Protective", "Graveyard"], + tags: ["Revive", "Protective", "Graveyard", "Visiting"], description: [ "Visits a dead player during the night once per game.", "That player will be resurrected the following day.", @@ -177,7 +177,7 @@ const roleData = { Shrink: { alignment: "Village", category: "Protective", - tags: ["Convert Saver", "Protective", "Conversion", "Villager"], + tags: ["Convert Saver", "Protective", "Conversion", "Visiting"], description: [ "Prevents their target from being converted to another role.", "If their target was a Hostile Independent, the target will become a Villager.", @@ -186,7 +186,13 @@ const roleData = { Surgeon: { alignment: "Village", category: "Protective", - tags: ["Convert Saver", "Protective", "Killing", "Night Saver"], + tags: [ + "Convert Saver", + "Protective", + "Killing", + "Night Saver", + "Visiting", + ], description: [ "Each night, operates on one player to prevent them from dying or being converted.", "If attacked, kills one of their killers", @@ -195,7 +201,7 @@ const roleData = { "Tea Lady": { alignment: "Village", category: "Protective", - tags: ["Protective", "Condemn Immune", "Immortal", "Neighbors"], + tags: ["Protective", "Condemn", "Neighbors", "Night Saver"], description: [ "If both of the Tea Lady's neighbors are aligned with the Village, the neighbors can't die.", ], @@ -204,7 +210,7 @@ const roleData = { Baker: { alignment: "Village", category: "Gifting", - tags: ["Gifting", "Famine", "Items", "Bread"], + tags: ["Gifting", "Famine", "Items", "Bread", "Visiting"], description: [ "When baker is present in the game, all players start with two breads. A famine will start.", "Gives out up to two breads each night.", @@ -214,7 +220,7 @@ const roleData = { Blacksmith: { alignment: "Village", category: "Gifting", - tags: ["Gifting", "Protective", "Items", "Armor"], + tags: ["Gifting", "Protective", "Items", "Armor", "Visiting"], description: [ "Gives out armor to one player each night.", "Armor will protect from one attack before breaking.", @@ -223,7 +229,14 @@ const roleData = { Chandler: { alignment: "Village", category: "Gifting", - tags: ["Gifting", "Information", "Items", "Candle", "Visits"], + tags: [ + "Gifting", + "Information", + "Items", + "Candle", + "Visit Interaction", + "Visiting", + ], description: [ "Gives out a candle to one player each night.", "Candles will tell a player the names of their visitors from the previous night.", @@ -232,7 +245,7 @@ const roleData = { Cutler: { alignment: "Village", category: "Gifting", - tags: ["Gifting", "Bleeding", "Items", "Knife", "Killing"], + tags: ["Gifting", "Bleeding", "Items", "Knife", "Killing", "Visiting"], description: [ "Gives out a knife each night.", "Knives can be used to attack another player, causing them to bleed.", @@ -241,7 +254,7 @@ const roleData = { Demolitionist: { alignment: "Village", category: "Gifting", - tags: ["Gifting", "Items", "Bomb", "Killing"], + tags: ["Gifting", "Items", "Bomb", "Killing", "Visiting"], description: [ "Gives out bomb to one player each night.", "If a player holding a bomb is attacked, their attacker will die along with them.", @@ -250,7 +263,14 @@ const roleData = { Falconer: { alignment: "Village", category: "Gifting", - tags: ["Gifting", "Information", "Items", "Falcon", "Visits"], + tags: [ + "Gifting", + "Information", + "Items", + "Falcon", + "Visit Interaction", + "Visiting", + ], description: [ "Gives out a falcon to one player each night.", "Falcons can be used to track another player's movements during the night.", @@ -259,7 +279,7 @@ const roleData = { Funsmith: { alignment: "Village", category: "Gifting", - tags: ["Gifting", "Killing", "Items", "Gun", "Reflexive"], + tags: ["Gifting", "Killing", "Items", "Gun", "Reflexive", "Visiting"], description: [ "Gives out a gun each night.", "Gives out a gun to all visitors at night.", @@ -268,7 +288,7 @@ const roleData = { Gemcutter: { alignment: "Village", category: "Gifting", - tags: ["Gifting", "Revealing", "Items", "Crystal"], + tags: ["Gifting", "Revealing", "Items", "Crystal", "Visiting"], description: [ "Gives out a crystal ball to a player each night.", "If a player holding the crystal ball dies, their target's role will be revealed.", @@ -277,7 +297,7 @@ const roleData = { Gunsmith: { alignment: "Village", category: "Gifting", - tags: ["Gifting", "Killing", "Items", "Gun"], + tags: ["Gifting", "Killing", "Items", "Gun", "Visiting"], description: [ "Gives out a gun each night.", "Guns can be used to shoot and kill someone during the day.", @@ -286,7 +306,7 @@ const roleData = { Keymaker: { alignment: "Village", category: "Gifting", - tags: ["Gifting", "Role Blocker", "Items", "Key"], + tags: ["Gifting", "Role Blocker", "Items", "Key", "Visiting"], description: [ "Gives out a key to one player each night.", "Keys can be used to lock a player in the next night; they cannot be visited, but also cannot perform any actions.", @@ -295,7 +315,7 @@ const roleData = { Mailman: { alignment: "Village", category: "Gifting", - tags: ["Gifting", "Messages", "Items", "Envelope"], + tags: ["Gifting", "Messages", "Items", "Envelope", "Visiting"], description: [ "Gives out an envelope to one player each night.", "Envelopes can be used to send an anonymous message to another player at night.", @@ -304,7 +324,14 @@ const roleData = { Missionary: { alignment: "Village", category: "Gifting", - tags: ["Gifting", "Convert Saver", "Items", "Tract", "Protective"], + tags: [ + "Gifting", + "Convert Saver", + "Items", + "Tract", + "Protective", + "Visiting", + ], description: [ "Gives out a tract to one player each night.", "Tracts will prevent one conversion attempt.", @@ -313,7 +340,7 @@ const roleData = { Pharmacist: { alignment: "Village", category: "Gifting", - tags: ["Gifting", "Role Blocker", "Items", "Whiskey"], + tags: ["Gifting", "Role Blocker", "Items", "Whiskey", "Visiting"], description: [ "Gives out a bottle of whiskey each night.", "Whiskey can be used to distract another player, preventing them from acting the next night.", @@ -322,7 +349,7 @@ const roleData = { Reanimator: { alignment: "Village", category: "Gifting", - tags: ["Gifting", "Revive", "Items", "Syringe", "Graveyard"], + tags: ["Gifting", "Revive", "Items", "Syringe", "Graveyard", "Visiting"], description: [ "Gives out a syringe each night.", "Syringes can be used on dead players to resurrect them.", @@ -332,7 +359,16 @@ const roleData = { Santa: { alignment: "Village", category: "Gifting", - tags: ["Gifting", "Items", "Graveyard", "Investigative", "Alignments"], + tags: [ + "Gifting", + "Items", + "Graveyard", + "Investigative", + "Alignments", + "Visiting", + "Information", + "Self Blocking", + ], description: [ "Visits a player each night to learn their role alignment.", "If not visited during the night, will learn whether that player is naughty or nice.", @@ -345,7 +381,7 @@ const roleData = { alignment: "Village", newlyAdded: true, category: "Investigative", - tags: ["Investigative", "Roles"], + tags: ["Investigative", "Roles", "Visiting"], description: [ "Attempts to guess the roles of up to five players.", "Learns how many of the guesses were correct.", @@ -365,7 +401,7 @@ const roleData = { Bloodhound: { alignment: "Village", category: "Investigative", - tags: ["Investigative", "Visits"], + tags: ["Investigative", "Visits", "Visiting"], description: [ "Tracks a player each night and learns if they visited anybody.", ], @@ -374,7 +410,7 @@ const roleData = { alignment: "Village", recentlyUpdated: true, category: "Investigative", - tags: ["Investigative", "Visits", "Reports"], + tags: ["Investigative", "Visits", "Reports", "Visiting"], description: [ "Each night chooses 2 players, Learns how many of those players visited or received reports", ], @@ -382,7 +418,7 @@ const roleData = { Cop: { alignment: "Village", category: "Investigative", - tags: ["Investigative", "Alignment", "Cop"], + tags: ["Investigative", "Alignment", "Cop", "Visiting"], description: [ "Investigates one player each night and learns their alignment.", "Some other roles appear as Cop to themself.", @@ -391,7 +427,7 @@ const roleData = { "Insane Cop": { alignment: "Village", category: "Investigative", - tags: ["Investigative", "Alignment", "Cop"], + tags: ["Investigative", "Alignment", "Cop", "Visiting"], description: [ "Investigates one player each night and learns their alignment (alignment will be reversed).", "Appears as normal cop upon death.", @@ -400,7 +436,7 @@ const roleData = { "Naive Cop": { alignment: "Village", category: "Investigative", - tags: ["Investigative", "Alignment", "Cop"], + tags: ["Investigative", "Alignment", "Cop", "Visiting"], description: [ "Investigates one player each night and learns their alignment (alignments will always appear innocent).", "Appears as normal cop upon death.", @@ -409,7 +445,7 @@ const roleData = { "Paranoid Cop": { alignment: "Village", category: "Investigative", - tags: ["Investigative", "Alignment", "Cop"], + tags: ["Investigative", "Alignment", "Cop", "Visiting"], description: [ "Investigates one player each night and learns their alignment (alignments will always appear guilty).", "Appears as normal cop upon death.", @@ -418,7 +454,7 @@ const roleData = { "Confused Cop": { alignment: "Village", category: "Investigative", - tags: ["Investigative", "Alignment", "Cop"], + tags: ["Investigative", "Alignment", "Cop", "Visiting"], description: [ "Investigates one player each night and learns their alignment (alignments will always be random).", "Appears as normal cop upon death.", @@ -427,7 +463,7 @@ const roleData = { Coroner: { alignment: "Village", category: "Investigative", - tags: ["Investigative", "Roles", "Dead"], + tags: ["Investigative", "Roles", "Dead", "Visiting"], description: [ "Chooses to visit a dead player at night and learns their role identity.", ], @@ -435,7 +471,7 @@ const roleData = { Detective: { alignment: "Village", category: "Investigative", - tags: ["Investigative", "Roles"], + tags: ["Investigative", "Roles", "Visiting"], description: [ "Investigates one player each night and learns their role.", ], @@ -497,7 +533,7 @@ const roleData = { Diviner: { alignment: "Village", category: "Investigative", - tags: ["Investigative", "Roles"], + tags: ["Investigative", "Roles", "Visiting"], description: [ "Investigates one player each night and learns their role and another role of the opposite alignment.", ], @@ -505,7 +541,7 @@ const roleData = { Journalist: { alignment: "Village", category: "Investigative", - tags: ["Investigative", "Reports"], + tags: ["Investigative", "Reports", "Visiting"], description: [ "Chooses a player each night and views any reports they receive the following day.", ], @@ -513,7 +549,7 @@ const roleData = { Justice: { alignment: "Village", category: "Investigative", - tags: ["Investigative", "Alignment"], + tags: ["Investigative", "Alignment", "Visiting"], description: [ "Investigates two players at night and learns if they share an alignment.", ], @@ -553,7 +589,7 @@ const roleData = { Manhunter: { alignment: "Village", category: "Investigative", - tags: ["Investigative", "Roles"], + tags: ["Investigative", "Roles", "Visiting"], description: [ "Chooses a player and a role and learns if they are that role or not.", ], @@ -561,7 +597,7 @@ const roleData = { Pathologist: { alignment: "Village", category: "Investigative", - tags: ["Investigative", "Reports", "Dead"], + tags: ["Investigative", "Reports", "Dead", "Visiting"], description: [ "Each night, visits one dead player.", "Will receive a list of all visitors that player ever received, but not specific actions or days.", @@ -570,7 +606,13 @@ const roleData = { Psychic: { alignment: "Village", category: "Investigative", - tags: ["Investigative", "Alignment"], + tags: [ + "Investigative", + "Alignment", + "Visiting", + "Reflexive", + "Self Blocking", + ], description: [ "Each night, reads the mind of someone and learns their true alignment.", "Will learn nothing if disturbed at night.", @@ -595,7 +637,7 @@ const roleData = { Snoop: { alignment: "Village", category: "Investigative", - tags: ["Investigative", "Items"], + tags: ["Investigative", "Items", "Visiting"], description: [ "Visits a player each night and learns what items they are carrying.", ], @@ -603,7 +645,7 @@ const roleData = { Tracker: { alignment: "Village", category: "Investigative", - tags: ["Investigative", "Visits"], + tags: ["Investigative", "Visits", "Visiting"], description: ["Tracks a player each night and learns who they visited."], }, Voyeur: { @@ -637,7 +679,14 @@ const roleData = { Avenger: { alignment: "Village", category: "Night-acting", - tags: ["Night-acting", "Killing", "Gun", "Items"], + tags: [ + "Night-acting", + "Killing", + "Gun", + "Items", + "Visiting", + "Day Killer", + ], description: [ "Each night, chooses someone to avenge.", "Gets a gun if their chosen target dies.", @@ -646,7 +695,7 @@ const roleData = { Caroler: { alignment: "Village", category: "Night-acting", - tags: ["Night-acting", "Information", "Alignment"], + tags: ["Night-acting", "Information", "Alignment", "Visiting"], description: [ "Each night, sings a carol to a player about 3 players, at least one of whom is Mafia or Cult.", "The carol is not heard if the player chosen visits at night.", @@ -656,7 +705,7 @@ const roleData = { Comedian: { alignment: "Village", category: "Night-acting", - tags: ["Night-acting", "Information", "Roles"], + tags: ["Night-acting", "Information", "Roles", "Visiting"], description: [ "Each night, tells a joke to a player about 3 roles, and a different player who is one of the roles.", "The joke is not heard if the target chosen visits at night.", @@ -667,7 +716,7 @@ const roleData = { alignment: "Village", recentlyAdded: true, category: "Night-acting", - tags: ["Night-acting", "Dead", "Graveyard", "Exorcise"], + tags: ["Night-acting", "Dead", "Graveyard", "Exorcise", "Visiting"], description: [ "Each Night, the Exorcist can Exorcise a dead Player.", "Exorcised players can't be revived or use Graveyard abilites.", @@ -687,7 +736,7 @@ const roleData = { Drunk: { alignment: "Village", category: "Night-acting", - tags: ["Night-acting", "Role Blocker"], + tags: ["Night-acting", "Role Blocker", "Visiting"], description: [ "Visits one player each night and blocks them from performing any night actions.", "Some actions cannot be blocked.", @@ -697,7 +746,13 @@ const roleData = { alignment: "Village", newlyAdded: true, category: "Night-acting", - tags: ["Night-acting", "Mind Rot", "Immortal", "Condemn Immune"], + tags: [ + "Night-acting", + "Mind Rot", + "Unkillable", + "Condemn Immune", + "Visiting", + ], description: [ "Visits one player each night and inflicts them with Mind Rot.", "Mind Rot blocks all non-Investigative actions.", @@ -709,7 +764,7 @@ const roleData = { alignment: "Village", recentlyUpdated: true, category: "Night-acting", - tags: ["Night-acting", "Conversion", "Role Swapping"], + tags: ["Night-acting", "Conversion", "Role Swapping", "Visiting"], description: [ "Each night chooses a player.", "Swaps roles with that player.", @@ -721,7 +776,7 @@ const roleData = { alignment: "Village", newlyAdded: true, category: "Night-acting", - tags: ["Night-acting", "Conversion", "Role Swapping"], + tags: ["Night-acting", "Conversion", "Role Swapping", "Visiting"], description: [ "Each night chooses 2 players.", "The selected players will swap roles.", @@ -730,13 +785,13 @@ const roleData = { Guard: { alignment: "Village", category: "Night-acting", - tags: ["Night-acting", "Role Blocker", "Visits"], + tags: ["Night-acting", "Role Blocker", "Visiting"], description: ["Each night, protects one player from all visits."], }, Marathoner: { alignment: "Village", category: "Night-acting", - tags: ["Night-acting", "Visits"], + tags: ["Night-acting", "Visiting"], description: [ "Once per game, visits every other player during the night.", ], @@ -744,7 +799,7 @@ const roleData = { Mechanic: { alignment: "Village", category: "Night-acting", - tags: ["Night-acting", "Items"], + tags: ["Night-acting", "Items", "Visiting"], description: [ "Once per night, fixes the target's item(s).", "Can undo an item's fabricated/sabotaged status, and can turn Gunrunner guns into normal guns and Gremlin guns into normal guns.", @@ -754,7 +809,7 @@ const roleData = { Mime: { alignment: "Village", category: "Night-acting", - tags: ["Night-acting", "Villager", "Conversion", "Alignment"], + tags: ["Night-acting", "Villager", "Conversion", "Alignment", "Visiting"], description: [ "Chooses a player at night and attempts to mime their role.", "If player is Village, Mime steals their role and that player becomes a villager.", @@ -766,14 +821,14 @@ const roleData = { alignment: "Village", newlyAdded: true, category: "Night-acting", - tags: ["Night-acting", "Modifiers", "Conversion"], + tags: ["Night-acting", "Modifiers", "Conversion", "Visiting"], description: ["Removes modifiers from other players at night"], }, Photographer: { alignment: "Village", recentlyUpdated: true, category: "Night-acting", - tags: ["Night-acting", "Revealing"], + tags: ["Night-acting", "Revealing", "Visiting"], description: [ "Each Night the Photographer can take a picture of a player during the night.", "The role of the photographed player will be revealed to everyone the next day.", @@ -791,7 +846,7 @@ const roleData = { Vegan: { alignment: "Village", category: "Night-acting", - tags: ["Night-acting", "Revealing", "Selective Revealing"], + tags: ["Night-acting", "Revealing", "Selective Revealing", "Visiting"], description: [ "Chooses a player each night to reveal their identity as Vegan.", ], @@ -799,7 +854,7 @@ const roleData = { Oracle: { alignment: "Village", category: "Night-acting", - tags: ["Night-acting", "Revealing", "Sacrificial"], + tags: ["Night-acting", "Revealing", "Sacrificial", "Visiting"], description: [ "Visits one player each night whose role will be revealed upon death.", ], @@ -807,13 +862,13 @@ const roleData = { Penguin: { alignment: "Village", category: "Night-acting", - tags: ["Night-acting", "Information", "Items", "Visits"], + tags: ["Night-acting", "Information", "Items", "Visits", "Visiting"], description: ["Each night, waddles up to someone to tell them a secret."], }, "Robin Hood": { alignment: "Village", category: "Night-acting", - tags: ["Night-acting", "Items"], + tags: ["Night-acting", "Items", "Visiting"], description: [ "Chooses one player to steal from each night and another player to receive their items.", "If the player chosen to receive an item is mafia, the steal will not go through.", @@ -822,7 +877,7 @@ const roleData = { Visitor: { alignment: "Village", category: "Night-acting", - tags: ["Night-acting", "Visits"], + tags: ["Night-acting", "Visiting"], description: [ "Pays a visit to another player at night.", "Annoyingly, this visit has no effect.", @@ -832,12 +887,12 @@ const roleData = { Waitress: { alignment: "Village", category: "Night-acting", - tags: ["Night-acting", "Items"], + tags: ["Night-acting", "Items", "Visiting"], description: ["Chooses a player to steal an item from each night."], }, "Drama Queen": { alignment: "Village", - tags: ["Night-acting"], + tags: ["Night-acting", "Visiting"], description: [ "Each night, starts drama against one player.", "The following day, targeted player has two options:", @@ -870,7 +925,7 @@ const roleData = { Hunter: { alignment: "Village", category: "Sacrificial", - tags: ["Sacrificial", "Killing", "Dusk"], + tags: ["Sacrificial", "Killing", "Dusk", "Day Killer"], description: [ "Chooses a player to kill when condemned by town during the day.", ], @@ -903,7 +958,7 @@ const roleData = { Sheep: { alignment: "Village", category: "Sacrificial", - tags: ["Sacrificial", "Killing", "Setup Changes"], + tags: ["Sacrificial", "Killing", "Setup Changes", "Self Kill"], description: [ "If one Sheep dies, all Sheep die.", "Adds 1 Sheep in Closed setups", @@ -955,7 +1010,7 @@ const roleData = { alignment: "Village", newlyAdded: true, category: "Voting", - tags: ["Voting"], + tags: ["Voting", "Visiting"], description: [ "Vote weight is worth 0 votes", "Each night chooses a player to have a Vote weight of 2 the following day", @@ -982,7 +1037,7 @@ const roleData = { Kingmaker: { alignment: "Village", category: "Voting", - tags: ["Voting", "Items"], + tags: ["Voting", "Items", "Visiting"], description: [ "Gives out a sceptre each night.", "Sceptres give the player final say in the village vote for one turn.", @@ -992,7 +1047,7 @@ const roleData = { alignment: "Village", recentlyUpdated: true, category: "Voting", - tags: ["Voting", "Condemn", "Condemn Immune"], + tags: ["Voting", "Condemn", "Condemn Immune", "Visiting"], description: [ "Every night, chooses one player and prevents them from voting and from being voted.", "Cannot place themselves under house arrest.", @@ -1040,7 +1095,7 @@ const roleData = { Coward: { alignment: "Village", category: "Manipulative", - tags: ["Manipulative", "Redirection", "Reflexive"], + tags: ["Manipulative", "Redirection", "Reflexive", "Visiting"], description: [ "Each night, chooses one player to redirect all visitors to.", ], @@ -1048,7 +1103,7 @@ const roleData = { Chauffeur: { alignment: "Village", category: "Manipulative", - tags: ["Manipulative", "Redirection"], + tags: ["Manipulative", "Redirection", "Visiting"], description: [ "Chooses two players, A and B, each night.", "Players who visit A will be redirected to B.", @@ -1101,7 +1156,7 @@ const roleData = { Capybara: { alignment: "Village", category: "Meeting", - tags: ["Meeting", "Food", "Items"], + tags: ["Meeting", "Food", "Items", "Visiting"], description: [ "Chooses a player to invite to a hot springs relaxation by giving them a Yuzu Orange each night.", "When holding a Yuzu Orange, player can choose during the day to anonymously meet with the Capybara and other Yuzu Orange holders the following night.", @@ -1120,7 +1175,7 @@ const roleData = { Freemason: { alignment: "Village", category: "Meeting", - tags: ["Meeting", "Conversion", "Alignment"], + tags: ["Meeting", "Conversion", "Alignment", "Visiting"], description: [ "Converts one player into a Freemason each night.", "Shares a night meeting with other Freemasons.", @@ -1202,7 +1257,7 @@ const roleData = { Debtor: { alignment: "Village", category: "Killing", - tags: ["Killing", "Information", "Roles"], + tags: ["Killing", "Information", "Roles", "Visiting", "Self Kill"], description: [ "Each night must choose a player and role from the Setup.", "If the selected role is not the player's role, The Debtor dies.", @@ -1211,7 +1266,7 @@ const roleData = { Firebrand: { alignment: "Village", category: "Killing", - tags: ["Killing", "Gasoline"], + tags: ["Killing", "Gasoline", "Visiting", "Day Killer"], description: [ "Douses one player with Gasoline each night.", "Chooses to light a match during the day to burn doused players to ashes.", @@ -1230,7 +1285,14 @@ const roleData = { Jailer: { alignment: "Village", category: "Killing", - tags: ["Killing", "Meeting", "Role Blocker", "Condemn", "Dusk"], + tags: [ + "Killing", + "Meeting", + "Role Blocker", + "Condemn", + "Dusk", + "Night Killer", + ], description: [ "If no one was condemned, chooses a player to jail after each day meeting.", "Meets with the prisoner at night and the prisoner cannot perform actions or attend other meetings or be targeted.", @@ -1240,7 +1302,7 @@ const roleData = { Seeker: { alignment: "Village", category: "Killing", - tags: ["Killing", "Setup Change", "Hide and Seek"], + tags: ["Killing", "Setup Change", "Hide and Seek", "Visiting"], description: [ "Attempts to guess the identity of the Hider or Invader each night.", "Kills the Hider/Invader if guess is correct.", @@ -1261,7 +1323,7 @@ const roleData = { Trapper: { alignment: "Village", category: "Killing", - tags: ["Killing", "Visits"], + tags: ["Killing", "Visits", "Visiting"], description: [ "Each night, visits one player and kills one of their visitors.", "Preferentially kills Mafia, Cult, Independents, then Villagers.", @@ -1271,7 +1333,7 @@ const roleData = { Vigilante: { alignment: "Village", category: "Killing", - tags: ["Killing"], + tags: ["Killing", "Visiting", "Night Killer"], description: ["Kills one player each night."], }, //speaking roles @@ -1485,7 +1547,7 @@ const roleData = { Sniper: { alignment: "Mafia", category: "Basic", - tags: ["Killing", "Basic", "Gun", "Items"], + tags: ["Killing", "Basic", "Gun", "Items", "Day Killer"], description: [ "Starts with a gun.", "Gun does not reveal identity when fired.", @@ -1495,7 +1557,7 @@ const roleData = { Arsonist: { alignment: "Mafia", category: "Killing", - tags: ["Killing", "Gasoline"], + tags: ["Killing", "Gasoline", "Visiting", "Day Killer"], description: [ "Douses one player with Gasoline each night.", "Chooses to light a match during the day to burn doused players to ashes.", @@ -1504,7 +1566,7 @@ const roleData = { Caporegime: { alignment: "Mafia", category: "Killing", - tags: ["Killing", "Visits", "Extra Night Deaths"], + tags: ["Killing", "Visits", "Extra Night Deaths", "Visiting"], description: [ "Gives the kiss of death to someone each night.", "Target will die if visited by a non-Mafia player that night.", @@ -1513,7 +1575,7 @@ const roleData = { Hider: { alignment: "Mafia", category: "Killing", - tags: ["Killing", "Setup Change", "Hide and Seek"], + tags: ["Killing", "Setup Change", "Hide and Seek", "Visiting"], description: [ "Attempts to guess the identity of the Seeker or Invader each night.", "Kills the Seeker/Invader if guess is correct.", @@ -1523,13 +1585,13 @@ const roleData = { Hitman: { alignment: "Mafia", category: "Killing", - tags: ["Killing", "Extra Night Deaths"], + tags: ["Killing", "Extra Night Deaths", "Visiting", "Night Killer"], description: ["Kills one player each night."], }, Jinx: { alignment: "Mafia", category: "Killing", - tags: ["Killing", "Word Kill"], + tags: ["Killing", "Word Kill", "Visiting"], description: [ "Curses a player with a forbidden word each night.", "If the player speaks the word the next day, they will die.", @@ -1538,7 +1600,13 @@ const roleData = { Poisoner: { alignment: "Mafia", category: "Killing", - tags: ["Killing", "Poison", "Malicious Effects"], + tags: [ + "Killing", + "Poison", + "Malicious Effects", + "Visiting", + "Night Killer", + ], description: [ "Concocts a deadly poison and administers it to one player each night.", "The poisoned target will die at the end of the following night unless saved.", @@ -1556,7 +1624,7 @@ const roleData = { Rottweiler: { alignment: "Mafia", category: "Killing", - tags: ["Killing", "Extra Night Deaths", "Visits"], + tags: ["Killing", "Extra Night Deaths", "Visits", "Visiting"], description: [ "Each night, visits one player and kills one of their visitors.", "Other visitors will learn the identity of the Rottweiler.", @@ -1565,7 +1633,7 @@ const roleData = { Terrorist: { alignment: "Mafia", category: "Killing", - tags: ["Killing", "Sacrificial", "Visits"], + tags: ["Killing", "Sacrificial", "Day Killer"], description: [ "Once per game, can rush at another player during the day, killing them both.", ], @@ -1574,7 +1642,7 @@ const roleData = { Actress: { alignment: "Mafia", category: "Investigative", - tags: ["Investigative", "Roles", "Deception", "Suits"], + tags: ["Investigative", "Roles", "Deception", "Suits", "Visiting"], description: [ "Visits a player to appears as their role.", "Learns chosen player's role.", @@ -1583,7 +1651,7 @@ const roleData = { Bondsman: { alignment: "Mafia", category: "Investigative", - tags: ["Investigative", "Roles"], + tags: ["Investigative", "Roles", "Visiting"], description: [ "Chooses a player and a role and learns if they are that role or not.", ], @@ -1609,7 +1677,7 @@ const roleData = { Informant: { alignment: "Mafia", category: "Investigative", - tags: ["Investigative", "Reports"], + tags: ["Investigative", "Reports", "Visiting"], description: [ "Chooses a player each night and views any reports they receive the following day.", ], @@ -1626,7 +1694,7 @@ const roleData = { Lurker: { alignment: "Mafia", category: "Investigative", - tags: ["Investigative", "Visits"], + tags: ["Investigative", "Visits", "Visiting"], description: [ "Tracks a player each night and learns if they visited anybody.", ], @@ -1634,7 +1702,7 @@ const roleData = { Revisionist: { alignment: "Mafia", category: "Investigative", - tags: ["Investigative", "Reports", "Dead"], + tags: ["Investigative", "Reports", "Dead", "Visiting"], description: [ "Each night, visits one dead player.", "Will receive all system messages the player ever received.", @@ -1643,19 +1711,19 @@ const roleData = { Scout: { alignment: "Mafia", category: "Investigative", - tags: ["Investigative", "Visits"], + tags: ["Investigative", "Visits", "Visiting"], description: ["Tracks a player each night and learns who they visited."], }, Stalker: { alignment: "Mafia", category: "Investigative", - tags: ["Investigative", "Roles"], + tags: ["Investigative", "Roles", "Visiting"], description: ["Stalks one player each night and learns their role."], }, //unsorted Hooker: { alignment: "Mafia", - tags: ["Role Blocker", "Night-acting"], + tags: ["Role Blocker", "Night-acting", "Visiting"], description: [ "Visits one player each night and blocks them from performing any night actions.", "Some actions cannot be blocked.", @@ -1672,7 +1740,7 @@ const roleData = { }, Driver: { alignment: "Mafia", - tags: ["Manipulative", "Redirection"], + tags: ["Manipulative", "Redirection", "Visiting"], description: [ "Chooses two players, A and B, each night.", "Players who visit A will be redirected to B.", @@ -1683,7 +1751,7 @@ const roleData = { }, Gondolier: { alignment: "Mafia", - tags: ["Manipulative", "Redirection", "Control"], + tags: ["Manipulative", "Redirection", "Control", "Visiting"], description: [ "Chooses one player to control.", "Chooses who that player will perform their actions on. (Not a Visit)", @@ -1692,7 +1760,7 @@ const roleData = { }, Snitch: { alignment: "Mafia", - tags: ["Manipulative", "Redirection"], + tags: ["Manipulative", "Redirection", "Visiting"], description: [ "Chooses one player every night to snitch on.", "Chooses another player to divert attention from and redirect their visitors to the first target.", @@ -1718,7 +1786,7 @@ const roleData = { }, Santista: { alignment: "Mafia", - tags: ["Meeting", "Conversion", "Alignment"], + tags: ["Meeting", "Conversion", "Alignment", "Visiting"], description: [ "Shares a night meeting with the Freemasons.", "Can convert players to Freemasons.", @@ -1726,7 +1794,7 @@ const roleData = { }, Lawyer: { alignment: "Mafia", - tags: ["Deception", "Alignment"], + tags: ["Deception", "Alignment", "Visiting"], description: [ "Chooses a player each night and flips their alignment to investigative roles.", ], @@ -1761,7 +1829,7 @@ const roleData = { alignment: "Mafia", recentlyAdded: true, category: "Night-acting", - tags: ["Night-acting", "Dead", "Graveyard", "Exorcise"], + tags: ["Night-acting", "Dead", "Graveyard", "Exorcise", "Visiting"], description: [ "Each Night, the Ghostbuster can Exorcise a dead Player.", "Exorcised players can't be revived or use Graveyard abilites.", @@ -1783,7 +1851,7 @@ const roleData = { }, Gunrunner: { alignment: "Mafia", - tags: ["Gifting", "Killing", "Items", "Gun", "Tommy"], + tags: ["Gifting", "Killing", "Items", "Gun", "Tommy", "Visiting"], description: [ "Gives out a tommy gun each night.", "Tommy gun will only kill the target if not aligned with the Mafia.", @@ -1792,7 +1860,7 @@ const roleData = { }, Tailor: { alignment: "Mafia", - tags: ["Gifting", "Deception", "Items", "Suits"], + tags: ["Gifting", "Deception", "Items", "Suits", "Visiting"], description: [ "Gives out a suit each night that disguises the wearer's role identity.", "Suits can be selected from any role within the current game.", @@ -1800,7 +1868,7 @@ const roleData = { }, Fabricator: { alignment: "Mafia", - tags: ["Gifting", "Broken", "Items"], + tags: ["Gifting", "Broken", "Items", "Visiting"], description: [ "Gives out a cursed item once per night.", "Cursed Guns and Knives will backfire against the player who used them.", @@ -1809,12 +1877,12 @@ const roleData = { }, Saboteur: { alignment: "Mafia", - tags: ["Broken", "Items"], + tags: ["Broken", "Items", "Visiting"], description: ["Once per night, sabotages the target's item(s)."], }, Heartbreaker: { alignment: "Mafia", - tags: ["Linked", "Lover"], + tags: ["Linked", "Lover", "Visiting"], description: [ "Falls in love with another player once per game.", "Both players will die if Heartbreaker dies.", @@ -1822,14 +1890,14 @@ const roleData = { }, Yakuza: { alignment: "Mafia", - tags: ["Conversion", "Sacrificial"], + tags: ["Conversion", "Sacrificial", "Visiting"], description: [ "Chooses to sacrifice self once per game to convert another player to Mafioso.", ], }, Graverobber: { alignment: "Mafia", - tags: ["Revive", "Protective", "Graveyard"], + tags: ["Revive", "Protective", "Graveyard", "Visiting"], description: [ "Visits a dead player during the night once per game.", "That player will be resurrected the following day.", @@ -1846,7 +1914,7 @@ const roleData = { }, Illusionist: { alignment: "Mafia", - tags: ["Killing", "Gun", "Items", "Deception"], + tags: ["Killing", "Gun", "Items", "Deception", "Day Killer"], description: [ "Starts with a gun.", "Chooses one player each night to frame as the shooter of any guns or rifles shot by the Illusionist.", @@ -1870,7 +1938,7 @@ const roleData = { }, Scrutineer: { alignment: "Mafia", - tags: ["Killing", "Voting", "Vote Kills"], + tags: ["Killing", "Voting", "Vote Kills", "Visiting"], description: [ "Chooses a victim and a target each night.", "If the victim votes for the target in the village meeting the following day, the victim will die.", @@ -1878,7 +1946,7 @@ const roleData = { }, Trespasser: { alignment: "Mafia", - tags: ["Visits", "Night-Acting"], + tags: ["Night-Acting", "Visiting"], description: [ "Chooses to trespass on another player's property at night.", "Annoyingly, this visit has no effect.", @@ -1887,7 +1955,7 @@ const roleData = { }, Thief: { alignment: "Mafia", - tags: ["Items", "Night-Acting"], + tags: ["Items", "Night-Acting", "Visiting"], description: ["Chooses a player to steal an item from each night."], }, Crank: { @@ -1901,7 +1969,14 @@ const roleData = { }, Interrogator: { alignment: "Mafia", - tags: ["Meeting", "Killing", "Condemn", "Role Blocker", "Dusk"], + tags: [ + "Meeting", + "Killing", + "Condemn", + "Role Blocker", + "Dusk", + "Night Killer", + ], description: [ "If no one was condemned, chooses a player to jail after each day meeting.", "Meets with the prisoner at night and the prisoner cannot perform actions or attend other meetings or be targeted.", @@ -1910,7 +1985,7 @@ const roleData = { }, Bookie: { alignment: "Mafia", - tags: ["Killing", "Condemn", "Voting", "Extra Night Deaths"], + tags: ["Killing", "Condemn", "Voting", "Extra Night Deaths", "Visiting"], description: [ "Each night, predicts the village vote.", "If they successfully predict the village vote, they gain a bonus kill.", @@ -1941,21 +2016,21 @@ const roleData = { }, Fiddler: { alignment: "Mafia", - tags: ["Speech", "Deafean"], + tags: ["Speech", "Deafean", "Visiting"], description: [ "Serenades a player each night, causing them to be unable to hear anything the next day.", ], }, Silencer: { alignment: "Mafia", - tags: ["Speech", "Silence"], + tags: ["Speech", "Silence", "Visiting"], description: [ "Can silence someone each night, causing them to be unable to speak the next day.", ], }, Scrambler: { alignment: "Mafia", - tags: ["Speech", "Clueless", "Random Messages"], + tags: ["Speech", "Clueless", "Random Messages", "Visiting"], description: [ "Scrambles a player each night, causing them to see messages from random players the next day.", ], @@ -1983,12 +2058,12 @@ const roleData = { }, Toreador: { alignment: "Mafia", - tags: ["Manipulative", "Redirection", "Control"], + tags: ["Manipulative", "Redirection", "Control", "Visiting"], description: ["Each night, attracts a player to visit them."], }, Blinder: { alignment: "Mafia", - tags: ["Speech", "Blind"], + tags: ["Speech", "Blind", "Visiting"], description: [ "Each night, blinds a player.", "Blinded players are unable to see the names of players typing the next day.", @@ -1996,12 +2071,12 @@ const roleData = { }, Quack: { alignment: "Mafia", - tags: ["Protective", "Night Saver"], + tags: ["Protective", "Night Saver", "Visiting"], description: ["Saves another player from dying each night."], }, Homeopath: { alignment: "Mafia", - tags: ["Protective", "Malicious Effects"], + tags: ["Protective", "Malicious Effects", "Visiting"], description: [ "Visits one player each night and cleanses them of malicious effects.", "Malicious effects include poison, bleeding, insanity, and polarization.", @@ -2018,7 +2093,7 @@ const roleData = { }, Diplomat: { alignment: "Mafia", - tags: ["Condemn", "Protective", "Condemn Immune"], + tags: ["Condemn", "Protective", "Condemn Immune", "Visiting"], newlyAdded: true, description: [ "Each night chooses a player to be safe from being Condemned.", @@ -2027,7 +2102,13 @@ const roleData = { }, Enforcer: { alignment: "Mafia", - tags: ["Convert Saver", "Protective", "Conversion", "Traitor"], + tags: [ + "Convert Saver", + "Protective", + "Conversion", + "Traitor", + "Visiting", + ], description: [ "Each night, counsels one player and heals their insanity.", "Prevents their target from being converted.", @@ -2044,19 +2125,19 @@ const roleData = { }, Bouncer: { alignment: "Mafia", - tags: ["Night-acting", "Role Blocker", "Visits"], + tags: ["Night-acting", "Role Blocker", "Visits", "Visiting"], description: ["Each night, protects one player from all visits."], }, Plumber: { alignment: "Mafia", - tags: ["Whispers", "Speech"], + tags: ["Whispers", "Speech", "Visiting"], description: [ "Every night, can block all sent and received whispers of the target.", ], }, Gossiper: { alignment: "Mafia", - tags: ["Whispers", "Speech"], + tags: ["Whispers", "Speech", "Visiting"], description: [ "Every night, can make a player leaky the next day.", "Leaky players will always read their whispers aloud.", @@ -2079,14 +2160,14 @@ const roleData = { }, Cyclist: { alignment: "Mafia", - tags: ["Night-acting", "Visits"], + tags: ["Night-acting", "Visiting"], description: [ "Once per game, visits every other player during the night.", ], }, Lobotomist: { alignment: "Mafia", - tags: ["Night-acting", "Conversion", "Vanilla", "Villager"], + tags: ["Night-acting", "Conversion", "Vanilla", "Visiting"], description: [ "Each night, visits one player.", "Village roles convert to Villager. Cult roles convert to Cultist. Independent roles convert to Grouch.", @@ -2096,19 +2177,25 @@ const roleData = { alignment: "Mafia", newlyAdded: true, category: "Night-acting", - tags: ["Night-acting", "Modifiers", "Conversion"], + tags: ["Night-acting", "Modifiers", "Conversion", "Visiting"], description: ["Removes modifiers from other players at night"], }, Pedagogue: { alignment: "Mafia", - tags: ["Night-acting", "Conversion", "Random"], + tags: ["Night-acting", "Conversion", "Random", "Visiting"], description: [ "Each night, converts another Mafia teammate into a random Mafia-aligned role.", ], }, Bartender: { alignment: "Mafia", - tags: ["Night-acting", "Effects", "Alcoholics", "Role Blocker"], + tags: [ + "Night-acting", + "Effects", + "Alcoholics", + "Role Blocker", + "Visiting", + ], description: [ "Each night, serves a non-Mafia player and turns them into an Alcoholic.", "Alcoholics retain their original roles, but they unknowingly roleblock a random non-Mafia player during the night.", @@ -2117,14 +2204,14 @@ const roleData = { }, Rat: { alignment: "Mafia", - tags: ["Manipulative", "Redirection", "Reflexive"], + tags: ["Manipulative", "Redirection", "Reflexive", "Visiting"], description: [ "Each night, chooses one player to redirect all visitors to.", ], }, Cannoneer: { alignment: "Mafia", - tags: ["Killing", "Meeting", "Gun", "Items"], + tags: ["Killing", "Meeting", "Gun", "Items", "Day Killer"], description: [ "Will gain a gun once per game if Mafia chose to abstain from killing the previous night.", "Gun will always reveal the shooter.", @@ -2194,7 +2281,7 @@ const roleData = { "Cult Leader": { alignment: "Cult", category: "Conversion", - tags: ["Conversion", "Kills Cultist"], + tags: ["Conversion", "Kills Cultist", "Visiting"], description: [ "Converts one player into a Cultist each night.", "All Cultists die if the Cult Leader dies.", @@ -2213,7 +2300,7 @@ const roleData = { Zombie: { alignment: "Cult", category: "Conversion", - tags: ["Conversion"], + tags: ["Conversion", "Visiting"], newlyAdded: true, description: [ "Can infect one person each night.", @@ -2225,7 +2312,7 @@ const roleData = { Hexer: { alignment: "Cult", category: "Conversion", - tags: ["Conversion", "Messages"], + tags: ["Conversion", "Messages", "Visiting"], description: [ "Engraves a forbidden word on a player each night.", "If the player speaks the word the next day, they will convert to Cultist.", @@ -2234,7 +2321,13 @@ const roleData = { Inquisitor: { alignment: "Cult", category: "Conversion", - tags: ["Conversion", "Kills Cultist", "Killing"], + tags: [ + "Conversion", + "Kills Cultist", + "Killing", + "Visiting", + "Day Killer", + ], description: [ "Kills a player each night.", "If the victim is night-saved, they will convert to Cultist.", @@ -2243,7 +2336,7 @@ const roleData = { Invader: { alignment: "Cult", category: "Conversion", - tags: ["Conversion", "Setup Changes", "Hide and Seek"], + tags: ["Conversion", "Setup Changes", "Hide and Seek", "Visiting"], description: [ "Attempts to guess the identities of the Hider or Seeker each night.", "Converts the Hider/Seeker to Cultist if guess is correct.", @@ -2253,7 +2346,13 @@ const roleData = { "Witch Doctor": { alignment: "Cult", category: "Conversion", - tags: ["Conversion", "Kills Cultist", "Protective", "Night Saver"], + tags: [ + "Conversion", + "Kills Cultist", + "Protective", + "Night Saver", + "Visiting", + ], description: [ "Chooses a player each night.", "If that player was targeted by a kiling role, that player is saved and converts to Cultist.", @@ -2264,7 +2363,7 @@ const roleData = { Diabolist: { alignment: "Cult", category: "Killing", - tags: ["Vote Kills", "Killing", "Voting"], + tags: ["Vote Kills", "Killing", "Voting", "Visiting"], description: [ "Chooses a victim and a target each night.", "If the victim votes for the target in the village meeting the following day, the victim will die.", @@ -2273,7 +2372,7 @@ const roleData = { Harpy: { alignment: "Cult", category: "Killing", - tags: ["Killing", "Voting"], + tags: ["Killing", "Voting", "Visiting"], description: [ "Chooses a victim and a target each night.", "If the victim doesn't get 1/3 of living players to vote for the Target, the Victim, Target, or Both will die.", @@ -2291,7 +2390,7 @@ const roleData = { Leech: { alignment: "Cult", category: "Killing", - tags: ["Killing", "Blood", "Extra Lives"], + tags: ["Killing", "Blood", "Extra Lives", "Visiting"], description: [ "Is bloodthirsty.", "During the night, can attach to a player and leech from them, stealing 50% of their blood.", @@ -2317,6 +2416,7 @@ const roleData = { "Information", "Setup Changes", "Extra Night Deaths", + "Visiting", ], newlyAdded: true, description: [ @@ -2328,7 +2428,7 @@ const roleData = { Werewolf: { alignment: "Cult", category: "Killing", - tags: ["Killing", "Lycan", "Effect", "Full Moons"], + tags: ["Killing", "Lycan", "Effect", "Full Moons", "Visiting"], description: [ "When a Werewolf is present in the game, full moons will occur on odd nights.", "Each night, bites a non-Cult player and turns them into a Lycan.", @@ -2370,7 +2470,15 @@ const roleData = { Fungoid: { alignment: "Cult", category: "Speaking", - tags: ["Speaking", "Speech", "Silence", "Blind", "Clueless", "Deafen"], + tags: [ + "Speaking", + "Speech", + "Silence", + "Blind", + "Clueless", + "Deafen", + "Visiting", + ], description: [ "Can choose between four fungi to cast at night.", "Thrush, which silences the target.", @@ -2390,6 +2498,7 @@ const roleData = { "Redirection", "Control", "Graveyard", + "Visiting", ], description: [ "Chooses a player to Infest on their first night and Dies.", @@ -2403,7 +2512,7 @@ const roleData = { Psion: { alignment: "Cult", category: "Speaking", - tags: ["Speaking", "Insanity", "Visits"], + tags: ["Speaking", "Insanity", "Visits", "Visiting"], description: [ "Visits a player each night.", "If that player is not visited by a non-Cult player during the next night, they will go insane.", @@ -2429,6 +2538,7 @@ const roleData = { "Conversion", "Random", "Exorcise Village Meeting", + "Visiting", ], recentlyUpdated: true, description: [ @@ -2444,6 +2554,7 @@ const roleData = { "Roles", "Alignment", "Manipulative", + "Visiting", ], newlyAdded: true, description: [ @@ -2455,7 +2566,7 @@ const roleData = { "Queen Bee": { alignment: "Cult", category: "Manipulative", - tags: ["Manipulative", "Delayed"], + tags: ["Manipulative", "Delayed", "Visiting"], description: [ "Every night, visits a player and covers them with sticky honey.", "Delays their action by one day/night cycle.", @@ -2464,7 +2575,7 @@ const roleData = { Selkie: { alignment: "Cult", category: "Manipulative", - tags: ["Manipulative", "Redirection", "Control"], + tags: ["Manipulative", "Redirection", "Control", "Visiting"], description: [ "Each night, chooses two players who are forced to target each other.", ], @@ -2482,7 +2593,7 @@ const roleData = { Succubus: { alignment: "Cult", category: "Manipulative", - tags: ["Manipulative", "Mind Rot"], + tags: ["Manipulative", "Mind Rot", "Visiting"], recentlyUpdated: true, description: [ "Visits one player each night and inflicts them with Mind Rot", @@ -2493,7 +2604,7 @@ const roleData = { Witch: { alignment: "Cult", category: "Manipulative", - tags: ["Manipulative", "Redirection", "Control"], + tags: ["Manipulative", "Redirection", "Control", "Visiting"], description: [ "Chooses one player to control.", "Chooses who that player will perform their actions on. (Not a Visit)", @@ -2511,6 +2622,7 @@ const roleData = { "Protective", "Night Saver", "Extra Night Deaths", + "Visiting", ], description: [ "Can choose between three potions to cast at night.", @@ -2564,7 +2676,14 @@ const roleData = { Gremlin: { alignment: "Cult", category: "Chaos", - tags: ["Conversion", "Items", "Cult Items", "Insanity", "Magic"], + tags: [ + "Conversion", + "Items", + "Cult Items", + "Insanity", + "Magic", + "Visiting", + ], description: [ "Once per night, corrupts the target's item(s) into magic items that benefit the Cult.", "Guns, Rifles, and Knives will convert instead of killing.", @@ -2582,7 +2701,7 @@ const roleData = { Haruspex: { alignment: "Cult", category: "Chaos", - tags: ["Extra Lives", "Protective", "Sacrificial"], + tags: ["Extra Lives", "Protective", "Sacrificial", "Visiting"], description: [ "Visits two Cult-aligned players each night.", "The first player is killed while the second player gains an extra life.", @@ -2592,7 +2711,7 @@ const roleData = { Imp: { alignment: "Cult", category: "Demon", - tags: ["Endangered", "Killing", "Conversion"], + tags: ["Endangered", "Killing", "Conversion", "Visiting", "Night Killer"], recentlyUpdated: true, description: [ "Each night, may choose any player to kill.", @@ -2609,6 +2728,9 @@ const roleData = { "Conversion", "Banished", "Setup Changes", + "Visiting", + "Night Killer", + "Self Kill", ], newlyAdded: true, description: [ @@ -2621,7 +2743,15 @@ const roleData = { Lich: { alignment: "Cult", category: "Demon", - tags: ["Endangered", "Killing", "Banished", "Setup Changes", "Mind Rot"], + tags: [ + "Endangered", + "Killing", + "Banished", + "Setup Changes", + "Mind Rot", + "Visiting", + "Night Killer", + ], newlyAdded: true, description: [ "Each night, may choose a player to kill.", @@ -2635,7 +2765,15 @@ const roleData = { Nyarlathotep: { alignment: "Cult", category: "Demon", - tags: ["Endangered", "Killing", "Win Con", "Deception", "False Mode"], + tags: [ + "Endangered", + "Killing", + "Win Con", + "Deception", + "False Mode", + "Visiting", + "Night Killer", + ], newlyAdded: true, description: [ "Each night, may choose a player to kill.", @@ -2647,7 +2785,7 @@ const roleData = { Puca: { alignment: "Cult", category: "Demon", - tags: ["Endangered", "Killing", "Mind Rot", "Poison"], + tags: ["Endangered", "Killing", "Mind Rot", "Poison", "Visiting"], newlyAdded: true, description: [ "Each night, may choose a player to Mind Rot and Poison.", @@ -2658,7 +2796,15 @@ const roleData = { Satyr: { alignment: "Cult", category: "Demon", - tags: ["Endangered", "Killing", "Mind Rot", "Neighbors", "Banished"], + tags: [ + "Endangered", + "Killing", + "Mind Rot", + "Neighbors", + "Banished", + "Visiting", + "Night Killer", + ], newlyAdded: true, description: [ "Each night, may choose a player to kill.", @@ -2676,6 +2822,8 @@ const roleData = { "Extra Night Deaths", "Graveyard", "Revive", + "Visiting", + "Night Killer", ], newlyAdded: true, description: [ @@ -2688,7 +2836,13 @@ const roleData = { Snallygaster: { alignment: "Cult", category: "Demon", - tags: ["Endangered", "Killing", "Extra Night Deaths"], + tags: [ + "Endangered", + "Killing", + "Extra Night Deaths", + "Visiting", + "Night Killer", + ], newlyAdded: true, description: [ "Each night, may choose a Kill.", @@ -2706,6 +2860,7 @@ const roleData = { "Condemn", "Graveyard", "Exorcise Village Meeting", + "Visiting", ], newlyAdded: true, description: [ @@ -2720,7 +2875,14 @@ const roleData = { Vampire: { alignment: "Cult", category: "Demon", - tags: ["Killing", "Condemn", "Voting", "Setup Changes"], + tags: [ + "Killing", + "Condemn", + "Voting", + "Setup Changes", + "Visiting", + "Night Killer", + ], newlyAdded: true, description: [ "Vampire Votes count as 0.01 during the Village Meeting", @@ -2735,7 +2897,7 @@ const roleData = { //Other Theocrat: { alignment: "Cult", - tags: ["Condemn", "Protective", "Condemn Immune"], + tags: ["Condemn", "Protective", "Condemn Immune", "Visiting"], newlyAdded: true, description: [ "Each night chooses a player to be safe from being Condemned.", @@ -2753,7 +2915,7 @@ const roleData = { }, Shadow: { alignment: "Cult", - tags: ["Investigative", "Visits"], + tags: ["Investigative", "Visits", "Visiting"], description: [ "Visits a player each night.", "Can see who that player visits as well as everyone who visits that player.", @@ -2761,7 +2923,7 @@ const roleData = { }, Druid: { alignment: "Cult", - tags: ["Tree", "Graveyard", "Dead", "Revive"], + tags: ["Tree", "Graveyard", "Dead", "Revive", "Visiting"], description: [ "Visits a dead player during the night.", "That player will be resurrected as a Tree the following day.", @@ -2770,7 +2932,7 @@ const roleData = { }, Necromancer: { alignment: "Cult", - tags: ["Revive", "Protective", "Graveyard"], + tags: ["Revive", "Protective", "Graveyard", "Visiting"], description: [ "Visits a dead player during the night once per game.", "That player will be resurrected the following day.", @@ -2781,7 +2943,7 @@ const roleData = { Bogeyman: { alignment: "Cult", category: "Night-acting", - tags: ["Night-acting", "Visits"], + tags: ["Night-acting", "Visiting"], description: [ "Pays a visit to another player at night.", "Annoyingly, this visit has no effect.", @@ -2792,7 +2954,7 @@ const roleData = { //Independent Fool: { alignment: "Independent", - tags: ["Condenm", "Visits"], + tags: ["Condenm", "Visiting"], description: [ "Fools around at night, visiting another player with no effect.", "Wins if condemned by the town.", @@ -2809,7 +2971,7 @@ const roleData = { }, Dodo: { alignment: "Independent", - tags: ["Gifting", "Items", "Gun", "Killing", "No Joints"], + tags: ["Gifting", "Items", "Gun", "Killing", "No Joints", "Visiting"], description: [ "Wins if shot and killed with a gun.", "Flocks around at night, giving their target a gun.", @@ -2837,7 +2999,7 @@ const roleData = { }, Amnesiac: { alignment: "Independent", - tags: ["Dead", "Conversion"], + tags: ["Dead", "Conversion", "Visiting"], description: [ "Chooses to become the role of a dead player once per game.", "Cannot win the game as Amnesiac.", @@ -2850,7 +3012,7 @@ const roleData = { }, "Old Maid": { alignment: "Independent", - tags: ["Conversion", "Role Swapping"], + tags: ["Conversion", "Role Swapping", "Visiting"], description: [ "Chooses a player to swap roles with each night.", "Chosen player becomes the Old Maid.", @@ -2867,7 +3029,7 @@ const roleData = { }, Clown: { alignment: "Independent", - tags: ["Condmen", "Mafia", "Win Con"], + tags: ["Condmen", "Mafia", "Win Con", "Visiting"], description: [ "Clowns around at night, visiting another player. The visit does nothing.", "The Mafia will be alerted that there is a Clown they must condemn in order to win.", @@ -2883,7 +3045,7 @@ const roleData = { }, Palladist: { alignment: "Independent", - tags: ["Village", "Win Steal", "Meeting", "Conversion"], + tags: ["Village", "Win Steal", "Meeting", "Conversion", "Visiting"], description: [ "If there are no Freemasons, converts a player to Freemason.", "Anonymizes Freemason meetings and forces them to act.", @@ -2893,7 +3055,7 @@ const roleData = { }, "Panda Bear": { alignment: "Independent", - tags: ["Village", "Win Steal", "Visits", "Setup Changes"], + tags: ["Village", "Win Steal", "Visits", "Setup Changes", "Visiting"], description: [ "Walks around at night, visiting another player with no effect.", "When present in the game, the Village cannot win unless the Panda Bear visits another Panda Bear and they mate.", @@ -2922,7 +3084,7 @@ const roleData = { }, Lover: { alignment: "Independent", - tags: ["Survivor", "Lover", "Linked"], + tags: ["Survivor", "Lover", "Linked", "Visiting"], description: [ "Falls in love with another player once per game.", "Both players die if either of them are killed.", @@ -2965,7 +3127,7 @@ const roleData = { }, "Vengeful Spirit": { alignment: "Independent", - tags: ["Killing", "Graveyard"], + tags: ["Killing", "Graveyard", "Visiting"], description: [ "If murdered by another player, gains the ability to kill each night from the graveyard.", "Does not gain the ability if condemned by village vote.", @@ -2975,7 +3137,7 @@ const roleData = { }, Phantom: { alignment: "Independent", - tags: ["Killing", "Conversion"], + tags: ["Killing", "Conversion", "Visiting"], description: [ "Chooses a player to kill once during the night and convert to their role.", "The killed player will have their role hidden upon death, and instead reveal as their alignment.", @@ -2984,7 +3146,7 @@ const roleData = { }, Prince: { alignment: "Independent", - tags: ["Essential", "Alignment"], + tags: ["Essential", "Alignment", "Visiting"], description: [ "Once per game, visits a player and joins their alignment.", "If the Prince dies, everyone of that alignment dies.", @@ -2993,7 +3155,7 @@ const roleData = { }, Nomad: { alignment: "Independent", - tags: ["Alignment", "Visits"], + tags: ["Alignments", "Visiting"], description: [ "Must visit another player every night.", "Cannot choose the same player consecutively.", @@ -3002,7 +3164,7 @@ const roleData = { }, Hitchhiker: { alignment: "Independent", - tags: ["Alignment", "Visits", "Mind Rot", "Reflexive"], + tags: ["Alignments", "Visits", "Mind Rot", "Reflexive"], description: [ "Each Night Will inflict one of their visitors with Mind Rot.", "Will Switch to that player's alignment.", @@ -3047,7 +3209,7 @@ const roleData = { }, "Gingerbread Man": { alignment: "Independent", - tags: ["Survivor", "Visits", "Extra Lives"], + tags: ["Survivor", "Visits", "Extra Lives", "Visiting"], description: [ "Each night, hides behind a player and becomes immune to death.", "Will get eaten if the player visits them. That player will gain an extra life.", @@ -3056,7 +3218,7 @@ const roleData = { }, Astrologer: { alignment: "Independent", - tags: ["Linked", "Survivor"], + tags: ["Linked", "Survivor", "Visiting"], description: [ "Chooses two players and makes them fall in love with each other.", "Wins if their chosen lovers are alive at the end of the game.", @@ -3082,7 +3244,13 @@ const roleData = { }, Monk: { alignment: "Independent", - tags: ["Voting", "Night Saver", "Protective", "Condemn Immune"], + tags: [ + "Voting", + "Night Saver", + "Protective", + "Condemn Immune", + "Visiting", + ], description: [ "Has no voting power.", "Each night, can save one player and also grant them condemn immunity the following day.", @@ -3091,7 +3259,7 @@ const roleData = { }, Warlock: { alignment: "Independent", - tags: ["Voting", "Condemn", "Extra Lives"], + tags: ["Voting", "Condemn", "Extra Lives", "Visiting"], recentlyUpdated: true, description: [ "Each night chooses one person.", @@ -3109,7 +3277,7 @@ const roleData = { }, Picciotto: { alignment: "Independent", - tags: ["Mafia", "Conversion", "Visits"], + tags: ["Mafia", "Conversion", "Visiting"], description: [ "Every night, can visit a player.", "If that player is mafia, the Picciotto will be notified.", @@ -3129,7 +3297,7 @@ const roleData = { }, Emperor: { alignment: "Independent", - tags: ["Voting", "Condemn"], + tags: ["Voting", "Condemn", "Visiting"], description: [ "Chooses two players each night to force into a duel.", "During the following day, only the two duelists may be voted.", @@ -3140,7 +3308,7 @@ const roleData = { Magus: { alignment: "Independent", newlyAdded: true, - tags: ["Magus", "Setup Changes", "Village"], + tags: ["Magus", "Setup Changes", "Village", "Visiting"], description: [ "If a player rolls Magus at the beginning of the game, no Mafia or Cult roles will be present in the game.", "At night, the Magus will passively kill a random player each night, even if dead.", @@ -3164,6 +3332,7 @@ const roleData = { "Position", "Excess Roles", "Meeting", + "Hostile", ], description: [ "Meets with All Independents", @@ -3196,7 +3365,7 @@ const roleData = { }, "Serial Killer": { alignment: "Independent", - tags: ["Killing", "Must Act", "Last Two"], + tags: ["Killing", "Must Act", "Last Two", "Visiting", "Hostile"], description: [ "Must kill a player each night.", "Wins if among last two alive.", @@ -3204,7 +3373,14 @@ const roleData = { }, Yandere: { alignment: "Independent", - tags: ["Killing", "Must Act", "Linked", "Last Two"], + tags: [ + "Killing", + "Must Act", + "Linked", + "Last Two", + "Visiting", + "Hostile", + ], description: [ "Falls in love with another player once per game.", "The beloved will not be alerted. If the beloved dies, the Yandere dies. If the Yandere dies, the beloved will not die.", @@ -3214,7 +3390,7 @@ const roleData = { }, Clockmaker: { alignment: "Independent", - tags: ["Killing", "Alignment", "Extra Lives"], + tags: ["Killing", "Alignment", "Extra Lives", "Visiting", "Hostile"], description: [ "Has a clock that starts at 6 o'clock.", "Choosing to kill a player each night changes the time based on that player's alignment.", @@ -3226,7 +3402,7 @@ const roleData = { }, Pyromaniac: { alignment: "Independent", - tags: ["Killing", "Gasoline", "Last Two"], + tags: ["Killing", "Gasoline", "Last Two", "Visiting", "Hostile"], description: [ "Douses one player with Gasoline each night.", "Chooses to light a match during the day to burn doused players to ashes.", @@ -3235,7 +3411,7 @@ const roleData = { }, Dentist: { alignment: "Independent", - tags: ["Killing", "Visits", "Last Two"], + tags: ["Killing", "Visits", "Last Two", "Visiting", "Hostile"], description: [ "Gasses one player with anesthetic each night.", "If that player acts the next night, they die.", @@ -3245,7 +3421,15 @@ const roleData = { }, Hellhound: { alignment: "Independent", - tags: ["Killing", "Roles", "Last Two", "Immortal", "Condemn Immune"], + tags: [ + "Killing", + "Roles", + "Last Two", + "Immortal", + "Condemn Immune", + "Visiting", + "Hostile", + ], description: [ "Chooses to hunt at night by choosing a player and guessing their role.", "If guessed correct, becomes immortal for the following day.", @@ -3255,7 +3439,7 @@ const roleData = { }, Shinigami: { alignment: "Independent", - tags: ["Killing", "Items"], + tags: ["Killing", "Items", "Visiting", "Hostile"], description: [ "At the beginning of the game, one player randomly receives a notebook.", "That player can kill during the night.", @@ -3265,7 +3449,7 @@ const roleData = { }, Ripper: { alignment: "Independent", - tags: ["Killing", "Independent"], + tags: ["Killing", "Independent", "Visiting", "Hostile"], description: [ "Kills one player every night.", "Wins when all other Hostile Independents are dead.", @@ -3279,6 +3463,8 @@ const roleData = { "Extra Lives", "Last Two", "Clean Night Kill", + "Visiting", + "Hostile", ], description: [ "Absorbs one person each night, killing them and cleaning their deaths.", @@ -3290,7 +3476,7 @@ const roleData = { }, Mastermind: { alignment: "Independent", - tags: ["Mafia", "Cult", "Meeting", "AnonymizeMeeting"], + tags: ["Mafia", "Cult", "Meeting", "AnonymizeMeeting", "Hostile"], description: [ "Mafia and Cult meetings are anonymous if Mastermind is present in the game.", "Wins instead of mafia/cult and counts toward their total.", @@ -3298,7 +3484,15 @@ const roleData = { }, Usurper: { alignment: "Independent", - tags: ["Mafia", "Mafioso", "Meeting", "AnonymizeMeeting", "Cultist"], + tags: [ + "Mafia", + "Mafioso", + "Meeting", + "AnonymizeMeeting", + "Cultist", + "Visiting", + "Hostile", + ], description: [ "Meets with the Mafia and Cult, makes their night meeting anonymous.", "Each night, chooses a player. If the player is sided with the mafia/cult, they become a Mafioso/Cultist.", @@ -3307,7 +3501,15 @@ const roleData = { }, Mutineer: { alignment: "Independent", - tags: ["Mafia", "Meeting", "Killing", "Last Two", "AnonymizeMeeting"], + tags: [ + "Mafia", + "Meeting", + "Killing", + "Last Two", + "AnonymizeMeeting", + "Visiting", + "Hostile", + ], description: [ "Can kill one player per night.", "Appears as Mafia on investigation.", @@ -3317,7 +3519,7 @@ const roleData = { }, Alien: { alignment: "Independent", - tags: ["Probe", "Visits"], + tags: ["Probe", "Visiting", "Hostile"], description: [ "Chooses one player to probe each night.", "Wins if all players left alive have been probed.", @@ -3325,7 +3527,7 @@ const roleData = { }, Matchmaker: { alignment: "Independent", - tags: ["Linked", "Alignment"], + tags: ["Linked", "Alignment", "Visiting", "Hostile"], description: [ "Each night chooses two players to go on a date. If they are the same alignment, the date will be succesful.", "Wins if all players left alive have went on a successful date.", @@ -3333,7 +3535,7 @@ const roleData = { }, Tofurkey: { alignment: "Independent", - tags: ["Famine", "Alignment", "Survivor"], + tags: ["Famine", "Alignment", "Survivor", "Hostile"], description: [ "The game begins with a famine, with each player starting with four bread.", "Tofurkeys are immune to the famine.", @@ -3344,7 +3546,7 @@ const roleData = { }, Turkey: { alignment: "Independent", - tags: ["Famine", "Alignment", "Survivor"], + tags: ["Famine", "Alignment", "Survivor", "Hostile"], description: [ "The game begins with a famine, with each player starting with four bread.", "Turkeys are immune to the famine.", @@ -3354,7 +3556,7 @@ const roleData = { }, Leprechaun: { alignment: "Independent", - tags: ["Items", "Killing"], + tags: ["Items", "Killing", "Visiting", "Hostile"], description: [ "When present in the game, four-leaf clovers are randomly assigned to players.", "Each night, steals a random item from their target, preferentially stealing Clovers.", @@ -3364,7 +3566,15 @@ const roleData = { }, Anarchist: { alignment: "Independent", - tags: ["Items", "Killing", "Revealing", "Last Two", "Mini-Game"], + tags: [ + "Items", + "Killing", + "Revealing", + "Last Two", + "Mini-Game", + "Visiting", + "Hostile", + ], description: [ "Gives out a timebomb each night.", "The timebomb can be passed around during the day, randomly exploding.", @@ -3374,7 +3584,7 @@ const roleData = { }, Communist: { alignment: "Independent", - tags: ["Conversion", "Vanilla"], + tags: ["Conversion", "Vanilla", "Visiting", "Hostile"], description: [ "Visits one player each night.", "Turns that player into their alignment's vanilla role.", @@ -3383,7 +3593,7 @@ const roleData = { }, Gambler: { alignment: "Independent", - tags: ["Killing", "Last Two", "Mini-Game"], + tags: ["Killing", "Last Two", "Mini-Game", "Visiting", "Hostile"], description: [ "Each night, challenges a player to a game of Rock, Paper, Scissors. Game is played during the day.", "If the Gambler wins, the Challenger dies.", @@ -3392,7 +3602,7 @@ const roleData = { }, "Grizzly Bear": { alignment: "Independent", - tags: ["Killing", "Last Two", "Visits"], + tags: ["Killing", "Last Two", "Visits", "Visiting", "Hostile"], description: [ "Visits one player each night.", "Any player to visit the Grizzly Bear's target will be killed. If the Grizzly Bear's target does not visit that night, they will be killed as well.", @@ -3401,7 +3611,14 @@ const roleData = { }, "Polar Bear": { alignment: "Independent", - tags: ["Killing", "Last Two", "Visits", "Malicious Effects"], + tags: [ + "Killing", + "Last Two", + "Visits", + "Malicious Effects", + "Visiting", + "Hostile", + ], description: [ "Visits two players each night, polarising them.", "A polarised player visiting another polarised player will kill both of them.", @@ -3411,7 +3628,7 @@ const roleData = { }, Samurai: { alignment: "Independent", - tags: ["Killing", "Turn Based", "Mini-Game"], + tags: ["Killing", "Turn Based", "Mini-Game", "Visiting", "Hostile"], disabled: true, newlyAdded: true, description: [ @@ -3425,7 +3642,7 @@ const roleData = { }, Snowman: { alignment: "Independent", - tags: ["Items", "Mini-Game"], + tags: ["Items", "Mini-Game", "Hostile"], description: [ "Each night, may declare a snowball fight.", "Half of all players will receive a snowball.", @@ -3436,7 +3653,7 @@ const roleData = { }, Judge: { alignment: "Independent", - tags: ["Speaking", "Voting", "Meeting", "Dusk"], + tags: ["Speaking", "Voting", "Meeting", "Dusk", "Hostile"], description: [ "Can anonymously broadcast messages during the day.", "Twice per game, may declare a court session.", @@ -3688,6 +3905,19 @@ const roleData = { "Turns game into a Wacky People game, where players answer personal questions.", ], }, + Governor: { + alignment: "Town", + tags: ["None"], + description: [ + "Can answer prompts and vote for answers.", + "Turns game into a Acrotopia game, where players create backronyms based on Acronyms.", + ], + }, + Host: { + alignment: "Host", + tags: ["None"], + description: ["Can make Prompets/Acronyms", "Facilitates the game."], + }, }, "Liars Dice": { Liar: { @@ -3699,6 +3929,11 @@ const roleData = { "Loses if runs out of dice.", ], }, + Host: { + alignment: "Host", + tags: ["None"], + description: ["Facilitates the game."], + }, }, }; diff --git a/react_main/src/Constants.jsx b/react_main/src/Constants.jsx index 0a1258d53..5e3d2c4b1 100644 --- a/react_main/src/Constants.jsx +++ b/react_main/src/Constants.jsx @@ -26,8 +26,8 @@ export const Alignments = { Jotto: ["Town"], Acrotopia: ["Town"], "Secret Dictator": ["Liberals", "Fascists"], - "Wacky Words": ["Town"], - "Liars Dice": ["Liars"], + "Wacky Words": ["Town", "Host"], + "Liars Dice": ["Liars", "Host"], }; export const GameStates = { diff --git a/react_main/src/components/Popover.jsx b/react_main/src/components/Popover.jsx index 8cac6c7cc..8d68aa83a 100644 --- a/react_main/src/components/Popover.jsx +++ b/react_main/src/components/Popover.jsx @@ -802,6 +802,53 @@ export function parseGamePopover(game) { /> ); + const standardiseCapitalisationWW = + game.settings.gameTypeOptions.standardiseCapitalisation; + result.push( + + ); + + if (standardiseCapitalisationWW) { + result.push( + + ); + } + break; + case "Wacky Words": + result.push( + + ); + + result.push( + + ); + + result.push( + + ); + const standardiseCapitalisation = game.settings.gameTypeOptions.standardiseCapitalisation; result.push( diff --git a/react_main/src/css/roles.css b/react_main/src/css/roles.css index 161c77ba2..f3375c3c2 100644 --- a/react_main/src/css/roles.css +++ b/react_main/src/css/roles.css @@ -2276,6 +2276,14 @@ background-image: url("/images/roles/neighbor-vivid.png"); } +.role-Wacky-Words-Governor { + background-image: url("/images/roles/governor-vivid.png"); +} + +.role-Wacky-Words-Host { + background-image: url("/images/roles/host-vivid.png"); +} + /*********** * * Liars Dice @@ -2285,6 +2293,9 @@ .role-Liars-Dice-Liar { background-image: url("/images/roles/buccaneer-vivid.png"); } +.role-Liars-Dice-Host { + background-image: url("/images/roles/host-vivid.png"); +} /*********** * diff --git a/react_main/src/pages/Play/Host/HostWackyWords.jsx b/react_main/src/pages/Play/Host/HostWackyWords.jsx index 1f388afab..1a1ab9a88 100644 --- a/react_main/src/pages/Play/Host/HostWackyWords.jsx +++ b/react_main/src/pages/Play/Host/HostWackyWords.jsx @@ -44,6 +44,33 @@ export default function HostWackyWords() { min: 3, max: 15, }, + { + label: "Acronym Size", + ref: "acronymSize", + type: "number", + value: 5, + min: 3, + max: 7, + }, + { + label: "Enable Punctuation", + ref: "enablePunctuation", + type: "boolean", + value: true, + }, + { + label: "Standardise Capitalisation", + ref: "standardiseCapitalisation", + type: "boolean", + value: true, + }, + { + label: "Turn On Caps", + ref: "turnOnCaps", + type: "boolean", + value: true, + showIf: "standardiseCapitalisation", + }, { label: "Lobby", ref: "lobby", @@ -155,6 +182,12 @@ export default function HostWackyWords() { Day: getFormFieldValue("dayLength"), }, roundAmt: getFormFieldValue("roundAmt"), + acronymSize: getFormFieldValue("acronymSize"), + enablePunctuation: getFormFieldValue("enablePunctuation"), + standardiseCapitalisation: getFormFieldValue( + "standardiseCapitalisation" + ), + turnOnCaps: getFormFieldValue("turnOnCaps"), anonymousGame: getFormFieldValue("anonymousGame"), anonymousDeckId: getFormFieldValue("anonymousDeckId"), }) diff --git a/routes/game.js b/routes/game.js index 62393f445..61e7f8a20 100644 --- a/routes/game.js +++ b/routes/game.js @@ -991,9 +991,17 @@ const settingsChecks = { }, "Wacky Words": (settings, setup) => { let roundAmt = settings.roundAmt; + let acronymSize = settings.acronymSize; + let enablePunctuation = settings.enablePunctuation; + let standardiseCapitalisation = settings.standardiseCapitalisation; + let turnOnCaps = settings.turnOnCaps; return { roundAmt, + acronymSize, + enablePunctuation, + standardiseCapitalisation, + turnOnCaps, }; }, "Liars Dice": (settings, setup) => { diff --git a/routes/setup.js b/routes/setup.js index 95d24d2c3..3a1be30f3 100644 --- a/routes/setup.js +++ b/routes/setup.js @@ -396,7 +396,7 @@ router.post("/create", async function (req, res) { setup.mustAct = Boolean(setup.mustAct); setup.mustCondemn = Boolean(setup.mustCondemn); setup.gameStartPrompt = String(setup.gameStartPrompt || ""); - setup.banished = Number(setup.banished); + setup.banished = Number(setup.banished || 0); setup.talkingDead = Boolean(setup.talkingDead); setup.votingDead = Boolean(setup.votingDead); setup.OneNightMode = Boolean(setup.OneNightMode);