2022-09-16 12:20:18 +02:00
//Plugin reworked by JetDave, original version by MaskedMonkeyMan
2022-03-19 16:11:24 -07:00
2022-09-17 01:10:05 +02:00
// import BasePlugin from "./base-plugin.js";
2022-09-15 14:17:43 -05:00
import DiscordBasePlugin from './discord-base-plugin.js' ;
2022-03-19 16:11:24 -07:00
import { Layers } from "../layers/index.js"
2022-09-15 14:05:06 +02:00
import axios from "axios"
2022-11-18 00:22:21 +01:00
import Layer from '../layers/layer.js' ;
2022-11-21 15:15:11 +01:00
import fs from 'fs'
import process from 'process'
2022-03-19 16:11:24 -07:00
2022-09-17 01:10:05 +02:00
export default class MapVote extends DiscordBasePlugin {
2022-08-13 12:05:40 +02:00
static get description ( ) {
2022-03-19 16:11:24 -07:00
return "Map Voting plugin" ;
}
2022-08-13 12:05:40 +02:00
static get defaultEnabled ( ) {
2022-03-19 16:11:24 -07:00
return true ;
}
2022-08-13 12:05:40 +02:00
static get optionsSpecification ( ) {
2022-03-19 16:11:24 -07:00
return {
2022-09-17 01:10:05 +02:00
... DiscordBasePlugin . optionsSpecification ,
2022-03-19 16:11:24 -07:00
commandPrefix :
{
required : false ,
description : "command name to use in chat" ,
default : "!vote"
} ,
2022-09-15 14:05:06 +02:00
automaticVoteStart : {
required : false ,
description : "a map vote will automatically start after a new match if set to true" ,
default : true
} ,
2022-10-20 23:59:28 +02:00
votingDuration : {
required : false ,
description : "How long the voting will be active (in minutes). Set to 0 for unlimited time." ,
default : 0
} ,
2022-08-13 12:05:40 +02:00
minPlayersForVote :
2022-03-19 16:11:24 -07:00
{
required : false ,
description : 'number of players needed on the server for a vote to start' ,
2022-08-15 13:25:16 +02:00
default : 40
2022-03-19 16:11:24 -07:00
} ,
voteWaitTimeFromMatchStart :
{
required : false ,
2022-08-15 13:25:16 +02:00
description : 'time in mins from the start of a round to the start of a new map vote' ,
default : 15
2022-03-19 16:11:24 -07:00
} ,
voteBroadcastInterval :
{
required : false ,
2022-08-15 13:25:16 +02:00
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
2022-08-25 21:43:06 +02:00
} ,
2022-08-31 11:24:12 +02:00
numberRecentMapsToExlude : {
2022-08-25 21:43:06 +02:00
required : false ,
description : 'random layer list will not include the n. recent maps' ,
default : 4
2022-09-06 13:13:24 +02:00
} ,
gamemodeWhitelist : {
required : false ,
description : 'random layer list will be generated with only selected gamemodes' ,
default : [ "AAS" , "RAAS" , "INVASION" ]
} ,
2022-09-21 16:43:37 +02:00
layerFilteringMode : {
required : false ,
description : "Select Whitelist mode or Blacklist mode" ,
default : "blacklist"
} ,
layerLevelWhitelist : {
required : false ,
2022-09-21 16:46:11 +02:00
description : 'random layer list will include only the whitelisted layers or levels. (acceptable formats: Gorodok/Gorodok_RAAS/Gorodok_AAS_v1)' ,
2022-09-21 16:43:37 +02:00
default : [ ]
} ,
2022-09-06 13:13:24 +02:00
layerLevelBlacklist : {
required : false ,
description : 'random layer list will not include the blacklisted layers or levels. (acceptable formats: Gorodok/Gorodok_RAAS/Gorodok_AAS_v1)' ,
default : [ ]
2022-09-15 14:17:43 -05:00
} ,
2022-10-20 23:41:43 +02:00
applyBlacklistToWhitelist : {
required : false ,
description : 'if set to true the blacklisted layers won\'t be included also in whitelist mode' ,
default : true
} ,
2022-11-18 20:30:14 +01:00
factionsBlacklist : {
required : false ,
description : "factions to exclude in map vote. ( ex: ['CAF'] )" ,
default : [ ]
} ,
2022-10-21 00:48:57 +02:00
minRaasEntries : {
required : false ,
description : 'Minimum amount of RAAS layers in the vote list.' ,
default : 2
} ,
2022-09-16 00:41:20 +02:00
hideVotesCount : {
required : false ,
2022-09-16 01:07:54 +02:00
description : 'hides the number of votes a layer received in broadcast message' ,
2022-09-16 00:41:20 +02:00
default : false
} ,
showRerollOption : {
required : false ,
2022-09-16 01:07:54 +02:00
description : 'vote option to restart the vote with random entries' ,
2022-09-16 00:41:20 +02:00
default : false
2022-09-16 22:41:55 +02:00
} ,
voteBroadcastMessage : {
required : false ,
description : 'Message that is sent as broadcast to announce a vote' ,
2022-09-17 01:10:05 +02:00
default : "✯ MAPVOTE ✯\nVote for the next map by writing in chat the corresponding number!"
2022-09-16 22:48:08 +02:00
} ,
2022-10-21 00:48:57 +02:00
voteWinnerBroadcastMessage : {
required : false ,
description : 'Message that is sent as broadcast to announce the winning layer' ,
default : "✯ MAPVOTE ✯\nThe winning layer is\n\n"
} ,
2023-01-20 01:05:29 +01:00
showWinnerBroadcastMessage : {
required : false ,
description : 'Enables the broadcast at the end of the voting.' ,
default : true
} ,
2022-10-04 01:05:43 +02:00
allowedSameMapEntries : {
2022-10-04 00:52:47 +02:00
required : false ,
description : 'Allowed NUMBER of duplicate map entries in vote list' ,
default : 1
} ,
2022-09-15 14:17:43 -05:00
logToDiscord : {
required : false ,
2022-09-16 23:36:48 +02:00
description : 'Enables/disables vote logging to Discord' ,
2022-09-15 14:17:43 -05:00
default : false
} ,
channelID : {
required : false ,
description : 'The ID of the channel to log votes to.' ,
default : '' ,
2022-09-16 23:36:48 +02:00
example : '112233445566778899'
2022-09-18 00:38:06 +02:00
} ,
2022-11-21 15:15:11 +01:00
persistentDataFile : {
required : false ,
description : 'Path to file in which to store important data that should be restored after a restart' ,
default : ""
} ,
2022-09-20 23:21:05 +02:00
timezone : {
required : false ,
description : "Timezone relative to UTC time. 0 for UTC, 2 for CEST (UTC+2), -1 (UTC-1) " ,
default : 0
} ,
2023-01-21 00:49:06 +01:00
includeMainAssetsInBroadcast : {
required : false ,
description : "Shows/Hides Helis and Tanks in the broadcast if they are included in the voting layer" ,
default : true
} ,
2022-09-18 00:38:06 +02:00
timeFrames : {
required : false ,
description : 'Array of timeframes to override options' ,
default : [ ]
2022-04-14 02:08:53 -07:00
}
2022-03-19 16:11:24 -07:00
} ;
}
2022-08-13 12:05:40 +02:00
constructor ( server , options , connectors ) {
2022-03-19 16:11:24 -07:00
super ( server , options , connectors ) ;
2022-08-13 12:05:40 +02:00
2022-09-20 21:29:46 +02:00
this . options . timeFrames . forEach ( ( e , key , arr ) => { arr [ key ] . id = key + 1 } ) ;
2022-11-20 22:46:49 +01:00
if ( this . options . allowedSameMapEntries < 1 ) this . options . allowedSameMapEntries = 1
2022-03-19 16:11:24 -07:00
this . voteRules = { } ; //data object holding vote configs
this . nominations = [ ] ; //layer strings for the current vote choices
2022-08-13 12:05:40 +02:00
this . trackedVotes = { } ; //player votes, keyed by steam id
2022-03-19 16:11:24 -07:00
this . tallies = [ ] ; //votes per layer, parellel with nominations
this . votingEnabled = false ;
this . broadcastIntervalTask = null ;
2022-08-13 12:05:40 +02:00
this . firstBroadcast = true ;
2022-09-16 00:41:20 +02:00
this . newVoteTimeout = null ;
2022-09-16 20:35:08 +02:00
this . newVoteOptions = {
steamid : null ,
cmdLayers : [ ] ,
bypassRaasFilter : false
} ;
2022-09-18 00:38:06 +02:00
this . or _options = { ... this . options } ;
2022-09-28 22:42:53 +02:00
this . autovotestart = null ;
2022-10-01 14:30:26 +02:00
this . lastMapUpdate = new Date ( ) ;
2022-10-25 19:51:05 +02:00
this . timeout _ps = [ ]
2022-08-13 12:05:40 +02:00
2022-03-19 16:11:24 -07:00
this . onNewGame = this . onNewGame . bind ( this ) ;
this . onPlayerDisconnected = this . onPlayerDisconnected . bind ( this ) ;
this . onChatMessage = this . onChatMessage . bind ( this ) ;
2022-08-13 12:05:40 +02:00
this . broadcastNominations = this . broadcastNominations . bind ( this ) ;
2022-03-19 16:11:24 -07:00
this . beginVoting = this . beginVoting . bind ( this ) ;
2022-09-08 00:46:37 +02:00
this . setSeedingMode = this . setSeedingMode . bind ( this ) ;
2022-09-17 01:33:21 +02:00
this . logVoteToDiscord = this . logVoteToDiscord . bind ( this ) ;
2022-09-18 00:38:06 +02:00
this . timeframeOptionOverrider = this . timeframeOptionOverrider . bind ( this ) ;
2022-11-21 15:15:11 +01:00
this . savePersistentData = this . savePersistentData . bind ( this )
this . restorePersistentData = this . restorePersistentData . bind ( this )
2023-01-19 16:21:23 +01:00
this . endVotingGently = this . endVotingGently . bind ( this )
2022-03-19 16:11:24 -07:00
2023-01-21 00:49:06 +01:00
this . broadcast = async ( msg ) => { await this . server . rcon . broadcast ( msg ) ; } ;
this . warn = async ( steamid , msg ) => { await this . server . rcon . warn ( steamid , msg ) ; } ;
2022-11-21 15:15:11 +01:00
2022-11-22 19:53:19 +01:00
process . on ( 'uncaughtException' , this . savePersistentData ) ;
2022-03-19 16:11:24 -07:00
}
2022-08-13 12:05:40 +02:00
async mount ( ) {
2022-11-18 00:22:21 +01:00
await this . updateLayerList ( ) ;
2022-11-21 15:15:11 +01:00
this . restorePersistentData ( ) ;
2022-08-13 12:05:40 +02:00
this . server . on ( 'NEW_GAME' , this . onNewGame ) ;
2022-03-19 16:11:24 -07:00
this . server . on ( 'CHAT_MESSAGE' , this . onChatMessage ) ;
this . server . on ( 'PLAYER_DISCONNECTED' , this . onPlayerDisconnected ) ;
2023-01-15 20:46:59 +01:00
this . server . on ( 'ROUND_ENDED' , this . endVotingGently )
2022-09-20 23:21:05 +02:00
setTimeout ( ( ) => {
this . verbose ( 1 , 'Enabled late listeners.' ) ;
this . server . on ( 'PLAYER_CONNECTED' , this . setSeedingMode ) ;
} , 10 * 1000 ) // wait 10 seconds to be sure to have an updated player list
2022-08-13 12:05:40 +02:00
this . verbose ( 1 , 'Map vote was mounted.' ) ;
2022-09-06 23:21:33 +02:00
this . verbose ( 1 , "Blacklisted Layers/Levels: " + this . options . layerLevelBlacklist . join ( ', ' ) )
2022-09-16 00:41:20 +02:00
// await this.checkUpdates();
2022-09-18 00:38:06 +02:00
this . timeframeOptionOverrider ( ) ;
2022-09-18 01:19:19 +02:00
setInterval ( this . timeframeOptionOverrider , 1 * 60 * 1000 )
2022-11-21 15:15:11 +01:00
setInterval ( this . savePersistentData , 20 * 1000 )
2022-03-19 16:11:24 -07:00
}
2022-08-13 12:05:40 +02:00
async unmount ( ) {
this . server . removeEventListener ( 'NEW_GAME' , this . onNewGame ) ;
2022-03-19 16:11:24 -07:00
this . server . removeEventListener ( 'CHAT_MESSAGE' , this . onChatMessage ) ;
this . server . removeEventListener ( 'PLAYER_DISCONNECTED' , this . onPlayerDisconnected ) ;
clearInterval ( this . broadcastIntervalTask ) ;
2022-08-13 12:05:40 +02:00
this . verbose ( 1 , 'Map vote was un-mounted.' ) ;
2022-03-19 16:11:24 -07:00
}
2022-08-13 12:05:40 +02:00
async onNewGame ( ) {
2022-10-25 19:51:05 +02:00
for ( let x of this . timeout _ps ) clearTimeout ( this . timeout _ps . pop ( ) )
2022-08-25 15:43:38 +02:00
setTimeout ( async ( ) => {
this . endVoting ( ) ;
this . trackedVotes = { } ;
this . tallies = [ ] ;
this . nominations = [ ] ;
this . factionStrings = [ ] ;
2022-09-28 22:42:53 +02:00
if ( this . options . automaticVoteStart ) this . autovotestart = setTimeout ( this . beginVoting , toMils ( this . options . voteWaitTimeFromMatchStart ) ) ;
2022-09-08 01:06:20 +02:00
setTimeout ( ( ) => this . setSeedingMode ( true ) , 10000 ) ;
2022-08-25 15:43:38 +02:00
} , 10000 )
2022-08-13 12:05:40 +02:00
}
2022-08-16 23:53:08 +02:00
2022-08-13 12:05:40 +02:00
async onPlayerDisconnected ( ) {
2022-03-20 15:35:43 -07:00
if ( ! this . votingEnabled ) return ;
2022-08-13 12:05:40 +02:00
await this . server . updatePlayerList ( ) ;
2022-03-19 16:11:24 -07:00
this . clearVote ( ) ;
2022-10-01 14:30:26 +02:00
if ( new Date ( ) - this . lastMapUpdate > 5 * 1000 ) this . updateNextMap ( ) ;
2022-08-15 13:25:16 +02:00
}
2022-09-18 00:38:06 +02:00
async timeframeOptionOverrider ( ) {
2022-09-20 21:29:46 +02:00
const orOpt = { ... this . or _options } ;
2022-09-20 23:21:05 +02:00
const utcDelay = parseFloat ( this . options . timezone ) ;
2022-10-04 00:52:47 +02:00
let timeNow = new Date ( 0 , 0 , 0 , new Date ( ) . getUTCHours ( ) + utcDelay , new Date ( ) . getUTCMinutes ( ) ) ;
2022-10-04 00:58:11 +02:00
timeNow = new Date ( 0 , 0 , 0 , timeNow . getHours ( ) , timeNow . getMinutes ( ) )
2022-10-04 00:52:47 +02:00
// console.log(timeNow, timeNow.toTimeString(), timeNow.toLocaleTimeString())
2022-10-04 00:58:11 +02:00
this . verbose ( 1 , ` Current time (UTC ${ ( utcDelay >= 0 ? '+' : '' ) + utcDelay } ) ${ timeNow . toLocaleTimeString ( 'en-GB' ) . split ( ':' ) . splice ( 0 , 2 ) . join ( ':' ) } ` )
2022-09-21 16:43:37 +02:00
2022-09-20 21:29:46 +02:00
const activeTimeframes = orOpt . timeFrames . filter ( tfFilter ) ;
2022-09-18 01:19:19 +02:00
let logTimeframe = "Active Time Frames: " ;
let activeTfIds = [ ] ;
2022-09-18 21:45:29 +02:00
this . options = { ... this . or _options } ;
2022-09-18 01:19:19 +02:00
for ( let atfK in activeTimeframes ) {
const atf = activeTimeframes [ atfK ] ;
activeTfIds . push ( atf . name || atf . id ) ;
2022-09-18 14:19:22 +02:00
for ( let o in atf . overrides ) {
this . options [ o ] = atf . overrides [ o ] ;
2022-09-18 01:19:19 +02:00
}
}
this . verbose ( 1 , logTimeframe + activeTfIds . join ( ', ' ) ) ;
2022-09-18 00:38:06 +02:00
2022-09-20 23:21:05 +02:00
function tfFilter ( tf , key , arr ) {
const tfStartSplit = [ parseInt ( tf . start . split ( ':' ) [ 0 ] ) , parseInt ( tf . start . split ( ':' ) [ 1 ] ) ] ;
const tfEndSplit = [ parseInt ( tf . end . split ( ':' ) [ 0 ] ) , parseInt ( tf . end . split ( ':' ) [ 1 ] ) ] ;
const tfStart = new Date ( 0 , 0 , 0 , ... tfStartSplit )
const tfStart2 = new Date ( 0 , 0 , 0 , 0 , 0 )
const tfEnd = new Date ( 0 , 0 , 0 , ... tfEndSplit )
const tfEnd2 = new Date ( 0 , 0 , 0 , 24 , 0 )
2022-10-04 00:52:47 +02:00
// console.log(timeNow, tfStart, tfEnd, tfStart2 <= timeNow, timeNow < tfEnd)
2022-09-20 23:21:05 +02:00
return ( tfStart <= timeNow && timeNow < tfEnd ) || ( tfStart > tfEnd && ( ( tfStart <= timeNow && timeNow < tfEnd2 ) || ( tfStart2 <= timeNow && timeNow < tfEnd ) ) )
}
2022-09-18 00:38:06 +02:00
}
2022-09-08 01:06:20 +02:00
setSeedingMode ( isNewGameEvent = false ) {
2022-08-25 15:43:38 +02:00
// setTimeout(()=>{this.msgDirect('76561198419229279',"MV\ntest\ntest")},1000)
// this.msgBroadcast("[MapVote] Seeding mode active")
2022-09-08 00:46:37 +02:00
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 ) {
2022-09-11 01:09:28 +02:00
const seedingMaps = Layers . layers . filter ( ( l ) => l . layerid && l . gamemode . toUpperCase ( ) == "SEED" && ! this . options . layerLevelBlacklist . find ( ( fl ) => l . layerid . toLowerCase ( ) . startsWith ( fl . toLowerCase ( ) ) ) )
2022-09-15 14:05:06 +02:00
2022-09-16 16:12:48 +02:00
const rndMap = randomElement ( seedingMaps ) ;
2022-09-08 00:46:37 +02:00
if ( this . server . currentLayer ) {
2022-09-11 01:09:28 +02:00
if ( this . server . currentLayer . gamemode . toLowerCase ( ) != "seed" ) {
if ( this . server . players . length <= 5 ) {
const newCurrentMap = rndMap . layerid ;
this . verbose ( 1 , 'Going into seeding mode.' ) ;
2022-10-04 00:58:11 +02:00
this . server . rcon . execute ( ` AdminChangeLayer ${ newCurrentMap } ` ) ;
2022-09-11 01:09:28 +02:00
}
}
2022-09-15 13:20:09 +02:00
} else this . verbose ( 1 , "Bad data (currentLayer). Seeding mode for current layer skipped to prevent errors." ) ;
2022-09-15 14:05:06 +02:00
2023-01-15 20:46:59 +01:00
/ * i f ( t h i s . s e r v e r . n e x t L a y e r ) {
2022-09-15 13:20:09 +02:00
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 ;
2022-10-04 00:58:11 +02:00
this . server . rcon . execute ( ` AdminSetNextLayer ${ newNextMap } ` ) ;
2022-09-15 13:20:09 +02:00
}
2023-01-15 20:46:59 +01:00
} else this . verbose ( 1 , "Bad data (nextLayer). Seeding mode for next layer skipped to prevent errors." ) ; * /
2022-09-11 01:09:28 +02:00
2022-09-15 13:20:09 +02:00
} else this . verbose ( 1 , ` Player count doesn't allow seeding mode ( ${ this . server . players . length } /20) ` ) ;
2022-09-08 00:46:37 +02:00
} 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." ) ;
2022-03-19 16:11:24 -07:00
}
2022-08-13 12:05:40 +02:00
async onChatMessage ( info ) {
const { steamID , name : playerName } = info ;
2022-03-19 16:11:24 -07:00
const message = info . message . toLowerCase ( ) ;
//check to see if this message has a command prefix
2022-08-13 12:05:40 +02:00
if ( ! message . startsWith ( this . options . commandPrefix ) && isNaN ( message ) )
2022-03-19 16:11:24 -07:00
return ;
2022-08-13 12:05:40 +02:00
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
2022-03-19 16:11:24 -07:00
{
2022-08-13 12:05:40 +02:00
const mapNumber = parseInt ( subCommand ) ; //try to get a vote number
2022-09-16 01:04:04 +02:00
if ( this . nominations [ mapNumber ] ) {
2022-09-16 00:41:20 +02:00
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" ) ;
2022-03-19 16:11:24 -07:00
return ;
}
2022-08-13 12:05:40 +02:00
2022-03-19 16:11:24 -07:00
const isAdmin = info . chat === "ChatAdmin" ;
2022-08-13 12:05:40 +02:00
switch ( subCommand ) // select the sub command
2022-03-19 16:11:24 -07:00
{
case "choices" : //sends choices to player in the from of a warning
case "results" : //sends player the results in a warning
2022-08-13 12:05:40 +02:00
if ( ! this . votingEnabled ) {
2022-09-02 12:52:42 +02:00
await this . warn ( steamID , "There is no vote running right now" ) ;
2022-03-19 16:11:24 -07:00
return ;
}
this . directMsgNominations ( steamID ) ;
return ;
2022-08-13 12:05:40 +02:00
case "start" : //starts the vote again if it was canceled
if ( ! isAdmin ) return ;
if ( this . votingEnabled ) {
2022-09-02 12:52:42 +02:00
await this . warn ( steamID , "Voting is already enabled" ) ;
2022-03-19 16:11:24 -07:00
return ;
}
2022-08-13 12:05:40 +02:00
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 ) ;
2022-03-19 16:11:24 -07:00
return ;
case "cancel" : //cancels the current vote and wont set next map to current winnner
2022-08-13 12:05:40 +02:00
if ( ! isAdmin ) return ;
if ( ! this . votingEnabled ) {
2022-09-02 12:52:42 +02:00
await this . warn ( steamID , "There is no vote running right now" ) ;
2022-03-19 16:11:24 -07:00
return ;
}
this . endVoting ( ) ;
2022-09-02 12:52:42 +02:00
await this . warn ( steamID , "Ending current vote" ) ;
2022-03-19 16:11:24 -07:00
return ;
2022-10-25 19:51:05 +02:00
case "end" : //gently ends the current vote and announces the winner layer
if ( ! isAdmin ) return ;
if ( ! this . votingEnabled ) {
await this . warn ( steamID , "There is no vote running right now" ) ;
return ;
}
this . endVotingGently ( ) ;
await this . warn ( steamID , "Ending current vote" ) ;
return ;
2022-09-28 22:42:53 +02:00
case "cancelauto" : //cancels the current vote and wont set next map to current winnner
if ( ! isAdmin ) return ;
if ( ! this . autovotestart ) {
await this . warn ( steamID , "There is no automatic vote start scheduled" ) ;
return ;
}
clearTimeout ( this . autovotestart ) ;
this . autovotestart = null ;
await this . warn ( steamID , "Ending current vote" ) ;
return ;
2022-08-13 12:05:40 +02:00
case "broadcast" :
if ( ! this . votingEnabled ) {
2022-09-02 12:52:42 +02:00
await this . warn ( steamID , "There is no vote running right now" ) ;
2022-08-13 12:05:40 +02:00
return ;
}
this . broadcastNominations ( ) ;
2022-03-19 16:11:24 -07:00
return ;
case "help" : //displays available commands
2022-08-31 11:24:12 +02:00
let msg = "" ;
2022-09-06 13:13:24 +02:00
msg += ( ` !vote \n > choices \n > results \n ` ) ;
if ( isAdmin ) msg += ( ` \n Admin only: \n > start \n > restart \n > cancel \n > broadcast ` ) ;
2022-08-13 12:05:40 +02:00
2022-09-02 12:52:42 +02:00
await this . warn ( steamID , msg + ` \n MapVote SquadJS plugin built by JetDave ` ) ;
2022-03-19 16:11:24 -07:00
return ;
2022-11-21 15:15:11 +01:00
case "endsqjs" :
case "closesqjs" :
case "stopesqjs" :
case "restartsqjs" :
if ( ! isAdmin ) return ;
2023-01-15 20:46:59 +01:00
await this . warn ( steamID , "Saving persistent data.\nTerminating SquadJS process.\nIf managed by a process manager it will automatically restart." )
2023-01-21 21:13:07 +01:00
this . savePersistentData ( steamID ) ;
2022-11-21 15:15:11 +01:00
process . exit ( 0 ) ;
return ;
2022-03-19 16:11:24 -07:00
default :
//give them an error
2022-09-02 12:52:42 +02:00
await this . warn ( steamID , ` Unknown vote subcommand: ${ subCommand } ` ) ;
2022-03-19 16:11:24 -07:00
return ;
}
2022-08-13 12:05:40 +02:00
2022-03-19 16:11:24 -07:00
}
2022-08-13 12:05:40 +02:00
2022-03-19 16:11:24 -07:00
updateNextMap ( ) //sets next map to current mapvote winner, if there is a tie will pick at random
{
2022-10-01 14:30:26 +02:00
this . lastMapUpdate = new Date ( ) ;
2022-09-16 00:41:20 +02:00
let cpyWinners = this . currentWinners ;
let skipSetNextMap = false ;
if ( cpyWinners . find ( e => e == this . nominations [ 0 ] ) ) {
2022-09-16 01:16:04 +02:00
if ( cpyWinners . length > 1 ) {
delete cpyWinners [ cpyWinners . indexOf ( this . nominations [ 0 ] ) ]
cpyWinners = cpyWinners . filter ( e => e != null )
}
2022-09-16 00:41:20 +02:00
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 ( )
2022-09-21 16:43:37 +02:00
this . broadcast ( "The previous Map Vote has been canceled and a new one has been generated!" )
2022-09-16 20:35:08 +02:00
this . beginVoting ( true , this . newVoteOptions . steamid , this . newVoteOptions . cmdLayers )
2022-09-16 00:41:20 +02:00
}
2022-09-16 01:04:04 +02:00
} , 2 * 60 * 1000 )
2022-09-16 20:35:08 +02:00
setTimeout ( this . broadcastNominations , 1 * 60 * 1000 )
2022-09-16 00:41:20 +02:00
}
}
}
2022-10-21 00:48:57 +02:00
const nextMap = randomElement ( cpyWinners ) ;
2022-09-16 00:41:20 +02:00
if ( ! skipSetNextMap ) {
2022-10-04 19:48:38 +02:00
const baseDataExist = this && this . server ;
const layerDataExist = this . server . nextLayer && this . server . nextLayer . layerid ;
if ( baseDataExist && ( ! layerDataExist || this . server . nextLayer . layerid != nextMap ) )
2022-10-03 12:28:50 +02:00
this . server . rcon . execute ( ` AdminSetNextLayer ${ nextMap } ` ) ;
2022-10-04 19:48:38 +02:00
else console . log ( "[MapVote][1] Bad data (this/this.server). Next layer not set to prevent errors." ) ;
2022-09-16 00:41:20 +02:00
}
2022-10-21 00:48:57 +02:00
return nextMap ;
2022-03-19 16:11:24 -07:00
}
2022-08-13 12:05:40 +02:00
matchLayers ( builtString ) {
2022-04-14 13:55:02 -07:00
return Layers . layers . filter ( element => element . layerid . includes ( builtString ) ) ;
2022-04-14 02:08:53 -07:00
}
2022-08-13 12:05:40 +02:00
getMode ( nomination , currentMode ) {
2022-04-14 13:55:02 -07:00
const mapName = nomination . map ;
let modes = nomination . modes ;
2022-08-13 12:05:40 +02:00
let mode = modes [ 0 ] ;
2022-04-14 02:08:53 -07:00
if ( mode === "Any" )
modes = this . voteRules . modes ;
2022-08-13 12:05:40 +02:00
if ( this . voteRules . mode _repeat _blacklist . includes ( currentMode ) ) {
2022-04-14 02:08:53 -07:00
modes = modes . filter ( mode => ! mode . includes ( currentMode ) ) ;
}
2022-08-13 12:05:40 +02:00
while ( modes . length > 0 ) {
2022-04-23 19:21:07 -07:00
mode = randomElement ( modes ) ;
2022-04-14 02:08:53 -07:00
modes = modes . filter ( elem => elem !== mode ) ;
2022-04-23 19:21:07 -07:00
if ( this . matchLayers ( ` ${ mapName } _ ${ mode } ` ) . length > 0 )
2022-04-14 02:08:53 -07:00
break ;
}
return mode ;
}
2022-03-19 16:11:24 -07:00
//TODO: right now if version is set to "Any" no caf layers will be selected
2022-11-10 22:05:21 +01:00
populateNominations ( steamid = null , cmdLayers = [ ] , bypassRaasFilter = false , tries = 10 ) //gets nomination strings from layer options
2022-03-19 16:11:24 -07:00
{
2022-09-18 01:23:17 +02:00
this . options . gamemodeWhitelist . forEach ( ( e , k , a ) => a [ k ] = e . toUpperCase ( ) ) ;
2022-08-13 12:05:40 +02:00
// 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" ,
2022-04-23 19:21:07 -07:00
}
2022-03-19 16:11:24 -07:00
this . nominations = [ ] ;
2022-08-13 12:05:40 +02:00
this . tallies = [ ] ;
this . factionStrings = [ ] ;
let rnd _layers = [ ] ;
2022-11-18 20:46:03 +01:00
2022-09-21 16:43:37 +02:00
const sanitizedLayers = Layers . layers . filter ( ( l ) => l . layerid && l . map ) ;
const maxOptions = this . options . showRerollOption ? 5 : 6 ;
2022-11-21 15:15:11 +01:00
const recentlyPlayedMaps = this . objArrToValArr ( this . server . layerHistory . slice ( 0 , this . options . numberRecentMapsToExlude ) , "layer" , "map" , "name" ) ;
this . verbose ( 1 , "Recently played maps: " + recentlyPlayedMaps . join ( ', ' ) ) ; //recentlyPlayedMaps.filter((l) => l && l.map && l.map.name).map((l) => l.map.name).join(', '))
if ( ! cmdLayers || cmdLayers . length == 0 ) {
2022-09-21 16:43:37 +02:00
const all _layers = sanitizedLayers . filter ( ( l ) =>
this . options . gamemodeWhitelist . includes ( l . gamemode . toUpperCase ( ) ) &&
! [ this . server . currentLayer ? this . server . currentLayer . map . name : null , ... recentlyPlayedMaps ] . includes ( l . map . name ) &&
(
2022-10-25 19:51:05 +02:00
( this . options . layerFilteringMode . toLowerCase ( ) == "blacklist" && ! this . options . layerLevelBlacklist . find ( ( fl ) => this . getLayersFromStringId ( fl ) . map ( ( e ) => e . layerid ) . includes ( l . layerid ) ) ) ||
2022-10-20 23:41:43 +02:00
(
this . options . layerFilteringMode . toLowerCase ( ) == "whitelist"
2022-10-25 19:51:05 +02:00
&& this . options . layerLevelWhitelist . find ( ( fl ) => this . getLayersFromStringId ( fl ) . map ( ( e ) => e . layerid ) . includes ( l . layerid ) )
&& ! ( this . options . applyBlacklistToWhitelist && this . options . layerLevelBlacklist . find ( ( fl ) => this . getLayersFromStringId ( fl ) . map ( ( e ) => e . layerid ) . includes ( l . layerid ) ) )
2022-10-20 23:41:43 +02:00
)
2022-09-21 16:43:37 +02:00
)
2022-11-22 19:54:51 +01:00
&& ! ( this . options . factionsBlacklist . find ( ( f ) => [ getTranslation ( l . teams [ 0 ] ) , getTranslation ( l . teams [ 1 ] ) ] . includes ( f ) ) )
2022-09-21 16:43:37 +02:00
) ;
for ( let i = 1 ; i <= maxOptions ; i ++ ) {
2022-10-21 00:48:57 +02:00
const needMoreRAAS = ! bypassRaasFilter && rnd _layers . filter ( ( l ) => l . gamemode === 'RAAS' ) . length < this . options . minRaasEntries ;
let l , maxtries = 20 ;
do l = randomElement ( needMoreRAAS ? all _layers . filter ( ( l ) => l . gamemode . toLowerCase ( ) == "raas" ) : all _layers ) ; while ( ( rnd _layers . find ( lf => lf . layerid == l . layerid ) || rnd _layers . filter ( lf => lf . map . name == l . map . name ) . length > ( this . options . allowedSameMapEntries - 1 ) ) && -- maxtries >= 0 )
2022-10-25 00:01:18 +02:00
if ( maxtries > 0 && l ) {
// this.verbose(1,"Testing layer",l, maxtries);
2022-09-08 11:49:08 +02:00
rnd _layers . push ( l ) ;
2022-09-16 00:41:20 +02:00
this . nominations [ i ] = l . layerid
this . tallies [ i ] = 0 ;
this . factionStrings [ i ] = getTranslation ( l . teams [ 0 ] ) + "-" + getTranslation ( l . teams [ 1 ] ) ;
2022-09-08 11:49:08 +02:00
}
2022-03-19 16:11:24 -07:00
}
2022-10-21 00:48:57 +02:00
// if (!bypassRaasFilter && this.options.gamemodeWhitelist.includes("RAAS") && rnd_layers.filter((l) => l.gamemode === 'RAAS').length < Math.floor(maxOptions / 2)) this.populateNominations();
2022-10-04 00:52:47 +02:00
if ( this . nominations . length == 0 ) {
2022-11-18 00:22:21 +01:00
if ( -- tries > 0 ) this . populateNominations ( steamid , cmdLayers , bypassRaasFilter , tries ) ;
else this . warn ( "" )
2022-10-04 00:52:47 +02:00
return ;
}
2022-08-13 12:05:40 +02:00
} else {
2022-11-21 15:15:11 +01:00
if ( cmdLayers . length == 1 ) while ( cmdLayers . length < maxOptions ) cmdLayers . push ( cmdLayers [ 0 ] )
2022-11-18 20:46:03 +01:00
if ( cmdLayers . length <= maxOptions ) {
2022-09-16 01:04:04 +02:00
let i = 1 ;
2022-08-13 12:05:40 +02:00
for ( let cl of cmdLayers ) {
const cls = cl . split ( '_' ) ;
2022-11-18 20:46:03 +01:00
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 , '' ) ) )
2022-11-20 22:44:54 +01:00
&& ! ( this . options . factionsBlacklist . find ( ( f ) => [ getTranslation ( l . teams [ 0 ] ) , getTranslation ( l . teams [ 1 ] ) ] . includes ( f ) ) )
2022-11-18 20:46:03 +01:00
) ) ;
2022-10-04 01:02:51 +02:00
let l , maxtries = 10 ;
2022-10-04 19:48:38 +02:00
do l = randomElement ( fLayers ) ; while ( ( rnd _layers . filter ( lf => lf . map . name == l . map . name ) . length > ( this . options . allowedSameMapEntries - 1 ) ) && -- maxtries >= 0 )
2022-09-06 13:13:24 +02:00
if ( l ) {
2022-09-02 00:33:30 +02:00
rnd _layers . push ( l ) ;
2022-09-16 00:41:20 +02:00
this . nominations [ i ] = l . layerid
this . tallies [ i ] = 0 ;
this . factionStrings [ i ] = getTranslation ( l . teams [ 0 ] ) + "-" + getTranslation ( l . teams [ 1 ] ) ;
2022-09-16 01:04:04 +02:00
i ++ ;
2022-09-02 00:33:30 +02:00
}
2022-08-13 12:05:40 +02:00
}
2022-09-16 01:04:04 +02:00
}
else if ( steamid ) {
this . warn ( steamid , "You cannot start a vote with more than " + maxOptions + " options" ) ;
return ;
}
2022-03-19 16:11:24 -07:00
}
2022-04-14 02:08:53 -07:00
2022-09-16 00:41:20 +02:00
if ( this . options . showRerollOption ) {
2022-09-16 01:04:04 +02:00
if ( this . nominations . length > 5 ) {
2022-09-16 20:35:08 +02:00
this . nominations . splice ( 6 , 1 ) ;
this . tallies . splice ( 6 , 1 ) ;
this . factionStrings . splice ( 6 , 1 ) ;
2022-09-16 01:04:04 +02:00
}
2022-09-16 20:35:08 +02:00
this . newVoteOptions . steamid = steamid ;
this . newVoteOptions . bypassRaasFilter = bypassRaasFilter ;
this . newVoteOptions . cmdLayers = cmdLayers ;
2022-09-16 01:04:04 +02:00
this . nominations [ 0 ] = "Reroll vote list with random options"
2022-09-16 00:41:20 +02:00
this . tallies [ 0 ] = 0 ;
this . factionStrings [ 0 ] = "" ;
2022-09-16 20:35:08 +02:00
2022-03-19 16:11:24 -07:00
}
2022-04-14 02:08:53 -07:00
2022-11-20 22:44:54 +01:00
function getTranslation ( layer ) {
if ( translations [ layer . faction ] ) return translations [ layer . faction ]
else if ( layer . faction ) {
const f = layer . faction . split ( ' ' ) ;
2022-08-13 12:05:40 +02:00
let fTag = "" ;
f . forEach ( ( e ) => { fTag += e [ 0 ] } ) ;
return fTag . toUpperCase ( ) ;
2022-11-18 13:12:11 +01:00
} else return "Unknown"
2022-03-19 16:11:24 -07:00
}
}
//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
2022-09-08 11:51:26 +02:00
beginVoting ( force = false , steamid = null , cmdLayers = [ ] ) {
2022-11-07 13:03:26 +01:00
if ( ! this . options . automaticVoteStart && ! force ) return ;
2022-08-25 15:43:38 +02:00
this . verbose ( 1 , "Starting vote" )
2022-03-19 16:11:24 -07:00
const playerCount = this . server . players . length ;
const minPlayers = this . options . minPlayersForVote ;
2022-04-02 16:04:23 -07:00
if ( this . votingEnabled ) //voting has already started
return ;
2022-11-07 13:03:26 +01:00
2022-08-13 12:05:40 +02:00
if ( playerCount < minPlayers && ! force ) {
2022-10-20 23:59:28 +02:00
this . autovotestart = setTimeout ( ( ) => { this . beginVoting ( force , steamid , cmdLayers ) } , 60 * 1000 )
2022-03-19 16:11:24 -07:00
return ;
}
2022-10-20 23:59:28 +02:00
2022-10-25 19:51:05 +02:00
if ( this . options . votingDuration > 0 ) this . timeout _ps . push ( setTimeout ( this . endVotingGently , this . options . votingDuration * 60 * 1000 ) )
2022-04-02 16:04:23 -07:00
2022-03-19 16:11:24 -07:00
// these need to be reset after reenabling voting
this . trackedVotes = { } ;
this . tallies = [ ] ;
2022-08-13 12:05:40 +02:00
this . populateNominations ( steamid , cmdLayers ) ;
2022-03-19 16:11:24 -07:00
this . votingEnabled = true ;
2022-08-13 12:05:40 +02:00
this . firstBroadcast = true ;
this . broadcastNominations ( ) ;
2022-03-19 16:11:24 -07:00
this . broadcastIntervalTask = setInterval ( this . broadcastNominations , toMils ( this . options . voteBroadcastInterval ) ) ;
}
2022-08-13 12:05:40 +02:00
2023-01-15 20:46:59 +01:00
async endVotingGently ( ) {
2022-10-25 19:51:05 +02:00
this . endVoting ( ) ;
2023-01-15 22:53:35 +01:00
const winnerLayer = Layers . layers . find ( ( l ) => l . layerid == this . updateNextMap ( ) ) ;
const fancyWinner = this . formatFancyLayer ( winnerLayer ) ;
2023-01-20 01:05:29 +01:00
if ( this . showWinnerBroadcastMessage ) await this . broadcast ( this . options . voteWinnerBroadcastMessage + fancyWinner ) ;
2023-01-15 22:53:35 +01:00
if ( ! this . options . logToDiscord ) return
return await this . sendDiscordMessage ( {
embed : {
title : ` Vote winner: ${ fancyWinner } ` ,
color : 16761867 ,
fields : [
{
name : 'Map' ,
value : winnerLayer . map . name ,
inline : true
} ,
{
name : 'Gamemode' ,
value : winnerLayer . gamemode ,
inline : true
} ,
{
name : 'Version' ,
value : winnerLayer . version ,
inline : true
} ,
{
name : 'LayerID' ,
value : winnerLayer . layerid ,
inline : false
} ,
{
name : 'Team 1' ,
value : winnerLayer . teams [ 0 ] . faction ,
inline : true
} ,
{
name : 'Team 2' ,
value : winnerLayer . teams [ 1 ] . faction ,
inline : true
} ,
] ,
image : {
url : ` https://squad-data.nyc3.cdn.digitaloceanspaces.com/main/ ${ winnerLayer . layerid } .jpg `
} ,
} ,
timestamp : ( new Date ( ) ) . toISOString ( )
} ) ;
2022-10-25 19:51:05 +02:00
}
2022-08-13 12:05:40 +02:00
endVoting ( ) {
2022-03-19 16:11:24 -07:00
this . votingEnabled = false ;
clearInterval ( this . broadcastIntervalTask ) ;
2022-09-16 01:04:04 +02:00
clearTimeout ( this . newVoteTimeout ) ;
this . newVoteTimeout = null ;
2022-03-19 16:11:24 -07:00
this . broadcastIntervalTask = null ;
}
2022-09-06 13:13:24 +02:00
objArrToValArr ( arr , ... key ) {
2022-08-25 21:43:06 +02:00
let vet = [ ] ;
2022-09-06 13:13:24 +02:00
for ( let o of arr ) {
let obj = o ;
for ( let k of key ) {
if ( obj [ k ] )
obj = obj [ k ] ;
}
vet . push ( obj ) ;
}
2022-08-25 21:43:06 +02:00
return vet ;
}
2022-03-19 16:11:24 -07:00
//sends a message about nominations through a broadcast
2022-08-13 12:05:40 +02:00
//NOTE: max squad broadcast message length appears to be 485 characters
2022-03-19 16:11:24 -07:00
//Note: broadcast strings with multi lines are very strange
2022-08-13 12:05:40 +02:00
async broadcastNominations ( ) {
2022-08-25 15:43:38 +02:00
if ( this . nominations . length > 0 && this . votingEnabled ) {
2022-09-16 22:41:55 +02:00
await this . broadcast ( this . options . voteBroadcastMessage ) ;
2022-08-13 12:05:40 +02:00
let nominationStrings = [ ] ;
2023-01-21 00:49:06 +01:00
2022-09-16 00:41:20 +02:00
for ( let choice = 1 ; choice < this . nominations . length ; choice ++ ) {
2022-08-13 12:05:40 +02:00
choice = Number ( choice ) ;
2022-09-16 00:41:20 +02:00
let vLayer = Layers . layers . find ( e => e . layerid == this . nominations [ choice ] ) ;
2023-01-21 00:49:06 +01:00
// const allVecs = vLayer.teams[0].vehicles.concat(vLayer.teams[1].vehicles);
const helis = vLayer . teams [ 0 ] . numberOfHelicopters + vLayer . teams [ 1 ] . numberOfHelicopters
const tanks = vLayer . teams [ 0 ] . numberOfTanks + vLayer . teams [ 1 ] . numberOfTanks
let assets = [ ] ;
if ( helis > 0 ) assets . push ( 'Helis' ) ;
if ( tanks > 0 ) assets . push ( 'Tanks' ) ;
const vehiclesString = this . options . includeMainAssetsInBroadcast ? ' ' + assets . join ( '-' ) : '' ;
nominationStrings . push ( formatChoice ( choice , vLayer . map . name + ' ' + vLayer . gamemode + ' ' + this . factionStrings [ choice ] + vehiclesString , this . tallies [ choice ] , ( this . options . hideVotesCount || this . firstBroadcast ) ) ) ;
if ( nominationStrings . length == 3 ) {
await this . broadcast ( nominationStrings . join ( "\n" ) ) ;
nominationStrings = [ ] ;
}
2022-08-13 12:05:40 +02:00
}
2023-01-21 00:49:06 +01:00
2022-09-16 00:41:20 +02:00
if ( this . nominations [ 0 ] ) nominationStrings . push ( formatChoice ( 0 , this . nominations [ 0 ] , this . tallies [ 0 ] , ( this . options . hideVotesCount || this . firstBroadcast ) ) )
2022-09-02 12:52:42 +02:00
await this . broadcast ( nominationStrings . join ( "\n" ) ) ;
2022-09-17 01:10:05 +02:00
2022-09-15 14:17:43 -05:00
if ( this . firstBroadcast )
2022-09-17 01:10:05 +02:00
await this . logVoteToDiscord ( nominationStrings . join ( "\n" ) )
2022-08-13 12:05:40 +02:00
this . firstBroadcast = false ;
}
2022-03-19 16:11:24 -07:00
//const winners = this.currentWinners;
//await this.msgBroadcast(`Current winner${winners.length > 1 ? "s" : ""}: ${winners.join(", ")}`);
}
2022-10-21 00:48:57 +02:00
formatFancyLayer ( layer ) {
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" ,
}
const factionString = getTranslation ( layer . teams [ 0 ] ) + "-" + getTranslation ( layer . teams [ 1 ] ) ;
return layer . map . name + ' ' + layer . gamemode + ' ' + factionString
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 ( ) ;
}
}
}
2022-03-19 16:11:24 -07:00
2022-10-25 00:01:18 +02:00
getLayersFromStringId ( stringid ) {
const cls = stringid . toLowerCase ( ) . split ( '_' ) ;
const ret = Layers . layers . 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 ] || parseInt ( l . version . toLowerCase ( ) . replace ( /v/gi , '' ) ) == parseInt ( cls [ 2 ] . replace ( /v/gi , '' ) ) ) ) ) ;
// this.verbose(1,"layers from string",stringid,cls,ret)
return ret ;
}
2022-08-13 12:05:40 +02:00
async directMsgNominations ( steamID ) {
2022-08-25 15:43:38 +02:00
let strMsg = "" ;
2022-08-13 12:05:40 +02:00
for ( let choice in this . nominations ) {
choice = Number ( choice ) ;
2023-01-21 21:13:07 +01:00
let vLayer = Layers . layers . find ( e => e . layerid == this . nominations [ choice ] ) ;
// const allVecs = vLayer.teams[0].vehicles.concat(vLayer.teams[1].vehicles);
// const helis = vLayer?.teams[ 0 ].numberOfHelicopters || 0 + vLayer?.teams[ 1 ].numberOfHelicopters || 0
// const tanks = vLayer?.teams[ 0 ].numberOfTanks || 0 + vLayer?.teams[ 1 ].numberOfTanks || 0
// let assets = [];
// if (helis > 0) assets.push('Helis');
// if (tanks > 0) assets.push('Tanks');
// const vehiclesString = ' ' + assets.join('-');
2022-08-25 15:43:38 +02:00
// await this.msgDirect(steamID, formatChoice(choice, this.nominations[ choice ], this.tallies[ choice ]));
2023-01-21 21:13:07 +01:00
strMsg += ( steamID , formatChoice ( choice , this . nominations [ choice ] , this . tallies [ choice ] ) ) + ` H: ${ helis } -T: ${ tanks } ` + "\n" ;
2022-08-13 12:05:40 +02:00
}
2022-08-25 15:43:38 +02:00
strMsg . trim ( ) ;
2022-09-11 01:01:43 +02:00
if ( steamID ) this . warn ( steamID , strMsg )
2022-08-13 12:05:40 +02:00
2022-08-25 15:43:38 +02:00
// const winners = this.currentWinners;
// await this.msgDirect(steamID, `Current winner${winners.length > 1 ? "s" : ""}: ${winners.join(", ")}`);
2022-03-19 16:11:24 -07:00
}
//counts a vote from a player and adds it to tallies
2022-08-13 12:05:40 +02:00
async registerVote ( steamID , nominationIndex , playerName ) {
2022-09-16 00:41:20 +02:00
// nominationIndex -= 1; // shift indices from display range
2022-08-13 12:05:40 +02:00
if ( nominationIndex < 0 || nominationIndex > this . nominations . length ) {
2022-09-02 12:52:42 +02:00
await this . warn ( steamID , ` [Map Vote] ${ playerName } : invalid map number, typ !vote results to see map numbers ` ) ;
2022-03-19 16:11:24 -07:00
return ;
}
2022-08-13 12:05:40 +02:00
const previousVote = this . trackedVotes [ steamID ] ;
this . trackedVotes [ steamID ] = nominationIndex ;
this . tallies [ nominationIndex ] += 1 ;
if ( previousVote !== undefined )
this . tallies [ previousVote ] -= 1 ;
2022-09-16 01:04:04 +02:00
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) ` ) ) ;
2022-08-13 12:05:40 +02:00
// 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`);
2022-03-19 16:11:24 -07:00
}
2022-08-13 12:05:40 +02:00
2022-09-17 01:33:21 +02:00
async logVoteToDiscord ( message ) {
if ( ! this . options . logToDiscord ) return
2023-01-15 22:53:35 +01:00
return await this . sendDiscordMessage ( {
2022-09-17 01:33:21 +02:00
embed : {
title : 'Vote Started' ,
color : 16761867 ,
fields : [
{
name : 'Options:' ,
value : ` ${ message } `
}
]
2022-09-17 01:39:55 +02:00
} ,
timestamp : ( new Date ( ) ) . toISOString ( )
2022-09-17 01:33:21 +02:00
} ) ;
}
2022-09-17 01:10:05 +02:00
2022-03-19 16:11:24 -07:00
//removes a players vote if they disconnect from the sever
2022-08-13 12:05:40 +02:00
clearVote ( ) {
2022-03-19 16:11:24 -07:00
const currentPlayers = this . server . players . map ( ( p ) => p . steamID ) ;
2022-08-13 12:05:40 +02:00
for ( const steamID in this . trackedVotes ) {
if ( ! ( currentPlayers . includes ( steamID ) ) ) {
const vote = this . trackedVotes [ steamID ] ;
this . tallies [ vote ] -= 1 ;
delete this . trackedVotes [ steamID ] ;
2022-03-19 16:11:24 -07:00
}
2022-08-13 12:05:40 +02:00
}
2022-03-19 16:11:24 -07:00
}
2022-11-21 15:15:11 +01:00
restorePersistentData ( ) {
this . verbose ( 1 , ` Restoring persistent data from: ${ this . options . persistentDataFile } ` )
if ( this . options . persistentDataFile == "" ) return ;
if ( ! fs . existsSync ( this . options . persistentDataFile ) ) return ;
let bkData = fs . readFileSync ( this . options . persistentDataFile ) ;
if ( bkData == "" ) return ;
try {
bkData = JSON . parse ( bkData )
} catch ( e ) {
this . verbose ( 1 , "Error restoring persistent data" , e )
return
}
2023-01-21 21:13:07 +01:00
if ( bkData . manualRestartSender && bkData . manualRestartSender != "" ) this . warn ( bkData . manualRestartSender , ` SquadJS has completed the restart. \n Persistent data restored. ` )
for ( let k in bkData . server ) this . server [ k ] = bkData . server [ k ] ;
2022-11-21 15:15:11 +01:00
const maxSecondsDiffierence = 60
if ( ( new Date ( ) - new Date ( bkData . saveDateTime ) ) / 1000 > maxSecondsDiffierence ) return
this . verbose ( 1 , "Restoring data:" , bkData )
// if (bkData.custom.layerHistory) this.server.layerHistory = Layers.layers.filter(l => bkData.custom.layerHistory.includes(l.layerid));
this . verbose ( 1 , "Recently played maps: " + this . server . layerHistory . filter ( ( l ) => l && l . map && l . map . name ) . map ( ( l ) => l . layer . map . name ) . join ( ', ' ) )
for ( let k in bkData . plugin ) this [ k ] = bkData . plugin [ k ] ;
if ( this . votingEnabled ) {
this . broadcastIntervalTask = setInterval ( this . broadcastNominations , toMils ( this . options . voteBroadcastInterval ) ) ;
}
}
2023-01-21 21:13:07 +01:00
savePersistentData ( steamID = null ) {
2022-11-21 15:15:11 +01:00
if ( this . options . persistentDataFile == "" ) return ;
const saveDt = {
custom : {
// layerHistory: this.server.layerHistory.slice(0, this.options.numberRecentMapsToExlude * 2).filter(l => l && l.layerid).map(l => l.layerid),
} ,
server : {
layerHistory : this . server . layerHistory
} ,
plugin : {
nominations : this . nominations ,
trackedVotes : this . trackedVotes ,
tallies : this . tallies ,
votingEnabled : this . votingEnabled ,
factionStrings : this . factionStrings ,
firstBroadcast : this . firstBroadcast
} ,
2023-01-21 21:13:07 +01:00
manualRestartSender : steamID ,
2022-11-21 15:15:11 +01:00
saveDateTime : new Date ( )
}
// this.verbose(1, `Saving persistent data to: ${this.options.persistentDataFile}\n`, saveDt.server.layerHistory)
fs . writeFileSync ( this . options . persistentDataFile , JSON . stringify ( saveDt , null , 2 ) )
}
2022-03-19 16:11:24 -07:00
//calculates the current winner(s) of the vote and returns thier strings in an array
2022-08-13 12:05:40 +02:00
get currentWinners ( ) {
2022-03-19 16:11:24 -07:00
const ties = [ ] ;
2022-08-13 12:05:40 +02:00
2022-03-19 16:11:24 -07:00
let highestScore = - Infinity ;
2022-08-13 12:05:40 +02:00
for ( let choice in this . tallies ) {
const score = this . tallies [ choice ] ;
if ( score < highestScore )
2022-03-19 16:11:24 -07:00
continue ;
2022-08-13 12:05:40 +02:00
else if ( score > highestScore ) {
2022-03-19 16:11:24 -07:00
highestScore = score ;
ties . length = 0 ;
ties . push ( choice ) ;
}
else // equal
ties . push ( choice ) ;
}
2022-08-13 12:05:40 +02:00
return ties . map ( i => this . nominations [ i ] ) ;
2022-03-19 16:11:24 -07:00
}
2022-11-18 00:22:21 +01:00
async updateLayerList ( ) {
2022-11-18 13:12:11 +01:00
// Layers.layers = [];
2022-11-18 00:22:21 +01:00
2023-01-19 16:21:23 +01:00
this . verbose ( 1 , 'Pulling updated layer list...' ) ;
2022-11-18 00:22:21 +01:00
const response = await axios . get (
2023-01-19 16:21:23 +01:00
'https://raw.githubusercontent.com/Squad-Wiki/squad-wiki-pipeline-map-data/master/completed_output/_Current%20Version/finished.json'
2022-11-18 00:22:21 +01:00
) ;
for ( const layer of response . data . Maps ) {
2022-12-18 14:08:38 -05:00
if ( ! Layers . layers . find ( ( e ) => e . layerid == layer . rawName ) ) Layers . layers . push ( new Layer ( layer ) ) ;
2022-11-18 00:22:21 +01:00
}
2022-11-18 13:12:11 +01:00
this . verbose ( 1 , 'Layer list updated' ) ;
2022-11-18 00:22:21 +01:00
}
2022-09-17 01:10:05 +02:00
}
2022-09-15 14:17:43 -05:00
2022-09-17 01:10:05 +02:00
function randomElement ( array ) {
return array [ Math . floor ( Math . random ( ) * array . length ) ] ;
2022-09-06 15:32:48 +02:00
}
2022-09-17 01:10:05 +02:00
function formatChoice ( choiceIndex , mapString , currentVotes , firstBroadcast ) {
return ` ${ choiceIndex } ➤ ${ mapString } ` + ( ! firstBroadcast ? ` ( ${ currentVotes } ) ` : "" ) ;
// return `${choiceIndex + 1}❱ ${mapString} (${currentVotes} votes)`
}
function toMils ( min ) {
return min * 60 * 1000 ;
2022-11-21 15:15:11 +01:00
}