diff --git a/mapvote.js b/mapvote.js index 46d4684..62824c1 100644 --- a/mapvote.js +++ b/mapvote.js @@ -89,11 +89,6 @@ export default class MapVote extends DiscordBasePlugin { description: 'Enables/disables vote logging to Discord', default: false }, - discordClient: { - required: false, - description: "", - default: "discord" - }, channelID: { required: false, description: 'The ID of the channel to log votes to.', @@ -127,7 +122,7 @@ export default class MapVote extends DiscordBasePlugin { this.broadcastNominations = this.broadcastNominations.bind(this); this.beginVoting = this.beginVoting.bind(this); this.setSeedingMode = this.setSeedingMode.bind(this); - // this.logVoteToDiscord = this.logVoteToDiscord.bind(this); + this.logVoteToDiscord = this.logVoteToDiscord.bind(this); this.broadcast = (msg) => { this.server.rcon.broadcast(msg); }; this.warn = (steamid, msg) => { this.server.rcon.warn(steamid, msg); }; @@ -640,21 +635,21 @@ export default class MapVote extends DiscordBasePlugin { // await this.msgDirect(steamID, `${this.tallies[ nominationIndex ]} votes`); } - // async logVoteToDiscord(message) { - // if (!this.options.logToDiscord) return - // await this.sendDiscordMessage({ - // embed: { - // title: 'Vote Started', - // color: 16761867, - // fields: [ - // { - // name: 'Options:', - // value: `${message}` - // } - // ] - // } - // }); - // } + async logVoteToDiscord(message) { + if (!this.options.logToDiscord) return + await this.sendDiscordMessage({ + embed: { + title: 'Vote Started', + color: 16761867, + fields: [ + { + name: 'Options:', + value: `${message}` + } + ] + } + }); + } //removes a players vote if they disconnect from the sever clearVote() { diff --git a/mapvote.js.bak b/mapvote.js.bak deleted file mode 100644 index d70742c..0000000 --- a/mapvote.js.bak +++ /dev/null @@ -1,662 +0,0 @@ -//Plugin reworked by JetDave, original version by MaskedMonkeyMan - -import BasePlugin from "./base-plugin.js"; - -import fs from "fs"; -import { Layers } from "../layers/index.js" -import axios from "axios" - -function randomElement(array) { - return array[ Math.floor(Math.random() * array.length) ]; -} - -function formatChoice(choiceIndex, mapString, currentVotes, firstBroadcast) { - return `${choiceIndex}➤ ${mapString} ` + (!firstBroadcast ? `(${currentVotes})` : ""); - // return `${choiceIndex + 1}❱ ${mapString} (${currentVotes} votes)` -} - -function toMils(min) { - return min * 60 * 1000; -} - -export default class MapVote extends BasePlugin { - static get description() { - return "Map Voting plugin"; - } - - static get defaultEnabled() { - return true; - } - - static get optionsSpecification() { - return { - commandPrefix: - { - required: false, - description: "command name to use in chat", - default: "!vote" - }, - automaticVoteStart: { - required: false, - description: "a map vote will automatically start after a new match if set to true", - default: true - }, - minPlayersForVote: - { - required: false, - description: 'number of players needed on the server for a vote to start', - default: 40 - }, - voteWaitTimeFromMatchStart: - { - required: false, - description: 'time in mins from the start of a round to the start of a new map vote', - default: 15 - }, - voteBroadcastInterval: - { - required: false, - description: 'broadcast interval for vote notification in mins', - default: 7 - }, - automaticSeedingMode: - { - required: false, - description: 'set a seeding layer if server has less than 20 players', - default: true - }, - numberRecentMapsToExlude: { - required: false, - description: 'random layer list will not include the n. recent maps', - default: 4 - }, - gamemodeWhitelist: { - required: false, - description: 'random layer list will be generated with only selected gamemodes', - default: [ "AAS", "RAAS", "INVASION" ] - }, - layerLevelBlacklist: { - required: false, - description: 'random layer list will not include the blacklisted layers or levels. (acceptable formats: Gorodok/Gorodok_RAAS/Gorodok_AAS_v1)', - default: [] - }, - hideVotesCount: { - required: false, - description: 'hides the number of votes a layer received in broadcast message', - default: false - }, - showRerollOption: { - required: false, - description: 'vote option to restart the vote with random entries', - default: false - } - }; - } - - constructor(server, options, connectors) { - super(server, options, connectors); - - this.voteRules = {}; //data object holding vote configs - this.nominations = []; //layer strings for the current vote choices - this.trackedVotes = {}; //player votes, keyed by steam id - this.tallies = []; //votes per layer, parellel with nominations - this.votingEnabled = false; - this.onConnectBound = false; - this.broadcastIntervalTask = null; - this.firstBroadcast = true; - this.newVoteTimeout = null; - this.newVoteOptions = { - steamid: null, - cmdLayers: [], - bypassRaasFilter: false - }; - - this.onNewGame = this.onNewGame.bind(this); - this.onPlayerDisconnected = this.onPlayerDisconnected.bind(this); - this.onChatMessage = this.onChatMessage.bind(this); - this.broadcastNominations = this.broadcastNominations.bind(this); - this.beginVoting = this.beginVoting.bind(this); - this.setSeedingMode = this.setSeedingMode.bind(this); - - this.broadcast = (msg) => { this.server.rcon.broadcast(msg); }; - this.warn = (steamid, msg) => { this.server.rcon.warn(steamid, msg); }; - } - - async mount() { - this.options.gamemodeWhitelist.forEach((e, k, a) => a[ k ] = e.toUpperCase()); - this.server.on('NEW_GAME', this.onNewGame); - this.server.on('CHAT_MESSAGE', this.onChatMessage); - this.server.on('PLAYER_DISCONNECTED', this.onPlayerDisconnected); - this.server.on('PLAYER_CONNECTED', this.setSeedingMode); - this.verbose(1, 'Map vote was mounted.'); - this.verbose(1, "Blacklisted Layers/Levels: " + this.options.layerLevelBlacklist.join(', ')) - // await this.checkUpdates(); - // console.log("mapvote removeEventListener", this.server) - } - - async unmount() { - this.server.removeEventListener('NEW_GAME', this.onNewGame); - this.server.removeEventListener('CHAT_MESSAGE', this.onChatMessage); - this.server.removeEventListener('PLAYER_DISCONNECTED', this.onPlayerDisconnected); - clearInterval(this.broadcastIntervalTask); - this.verbose(1, 'Map vote was un-mounted.'); - } - - async onNewGame() { - setTimeout(async () => { - this.endVoting(); - this.trackedVotes = {}; - this.tallies = []; - this.nominations = []; - this.factionStrings = []; - if (this.options.automaticVoteStart) setTimeout(this.beginVoting, toMils(this.options.voteWaitTimeFromMatchStart)); - setTimeout(() => this.setSeedingMode(true), 10000); - }, 10000) - } - - async onPlayerDisconnected() { - if (!this.votingEnabled) return; - await this.server.updatePlayerList(); - this.clearVote(); - this.updateNextMap(); - } - async checkUpdates(callback) { - const versionN = "1.0.0" - let releasesUrl = "https://api.github.com/repos/fantinodavide/squad-js-map-vote/releases"; - let curDate = new Date(); - console.log("Current version: ", versionN, "\n > Checking for updates", curDate.toLocaleString()); - axios - .get(releasesUrl) - .then(res => { - const gitResData = res.data[ 0 ]; - const checkV = gitResData.tag_name.toUpperCase().replace("V", "").split("."); - const versionSplit = versionN.toString().split("."); - - const config_authorized_update = true;//((config.other.install_beta_versions && gitResData.prerelease) || !gitResData.prerelease); - const major_version_update = (parseInt(versionSplit[ 0 ]) < parseInt(checkV[ 0 ])); - const minor_version_update = (parseInt(versionSplit[ 0 ]) <= parseInt(checkV[ 0 ]) && parseInt(versionSplit[ 1 ]) < parseInt(checkV[ 1 ])); - const patch_version_update = (parseInt(versionSplit[ 0 ]) <= parseInt(checkV[ 0 ]) && parseInt(versionSplit[ 1 ]) <= parseInt(checkV[ 1 ]) && parseInt(versionSplit[ 2 ]) < parseInt(checkV[ 2 ])); - - if (config_authorized_update && (major_version_update || minor_version_update || patch_version_update)) { - console.log(" > Update found: " + gitResData.tag_name, gitResData.name); - //if (updateFoundCallback) updateFoundCallback(); - // server.close(); - if (downloadInstallUpdate) downloadLatestUpdate(gitResData); - else if (callback) callback(); - } else { - console.log(" > No updates found"); - if (callback) callback(); - } - }) - .catch(err => { - console.error(" > Couldn't check for updates. Proceding startup", err); - if (callback) callback(); - }) - } - downloadLatestUpdate(gitResData) { - // const url = gitResData.zipball_url; - const url = gitResData.zipball_url; - console.log(" > Downloading update: " + gitResData.tag_name, gitResData.name, url); - const dwnDir = path.resolve(__dirname, 'tmp_update');//, 'gitupd.zip') - const dwnFullPath = path.resolve(dwnDir, 'gitupd.zip') - - if (!fs.existsSync(dwnDir)) fs.mkdirSync(dwnDir); - - const writer = fs.createWriteStream(dwnFullPath) - axios({ - method: "get", - url: url, - responseType: "stream" - }).then((response) => { - response.data.pipe(writer); - }); - - writer.on('finish', (res) => { - setTimeout(() => { - installLatestUpdate(dwnDir, dwnFullPath, gitResData); - }, 1000) - }) - writer.on('error', (err) => { - console.error(err); - }) - } - installLatestUpdate(dwnDir, dwnFullPath, gitResData) { - const zip = new StreamZip({ - file: dwnFullPath, - storeEntries: true, - skipEntryNameValidation: true - }); - zip.on('ready', () => { - fs.remove(__dirname + "/dist", () => { - zip.extract("release/", __dirname, (err, res) => { - zip.close(); - nrc.run('npm install'); - console.log(" > Extracted", res, "files"); - fs.remove(dwnDir, () => { - console.log(`${dwnDir} folder deleted`); - const restartTimeout = 5000; - console.log(" > Restart in", restartTimeout / 1000, "seconds"); - restartProcess(restartTimeout); - }) - }); - }) - - }); - } - - setSeedingMode(isNewGameEvent = false) { - // setTimeout(()=>{this.msgDirect('76561198419229279',"MV\ntest\ntest")},1000) - // this.msgBroadcast("[MapVote] Seeding mode active") - const baseDataExist = this && this.options && this.server && this.server.players; - if (baseDataExist) { - this.verbose(1, "Checking seeding mode"); - if (this.options.automaticSeedingMode) { - if (this.server.players.length >= 1 && this.server.players.length < 40) { - const seedingMaps = Layers.layers.filter((l) => l.layerid && l.gamemode.toUpperCase() == "SEED" && !this.options.layerLevelBlacklist.find((fl) => l.layerid.toLowerCase().startsWith(fl.toLowerCase()))) - - const rndMap = randomElement(seedingMaps); - if (this.server.currentLayer) { - if (this.server.currentLayer.gamemode.toLowerCase() != "seed") { - if (this.server.players.length <= 5) { - const newCurrentMap = rndMap.layerid; - this.verbose(1, 'Going into seeding mode.'); - this.server.rcon.execute(`AdminChangeLayer ${newCurrentMap}`); - } - } - } else this.verbose(1, "Bad data (currentLayer). Seeding mode for current layer skipped to prevent errors."); - - if (this.server.nextLayer) { - const nextMaps = seedingMaps.filter((l) => (!this.server.currentLayer || l.layerid != this.server.currentLayer.layerid)) - let rndMap2; - do rndMap2 = randomElement(nextMaps); - while (rndMap2.layerid == rndMap.layerid) - - if (isNewGameEvent && this.server.players.length < 20 && this.server.nextLayer.gamemode.toLowerCase() != "seed") { - const newNextMap = rndMap2.layerid; - this.server.rcon.execute(`AdminSetNextLayer ${newNextMap}`); - } - } else this.verbose(1, "Bad data (nextLayer). Seeding mode for next layer skipped to prevent errors."); - - } else this.verbose(1, `Player count doesn't allow seeding mode (${this.server.players.length}/20)`); - } else this.verbose(1, "Seeding mode disabled in config"); - } else console.log("[MapVote][1] Bad data (this/this.server/this.options). Seeding mode skipped to prevent errors."); - } - - async onChatMessage(info) { - const { steamID, name: playerName } = info; - const message = info.message.toLowerCase(); - //check to see if this message has a command prefix - if (!message.startsWith(this.options.commandPrefix) && isNaN(message)) - return; - - const commandSplit = (isNaN(message) ? message.substring(this.options.commandPrefix.length).trim().split(' ') : [ message ]); - let cmdLayers = commandSplit.slice(1); - for (let k in cmdLayers) cmdLayers[ k ] = cmdLayers[ k ].toLowerCase(); - const subCommand = commandSplit[ 0 ]; - if (!isNaN(subCommand)) // if this succeeds player is voting for a map - { - const mapNumber = parseInt(subCommand); //try to get a vote number - if (this.nominations[ mapNumber ]) { - if (!this.votingEnabled) { - await this.warn(steamID, "There is no vote running right now"); - return; - } - await this.registerVote(steamID, mapNumber, playerName); - this.updateNextMap(); - } else - await this.warn(steamID, "Please vote a valid option"); - return; - } - - const isAdmin = info.chat === "ChatAdmin"; - switch (subCommand) // select the sub command - { - case "choices": //sends choices to player in the from of a warning - case "results": //sends player the results in a warning - if (!this.votingEnabled) { - await this.warn(steamID, "There is no vote running right now"); - return; - } - this.directMsgNominations(steamID); - return; - case "start": //starts the vote again if it was canceled - if (!isAdmin) return; - - if (this.votingEnabled) { - await this.warn(steamID, "Voting is already enabled"); - return; - } - this.beginVoting(true, steamID, cmdLayers); - return; - case "restart": //starts the vote again if it was canceled - if (!isAdmin) return; - this.endVoting(); - this.beginVoting(true, steamID, cmdLayers); - return; - case "cancel": //cancels the current vote and wont set next map to current winnner - if (!isAdmin) return; - - if (!this.votingEnabled) { - await this.warn(steamID, "There is no vote running right now"); - return; - } - this.endVoting(); - await this.warn(steamID, "Ending current vote"); - return; - case "broadcast": - if (!this.votingEnabled) { - await this.warn(steamID, "There is no vote running right now"); - return; - } - this.broadcastNominations(); - return; - case "help": //displays available commands - let msg = ""; - msg += (`!vote\n > choices\n > results\n`); - if (isAdmin) msg += (`\n Admin only:\n > start\n > restart\n > cancel\n > broadcast`); - - await this.warn(steamID, msg + `\nMapVote SquadJS plugin built by JetDave`); - return; - default: - //give them an error - await this.warn(steamID, `Unknown vote subcommand: ${subCommand}`); - return; - } - - } - - updateNextMap() //sets next map to current mapvote winner, if there is a tie will pick at random - { - let cpyWinners = this.currentWinners; - let skipSetNextMap = false; - if (cpyWinners.find(e => e == this.nominations[ 0 ])) { - if (cpyWinners.length > 1) { - delete cpyWinners[ cpyWinners.indexOf(this.nominations[ 0 ]) ] - cpyWinners = cpyWinners.filter(e => e != null) - } - else { - skipSetNextMap = true; - if (this.newVoteTimeout == null) { - this.newVoteTimeout = setTimeout(() => { - if (this.currentWinners.find(e => e == this.nominations[ 0 ]) && this.currentWinners.length == 1) { - this.newVoteTimeout = null; - this.endVoting() - this.beginVoting(true, this.newVoteOptions.steamid, this.newVoteOptions.cmdLayers) - } - }, 2 * 60 * 1000) - setTimeout(this.broadcastNominations, 1 * 60 * 1000) - } - } - } - if (!skipSetNextMap) { - const nextMap = randomElement(cpyWinners); - this.server.rcon.execute(`AdminSetNextLayer ${nextMap}`); - } - } - - matchLayers(builtString) { - return Layers.layers.filter(element => element.layerid.includes(builtString)); - } - - getMode(nomination, currentMode) { - const mapName = nomination.map; - let modes = nomination.modes; - let mode = modes[ 0 ]; - - if (mode === "Any") - modes = this.voteRules.modes; - - if (this.voteRules.mode_repeat_blacklist.includes(currentMode)) { - modes = modes.filter(mode => !mode.includes(currentMode)); - } - - while (modes.length > 0) { - mode = randomElement(modes); - modes = modes.filter(elem => elem !== mode); - if (this.matchLayers(`${mapName}_${mode}`).length > 0) - break; - } - - return mode; - } - - //TODO: right now if version is set to "Any" no caf layers will be selected - populateNominations(steamid = null, cmdLayers = [], bypassRaasFilter = false) //gets nomination strings from layer options - { - // this.nominations.push(builtLayerString); - // this.tallies.push(0); - - const translations = { - 'United States Army': "USA", - 'United States Marine Corps': "USMC", - 'Russian Ground Forces': "RUS", - 'British Army': "GB", - 'Canadian Army': "CAF", - 'Australian Defence Force': "AUS", - 'Irregular Militia Forces': "IRR", - 'Middle Eastern Alliance': "MEA", - 'Insurgent Forces': "INS", - } - - this.nominations = []; - this.tallies = []; - this.factionStrings = []; - let rnd_layers = []; - // let rnd_layers = []; - const sanitizedLayers = Layers.layers.filter((l) => l.layerid); - if (!cmdLayers || cmdLayers.length == 0) { - - const recentlyPlayedMaps = this.objArrToValArr(this.server.layerHistory.splice(0, this.options.numberRecentMapsToExlude), "layer", "map", "name"); - this.verbose(1, "Recently played maps: " + recentlyPlayedMaps.join(', ')) - const all_layers = sanitizedLayers.filter((l) => l.layerid && l.map && this.options.gamemodeWhitelist.includes(l.gamemode.toUpperCase()) && (![ this.server.currentLayer ? this.server.currentLayer.map.name : null, ...recentlyPlayedMaps ].includes(l.map.name)) && !this.options.layerLevelBlacklist.find((fl) => l.layerid.toLowerCase().startsWith(fl.toLowerCase()))); - for (let i = 1; i <= 6; i++) { - let l, maxtries = 10; - do l = randomElement(all_layers); while (rnd_layers.find(lf => lf.layerid == l.layerid) && --maxtries == 0) - if (maxtries > 0) { - rnd_layers.push(l); - this.nominations[ i ] = l.layerid - this.tallies[ i ] = 0; - this.factionStrings[ i ] = getTranslation(l.teams[ 0 ]) + "-" + getTranslation(l.teams[ 1 ]); - } - } - if (!bypassRaasFilter && rnd_layers.filter((l) => l.gamemode === 'RAAS' && this.options.gamemodeWhitelist.includes("RAAS")).length < 3) this.populateNominations(); - } else { - const maxOptions = this.options.showRerollOption ? 5 : 6; - let singleGamemodeVote = false; - if (cmdLayers.length == 1 && cmdLayers[ 0 ].split('_')[ 0 ] == "*") { - singleGamemodeVote = true; - for (let i = 0; i < maxOptions; i++) cmdLayers.push(cmdLayers[ 0 ]) - } - if (singleGamemodeVote || cmdLayers.length <= maxOptions) { - let i = 1; - for (let cl of cmdLayers) { - const cls = cl.split('_'); - const fLayers = sanitizedLayers.filter((l) => ((cls[ 0 ] == "*" || l.layerid.toLowerCase().startsWith(cls[ 0 ])) && (l.gamemode.toLowerCase().startsWith(cls[ 1 ]) || (!cls[ 1 ] && [ 'RAAS', 'AAS', 'INVASION' ].includes(l.gamemode.toUpperCase()))) && (!cls[ 2 ] || l.version.toLowerCase().startsWith("v" + cls[ 2 ].replace(/v/gi, ''))))); - let l; - do l = randomElement(fLayers); while (rnd_layers.includes(l)) - if (l) { - rnd_layers.push(l); - this.nominations[ i ] = l.layerid - this.tallies[ i ] = 0; - this.factionStrings[ i ] = getTranslation(l.teams[ 0 ]) + "-" + getTranslation(l.teams[ 1 ]); - i++; - } - } - } - else if (steamid) { - this.warn(steamid, "You cannot start a vote with more than " + maxOptions + " options"); - return; - } - } - - if (this.options.showRerollOption) { - if (this.nominations.length > 5) { - this.nominations.splice(6, 1); - this.tallies.splice(6, 1); - this.factionStrings.splice(6, 1); - } - - this.newVoteOptions.steamid = steamid; - this.newVoteOptions.bypassRaasFilter = bypassRaasFilter; - this.newVoteOptions.cmdLayers = cmdLayers; - - this.nominations[ 0 ] = "Reroll vote list with random options" - this.tallies[ 0 ] = 0; - this.factionStrings[ 0 ] = ""; - - } - - function getTranslation(t) { - if (translations[ t.faction ]) return translations[ t.faction ] - else { - const f = t.faction.split(' '); - let fTag = ""; - f.forEach((e) => { fTag += e[ 0 ] }); - return fTag.toUpperCase(); - } - } - } - - //checks if there are enough players to start voting, if not binds itself to player connected - //when there are enough players it clears old votes, sets up new nominations, and starts broadcast - beginVoting(force = false, steamid = null, cmdLayers = []) { - this.verbose(1, "Starting vote") - const playerCount = this.server.players.length; - const minPlayers = this.options.minPlayersForVote; - - if (this.votingEnabled) //voting has already started - return; - - if (playerCount < minPlayers && !force) { - if (this.onConnectBound == false) { - this.server.on("PLAYER_CONNECTED", this.beginVoting) - this.onConnectBound = true; - } - return; - } - if (this.onConnectBound) { - - this.server.removeEventListener("PLAYER_CONNECTED", this.beginVoting); - this.onConnectBound = false; - } - - // these need to be reset after reenabling voting - this.trackedVotes = {}; - this.tallies = []; - - this.populateNominations(steamid, cmdLayers); - - this.votingEnabled = true; - this.firstBroadcast = true; - this.broadcastNominations(); - this.broadcastIntervalTask = setInterval(this.broadcastNominations, toMils(this.options.voteBroadcastInterval)); - } - - endVoting() { - this.votingEnabled = false; - clearInterval(this.broadcastIntervalTask); - clearTimeout(this.newVoteTimeout); - this.newVoteTimeout = null; - this.broadcastIntervalTask = null; - } - objArrToValArr(arr, ...key) { - let vet = []; - for (let o of arr) { - let obj = o; - for (let k of key) { - if (obj[ k ]) - obj = obj[ k ]; - } - vet.push(obj); - } - return vet; - } - //sends a message about nominations through a broadcast - //NOTE: max squad broadcast message length appears to be 485 characters - //Note: broadcast strings with multi lines are very strange - async broadcastNominations() { - if (this.nominations.length > 0 && this.votingEnabled) { - await this.broadcast("✯ MAPVOTE ✯ Vote for the next map by writing in chat the corresponding number!\n"); - let nominationStrings = []; - for (let choice = 1; choice < this.nominations.length; choice++) { - choice = Number(choice); - let vLayer = Layers.layers.find(e => e.layerid == this.nominations[ choice ]); - nominationStrings.push(formatChoice(choice, vLayer.map.name + ' ' + vLayer.gamemode + ' ' + this.factionStrings[ choice ], this.tallies[ choice ], (this.options.hideVotesCount || this.firstBroadcast))); - } - if (this.nominations[ 0 ]) nominationStrings.push(formatChoice(0, this.nominations[ 0 ], this.tallies[ 0 ], (this.options.hideVotesCount || this.firstBroadcast))) - await this.broadcast(nominationStrings.join("\n")); - - this.firstBroadcast = false; - } - //const winners = this.currentWinners; - //await this.msgBroadcast(`Current winner${winners.length > 1 ? "s" : ""}: ${winners.join(", ")}`); - } - - async directMsgNominations(steamID) { - let strMsg = ""; - for (let choice in this.nominations) { - choice = Number(choice); - // await this.msgDirect(steamID, formatChoice(choice, this.nominations[ choice ], this.tallies[ choice ])); - strMsg += (steamID, formatChoice(choice, this.nominations[ choice ], this.tallies[ choice ])) + "\n"; - } - strMsg.trim(); - if (steamID) this.warn(steamID, strMsg) - - // const winners = this.currentWinners; - // await this.msgDirect(steamID, `Current winner${winners.length > 1 ? "s" : ""}: ${winners.join(", ")}`); - } - - //counts a vote from a player and adds it to tallies - async registerVote(steamID, nominationIndex, playerName) { - // nominationIndex -= 1; // shift indices from display range - if (nominationIndex < 0 || nominationIndex > this.nominations.length) { - await this.warn(steamID, `[Map Vote] ${playerName}: invalid map number, typ !vote results to see map numbers`); - return; - } - - const previousVote = this.trackedVotes[ steamID ]; - this.trackedVotes[ steamID ] = nominationIndex; - - this.tallies[ nominationIndex ] += 1; - if (previousVote !== undefined) - this.tallies[ previousVote ] -= 1; - await this.warn(steamID, `Registered vote: ${this.nominations[ nominationIndex ].replace(/\_/gi, ' ').replace(/\sv\d{1,2}/gi, '')} ${this.factionStrings[ nominationIndex ]} ` + (this.options.hideVotesCount ? `` : `(${this.tallies[ nominationIndex ]} votes)`)); - // await this.msgDirect(steamID, `Registered vote`);// ${this.nominations[ nominationIndex ]} ${this.factionStrings[ nominationIndex ]} (${this.tallies[ nominationIndex ]} votes)`); - // await this.msgDirect(steamID, `${this.nominations[ nominationIndex ]} (${this.tallies[ nominationIndex ]} votes)`); - // await this.msgDirect(steamID, `${this.factionStrings[ nominationIndex ]}`); - // await this.msgDirect(steamID, `${this.tallies[ nominationIndex ]} votes`); - } - - //removes a players vote if they disconnect from the sever - clearVote() { - const currentPlayers = this.server.players.map((p) => p.steamID); - for (const steamID in this.trackedVotes) { - if (!(currentPlayers.includes(steamID))) { - const vote = this.trackedVotes[ steamID ]; - this.tallies[ vote ] -= 1; - delete this.trackedVotes[ steamID ]; - } - } - } - - //calculates the current winner(s) of the vote and returns thier strings in an array - get currentWinners() { - const ties = []; - - let highestScore = -Infinity; - for (let choice in this.tallies) { - const score = this.tallies[ choice ]; - if (score < highestScore) - continue; - else if (score > highestScore) { - highestScore = score; - ties.length = 0; - ties.push(choice); - } - else // equal - ties.push(choice); - } - - return ties.map(i => this.nominations[ i ]); - } -}