diff --git a/chat-plugins/mafia-data.js b/chat-plugins/mafia-data.js
new file mode 100755
index 0000000000..2055ab3718
--- /dev/null
+++ b/chat-plugins/mafia-data.js
@@ -0,0 +1,99 @@
+// Data for the mafia chat plugin.
+
+'use strict';
+
+// This object contains all functions that execute in the callback function of any mafia class. Executed from the context of the executing player.
+// Target is a MafiaPlayer object.
+let MafiaFunctions = {
+ copReport: function (target) {
+ if (target.class.side === 'village') {
+ return 'After investigating ' + target.name + ' you find out they\'re sided with the village.';
+ } else if (target.class.side === 'village') {
+ return 'After investigating ' + target.name + ' you find out they\'re sided with the mafia.';
+ } else {
+ return 'After investigating ' + target.name + ' you find out they\'re not sided with the village or mafia.';
+ }
+ },
+ roleBlock: function (target) {
+ target.roleBlocked = true;
+ return 'You visit ' + target.name + ' during the night.';
+ },
+ protect: function (target) {
+ target.invincible = true;
+ return 'You give ' + target.name + ' their daily dose of medicine to keep them safe and sound.';
+ },
+ killTarget: function (target) {
+ target.kill('The werewolf has eaten a tasty snack!');
+ }
+};
+
+// Every role has a side they belong to, as well as all functions they have. These functions are objects with the targeting mechanics and a callback.
+// events are atStart, onNight, onDay, onLynch.
+exports.MafiaClasses = {
+ villager: {
+ name: "Villager",
+ side: 'town',
+ image: '',
+ flavorText: 'You are a villager. You live peacefully in the town, which with the mafia activity hasn\'t been all too peaceful, actually.'
+ },
+
+ mafia: {
+ name: "Mafia",
+ side: 'mafia',
+ image: '',
+ flavorText: 'You are a member of the mafia. Every night, you get together with the other mafia members to eliminate someone in the town. The townsfolk aren\'t all that happy with that, however.'
+ },
+
+ hooker: {
+ name: "Hooker",
+ side: 'town',
+ image: '',
+ flavorText: 'You are the hooker. Every night, you can visit someone in the town. The person you visit can\'t execute any actions that night.',
+
+ onNight: {
+ target: {side: 'any', count: 'single'},
+ priority: 5,
+ callback: MafiaFunctions.roleBlock
+ }
+ },
+
+ doctor: {
+ name: "Doctor",
+ side: 'town',
+ image: '',
+ flavorText: 'You are the doctor. Every night, you can visit someone in the town. This person can\'t die that night.',
+
+ onNight: {
+ target: {side: 'any', count: 'single'},
+ priority: 4,
+ callback: MafiaFunctions.protected
+ }
+ },
+
+ cop: {
+ name: "Cop",
+ side: 'town',
+ image: '',
+ flavorText: 'You are the cop. Every night, you can visit someone in town. When the night is over, you\'ll receive a report with that person\'s alignment.',
+
+ onNight: {
+ target: {side: 'any', count: 'single'},
+ priority: -1,
+ callback: MafiaFunctions.copReport
+ }
+ },
+
+ werewolf: {
+ name: "Werewolf",
+ side: 'solo',
+ image: '',
+ flavorText: 'You are the werewolf. You\'re not aligned with either town or mafia, and instead kill someone every night. You win if you\'re the only remaining player.',
+ victoryText: 'The wolf howls victorious, knowing he came out of this mess alive, and with some lunch as well.',
+
+ onNight: {
+ target: {side: 'any', count: 'single'},
+ priority: 2,
+ callback: MafiaFunctions.killTarget
+ }
+ }
+};
diff --git a/chat-plugins/mafia.js b/chat-plugins/mafia.js
new file mode 100755
index 0000000000..7918c380fa
--- /dev/null
+++ b/chat-plugins/mafia.js
@@ -0,0 +1,609 @@
+// Mafia chat plugin.
+// By bumbadadabum, with input from Zarel and art by crobat.
+
+'use strict';
+
+let MafiaData = require('./mafia-data.js');
+
+const permission = 'ban';
+
+const deadImage = '';
+const meetingMsg = {town: 'The town has lynched a subject!', mafia: 'The mafia strikes again!'};
+
+class MafiaPlayer extends Rooms.RoomGamePlayer {
+ constructor(user, game) {
+ super(user, game);
+
+ this.voting = false;
+ this.targeting = false;
+ }
+
+ event(event) {
+ if (this.class[event].target) {
+ this.targeting = true;
+ this.toExecute = this.class[event].callback;
+ if (this.class[event].target.count === 'single') {
+ this.singleTarget(this.class[event].target.side);
+ }
+ this.targetWindow(this.class.image, this.class[event].flavorText);
+ } else {
+ this.toExecute = this.class[event].function;
+ }
+
+ this.game.executionOrder.push(this);
+ }
+
+ kill(message) {
+ if (this.invincible) return;
+
+ this.game.announcementWindow(deadImage, message + ' ' + Tools.escapeHTML(this.name + ', the ' + this.class.name) + ' lies dead on the ground.');
+ delete this.game.players[this.userid];
+ this.destroy();
+ }
+
+ eliminate() {
+ if (this.invincible) return;
+
+ if (this.game.gamestate === 'pregame') {
+ this.game.announcementWindow('', Tools.escapeHTML(this.name) + ' was kicked from the game.');
+ } else {
+ this.game.announcementWindow(deadImage, Tools.escapeHTML(this.name + ', the ' + this.class.name) + ' was eliminated from the game.');
+ }
+ delete this.game.players[this.userid];
+ this.destroy();
+ }
+
+ playerWindow(image, content) {
+ this.sendRoom('|html|' + this.game.mafiaWindow(image, content));
+ }
+
+ getRole() {
+ this.sendRoom('|html|' + this.game.mafiaWindow(this.class.image, Tools.escapeHTML(this.class.flavorText)));
+ }
+
+ targetWindow(image, content) {
+ let output = content;
+ output += '