mirror of
https://github.com/AsgardEternal/squad-js-map-vote.git
synced 2025-01-23 23:03:51 -06:00
fixed discord integration
This commit is contained in:
parent
40b9d4c295
commit
542c74be11
37
mapvote.js
37
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() {
|
||||
|
662
mapvote.js.bak
662
mapvote.js.bak
@ -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 ]);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user