diff --git a/README.MD b/README.MD index b0d9091..92dc45b 100644 --- a/README.MD +++ b/README.MD @@ -83,6 +83,27 @@ The random layer list will not include the blacklisted layers or levels. (accept ``` [] ``` +#### hideVotesCount +###### Description +Hides the number of votes a layer received in broadcast message. +###### Default +```json +false +``` +#### showRerollOption +###### Description +vote option to restart the vote with random entries. +###### Default +```json +false +``` +#### voteBroadcastMessage +###### Description +Message that is sent as broadcast to announce a vote. +###### Default +``` +✯ MAPVOTE ✯ Vote for the next map by writing in chat the corresponding number! +``` ### Example configuration ```json { @@ -94,6 +115,9 @@ The random layer list will not include the blacklisted layers or levels. (accept "numberRecentMapsToExlude": 4, "automaticSeedingMode": true, "gamemodeWhitelist": [ "AAS", "RAAS", "Invasion" ], - "layerLevelBlacklist": [ "BlackCoast_Seed" ] + "layerLevelBlacklist": [ "BlackCoast_Seed" ], + "hideVotesCount": false, + "showRerollOption": false, + "voteBroadcastMessage": "✯ MAPVOTE ✯ Vote for the next map by writing in chat the corresponding number!" } ``` diff --git a/mapvote.js b/mapvote.js index b9c3d74..218c051 100644 --- a/mapvote.js +++ b/mapvote.js @@ -1,4 +1,4 @@ -//Plugin by MaskedMonkeyMan +//Plugin reworked by JetDave, original version by MaskedMonkeyMan import BasePlugin from "./base-plugin.js"; import DiscordBasePlugin from './discord-base-plugin.js'; @@ -12,7 +12,7 @@ function randomElement(array) { } function formatChoice(choiceIndex, mapString, currentVotes, firstBroadcast) { - return `${choiceIndex + 1}➤ ${mapString} ` + (!firstBroadcast ? `(${currentVotes})` : ""); + return `${choiceIndex}➤ ${mapString} ` + (!firstBroadcast ? `(${currentVotes})` : ""); // return `${choiceIndex + 1}❱ ${mapString} (${currentVotes} votes)` } @@ -81,6 +81,21 @@ export default class MapVote extends BasePlugin { 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 + }, + voteBroadcastMessage: { + required: false, + description: 'Message that is sent as broadcast to announce a vote', + default: "✯ MAPVOTE ✯ Vote for the next map by writing in chat the corresponding number!" + }, logToDiscord: { required: false, description: 'Log votes to Discord', @@ -92,7 +107,7 @@ export default class MapVote extends BasePlugin { description: 'The ID of the channel to log votes to.', default: '', example: '667741905228136459' - } + } }; } @@ -107,6 +122,12 @@ export default class MapVote extends BasePlugin { 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); @@ -127,7 +148,7 @@ export default class MapVote extends BasePlugin { 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(); + // await this.checkUpdates(); // console.log("mapvote removeEventListener", this.server) } @@ -251,8 +272,8 @@ export default class MapVote extends BasePlugin { 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) { - const rndMap = randomElement(seedingMaps); if (this.server.currentLayer.gamemode.toLowerCase() != "seed") { if (this.server.players.length <= 5) { const newCurrentMap = rndMap.layerid; @@ -293,12 +314,15 @@ export default class MapVote extends BasePlugin { if (!isNaN(subCommand)) // if this succeeds player is voting for a map { const mapNumber = parseInt(subCommand); //try to get a vote number - if (!this.votingEnabled) { - await this.warn(steamID, "There is no vote running right now"); - return; - } - await this.registerVote(steamID, mapNumber, playerName); - this.updateNextMap(); + 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; } @@ -361,8 +385,31 @@ export default class MapVote extends BasePlugin { updateNextMap() //sets next map to current mapvote winner, if there is a tie will pick at random { - const nextMap = randomElement(this.currentWinners); - this.server.rcon.execute(`AdminSetNextLayer ${nextMap}`); + 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) { @@ -420,33 +467,61 @@ export default class MapVote extends BasePlugin { 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 = 0; i < 6; i++) { + 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.push(l.layerid) - this.tallies.push(0); - this.factionStrings.push(getTranslation(l.teams[ 0 ]) + "-" + getTranslation(l.teams[ 1 ])); + 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 { - if (cmdLayers.length == 1 && cmdLayers[ 0 ].split('_')[ 0 ] == "*") for (let i = 0; i < 5; i++) cmdLayers.push(cmdLayers[ 0 ]) - if (cmdLayers.length <= 6) + 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.classname.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, ''))))); + 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.push(l.layerid) - this.tallies.push(0); - this.factionStrings.push(getTranslation(l.teams[ 0 ]) + "-" + getTranslation(l.teams[ 1 ])); + 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 6 options"); return; + } + 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) { @@ -472,13 +547,14 @@ export default class MapVote extends BasePlugin { if (playerCount < minPlayers && !force) { if (this.onConnectBound == false) { - this.server.on("PLAYER_CONNECTED", () => { this.beginVoting }) + this.server.on("PLAYER_CONNECTED", this.beginVoting) this.onConnectBound = true; } return; } if (this.onConnectBound) { - this.server.removeEventListener("PLAYER_CONNECTED", () => { this.beginVoting }); + + this.server.removeEventListener("PLAYER_CONNECTED", this.beginVoting); this.onConnectBound = false; } @@ -497,6 +573,8 @@ export default class MapVote extends BasePlugin { endVoting() { this.votingEnabled = false; clearInterval(this.broadcastIntervalTask); + clearTimeout(this.newVoteTimeout); + this.newVoteTimeout = null; this.broadcastIntervalTask = null; } objArrToValArr(arr, ...key) { @@ -516,12 +594,14 @@ export default class MapVote extends BasePlugin { //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"); + await this.broadcast(this.options.voteBroadcastMessage); let nominationStrings = []; - for (let choice in this.nominations) { + for (let choice = 1; choice < this.nominations.length; choice++) { choice = Number(choice); - nominationStrings.push(formatChoice(choice, this.nominations[ choice ].replace(/\_/gi, ' ').replace(/\sv\d{1,2}/gi, '') + ' ' + this.factionStrings[ choice ], this.tallies[ choice ], this.firstBroadcast)); + 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")); if (this.firstBroadcast) @@ -548,7 +628,7 @@ export default class MapVote extends BasePlugin { //counts a vote from a player and adds it to tallies async registerVote(steamID, nominationIndex, playerName) { - nominationIndex -= 1; // shift indices from display range + // 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; @@ -560,7 +640,7 @@ export default class MapVote extends BasePlugin { 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.tallies[ nominationIndex ]} votes)`); + 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 ]}`);