Spaces:
Running
Running
; | |
var __defProp = Object.defineProperty; | |
var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | |
var __getOwnPropNames = Object.getOwnPropertyNames; | |
var __hasOwnProp = Object.prototype.hasOwnProperty; | |
var __export = (target, all) => { | |
for (var name in all) | |
__defProp(target, name, { get: all[name], enumerable: true }); | |
}; | |
var __copyProps = (to, from, except, desc) => { | |
if (from && typeof from === "object" || typeof from === "function") { | |
for (let key of __getOwnPropNames(from)) | |
if (!__hasOwnProp.call(to, key) && key !== except) | |
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | |
} | |
return to; | |
}; | |
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | |
var mafia_exports = {}; | |
__export(mafia_exports, { | |
commands: () => commands, | |
pages: () => pages, | |
roomSettings: () => roomSettings | |
}); | |
module.exports = __toCommonJS(mafia_exports); | |
var import_lib = require("../../lib"); | |
const MafiaEliminateType = { | |
ELIMINATE: "was eliminated", | |
// standard (vote + faction kill + anything else) | |
KICK: "was kicked from the game", | |
// staff kick | |
TREESTUMP: "was treestumped", | |
// can still talk | |
SPIRIT: "became a restless spirit", | |
// can still vote | |
SPIRITSTUMP: "became a restless treestump" | |
// treestump + spirit | |
}; | |
const DATA_FILE = "config/chat-plugins/mafia-data.json"; | |
const LOGS_FILE = "config/chat-plugins/mafia-logs.json"; | |
const VALID_IMAGES = [ | |
"cop", | |
"dead", | |
"doctor", | |
"fool", | |
"godfather", | |
"goon", | |
"hooker", | |
"mafia", | |
"mayor", | |
"villager", | |
"werewolf" | |
]; | |
let MafiaData = /* @__PURE__ */ Object.create(null); | |
let logs = { leaderboard: {}, mvps: {}, hosts: {}, plays: {}, leavers: {} }; | |
Punishments.addRoomPunishmentType({ | |
type: "MAFIAGAMEBAN", | |
desc: "banned from playing mafia games" | |
}); | |
Punishments.addRoomPunishmentType({ | |
type: "MAFIAHOSTBAN", | |
desc: "banned from hosting mafia games" | |
}); | |
const hostQueue = []; | |
const IDEA_TIMER = 60 * 1e3; | |
function readFile(path) { | |
try { | |
const json = (0, import_lib.FS)(path).readIfExistsSync(); | |
if (!json) { | |
return false; | |
} | |
return Object.assign(/* @__PURE__ */ Object.create(null), JSON.parse(json)); | |
} catch (e) { | |
if (e.code !== "ENOENT") | |
throw e; | |
} | |
} | |
function writeFile(path, data) { | |
(0, import_lib.FS)(path).writeUpdate(() => JSON.stringify(data)); | |
} | |
MafiaData = readFile(DATA_FILE) || { alignments: {}, roles: {}, themes: {}, IDEAs: {}, terms: {}, aliases: {} }; | |
if (!MafiaData.alignments.town) { | |
MafiaData.alignments.town = { | |
name: "town", | |
plural: "town", | |
memo: [`This alignment is required for the script to function properly.`] | |
}; | |
} | |
if (!MafiaData.alignments.solo) { | |
MafiaData.alignments.solo = { | |
name: "solo", | |
plural: "solo", | |
memo: [`This alignment is required for the script to function properly.`] | |
}; | |
} | |
logs = readFile(LOGS_FILE) || { leaderboard: {}, mvps: {}, hosts: {}, plays: {}, leavers: {} }; | |
const tables = ["leaderboard", "mvps", "hosts", "plays", "leavers"]; | |
for (const section of tables) { | |
const month = new Date().toLocaleString("en-us", { month: "numeric", year: "numeric" }); | |
if (!logs[section]) | |
logs[section] = {}; | |
if (!logs[section][month]) | |
logs[section][month] = {}; | |
if (Object.keys(logs[section]).length >= 3) { | |
const keys = import_lib.Utils.sortBy(Object.keys(logs[section]), (key) => { | |
const [monthStr, yearStr] = key.split("/"); | |
return [parseInt(yearStr), parseInt(monthStr)]; | |
}); | |
while (keys.length > 2) { | |
const curKey = keys.shift(); | |
if (!curKey) | |
break; | |
delete logs[section][curKey]; | |
} | |
} | |
} | |
writeFile(LOGS_FILE, logs); | |
class MafiaPlayer extends Rooms.RoomGamePlayer { | |
constructor(user, game) { | |
super(user, game); | |
this.safeName = import_lib.Utils.escapeHTML(this.name); | |
this.role = null; | |
this.voting = ""; | |
this.hammerRestriction = null; | |
this.lastVote = 0; | |
this.eliminated = null; | |
this.eliminationOrder = 0; | |
this.silenced = false; | |
this.nighttalk = false; | |
this.revealed = ""; | |
this.IDEA = null; | |
this.action = null; | |
this.actionArr = []; | |
} | |
/** | |
* Called if the player's name changes. | |
*/ | |
updateSafeName() { | |
this.safeName = import_lib.Utils.escapeHTML(this.name); | |
} | |
getStylizedRole(button = false) { | |
if (!this.role) | |
return; | |
let color = MafiaData.alignments[this.role.alignment].color; | |
if (button && MafiaData.alignments[this.role.alignment].buttonColor) { | |
color = MafiaData.alignments[this.role.alignment].buttonColor; | |
} | |
return `<span style="font-weight:bold;color:${color}">${this.role.safeName}</span>`; | |
} | |
isEliminated() { | |
return this.eliminated !== null; | |
} | |
isTreestump() { | |
return this.eliminated === MafiaEliminateType.TREESTUMP || this.eliminated === MafiaEliminateType.SPIRITSTUMP; | |
} | |
isSpirit() { | |
return this.eliminated === MafiaEliminateType.SPIRIT || this.eliminated === MafiaEliminateType.SPIRITSTUMP; | |
} | |
// Only call updateHtmlRoom if the player is still involved in the game in some way | |
tryUpdateHtmlRoom() { | |
if ([ | |
null, | |
MafiaEliminateType.SPIRIT, | |
MafiaEliminateType.TREESTUMP, | |
MafiaEliminateType.SPIRITSTUMP | |
].includes(this.eliminated)) { | |
this.updateHtmlRoom(); | |
} | |
} | |
/** | |
* Updates the mafia HTML room for this player. | |
* @param id Only provided during the destruction process to update the HTML one last time after player.id is cleared. | |
*/ | |
updateHtmlRoom() { | |
if (this.game.ended) | |
return this.closeHtmlRoom(); | |
const user = Users.get(this.id); | |
if (!user?.connected) | |
return; | |
for (const conn of user.connections) { | |
void Chat.resolvePage(`view-mafia-${this.game.room.roomid}`, user, conn); | |
} | |
} | |
closeHtmlRoom() { | |
const user = Users.get(this.id); | |
if (!user?.connected) | |
return; | |
return user.send(`>view-mafia-${this.game.room.roomid} | |
|deinit`); | |
} | |
updateHtmlVotes() { | |
const user = Users.get(this.id); | |
if (!user?.connected) | |
return; | |
const votes = this.game.voteBoxFor(this.id); | |
user.send(`>view-mafia-${this.game.room.roomid} | |
|selectorhtml|#mafia-votes|` + votes); | |
} | |
} | |
class Mafia extends Rooms.RoomGame { | |
constructor(room, host) { | |
super(room); | |
this.gameid = "mafia"; | |
this.title = "Mafia"; | |
this.playerCap = 20; | |
this.allowRenames = false; | |
this.started = false; | |
this.theme = null; | |
this.hostid = host.id; | |
this.host = import_lib.Utils.escapeHTML(host.name); | |
this.cohostids = []; | |
this.cohosts = []; | |
this.subs = []; | |
this.autoSub = true; | |
this.requestedSub = []; | |
this.hostRequestedSub = []; | |
this.played = []; | |
this.hammerCount = 0; | |
this.votes = /* @__PURE__ */ Object.create(null); | |
this.voteModifiers = /* @__PURE__ */ Object.create(null); | |
this.hammerModifiers = /* @__PURE__ */ Object.create(null); | |
this.hasPlurality = null; | |
this.enableNV = true; | |
this.voteLock = false; | |
this.votingEnabled = true; | |
this.forceVote = false; | |
this.closedSetup = false; | |
this.noReveal = true; | |
this.selfEnabled = false; | |
this.takeIdles = true; | |
this.originalRoles = []; | |
this.originalRoleString = ""; | |
this.roles = []; | |
this.roleString = ""; | |
this.phase = "signups"; | |
this.dayNum = 0; | |
this.timer = null; | |
this.dlAt = 0; | |
this.IDEA = { | |
data: null, | |
timer: null, | |
discardsHidden: false, | |
discardsHTML: "", | |
waitingPick: [] | |
}; | |
this.sendHTML(this.roomWindow()); | |
} | |
join(user, staffAdd = null, force = false) { | |
if (this.phase !== "signups" && !staffAdd) { | |
return this.sendUser(user, `|error|The game of ${this.title} has already started.`); | |
} | |
this.canJoin(user, !staffAdd, force); | |
if (this.playerCount >= this.playerCap) | |
return this.sendUser(user, `|error|The game of ${this.title} is full.`); | |
const player = this.addPlayer(user); | |
if (!player) | |
return this.sendUser(user, `|error|You have already joined the game of ${this.title}.`); | |
if (this.started) { | |
player.role = { | |
name: `Unknown`, | |
safeName: `Unknown`, | |
id: `unknown`, | |
alignment: "solo", | |
image: "", | |
memo: [`You were added to the game after it had started. To learn about your role, PM the host (${this.host}).`] | |
}; | |
this.roles.push(player.role); | |
this.played.push(player.id); | |
} else { | |
this.originalRoles = []; | |
this.originalRoleString = ""; | |
this.roles = []; | |
this.roleString = ""; | |
} | |
if (this.subs.includes(user.id)) | |
this.subs.splice(this.subs.indexOf(user.id), 1); | |
player.updateHtmlRoom(); | |
if (staffAdd) { | |
this.sendDeclare(`${player.name} has been added to the game by ${staffAdd.name}.`); | |
this.logAction(staffAdd, "added player"); | |
} else { | |
this.sendRoom(`${player.name} has joined the game.`); | |
} | |
} | |
leave(user) { | |
const player = this.getPlayer(user.id); | |
if (!player) { | |
return this.sendUser(user, `|error|You have not joined the game of ${this.title}.`); | |
} | |
if (this.phase !== "signups") | |
return this.sendUser(user, `|error|The game of ${this.title} has already started.`); | |
this.removePlayer(player); | |
let subIndex = this.requestedSub.indexOf(user.id); | |
if (subIndex !== -1) | |
this.requestedSub.splice(subIndex, 1); | |
subIndex = this.hostRequestedSub.indexOf(user.id); | |
if (subIndex !== -1) | |
this.hostRequestedSub.splice(subIndex, 1); | |
this.sendRoom(`${user.name} has left the game.`); | |
for (const conn of user.connections) { | |
void Chat.resolvePage(`view-mafia-${this.room.roomid}`, user, conn); | |
} | |
} | |
static isGameBanned(room, user) { | |
return Punishments.hasRoomPunishType(room, toID(user), "MAFIAGAMEBAN"); | |
} | |
static gameBan(room, user, reason, duration) { | |
Punishments.roomPunish(room, user, { | |
type: "MAFIAGAMEBAN", | |
id: toID(user), | |
expireTime: Date.now() + duration * 24 * 60 * 60 * 1e3, | |
reason | |
}); | |
} | |
static ungameBan(room, user) { | |
Punishments.roomUnpunish(room, toID(user), "MAFIAGAMEBAN", false); | |
} | |
static isHostBanned(room, user) { | |
return Mafia.isGameBanned(room, user) || Punishments.hasRoomPunishType(room, toID(user), "MAFIAGAMEBAN"); | |
} | |
static hostBan(room, user, reason, duration) { | |
Punishments.roomPunish(room, user, { | |
type: "MAFIAHOSTBAN", | |
id: toID(user), | |
expireTime: Date.now() + duration * 24 * 60 * 60 * 1e3, | |
reason | |
}); | |
} | |
static unhostBan(room, user) { | |
Punishments.roomUnpunish(room, toID(user), "MAFIAHOSTBAN", false); | |
} | |
makePlayer(user) { | |
return new MafiaPlayer(user, this); | |
} | |
getPlayer(userid) { | |
const matches = this.players.filter((p) => p.id === userid); | |
if (matches.length > 1) { | |
throw new Error(`Duplicate player IDs in Mafia game! Matches: ${matches.map((p) => p.id).join(", ")}`); | |
} | |
return matches.length > 0 ? matches[0] : null; | |
} | |
setRoles(user, roleString, force = false, reset = false) { | |
let roles = roleString.split(",").map((x) => x.trim()); | |
if (roles.length === 1) { | |
let themeName = toID(roles[0]); | |
if (themeName in MafiaData.aliases) | |
themeName = MafiaData.aliases[themeName]; | |
if (themeName in MafiaData.themes) { | |
const theme = MafiaData.themes[themeName]; | |
if (!theme[this.playerCount]) { | |
return user.sendTo( | |
this.room, | |
`|error|The theme "${theme.name}" does not have a role list for ${this.playerCount} players.` | |
); | |
} | |
const themeRoles = theme[this.playerCount]; | |
roles = themeRoles.split(",").map((x) => x.trim()); | |
this.theme = theme; | |
} else if (themeName in MafiaData.IDEAs) { | |
const IDEA = MafiaData.IDEAs[themeName]; | |
roles = IDEA.roles; | |
this.theme = null; | |
} else { | |
return this.sendUser(user, `|error|${roles[0]} is not a valid theme or IDEA.`); | |
} | |
} else { | |
this.theme = null; | |
} | |
if (roles.length < this.playerCount) { | |
return this.sendUser(user, `|error|You have not provided enough roles for the players.`); | |
} else if (roles.length > this.playerCount) { | |
user.sendTo( | |
this.room, | |
`|error|You have provided too many roles, ${roles.length - this.playerCount} ${Chat.plural(roles.length - this.playerCount, "roles", "role")} will not be assigned.` | |
); | |
} | |
if (force) { | |
this.originalRoles = roles.map((r) => ({ | |
name: r, | |
safeName: import_lib.Utils.escapeHTML(r), | |
id: toID(r), | |
alignment: "solo", | |
image: "", | |
memo: [`To learn more about your role, PM the host (${this.host}).`] | |
})); | |
import_lib.Utils.sortBy(this.originalRoles, (role) => role.name); | |
this.roles = this.originalRoles.slice(); | |
this.originalRoleString = this.originalRoles.map( | |
(r) => `<span style="font-weight:bold;color:${MafiaData.alignments[r.alignment].color || "#FFF"}">${r.safeName}</span>` | |
).join(", "); | |
this.roleString = this.originalRoleString; | |
this.sendRoom(`The roles have been ${reset ? "re" : ""}set.`); | |
if (reset) | |
this.distributeRoles(); | |
return; | |
} | |
const newRoles = []; | |
const problems = []; | |
const alignments = []; | |
const cache = /* @__PURE__ */ Object.create(null); | |
for (const roleName of roles) { | |
const roleId = roleName.toLowerCase().replace(/[^\w\d\s]/g, ""); | |
if (roleId in cache) { | |
newRoles.push({ ...cache[roleId] }); | |
} else { | |
const role = Mafia.parseRole(roleName); | |
if (role.problems.length) { | |
problems.push(...role.problems); | |
} | |
if (!alignments.includes(role.role.alignment)) | |
alignments.push(role.role.alignment); | |
cache[roleId] = role.role; | |
newRoles.push(role.role); | |
} | |
} | |
if (alignments.length < 2 && alignments[0] !== "solo") { | |
problems.push(`There must be at least 2 different alignments in a game!`); | |
} | |
if (problems.length) { | |
for (const problem of problems) { | |
this.sendUser(user, `|error|${problem}`); | |
} | |
return this.sendUser(user, `|error|To forcibly set the roles, use /mafia force${reset ? "re" : ""}setroles`); | |
} | |
this.IDEA.data = null; | |
this.originalRoles = newRoles; | |
import_lib.Utils.sortBy(this.originalRoles, (role) => [role.alignment, role.name]); | |
this.roles = this.originalRoles.slice(); | |
this.originalRoleString = this.originalRoles.map( | |
(r) => `<span style="font-weight:bold;color:${MafiaData.alignments[r.alignment].color || "#FFF"}">${r.safeName}</span>` | |
).join(", "); | |
this.roleString = this.originalRoleString; | |
if (!reset) | |
this.phase = "locked"; | |
this.updatePlayers(); | |
this.sendRoom(`The roles have been ${reset ? "re" : ""}set.`); | |
if (reset) | |
this.distributeRoles(); | |
} | |
resetGame() { | |
this.clearVotes(); | |
this.dayNum = 0; | |
this.phase = "night"; | |
for (const hostid of [...this.cohostids, this.hostid]) { | |
const host = Users.get(hostid); | |
if (host?.connected) | |
host.send(`>${this.room.roomid} | |
|notify|It's night in your game of Mafia!`); | |
} | |
for (const player of this.players) { | |
const user = Users.get(player.id); | |
if (user?.connected) { | |
this.sendUser(user, `|notify|It's night in the game of Mafia! Send in an action or idle.`); | |
} | |
player.actionArr.length = 0; | |
} | |
if (this.timer) | |
this.setDeadline(0); | |
this.sendDeclare(`The game has been reset.`); | |
this.distributeRoles(); | |
if (this.takeIdles) { | |
this.sendDeclare(`Night ${this.dayNum}. Submit whether you are using an action or idle. If you are using an action, DM your action to the host.`); | |
} else { | |
this.sendDeclare(`Night ${this.dayNum}. PM the host your action, or idle.`); | |
} | |
} | |
static parseRole(roleString) { | |
const roleName = roleString.replace(/solo/, "").trim(); | |
const role = { | |
name: roleName, | |
safeName: import_lib.Utils.escapeHTML(roleName), | |
id: toID(roleName), | |
image: "", | |
memo: ["During the Day, you may vote for someone to be eliminated."], | |
alignment: "" | |
}; | |
const problems = []; | |
let modAlignment = ""; | |
const roleWords = roleString.replace(/\(.+?\)/g, "").split(" ").map(toID); | |
let iters = 0; | |
outer: | |
while (roleWords.length) { | |
const currentWord = roleWords.slice(); | |
while (currentWord.length) { | |
if (iters++ === 1e3) | |
throw new Error(`Infinite loop.`); | |
let currentSearch = currentWord.join(""); | |
if (currentSearch in MafiaData.aliases) | |
currentSearch = MafiaData.aliases[currentSearch]; | |
if (currentSearch in MafiaData.roles) { | |
const mod = MafiaData.roles[currentSearch]; | |
if (mod.memo) | |
role.memo.push(...mod.memo); | |
if (mod.alignment && !modAlignment) | |
modAlignment = mod.alignment; | |
if (mod.image && !role.image) | |
role.image = mod.image; | |
roleWords.splice(0, currentWord.length); | |
continue outer; | |
} else if (currentSearch in MafiaData.alignments) { | |
if (role.alignment && role.alignment !== currentSearch) { | |
problems.push(`The role ${roleString} has multiple possible alignments (${role.alignment} and ${currentSearch})`); | |
} | |
role.alignment = currentSearch; | |
roleWords.splice(0, currentWord.length); | |
continue outer; | |
} | |
currentWord.pop(); | |
} | |
roleWords.shift(); | |
} | |
role.alignment = role.alignment || modAlignment; | |
if (!role.alignment) { | |
role.alignment = "town"; | |
} | |
if (problems.length) { | |
role.alignment = "solo"; | |
role.memo.push(`Your role has multiple conflicting alignments, ask the host for details.`); | |
} else { | |
const alignment = MafiaData.alignments[role.alignment]; | |
if (alignment) { | |
role.memo.push(...MafiaData.alignments[role.alignment].memo); | |
if (alignment.image && !role.image) | |
role.image = alignment.image; | |
} else { | |
problems.push(`Alignment desync: role ${role.name}'s alignment ${role.alignment} doesn't exist in data. Please report this to a mod.`); | |
} | |
} | |
return { role, problems }; | |
} | |
start(user, day = false) { | |
if (!user) | |
return; | |
if (this.phase !== "locked" && this.phase !== "IDEAlocked") { | |
if (this.phase === "signups") | |
return this.sendUser(user, `You need to close the signups first.`); | |
if (this.phase === "IDEApicking") { | |
return this.sendUser(user, `You must wait for IDEA picks to finish before starting.`); | |
} | |
return this.sendUser(user, `The game is already started!`); | |
} | |
if (this.playerCount < 2) | |
return this.sendUser(user, `You need at least 2 players to start.`); | |
if (this.phase === "IDEAlocked") { | |
for (const p of this.players) { | |
if (!p.role) | |
return this.sendUser(user, `|error|Not all players have a role.`); | |
} | |
} else { | |
if (!Object.keys(this.roles).length) | |
return this.sendUser(user, `You need to set the roles before starting.`); | |
if (Object.keys(this.roles).length < this.playerCount) { | |
return this.sendUser(user, `You have not provided enough roles for the players.`); | |
} | |
} | |
this.started = true; | |
this.sendDeclare(`The game of ${this.title} is starting!`); | |
this.distributeRoles(); | |
if (day) { | |
this.day(null, true); | |
} else { | |
this.night(false, true); | |
} | |
if (this.IDEA.data && !this.IDEA.discardsHidden) { | |
this.room.add(`|html|<div class="infobox"><details><summary>IDEA discards:</summary>${this.IDEA.discardsHTML}</details></div>`).update(); | |
} | |
} | |
distributeRoles() { | |
const roles = import_lib.Utils.shuffle(this.roles.slice()); | |
if (roles.length) { | |
for (const p of this.players) { | |
const role = roles.shift(); | |
p.role = role; | |
const u = Users.get(p.id); | |
p.revealed = ""; | |
if (u?.connected) { | |
u.send(`>${this.room.roomid} | |
|notify|Your role is ${role.safeName}. For more details of your role, check your Role PM.`); | |
} | |
} | |
} | |
this.clearEliminations(); | |
this.played = [this.hostid, ...this.cohostids, ...this.players.map((p) => p.id)]; | |
this.sendDeclare(`The roles have been distributed.`); | |
this.updatePlayers(); | |
} | |
getPartners(alignment, player) { | |
if (!player?.role || ["town", "solo", "traitor"].includes(player.role.alignment)) | |
return ""; | |
const partners = []; | |
for (const p of this.players) { | |
if (p.id === player.id) | |
continue; | |
const role = p.role; | |
if (role && role.alignment === player.role.alignment) | |
partners.push(p.name); | |
} | |
return partners.join(", "); | |
} | |
day(extension = null, initial = false) { | |
if (this.phase !== "night" && !initial) | |
return; | |
if (this.dayNum === 0 && extension !== null) | |
return this.sendUser(this.hostid, `|error|You cannot extend on day 0.`); | |
if (this.timer) | |
this.setDeadline(0); | |
if (extension === null) { | |
if (!isNaN(this.hammerCount)) | |
this.hammerCount = Math.floor(this.getRemainingPlayers().length / 2) + 1; | |
this.clearVotes(); | |
} | |
this.phase = "day"; | |
if (extension !== null && !initial) { | |
this.setDeadline(extension); | |
} else { | |
this.dayNum++; | |
} | |
if (isNaN(this.hammerCount)) { | |
this.sendDeclare(`Day ${this.dayNum}. Hammering is disabled.`); | |
} else { | |
this.sendDeclare(`Day ${this.dayNum}. The hammer count is set at ${this.hammerCount}`); | |
} | |
for (const p of this.players) { | |
p.action = null; | |
} | |
this.sendPlayerList(); | |
this.updatePlayers(); | |
} | |
night(early = false, initial = false) { | |
if (this.phase !== "day" && !initial) | |
return; | |
if (this.timer) | |
this.setDeadline(0, true); | |
this.phase = "night"; | |
for (const hostid of [...this.cohostids, this.hostid]) { | |
const host = Users.get(hostid); | |
if (host?.connected) | |
host.send(`>${this.room.roomid} | |
|notify|It's night in your game of Mafia!`); | |
} | |
for (const player of this.players) { | |
const user = Users.get(player.id); | |
if (user?.connected) { | |
this.sendUser(user, `|notify|It's night in the game of Mafia! Send in an action or idle.`); | |
} | |
} | |
if (this.takeIdles) { | |
this.sendDeclare(`Night ${this.dayNum}. Submit whether you are using an action or idle. If you are using an action, DM your action to the host.`); | |
} else { | |
this.sendDeclare(`Night ${this.dayNum}. PM the host your action, or idle.`); | |
} | |
const hasPlurality = this.getPlurality(); | |
if (!early && hasPlurality) { | |
this.sendRoom(`Plurality is on ${this.getPlayer(hasPlurality)?.name || "No Vote"}`); | |
} | |
if (!early && !initial) | |
this.sendRoom(`|raw|<div class="infobox">${this.voteBox()}</div>`); | |
if (initial && !isNaN(this.hammerCount)) | |
this.hammerCount = Math.floor(this.getRemainingPlayers().length / 2) + 1; | |
this.updatePlayers(); | |
} | |
vote(voter, targetId) { | |
if (!this.votingEnabled) | |
return this.sendUser(voter, `|error|Voting is not allowed.`); | |
if (this.phase !== "day") | |
return this.sendUser(voter, `|error|You can only vote during the day.`); | |
if (!voter || voter.isEliminated() && !voter.isSpirit()) | |
return; | |
const target = this.getPlayer(targetId); | |
if ((!target || target.isEliminated()) && targetId !== "novote") { | |
return this.sendUser(voter, `|error|${targetId} is not a valid player.`); | |
} | |
if (!this.enableNV && targetId === "novote") | |
return this.sendUser(voter, `|error|No Vote is not allowed.`); | |
if (targetId === voter.id && !this.selfEnabled) | |
return this.sendUser(voter, `|error|Self voting is not allowed.`); | |
if (this.voteLock && voter.voting) { | |
return this.sendUser(voter, `|error|You cannot switch your vote because votes are locked.`); | |
} | |
const currentVotes = this.votes[targetId] ? this.votes[targetId].count : 0; | |
const hammering = currentVotes + 1 >= this.hammerCount; | |
if (targetId === voter.id && !hammering && this.selfEnabled === "hammer") { | |
return this.sendUser(voter, `|error|You may only vote yourself when placing the hammer vote.`); | |
} | |
if (voter.hammerRestriction !== null) { | |
if (voter.hammerRestriction && !hammering) { | |
return this.sendUser(voter, `|error|You can only vote when placing the hammer vote.`); | |
} else if (!voter.hammerRestriction && hammering) { | |
return this.sendUser(voter, `|error|You cannot place the hammer vote.`); | |
} | |
} | |
if (voter.lastVote + 2e3 >= Date.now()) { | |
return this.sendUser( | |
voter, | |
`|error|You must wait another ${Chat.toDurationString(voter.lastVote + 2e3 - Date.now()) || "1 second"} before you can change your vote.` | |
); | |
} | |
const previousVote = voter.voting; | |
if (previousVote) | |
this.unvote(voter, true); | |
let vote = this.votes[targetId]; | |
if (!vote) { | |
this.votes[targetId] = { | |
count: 1, | |
trueCount: this.getVoteValue(voter), | |
lastVote: Date.now(), | |
dir: "up", | |
voters: [voter.id] | |
}; | |
vote = this.votes[targetId]; | |
} else { | |
vote.count++; | |
vote.trueCount += this.getVoteValue(voter); | |
vote.lastVote = Date.now(); | |
vote.dir = "up"; | |
vote.voters.push(voter.id); | |
} | |
voter.voting = targetId; | |
voter.lastVote = Date.now(); | |
const name = voter.voting === "novote" ? "No Vote" : target?.name; | |
if (previousVote) { | |
this.sendTimestamp(`${voter.name} has shifted their vote from ${previousVote === "novote" ? "No Vote" : this.getPlayer(previousVote)?.name} to ${name}`); | |
} else { | |
this.sendTimestamp( | |
name === "No Vote" ? `${voter.name} has abstained from voting.` : `${voter.name} has voted ${name}.` | |
); | |
} | |
this.hasPlurality = null; | |
if (this.getHammerValue(targetId) <= vote.trueCount) { | |
this.sendDeclare(`Hammer! ${targetId === "novote" ? "Nobody" : import_lib.Utils.escapeHTML(name)} was voted out!`); | |
this.sendRoom(`|raw|<div class="infobox">${this.voteBox()}</div>`); | |
if (targetId !== "novote") | |
this.eliminate(target, MafiaEliminateType.ELIMINATE); | |
this.night(true); | |
return; | |
} | |
this.updatePlayersVotes(); | |
} | |
unvote(voter, force = false) { | |
if (!force) { | |
if (this.phase !== "day") | |
return this.sendUser(voter, `|error|You can only vote during the day.`); | |
if (voter.isEliminated() && !voter.isSpirit()) { | |
return; | |
} | |
if (!voter.isEliminated() && this.forceVote) { | |
return this.sendUser(voter, `|error|You can only shift your vote, not unvote.`); | |
} | |
if (this.voteLock && voter.voting) { | |
return this.sendUser(voter, `|error|You cannot unvote because votes are locked.`); | |
} | |
if (voter.lastVote + 2e3 >= Date.now()) { | |
return this.sendUser( | |
voter, | |
`|error|You must wait another ${Chat.toDurationString(voter.lastVote + 2e3 - Date.now()) || "1 second"} before you can change your vote.` | |
); | |
} | |
} | |
if (!voter.voting) | |
return this.sendUser(voter, `|error|You are not voting for anyone.`); | |
const vote = this.votes[voter.voting]; | |
vote.count--; | |
vote.trueCount -= this.getVoteValue(voter); | |
if (vote.count <= 0) { | |
delete this.votes[voter.voting]; | |
} else { | |
vote.lastVote = Date.now(); | |
vote.dir = "down"; | |
vote.voters.splice(vote.voters.indexOf(voter.id), 1); | |
} | |
const target = this.getPlayer(voter.voting); | |
if (!target && voter.voting !== "novote") { | |
throw new Error(`Unable to find target when unvoting. Voter: ${voter.id}, Target: ${voter.voting}`); | |
} | |
if (!force) { | |
this.sendTimestamp( | |
voter.voting === "novote" ? `${voter.name} is no longer abstaining from voting.` : `${voter.name} has unvoted ${target?.name}.` | |
); | |
} | |
voter.voting = ""; | |
voter.lastVote = Date.now(); | |
this.hasPlurality = null; | |
this.updatePlayersVotes(); | |
} | |
/** | |
* Returns HTML code that contains information about the current vote. | |
*/ | |
voteBox() { | |
if (!this.started) | |
return `<strong>The game has not started yet.</strong>`; | |
let buf = `<strong>Votes (Hammer: ${this.hammerCount || "Disabled"})</strong><br />`; | |
const plur = this.getPlurality(); | |
const list = import_lib.Utils.sortBy(Object.entries(this.votes), ([key, vote]) => [ | |
key === plur, | |
-vote.count | |
]); | |
for (const [key, vote] of list) { | |
const player = this.getPlayer(toID(key)); | |
buf += `${vote.count}${plur === key ? "*" : ""} ${player?.safeName || "No Vote"} (${vote.voters.map((a) => this.getPlayer(a)?.safeName || a).join(", ")})<br />`; | |
} | |
return buf; | |
} | |
voteBoxFor(userid) { | |
let buf = ""; | |
buf += `<h3>Votes (Hammer: ${this.hammerCount || "Disabled"}) <button class="button" name="send" value="/msgroom ${this.roomid},/mafia refreshvotes"><i class="fa fa-refresh"></i> Refresh</button></h3>`; | |
const plur = this.getPlurality(); | |
const self = this.getPlayer(userid); | |
for (const key of this.getRemainingPlayers().map((p) => p.id).concat(this.enableNV ? ["novote"] : [])) { | |
const votes = this.votes[key]; | |
const player = this.getPlayer(key); | |
buf += `<p style="font-weight:bold">${votes?.count || 0}${plur === key ? "*" : ""} `; | |
if (player) { | |
buf += `${player.safeName} ${player.revealed ? `[${player.revealed}]` : ""} `; | |
} else { | |
buf += `No Vote `; | |
} | |
if (votes) { | |
buf += `(${votes.voters.map((v) => this.getPlayer(v)?.safeName || v).join(", ")}) `; | |
} | |
if (userid === this.hostid || this.cohostids.includes(userid)) { | |
if (votes && votes.count !== votes.trueCount) | |
buf += `(${votes.trueCount})`; | |
if (this.hammerModifiers[key]) | |
buf += `(${this.getHammerValue(key)} to hammer)`; | |
} else if (self && this.votingEnabled && (!this.voteLock || !self.voting) && (!self.isEliminated() || self.isSpirit())) { | |
let cmd = ""; | |
if (self.voting === key) { | |
cmd = "unvote"; | |
} else if (self.id !== key || this.selfEnabled && !self.isSpirit()) { | |
cmd = `vote ${key}`; | |
} | |
if (cmd) { | |
buf += `<button class="button" name="send" value="/msgroom ${this.roomid},/mafia ${cmd}">${cmd === "unvote" ? "Unvote" : "Vote"} ${player?.safeName || "No Vote"}</button>`; | |
} | |
} | |
buf += `</p>`; | |
} | |
return buf; | |
} | |
applyVoteModifier(requester, targetPlayer, mod) { | |
if (!targetPlayer) | |
return this.sendUser(requester, `|error|${targetPlayer} is not in the game of mafia.`); | |
const oldMod = this.voteModifiers[targetPlayer.id]; | |
if (mod === oldMod || (isNaN(mod) || mod === 1) && oldMod === void 0) { | |
if (isNaN(mod) || mod === 1) | |
return this.sendUser(requester, `|error|${targetPlayer} already has no vote modifier.`); | |
return this.sendUser(requester, `|error|${targetPlayer} already has a vote modifier of ${mod}`); | |
} | |
const newMod = isNaN(mod) ? 1 : mod; | |
if (targetPlayer.voting) { | |
this.votes[targetPlayer.voting].trueCount += oldMod - newMod; | |
if (this.getHammerValue(targetPlayer.voting) <= this.votes[targetPlayer.voting].trueCount) { | |
this.sendRoom(`${targetPlayer.voting} has been voted out due to a modifier change! They have not been eliminated.`); | |
this.night(true); | |
} | |
} | |
if (newMod === 1) { | |
delete this.voteModifiers[targetPlayer.id]; | |
return this.sendUser(requester, `${targetPlayer.name} has had their vote modifier removed.`); | |
} else { | |
this.voteModifiers[targetPlayer.id] = newMod; | |
return this.sendUser(requester, `${targetPlayer.name} has been given a vote modifier of ${newMod}`); | |
} | |
} | |
applyHammerModifier(user, target, mod) { | |
const oldMod = this.hammerModifiers[target.id]; | |
if (mod === oldMod || (isNaN(mod) || mod === 0) && oldMod === void 0) { | |
if (isNaN(mod) || mod === 0) | |
return this.sendUser(user, `|error|${target} already has no hammer modifier.`); | |
return this.sendUser(user, `|error|${target} already has a hammer modifier of ${mod}`); | |
} | |
const newMod = isNaN(mod) ? 0 : mod; | |
if (this.votes[target.id]) { | |
if (this.hammerCount + newMod <= this.votes[target.id].trueCount) { | |
this.sendRoom(`${target} has been voted due to a modifier change! They have not been eliminated.`); | |
this.night(true); | |
} | |
} | |
if (newMod === 0) { | |
delete this.hammerModifiers[target.id]; | |
return this.sendUser(user, `${target} has had their hammer modifier removed.`); | |
} else { | |
this.hammerModifiers[target.id] = newMod; | |
return this.sendUser(user, `${target} has been given a hammer modifier of ${newMod}`); | |
} | |
} | |
clearVoteModifiers(user) { | |
for (const player of this.players) { | |
if (this.voteModifiers[player.id]) | |
this.applyVoteModifier(user, player, 1); | |
} | |
} | |
clearHammerModifiers(user) { | |
for (const player of this.players) { | |
if (this.hammerModifiers[player.id]) | |
this.applyHammerModifier(user, player, 0); | |
} | |
} | |
getVoteValue(player) { | |
const mod = this.voteModifiers[player.id]; | |
return mod === void 0 ? 1 : mod; | |
} | |
getHammerValue(player) { | |
const mod = this.hammerModifiers[player]; | |
return mod === void 0 ? this.hammerCount : this.hammerCount + mod; | |
} | |
resetHammer() { | |
this.setHammer(Math.floor(this.players.length / 2) + 1); | |
} | |
setHammer(count) { | |
this.hammerCount = count; | |
if (isNaN(count)) { | |
this.sendDeclare(`Hammering has been disabled, and votes have been reset.`); | |
} else { | |
this.sendDeclare(`The hammer count has been set at ${this.hammerCount}, and votes have been reset.`); | |
} | |
this.clearVotes(); | |
} | |
shiftHammer(count) { | |
this.hammerCount = count; | |
if (isNaN(count)) { | |
this.sendDeclare(`Hammering has been disabled. Votes have not been reset.`); | |
} else { | |
this.sendDeclare(`The hammer count has been shifted to ${this.hammerCount}. Votes have not been reset.`); | |
} | |
const hammered = []; | |
for (const vote in this.votes) { | |
if (this.votes[vote].trueCount >= this.getHammerValue(vote)) { | |
hammered.push(vote === "novote" ? "Nobody" : vote); | |
} | |
} | |
if (hammered.length) { | |
this.sendDeclare(`${Chat.count(hammered, "players have")} been hammered: ${hammered.join(", ")}. They have not been removed from the game.`); | |
this.night(true); | |
} | |
} | |
getPlurality() { | |
if (this.hasPlurality) | |
return this.hasPlurality; | |
if (!Object.keys(this.votes).length) | |
return null; | |
let max = 0; | |
let topVotes = []; | |
for (const [key, vote] of Object.entries(this.votes)) { | |
if (vote.count > max) { | |
max = vote.count; | |
topVotes = [[key, vote]]; | |
} else if (vote.count === max) { | |
topVotes.push([key, vote]); | |
} | |
} | |
if (topVotes.length <= 1) { | |
[this.hasPlurality] = topVotes[0]; | |
return this.hasPlurality; | |
} | |
topVotes = import_lib.Utils.sortBy(topVotes, ([key, vote]) => [ | |
vote.dir === "down", | |
vote.dir === "up" ? vote.lastVote : -vote.lastVote | |
]); | |
[this.hasPlurality] = topVotes[0]; | |
return this.hasPlurality; | |
} | |
removePlayer(player) { | |
player.closeHtmlRoom(); | |
const result = super.removePlayer(player); | |
return result; | |
} | |
eliminate(toEliminate, ability) { | |
if (!this.started) { | |
this.sendDeclare(`${toEliminate.safeName} was kicked from the game!`); | |
if (this.hostRequestedSub.includes(toEliminate.id)) { | |
this.hostRequestedSub.splice(this.hostRequestedSub.indexOf(toEliminate.id), 1); | |
} | |
if (this.requestedSub.includes(toEliminate.id)) { | |
this.requestedSub.splice(this.requestedSub.indexOf(toEliminate.id), 1); | |
} | |
this.removePlayer(toEliminate); | |
return; | |
} | |
toEliminate.eliminationOrder = this.getEliminatedPlayers().map((p) => p.eliminationOrder).reduce((a, b) => Math.max(a, b), 0) + 1; | |
toEliminate.eliminated = ability; | |
if (toEliminate.voting) | |
this.unvote(toEliminate, true); | |
this.sendDeclare(`${toEliminate.safeName} ${ability}! ${!this.noReveal && ability === MafiaEliminateType.ELIMINATE ? `${toEliminate.safeName}'s role was ${toEliminate.getStylizedRole()}.` : ""}`); | |
if (toEliminate.role && !this.noReveal && ability === MafiaEliminateType.ELIMINATE) { | |
toEliminate.revealed = toEliminate.getStylizedRole(); | |
} | |
const targetRole = toEliminate.role; | |
if (targetRole) { | |
for (const [roleIndex, role] of this.roles.entries()) { | |
if (role.id === targetRole.id) { | |
this.roles.splice(roleIndex, 1); | |
break; | |
} | |
} | |
} | |
this.clearVotes(toEliminate.id); | |
let subIndex = this.requestedSub.indexOf(toEliminate.id); | |
if (subIndex !== -1) | |
this.requestedSub.splice(subIndex, 1); | |
subIndex = this.hostRequestedSub.indexOf(toEliminate.id); | |
if (subIndex !== -1) | |
this.hostRequestedSub.splice(subIndex, 1); | |
this.updateRoleString(); | |
if (ability === MafiaEliminateType.KICK) { | |
toEliminate.closeHtmlRoom(); | |
this.removePlayer(toEliminate); | |
} | |
this.updatePlayers(); | |
} | |
revealRole(user, toReveal, revealAs) { | |
if (!this.started) { | |
return this.sendUser(user, `|error|You may only reveal roles once the game has started.`); | |
} | |
if (!toReveal.role) { | |
return this.sendUser(user, `|error|The user ${toReveal.id} is not assigned a role.`); | |
} | |
toReveal.revealed = revealAs; | |
this.sendDeclare(`${toReveal.safeName}'s role ${toReveal.isEliminated() ? `was` : `is`} ${revealAs}.`); | |
this.updatePlayers(); | |
} | |
revive(user, toRevive) { | |
if (this.phase === "IDEApicking") { | |
return this.sendUser(user, `|error|You cannot add or remove players while IDEA roles are being picked.`); | |
} | |
if (!toRevive.isEliminated()) { | |
this.sendUser(user, `|error|The user ${toRevive} is already a living player.`); | |
return; | |
} | |
toRevive.eliminated = null; | |
this.sendDeclare(`${toRevive.safeName} was revived!`); | |
const targetRole = toRevive.role; | |
if (targetRole) { | |
this.roles.push(targetRole); | |
} else { | |
toRevive.role = { | |
name: `Unknown`, | |
safeName: `Unknown`, | |
id: `unknown`, | |
alignment: "solo", | |
image: "", | |
memo: [ | |
`You were revived, but had no role. Please let a Mafia Room Owner know this happened. To learn about your role, PM the host (${this.host}).` | |
] | |
}; | |
this.roles.push(toRevive.role); | |
} | |
import_lib.Utils.sortBy(this.roles, (r) => [r.alignment, r.name]); | |
this.updateRoleString(); | |
this.updatePlayers(); | |
return true; | |
} | |
getRemainingPlayers() { | |
return this.players.filter((player) => !player.isEliminated()); | |
} | |
getEliminatedPlayers() { | |
return this.players.filter((player) => player.isEliminated()).sort((a, b) => a.eliminationOrder - b.eliminationOrder); | |
} | |
setDeadline(minutes, silent = false) { | |
if (isNaN(minutes)) | |
return; | |
if (!minutes) { | |
if (!this.timer) | |
return; | |
clearTimeout(this.timer); | |
this.timer = null; | |
this.dlAt = 0; | |
if (!silent) | |
this.sendTimestamp(`**The deadline has been cleared.**`); | |
return; | |
} | |
if (minutes < 1 || minutes > 20) | |
return; | |
if (this.timer) | |
clearTimeout(this.timer); | |
this.dlAt = Date.now() + minutes * 6e4; | |
if (minutes > 3) { | |
this.timer = setTimeout(() => { | |
this.sendTimestamp(`**3 minutes left!**`); | |
this.timer = setTimeout(() => { | |
this.sendTimestamp(`**1 minute left!**`); | |
this.timer = setTimeout(() => { | |
this.sendTimestamp(`**Time is up!**`); | |
this.night(); | |
}, 6e4); | |
}, 2 * 6e4); | |
}, (minutes - 3) * 6e4); | |
} else if (minutes > 1) { | |
this.timer = setTimeout(() => { | |
this.sendTimestamp(`**1 minute left!**`); | |
this.timer = setTimeout(() => { | |
this.sendTimestamp(`**Time is up!**`); | |
if (this.phase === "day") | |
this.night(); | |
}, 6e4); | |
}, (minutes - 1) * 6e4); | |
} else { | |
this.timer = setTimeout(() => { | |
this.sendTimestamp(`**Time is up!**`); | |
if (this.phase === "day") | |
this.night(); | |
}, minutes * 6e4); | |
} | |
this.sendTimestamp(`**The deadline has been set for ${minutes} minute${minutes === 1 ? "" : "s"}.**`); | |
} | |
sub(player, newUser) { | |
const oldPlayerId = player.id; | |
const oldSafeName = player.safeName; | |
this.setPlayerUser(player, newUser); | |
player.updateSafeName(); | |
if (player.voting) { | |
const vote = this.votes[player.voting]; | |
vote.voters.splice(vote.voters.indexOf(oldPlayerId), 1); | |
vote.voters.push(player.id); | |
} | |
if (this.votes[oldPlayerId]) { | |
this.votes[player.id] = this.votes[oldPlayerId]; | |
delete this.votes[oldPlayerId]; | |
for (const p of this.players) { | |
if (p.voting === oldPlayerId) { | |
p.voting = player.id; | |
} | |
} | |
} | |
if (this.hasPlurality === oldPlayerId) | |
this.hasPlurality = player.id; | |
if (newUser?.connected) { | |
for (const conn of newUser.connections) { | |
void Chat.resolvePage(`view-mafia-${this.room.roomid}`, newUser, conn); | |
} | |
newUser.send(`>${this.room.roomid} | |
|notify|You have been substituted in the mafia game for ${oldSafeName}.`); | |
} | |
if (this.started) | |
this.played.push(player.id); | |
this.sendDeclare(`${oldSafeName} has been subbed out. ${player.safeName} has joined the game.`); | |
this.updatePlayers(); | |
if (this.room.roomid === "mafia" && this.started) { | |
const month = new Date().toLocaleString("en-us", { month: "numeric", year: "numeric" }); | |
if (!logs.leavers[month]) | |
logs.leavers[month] = {}; | |
if (!logs.leavers[month][player.id]) | |
logs.leavers[month][player.id] = 0; | |
logs.leavers[month][player.id]++; | |
writeFile(LOGS_FILE, logs); | |
} | |
} | |
nextSub(userid = null) { | |
if (!this.subs.length || !this.hostRequestedSub.length && (!this.requestedSub.length || !this.autoSub) && !userid) { | |
return; | |
} | |
const nextSub = this.subs.shift(); | |
if (!nextSub) | |
return; | |
const sub = Users.get(nextSub, true); | |
if (!sub?.connected || !sub.named || !this.room.users[sub.id]) | |
return; | |
const toSubOut = this.getPlayer(userid || this.hostRequestedSub.shift() || this.requestedSub.shift() || ""); | |
if (!toSubOut) { | |
this.subs.unshift(nextSub); | |
return; | |
} | |
if (this.hostRequestedSub.includes(toSubOut.id)) { | |
this.hostRequestedSub.splice(this.hostRequestedSub.indexOf(toSubOut.id), 1); | |
} | |
if (this.requestedSub.includes(toSubOut.id)) { | |
this.requestedSub.splice(this.requestedSub.indexOf(toSubOut.id), 1); | |
} | |
this.sub(toSubOut, sub); | |
} | |
customIdeaInit(user, choices, picks, rolesString) { | |
this.originalRoles = []; | |
this.originalRoleString = ""; | |
this.roles = []; | |
this.roleString = ""; | |
const roles = import_lib.Utils.stripHTML(rolesString); | |
let roleList = roles.split("\n"); | |
if (roleList.length === 1) { | |
roleList = roles.split(",").map((r) => r.trim()); | |
} | |
this.IDEA.data = { | |
name: `${this.host}'s custom IDEA`, | |
// already escaped | |
untrusted: true, | |
roles: roleList, | |
picks, | |
choices | |
}; | |
return this.ideaDistributeRoles(user); | |
} | |
ideaInit(user, moduleID) { | |
this.originalRoles = []; | |
this.originalRoleString = ""; | |
this.roles = []; | |
this.roleString = ""; | |
if (moduleID in MafiaData.aliases) | |
moduleID = MafiaData.aliases[moduleID]; | |
this.IDEA.data = MafiaData.IDEAs[moduleID]; | |
if (!this.IDEA.data) | |
return this.sendUser(user, `|error|${moduleID} is not a valid IDEA.`); | |
return this.ideaDistributeRoles(user); | |
} | |
ideaDistributeRoles(user) { | |
if (!this.IDEA.data) | |
return this.sendUser(user, `|error|No IDEA module loaded`); | |
if (this.phase !== "locked" && this.phase !== "IDEAlocked") { | |
return this.sendUser(user, `|error|The game must be in a locked state to distribute IDEA roles.`); | |
} | |
const neededRoles = this.IDEA.data.choices * this.playerCount; | |
if (neededRoles > this.IDEA.data.roles.length) { | |
return this.sendUser(user, `|error|Not enough roles in the IDEA module.`); | |
} | |
const roles = []; | |
const selectedIndexes = []; | |
for (let i = 0; i < neededRoles; i++) { | |
let randomIndex; | |
do { | |
randomIndex = Math.floor(Math.random() * this.IDEA.data.roles.length); | |
} while (selectedIndexes.includes(randomIndex)); | |
roles.push(this.IDEA.data.roles[randomIndex]); | |
selectedIndexes.push(randomIndex); | |
} | |
import_lib.Utils.shuffle(roles); | |
this.IDEA.waitingPick = []; | |
for (const player of this.players) { | |
player.role = null; | |
player.IDEA = { | |
choices: roles.splice(0, this.IDEA.data.choices), | |
originalChoices: [], | |
// MAKE SURE TO SET THIS | |
picks: {} | |
}; | |
player.IDEA.originalChoices = player.IDEA.choices.slice(); | |
for (const pick of this.IDEA.data.picks) { | |
player.IDEA.picks[pick] = null; | |
this.IDEA.waitingPick.push(player.id); | |
} | |
const u = Users.get(player.id); | |
if (u?.connected) | |
u.send(`>${this.room.roomid} | |
|notify|Pick your role in the IDEA module.`); | |
} | |
this.phase = "IDEApicking"; | |
this.updatePlayers(); | |
this.sendDeclare(`${this.IDEA.data.name} roles have been distributed. You will have ${IDEA_TIMER / 1e3} seconds to make your picks.`); | |
this.IDEA.timer = setTimeout(() => { | |
this.ideaFinalizePicks(); | |
}, IDEA_TIMER); | |
return ``; | |
} | |
ideaPick(user, selection) { | |
let buf = ""; | |
if (this.phase !== "IDEApicking") | |
return "The game is not in the IDEA picking phase."; | |
if (!this.IDEA?.data) { | |
return this.sendRoom(`Trying to pick an IDEA role with no module running, target: ${JSON.stringify(selection)}. Please report this to a mod.`); | |
} | |
const player = this.getPlayer(user.id); | |
if (!player?.IDEA) { | |
return this.sendRoom(`Trying to pick an IDEA role with no player IDEA object, user: ${user.id}. Please report this to a mod.`); | |
} | |
selection = selection.map(toID); | |
if (selection.length === 1 && this.IDEA.data.picks.length === 1) | |
selection = [this.IDEA.data.picks[0], selection[0]]; | |
if (selection.length !== 2) | |
return this.sendUser(user, `|error|Invalid selection.`); | |
if (selection[1]) { | |
const roleIndex = player.IDEA.choices.map(toID).indexOf(selection[1]); | |
if (roleIndex === -1) { | |
return this.sendUser(user, `|error|${selection[1]} is not an available role, perhaps it is already selected?`); | |
} | |
selection[1] = player.IDEA.choices.splice(roleIndex, 1)[0]; | |
} else { | |
selection[1] = ""; | |
} | |
const selected = player.IDEA.picks[selection[0]]; | |
if (selected) { | |
buf += `You have deselected ${selected}. `; | |
player.IDEA.choices.push(selected); | |
} | |
if (player.IDEA.picks[selection[0]] && !selection[1]) { | |
this.IDEA.waitingPick.push(player.id); | |
} else if (!player.IDEA.picks[selection[0]] && selection[1]) { | |
this.IDEA.waitingPick.splice(this.IDEA.waitingPick.indexOf(player.id), 1); | |
} | |
player.IDEA.picks[selection[0]] = selection[1]; | |
if (selection[1]) | |
buf += `You have selected ${selection[0]}: ${selection[1]}.`; | |
player.updateHtmlRoom(); | |
if (!this.IDEA.waitingPick.length) { | |
if (this.IDEA.timer) | |
clearTimeout(this.IDEA.timer); | |
this.ideaFinalizePicks(); | |
return; | |
} | |
return this.sendUser(user, buf); | |
} | |
ideaFinalizePicks() { | |
if (!this.IDEA?.data) { | |
return this.sendRoom(`Tried to finalize IDEA picks with no IDEA module running, please report this to a mod.`); | |
} | |
const randed = []; | |
for (const player of this.players) { | |
if (!player.IDEA) { | |
return this.sendRoom(`Trying to pick an IDEA role with no player IDEA object, user: ${player.id}. Please report this to a mod.`); | |
} | |
let randPicked = false; | |
const role = []; | |
for (const choice of this.IDEA.data.picks) { | |
if (!player.IDEA.picks[choice]) { | |
randPicked = true; | |
const randomChoice = player.IDEA.choices.shift(); | |
if (randomChoice) { | |
player.IDEA.picks[choice] = randomChoice; | |
} else { | |
throw new Error(`No roles left to randomly assign from IDEA module choices.`); | |
} | |
this.sendUser(player.id, `You were randomly assigned ${choice}: ${randomChoice}`); | |
} | |
role.push(`${choice}: ${player.IDEA.picks[choice]}`); | |
} | |
if (randPicked) | |
randed.push(player.id); | |
let roleName = ""; | |
if (this.IDEA.data.picks.length === 1) { | |
const pick = player.IDEA.picks[this.IDEA.data.picks[0]]; | |
if (!pick) | |
throw new Error("Pick not found when parsing role selected in IDEA module."); | |
const parsedRole = Mafia.parseRole(pick); | |
if (parsedRole.problems.length) { | |
this.sendRoom(`Problems found when parsing IDEA role ${player.IDEA.picks[this.IDEA.data.picks[0]]}. Please report this to a mod.`); | |
} | |
player.role = parsedRole.role; | |
} else { | |
roleName = role.join("; "); | |
player.role = { | |
name: roleName, | |
safeName: import_lib.Utils.escapeHTML(roleName), | |
id: toID(roleName), | |
alignment: "solo", | |
memo: [`(Your role was set from an IDEA.)`], | |
image: "" | |
}; | |
if (!this.IDEA.data.untrusted) { | |
for (const pick of role) { | |
if (pick.substr(0, 10) === "alignment:") { | |
const parsedRole = Mafia.parseRole(pick.substr(9)); | |
if (parsedRole.problems.length) { | |
this.sendRoom(`Problems found when parsing IDEA role ${pick}. Please report this to a mod.`); | |
} | |
player.role.alignment = parsedRole.role.alignment; | |
} | |
} | |
} | |
} | |
if (player.IDEA.choices.includes("Innocent Discard")) | |
player.role.alignment = "town"; | |
} | |
this.IDEA.discardsHTML = `<b>Discards:</b><br />`; | |
for (const player of this.players.sort((a, b) => a.id.localeCompare(b.id))) { | |
const IDEA = player.IDEA; | |
if (!IDEA) { | |
return this.sendRoom(`No IDEA data for player ${player} when finalising IDEAs. Please report this to a mod.`); | |
} | |
this.IDEA.discardsHTML += `<b>${player.safeName}:</b> ${IDEA.choices.join(", ")}<br />`; | |
} | |
this.phase = "IDEAlocked"; | |
if (randed.length) { | |
this.sendDeclare(`${randed.join(", ")} did not pick a role in time and were randomly assigned one.`); | |
} | |
this.sendDeclare(`IDEA picks are locked!`); | |
this.sendRoom(`To start, use /mafia start, or to reroll use /mafia ideareroll`); | |
this.updatePlayers(); | |
} | |
sendPlayerList() { | |
this.room.add(`|c:|${Math.floor(Date.now() / 1e3)}|~|**Players (${this.getRemainingPlayers().length})**: ${this.getRemainingPlayers().map((p) => p.name).sort().join(", ")}`).update(); | |
} | |
updatePlayers() { | |
for (const p of this.players) { | |
p.tryUpdateHtmlRoom(); | |
} | |
this.updateHost(); | |
} | |
updatePlayersVotes() { | |
for (const p of this.players) { | |
p.tryUpdateHtmlRoom(); | |
} | |
} | |
updateHost(...hosts) { | |
if (!hosts.length) | |
hosts = [...this.cohostids, this.hostid]; | |
for (const hostid of hosts) { | |
const host = Users.get(hostid); | |
if (!host?.connected) | |
return; | |
for (const conn of host.connections) { | |
void Chat.resolvePage(`view-mafia-${this.room.roomid}`, host, conn); | |
} | |
} | |
} | |
updateRoleString() { | |
this.roleString = this.roles.map( | |
(r) => `<span style="font-weight:bold;color:${MafiaData.alignments[r.alignment].color || "#FFF"}">${r.safeName}</span>` | |
).join(", "); | |
} | |
sendRoom(message) { | |
this.room.add(message).update(); | |
} | |
sendHTML(message) { | |
this.room.add(`|uhtml|mafia|${message}`).update(); | |
} | |
sendDeclare(message) { | |
this.room.add(`|raw|<div class="broadcast-blue">${message}</div>`).update(); | |
} | |
sendStrong(message) { | |
this.room.add(`|raw|<strong>${message}</strong>`).update(); | |
} | |
sendTimestamp(message) { | |
this.room.add(`|c:|${Math.floor(Date.now() / 1e3)}|~|${message}`).update(); | |
} | |
logAction(user, message) { | |
if (user.id === this.hostid || this.cohostids.includes(user.id)) | |
return; | |
this.room.sendModsByUser(user, `(${user.name}: ${message})`); | |
} | |
secretLogAction(user, message) { | |
if (user.id === this.hostid || this.cohostids.includes(user.id)) | |
return; | |
this.room.roomlog(`(${user.name}: ${message})`); | |
} | |
roomWindow() { | |
if (this.ended) | |
return `<div class="infobox">The game of ${this.title} has ended.</div>`; | |
let output = `<div class="broadcast-blue">`; | |
if (this.phase === "signups") { | |
output += `<h1 style="text-align: center">A game of ${this.title} was created</h2><p style="text-align: center"><button class="button" name="send" value="/mafia join">Join the game</button> <button class="button" name="send" value="/join view-mafia-${this.room.roomid}">Spectate the game</button> <button class="button" name="send" value="/help mafia">Mafia Commands</button></p>`; | |
} else { | |
output += `<p style="font-weight: bold">A game of ${this.title} is in progress.</p><p><button class="button" name="send" value="/msgroom ${this.room.roomid},/mafia sub in">Become a substitute</button> <button class="button" name="send" value="/join view-mafia-${this.room.roomid}">Spectate the game</button> <button class="button" name="send" value="/help mafia">Mafia Commands</button></p>`; | |
} | |
output += `</div>`; | |
return output; | |
} | |
canJoin(user, self = false, force = false) { | |
if (!user?.connected) | |
throw new Chat.ErrorMessage(`User not found.`); | |
const targetString = self ? `You are` : `${user.id} is`; | |
if (!this.room.users[user.id]) | |
throw new Chat.ErrorMessage(`${targetString} not in the room.`); | |
for (const id of [user.id, ...user.previousIDs]) { | |
if (this.getPlayer(id)) | |
throw new Chat.ErrorMessage(`${targetString} already in the game.`); | |
if (!force && this.played.includes(id)) { | |
throw new Chat.ErrorMessage(`${targetString} a previous player and cannot rejoin.`); | |
} | |
if (Mafia.isGameBanned(this.room, user)) { | |
throw new Chat.ErrorMessage(`${targetString} banned from joining mafia games.`); | |
} | |
if (this.hostid === id) | |
throw new Chat.ErrorMessage(`${targetString} the host.`); | |
if (this.cohostids.includes(id)) | |
throw new Chat.ErrorMessage(`${targetString} a cohost.`); | |
} | |
for (const alt of user.getAltUsers(true)) { | |
if (!force && (this.getPlayer(alt.id) || this.played.includes(alt.id))) { | |
throw new Chat.ErrorMessage(`${self ? `You already have` : `${user.id} already has`} an alt in the game.`); | |
} | |
if (this.hostid === alt.id || this.cohostids.includes(alt.id)) { | |
throw new Chat.ErrorMessage(`${self ? `You have` : `${user.id} has`} an alt as a game host.`); | |
} | |
} | |
} | |
sendUser(user, message) { | |
let userObject; | |
if (user instanceof MafiaPlayer) { | |
userObject = user.getUser(); | |
} else if (typeof user === "string") { | |
userObject = Users.get(user); | |
} else { | |
userObject = user; | |
} | |
if (!userObject?.connected) | |
return; | |
userObject.sendTo(this.room, message); | |
} | |
setSelfVote(user, setting) { | |
const from = this.selfEnabled; | |
if (from === setting) { | |
return user.sendTo( | |
this.room, | |
`|error|Selfvoting is already ${setting ? `set to Self${setting === "hammer" ? "hammering" : "voting"}` : "disabled"}.` | |
); | |
} | |
if (from) { | |
this.sendDeclare(`Self${from === "hammer" ? "hammering" : "voting"} has been ${setting ? `changed to Self${setting === "hammer" ? "hammering" : "voting"}` : "disabled"}.`); | |
} else { | |
this.sendDeclare(`Self${setting === "hammer" ? "hammering" : "voting"} has been ${setting ? "enabled" : "disabled"}.`); | |
} | |
this.selfEnabled = setting; | |
if (!setting) { | |
for (const player of this.players) { | |
if (player.voting === player.id) | |
this.unvote(player, true); | |
} | |
} | |
this.updatePlayers(); | |
} | |
setNoVote(user, setting) { | |
if (this.enableNV === setting) { | |
return this.sendUser(user, `|error|No Vote is already ${setting ? "enabled" : "disabled"}.`); | |
} | |
this.enableNV = setting; | |
this.sendDeclare(`No Vote has been ${setting ? "enabled" : "disabled"}.`); | |
if (!setting) | |
this.clearVotes("novote"); | |
this.updatePlayers(); | |
} | |
setVotelock(user, setting) { | |
if (!this.started) | |
return this.sendUser(user, `The game has not started yet.`); | |
if (this.voteLock === setting) { | |
return this.sendUser(user, `|error|Votes are already ${setting ? "set to lock" : "set to not lock"}.`); | |
} | |
this.voteLock = setting; | |
this.clearVotes(); | |
this.sendDeclare(`Votes are cleared and ${setting ? "set to lock" : "set to not lock"}.`); | |
this.updatePlayers(); | |
} | |
setVoting(user, setting) { | |
if (!this.started) | |
return this.sendUser(user, `The game has not started yet.`); | |
if (this.votingEnabled === setting) { | |
return this.sendUser(user, `|error|Voting is already ${setting ? "allowed" : "disallowed"}.`); | |
} | |
this.votingEnabled = setting; | |
this.clearVotes(); | |
this.sendDeclare(`Voting is now ${setting ? "allowed" : "disallowed"}.`); | |
this.updatePlayers(); | |
} | |
clearVotes(target = "") { | |
if (target) | |
delete this.votes[target]; | |
if (!target) | |
this.votes = /* @__PURE__ */ Object.create(null); | |
for (const player of this.players) { | |
if (player.isEliminated() && !player.isSpirit()) | |
continue; | |
if (this.forceVote) { | |
if (!target || player.voting === target) { | |
player.voting = player.id; | |
this.votes[player.id] = { | |
count: 1, | |
trueCount: this.getVoteValue(player), | |
lastVote: Date.now(), | |
dir: "up", | |
voters: [player.id] | |
}; | |
} | |
} else { | |
if (!target || player.voting === target) | |
player.voting = ""; | |
} | |
} | |
this.hasPlurality = null; | |
} | |
/** | |
* Only intended to be used during pre-game setup. | |
*/ | |
clearEliminations() { | |
for (const player of this.players) { | |
player.eliminated = null; | |
} | |
} | |
onChatMessage(message, user) { | |
const subIndex = this.hostRequestedSub.indexOf(user.id); | |
if (subIndex !== -1) { | |
this.hostRequestedSub.splice(subIndex, 1); | |
for (const hostid of [...this.cohostids, this.hostid]) { | |
this.sendUser(hostid, `${user.id} has spoken and been removed from the host sublist.`); | |
} | |
} | |
if (this.hostid === user.id || this.cohostids.includes(user.id) || !this.started) { | |
return; | |
} | |
const player = this.getPlayer(user.id); | |
const eliminated = player?.isEliminated(); | |
const staff = user.can("mute", null, this.room); | |
if (!player) { | |
if (staff) { | |
return; | |
} else { | |
return `You cannot talk while a game of ${this.title} is going on.`; | |
} | |
} | |
if (player.silenced) { | |
return `You are silenced and cannot speak.${staff ? " You can remove this with /mafia unsilence." : ""}`; | |
} | |
if (eliminated) { | |
if (!player.isTreestump()) { | |
return `You are dead.${staff ? " You can treestump yourself with /mafia treestump." : ""}`; | |
} | |
} | |
if (this.phase === "night") { | |
if (!player.nighttalk) { | |
return `You cannot talk at night.${staff ? " You can bypass this using /mafia nighttalk." : ""}`; | |
} | |
} | |
} | |
onConnect(user) { | |
this.sendUser(user, `|uhtml|mafia|${this.roomWindow()}`); | |
} | |
onJoin(user) { | |
const player = this.getPlayer(user.id); | |
if (player) { | |
return player.updateHtmlRoom(); | |
} | |
if (user.id === this.hostid || this.cohostids.includes(user.id)) | |
return this.updateHost(user.id); | |
} | |
removeBannedUser(user) { | |
if (!this.getPlayer(user.id)) | |
return; | |
this.requestedSub.unshift(user.id); | |
this.nextSub(); | |
} | |
forfeit(user) { | |
const player = this.getPlayer(user.id); | |
if (!player || player.isEliminated()) | |
return; | |
this.requestedSub.push(user.id); | |
this.nextSub(); | |
} | |
end() { | |
this.setEnded(); | |
this.sendHTML(this.roomWindow()); | |
this.updatePlayers(); | |
if (this.room.roomid === "mafia" && this.started) { | |
const played = this.players.map((p) => p.id); | |
const month = new Date().toLocaleString("en-us", { month: "numeric", year: "numeric" }); | |
if (!logs.plays[month]) | |
logs.plays[month] = {}; | |
for (const player of played) { | |
if (!logs.plays[month][player]) | |
logs.plays[month][player] = 0; | |
logs.plays[month][player]++; | |
} | |
if (!logs.hosts[month]) | |
logs.hosts[month] = {}; | |
for (const hostid of [...this.cohostids, this.hostid]) { | |
if (!logs.hosts[month][hostid]) | |
logs.hosts[month][hostid] = 0; | |
logs.hosts[month][hostid]++; | |
} | |
writeFile(LOGS_FILE, logs); | |
} | |
if (this.timer) { | |
clearTimeout(this.timer); | |
this.timer = null; | |
} | |
this.destroy(); | |
} | |
destroy() { | |
if (this.timer) | |
clearTimeout(this.timer); | |
if (this.IDEA.timer) | |
clearTimeout(this.IDEA.timer); | |
super.destroy(); | |
} | |
} | |
const pages = { | |
mafia(query, user) { | |
if (!user.named) | |
return Rooms.RETRY_AFTER_LOGIN; | |
if (!query.length) | |
return this.close(); | |
let roomid = query.shift(); | |
if (roomid === "groupchat") | |
roomid += `-${query.shift()}-${query.shift()}`; | |
const room = Rooms.get(roomid); | |
const game = room?.getGame(Mafia); | |
if (!room?.users[user.id] || !game || game.ended) { | |
return this.close(); | |
} | |
const isPlayer = game.getPlayer(user.id); | |
const isHost = user.id === game.hostid || game.cohostids.includes(user.id); | |
const players = game.getRemainingPlayers(); | |
this.title = game.title; | |
let buf = `<div class="pad broadcast-blue">`; | |
buf += `<button class="button" name="send" value="/join view-mafia-${room.roomid}" style="float:left"><i class="fa fa-refresh"></i> Refresh</button>`; | |
buf += `<br/><br/><h1 style="text-align:center;">${game.title}</h1><h3>Host: ${game.host}</h3>${game.cohostids[0] ? `<h3>Cohosts: ${game.cohosts.sort().join(", ")}</h3>` : ""}`; | |
buf += `<p style="font-weight:bold;">Players (${players.length}): ${players.map((p) => p.safeName).sort().join(", ")}</p>`; | |
const eliminatedPlayers = game.getEliminatedPlayers(); | |
if (game.started && eliminatedPlayers.length > 0) { | |
buf += `<p><details><summary class="button" style="text-align:left; display:inline-block">Dead Players</summary>`; | |
for (const eliminated of eliminatedPlayers) { | |
buf += `<p style="font-weight:bold;">${eliminated.safeName} ${eliminated.revealed ? "(" + eliminated.revealed + ")" : ""}`; | |
if (eliminated.isTreestump()) | |
buf += ` (is a Treestump)`; | |
if (eliminated.isSpirit()) | |
buf += ` (is a Restless Spirit)`; | |
if (isHost && !eliminated.revealed) { | |
buf += `<button class="button" name="send" value="/msgroom ${room.roomid},/mafia revealrole ${eliminated.id}";">Reveal</button>`; | |
} | |
buf += `</p>`; | |
} | |
buf += `</details></p>`; | |
} | |
buf += `<hr/>`; | |
if (isPlayer && game.phase === "IDEApicking") { | |
buf += `<p><b>IDEA information:</b><br />`; | |
const IDEA = isPlayer.IDEA; | |
if (!IDEA) { | |
return game.sendRoom(`IDEA picking phase but no IDEA object for user: ${user.id}. Please report this to a mod.`); | |
} | |
for (const key in IDEA.picks) { | |
const pick = IDEA.picks[key]; | |
buf += `<b>${key}:</b> `; | |
if (!pick) { | |
buf += `<button class="button disabled" style="font-weight:bold; color:#575757; font-weight:bold; background-color:#d3d3d3;">clear</button>`; | |
} else { | |
buf += `<button class="button" name="send" value="/msgroom ${room.roomid},/mafia ideapick ${key},">clear</button>`; | |
} | |
const selectedIndex = pick ? IDEA.originalChoices.indexOf(pick) : -1; | |
for (let i = 0; i < IDEA.originalChoices.length; i++) { | |
const choice = IDEA.originalChoices[i]; | |
if (i === selectedIndex) { | |
buf += `<button class="button disabled" style="font-weight:bold; color:#575757; font-weight:bold; background-color:#d3d3d3;">${choice}</button>`; | |
} else { | |
buf += `<button class="button" name="send" value="/msgroom ${room.roomid},/mafia ideapick ${key}, ${toID(choice)}">${choice}</button>`; | |
} | |
} | |
buf += `<br />`; | |
} | |
buf += `</p>`; | |
buf += `<p><details><summary class="button" style="display:inline-block"><b>Role details:</b></summary><p>`; | |
for (const role of IDEA.originalChoices) { | |
const roleObject = Mafia.parseRole(role); | |
buf += `<details><summary>${role}</summary>`; | |
buf += `<table><tr><td style="text-align:center;"><td style="text-align:left;width:100%"><ul>${roleObject.role.memo.map((m) => `<li>${m}</li>`).join("")}</ul></td></tr></table>`; | |
buf += `</details>`; | |
} | |
buf += `</p></details></p>`; | |
} | |
if (game.IDEA.data) { | |
buf += `<p><details><summary class="button" style="text-align:left; display:inline-block">${game.IDEA.data.name} information</summary>`; | |
if (game.IDEA.discardsHTML && (!game.IDEA.discardsHidden || isHost)) { | |
buf += `<details><summary class="button" style="text-align:left; display:inline-block">Discards:</summary><p>${game.IDEA.discardsHTML}</p></details>`; | |
} | |
buf += `<details><summary class="button" style="text-align:left; display:inline-block">Role list</summary><p>${game.IDEA.data.roles.join("<br />")}</p></details>`; | |
buf += `</details></p>`; | |
} else { | |
if (!game.closedSetup || isHost) { | |
if (game.theme) { | |
buf += `<p><span style="font-weight:bold;">Theme</span>: ${game.theme.name}</p>`; | |
buf += `<p>${game.theme.desc}</p>`; | |
} | |
if (game.noReveal) { | |
buf += `<p><span style="font-weight:bold;">Original Rolelist${game.closedSetup ? " (CS)" : ""}</span>: ${game.originalRoleString}</p>`; | |
} else { | |
buf += `<p><span style="font-weight:bold;">Rolelist${game.closedSetup ? " (CS)" : ""}</span>: ${game.roleString}</p>`; | |
} | |
} | |
} | |
if (isPlayer) { | |
const role = isPlayer.role; | |
let previousActionsPL = `<br/>`; | |
if (role) { | |
buf += `<h3>${isPlayer.safeName}, you are a ${isPlayer.getStylizedRole()}</h3>`; | |
if (!["town", "solo"].includes(role.alignment)) { | |
buf += `<p><span style="font-weight:bold">Partners</span>: ${game.getPartners(role.alignment, isPlayer)}</p>`; | |
} | |
buf += `<p><details><summary class="button" style="text-align:left; display:inline-block">Role Details</summary>`; | |
buf += `<table><tr><td style="text-align:center;"><img width="75" height="75" src="//${Config.routes.client}/fx/mafia-${role.image || "villager"}.png"></td><td style="text-align:left;width:100%"><ul>${role.memo.map((m) => `<li>${m}</li>`).join("")}</ul></td></tr></table>`; | |
buf += `</details></p>`; | |
for (let i = 0; i < game.dayNum; i++) { | |
previousActionsPL += `<b>Night ${i}</b><br/>`; | |
previousActionsPL += `${isPlayer.actionArr?.[i] ? `${isPlayer.actionArr[i]}` : ""}<br/>`; | |
} | |
if (game.dayNum > 0) { | |
buf += `<p><details><summary class="button" style="text-align:left; display:inline-block">Previous Actions</summary>${previousActionsPL}</span></details></p>`; | |
} | |
} | |
} | |
if (game.phase === "day") { | |
buf += `<span id="mafia-votes">`; | |
buf += game.voteBoxFor(user.id); | |
buf += `</span>`; | |
} else if (game.phase === "night" && isPlayer && !isPlayer.isEliminated()) { | |
if (!game.takeIdles) { | |
buf += `<p style="font-weight:bold;">PM the host (${game.host}) the action you want to use tonight, and who you want to use it on. Or PM the host "idle".</p>`; | |
} else { | |
buf += `<b>Night Actions:</b>`; | |
if (isPlayer.action === null) { | |
buf += `<button class="button disabled" style="font-weight:bold; color:#575757; font-weight:bold; background-color:#d3d3d3;">Clear</button>`; | |
buf += `<button class="button" name="send" value="/msgroom ${room.roomid},/mafia idle">Idle</button>`; | |
buf += `<button class="button" name="send" value="/msgroom ${room.roomid},/mafia action">Action</button><br/>`; | |
} else { | |
buf += `<button class="button" name="send" value="/msgroom ${room.roomid},/mafia noresponse">Clear</button>`; | |
if (isPlayer.action) { | |
buf += `<button class="button" name="send" value="/msgroom ${room.roomid},/mafia idle">Idle</button>`; | |
buf += `<button class="button disabled" style="font-weight:bold; color:#575757; font-weight:bold; background-color:#d3d3d3;">Action</button>`; | |
if (isPlayer.action === true) { | |
buf += `<form data-submitsend="/msgroom ${game.room.roomid},/mafia action {submission}"><label><b>Submission:</b> <input name="submission" class="textbox" placeholder="Action Details" /></label> <button class="button">Confirm</button></form>`; | |
} else { | |
buf += `<form data-submitsend="/msgroom ${game.room.roomid},/mafia action"><label><b>Submission:</b> ${isPlayer.action} <button class="button">Clear Submission</button></label></form>`; | |
} | |
} else { | |
buf += `<button class="button disabled" style="font-weight:bold; color:#575757; font-weight:bold; background-color:#d3d3d3;">Idle</button>`; | |
buf += `<button class="button" name="send" value="/msgroom ${room.roomid},/mafia action">Action</button><br/>`; | |
} | |
} | |
} | |
} | |
if (isHost) { | |
if (game.phase === "night" && isHost && game.takeIdles) { | |
buf += `<h3>Night Responses</h3>`; | |
let actions = `<br/>`; | |
let idles = `<br/>`; | |
let noResponses = `<br/>`; | |
for (const player of game.getRemainingPlayers()) { | |
if (player.action) { | |
actions += `<b>${player.safeName}</b>${player.action === true ? "" : `: ${player.action}`}<br/>`; | |
} else if (player.action === false) { | |
idles += `<b>${player.safeName}</b><br/>`; | |
} else { | |
noResponses += `<b>${player.safeName}</b><br/>`; | |
} | |
} | |
buf += `<p><details><summary class="button" style="text-align:left; display:inline-block">Idles</summary>${idles}</span></details></p>`; | |
buf += `<p><details><summary class="button" style="text-align:left; display:inline-block">Actions</summary>${actions}</span></details></p>`; | |
buf += `<p><details><summary class="button" style="text-align:left; display:inline-block">No Response</summary>${noResponses}</span></details></p>`; | |
} | |
let previousActions = `<br/>`; | |
for (let i = 0; i < game.dayNum; i++) { | |
previousActions += `<b>Night ${i}</b><br/>`; | |
for (const player of game.players) { | |
previousActions += `<b>${player.safeName}</b>:${player.actionArr[i] ? `${player.actionArr[i]}` : ""}<br/>`; | |
} | |
previousActions += `<br/>`; | |
} | |
buf += `<h3>Host options</h3>`; | |
buf += `<p><details><summary class="button" style="text-align:left; display:inline-block">General Options</summary>`; | |
buf += `<h3>General Options</h3>`; | |
if (!game.started) { | |
buf += `<button class="button" name="send" value="/msgroom ${room.roomid},/mafia closedsetup ${game.closedSetup ? "off" : "on"}">${game.closedSetup ? "Disable" : "Enable"} Closed Setup</button>`; | |
if (game.phase === "locked" || game.phase === "IDEAlocked") { | |
buf += ` <button class="button" name="send" value="/msgroom ${room.roomid},/mafia start">Start Game</button>`; | |
} else { | |
buf += ` <button class="button" name="send" value="/msgroom ${room.roomid},/mafia close">Close Signups</button>`; | |
} | |
} else if (game.phase === "day") { | |
buf += `<button class="button" name="send" value="/msgroom ${room.roomid},/mafia night">Go to Night ${game.dayNum}</button>`; | |
} else if (game.phase === "night") { | |
if (game.dayNum !== 0) { | |
buf += `<button class="button" name="send" value="/msgroom ${room.roomid},/mafia day">Go to Day ${game.dayNum + 1}</button> <button class="button" name="send" value="/msgroom ${room.roomid},/mafia extend">Return to Day ${game.dayNum}</button>`; | |
} else { | |
buf += `<button class="button" name="send" value="/msgroom ${room.roomid},/mafia day">Go to Day ${game.dayNum + 1}</button>`; | |
} | |
} | |
buf += ` <button class="button" name="send" value="/msgroom ${room.roomid},/mafia selfvote ${game.selfEnabled === true ? "off" : "on"}">${game.selfEnabled === true ? "Disable" : "Enable"} self voting</button> `; | |
buf += `<button class="button" name="send" value="/msgroom ${room.roomid},/mafia ${game.enableNV ? "disable" : "enable"}nl">${game.enableNV ? "Disable" : "Enable"} No Vote</button> `; | |
buf += `<button class="button" name="send" value="/msgroom ${room.roomid},/mafia reveal ${game.noReveal ? "on" : "off"}">${game.noReveal ? "Enable" : "Disable"} revealing of roles</button> `; | |
buf += `<button class="button" name="send" value="/msgroom ${room.roomid},/mafia autosub ${game.autoSub ? "off" : "on"}">${game.autoSub ? "Disable" : "Enable"} automatic subbing of players</button> `; | |
buf += `<button class="button" name="send" value="/msgroom ${room.roomid},/mafia end">End Game</button>`; | |
buf += `<p>To set a deadline, use <strong>/mafia deadline [minutes]</strong>.<br />To clear the deadline use <strong>/mafia deadline off</strong>.</p><hr/></details></p>`; | |
buf += `<p><details><summary class="button" style="text-align:left; display:inline-block">Player Options</summary>`; | |
buf += `<h3>Player Options</h3>`; | |
for (const player of game.getRemainingPlayers()) { | |
buf += `<p><details><summary class="button" style="text-align:left; display:inline-block"><span style="font-weight:bold;">`; | |
buf += `${player.safeName} (${player.role ? player.getStylizedRole(true) : ""})`; | |
buf += game.voteModifiers[player.id] !== void 0 ? `(votes worth ${game.getVoteValue(player)})` : ""; | |
buf += player.hammerRestriction !== null ? `(${player.hammerRestriction ? "actor" : "priest"})` : ""; | |
buf += player.silenced ? "(silenced)" : ""; | |
buf += player.nighttalk ? "(insomniac)" : ""; | |
buf += `</summary>`; | |
buf += `<button class="button" name="send" value="/msgroom ${room.roomid},/mafia kill ${player.id}">Kill</button> `; | |
buf += `<button class="button" name="send" value="/msgroom ${room.roomid},/mafia treestump ${player.id}">Make a Treestump (Kill)</button> `; | |
buf += `<button class="button" name="send" value="/msgroom ${room.roomid},/mafia spirit ${player.id}">Make a Restless Spirit (Kill)</button> `; | |
buf += `<button class="button" name="send" value="/msgroom ${room.roomid},/mafia spiritstump ${player.id}">Make a Restless Treestump (Kill)</button> `; | |
buf += `<button class="button" name="send" value="/msgroom ${room.roomid},/mafia sub next, ${player.id}">Force sub</button></span></details></p>`; | |
} | |
for (const eliminated of game.getEliminatedPlayers()) { | |
buf += `<p style="font-weight:bold;">${eliminated.safeName} (${eliminated.role ? eliminated.getStylizedRole() : ""})`; | |
if (eliminated.isTreestump()) | |
buf += ` (is a Treestump)`; | |
if (eliminated.isSpirit()) | |
buf += ` (is a Restless Spirit)`; | |
if (game.voteModifiers[eliminated.id] !== void 0) | |
buf += ` (votes worth ${game.getVoteValue(eliminated)})`; | |
buf += eliminated.hammerRestriction !== null ? `(${eliminated.hammerRestriction ? "actor" : "priest"})` : ""; | |
buf += eliminated.silenced ? "(silenced)" : ""; | |
buf += eliminated.nighttalk ? "(insomniac)" : ""; | |
buf += `: <button class="button" name="send" value="/msgroom ${room.roomid},/mafia revive ${eliminated.id}">Revive</button></p>`; | |
} | |
buf += `<hr/></details></p>`; | |
if (game.dayNum > 0) { | |
buf += `<p><details><summary class="button" style="text-align:left; display:inline-block">Previous Night Actions</summary>${previousActions}</span></details></p>`; | |
} | |
buf += `<p><details><summary class="button" style="text-align:left; display:inline-block">How to setup roles</summary>`; | |
buf += `<h3>Setting the roles</h3>`; | |
buf += `<p>To set the roles, use /mafia setroles [comma seperated list of roles] OR /mafia setroles [theme] in ${room.title}.</p>`; | |
buf += `<p>If you set the roles from a theme, the role parser will get all the correct roles for you. (Not all themes are supported).</p>`; | |
buf += `<p>The following key words determine a role's alignment (If none are found, the default alignment is town):</p>`; | |
buf += `<p style="font-weight:bold">${Object.values(MafiaData.alignments).map((a) => `<span style="color:${a.color || "#FFF"}">${a.name}</span>`).join(", ")}</p>`; | |
buf += `<p>Please note that anything inside (parentheses) is ignored by the role parser.</p>`; | |
buf += `<p>If you have roles that have conflicting alignments or base roles, you can use /mafia forcesetroles [comma seperated list of roles] to forcibly set the roles.</p>`; | |
buf += `<p>Please note that you will have to PM all the players their alignment, partners (if any), and other information about their role because the server will not provide it.</p>`; | |
buf += `<hr/></details></p>`; | |
buf += `<p style="font-weight:bold;">Players who will be subbed unless they talk: ${game.hostRequestedSub.join(", ")}</p>`; | |
buf += `<p style="font-weight:bold;">Players who are requesting a sub: ${game.requestedSub.join(", ")}</p>`; | |
} | |
buf += `<p style="font-weight:bold;">Sub List: ${game.subs.join(", ")}</p>`; | |
if (!isHost) { | |
if (game.phase === "signups") { | |
if (isPlayer) { | |
buf += `<p><button class="button" name="send" value="/msgroom ${room.roomid},/mafia leave">Leave game</button></p>`; | |
} else { | |
buf += `<p><button class="button" name="send" value="/msgroom ${room.roomid},/mafia join">Join game</button></p>`; | |
} | |
} else if (!isPlayer?.isEliminated()) { | |
if (!isPlayer && game.subs.includes(user.id) || isPlayer && !game.requestedSub.includes(user.id)) { | |
buf += `<p><details><summary class="button" style="text-align:left; display:inline-block">${isPlayer ? "Request to be subbed out" : "Cancel sub request"}</summary>`; | |
buf += `<button class="button" name="send" value="/msgroom ${room.roomid},/mafia sub out">${isPlayer ? "Confirm request to be subbed out" : "Confirm cancelation of sub request"}</button></details></p>`; | |
} else { | |
buf += `<p><details><summary class="button" style="text-align:left; display:inline-block">${isPlayer ? "Cancel sub request" : "Join the game as a sub"}</summary>`; | |
buf += `<button class="button" name="send" value="/msgroom ${room.roomid},/mafia sub in">${isPlayer ? "Confirm cancelation of sub request" : "Confirm that you want to join the game"}</button></details></p>`; | |
} | |
} | |
} | |
buf += `</div>`; | |
return buf; | |
}, | |
mafialadder(query, user) { | |
if (!user.named) | |
return Rooms.RETRY_AFTER_LOGIN; | |
const mafiaRoom = Rooms.get("mafia"); | |
if (!query.length || !mafiaRoom) | |
return this.close(); | |
const headers = { | |
leaderboard: { title: "Leaderboard", type: "Points", section: "leaderboard" }, | |
mvpladder: { title: "MVP Ladder", type: "MVPs", section: "mvps" }, | |
hostlogs: { title: "Host Logs", type: "Hosts", section: "hosts" }, | |
playlogs: { title: "Play Logs", type: "Plays", section: "plays" }, | |
leaverlogs: { title: "Leaver Logs", type: "Leavers", section: "leavers" } | |
}; | |
const date = new Date(); | |
if (query[1] === "prev") | |
date.setMonth(date.getMonth() - 1); | |
const month = date.toLocaleString("en-us", { month: "numeric", year: "numeric" }); | |
const ladder = headers[query[0]]; | |
if (!ladder) | |
return this.close(); | |
if (["hosts", "plays", "leavers"].includes(ladder.section)) | |
this.checkCan("mute", null, mafiaRoom); | |
this.title = `Mafia ${ladder.title} (${date.toLocaleString("en-us", { month: "long" })} ${date.getFullYear()})`; | |
let buf = `<div class="pad ladder">`; | |
buf += `${query[1] === "prev" ? "" : `<button class="button" name="send" value="/join view-mafialadder-${query[0]}" style="float:left"><i class="fa fa-refresh"></i> Refresh</button> <button class="button" name="send" value="/join view-mafialadder-${query[0]}-prev" style="float:left">View last month's ${ladder.title}</button>`}`; | |
buf += `<br /><br />`; | |
const section = ladder.section; | |
if (!logs[section][month] || !Object.keys(logs[section][month]).length) { | |
buf += `${ladder.title} for ${date.toLocaleString("en-us", { month: "long" })} ${date.getFullYear()} not found.</div>`; | |
return buf; | |
} | |
const entries = import_lib.Utils.sortBy(Object.entries(logs[section][month]), ([key, value]) => -value); | |
buf += `<table style="margin-left: auto; margin-right: auto"><tbody><tr><th colspan="2"><h2 style="margin: 5px auto">Mafia ${ladder.title} for ${date.toLocaleString("en-us", { month: "long" })} ${date.getFullYear()}</h1></th></tr>`; | |
buf += `<tr><th>User</th><th>${ladder.type}</th></tr>`; | |
for (const [key, value] of entries) { | |
buf += `<tr><td>${key}</td><td>${value}</td></tr>`; | |
} | |
return buf + `</table></div>`; | |
} | |
}; | |
const commands = { | |
mafia: { | |
""(target, room, user) { | |
room = this.requireRoom(); | |
const game = room.getGame(Mafia); | |
if (game) { | |
if (!this.runBroadcast()) | |
return; | |
return this.sendReply(`|html|${game.roomWindow()}`); | |
} | |
return this.parse("/help mafia"); | |
}, | |
forcehost: "host", | |
nexthost: "host", | |
host(target, room, user, connection, cmd) { | |
room = this.requireRoom(); | |
if (room.settings.mafiaDisabled) | |
return this.errorReply(`Mafia is disabled for this room.`); | |
this.checkChat(); | |
if (room.type !== "chat") | |
return this.errorReply(`This command is only meant to be used in chat rooms.`); | |
if (room.game) | |
return this.errorReply(`There is already a game of ${room.game.title} in progress in this room.`); | |
const nextHost = room.roomid === "mafia" && cmd === "nexthost"; | |
if (nextHost || !room.auth.has(user.id)) | |
this.checkCan("show", null, room); | |
let targetUser; | |
let targetUsername; | |
if (nextHost) { | |
if (!hostQueue.length) | |
return this.errorReply(`Nobody is on the host queue.`); | |
const skipped = []; | |
let hostid; | |
while (hostid = hostQueue.shift()) { | |
({ targetUser, targetUsername } = this.splitUser(hostid, { exactName: true })); | |
if (!targetUser?.connected || !room.users[targetUser.id] || Mafia.isHostBanned(room, targetUser)) { | |
skipped.push(hostid); | |
targetUser = null; | |
} else { | |
break; | |
} | |
} | |
if (skipped.length) { | |
this.sendReply(`${skipped.join(", ")} ${Chat.plural(skipped.length, "were", "was")} not online, not in the room, or are host banned and were removed from the host queue.`); | |
} | |
if (!targetUser) | |
return this.errorReply(`Nobody on the host queue could be hosted.`); | |
} else { | |
({ targetUser, targetUsername } = this.splitUser(target, { exactName: true })); | |
if (room.roomid === "mafia" && hostQueue.length && toID(targetUsername) !== hostQueue[0]) { | |
if (!cmd.includes("force")) { | |
return this.errorReply(`${targetUsername} isn't the next host on the queue. Use /mafia forcehost if you're sure.`); | |
} | |
} | |
} | |
if (!targetUser?.connected) { | |
return this.errorReply(`The user "${targetUsername}" was not found.`); | |
} | |
if (!nextHost && targetUser.id !== user.id) | |
this.checkCan("mute", null, room); | |
if (!room.users[targetUser.id]) { | |
return this.errorReply(`${targetUsername} is not in this room, and cannot be hosted.`); | |
} | |
if (Mafia.isHostBanned(room, targetUser)) { | |
return this.errorReply(`${targetUsername} is banned from hosting mafia games.`); | |
} | |
room.game = new Mafia(room, targetUser); | |
for (const conn of targetUser.connections) { | |
void Chat.resolvePage(`view-mafia-${room.roomid}`, targetUser, conn); | |
} | |
room.addByUser(user, `${targetUser.name} was appointed the mafia host by ${user.name}.`); | |
if (room.roomid === "mafia") { | |
const queueIndex = hostQueue.indexOf(targetUser.id); | |
if (queueIndex > -1) | |
hostQueue.splice(queueIndex, 1); | |
room.add(`|c:|${Math.floor(Date.now() / 1e3)}|~|**Mafiasignup!**`).update(); | |
} | |
this.modlog("MAFIAHOST", targetUser, null, { noalts: true, noip: true }); | |
}, | |
hosthelp: [ | |
`/mafia host [user] - Create a game of Mafia with [user] as the host. Requires whitelist + % @ # ~, drivers+ can host other people.` | |
], | |
q: "queue", | |
queue(target, room, user) { | |
room = this.requireRoom("mafia"); | |
if (room.settings.mafiaDisabled) | |
return this.errorReply(`Mafia is disabled for this room.`); | |
const [command, targetUserID] = target.split(",").map(toID); | |
switch (command) { | |
case "forceadd": | |
case "add": | |
this.checkChat(); | |
if (targetUserID === user.id) { | |
if (!room.auth.has(user.id)) | |
this.checkCan("show", null, room); | |
} else { | |
this.checkCan("mute", null, room); | |
} | |
if (!targetUserID) | |
return this.parse(`/help mafia queue`); | |
const targetUser = Users.get(targetUserID); | |
if (!targetUser?.connected && !command.includes("force")) { | |
return this.errorReply(`User ${targetUserID} not found. To forcefully add the user to the queue, use /mafia queue forceadd, ${targetUserID}`); | |
} | |
if (hostQueue.includes(targetUserID)) | |
return this.errorReply(`User ${targetUserID} is already on the host queue.`); | |
if (targetUser && Mafia.isHostBanned(room, targetUser)) { | |
return this.errorReply(`User ${targetUserID} is banned from hosting mafia games.`); | |
} | |
hostQueue.push(targetUserID); | |
room.add(`User ${targetUserID} has been added to the host queue by ${user.name}.`).update(); | |
break; | |
case "del": | |
case "delete": | |
case "remove": | |
if (targetUserID !== user.id) | |
this.checkCan("mute", null, room); | |
const index = hostQueue.indexOf(targetUserID); | |
if (index === -1) | |
return this.errorReply(`User ${targetUserID} is not on the host queue.`); | |
hostQueue.splice(index, 1); | |
room.add(`User ${targetUserID} has been removed from the host queue by ${user.name}.`).update(); | |
break; | |
case "": | |
case "show": | |
case "view": | |
if (!this.runBroadcast()) | |
return; | |
this.sendReplyBox(`<strong>Host Queue:</strong> ${hostQueue.join(", ")}`); | |
break; | |
default: | |
this.parse("/help mafia queue"); | |
} | |
}, | |
queuehelp: [ | |
`/mafia queue - Shows the upcoming users who are going to host.`, | |
`/mafia queue add, (user) - Adds the user to the hosting queue. Requires whitelist + % @ # ~`, | |
`/mafia queue remove, (user) - Removes the user from the hosting queue. Requires whitelist + % @ # ~` | |
], | |
qadd: "queueadd", | |
qforceadd: "queueadd", | |
queueforceadd: "queueadd", | |
queueadd(target, room, user, connection, cmd) { | |
this.parse(`/mafia queue ${cmd.includes("force") ? `forceadd` : `add`}, ${target}`); | |
}, | |
qdel: "queueremove", | |
qdelete: "queueremove", | |
qremove: "queueremove", | |
queueremove(target, room, user) { | |
this.parse(`/mafia queue remove, ${target}`); | |
}, | |
join(target, room, user) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
this.checkChat(null, room); | |
game.join(user); | |
}, | |
joinhelp: [`/mafia join - Join the game.`], | |
leave(target, room, user) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
game.leave(user); | |
}, | |
leavehelp: [`/mafia leave - Leave the game. Can only be done while signups are open.`], | |
playercap(target, room, user) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
if (game.phase !== "signups") | |
return this.errorReply(`Signups are already closed.`); | |
const num = parseInt(target); | |
if (isNaN(num) || num > 50 || num < 2) | |
return this.parse("/help mafia playercap"); | |
if (num < game.playerCount) { | |
return this.errorReply(`Player cap has to be equal or more than the amount of players in game.`); | |
} | |
if (num === game.playerCap) | |
return this.errorReply(`Player cap is already set at ${game.playerCap}.`); | |
game.playerCap = num; | |
game.sendDeclare(`Player cap has been set to ${game.playerCap}`); | |
game.logAction(user, `set playercap to ${num}`); | |
}, | |
playercaphelp: [ | |
`/mafia playercap [cap]- Limit the number of players being able to join the game. Player cap cannot be more than 50 or less than 2. Default is 20. Requires host % @ # ~` | |
], | |
close(target, room, user) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
if (game.phase !== "signups") | |
return this.errorReply(`Signups are already closed.`); | |
if (game.playerCount < 2) | |
return this.errorReply(`You need at least 2 players to start.`); | |
game.phase = "locked"; | |
game.sendHTML(game.roomWindow()); | |
game.updatePlayers(); | |
game.logAction(user, `closed signups`); | |
}, | |
closehelp: [`/mafia close - Closes signups for the current game. Requires host % @ # ~`], | |
cs: "closedsetup", | |
closedsetup(target, room, user) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
const action = toID(target); | |
if (!["on", "off"].includes(action)) | |
return this.parse("/help mafia closedsetup"); | |
if (game.started) { | |
return this.errorReply(`You can't ${action === "on" ? "enable" : "disable"} closed setup because the game has already started.`); | |
} | |
if (action === "on" && game.closedSetup || action === "off" && !game.closedSetup) { | |
return this.errorReply(`Closed setup is already ${game.closedSetup ? "enabled" : "disabled"}.`); | |
} | |
game.closedSetup = action === "on"; | |
game.sendDeclare(`The game is ${action === "on" ? "now" : "no longer"} a closed setup.`); | |
game.updateHost(); | |
game.logAction(user, `${game.closedSetup ? "enabled" : "disabled"} closed setup`); | |
}, | |
closedsetuphelp: [ | |
`/mafia closedsetup [on|off] - Sets if the game is a closed setup. Closed setups don't show the role list to players. Requires host % @ # ~` | |
], | |
reveal(target, room, user) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
const action = toID(target); | |
if (!["on", "off"].includes(action)) | |
return this.parse("/help mafia reveal"); | |
if (action === "off" && game.noReveal || action === "on" && !game.noReveal) { | |
return user.sendTo( | |
room, | |
`|error|Revealing of roles is already ${game.noReveal ? "disabled" : "enabled"}.` | |
); | |
} | |
game.noReveal = action === "off"; | |
game.sendDeclare(`Revealing of roles has been ${action === "off" ? "disabled" : "enabled"}.`); | |
game.updatePlayers(); | |
game.logAction(user, `${game.noReveal ? "disabled" : "enabled"} reveals`); | |
}, | |
revealhelp: [`/mafia reveal [on|off] - Sets if roles reveal on death or not. Requires host % @ # ~`], | |
takeidles(target, room, user) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
const action = toID(target); | |
if (!["on", "off"].includes(action)) | |
return this.parse("/help mafia takeidles"); | |
if (action === "off" && !game.takeIdles || action === "on" && game.takeIdles) { | |
return this.errorReply(`Actions and idles are already ${game.takeIdles ? "" : "not "}being accepted.`); | |
} | |
game.takeIdles = action === "on"; | |
game.sendDeclare(`Actions and idles are ${game.takeIdles ? "now" : "no longer"} being accepted.`); | |
game.updatePlayers(); | |
}, | |
takeidleshelp: [`/mafia takeidles [on|off] - Sets if idles are accepted by the script or not. Requires host % @ # ~`], | |
resetroles: "setroles", | |
forceresetroles: "setroles", | |
forcesetroles: "setroles", | |
setroles(target, room, user, connection, cmd) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
const reset = cmd.includes("reset"); | |
if (reset) { | |
if (game.phase !== "day" && game.phase !== "night") | |
return this.errorReply(`The game has not started yet.`); | |
} else { | |
if (game.phase !== "locked" && game.phase !== "IDEAlocked") { | |
return this.errorReply(game.phase === "signups" ? `You need to close signups first.` : `The game has already started.`); | |
} | |
} | |
if (!target) | |
return this.parse("/help mafia setroles"); | |
game.setRoles(user, target, cmd.includes("force"), reset); | |
game.logAction(user, `${reset ? "re" : ""}set roles`); | |
}, | |
setroleshelp: [ | |
`/mafia setroles [comma separated roles] - Set the roles for a game of mafia. You need to provide one role per player.`, | |
`/mafia forcesetroles [comma separated roles] - Forcibly set the roles for a game of mafia. No role PM information or alignment will be set.`, | |
`/mafia resetroles [comma separated roles] - Reset the roles in an ongoing game.` | |
], | |
resetgame: "gamereset", | |
forceresetgame: "gamereset", | |
gamereset(target, room, user, connection) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
if (target) | |
return this.parse("/help mafia resetgame"); | |
if (game.phase !== "day" && game.phase !== "night") | |
return this.errorReply(`The game has not started yet.`); | |
if (game.IDEA.data) | |
return this.errorReply(`You cannot use this command in IDEA.`); | |
game.resetGame(); | |
game.logAction(user, "reset the game state"); | |
}, | |
resetgamehelp: [ | |
`/mafia resetgame - Resets game data. Does not change settings from the host (besides deadlines) or add/remove any players. Requires host % @ # ~` | |
], | |
idea(target, room, user) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
this.checkCan("show", null, room); | |
if (!user.can("mute", null, room) && game.hostid !== user.id && !game.cohostids.includes(user.id)) { | |
return this.errorReply(`/mafia idea - Access denied.`); | |
} | |
if (game.started) | |
return this.errorReply(`You cannot start an IDEA after the game has started.`); | |
if (game.phase !== "locked" && game.phase !== "IDEAlocked") { | |
return this.errorReply(`You need to close the signups first.`); | |
} | |
game.ideaInit(user, toID(target)); | |
game.logAction(user, `started an IDEA`); | |
}, | |
ideahelp: [ | |
`/mafia idea [idea] - starts the IDEA module [idea]. Requires + % @ # ~, voices can only start for themselves`, | |
`/mafia ideareroll - rerolls the IDEA module. Requires host % @ # ~`, | |
`/mafia ideapick [selection], [role] - selects a role`, | |
`/mafia ideadiscards - shows the discarded roles` | |
], | |
customidea(target, room, user) { | |
room = this.requireRoom(); | |
this.checkCan("mute", null, room); | |
const game = this.requireGame(Mafia); | |
if (game.started) | |
return this.errorReply(`You cannot start an IDEA after the game has started.`); | |
if (game.phase !== "locked" && game.phase !== "IDEAlocked") { | |
return this.errorReply(`You need to close the signups first.`); | |
} | |
const [options, roles] = import_lib.Utils.splitFirst(target, "\n"); | |
if (!options || !roles) | |
return this.parse("/help mafia idea"); | |
const [choicesStr, ...picks] = options.split(",").map((x) => x.trim()); | |
const choices = parseInt(choicesStr); | |
if (!choices || choices <= picks.length) | |
return this.errorReply(`You need to have more choices than picks.`); | |
if (picks.some((value, index, arr) => arr.indexOf(value, index + 1) > 0)) { | |
return this.errorReply(`Your picks must be unique.`); | |
} | |
game.customIdeaInit(user, choices, picks, roles); | |
}, | |
customideahelp: [ | |
`/mafia customidea choices, picks (new line here, shift+enter)`, | |
`(comma or newline separated rolelist) - Starts an IDEA module with custom roles. Requires % @ # ~`, | |
`choices refers to the number of roles you get to pick from. In GI, this is 2, in GestI, this is 3.`, | |
`picks refers to what you choose. In GI, this should be 'role', in GestI, this should be 'role, alignment'` | |
], | |
ideapick(target, room, user) { | |
room = this.requireRoom(); | |
const args = target.split(","); | |
const game = this.requireGame(Mafia); | |
const player = game.getPlayer(user.id); | |
if (!player) { | |
return user.sendTo(room, "|error|You are not a player in the game."); | |
} | |
if (game.phase !== "IDEApicking") { | |
return this.errorReply(`The game is not in the IDEA picking phase.`); | |
} | |
game.ideaPick(user, args); | |
}, | |
ideareroll(target, room, user) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
game.ideaDistributeRoles(user); | |
game.logAction(user, `rerolled an IDEA`); | |
}, | |
idearerollhelp: [`/mafia ideareroll - rerolls the roles for the current IDEA module. Requires host % @ # ~`], | |
discards: "ideadiscards", | |
ideadiscards(target, room, user) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (!game.IDEA.data) | |
return this.errorReply(`There is no IDEA module in the mafia game.`); | |
if (target) { | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
if (this.meansNo(target)) { | |
if (game.IDEA.discardsHidden) | |
return this.errorReply(`IDEA discards are already hidden.`); | |
game.IDEA.discardsHidden = true; | |
} else if (this.meansYes(target)) { | |
if (!game.IDEA.discardsHidden) | |
return this.errorReply(`IDEA discards are already visible.`); | |
game.IDEA.discardsHidden = false; | |
} else { | |
return this.parse("/help mafia ideadiscards"); | |
} | |
game.logAction(user, `${game.IDEA.discardsHidden ? "hid" : "unhid"} IDEA discards`); | |
return this.sendReply(`IDEA discards are now ${game.IDEA.discardsHidden ? "hidden" : "visible"}.`); | |
} | |
if (game.IDEA.discardsHidden) | |
return this.errorReply(`Discards are not visible.`); | |
if (!game.IDEA.discardsHTML) | |
return this.errorReply(`The IDEA module does not have finalised discards yet.`); | |
if (!this.runBroadcast()) | |
return; | |
this.sendReplyBox(`<details><summary>IDEA discards:</summary>${game.IDEA.discardsHTML}</details>`); | |
}, | |
ideadiscardshelp: [ | |
`/mafia ideadiscards - shows the discarded roles`, | |
`/mafia ideadiscards off - hides discards from the players. Requires host % @ # ~`, | |
`/mafia ideadiscards on - shows discards to the players. Requires host % @ # ~` | |
], | |
daystart: "start", | |
nightstart: "start", | |
start(target, room, user, connection, cmd) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
if (target) { | |
this.parse(`/mafia close`); | |
this.parse(`/mafia setroles ${target}`); | |
this.parse(`/mafia ${cmd}`); | |
return; | |
} | |
game.start(user, cmd === "daystart"); | |
game.logAction(user, `started the game`); | |
}, | |
starthelp: [`/mafia start - Start the game of mafia. Signups must be closed. Requires host % @ # ~`], | |
extend: "day", | |
night: "day", | |
day(target, room, user, connection, cmd) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
if (cmd === "night") { | |
game.night(); | |
} else { | |
let extension = parseInt(toID(target)); | |
if (isNaN(extension)) { | |
extension = 0; | |
} else { | |
if (extension < 1) | |
extension = 1; | |
if (extension > 10) | |
extension = 10; | |
} | |
if (cmd === "extend") { | |
for (const player of game.getRemainingPlayers()) { | |
player.actionArr[game.dayNum] = ""; | |
} | |
} | |
game.day(cmd === "extend" ? extension : null); | |
} | |
game.logAction(user, `set day/night`); | |
}, | |
dayhelp: [ | |
`/mafia day - Move to the next game day. Requires host % @ # ~`, | |
`/mafia night - Move to the next game night. Requires host % @ # ~`, | |
`/mafia extend (minutes) - Return to the previous game day. If (minutes) is provided, set the deadline for (minutes) minutes. Requires host % @ # ~` | |
], | |
prod(target, room, user) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
if (game.phase !== "night") | |
return; | |
for (const player of game.getRemainingPlayers()) { | |
const playerid = Users.get(player.id); | |
if (playerid?.connected && player.action === null) { | |
playerid.sendTo(room, `|notify|Send in an action or idle!`); | |
playerid.sendTo(room, `Send in an action or idle, or else you will get subbed out!`); | |
} | |
} | |
game.sendDeclare(`Unsubmitted players have been reminded to submit an action or idle.`); | |
}, | |
prodhelp: [ | |
`/mafia prod - Notifies players that they must submit an action or idle if they haven't yet. Requires host % @ # ~` | |
], | |
v: "vote", | |
vote(target, room, user) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
this.checkChat(null, room); | |
const player = game.getPlayer(user.id); | |
if (!player || player.isEliminated() && !player.isSpirit()) { | |
return this.errorReply(`You are not in the game of ${game.title}.`); | |
} | |
game.vote(player, toID(target)); | |
}, | |
votehelp: [`/mafia vote [player|novote] - Vote the specified player or abstain from voting.`], | |
uv: "unvote", | |
unv: "unvote", | |
unnovote: "unvote", | |
unvote(target, room, user) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
this.checkChat(null, room); | |
const player = game.getPlayer(user.id); | |
if (!player || player.isEliminated() && !player.isSpirit()) { | |
return this.errorReply(`You are not in the game of ${game.title}.`); | |
} | |
game.unvote(player); | |
}, | |
unvotehelp: [`/mafia unvote - Withdraw your vote. Fails if you're not voting anyone`], | |
nv: "novote", | |
novote() { | |
this.parse("/mafia vote novote"); | |
}, | |
enableself: "selfvote", | |
selfvote(target, room, user, connection, cmd) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
const action = toID(target); | |
if (!action) | |
return this.parse(`/help mafia selfvote`); | |
if (this.meansYes(action)) { | |
game.setSelfVote(user, true); | |
} else if (this.meansNo(action)) { | |
game.setSelfVote(user, false); | |
} else if (action === "hammer") { | |
game.setSelfVote(user, "hammer"); | |
} else { | |
return this.parse(`/help mafia selfvote`); | |
} | |
game.logAction(user, `changed selfvote`); | |
}, | |
selfvotehelp: [ | |
`/mafia selfvote [on|hammer|off] - Allows players to self vote themselves either at hammer or anytime. Requires host % @ # ~` | |
], | |
treestump: "kill", | |
spirit: "kill", | |
spiritstump: "kill", | |
kick: "kill", | |
kill(target, room, user, connection, cmd) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
if (game.phase === "IDEApicking") { | |
return this.errorReply(`You cannot add or remove players while IDEA roles are being picked.`); | |
} | |
if (!target) | |
return this.parse("/help mafia kill"); | |
const player = game.getPlayer(toID(target)); | |
if (!player) { | |
return this.errorReply(`${target.trim()} is not a player.`); | |
} | |
let repeat, elimType; | |
switch (cmd) { | |
case "treestump": | |
elimType = MafiaEliminateType.TREESTUMP; | |
repeat = player.isTreestump() && !player.isSpirit(); | |
break; | |
case "spirit": | |
elimType = MafiaEliminateType.SPIRIT; | |
repeat = !player.isTreestump() && player.isSpirit(); | |
break; | |
case "spiritstump": | |
elimType = MafiaEliminateType.SPIRITSTUMP; | |
repeat = player.isTreestump() && player.isSpirit(); | |
break; | |
case "kick": | |
elimType = MafiaEliminateType.KICK; | |
break; | |
default: | |
elimType = MafiaEliminateType.ELIMINATE; | |
repeat = player.eliminated === MafiaEliminateType.ELIMINATE; | |
break; | |
} | |
if (repeat) | |
return this.errorReply(`${player.safeName} has already been ${cmd}ed.`); | |
game.eliminate(player, elimType); | |
game.logAction(user, `${cmd}ed ${player.safeName}`); | |
}, | |
killhelp: [ | |
`/mafia kill [player] - Kill a player, eliminating them from the game. Requires host % @ # ~`, | |
`/mafia treestump [player] - Kills a player, but allows them to talk during the day still.`, | |
`/mafia spirit [player] - Kills a player, but allows them to vote still.`, | |
`/mafia spiritstump [player] Kills a player, but allows them to talk and vote during the day.` | |
], | |
revealas: "revealrole", | |
revealrole(target, room, user, connection, cmd) { | |
const args = target.split(","); | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
let revealAs = ""; | |
let revealedRole = null; | |
if (cmd === "revealas") { | |
if (!args[0]) { | |
return this.parse("/help mafia revealas"); | |
} else { | |
revealedRole = Mafia.parseRole(args.pop()); | |
const color = MafiaData.alignments[revealedRole.role.alignment].color; | |
revealAs = `<span style="font-weight:bold;color:${color}">${revealedRole.role.safeName}</span>`; | |
} | |
} | |
if (!args[0]) | |
return this.parse("/help mafia revealas"); | |
for (const targetUsername of args) { | |
const player = game.getPlayer(toID(targetUsername)); | |
if (player) { | |
game.revealRole(user, player, `${revealAs || player.getStylizedRole()}`); | |
game.logAction(user, `revealed ${player.name}`); | |
if (revealedRole) { | |
game.secretLogAction(user, `fakerevealed ${player.name} as ${revealedRole.role.name}`); | |
} | |
} else { | |
this.errorReply(`${targetUsername} is not a player.`); | |
} | |
} | |
}, | |
revealrolehelp: [ | |
`/mafia revealrole [player] - Reveals the role of a player. Requires host % @ # ~`, | |
`/mafia revealas [player], [role] - Fakereveals the role of a player as a certain role. Requires host % @ # ~` | |
], | |
unidle: "idle", | |
unaction: "idle", | |
noresponse: "idle", | |
action: "idle", | |
idle(target, room, user, connection, cmd) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
const player = game.getPlayer(user.id); | |
if (!player) | |
return this.errorReply(`You are not in the game of ${game.title}.`); | |
if (player.isEliminated()) { | |
return this.errorReply(`You have been eliminated from the game and cannot take any actions.`); | |
} | |
if (game.phase !== "night") { | |
return this.errorReply(`You can only submit an action or idle during the night phase.`); | |
} | |
if (!game.takeIdles) { | |
return this.errorReply(`The host is not accepting idles through the script. Send your action or idle to the host.`); | |
} | |
switch (cmd) { | |
case "idle": | |
player.action = false; | |
user.sendTo(room, `You have idled.`); | |
player.actionArr[game.dayNum] = "idle"; | |
break; | |
case "action": | |
player.action = true; | |
if (target) { | |
player.action = target; | |
try { | |
this.checkBanwords(room, target); | |
} catch { | |
throw new Chat.ErrorMessage(`Your action submission contained a word banned by this room.`); | |
} | |
user.sendTo(room, `You have decided to use an action, with the following details: ${target}`); | |
} else { | |
user.sendTo(room, `You have decided to use an action. Please submit details about your action.`); | |
} | |
player.actionArr[game.dayNum] = target; | |
break; | |
case "noresponse": | |
case "unidle": | |
case "unaction": | |
player.action = null; | |
user.sendTo(room, `You are no longer submitting an action or idle.`); | |
player.actionArr[game.dayNum] = ""; | |
break; | |
} | |
player.updateHtmlRoom(); | |
}, | |
actionhelp: "idlehelp", | |
idlehelp: [ | |
`/mafia idle - Tells the host if you are idling.`, | |
`/mafia action [details] - Tells the host you are using an action with the given submission details.` | |
], | |
forceadd: "add", | |
add(target, room, user, connection, cmd) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
if (!toID(target)) | |
return this.parse("/help mafia add"); | |
const targetUser = Users.get(target); | |
if (!targetUser) { | |
throw new Chat.ErrorMessage(`The user "${target}" was not found.`); | |
} | |
game.join(targetUser, user, cmd === "forceadd"); | |
}, | |
addhelp: [ | |
`/mafia add [player] - Add a new player to the game. Requires host % @ # ~` | |
], | |
revive(target, room, user, connection, cmd) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
if (!toID(target)) | |
return this.parse("/help mafia revive"); | |
const player = game.getPlayer(toID(target)); | |
if (!player) { | |
throw new Chat.ErrorMessage(`"${target}" is not currently playing`); | |
} | |
if (!player.isEliminated()) { | |
throw new Chat.ErrorMessage(`${player.name} has not been eliminated.`); | |
} | |
game.revive(user, player); | |
}, | |
revivehelp: [ | |
`/mafia revive [player] - Revives a player who was eliminated. Requires host % @ # ~` | |
], | |
dl: "deadline", | |
deadline(target, room, user) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
target = toID(target); | |
if (target && game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
if (target === "off") { | |
game.setDeadline(0); | |
} else { | |
const num = parseInt(target); | |
if (isNaN(num)) { | |
if (game.hostid === user.id || game.cohostids.includes(user.id)) { | |
this.broadcastMessage = this.message.toLowerCase().replace(/[^a-z0-9\s!,]/g, ""); | |
} | |
if (!this.runBroadcast()) | |
return false; | |
if (game.dlAt - Date.now() > 0) { | |
return this.sendReply(`|raw|<strong>The deadline is in ${Chat.toDurationString(game.dlAt - Date.now()) || "0 seconds"}.</strong>`); | |
} else { | |
return this.parse(`/help mafia deadline`); | |
} | |
} | |
if (num < 1 || num > 20) | |
return this.errorReply(`The deadline must be between 1 and 20 minutes.`); | |
game.setDeadline(num); | |
} | |
game.logAction(user, `changed deadline`); | |
}, | |
deadlinehelp: [ | |
`/mafia deadline [minutes|off] - Sets or removes the deadline for the game. Cannot be more than 20 minutes.` | |
], | |
applyvotemodifier: "applyhammermodifier", | |
applyhammermodifier(target, room, user, connection, cmd) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
if (!game.started) | |
return this.errorReply(`The game has not started yet.`); | |
const [playerId, mod] = target.split(","); | |
const player = game.getPlayer(toID(playerId)); | |
if (!player) { | |
throw new Chat.ErrorMessage(`The player "${playerId}" does not exist.`); | |
} | |
if (cmd === "applyhammermodifier") { | |
game.applyHammerModifier(user, player, parseInt(mod)); | |
game.secretLogAction(user, `changed a hammer modifier`); | |
} else { | |
game.applyVoteModifier(user, player, parseInt(mod)); | |
game.secretLogAction(user, `changed a vote modifier`); | |
} | |
}, | |
clearvotemodifiers: "clearhammermodifiers", | |
clearhammermodifiers(target, room, user, connection, cmd) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
if (!game.started) | |
return this.errorReply(`The game has not started yet.`); | |
if (cmd === "clearhammermodifiers") { | |
game.clearHammerModifiers(user); | |
game.secretLogAction(user, `cleared hammer modifiers`); | |
} else { | |
game.clearVoteModifiers(user); | |
game.secretLogAction(user, `cleared vote modifiers`); | |
} | |
}, | |
hate: "love", | |
unhate: "love", | |
unlove: "love", | |
removehammermodifier: "love", | |
love(target, room, user, connection, cmd) { | |
let mod; | |
switch (cmd) { | |
case "hate": | |
mod = -1; | |
break; | |
case "love": | |
mod = 1; | |
break; | |
case "unhate": | |
case "unlove": | |
case "removehammermodifier": | |
mod = 0; | |
break; | |
} | |
this.parse(`/mafia applyhammermodifier ${target}, ${mod}`); | |
}, | |
doublevoter: "mayor", | |
voteless: "mayor", | |
unvoteless: "mayor", | |
unmayor: "mayor", | |
removevotemodifier: "mayor", | |
mayor(target, room, user, connection, cmd) { | |
let mod; | |
switch (cmd) { | |
case "doublevoter": | |
case "mayor": | |
mod = 2; | |
break; | |
case "voteless": | |
mod = 0; | |
break; | |
case "unvoteless": | |
case "unmayor": | |
case "removevotemodifier": | |
mod = 1; | |
break; | |
} | |
this.parse(`/mafia applyvotemodifier ${target}, ${mod}`); | |
}, | |
unsilence: "silence", | |
silence(target, room, user, connection, cmd) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
if (!game.started) | |
return this.errorReply(`The game has not started yet.`); | |
target = toID(target); | |
const targetPlayer = game.getPlayer(target); | |
const silence = cmd === "silence"; | |
if (!targetPlayer) | |
return this.errorReply(`${target} is not in the game of mafia.`); | |
if (silence === targetPlayer.silenced) { | |
return this.errorReply(`${targetPlayer.name} is already ${!silence ? "not" : ""} silenced.`); | |
} | |
targetPlayer.silenced = silence; | |
this.sendReply(`${targetPlayer.name} has been ${!silence ? "un" : ""}silenced.`); | |
game.logAction(user, `${!silence ? "un" : ""}silenced a player`); | |
}, | |
silencehelp: [ | |
`/mafia silence [player] - Silences [player], preventing them from talking at all. Requires host % @ # ~`, | |
`/mafia unsilence [player] - Removes a silence on [player], allowing them to talk again. Requires host % @ # ~` | |
], | |
insomniac: "nighttalk", | |
uninsomniac: "nighttalk", | |
unnighttalk: "nighttalk", | |
nighttalk(target, room, user, connection, cmd) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
if (!game.started) | |
return this.errorReply(`The game has not started yet.`); | |
target = toID(target); | |
const targetPlayer = game.getPlayer(target); | |
const nighttalk = !cmd.startsWith("un"); | |
if (!targetPlayer) | |
return this.errorReply(`${target} is not in the game of mafia.`); | |
if (nighttalk === targetPlayer.nighttalk) { | |
return this.errorReply(`${targetPlayer.name} is already ${!nighttalk ? "not" : ""} able to talk during the night.`); | |
} | |
targetPlayer.nighttalk = nighttalk; | |
this.sendReply(`${targetPlayer.name} can ${!nighttalk ? "no longer" : "now"} talk during the night.`); | |
game.logAction(user, `${!nighttalk ? "un" : ""}insomniacd a player`); | |
}, | |
nighttalkhelp: [ | |
`/mafia nighttalk [player] - Makes [player] an insomniac, allowing them to talk freely during the night. Requires host % @ # ~`, | |
`/mafia unnighttalk [player] - Removes [player] as an insomniac, preventing them from talking during the night. Requires host % @ # ~` | |
], | |
actor: "priest", | |
unactor: "priest", | |
unpriest: "priest", | |
priest(target, room, user, connection, cmd) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
if (!game.started) | |
return this.errorReply(`The game has not started yet.`); | |
target = toID(target); | |
const targetPlayer = game.getPlayer(target); | |
if (!targetPlayer) | |
return this.errorReply(`${target} is not in the game of mafia.`); | |
const actor = cmd.endsWith("actor"); | |
const remove = cmd.startsWith("un"); | |
if (remove) { | |
if (targetPlayer.hammerRestriction === null) { | |
return this.errorReply(`${targetPlayer.name} already has no voting restrictions.`); | |
} | |
if (actor !== targetPlayer.hammerRestriction) { | |
return this.errorReply(`${targetPlayer.name} is ${targetPlayer.hammerRestriction ? "an actor" : "a priest"}.`); | |
} | |
targetPlayer.hammerRestriction = null; | |
return this.sendReply(`${targetPlayer}'s hammer restriction was removed.`); | |
} | |
if (actor === targetPlayer.hammerRestriction) { | |
return this.errorReply(`${targetPlayer.name} is already ${targetPlayer.hammerRestriction ? "an actor" : "a priest"}.`); | |
} | |
targetPlayer.hammerRestriction = actor; | |
this.sendReply(`${targetPlayer.name} is now ${targetPlayer.hammerRestriction ? "an actor (can only hammer)" : "a priest (can't hammer)"}.`); | |
if (actor) { | |
game.unvote(targetPlayer, true); | |
} | |
game.logAction(user, `made a player actor/priest`); | |
}, | |
priesthelp: [ | |
`/mafia (un)priest [player] - Makes [player] a priest, preventing them from placing the hammer vote. Requires host % @ # ~`, | |
`/mafia (un)actor [player] - Makes [player] an actor, preventing them from placing non-hammer votes. Requires host % @ # ~` | |
], | |
shifthammer: "hammer", | |
resethammer: "hammer", | |
hammer(target, room, user, connection, cmd) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
if (!game.started) | |
return this.errorReply(`The game has not started yet.`); | |
const hammer = parseInt(target); | |
if (toID(cmd) !== `resethammer` && (isNaN(hammer) && !this.meansNo(target) || hammer < 1)) { | |
return this.errorReply(`${target} is not a valid hammer count.`); | |
} | |
switch (cmd.toLowerCase()) { | |
case "shifthammer": | |
game.shiftHammer(hammer); | |
break; | |
case "hammer": | |
game.setHammer(hammer); | |
break; | |
default: | |
game.resetHammer(); | |
break; | |
} | |
game.logAction(user, `changed the hammer`); | |
}, | |
hammerhelp: [ | |
`/mafia hammer [hammer] - sets the hammer count to [hammer] and resets votes`, | |
`/mafia hammer off - disables hammering`, | |
`/mafia shifthammer [hammer] - sets the hammer count to [hammer] without resetting votes`, | |
`/mafia resethammer - sets the hammer to the default, resetting votes` | |
], | |
vl: "votelock", | |
votelock(target, room, user, connection, cmd) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
const action = toID(target); | |
if (this.meansYes(action)) { | |
game.setVotelock(user, true); | |
} else if (this.meansNo(action)) { | |
game.setVotelock(user, false); | |
} else { | |
return this.parse("/help mafia votelock"); | |
} | |
game.logAction(user, `changed votelock status`); | |
}, | |
votelockhelp: [ | |
`/mafia votelock [on|off] - Allows or disallows players to change their vote. Requires host % @ # ~` | |
], | |
voting: "votesall", | |
votesall(target, room, user, connection, cmd) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
const action = toID(target); | |
if (this.meansYes(action)) { | |
game.setVoting(user, true); | |
} else if (this.meansNo(action)) { | |
game.setVoting(user, false); | |
} else { | |
return this.parse("/help mafia voting"); | |
} | |
game.logAction(user, `changed voting status`); | |
}, | |
votinghelp: [ | |
`/mafia voting [on|off] - Allows or disallows players to vote. Requires host % @ # ~` | |
], | |
enablenv: "enablenl", | |
disablenv: "enablenl", | |
disablenl: "enablenl", | |
enablenl(target, room, user, connection, cmd) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
if (cmd === "enablenl" || cmd === "enablenv") { | |
game.setNoVote(user, true); | |
} else { | |
game.setNoVote(user, false); | |
} | |
game.logAction(user, `changed novote status`); | |
}, | |
enablenlhelp: [ | |
`/mafia [enablenv|disablenv] - Allows or disallows players abstain from voting. Requires host % @ # ~` | |
], | |
forcevote(target, room, user) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
target = toID(target); | |
if (this.meansYes(target)) { | |
if (game.forceVote) | |
return this.errorReply(`Forcevoting is already enabled.`); | |
game.forceVote = true; | |
if (game.started) | |
game.resetHammer(); | |
game.sendDeclare(`Forcevoting has been enabled. Your vote will start on yourself, and you cannot unvote!`); | |
} else if (this.meansNo(target)) { | |
if (!game.forceVote) | |
return this.errorReply(`Forcevoting is already disabled.`); | |
game.forceVote = false; | |
game.sendDeclare(`Forcevoting has been disabled. You can vote normally now!`); | |
} else { | |
this.parse("/help mafia forcevote"); | |
} | |
game.logAction(user, `changed forcevote status`); | |
}, | |
forcevotehelp: [ | |
`/mafia forcevote [yes/no] - Forces players' votes onto themselves, and prevents unvoting. Requires host % @ # ~` | |
], | |
votes(target, room, user) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (!game.started) | |
return this.errorReply(`The game of mafia has not started yet.`); | |
if (game.hostid === user.id || game.cohostids.includes(user.id)) { | |
this.broadcastMessage = this.message.toLowerCase().replace(/[^a-z0-9\s!,]/g, ""); | |
} | |
if (!this.runBroadcast()) | |
return false; | |
this.sendReplyBox(game.voteBox()); | |
}, | |
pl: "players", | |
players(target, room, user) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid === user.id || game.cohostids.includes(user.id)) { | |
this.broadcastMessage = this.message.toLowerCase().replace(/[^a-z0-9\s!,]/g, ""); | |
} | |
if (!this.runBroadcast()) | |
return false; | |
if (this.broadcasting) { | |
game.sendPlayerList(); | |
} else { | |
const players = game.getRemainingPlayers(); | |
this.sendReplyBox(`Players (${players.length}): ${players.map((p) => p.safeName).sort().join(", ")}`); | |
} | |
}, | |
originalrolelist: "rolelist", | |
orl: "rolelist", | |
rl: "rolelist", | |
rolelist(target, room, user, connection, cmd) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.closedSetup) | |
return this.errorReply(`You cannot show roles in a closed setup.`); | |
if (!this.runBroadcast()) | |
return false; | |
if (game.IDEA.data) { | |
const buf = `<details><summary>IDEA roles:</summary>${game.IDEA.data.roles.join(`<br />`)}</details>`; | |
return this.sendReplyBox(buf); | |
} | |
const showOrl = ["orl", "originalrolelist"].includes(cmd) || game.noReveal; | |
const roleString = import_lib.Utils.sortBy(showOrl ? game.originalRoles : game.roles, (role) => role.alignment).map((role) => role.safeName).join(", "); | |
this.sendReplyBox(`${showOrl ? `Original Rolelist: ` : `Rolelist: `}${roleString}`); | |
}, | |
playerroles(target, room, user) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) { | |
return this.errorReply(`Only the host can view roles.`); | |
} | |
if (!game.started) | |
return this.errorReply(`The game has not started.`); | |
this.sendReplyBox(game.players.map( | |
(p) => `${p.safeName}: ${p.role ? (p.role.alignment === "solo" ? "Solo " : "") + p.role.safeName : "No role"}` | |
).join("<br/>")); | |
}, | |
spectate: "view", | |
view(target, room, user, connection) { | |
room = this.requireRoom(); | |
this.requireGame(Mafia); | |
if (!this.runBroadcast()) | |
return; | |
if (this.broadcasting) { | |
return this.sendReplyBox(`<button name="joinRoom" value="view-mafia-${room.roomid}" class="button"><strong>Spectate the game</strong></button>`); | |
} | |
return this.parse(`/join view-mafia-${room.roomid}`); | |
}, | |
refreshvotes(target, room, user, connection) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
const votes = game.voteBoxFor(user.id); | |
user.send(`>view-mafia-${game.room.roomid} | |
|selectorhtml|#mafia-votes|` + votes); | |
}, | |
forcesub: "sub", | |
sub(target, room, user, connection, cmd) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
const args = target.split(","); | |
const action = toID(args.shift()); | |
const player = game.getPlayer(user.id); | |
switch (action) { | |
case "in": | |
if (player) { | |
if (!game.requestedSub.includes(user.id)) { | |
return this.errorReply(`You have not requested to be subbed out.`); | |
} | |
game.requestedSub.splice(game.requestedSub.indexOf(user.id), 1); | |
this.errorReply(`You have cancelled your request to sub out.`); | |
player.updateHtmlRoom(); | |
} else { | |
this.checkChat(null, room); | |
if (game.subs.includes(user.id)) | |
return this.errorReply(`You are already on the sub list.`); | |
if (game.played.includes(user.id)) | |
return this.errorReply(`You cannot sub back into the game.`); | |
game.canJoin(user, true); | |
game.subs.push(user.id); | |
game.nextSub(); | |
this.parse(`/join view-mafia-${room.roomid}`); | |
} | |
break; | |
case "out": | |
if (player) { | |
if (player.isEliminated()) { | |
return this.errorReply(`You cannot request to be subbed out once eliminated.`); | |
} | |
if (game.requestedSub.includes(user.id)) { | |
return this.errorReply(`You have already requested to be subbed out.`); | |
} | |
game.requestedSub.push(user.id); | |
player.updateHtmlRoom(); | |
game.nextSub(); | |
} else { | |
if (game.hostid === user.id || game.cohostids.includes(user.id)) { | |
return this.errorReply(`The host cannot sub out of the game.`); | |
} | |
if (!game.subs.includes(user.id)) | |
return this.errorReply(`You are not on the sub list.`); | |
game.subs.splice(game.subs.indexOf(user.id), 1); | |
this.parse(`/join view-mafia-${room.roomid}`); | |
} | |
break; | |
case "next": | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
const toSub = args.shift(); | |
if (!game.getPlayer(toID(toSub))) | |
return this.errorReply(`${toSub} is not in the game.`); | |
if (!game.subs.length) { | |
if (game.hostRequestedSub.includes(toID(toSub))) { | |
return this.errorReply(`${toSub} is already on the list to be subbed out.`); | |
} | |
user.sendTo( | |
room, | |
`|error|There are no subs to replace ${toSub}, they will be subbed if a sub is available before they speak next.` | |
); | |
game.hostRequestedSub.unshift(toID(toSub)); | |
} else { | |
game.nextSub(toID(toSub)); | |
} | |
game.logAction(user, `requested a sub for a player`); | |
break; | |
case "remove": | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
for (const toRemove of args) { | |
const toRemoveIndex = game.subs.indexOf(toID(toRemove)); | |
if (toRemoveIndex === -1) { | |
user.sendTo(room, `|error|${toRemove} is not on the sub list.`); | |
continue; | |
} | |
game.subs.splice(toRemoveIndex, 1); | |
user.sendTo(room, `${toRemove} has been removed from the sublist`); | |
game.logAction(user, `removed a player from the sublist`); | |
} | |
break; | |
case "unrequest": | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
const toUnrequest = toID(args.shift()); | |
const userIndex = game.requestedSub.indexOf(toUnrequest); | |
const hostIndex = game.hostRequestedSub.indexOf(toUnrequest); | |
if (userIndex < 0 && hostIndex < 0) | |
return user.sendTo(room, `|error|${toUnrequest} is not requesting a sub.`); | |
if (userIndex > -1) { | |
game.requestedSub.splice(userIndex, 1); | |
user.sendTo(room, `${toUnrequest}'s sub request has been removed.`); | |
} | |
if (hostIndex > -1) { | |
game.hostRequestedSub.splice(userIndex, 1); | |
user.sendTo(room, `${toUnrequest} has been removed from the host sublist.`); | |
} | |
break; | |
default: | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
const toSubOut = game.getPlayer(action); | |
const toSubIn = toID(args.shift()); | |
if (!toSubOut) | |
return this.errorReply(`${toSubOut} is not in the game.`); | |
const targetUser = Users.get(toSubIn); | |
if (!targetUser) | |
return this.errorReply(`The user "${toSubIn}" was not found.`); | |
game.canJoin(targetUser, false, cmd === "forcesub"); | |
if (game.subs.includes(targetUser.id)) { | |
game.subs.splice(game.subs.indexOf(targetUser.id), 1); | |
} | |
if (game.hostRequestedSub.includes(toSubOut.id)) { | |
game.hostRequestedSub.splice(game.hostRequestedSub.indexOf(toSubOut.id), 1); | |
} | |
if (game.requestedSub.includes(toSubOut.id)) { | |
game.requestedSub.splice(game.requestedSub.indexOf(toSubOut.id), 1); | |
} | |
game.sub(toSubOut, targetUser); | |
game.logAction(user, `substituted a player`); | |
} | |
}, | |
subhelp: [ | |
`/mafia sub in - Request to sub into the game, or cancel a request to sub out.`, | |
`/mafia sub out - Request to sub out of the game, or cancel a request to sub in.`, | |
`/mafia sub next, [player] - Forcibly sub [player] out of the game. Requires host % @ # ~`, | |
`/mafia sub remove, [user] - Remove [user] from the sublist. Requres host % @ # ~`, | |
`/mafia sub unrequest, [player] - Remove's a player's request to sub out of the game. Requires host % @ # ~`, | |
`/mafia sub [player], [user] - Forcibly sub [player] for [user]. Requires host % @ # ~` | |
], | |
autosub(target, room, user) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("mute", null, room); | |
if (this.meansYes(toID(target))) { | |
if (game.autoSub) | |
return this.errorReply(`Automatic subbing of players is already enabled.`); | |
game.autoSub = true; | |
user.sendTo(room, `Automatic subbing of players has been enabled.`); | |
game.nextSub(); | |
} else if (this.meansNo(toID(target))) { | |
if (!game.autoSub) | |
return this.errorReply(`Automatic subbing of players is already disabled.`); | |
game.autoSub = false; | |
user.sendTo(room, `Automatic subbing of players has been disabled.`); | |
} else { | |
return this.parse(`/help mafia autosub`); | |
} | |
game.logAction(user, `changed autosub status`); | |
}, | |
autosubhelp: [ | |
`/mafia autosub [yes|no] - Sets if players will automatically sub out if a user is on the sublist. Requires host % @ # ~` | |
], | |
cohost: "subhost", | |
subhost(target, room, user, connection, cmd) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
this.checkChat(); | |
if (!target) | |
return this.parse(`/help mafia ${cmd}`); | |
this.checkCan("mute", null, room); | |
const { targetUser } = this.requireUser(target); | |
if (!room.users[targetUser.id]) | |
return this.errorReply(`${targetUser.name} is not in this room, and cannot be hosted.`); | |
if (game.hostid === targetUser.id) | |
return this.errorReply(`${targetUser.name} is already the host.`); | |
if (game.cohostids.includes(targetUser.id)) | |
return this.errorReply(`${targetUser.name} is already a cohost.`); | |
if (game.getPlayer(targetUser.id)) { | |
return this.errorReply(`${targetUser.name} cannot become a host because they are playing.`); | |
} | |
if (game.subs.includes(targetUser.id)) | |
game.subs.splice(game.subs.indexOf(targetUser.id), 1); | |
if (cmd.includes("cohost")) { | |
game.cohostids.push(targetUser.id); | |
game.cohosts.push(import_lib.Utils.escapeHTML(targetUser.name)); | |
game.sendDeclare(import_lib.Utils.html`${targetUser.name} has been added as a cohost by ${user.name}`); | |
for (const conn of targetUser.connections) { | |
void Chat.resolvePage(`view-mafia-${room.roomid}`, targetUser, conn); | |
} | |
this.modlog("MAFIACOHOST", targetUser, null, { noalts: true, noip: true }); | |
} else { | |
const oldHostid = game.hostid; | |
const oldHost = Users.get(game.hostid); | |
if (oldHost) | |
oldHost.send(`>view-mafia-${room.roomid} | |
|deinit`); | |
const queueIndex = hostQueue.indexOf(targetUser.id); | |
if (queueIndex > -1) | |
hostQueue.splice(queueIndex, 1); | |
game.host = import_lib.Utils.escapeHTML(targetUser.name); | |
game.hostid = targetUser.id; | |
game.played.push(targetUser.id); | |
for (const conn of targetUser.connections) { | |
void Chat.resolvePage(`view-mafia-${room.roomid}`, targetUser, conn); | |
} | |
game.sendDeclare(import_lib.Utils.html`${targetUser.name} has been substituted as the new host, replacing ${oldHostid}.`); | |
this.modlog("MAFIASUBHOST", targetUser, `replacing ${oldHostid}`, { noalts: true, noip: true }); | |
} | |
}, | |
subhosthelp: [`/mafia subhost [user] - Substitues the user as the new game host.`], | |
cohosthelp: [ | |
`/mafia cohost [user] - Adds the user as a cohost. Cohosts can talk during the game, as well as perform host actions.` | |
], | |
uncohost: "removecohost", | |
removecohost(target, room, user) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
this.checkChat(); | |
if (!target) | |
return this.parse("/help mafia subhost"); | |
this.checkCan("mute", null, room); | |
const targetID = toID(target); | |
const cohostIndex = game.cohostids.indexOf(targetID); | |
if (cohostIndex < 0) { | |
if (game.hostid === targetID) { | |
return this.errorReply(`${target} is the host, not a cohost. Use /mafia subhost to replace them.`); | |
} | |
return this.errorReply(`${target} is not a cohost.`); | |
} | |
game.cohostids.splice(cohostIndex, 1); | |
game.sendDeclare(import_lib.Utils.html`${target} was removed as a cohost by ${user.name}`); | |
this.modlog("MAFIAUNCOHOST", target, null, { noalts: true, noip: true }); | |
}, | |
end(target, room, user) { | |
room = this.requireRoom(); | |
const game = this.requireGame(Mafia); | |
if (game.hostid !== user.id && !game.cohostids.includes(user.id)) | |
this.checkCan("show", null, room); | |
game.end(); | |
this.modlog("MAFIAEND", null); | |
}, | |
endhelp: [`/mafia end - End the current game of mafia. Requires host + % @ # ~`], | |
role: "data", | |
alignment: "data", | |
theme: "data", | |
term: "data", | |
dt: "data", | |
data(target, room, user, connection, cmd) { | |
if (room?.settings.mafiaDisabled) | |
return this.errorReply(`Mafia is disabled for this room.`); | |
if (cmd === "role" && !target && room) { | |
const game2 = room.getGame(Mafia); | |
if (!game2) { | |
return this.errorReply(`There is no game of mafia running in this room. If you meant to display information about a role, use /mafia role [role name]`); | |
} | |
const player = game2.getPlayer(user.id); | |
if (!player) | |
return this.errorReply(`You are not in the game of ${game2.title}.`); | |
const role = player.role; | |
if (!role) | |
return this.errorReply(`You do not have a role yet.`); | |
return this.sendReplyBox(`Your role is: ${role.safeName}`); | |
} | |
const game = room?.getGame(Mafia); | |
if (game && (game.hostid === user.id || game.cohostids.includes(user.id))) { | |
this.broadcastMessage = this.message.toLowerCase().replace(/[^a-z0-9\s!,]/g, ""); | |
} | |
if (!this.runBroadcast()) | |
return false; | |
if (!target) | |
return this.parse(`/help mafia data`); | |
target = toID(target); | |
if (target in MafiaData.aliases) | |
target = MafiaData.aliases[target]; | |
let result = null; | |
let dataType = cmd; | |
const cmdTypes = { | |
role: "roles", | |
alignment: "alignments", | |
theme: "themes", | |
term: "terms", | |
idea: "IDEAs" | |
}; | |
if (cmd in cmdTypes) { | |
const toSearch = MafiaData[cmdTypes[cmd]]; | |
result = toSearch[target]; | |
} else { | |
for (const [cmdType, dataKey] of Object.entries(cmdTypes)) { | |
if (target in MafiaData[dataKey]) { | |
result = MafiaData[dataKey][target]; | |
dataType = cmdType; | |
break; | |
} | |
} | |
} | |
if (!result) | |
return this.errorReply(`"${target}" is not a valid mafia alignment, role, theme, or IDEA.`); | |
let buf = `<h3${result.color ? ` style="color: ${result.color}"` : ``}>${result.name}</h3><b>Type</b>: ${dataType}<br/>`; | |
if (dataType === "theme") { | |
if (result.desc) { | |
buf += `<b>Description</b>: ${result.desc}<br/><details><summary class="button" style="font-weight: bold; display: inline-block">Setups:</summary>`; | |
} | |
for (const i in result) { | |
const num = parseInt(i); | |
if (isNaN(num)) | |
continue; | |
buf += `${i}: `; | |
const count = {}; | |
const roles = []; | |
for (const role of result[num].split(",").map((x) => x.trim())) { | |
count[role] = count[role] ? count[role] + 1 : 1; | |
} | |
for (const role in count) { | |
roles.push(count[role] > 1 ? `${count[role]}x ${role}` : role); | |
} | |
buf += `${roles.join(", ")}<br/>`; | |
} | |
} else if (dataType === "idea") { | |
if (result.picks && result.choices) { | |
buf += `<b>Number of Picks</b>: ${result.picks.length} (${result.picks.join(", ")})<br/>`; | |
buf += `<b>Number of Choices</b>: ${result.choices}<br/>`; | |
} | |
buf += `<details><summary class="button" style="font-weight: bold; display: inline-block">Roles:</summary>`; | |
for (const idearole of result.roles) { | |
buf += `${idearole}<br/>`; | |
} | |
} else { | |
if (result.memo) | |
buf += `${result.memo.join("<br/>")}`; | |
} | |
return this.sendReplyBox(buf); | |
}, | |
datahelp: [ | |
`/mafia data [alignment|role|modifier|theme|term] - Get information on a mafia alignment, role, modifier, theme, or term.` | |
], | |
winfaction: "win", | |
unwinfaction: "win", | |
unwin: "win", | |
win(target, room, user, connection, cmd) { | |
const isUnwin = cmd.startsWith("unwin"); | |
room = this.requireRoom("mafia"); | |
if (!room || room.settings.mafiaDisabled) | |
return this.errorReply(`Mafia is disabled for this room.`); | |
this.checkCan("mute", null, room); | |
const args = target.split(","); | |
let points = parseInt(args[0]); | |
if (isUnwin) { | |
points *= -1; | |
} | |
if (isNaN(points)) { | |
points = 10; | |
if (isUnwin) { | |
points *= -1; | |
} | |
} else { | |
if (points > 100 || points < -100) { | |
return this.errorReply(`You cannot give or take more than 100 points at a time.`); | |
} | |
args.shift(); | |
} | |
if (!args.length) | |
return this.parse("/help mafia win"); | |
const month = new Date().toLocaleString("en-us", { month: "numeric", year: "numeric" }); | |
if (!logs.leaderboard[month]) | |
logs.leaderboard[month] = {}; | |
let toGiveTo = []; | |
let buf = `${points < 0 ? points * -1 : points} point${Chat.plural(points, "s were", " was")} ${points <= 0 ? "taken from " : "awarded to "} `; | |
if (cmd === "winfaction" || cmd === "unwinfaction") { | |
const game = this.requireGame(Mafia); | |
for (let faction of args) { | |
faction = toID(faction); | |
const inFaction = []; | |
for (const player of game.players) { | |
if (player.role && toID(player.role.alignment) === faction) { | |
toGiveTo.push(player.id); | |
inFaction.push(player.id); | |
} | |
} | |
if (inFaction.length) | |
buf += ` the ${faction} faction: ${inFaction.join(", ")};`; | |
} | |
} else { | |
toGiveTo = args; | |
buf += toGiveTo.join(", "); | |
} | |
if (!toGiveTo.length) | |
return this.parse("/help mafia win"); | |
let gavePoints = false; | |
for (let u of toGiveTo) { | |
u = toID(u); | |
if (!u) | |
continue; | |
if (!gavePoints) | |
gavePoints = true; | |
if (!logs.leaderboard[month][u]) | |
logs.leaderboard[month][u] = 0; | |
logs.leaderboard[month][u] += points; | |
if (logs.leaderboard[month][u] === 0) | |
delete logs.leaderboard[month][u]; | |
} | |
if (!gavePoints) | |
return this.parse("/help mafia win"); | |
writeFile(LOGS_FILE, logs); | |
this.modlog(`MAFIAPOINTS`, null, `${points < 0 ? points * -1 : points} points were ${points < 0 ? "taken from" : "awarded to"} ${Chat.toListString(toGiveTo)}`); | |
room.add(buf).update(); | |
}, | |
winhelp: [ | |
`/mafia (un)win (points), [user1], [user2], [user3], ... - Award the specified users points to the mafia leaderboard for this month. The amount of points can be negative to take points. Defaults to 10 points.`, | |
`/mafia (un)winfaction (points), [faction] - Award the specified points to all the players in the given faction.` | |
], | |
unmvp: "mvp", | |
mvp(target, room, user, connection, cmd) { | |
room = this.requireRoom("mafia"); | |
if (!room || room.settings.mafiaDisabled) | |
return this.errorReply(`Mafia is disabled for this room.`); | |
this.checkCan("mute", null, room); | |
const args = target.split(","); | |
if (!args.length) | |
return this.parse("/help mafia mvp"); | |
const month = new Date().toLocaleString("en-us", { month: "numeric", year: "numeric" }); | |
if (!logs.mvps[month]) | |
logs.mvps[month] = {}; | |
if (!logs.leaderboard[month]) | |
logs.leaderboard[month] = {}; | |
let gavePoints = false; | |
for (let u of args) { | |
u = toID(u); | |
if (!u) | |
continue; | |
if (!gavePoints) | |
gavePoints = true; | |
if (!logs.leaderboard[month][u]) | |
logs.leaderboard[month][u] = 0; | |
if (!logs.mvps[month][u]) | |
logs.mvps[month][u] = 0; | |
if (cmd === "unmvp") { | |
logs.mvps[month][u]--; | |
logs.leaderboard[month][u] -= 10; | |
if (logs.mvps[month][u] === 0) | |
delete logs.mvps[month][u]; | |
if (logs.leaderboard[month][u] === 0) | |
delete logs.leaderboard[month][u]; | |
} else { | |
logs.mvps[month][u]++; | |
logs.leaderboard[month][u] += 10; | |
} | |
} | |
if (!gavePoints) | |
return this.parse("/help mafia mvp"); | |
writeFile(LOGS_FILE, logs); | |
this.modlog(`MAFIA${cmd.toUpperCase()}`, null, `MVP and 10 points were ${cmd === "unmvp" ? "taken from" : "awarded to"} ${Chat.toListString(args)}`); | |
room.add(`MVP and 10 points were ${cmd === "unmvp" ? "taken from" : "awarded to"}: ${Chat.toListString(args)}`).update(); | |
}, | |
mvphelp: [ | |
`/mafia mvp [user1], [user2], ... - Gives a MVP point and 10 leaderboard points to the users specified.`, | |
`/mafia unmvp [user1], [user2], ... - Takes away a MVP point and 10 leaderboard points from the users specified.` | |
], | |
hostlogs: "leaderboard", | |
playlogs: "leaderboard", | |
leaverlogs: "leaderboard", | |
mvpladder: "leaderboard", | |
lb: "leaderboard", | |
leaderboard(target, room, user, connection, cmd) { | |
room = this.requireRoom("mafia"); | |
if (!room || room.settings.mafiaDisabled) | |
return this.errorReply(`Mafia is disabled for this room.`); | |
if (["hostlogs", "playlogs", "leaverlogs"].includes(cmd)) { | |
this.checkCan("mute", null, room); | |
} else { | |
if (!this.runBroadcast()) | |
return; | |
} | |
if (cmd === "lb") | |
cmd = "leaderboard"; | |
if (this.broadcasting) { | |
return this.sendReplyBox(`<button name="joinRoom" value="view-mafialadder-${cmd}" class="button"><strong>${cmd}</strong></button>`); | |
} | |
return this.parse(`/join view-mafialadder-${cmd}`); | |
}, | |
leaderboardhelp: [ | |
`/mafia [leaderboard|mvpladder] - View the leaderboard or MVP ladder for the current or last month.`, | |
`/mafia [hostlogs|playlogs|leaverlogs] - View the host, play, or leaver logs for the current or last month. Requires % @ # ~` | |
], | |
gameban: "hostban", | |
hostban(target, room, user, connection, cmd) { | |
if (!target) | |
return this.parse("/help mafia hostban"); | |
room = this.requireRoom(); | |
this.checkCan("warn", null, room); | |
const { targetUser, rest } = this.requireUser(target); | |
const [string1, string2] = this.splitOne(rest); | |
let duration, reason; | |
if (parseInt(string1)) { | |
duration = parseInt(string1); | |
reason = string2; | |
} else { | |
duration = parseInt(string2); | |
reason = string1; | |
} | |
if (!duration) | |
duration = 2; | |
if (!reason) | |
reason = ""; | |
if (reason.length > 300) { | |
return this.errorReply("The reason is too long. It cannot exceed 300 characters."); | |
} | |
const userid = toID(targetUser); | |
if (Punishments.hasRoomPunishType(room, userid, `MAFIA${this.cmd.toUpperCase()}`)) { | |
return this.errorReply(`User '${targetUser.name}' is already ${this.cmd}ned in this room.`); | |
} else if (Punishments.hasRoomPunishType(room, userid, `MAFIAGAMEBAN`)) { | |
return this.errorReply(`User '${targetUser.name}' is already gamebanned in this room, which also means they can't host.`); | |
} else if (Punishments.hasRoomPunishType(room, userid, `MAFIAHOSTBAN`)) { | |
user.sendTo(room, `User '${targetUser.name}' is already hostbanned in this room, but they will now be gamebanned.`); | |
this.parse(`/mafia unhostban ${targetUser.name}`); | |
} | |
if (cmd === "hostban") { | |
Mafia.hostBan(room, targetUser, reason, duration); | |
} else { | |
Mafia.gameBan(room, targetUser, reason, duration); | |
} | |
this.modlog(`MAFIA${cmd.toUpperCase()}`, targetUser, reason); | |
this.privateModAction(`${targetUser.name} was banned from ${cmd === "hostban" ? "hosting" : "playing"} mafia games by ${user.name}.`); | |
}, | |
hostbanhelp: [ | |
`/mafia (un)hostban [user], [reason], [duration] - Ban a user from hosting games for [duration] days. Requires % @ # ~`, | |
`/mafia (un)gameban [user], [reason], [duration] - Ban a user from playing games for [duration] days. Requires % @ # ~` | |
], | |
ban: "gamebanhelp", | |
banhelp: "gamebanhelp", | |
gamebanhelp() { | |
this.parse("/mafia hostbanhelp"); | |
}, | |
ungameban: "unhostban", | |
unhostban(target, room, user, connection, cmd) { | |
if (!target) | |
return this.parse("/help mafia hostban"); | |
room = this.requireRoom(); | |
this.checkCan("warn", null, room); | |
const { targetUser } = this.requireUser(target, { allowOffline: true }); | |
if (!Mafia.isGameBanned(room, targetUser) && cmd === "ungameban") { | |
return this.errorReply(`User '${targetUser.name}' isn't banned from playing mafia games.`); | |
} else if (!Mafia.isHostBanned(room, targetUser) && cmd === "unhostban") { | |
return this.errorReply(`User '${targetUser.name}' isn't banned from hosting mafia games.`); | |
} | |
if (cmd === "unhostban") | |
Mafia.unhostBan(room, targetUser); | |
else | |
Mafia.ungameBan(room, targetUser); | |
this.privateModAction(`${targetUser.name} was unbanned from ${cmd === "unhostban" ? "hosting" : "playing"} mafia games by ${user.name}.`); | |
this.modlog(`MAFIA${cmd.toUpperCase()}`, targetUser, null, { noip: 1, noalts: 1 }); | |
}, | |
overwriterole: "addrole", | |
addrole(target, room, user, connection, cmd) { | |
room = this.requireRoom("mafia"); | |
this.checkCan("mute", null, room); | |
const overwrite = cmd === "overwriterole"; | |
const [name, alignment, image, ...memo] = target.split("|").map((e) => e.trim()); | |
const id = toID(name); | |
if (!id || !memo.length) | |
return this.parse(`/help mafia addrole`); | |
if (alignment && !(alignment in MafiaData.alignments)) | |
return this.errorReply(`${alignment} is not a valid alignment.`); | |
if (image && !VALID_IMAGES.includes(image)) | |
return this.errorReply(`${image} is not a valid image.`); | |
if (!overwrite && id in MafiaData.roles) { | |
return this.errorReply(`${name} is already a role. Use /mafia overwriterole to overwrite.`); | |
} | |
if (id in MafiaData.alignments) | |
return this.errorReply(`${name} is already an alignment.`); | |
if (id in MafiaData.aliases) { | |
return this.errorReply(`${name} is already an alias (pointing to ${MafiaData.aliases[id]}).`); | |
} | |
const role = { name, memo }; | |
if (alignment) | |
role.alignment = alignment; | |
if (image) | |
role.image = image; | |
MafiaData.roles[id] = role; | |
writeFile(DATA_FILE, MafiaData); | |
this.modlog(`MAFIAADDROLE`, null, id, { noalts: true, noip: true }); | |
this.sendReply(`The role ${id} was added to the database.`); | |
}, | |
addrolehelp: [ | |
`/mafia addrole name|alignment|image|memo1|memo2... - adds a role to the database. Name, memo are required. Requires % @ # ~` | |
], | |
overwritealignment: "addalignment", | |
addalignment(target, room, user, connection, cmd) { | |
room = this.requireRoom("mafia"); | |
this.checkCan("mute", null, room); | |
const overwrite = cmd === "overwritealignment"; | |
const [name, plural, color, buttonColor, image, ...memo] = target.split("|").map((e) => e.trim()); | |
const id = toID(name); | |
if (!id || !plural || !memo.length) | |
return this.parse(`/help mafia addalignment`); | |
if (image && !VALID_IMAGES.includes(image)) | |
return this.errorReply(`${image} is not a valid image.`); | |
if (!overwrite && id in MafiaData.alignments) { | |
return this.errorReply(`${name} is already an alignment. Use /mafia overwritealignment to overwrite.`); | |
} | |
if (id in MafiaData.roles) | |
return this.errorReply(`${name} is already a role.`); | |
if (id in MafiaData.aliases) { | |
return this.errorReply(`${name} is already an alias (pointing to ${MafiaData.aliases[id]})`); | |
} | |
const alignment = { name, plural, memo }; | |
if (color) | |
alignment.color = color; | |
if (buttonColor) | |
alignment.buttonColor = buttonColor; | |
if (image) | |
alignment.image = image; | |
MafiaData.alignments[id] = alignment; | |
writeFile(DATA_FILE, MafiaData); | |
this.modlog(`MAFIAADDALIGNMENT`, null, id, { noalts: true, noip: true }); | |
this.sendReply(`The alignment ${id} was added to the database.`); | |
}, | |
addalignmenthelp: [ | |
`/mafia addalignment name|plural|color|button color|image|memo1|memo2... - adds a memo to the database. Name, plural, memo are required. Requires % @ # ~` | |
], | |
overwritetheme: "addtheme", | |
addtheme(target, room, user, connection, cmd) { | |
room = this.requireRoom("mafia"); | |
this.checkCan("mute", null, room); | |
const overwrite = cmd === "overwritetheme"; | |
const [name, desc, ...rolelists] = target.split("|").map((e) => e.trim()); | |
const id = toID(name); | |
if (!id || !desc || !rolelists.length) | |
return this.parse(`/help mafia addtheme`); | |
if (!overwrite && id in MafiaData.themes) { | |
return this.errorReply(`${name} is already a theme. Use /mafia overwritetheme to overwrite.`); | |
} | |
if (id in MafiaData.IDEAs) | |
return this.errorReply(`${name} is already an IDEA.`); | |
if (id in MafiaData.aliases) { | |
return this.errorReply(`${name} is already an alias (pointing to ${MafiaData.aliases[id]})`); | |
} | |
const rolelistsMap = {}; | |
const uniqueRoles = /* @__PURE__ */ new Set(); | |
for (const rolelist of rolelists) { | |
const [players, roles] = import_lib.Utils.splitFirst(rolelist, ":", 2).map((e) => e.trim()); | |
const playersNum = parseInt(players); | |
for (const role of roles.split(",")) { | |
uniqueRoles.add(role.trim()); | |
} | |
rolelistsMap[playersNum] = roles; | |
} | |
const problems = []; | |
for (const role of uniqueRoles) { | |
const parsedRole = Mafia.parseRole(role); | |
if (parsedRole.problems.length) | |
problems.push(...parsedRole.problems); | |
} | |
if (problems.length) | |
return this.errorReply(`Problems found when parsing roles: | |
${problems.join("\n")}`); | |
const theme = { name, desc, ...rolelistsMap }; | |
MafiaData.themes[id] = theme; | |
writeFile(DATA_FILE, MafiaData); | |
this.modlog(`MAFIAADDTHEME`, null, id, { noalts: true, noip: true }); | |
this.sendReply(`The theme ${id} was added to the database.`); | |
}, | |
addthemehelp: [ | |
`/mafia addtheme name|description|players:rolelist|players:rolelist... - adds a theme to the database. Requires % @ # ~` | |
], | |
overwriteidea: "addidea", | |
addidea(target, room, user, connection, cmd) { | |
room = this.requireRoom("mafia"); | |
this.checkCan("mute", null, room); | |
const overwrite = cmd === "overwriteidea"; | |
let [meta, ...roles] = target.split("\n"); | |
roles = roles.map((e) => e.trim()); | |
if (!meta || !roles.length) | |
return this.parse(`/help mafia addidea`); | |
const [name, choicesStr, ...picks] = meta.split("|"); | |
const id = toID(name); | |
const choices = parseInt(choicesStr); | |
if (!id || !choices || !picks.length) | |
return this.parse(`/help mafia addidea`); | |
if (choices <= picks.length) | |
return this.errorReply(`You need to have more choices than picks.`); | |
if (!overwrite && id in MafiaData.IDEAs) { | |
return this.errorReply(`${name} is already an IDEA. Use /mafia overwriteidea to overwrite.`); | |
} | |
if (id in MafiaData.themes) | |
return this.errorReply(`${name} is already a theme.`); | |
if (id in MafiaData.aliases) { | |
return this.errorReply(`${name} is already an alias (pointing to ${MafiaData.aliases[id]})`); | |
} | |
const checkedRoles = []; | |
const problems = []; | |
for (const role of roles) { | |
if (checkedRoles.includes(role)) | |
continue; | |
const parsedRole = Mafia.parseRole(role); | |
if (parsedRole.problems.length) | |
problems.push(...parsedRole.problems); | |
checkedRoles.push(role); | |
} | |
if (problems.length) | |
return this.errorReply(`Problems found when parsing roles: | |
${problems.join("\n")}`); | |
const IDEA = { name, choices, picks, roles }; | |
MafiaData.IDEAs[id] = IDEA; | |
writeFile(DATA_FILE, MafiaData); | |
this.modlog(`MAFIAADDIDEA`, null, id, { noalts: true, noip: true }); | |
this.sendReply(`The IDEA ${id} was added to the database.`); | |
}, | |
addideahelp: [ | |
`/mafia addidea name|choices (number)|pick1|pick2... (new line here)`, | |
`(newline separated rolelist) - Adds an IDEA to the database. Requires % @ # ~` | |
], | |
overwriteterm: "addterm", | |
addterm(target, room, user, connection, cmd) { | |
room = this.requireRoom("mafia"); | |
this.checkCan("mute", null, room); | |
const overwrite = cmd === "overwriteterm"; | |
const [name, ...memo] = target.split("|").map((e) => e.trim()); | |
const id = toID(name); | |
if (!id || !memo.length) | |
return this.parse(`/help mafia addterm`); | |
if (!overwrite && id in MafiaData.terms) { | |
return this.errorReply(`${name} is already a term. Use /mafia overwriteterm to overwrite.`); | |
} | |
if (id in MafiaData.aliases) { | |
return this.errorReply(`${name} is already an alias (pointing to ${MafiaData.aliases[id]})`); | |
} | |
const term = { name, memo }; | |
MafiaData.terms[id] = term; | |
writeFile(DATA_FILE, MafiaData); | |
this.modlog(`MAFIAADDTERM`, null, id, { noalts: true, noip: true }); | |
this.sendReply(`The term ${id} was added to the database.`); | |
}, | |
addtermhelp: [`/mafia addterm name|memo1|memo2... - Adds a term to the database. Requires % @ # ~`], | |
overwritealias: "addalias", | |
addalias(target, room, user, connection, cmd) { | |
room = this.requireRoom("mafia"); | |
this.checkCan("mute", null, room); | |
const [from, to] = target.split(",").map(toID); | |
if (!from || !to) | |
return this.parse(`/help mafia addalias`); | |
if (from in MafiaData.aliases) { | |
return this.errorReply(`${from} is already an alias (pointing to ${MafiaData.aliases[from]})`); | |
} | |
let foundTarget = false; | |
for (const entry of ["alignments", "roles", "themes", "IDEAs", "terms"]) { | |
const dataEntry = MafiaData[entry]; | |
if (from in dataEntry) | |
return this.errorReply(`${from} is already a ${entry.slice(0, -1)}`); | |
if (to in dataEntry) | |
foundTarget = true; | |
} | |
if (!foundTarget) | |
return this.errorReply(`No database entry exists with the key ${to}.`); | |
MafiaData.aliases[from] = to; | |
writeFile(DATA_FILE, MafiaData); | |
this.modlog(`MAFIAADDALIAS`, null, `${from}: ${to}`, { noalts: true, noip: true }); | |
this.sendReply(`The alias ${from} was added, pointing to ${to}.`); | |
}, | |
addaliashelp: [ | |
`/mafia addalias from,to - Adds an alias to the database, redirecting (from) to (to). Requires % @ # ~` | |
], | |
deletedata(target, room, user) { | |
room = this.requireRoom("mafia"); | |
this.checkCan("mute", null, room); | |
let [source, entry] = target.split(","); | |
entry = toID(entry); | |
if (!(source in MafiaData)) { | |
return this.errorReply(`Invalid source. Valid sources are ${Object.keys(MafiaData).join(", ")}`); | |
} | |
const dataSource = MafiaData[source]; | |
if (!(entry in dataSource)) | |
return this.errorReply(`${entry} does not exist in ${source}.`); | |
let buf = ""; | |
if (dataSource === MafiaData.alignments) { | |
if (entry === "solo" || entry === "town") | |
return this.errorReply(`You cannot delete the solo or town alignments.`); | |
for (const key in MafiaData.roles) { | |
if (MafiaData.roles[key].alignment === entry) { | |
buf += `Removed alignment of role ${key}.`; | |
delete MafiaData.roles[key].alignment; | |
} | |
} | |
} | |
if (dataSource !== MafiaData.aliases) { | |
for (const key in MafiaData.aliases) { | |
if (MafiaData.aliases[key] === entry) { | |
buf += `Removed alias ${key}`; | |
delete MafiaData.aliases[key]; | |
} | |
} | |
} | |
delete dataSource[entry]; | |
writeFile(DATA_FILE, MafiaData); | |
if (buf) | |
this.sendReply(buf); | |
this.modlog(`MAFIADELETEDATA`, null, `${entry} from ${source}`, { noalts: true, noip: true }); | |
this.sendReply(`The entry ${entry} was deleted from the ${source} database.`); | |
}, | |
deletedatahelp: [`/mafia deletedata source,entry - Removes an entry from the database. Requires % @ # ~`], | |
listdata(target, room, user) { | |
if (!(target in MafiaData)) { | |
return this.errorReply(`Invalid source. Valid sources are ${Object.keys(MafiaData).join(", ")}`); | |
} | |
const dataSource = MafiaData[target]; | |
if (dataSource === MafiaData.aliases) { | |
const aliases = Object.entries(MafiaData.aliases).map(([from, to]) => `${from}: ${to}`).join("<br/>"); | |
return this.sendReplyBox(`Mafia aliases:<br/>${aliases}`); | |
} else { | |
const entries = Object.entries(dataSource).map(([key, data]) => `<button class="button" name="send" value="/mafia dt ${key}">${data.name}</button>`).join(""); | |
return this.sendReplyBox(`Mafia ${target}:<br/>${entries}`); | |
} | |
}, | |
disable(target, room, user) { | |
room = this.requireRoom(); | |
this.checkCan("gamemanagement", null, room); | |
if (room.settings.mafiaDisabled) { | |
return this.errorReply("Mafia is already disabled."); | |
} | |
room.settings.mafiaDisabled = true; | |
room.saveSettings(); | |
this.modlog("MAFIADISABLE", null); | |
return this.sendReply("Mafia has been disabled for this room."); | |
}, | |
disablehelp: [`/mafia disable - Disables mafia in this room. Requires # ~`], | |
enable(target, room, user) { | |
room = this.requireRoom(); | |
this.checkCan("gamemanagement", null, room); | |
if (!room.settings.mafiaDisabled) { | |
return this.errorReply("Mafia is already enabled."); | |
} | |
room.settings.mafiaDisabled = false; | |
room.saveSettings(); | |
this.modlog("MAFIAENABLE", null); | |
return this.sendReply("Mafia has been enabled for this room."); | |
}, | |
enablehelp: [`/mafia enable - Enables mafia in this room. Requires # ~`] | |
}, | |
mafiahelp(target, room, user) { | |
if (!this.runBroadcast()) | |
return; | |
let buf = `<strong>Commands for the Mafia Plugin</strong><br/>Most commands are used through buttons in the game screen.<br/><br/>`; | |
buf += `<details><summary class="button">General Commands</summary>`; | |
buf += [ | |
`<br/><strong>General Commands for the Mafia Plugin</strong>:<br/>`, | |
`/mafia host [user] - Create a game of Mafia with [user] as the host. Roomvoices can only host themselves. Requires + % @ # ~`, | |
`/mafia nexthost - Host the next user in the host queue. Only works in the Mafia Room. Requires + % @ # ~`, | |
`/mafia forcehost [user] - Bypass the host queue and host [user]. Only works in the Mafia Room. Requires % @ # ~`, | |
`/mafia sub [in|out] - Request to sub into the game, or cancel a request to sub out.`, | |
`/mafia spectate - Spectate the game of mafia.`, | |
`/mafia votes - Display the current vote count, and who's voting who.`, | |
`/mafia players - Display the current list of players, will highlight players.`, | |
`/mafia [rl|orl] - Display the role list or the original role list for the current game.`, | |
`/mafia data [alignment|role|modifier|theme|term] - Get information on a mafia alignment, role, modifier, theme, or term.`, | |
`/mafia subhost [user] - Substitues the user as the new game host. Requires % @ # ~`, | |
`/mafia (un)cohost [user] - Adds/removes the user as a cohost. Cohosts can talk during the game, as well as perform host actions. Requires % @ # ~`, | |
`/mafia [enable|disable] - Enables/disables mafia in this room. Requires # ~` | |
].join("<br/>"); | |
buf += `</details><details><summary class="button">Player Commands</summary>`; | |
buf += [ | |
`<br/><strong>Commands that players can use</strong>:<br/>`, | |
`/mafia [join|leave] - Joins/leaves the game. Can only be done while signups are open.`, | |
`/mafia vote [player|novote] - Vote the specified player or abstain from voting.`, | |
`/mafia unvote - Withdraw your vote. Fails if you're not voting anyone`, | |
`/mafia deadline - View the deadline for the current game.`, | |
`/mafia sub in - Request to sub into the game, or cancel a request to sub out.`, | |
`/mafia sub out - Request to sub out of the game, or cancel a request to sub in.`, | |
`/mafia idle - Tells the host if you are idling.`, | |
`/mafia action [details] - Tells the host you are using an action with the given submission details.` | |
].join("<br/>"); | |
buf += `</details><details><summary class="button">Host Commands</summary>`; | |
buf += [ | |
`<br/><strong>Commands for game hosts and Cohosts to use</strong>:<br/>`, | |
`/mafia playercap [cap|none]- Limit the number of players able to join the game. Player cap cannot be more than 20 or less than 2. Requires host % @ # ~`, | |
`/mafia close - Closes signups for the current game. Requires host % @ # ~`, | |
`/mafia closedsetup [on|off] - Sets if the game is a closed setup. Closed setups don't show the role list to players. Requires host % @ # ~`, | |
`/mafia takeidles [on|off] - Sets if idles are accepted by the script or not. Requires host % @ # ~`, | |
`/mafia prod - Notifies players that they must submit an action or idle if they haven't yet. Requires host % @ # ~`, | |
`/mafia reveal [on|off] - Sets if roles reveal on death or not. Requires host % @ # ~`, | |
`/mafia selfvote [on|hammer|off] - Allows players to self vote either at hammer or anytime. Requires host % @ # ~`, | |
`/mafia [enablenl|disablenl] - Allows or disallows players abstain from voting. Requires host % @ # ~`, | |
`/mafia votelock [on|off] - Allows or disallows players to change their vote. Requires host % @ # ~`, | |
`/mafia voting [on|off] - Allows or disallows voting. Requires host % @ # ~`, | |
`/mafia forcevote [yes/no] - Forces players' votes onto themselves, and prevents unvoting. Requires host % @ # ~`, | |
`/mafia setroles [comma seperated roles] - Set the roles for a game of mafia. You need to provide one role per player. Requires host % @ # ~`, | |
`/mafia forcesetroles [comma seperated roles] - Forcibly set the roles for a game of mafia. No role PM information or alignment will be set. Requires host % @ # ~`, | |
`/mafia start - Start the game of mafia. Signups must be closed. Requires host % @ # ~`, | |
`/mafia [day|night] - Move to the next game day or night. Requires host % @ # ~`, | |
`/mafia extend (minutes) - Return to the previous game day. If (minutes) is provided, set the deadline for (minutes) minutes. Requires host % @ # ~`, | |
`/mafia kill [player] - Kill a player, eliminating them from the game. Requires host % @ # ~`, | |
`/mafia treestump [player] - Kills a player, but allows them to talk during the day still. Requires host % @ # ~`, | |
`/mafia spirit [player] - Kills a player, but allows them to vote still. Requires host % @ # ~`, | |
`/mafia spiritstump [player] - Kills a player, but allows them to talk and vote during the day. Requires host % @ # ~`, | |
`/mafia kick [player] - Kicks a player from the game without revealing their role. Requires host % @ # ~`, | |
`/mafia revive [player] - Revives a player who was eliminated. Requires host % @ # ~`, | |
`/mafia add [player] - Adds a new player to the game. Requires host % @ # ~`, | |
`/mafia revealrole [player] - Reveals the role of a player. Requires host % @ # ~`, | |
`/mafia revealas [player], [role] - Fakereveals the role of a player as a certain role. Requires host % @ # ~`, | |
`/mafia (un)silence [player] - Silences [player], preventing them from talking at all. Requires host % @ # ~`, | |
`/mafia (un)nighttalk [player] - Allows [player] to talk freely during the night. Requires host % @ # ~`, | |
`/mafia (un)[priest|actor] [player] - Makes [player] a priest (can't hammer) or actor (can only hammer). Requires host % @ # ~`, | |
`/mafia deadline [minutes|off] - Sets or removes the deadline for the game. Cannot be more than 20 minutes.`, | |
`/mafia sub next, [player] - Forcibly sub [player] out of the game. Requires host % @ # ~`, | |
`/mafia sub remove, [user] - Forcibly remove [user] from the sublist. Requres host % @ # ~`, | |
`/mafia sub unrequest, [player] - Remove's a player's request to sub out of the game. Requires host % @ # ~`, | |
`/mafia sub [player], [user] - Forcibly sub [player] for [user]. Requires host % @ # ~`, | |
`/mafia autosub [yes|no] - Sets if players will automatically sub out if a user is on the sublist. Defaults to yes. Requires host % @ # ~`, | |
`/mafia (un)[love|hate] [player] - Makes it take 1 more (love) or less (hate) vote to hammer [player]. Requires host % @ # ~`, | |
`/mafia (un)[mayor|voteless] [player] - Makes [player]'s' vote worth 2 votes (mayor) or makes [player]'s vote worth 0 votes (voteless). Requires host % @ # ~`, | |
`/mafia hammer [hammer] - sets the hammer count to [hammer] and resets votes`, | |
`/mafia hammer off - disables hammering`, | |
`/mafia shifthammer [hammer] - sets the hammer count to [hammer] without resetting votes`, | |
`/mafia resethammer - sets the hammer to the default, resetting votes`, | |
`/mafia playerroles - View all the player's roles in chat. Requires host`, | |
`/mafia resetgame - Resets game data. Does not change settings from the host besides deadlines or add/remove any players. Requires host % @ # ~`, | |
`/mafia end - End the current game of mafia. Requires host + % @ # ~` | |
].join("<br/>"); | |
buf += `</details><details><summary class="button">IDEA Module Commands</summary>`; | |
buf += [ | |
`<br/><strong>Commands for using IDEA modules</strong><br/>`, | |
`/mafia idea [idea] - starts the IDEA module [idea]. Requires + % @ # ~, voices can only start for themselves`, | |
`/mafia ideareroll - rerolls the IDEA module. Requires host % @ # ~`, | |
`/mafia ideapick [selection], [role] - selects a role`, | |
`/mafia ideadiscards - shows the discarded roles`, | |
`/mafia ideadiscards [off|on] - hides discards from the players. Requires host % @ # ~`, | |
`/mafia customidea choices, picks (new line here, shift+enter)`, | |
`(comma or newline separated rolelist) - Starts an IDEA module with custom roles. Requires % @ # ~` | |
].join("<br/>"); | |
buf += `</details>`; | |
buf += `</details><details><summary class="button">Mafia Room Specific Commands</summary>`; | |
buf += [ | |
`<br/><strong>Commands that are only useable in the Mafia Room</strong>:<br/>`, | |
`/mafia queue add, [user] - Adds the user to the host queue. Requires + % @ # ~, voices can only add themselves.`, | |
`/mafia queue remove, [user] - Removes the user from the queue. You can remove yourself regardless of rank. Requires % @ # ~.`, | |
`/mafia queue - Shows the list of users who are in queue to host.`, | |
`/mafia win (points) [user1], [user2], [user3], ... - Award the specified users points to the mafia leaderboard for this month. The amount of points can be negative to take points. Defaults to 10 points.`, | |
`/mafia winfaction (points), [faction] - Award the specified points to all the players in the given faction. Requires % @ # ~`, | |
`/mafia (un)mvp [user1], [user2], ... - Gives a MVP point and 10 leaderboard points to the users specified.`, | |
`/mafia [leaderboard|mvpladder] - View the leaderboard or MVP ladder for the current or last month.`, | |
`/mafia [hostlogs|playlogs] - View the host logs or play logs for the current or last month. Requires % @ # ~`, | |
`/mafia (un)hostban [user], [duration] - Ban a user from hosting games for [duration] days. Requires % @ # ~`, | |
`/mafia (un)gameban [user], [duration] - Ban a user from playing games for [duration] days. Requires % @ # ~` | |
].join("<br/>"); | |
buf += `</details>`; | |
return this.sendReplyBox(buf); | |
} | |
}; | |
const roomSettings = (room) => ({ | |
label: "Mafia", | |
permission: "editroom", | |
options: [ | |
[`disabled`, room.settings.mafiaDisabled || "mafia disable"], | |
[`enabled`, !room.settings.mafiaDisabled || "mafia enable"] | |
] | |
}); | |
process.nextTick(() => { | |
Chat.multiLinePattern.register("/mafia (custom|add|overwrite)idea"); | |
}); | |
//# sourceMappingURL=mafia.js.map | |