diff --git a/Games/core/Game.js b/Games/core/Game.js index 9a3f15a5e..337f7b11b 100644 --- a/Games/core/Game.js +++ b/Games/core/Game.js @@ -1050,11 +1050,7 @@ module.exports = class Game { ) { this.ExorciseVillageMeeting = true; } - if ( - this.getRoleTags(this.PossibleRoles[z]).includes( - "Pregame Actions" - ) - ) { + if (this.getRoleTags(this.PossibleRoles[z]).includes("Pregame Actions")) { this.HaveDuskOrDawn = true; } } @@ -1239,20 +1235,20 @@ module.exports = class Game { let roleFull = `${role}`; let modTags; let roleTags = roleData[this.type][roleFull.split(":")[0]].tags; - if(roleFull.split(":")[1] != null && roleFull.split(":")[1].length > 0){ - let modifiersArray = roleFull.split(":")[1].split("/"); - /*this.sendAlert( + if (roleFull.split(":")[1] != null && roleFull.split(":")[1].length > 0) { + let modifiersArray = roleFull.split(":")[1].split("/"); + /*this.sendAlert( `Stuff ${roleFull}: ${roleFull.split(":")[1]}: ${modifiersArray[0]}`, undefined, { color: "#F1F1F1" } );*/ - for(let w = 0; w < modifiersArray.length; w++){ - modTags = modifierData[this.type][modifiersArray[w]].tags; - for(let u = 0; u < modTags.length; u++){ - roleTags.push(modTags[u]); - } + for (let w = 0; w < modifiersArray.length; w++) { + modTags = modifierData[this.type][modifiersArray[w]].tags; + for (let u = 0; u < modTags.length; u++) { + roleTags.push(modTags[u]); + } + } } - } return roleTags; } diff --git a/Games/core/Player.js b/Games/core/Player.js index 216beced4..dacd3cce3 100644 --- a/Games/core/Player.js +++ b/Games/core/Player.js @@ -793,9 +793,8 @@ module.exports = class Player { // (options.shouldMeetMod != null && !options.shouldMeetMod.bind(this.role)(meetingName, options)) || - - (options.shouldMeetOneShot != null && - !options.shouldMeetOneShot.bind(this.role)(meetingName, options)) || + (options.shouldMeetOneShot != null && + !options.shouldMeetOneShot.bind(this.role)(meetingName, options)) || // (this.alive && options.whileAlive == false) || (!this.alive && !options.whileDead) || diff --git a/Games/types/Mafia/Game.js b/Games/types/Mafia/Game.js index bd4e16b1f..34238759e 100644 --- a/Games/types/Mafia/Game.js +++ b/Games/types/Mafia/Game.js @@ -54,6 +54,11 @@ module.exports = class MafiaGame extends Game { this.extensions = 0; this.extensionVotes = 0; this.hasBeenDay = false; + this.currentSwapAmt = 1; + this.RoomOne = []; + this.RoomTwo = []; + this.FinalRound = 3; + this.CurrentRound = 0; } rebroadcastSetup() { diff --git a/Games/types/Mafia/items/NoVillageMeeting.js b/Games/types/Mafia/items/NoVillageMeeting.js new file mode 100644 index 000000000..eb12a266e --- /dev/null +++ b/Games/types/Mafia/items/NoVillageMeeting.js @@ -0,0 +1,81 @@ +const Item = require("../Item"); +const { + EVIL_FACTIONS, + NOT_EVIL_FACTIONS, + CULT_FACTIONS, + MAFIA_FACTIONS, + FACTION_LEARN_TEAM, + FACTION_WIN_WITH_MAJORITY, + FACTION_WITH_MEETING, + FACTION_KILL, +} = require("../const/FactionList"); + +// TODO this should semantically be an effect "SnowedIn" not item +module.exports = class NoVillageMeeting extends Item { + constructor() { + super("NoVillageMeeting"); + + this.cannotBeStolen = true; + this.cannotBeSnooped = true; + this.lifespan = 1; + /* + this.meetings = { + NoVillageMeeting: { + actionName: "Done Waiting?", + states: ["Day"], + //flags: ["group", "speech", "voting", "mustAct", "noVeg"], + //inputType: "boolean", + passiveDead: true, + whileDead: true, + speakDead: true, + }, + }; +*/ + /* + this.listeners = { + state: function () { + const state = this.game.getStateName(); + if (state == "Day") { + this.drop(); + return; + } + + if (state != "Night") { + return; + } + + if (this.holder.role.alignment != "Cult") { + this.holder.queueAlert( + ":snowball: You're snowed in for the night… you cannot take any action!" + ); + } + }, + }; + */ + } + + shouldDisableMeeting(name) { + // do not disable jailing, gov actions + for(let x = 0; x < FACTION_WITH_MEETING.length; x++){ + if(name == `Fake ${FACTION_WITH_MEETING[x]}`){ + return true; + } + if(name == `${FACTION_WITH_MEETING[x]} Meeting`){ + return true; + } + if(name == `${FACTION_WITH_MEETING[x]} Kill`){ + return true; + } + } + + if (name == "Village") { + return true; + } + + if (name == "Magus Game") { + return true; + } + + return false; + } +}; diff --git a/Games/types/Mafia/items/Room.js b/Games/types/Mafia/items/Room.js new file mode 100644 index 000000000..bdd0ad7f9 --- /dev/null +++ b/Games/types/Mafia/items/Room.js @@ -0,0 +1,43 @@ +const Item = require("../Item"); +const { + EVIL_FACTIONS, + NOT_EVIL_FACTIONS, + CULT_FACTIONS, + MAFIA_FACTIONS, + FACTION_LEARN_TEAM, + FACTION_WIN_WITH_MAJORITY, + FACTION_WITH_MEETING, + FACTION_KILL, +} = require("../const/FactionList"); +const { PRIORITY_OVERTHROW_VOTE } = require("../const/Priority"); + +module.exports = class Room extends Item { + constructor(meetingName) { + super("Room"); + + //this.reveal = reveal; + this.lifespan = 1; + this.cannotBeStolen = true; + this.meetings[meetingName] = { + actionName: "Elect Leader", + states: ["Day"], + targets: { include: ["members"], exclude: ["dead"] }, + flags: ["group", "voting", "speech", "mustAct"], + whileDead: true, + passiveDead: true, + action: { + labels: ["hidden"], + priority: PRIORITY_OVERTHROW_VOTE, + run: function () { + if (meetingName == "Room 1") { + this.game.RoomOneLeader = this.target; + } else if (meetingName == "Room 2") { + this.game.RoomTwoLeader = this.target; + } else { + this.game.RoomThreeLeader = this.target; + } + }, + }, + }; + } +}; diff --git a/Games/types/Mafia/items/RoomLeader.js b/Games/types/Mafia/items/RoomLeader.js new file mode 100644 index 000000000..e2e1f57e5 --- /dev/null +++ b/Games/types/Mafia/items/RoomLeader.js @@ -0,0 +1,74 @@ +const Random = require("../../../../lib/Random"); +const Item = require("../Item"); +const { PRIORITY_SWAP_ROLES } = require("../const/Priority"); + +module.exports = class RoomLeader extends Item { + constructor(game, room) { + super("RoomLeader"); + this.room = room; + this.lifespan = 1; + this.cannotBeStolen = true; + if(this.room == 1){ + this.targets = [isInRoom1]; + } + else{ + this.targets = [isInRoom2]; + } + this.listeners = { + meetingsMade: function () { + this.holder.sendAlert( + `Choose ${this.game.currentSwapAmt} hostage${ + this.game.currentSwapAmt > 1 ? "s" : "" + } to swap.` + ); + }, + }; + this.meetings = { + Leaders: { + states: ["Night"], + flags: ["group", "speech"], + }, + "Hostage Swap": { + states: ["Night"], + flags: ["voting", "multi", "mustAct"], + targets: { include: this.targets, exclude: ["members"] }, + multiMin: game.currentSwapAmt, + multiMax: game.currentSwapAmt, + action: { + item: this, + priority: PRIORITY_SWAP_ROLES, + run: function () { + //var fromRoom = this.room; + if (!Array.isArray(this.target)) { + this.target = [this.target]; + } + if(this.item.room == 1){ + for (let player of this.target) { + this.game.RoomOne.splice(this.game.RoomOne.indexOf(player),1); + this.game.RoomTwo.push(player); + this.game.events.emit("RoonSwitch",player,this.actor,this.room); + } + } + else if(this.item.room == 2){ + for (let player of this.target) { + this.game.RoomTwo.splice(this.game.RoomTwo.indexOf(player),1); + this.game.RoomOne.push(player); + this.game.events.emit("RoonSwitch",player,this.actor,this.room); + } + } + + this.actor.dropItem("Leader"); + }, + }, + }, + }; + } +}; +function isInRoom1(player) { + return player.game.RoomOne.includes(player); + // return this.room && player == this.role.data.prevTarget; +} +function isInRoom2(player) { + return player.game.RoomTwo.includes(player); +// return this.room && player == this.role.data.prevTarget; +} \ No newline at end of file diff --git a/Games/types/Mafia/roles/Mafia/Bomber.js b/Games/types/Mafia/roles/Mafia/Bomber.js new file mode 100644 index 000000000..e94e86a73 --- /dev/null +++ b/Games/types/Mafia/roles/Mafia/Bomber.js @@ -0,0 +1,17 @@ +const Role = require("../../Role"); + +module.exports = class Bomber extends Role { + constructor(player, data) { + super("Bomber", player, data); + + this.alignment = "Mafia"; + this.cards = [ + "VillageCore", + "WinWithFaction", + "MeetingFaction", + "ForceSplitDecision", + "KillAllInRoom", + "AddCopyOfRole", + ]; + } +}; diff --git a/Games/types/Mafia/roles/Village/President.js b/Games/types/Mafia/roles/Village/President.js index 65b78cc4e..7aeac2bb4 100644 --- a/Games/types/Mafia/roles/Village/President.js +++ b/Games/types/Mafia/roles/Village/President.js @@ -11,7 +11,8 @@ module.exports = class President extends Role { if (player !== this.player) { return; } - + const bomberInGame = this.game.players.filter((p) => p.role.name === "Bomber"); + if(bomberInGame.length <= 0){ this.game.queueAlert( `President ${this.player.name}'s motorcade has broken down on the outskirts of town… the Villagers must protect them from assassination by the Mafia!`, 0, @@ -21,6 +22,7 @@ module.exports = class President extends Role { p != this.player ) ); + } }, ], }; diff --git a/Games/types/Mafia/roles/cards/AddCopyOfRole.js b/Games/types/Mafia/roles/cards/AddCopyOfRole.js index e4bd0f861..62f537534 100644 --- a/Games/types/Mafia/roles/cards/AddCopyOfRole.js +++ b/Games/types/Mafia/roles/cards/AddCopyOfRole.js @@ -31,7 +31,7 @@ module.exports = class AddCopyOfRole extends Card { shuffledPlayers[0].setRole("Templar", undefined, false, true); shuffledPlayers[0].role.data.reroll = true; shuffledPlayers[0].role.data.hasCopied = true; - } else if (this.player.role.name == "Vice President") { + } else if (this.player.role.name == "Vice President" || this.player.role.name == "Bomber") { let players = this.game.players.filter( (p) => p.role.alignment == "Village" || p.role.alignment == "Independent" diff --git a/Games/types/Mafia/roles/cards/ChangeRandomAlignment.js b/Games/types/Mafia/roles/cards/ChangeRandomAlignment.js index 91eae3983..37771602d 100644 --- a/Games/types/Mafia/roles/cards/ChangeRandomAlignment.js +++ b/Games/types/Mafia/roles/cards/ChangeRandomAlignment.js @@ -24,7 +24,7 @@ module.exports = class ChangeRandomAlignment extends Card { ) { this.player.faction = factions[x]; } - this.player.queueAlert(`${factions[x]}`); + //this.player.queueAlert(`${factions[x]}`); } /* const alignment = { diff --git a/Games/types/Mafia/roles/cards/ForceSplitDecision.js b/Games/types/Mafia/roles/cards/ForceSplitDecision.js new file mode 100644 index 000000000..e07d27ada --- /dev/null +++ b/Games/types/Mafia/roles/cards/ForceSplitDecision.js @@ -0,0 +1,106 @@ +const Card = require("../../Card"); +const Random = require("../../../../../lib/Random"); +const { PRIORITY_INVESTIGATIVE_AFTER_RESOLVE_DEFAULT } = require("../../const/Priority"); +const { PRIORITY_SWAP_ROLES } = require("../../const/Priority"); + +module.exports = class ForceSplitDecision extends Card { + constructor(role) { + super(role); + + + this.actions = [ + { + labels: ["hidden", "absolute"], + priority: PRIORITY_SWAP_ROLES+5, + run: function () { + if (!this.actor.alive) return; + this.game.statesSinceLastDeath = 0; + for(let player of this.game.RoomOne){ + player.holdItem("NoVillageMeeting"); + } + for(let player of this.game.RoomTwo){ + player.holdItem("NoVillageMeeting"); + } + if ((this.game.getStateName() == "Dusk" || this.game.getStateName() == "Day") && this.game.HasGiven == 1){ + this.game.HasGiven = 2; + if(this.game.RoomOneLeader == null || !this.game.RoomOneLeader.alive){ + let roomOne = this.game.RoomOne.filter((p)=> p.alive); + this.game.RoomOneLeader = roomOne[0]; + } + if(this.game.RoomTwoLeader == null || !this.game.RoomTwoLeader.alive){ + let roomTwo = this.game.RoomTwo.filter((p)=> p.alive); + this.game.RoomTwoLeader = roomTwo[0]; + } + this.game.RoomOneLeader.holdItem("RoomLeader",this.game, 1); + this.game.RoomTwoLeader.holdItem("RoomLeader",this.game, 2); + + } + + if ((this.game.getStateName() == "Dawn" || this.game.getStateName() == "Night") && this.game.HasGiven == 2){ + this.game.HasGiven = 1; + this.game.CurrentRound = this.game.CurrentRound+1; + this.game.queueAlert( + `Round ${this.game.CurrentRound}! Elect a Leader` + ); + if(this.game.currentSwapAmt>1){ + this.game.currentSwapAmt = this.game.currentSwapAmt-1; + } + for(let player of this.game.RoomOne){ + player.holdItem("Room","Room 1"); + } + for(let player of this.game.RoomTwo){ + player.holdItem("Room","Room 2"); + } + + + } + + }, + }, + ]; + + this.listeners = { + roleAssigned: function (player) { + if (this.player !== player) { + return; + } + const Presidents = this.game.players.filter( + (p) => + p.alive && + (p.role.name == "President" || p.role.name == "Senator") + ); + if(Presidents <= 0){ + let players = this.game.players.filter( + (p) => + p.role.alignment == "Village" || p.role.alignment == "Independent" + ); + let shuffledPlayers = Random.randomizeArray(players); + shuffledPlayers[0].setRole("President"); + } + if(this.game.HasGiven != 1 && this.game.HasGiven != 2){ + this.game.HasGiven = 2; + } + if(this.game.RoomOne.length <= 0 || this.game.RoomTwo.length <= 0){ + for(let x = 0; x < this.game.alivePlayers().length;x++){ + if(x % 2 == 0){ + this.game.RoomOne.push(this.game.alivePlayers()[x]) + } + if(x % 2 != 0){ + this.game.RoomTwo.push(this.game.alivePlayers()[x]) + } + } + } + if(this.game.alivePlayers().length <= 13){ + this.game.currentSwapAmt = 1; + } + else if(this.game.alivePlayers().length <= 21){ + this.game.currentSwapAmt = 2; + } + else{ + this.game.currentSwapAmt = 3; + } + }, + }; + + } +}; diff --git a/Games/types/Mafia/roles/cards/KillAllInRoom.js b/Games/types/Mafia/roles/cards/KillAllInRoom.js new file mode 100644 index 000000000..580d953ea --- /dev/null +++ b/Games/types/Mafia/roles/cards/KillAllInRoom.js @@ -0,0 +1,39 @@ +const Card = require("../../Card"); +const { PRIORITY_KILL_DEFAULT } = require("../../const/Priority"); + +module.exports = class KillAllInRoom extends Card { + constructor(role) { + super(role); + + this.actions = [ + { + priority: PRIORITY_KILL_DEFAULT, + labels: ["kill"], + run: function () { + if (!this.actor.alive) return; + if (this.game.getStateName() != "Night" && this.game.getStateName() != "Dawn") return; + if(this.game.FinalRound < this.game.CurrentRound){ + if(this.game.RoomOne.includes(this.actor)){ + for (let x = 0; x < this.game.RoomOne.length; x++) { + if (this.dominates(this.game.RoomOne[x]) && this.game.RoomOne[x] != this.actor){ + this.game.RoomOne[x].kill("basic", this.actor); + } + } + } + else if(this.game.RoomTwo.includes(this.actor)){ + for (let x = 0; x < this.game.RoomTwo.length; x++) { + if (this.dominates(this.game.RoomTwo[x]) && this.game.RoomTwo[x] != this.actor){ + this.game.RoomTwo[x].kill("basic", this.actor); + } + } + } + + this.actor.kill("basic", this.actor); + + + } + }, + }, + ]; + } +}; diff --git a/Games/types/Mafia/roles/cards/OneShot.js b/Games/types/Mafia/roles/cards/OneShot.js index 05358f6d3..5f0268405 100644 --- a/Games/types/Mafia/roles/cards/OneShot.js +++ b/Games/types/Mafia/roles/cards/OneShot.js @@ -29,7 +29,7 @@ module.exports = class OneShot extends Card { }, }, shouldMeet() { - return (this.OneShotNight == 0); + return this.OneShotNight == 0; }, }, "One Shot Day": { @@ -47,7 +47,7 @@ module.exports = class OneShot extends Card { }, }, shouldMeet() { - return (this.OneShotDay == 0); + return this.OneShotDay == 0; }, }, }; @@ -67,7 +67,10 @@ module.exports = class OneShot extends Card { return true; } } - if (meetingName == "One Shot Night" || meetingName == "One Shot Day"){ + if ( + meetingName == "One Shot Night" || + meetingName == "One Shot Day" + ) { return true; } if (meetingName == "Graveyard") return true; diff --git a/Games/types/Mafia/roles/cards/StrongModifier.js b/Games/types/Mafia/roles/cards/StrongModifier.js index caed893fa..370132ec0 100644 --- a/Games/types/Mafia/roles/cards/StrongModifier.js +++ b/Games/types/Mafia/roles/cards/StrongModifier.js @@ -5,22 +5,17 @@ module.exports = class StrongModifier extends Card { constructor(role) { super(role); - - this.actions = [ + this.actions = [ { 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); - } + 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); } + } }, }, ]; diff --git a/Games/types/Mafia/roles/cards/Suspended.js b/Games/types/Mafia/roles/cards/Suspended.js index 1a9d16f27..9e7cbbbb0 100644 --- a/Games/types/Mafia/roles/cards/Suspended.js +++ b/Games/types/Mafia/roles/cards/Suspended.js @@ -9,15 +9,12 @@ const { module.exports = class Suspended extends Card { constructor(role) { super(role); - if(this.role.SuspendedDate == null || this.role.SuspendedDate <= 0){ + if (this.role.SuspendedDate == null || this.role.SuspendedDate <= 0) { this.role.SuspendedDate = 1; - } - else{ + } else { this.role.SuspendedDate = this.role.SuspendedDate + 1; } - - this.meetingMods = { "*": { shouldMeetMod: function (meetingName) { diff --git a/Games/types/Mafia/roles/cards/Vigicultist.js b/Games/types/Mafia/roles/cards/Vigicultist.js index cf21955d5..57505b4bc 100644 --- a/Games/types/Mafia/roles/cards/Vigicultist.js +++ b/Games/types/Mafia/roles/cards/Vigicultist.js @@ -23,7 +23,7 @@ module.exports = class Vigicultist extends Card { } */ if (this.dominates()) this.target.kill("basic", this.actor); - if(this.target.alive){ + if (this.target.alive) { let action = new Action({ actor: this.actor, target: this.target, diff --git a/Games/types/Mafia/roles/cards/VillageCore.js b/Games/types/Mafia/roles/cards/VillageCore.js index b058826af..81aec4302 100644 --- a/Games/types/Mafia/roles/cards/VillageCore.js +++ b/Games/types/Mafia/roles/cards/VillageCore.js @@ -100,7 +100,16 @@ module.exports = class VillageCore extends Card { shouldSkip: function () { for (let player of this.game.players) { //this.game.sendAlert(`Stuff Village ${player.role.name}: ${player.role.modifier}: `); - if (this.game.getRoleTags(this.game.formatRoleInternal(player.role.name, player.role.modifier)).includes("Dusk")) { + if ( + this.game + .getRoleTags( + this.game.formatRoleInternal( + player.role.name, + player.role.modifier + ) + ) + .includes("Dusk") + ) { return false; } } @@ -115,7 +124,16 @@ module.exports = class VillageCore extends Card { type: "shouldSkip", shouldSkip: function () { for (let player of this.game.players) { - if (this.game.getRoleTags(this.game.formatRoleInternal(player.role.name,player.role.modifier)).includes("Dawn")) { + if ( + this.game + .getRoleTags( + this.game.formatRoleInternal( + player.role.name, + player.role.modifier + ) + ) + .includes("Dawn") + ) { return false; } } diff --git a/Games/types/Mafia/roles/cards/WinWithFaction.js b/Games/types/Mafia/roles/cards/WinWithFaction.js index 020c59b54..205a114aa 100644 --- a/Games/types/Mafia/roles/cards/WinWithFaction.js +++ b/Games/types/Mafia/roles/cards/WinWithFaction.js @@ -112,6 +112,7 @@ module.exports = class WinWithFaction extends Card { const clownInGame = this.game.players.filter( (p) => p.role.name === "Clown" && p.role.clownCondemned != true ); + const bomberInGame = this.game.players.filter((p) => p.role.name === "Bomber"); //Special Win Cons // win by Zealot @@ -193,6 +194,22 @@ module.exports = class WinWithFaction extends Card { return; } } + // win by killing senators + if (EVIL_FACTIONS.includes(this.player.faction)) { + var hasSenators = false; + var senatorCount = 0; + for (let p of this.game.players) { + if (p.role.name == "Senator") { + hasSenators = true; + senatorCount += p.alive ? 1 : -1; + } + } + + if (hasSenators && senatorCount <= 0) { + factionWin(this); + return; + } + } // win by guessing seer if (EVIL_FACTIONS.includes(this.player.faction)) { if ( @@ -205,6 +222,23 @@ module.exports = class WinWithFaction extends Card { } } + //win by killing Bomber + if (this.player.faction == "Village") { + var hasSenators = false; + var senatorCount = 0; + for (let p of this.game.players) { + if (p.role.name == "Senator") { + hasSenators = true; + senatorCount += p.alive ? 1 : -1; + } + } + if (hasSenators && senatorCount <= 0) return; + if (this.killedBomber) { + factionWin(this); + return; + } + } + //Win Blocking //Guessed Seer Conditional @@ -273,6 +307,13 @@ module.exports = class WinWithFaction extends Card { return; } } + //Bomber conditional + if (MAFIA_FACTIONS.includes(this.player.faction) || this.player.faction == "Village") { + if (bomberInGame.length > 0) { + //if bomber exist is not condemned, Mafia will not win by Majority and Village Will Not Win by killing all mafia. + return; + } + } /* //Shoggoth conditional if (CULT_FACTIONS.includes(this.player.faction) && !ONE_NIGHT) { @@ -449,22 +490,7 @@ module.exports = class WinWithFaction extends Card { return; } } - // win by killing senators - if (EVIL_FACTIONS.includes(this.player.faction)) { - var hasSenators = false; - var senatorCount = 0; - for (let p of this.game.players) { - if (p.role.name == "Senator") { - hasSenators = true; - senatorCount += p.alive ? 1 : -1; - } - } - - if (hasSenators && senatorCount <= 0) { - factionWin(this); - return; - } - } + //Village Normal Win if (this.player.faction == "Village" && !ONE_NIGHT) { if ( @@ -573,6 +599,9 @@ module.exports = class WinWithFaction extends Card { if (this.oblivious["Faction"]) return; + const bomberInGame = this.game.players.filter((p) => p.role.name === "Bomber"); + if(bomberInGame.length > 0) return; + if ( this.game.started == true && this.game.setup.hiddenConverts == true @@ -635,6 +664,17 @@ module.exports = class WinWithFaction extends Card { } this.killedPresident = true; } + if (player.role.name == "Bomber") { + const otherBombers = this.game.players.filter( + (p) => + p.alive && + (p.role.name == "Bomber") + ); + if (otherBombers.length > 0 || this.killedPresident == true) { + return; + } + this.killedBomber = true; + } if (player.role.name == "Clown") { if (deathType == "condemn") { this.clownCondemned = true; diff --git a/data/modifiers.js b/data/modifiers.js index 61088e2eb..9957b8cff 100644 --- a/data/modifiers.js +++ b/data/modifiers.js @@ -8,7 +8,7 @@ const modifierData = { }, Rifled: { internal: ["StartWithRifle"], - tags: ["Items", "Killing", "Gun","Alignments"], + tags: ["Items", "Killing", "Gun", "Alignments"], description: "Starts with a rifle.", allowDuplicate: true, }, @@ -106,14 +106,14 @@ const modifierData = { }, Delayed: { internal: ["Delayed"], - tags: ["Delayed","Meetings"], + tags: ["Delayed", "Meetings"], description: "Cannot attend secondary meetings for the first day and night.", incompatible: ["Lazy", "Odd", "Even", "Exhausted"], }, Suspended: { internal: ["Suspended"], - tags: ["Suspended","Meetings"], + tags: ["Suspended", "Meetings"], description: "Can only attend secondary meetings for the first day and night.", allowDuplicate: true, @@ -121,14 +121,14 @@ const modifierData = { }, Even: { internal: ["Even"], - tags: ["Even","Meetings"], + tags: ["Even", "Meetings"], description: "Can only attend secondary meetings on even days and nights.", incompatible: ["Lazy", "Odd", "Delayed", "Exhausted"], }, Odd: { internal: ["Odd"], - tags: ["Odd","Meetings"], + tags: ["Odd", "Meetings"], description: "Can only attend secondary meetings on odd days and nights.", incompatible: ["Lazy", "Even", "Delayed", "Exhausted"], }, @@ -141,7 +141,7 @@ const modifierData = { }, "One Shot": { internal: ["OneShot"], - tags: ["One Shot","Dawn","Dusk", "Pregame Actions"], + tags: ["One Shot", "Dawn", "Dusk", "Pregame Actions"], description: "Can only perform actions once.", incompatible: ["Exhausted"], }, @@ -169,9 +169,8 @@ const modifierData = { }, Strong: { internal: ["StrongModifier"], - tags: ["Unblockable","Strong"], - description: - "All kills performed by this player cannot be saved.", + tags: ["Unblockable", "Strong"], + description: "All kills performed by this player cannot be saved.", }, Unwavering: { internal: ["ConvertImmune"], @@ -180,7 +179,7 @@ const modifierData = { }, Reactionary: { internal: ["KillConverters"], - tags: ["Convert Saver","Killing","Reflexive"], + tags: ["Convert Saver", "Killing", "Reflexive"], description: "Kills anyone (up to two people) who tries to convert them at night.", }, @@ -214,28 +213,28 @@ const modifierData = { }, Vain: { internal: ["Vain"], - tags: ["Visits", "Killing","Alignments"], + tags: ["Visits", "Killing", "Alignments"], 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"], description: "If this player visits a player of the opposite alignment, they die.", incompatible: ["Vain"], }, Disloyal: { internal: ["Disloyal"], - tags: ["Visits", "Block Self","Alignments"], + tags: ["Visits", "Block Self", "Alignments"], description: "If this player visits a player of the same alignment, their secondary actions will be blocked.", incompatible: ["Loyal"], }, Loyal: { internal: ["Loyal"], - tags: ["Visits", "Block Self","Alignments"], + tags: ["Visits", "Block Self", "Alignments"], description: "If this player visits a player of the opposite alignment, their secondary actions will be blocked.", incompatible: ["Disloyal"], @@ -262,7 +261,7 @@ const modifierData = { }, Unassuming: { internal: ["AppearAsVillagerOnDeath"], - tags: ["Villager","Deception"], + tags: ["Villager", "Deception"], description: "Appears as Villager when condemned or on death.", incompatible: ["Shady", "Blasphemous", "Faceless"], }, @@ -274,7 +273,7 @@ const modifierData = { }, Clumsy: { internal: ["RevealRoleToTarget"], - tags: ["Information", "Visits","Roles"], + tags: ["Information", "Visits", "Roles"], description: "Announces the player's role to the targets of their night actions.", }, @@ -311,31 +310,31 @@ const modifierData = { }, Simple: { internal: ["Simple"], - tags: ["Visits", "Block Self","Vanilla"], + tags: ["Visits", "Block Self", "Vanilla"], description: "If this player visits a player with a power role, all their actions will be blocked.", incompatible: ["Complex"], }, Complex: { internal: ["Complex"], - tags: ["Visits", "Block Self","Vanilla"], + tags: ["Visits", "Block Self", "Vanilla"], description: "If this player visits a player with a vanilla role, all their actions will be blocked.", incompatible: ["Simple"], }, Morbid: { internal: ["VisitOnlyDead"], - tags: ["Visits", "Dead","Broken"], + tags: ["Visits", "Dead", "Broken"], description: "Secondary actions can only be used on dead players.", }, Restless: { internal: ["ActWhileDead"], - tags: ["Dead","Graveyard"], + tags: ["Dead", "Graveyard"], description: "Can only perform secondary actions while dead.", }, Transcendent: { internal: ["ActAliveOrDead"], - tags: ["Dead","Graveyard"], + tags: ["Dead", "Graveyard"], description: "Can perform secondary actions while either alive or dead.", }, Kleptomaniac: { @@ -377,7 +376,7 @@ const modifierData = { }, Sacrificial: { internal: ["Sacrificial"], - tags: ["Sacrificial","Killing"], + tags: ["Sacrificial", "Killing"], description: "Will sacrifice themselves and die, if they ever visit another player.", }, @@ -400,7 +399,7 @@ const modifierData = { }, Apprehensive: { internal: ["LearnVisitorsAndArm"], - tags: ["Items","Gun","Killing","Reflexive","Information"], + tags: ["Items", "Gun", "Killing", "Reflexive", "Information"], description: "Will receive a Gun (that will not reveal shooter) with each visit and learn the name of the visitor.", }, @@ -422,7 +421,7 @@ const modifierData = { }, Omniscient: { internal: ["Omniscient"], - tags: ["Roles","Visits","Information"], + tags: ["Roles", "Visits", "Information"], description: "Each night see all visits and learn all players roles.", }, Unkillable: { @@ -445,14 +444,14 @@ const modifierData = { }, Inclusive: { internal: ["Add1Banished"], - tags: ["Banished","Setup Changes"], + tags: ["Banished", "Setup Changes"], description: "Adds 1 Banished Role in Closed Setups.", allowDuplicate: true, incompatible: ["Banished", "Exclusive"], }, Exclusive: { internal: ["Remove1Banished"], - tags: ["Banished","Setup Changes"], + tags: ["Banished", "Setup Changes"], description: "Removes 1 Banished Role in Closed Setups.", allowDuplicate: true, incompatible: ["Banished", "Inclusive"], @@ -465,20 +464,20 @@ const modifierData = { }, Verrucose: { internal: ["GivePermaMindRot"], - tags: ["Sacrificial","Manipulative", "Mind Rot"], + tags: ["Sacrificial", "Manipulative", "Mind Rot"], description: "On death a random Village Aligned player will be chosen to be inflicted with Mind Rot for the rest of the game.", }, Rotten: { internal: ["Rotten"], - tags: ["Manipulative", "Mind Rot","Block Self"], + tags: ["Manipulative", "Mind Rot", "Block Self"], description: "At the start of the game is inflicted with Mind Rot for the rest of the game.", incompatible: ["Infected"], }, Infected: { internal: ["MindRot50Percent"], - tags: ["Manipulative", "Mind Rot","Block Self"], + tags: ["Manipulative", "Mind Rot", "Block Self"], description: "Each night has 50% chance to be inflicted with Mind Rot for that night.", incompatible: ["Rotten"], @@ -496,13 +495,13 @@ const modifierData = { }, Wise: { internal: ["MakePlayerLearnOneOfTwoPlayersOnDeath"], - tags: ["Sacrificial","Information"], + tags: ["Sacrificial", "Information"], description: "If killed at night, a player with this modifier learns that 1 of 2 players is evil.", }, Dovish: { internal: ["VillageMightSurviveCondemn"], - tags: ["Condemn", "Condemn Immune","Alignments","Protective"], + tags: ["Condemn", "Condemn Immune", "Alignments", "Protective"], description: "While a role with this modifier is in play, Village-aligned players might survive being condemned", }, @@ -519,13 +518,13 @@ const modifierData = { }, Clannish: { internal: ["AddRottenCopy"], - tags: ["Mind Rot","Setup Changes"], + tags: ["Mind Rot", "Setup Changes"], description: "In closed Setups will add 0 to 2 Copies of This Role, 1 of the added roles is Permanently inflicted with Mind Rot.", }, Chaotic: { internal: ["BecomeExcessRole"], - tags: ["Conversion","Excess Roles"], + tags: ["Conversion", "Excess Roles"], description: "On the first night, a player with this modifier will become a random excess role within their alignment. Independents will become excess roles from any alignment.", }, diff --git a/data/renamedRoles.js b/data/renamedRoles.js index 8672e1ad7..e13d387d6 100644 --- a/data/renamedRoles.js +++ b/data/renamedRoles.js @@ -48,7 +48,6 @@ module.exports = { Knight: "Villager", Veteran: "Villager", Bomb: "Villager", - Bomber: "Villager", "Village Idiot": "Villager", "Lazy Cop": "Cop", Checker: "Visitor", diff --git a/data/roles.js b/data/roles.js index 8eba31531..27dbd0b37 100644 --- a/data/roles.js +++ b/data/roles.js @@ -1336,7 +1336,7 @@ const roleData = { category: "Essential", tags: ["Essential", "Selective Revealing"], description: [ - "All villagers will know who the President is.", + "All villagers will know who the President is. Unless a Bomber is present.", "When the President dies, the Mafia will win.", ], }, @@ -2107,6 +2107,19 @@ const roleData = { "Can not be seen as a Villager, Impersonator or Imposter", ], }, + Bomber: { + alignment: "Mafia", + tags: ["Mini-game", "Essential", "Split Decision", "Killing","Setup Changes", "Dusk", "Pregame Actions", "Dawn"], + description: [ + "Splits All Players into 2 Rooms.", + "During the Day each room will meet and choose a Leader", + "At Night the Leaders will Meet and choose players to switch rooms.", + "After 3 Rounds of Switching, Will kill All Players who are in the same room as them.", + "Bomber will Force a President to spawn unless Senators are Present", + "Village Wins if a Bomber dies before a President dies or Majority of the Senators die.", + "Mafia will Not win by Majority if Bomber exists", + ], + }, //Cult //Basic diff --git a/react_main/public/images/roles/lich-vivid.png b/react_main/public/images/roles/lich-vivid.png new file mode 100644 index 000000000..909f53448 Binary files /dev/null and b/react_main/public/images/roles/lich-vivid.png differ diff --git a/react_main/src/constants/slangList.js b/react_main/src/constants/slangList.js index 460e3ce75..3e9a53f60 100644 --- a/react_main/src/constants/slangList.js +++ b/react_main/src/constants/slangList.js @@ -379,9 +379,9 @@ export const slangList = { definition: "Third Party. Legacy name for Independent roles, or roles with win conditions separate from Village, Mafia, and Cult.", }, - "Event": { + Event: { definition: - "Events are special things that will occur each night if placed in a setup. If an Event Occurs it will inform players the following day or night.", + "Events are special things that will occur each night if placed in a setup. If an Event Occurs it will inform players the following day or night.", emoji: "⚡", }, Faction: { @@ -400,9 +400,8 @@ export const slangList = { definition: "Closed Roles setup. A set of roles chosen for a game of Mafia that are unknown to the players. Not knowing what interactions might happen can create exciting game moments, and the necessity of deducing the presence or absence of certain roles among living players gives players a chance to show their cleverness.", }, - "Ng": { - definition: - "No Gun", + Ng: { + definition: "No Gun", emoji: "🔪", }, Day: { @@ -475,7 +474,7 @@ export const slangList = { "A night action which prevents another player from Visiting. Actions that specify a visit will fail to function. If a night action does not mention a visit, or if the player you're blocking only has day actions, Blocking does nothing. Synonymous with hook.", emoji: "🛡", }, - "Rot": { + Rot: { definition: "A Special version of Blocking/Hooking. If the Action performed is Investigative, Instead of being blocked the player will get False Info.", emoji: "🧠", diff --git a/react_main/src/css/roles.css b/react_main/src/css/roles.css index 24dcf4509..161c77ba2 100644 --- a/react_main/src/css/roles.css +++ b/react_main/src/css/roles.css @@ -1301,6 +1301,10 @@ background-image: url("/images/roles/shoggoth-vivid.png"); } +.role-icon-scheme-vivid .role-Mafia-Lich { + background-image: url("/images/roles/lich-vivid.png"); +} + .role-icon-scheme-vivid .role-Mafia-Enchantress { background-image: url("/images/roles/enchantress-vivid.png"); }