From 46532384f70622fcf2419e49ab272c4bb45e851b Mon Sep 17 00:00:00 2001 From: Taraman17 Date: Fri, 23 Feb 2024 10:31:27 +0000 Subject: [PATCH] Add support for workshop maps in CS2 --- config.js | 14 +- modules/apiV10.js | 21 +- modules/configClass.js | 16 + modules/logreceive.js | 240 +++++++------- modules/serverInfo.js | 50 ++- modules/sharedFunctions.js | 207 +++++++----- public/js/gameserver.js | 638 +++++++++++++++++++------------------ public/js/onload.js | 226 +++++++------ serverControl.js | 2 +- 9 files changed, 771 insertions(+), 643 deletions(-) mode change 100644 => 100755 modules/apiV10.js mode change 100644 => 100755 modules/configClass.js mode change 100644 => 100755 modules/logreceive.js mode change 100644 => 100755 modules/serverInfo.js mode change 100644 => 100755 modules/sharedFunctions.js mode change 100644 => 100755 serverControl.js diff --git a/config.js b/config.js index 5d21efe..9f6ddef 100644 --- a/config.js +++ b/config.js @@ -14,10 +14,20 @@ /* Optional settings */ // Anything you want your server command line to have additional to: - // -console -usercon -ip 0.0.0.0 +sv_logfile 1 -serverlogging +logaddress_add_http "http://${this._localIp}:${this.logPort}/log" + // -console -usercon -ip 0.0.0.0 +sv_logfile 1 -serverlogging + // +logaddress_add_http "http://${this._localIp}:${this.logPort}/log" "csgoOptionalArgs": "", // steam serverToken for public access. To get one see https://steamcommunity.com/dev/managegameservers "serverToken": "", + // Steam Web API token. Needed to get mapdetails like thumbnail, etc + // See https://steamcommunity.com/dev/apikey how to get one. + "apiToken": "", + // Workshop Collection to host on the server. + "workshopCollection": "", + // List of workshop ids of maps to add to available maps. + "workshopMaps": [ // '23423523523525', + // '37281723987123' + ], // If you want to use a different name / location for the update script (absolute path). "updateScript": "", // Time in minutes, after which a new login is needed. @@ -42,7 +52,7 @@ // If you use https, add the path to the certificate files here. "httpsCertificate": "", "httpsPrivateKey": "", - // Optional: In case your CA is not trusted by default (e.g. letsencrypt), you can add + // In case your CA is not trusted by default (e.g. letsencrypt), you can add // the CA-Cert here. "httpsCa": "", // Change this to any string of your liking to make it harder for attackers to profile your cookies. diff --git a/modules/apiV10.js b/modules/apiV10.js old mode 100644 new mode 100755 index 63839ad..f5754f3 --- a/modules/apiV10.js +++ b/modules/apiV10.js @@ -3,7 +3,8 @@ * @requires node-pty * @requires express * @requires ./config.js - * @requires ./emitters.js + * @requires ./serverInfo.js + * @requires ./controlEmitter.js * @requires ./sharedFunctions.js */ @@ -601,7 +602,7 @@ router.get('/control/update', (req, res) => { * @apiName changemap * @apiGroup Control * - * @apiParam {string} mapname filename of the map without extension (.bsp) + * @apiParam {string/int} map name, title or workshopID of a map. * @apiParamExample {string} Map-example * cs_italy * @@ -619,16 +620,24 @@ router.get('/control/changemap', (req, res) => { if (serverInfo.serverState.operationPending == 'none') { controlEmitter.emit('exec', 'mapchange', 'start'); // only try to change map, if it exists on the server. - if (serverInfo.mapsAvail.includes(args.map)) { - sf.executeRcon(`map ${args.map}`).then((answer) => { - // Answer on sucess: + let map = sf.getMap(args.map); + if (map != undefined) { + let mapchangeCommand = ''; + if (map.official) { + mapchangeCommand = `map ${map.name}`; + } else { + mapchangeCommand = `host_workshop_map ${map.workshopID}` + } + + sf.executeRcon(mapchangeCommand).then((answer) => { + // Answer on success (unfortunately only available for official maps): // Changelevel to de_nuke // changelevel "de_nuke" // CHostStateMgr::QueueNewRequest( Changelevel (de_nuke), 5 ) // // Answer on failure: // changelevel de_italy: invalid map name - if (answer.indexOf(`CHostStateMgr::QueueNewRequest( Changelevel (${args.map})`) == -1) { + if (map.official && answer.indexOf(`CHostStateMgr::QueueNewRequest( Changelevel (${map.name})`) == -1) { // If the mapchange command fails, return failure immediately res.status(501).json({ "error": `Mapchange failed: ${answer}` }); controlEmitter.emit('exec', 'mapchange', 'fail'); diff --git a/modules/configClass.js b/modules/configClass.js old mode 100644 new mode 100755 index 8a06662..4749f1d --- a/modules/configClass.js +++ b/modules/configClass.js @@ -14,6 +14,9 @@ class config { return `-console -usercon -ip 0.0.0.0 +sv_logfile 1 -serverlogging +logaddress_add_http "http://${this._localIp}:${this.logPort}/log" ${this._userOptions.csgoOptionalArgs}`; } + get apiToken() { + return this._userOptions.apiToken; + } get rconPass() { return this._userOptions.rconPass; } @@ -22,6 +25,19 @@ class config { return this._userOptions.admins; } + get workshopCollection() { + return this._userOptions.workshopCollection; + } + set workshopCollection(id) { + this._userOptions.workshopCollection = id; + } + get workshopMaps() { + return this._userOptions.workshopMaps; + } + set workshopMaps(maps) { + this._userOptions.workshopMaps = maps; + } + get redirectPage() { if (this._userOptions.redirectPage) { return this._userOptions.redirectPage; diff --git a/modules/logreceive.js b/modules/logreceive.js old mode 100644 new mode 100755 index f2cc6aa..d83ad52 --- a/modules/logreceive.js +++ b/modules/logreceive.js @@ -1,119 +1,123 @@ -const express = require('express'); -var router = express.Router(); -const { exec } = require('child_process'); -var controlEmitter = require('./controlEmitter.js'); -const logger = require('./logger.js'); -var serverInfo = require('./serverInfo.js'); -const sf = require('./sharedFunctions.js'); -var cfg = require('./configClass.js'); - -router.post('/log', (req, res) => { - const data = req.body; - var logs = data.split(/\r\n|\r|\n/); - - logs.forEach(line => { - if (line.length >= 20) { - // Start authentication, when not authenticated. - if ((line.indexOf('Log file started') != -1) && !serverInfo.serverState.authenticated) { - // Start of logfile - // L 08/13/2020 - 21:48:49: Log file started (file "logs/L000_000_000_000_27015_202008132148_000.log") (game "/home/user/csgo_ds/csgo") (version "7929") - logger.verbose('start authenticating RCON'); - // Since authentication is a vital step for the API to work, we start it automatically - // once the server runs. - sf.authenticate().then((data) => { - logger.verbose(`authentication ${data.authenticated}`); - }).catch((data) => { - logger.verbose(`authentication ${data.authenticated}`); - }); - if (cfg.script('logStart') != '') { - exec(cfg.script('logStart')); - } - } else if (line.indexOf('Loading map ') != -1) { - // Start of map. - // L 10/13/2023 - 14:28:38: Loading map "de_anubis" - let rex = /Loading map \"(\S+)\"/g; - let matches = rex.exec(line); - let mapstring = matches[1]; - mapstring = sf.cutMapName(mapstring); - serverInfo.map = mapstring; - serverInfo.pause = false; - // since 'started map' is also reported on server-start, only emit on mapchange. - if (serverInfo.serverState.operationPending == 'mapchange') { - controlEmitter.emit('exec', 'mapchange', 'end'); - } - logger.verbose(`Started map: ${mapstring}`); - serverInfo.clearPlayers(); - serverInfo.newMatch(); - if (cfg.script('mapStart') != '') { - exec(cfg.script('mapStart')); - } - } else if (line.indexOf('World triggered "Match_Start" on') != -1) { - // Start of a new match. - // L 08/13/2020 - 21:49:26: World triggered "Match_Start" on "de_nuke" - logger.verbose('Detected match start.'); - sf.queryMaxRounds(); - serverInfo.newMatch(); - let rex = /World triggered "Match_Start" on "(.+)"/ - let matches = rex.exec(line) - serverInfo.map = matches[1]; - if (cfg.script('matchStart') != '') { - exec(cfg.script('matchStart')); - } - } else if (line.indexOf('World triggered "Round_Start"') != -1) { - // Start of round. - // L 08/13/2020 - 21:49:28: World triggered "Round_Start" - if (cfg.script('roundStart') != '') { - exec(cfg.script('roundStart')); - } - } else if (/Team \"\S+\" scored/.test(line)) { - // Team scores at end of round. - // L 02/10/2019 - 21:31:15: Team "CT" scored "1" with "2" players - // L 02/10/2019 - 21:31:15: Team "TERRORIST" scored "1" with "2" players - rex = /Team \"(\S)\S+\" scored \"(\d+)\"/g; - let matches = rex.exec(line); - serverInfo.score = matches; - } else if (line.indexOf('World triggered "Round_End"') != -1) { - // End of round. - // L 08/13/2020 - 22:24:22: World triggered "Round_End" - if (cfg.script('roundEnd') != '') { - exec(cfg.script('roundEnd')); - } - } else if (line.indexOf("Game Over:") != -1) { - // End of match. - // L 08/13/2020 - 22:24:22: Game Over: competitive 131399785 de_nuke score 16:9 after 35 min - if (cfg.script('matchEnd') != '') { - exec(cfg.script('matchEnd')); - } - } else if (/\".{1,32}<\d{1,3}><\[\w:\d:\d{1,10}\]>/.test(line)) { - // Player join or teamchange. - // 10/12/2023 - 16:06:38: "[Klosser] Taraman<2><[U:1:12610374]><>" entered the game - // 10/12/2023 - 18:57:47: "[Klosser] Taraman<2><[U:1:12610374]>" switched from team to - // 10/12/2023 - 18:59:25: "[Klosser] Taraman<2><[U:1:12610374]>" switched from team to - // 10/16/2023 - 16:31:59.699 - "[Klosser] Taraman<2><[U:1:12610374]>" disconnected (reason "NETWORK_DISCONNECT_DISCONNECT_BY_USER") - let rex = /\"(.{1,32})<\d{1,3}><\[(\w:\d:\d{1,10})\]>\" switched from team <\S{1,10}> to <(\S{1,10})>/g; - matches = rex.exec(line); - serverInfo.assignPlayer(matches[1], matches[2]); - } - } else if (line.indexOf('Log file closed') != -1) { - // end of current log file. (Usually on mapchange or server quit.) - // L 08/13/2020 - 22:25:00: Log file closed - logger.verbose('logfile closed!'); - if (cfg.script('logEnd') != '') { - exec(cfg.script('logEnd')); - } - } - } - }); - - res.status(200).send("Receiving logs"); -}); - +const express = require('express'); +var router = express.Router(); +const { exec } = require('child_process'); +var controlEmitter = require('./controlEmitter.js'); +const logger = require('./logger.js'); +var serverInfo = require('./serverInfo.js'); +const sf = require('./sharedFunctions.js'); +var cfg = require('./configClass.js'); + +router.post('/log', (req, res) => { + const data = req.body; + var logs = data.split(/\r\n|\r|\n/); + + logs.forEach(line => { + if (line.length >= 20) { + // Start authentication, when not authenticated. + if ((line.indexOf('Log file started') != -1) && !serverInfo.serverState.authenticated) { + // Start of logfile + // L 08/13/2020 - 21:48:49: Log file started (file "logs/L000_000_000_000_27015_202008132148_000.log") (game "/home/user/csgo_ds/csgo") (version "7929") + logger.verbose('start authenticating RCON'); + // Since authentication is a vital step for the API to work, we start it automatically + // once the server runs. + sf.authenticate().then((data) => { + logger.verbose(`authentication ${data.authenticated}`); + }).catch((data) => { + logger.verbose(`authentication ${data.authenticated}`); + }); + if (cfg.script('logStart') != '') { + exec(cfg.script('logStart')); + } + } else if (line.indexOf('Loading map ') != -1) { + // Start of map. + // L 10/13/2023 - 14:28:38: Loading map "de_anubis" + let rex = /Loading map \"(\S+)\"/g; + let matches = rex.exec(line); + let mapstring = matches[1]; + mapstring = sf.cutMapName(mapstring); + serverInfo.map = mapstring; + serverInfo.pause = false; + // since 'started map' is also reported on server-start, only emit on mapchange. + if (serverInfo.serverState.operationPending == 'mapchange') { + controlEmitter.emit('exec', 'mapchange', 'end'); + } + logger.verbose(`Started map: ${mapstring}`); + serverInfo.clearPlayers(); + serverInfo.newMatch(); + if (cfg.script('mapStart') != '') { + exec(cfg.script('mapStart')); + } + } else if (line.indexOf('World triggered "Match_Start" on') != -1) { + // Start of a new match. + // L 08/13/2020 - 21:49:26: World triggered "Match_Start" on "de_nuke" + logger.verbose('Detected match start.'); + sf.queryMaxRounds(); + serverInfo.newMatch(); + let rex = /World triggered "Match_Start" on "(.+)"/ + let matches = rex.exec(line) + serverInfo.map = matches[1]; + if (cfg.script('matchStart') != '') { + exec(cfg.script('matchStart')); + } + } else if (line.indexOf('World triggered "Round_Start"') != -1) { + // Start of round. + // L 08/13/2020 - 21:49:28: World triggered "Round_Start" + if (cfg.script('roundStart') != '') { + exec(cfg.script('roundStart')); + } + } else if (/Team \"\S+\" scored/.test(line)) { + // Team scores at end of round. + // L 02/10/2019 - 21:31:15: Team "CT" scored "1" with "2" players + // L 02/10/2019 - 21:31:15: Team "TERRORIST" scored "1" with "2" players + rex = /Team \"(\S)\S+\" scored \"(\d+)\"/g; + let matches = rex.exec(line); + serverInfo.score = matches; + } else if (line.indexOf('World triggered "Round_End"') != -1) { + // End of round. + // L 08/13/2020 - 22:24:22: World triggered "Round_End" + if (cfg.script('roundEnd') != '') { + exec(cfg.script('roundEnd')); + } + } else if (line.indexOf("Game Over:") != -1) { + // End of match. + // L 08/13/2020 - 22:24:22: Game Over: competitive 131399785 de_nuke score 16:9 after 35 min + if (cfg.script('matchEnd') != '') { + exec(cfg.script('matchEnd')); + } + } else if (/\".{1,32}<\d{1,3}><\[\w:\d:\d{1,10}\]>/.test(line)) { + // Player join or teamchange. + // 10/12/2023 - 16:06:38: "[Klosser] Taraman<2><[U:1:12610374]><>" entered the game + // 10/12/2023 - 18:57:47: "[Klosser] Taraman<2><[U:1:12610374]>" switched from team to + // 10/12/2023 - 18:59:25: "[Klosser] Taraman<2><[U:1:12610374]>" switched from team to + // 10/16/2023 - 16:31:59.699 - "[Klosser] Taraman<2><[U:1:12610374]>" disconnected (reason "NETWORK_DISCONNECT_DISCONNECT_BY_USER") + // "Strapper<6>" + let rex = /\"(.{1,32})<\d{1,3}><\[(\w:\d:\d{1,10})\]><\[(\w:\d:\d{1,10})\]>\" switched from team <\S{1,10}> to <(\S{1,10})>/g; + matches = rex.exec(line); + serverInfo.assignPlayer(matches[1], matches[2], matches[3]); + } else if (line.search(/\[\w:\d:\d{1,10}\]><\w{1,10}>\" \[.{1,5} .{1,5} .{1,5}\] killed \".{1,32}<\d{1,3}><\[\w:\d:\d{1,10}\]/) != -1) { + rex = /\[(\w:\d:\d{1,10})\]><\w{1,10}>\" \[.{1,5} .{1,5} .{1,5}\] killed \".{1,32}<\d{1,3}><\[(\w:\d:\d{1,10})\]/ + matches = rex.exec(line); + serverInfo.recordKill(matches[1], matches[2]); + } + } else if (line.indexOf('Log file closed') != -1) { + // end of current log file. (Usually on mapchange or server quit.) + // L 08/13/2020 - 22:25:00: Log file closed + logger.verbose('logfile closed!'); + if (cfg.script('logEnd') != '') { + exec(cfg.script('logEnd')); + } + } + } + }); + + res.status(200).send("Receiving logs"); +}); + module.exports = router; \ No newline at end of file diff --git a/modules/serverInfo.js b/modules/serverInfo.js old mode 100644 new mode 100755 index 53a4788..cb8ad1b --- a/modules/serverInfo.js +++ b/modules/serverInfo.js @@ -24,6 +24,7 @@ class serverInfo { this._mapsAvail = [] this._mapsDetails = [ //{ 'name': '', + // 'official': true/false, // 'title': '', // 'workshopID': '', // 'description': '', @@ -41,7 +42,9 @@ class serverInfo { this._players = [ //{ 'name': '', // 'steamID': '', - // 'team': '' } + // 'team': '', + // 'kills': 0, + // 'deaths': 0 } ]; // emitter to notify of changes @@ -92,7 +95,7 @@ class serverInfo { } get mapsDetails() { - return this._mapsAvail; + return this._mapsDetails; } set mapsDetails(newMapsDetails) { this._mapsDetails = newMapsDetails; @@ -189,32 +192,43 @@ class serverInfo { return this._players; } addPlayer(newPlayer) { - newPlayer.team = 'U'; - this._players.push(newPlayer); + if (this._players.find(x => x.steamID === newPlayer.steamID) != undefined) { + this._players.find(x => x.steamID === newPlayer.steamID).disconnected = false + } else { + newPlayer.team = 'U'; + newPlayer.kills = 0; + newPlayer.deaths = 0; + newPlayer.disconnected = false; + this._players.push(newPlayer); + } this.serverInfoChanged.emit('change'); } - assignPlayer(steamID, team) { - for (let i = 0; i < this._players.length; i++) { - if (this._players[i].steamID == steamID) { - this._players[i].team = team.substr(0, 1); - i = this._players.length; - } + assignPlayer(name, steamID, team) { + if (this._players.find(x => x.steamID === steamID) == undefined ) { + this.addPlayer({'name': name, 'steamID': steamID }); } + let player = this._players.find(x => x.steamID === steamID); + player.team = team.substr(0, 1); this.serverInfoChanged.emit('change'); } removePlayer(steamID) { - for (let i = 0; i < this._players.length; i++) { - if (this._players[i].steamID == steamID) { - this._players.splice(i, 1); - i = this._players.length; - } - } + this._players.find(x => x.steamID === steamID).disconnected = true; + // this._players.splice(this._players.findIndex(x => x.steamID === steamID), 1); this.serverInfoChanged.emit('change'); } clearPlayers() { this._players = []; this.serverInfoChanged.emit('change'); } + recordKill(killer, victim) { + let killPlayer = this._players.find(x => x.steamID === killer); + if (killPlayer != undefined) + killPlayer.kills += 1; + let victimPlayer = this._players.find(x => x.steamID === victim); + if (victimPlayer != undefined) + victimPlayer.deaths += 1; + this.serverInfoChanged.emit('change'); + } // Methods getAll() { @@ -232,6 +246,10 @@ class serverInfo { newMatch() { this._score.C = 0; this._score.T = 0; + for (let i in this._players) { + this._players[i].kills = 0; + this._players[i].deaths = 0; + } this.serverInfoChanged.emit('change'); } reset() { diff --git a/modules/sharedFunctions.js b/modules/sharedFunctions.js old mode 100644 new mode 100755 index 60fa19c..c5d67ee --- a/modules/sharedFunctions.js +++ b/modules/sharedFunctions.js @@ -5,6 +5,7 @@ const logger = require('./logger.js'); var cfg = require('./configClass.js'); var serverInfo = require('./serverInfo.js'); var controlEmitter = require('./controlEmitter.js'); +const config = require('../config.js'); rconQ = new queue({ "autostart": true, "timeout": 500, "concurrency": 1 }); @@ -71,101 +72,153 @@ function authenticate() { * Get available maps from server and store them in serverInfo * @return {Promise} - Promise object that yields the result of reload. */ -function reloadMaplist() { - return new Promise((resolve, reject) => { - - function _sendApiRequest(_mapName, mapId) { +async function reloadMaplist() { + return new Promise( async (resolve, reject) => { + function getWorkshopCollection(id) { return new Promise((resolve, reject) => { - let workshopInfo = ''; - - const options = { - hostname: 'api.steampowered.com', - path: '/ISteamRemoteStorage/GetPublishedFileDetails/v1/', - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' } - }; - var steamApiRequest = https.request(options, (res) => { + https.get(`https://api.steampowered.com/IPublishedFileService/GetDetails/v1?key=${cfg.apiToken}&publishedfileids[0]=${cfg.workshopCollection}&includechildren=true`, (res) => { let resData = ''; res.on('data', (dataChunk) => { resData += dataChunk; }); res.on('end', () => { try { - resJSON = JSON.parse(resData); - let previewLink = resJSON.response.publishedfiledetails[0].preview_url; - let title = resJSON.response.publishedfiledetails[0].title; - let workshopID = resJSON.response.publishedfiledetails[0].publishedfileid; - let description = resJSON.response.publishedfiledetails[0].description; - let tags = resJSON.response.publishedfiledetails[0].tags; - resolve({ "name": _mapName, "title": title, "workshopID": workshopID, "description": description, "previewLink": previewLink, "tags": tags }); + let colMaps = [] + let resJson = JSON.parse(resData); + resJson.response.publishedfiledetails[0].children.forEach((colMap) => { + colMaps.push(colMap.publishedfileid); + }) + resolve(colMaps); } catch (e) { - reject({ "name": _mapName, "title": "", "workshopID": "", "description": "", "previewLink": "", "tags": "" }); + reject([]); } }); + }).on('error', (error) => { + logger.warn(`Steam Workshop Collection request failed: ${error}`); + reject([]); }); + }); + } - steamApiRequest.on('error', error => { - logger.warn(`steamApiRequest not successful: ${error}`); - reject({ "name": _mapName, "title": "", "workshopID": "", "description": "", "previewLink": "", "tags": "" }); + function getMapDetails(mapIDs, official) { + return new Promise((resolve, reject) => { + let idString = ''; + let i = 0; + mapIDs.forEach( (mapId) => { + idString += `&publishedfileids[${i}]=${mapId}`; + i++; }); - steamApiRequest.write(`itemcount=1&publishedfileids%5B0%5D=${mapId}`); - steamApiRequest.end(); + https.get(`https://api.steampowered.com/IPublishedFileService/GetDetails/v1?key=${cfg.apiToken}${idString}&appid=730`, (res) => { + let resData = ''; + let returnDetails = []; + res.on('data', (dataChunk) => { + resData += dataChunk; + }); + res.on('end', () => { + if (res.statusCode != 200) { + logger.warn(`getMapDetails api call failed. Status = ${res.statusCode}`); + reject([]); + } else { + try { + let resJson = JSON.parse(resData); + resJson.response.publishedfiledetails.forEach( details => { + let _mapName = ""; + if (details.filename != "") { + let re = /\S+\/(\S+).bsp/; + let matches = details.filename.match(re); + _mapName = matches[1]; + } + returnDetails.push({ + "name": _mapName, + "official": official, + "title": details.title, + "workshopID": details.publishedfileid, + "description": details.description, + "previewLink": details.preview_url, + "tags": details.tags }) + }); + resolve(returnDetails); + } catch (e) { + logger.warn(`Reading map details failed: ${e}`); + reject([]); + } + } + }); + }).on('error', (error) => { + logger.warn(`Steam Workshop Maps Request failed: ${error}`); + reject([]); + }); + }); } - // Temporarily work with a static maplist. - executeRcon('maps *').then((answer) => { - const officialMaps = require('../OfficialMaps.json'); - let re = /\(fs\) (\S+).bsp/g; - let maplist = []; - let mapdetails = []; - // let mapsArray = getMatches(answer, re, 1); - let mapsArray = ['cs_italy', 'cs_office', 'de_ancient', 'de_anubis', - 'de_dust2', 'de_inferno', 'de_mirage', 'de_nuke', - 'de_overpass', 'de_vertigo'] - let promises = []; - mapsArray.forEach((mapString) => { - let mapName = cutMapName(mapString); - maplist.push(mapName); - if (mapString.includes('workshop/')) { - let mapIdRegex = /workshop\/(\d+)\//; - let workshopId = mapString.match(mapIdRegex)[1]; - promises.push(_sendApiRequest(mapName, workshopId)); - } else { - let workshopId = officialMaps[mapName]; - if (workshopId != undefined) { - promises.push(_sendApiRequest(mapName, workshopId)); - } else { - mapdetails.push({ "name": mapName, "title": "", "workshopID": "", "description": "", "previewLink": "", "tags": "" }); - } - } - }); - Promise.allSettled(promises).then((results) => { - results.forEach((result) => { - mapdetails.push(result.value) - }) - - mapdetails.sort((a, b) => a.name.localeCompare(b.name)); - maplist.sort(); - // Only return, if list has at least one item. - logger.debug(`Maps found: ${maplist.length}`); - if (maplist.length > 0) { - logger.debug('Saving Maplist to ServerInfo'); - serverInfo.mapsAvail = maplist; - serverInfo.mapsDetails = mapdetails; - resolve({ "success": true }); - } else { - reject({ "success": false }); - } - }); - }).catch((err) => { - logger.warn(`Error executing maps rcon: ${err.message}`); - reject({ "success": false }); - }); + + // Available maps will be built from OfficialMaps.json static file, + // workshop collection and mapsfrom config. + let officialMapIds = []; + let workshopMapIds = []; + let mapdetails = []; + + let omJson = require('../OfficialMaps.json'); + + omJson.forEach( (om) => { + officialMapIds.push(om.id); + }) + + logger.debug("getting official maps"); + + try { + mapdetails = await getMapDetails(officialMapIds, true); + } catch(error) { + logger.warn(`Getting official maps details failed: ${error}`); + } + + if (cfg.workshopCollection != '') { + logger.debug("getting collection ids"); + try { + workshopMapIds = await getWorkshopCollection(cfg.workshopCollection); + } catch (error) { + logger.warn(`Getting Workshop map IDs failed: ${error}`); + } + } + workshopMapIds.push(...cfg.workshopMaps); + + logger.debug("getting workshop maps"); + try { + mapdetails.push(...await getMapDetails(workshopMapIds, false)); + } catch(error) { + logger.warn(`Getting Workshop maps details failed: ${error}`); + } + // mapdetails.sort((a, b) => a.name.localeCompare(b.title)); + + serverInfo.mapsDetails = mapdetails; + // TODO: Check if this is still needed. + // serverInfo.mapsAvail = maplist; + if(mapdetails.length > 0) { + resolve({ "success": true }); + } else { + logger.warn('Update maps failed: Maplist is empty.'); + reject( {"success": false}); + } }); } +/** + * Checks if a map is available on the server or not + * @param {string/int} map - a filename, title or workshopID + * @return {boolean} if the map was found in the details. + */ +function getMap(mapToFind) { + let returnMap = undefined; + serverInfo.mapsDetails.forEach( (map) => { + if (map.workshopID == mapToFind || map.name == mapToFind || map.title == mapToFind) { + returnMap = map; + } + }) + return returnMap; +} + /** * Executes a rcon command * @param {string} message - The rcon command to execute @@ -193,7 +246,7 @@ function executeRcon(message) { * @param {string} string - String to search. * @param {regex} regex - Regex to execute on the string. * @param {integer} index - Optional index which capturing group should be retreived. - * @returns {string[]} matches - Array holaction the found matches. + * @returns {string[]} matches - Array holding the found matches. */ function getMatches(string, regex, index) { index || (index = 1); // default to the first capturing group @@ -237,4 +290,4 @@ function queryMaxRounds() { }); } -module.exports = { authenticate, reloadMaplist, executeRcon, cutMapName, queryMaxRounds }; \ No newline at end of file +module.exports = { authenticate, reloadMaplist, getMap, executeRcon, cutMapName, queryMaxRounds }; \ No newline at end of file diff --git a/public/js/gameserver.js b/public/js/gameserver.js index 8f6b0ae..b9bb5e3 100644 --- a/public/js/gameserver.js +++ b/public/js/gameserver.js @@ -1,318 +1,322 @@ -// Change here if you don't host the webInterfae on the same host as the NodeJS API -var host = window.location.hostname; -var protocol = window.location.protocol -var address = `${protocol}//${host}:8090/csgoapi`; -var apiPath = `${address}/v1.0` -var maplistFile = './maplist.txt'; - -// Titles for throbber window. -var titles = { - 'start': 'Starting server', - 'stop': 'Stopping server', - 'auth': 'Authenticating RCON', - 'update': 'Updating server', - 'mapchange': 'Changing map', - 'pause': 'Pausing/Unpausing match' -} -var running = false; -var authenticated = false; - -// Redirect to login page. -function doLogin() { - window.location.href = `${address}/login`; -} - -// Sends a get Request with the headers needed for authentication with the seesion cookie. -function sendGet(address, data, callback) { - return $.ajax({ - type: "GET", - url: address, - data: data, - cache: false, - crossDomain: true, - xhrFields: { - withCredentials: true - }, - success: callback - }); -} - -// Load the maplist for serverstart from maplist.txt -function loadMaplist() { - // The Maplist file can be taken from the csgo folder. - $.get(maplistFile, (data) => { - let lines = data.split(/\r\n|\n/); - lines.forEach((map) => { - $("#mapAuswahl").append(``); - }); - }); -} - -// Setup the Elements according to server status. -function setupPage() { - $('#popupCaption').text('Querying Server'); - getPromise = (path) => { - return Promise.resolve(sendGet(`${address}/${path}`)); - } - - let loginCheck = getPromise('loginStatus'); - loginCheck.then((data) => { - if (data.login) { - let authenticated = getPromise('v1.0/info/rconauthstatus'); - authenticated.then((data) => { - if (data.rconauth) { - setupServerRunning(); - } else { - let serverRunning = getPromise('v1.0/info/runstatus'); - serverRunning.then((data) => { - if (data.running) { - window.location.href = './notauth.htm'; - } else { - setupServerStopped(); - } - }); - } - }).catch((error) => { - setupServerStopped(); - }); - } else { - setupNotLoggedIn(); - } - }).catch((error) => { - setupNotLoggedIn(); - }); - - $('#container-popup').css('display', 'none'); -} - -function setupNotLoggedIn() { - $('#power-image').hide(0); - $('#startMap').hide(0); - $('#buttonStop').hide(0); - $('#buttonStart').hide(0); - $('#buttonUpdate').hide(0); - $('#buttonLogin').show(0); - $('#addControl').hide(0); - $('#serverInfo').hide(0); - $('#mapControl').hide(0); -} - -function setupServerRunning() { - $('#power-image').attr('src', 'pic/power-on.png'); - if (socket.readyState != 1) { // if websocket not connected - getMaps(); - } else if ($("#mapSelector div").length < 2) { - socket.send('infoRequest'); - } - $('#startMap').hide(0); - $('#buttonStop').show(0); - $('#buttonStart').hide(0); - $('#buttonUpdate').hide(0); - $('#buttonLogin').hide(0); - $('#addControl').show(0); - $('#serverInfo').css('display', 'flex'); - $('#mapControl').show(0); -} - -function setupServerStopped() { - $('#power-image').attr('src', 'pic/power-off.png'); - $('#startMap').show(0); - $('#buttonStart').show(0); - $('#buttonStop').hide(0); - $('#buttonUpdate').show(0); - $('#buttonLogin').hide(0); - $('#serverInfo').hide(0); - $('#addControl').hide(0); - $('#mapControl').hide(0); - $('#mapSelector').hide('fast'); -} - -function clickButton(aButton) { - action = aButton.value.toLowerCase(); - $('#popupCaption').text(`${titles[action]}`); - $('#popupText').text('Moment bitte!'); - $('#container-popup').css('display', 'flex'); - startMap = document.getElementById('mapAuswahl').value; - - sendGet(`${apiPath}/control/${action}`, `startmap=${startMap}`).done((data) => { - if (socket.readyState != 1) { // if websocket not connected - if (action != 'update') { - setupPage(); - } - $('#container-popup').hide(); - } - }).fail((err) => { - let errorText = err.responseJSON.error; - if (errorText.indexOf('Another Operation is pending:') != -1) { - let operation = errorText.split(':')[1]; - alert(`${operation} running.\nTry again in a moment.`); - } else { - alert(`command ${action} failed!\nError: ${errorText}`); - window.location.href = './notauth.htm'; - } - if (socket.readyState != 1) { - $('#container-popup').css('display', 'none'); - } - }); -} - -function showPlayerMenu(event) { - $('#playerDropdown').css({ 'top': event.pageY, 'left': event.pageX, 'display': 'block' }); - $('#playerDropdown').attr('player', event.target.textContent); - // Close the dropdown menu if the user clicks outside of it - window.onclick = function(event) { - if (!event.target.matches('.dropbtn')) { - $('#playerDropdown').css('display', 'none'); - window.onclick = ''; - } - } -} - -function movePlayer(event) { - // This function uses sourcemod plugin "moveplayers" -> https://forums.alliedmods.net/showthread.php?p=2471466 - /* "sm_movect" - Move a player to the counter-terrorist team. - "sm_movespec" - Move a player to the spectators team. - "sm_movet" - Move a player to the terrorist team. */ - let player = event.target.parentElement.getAttribute('player') - let command = event.target.getAttribute('command'); - sendGet(`${apiPath}/rcon`, `message=sm_move${command} "${player}"`, (data) => { - // no actions for now. - }); -} - -function getMaps() { - function getServerInfo() { - return Promise.resolve(sendGet(`${apiPath}/info/serverInfo`)); - } - let serverInfo = getServerInfo(); - serverInfo.then((data) => { - $("#currentMap").html(`Current map: ${data.map}`); - maplist = data.mapsDetails; - $("#mapSelector").empty(); - maplist.forEach((map) => { - if ('content' in document.createElement('template')) { - var mapDiv = document.querySelector('#maptemplate'); - mapDiv.content.querySelector('.mapname').textContent = map.name; - mapDiv.content.querySelector('.mapimg').setAttribute("src", map.previewLink); - $('#mapSelector').append(document.importNode(mapDiv.content, true)); - } else { - let alttext = createElement('h2'); - text.html("Your browser does not have HTML template support - please use another browser."); - $('#mapSelector').append(alttext); - } - }); - }).catch((error) => { - // do nothing for now - }); -} - -function toggleMaplist() { - $('#mapSelector').toggle('fast'); -} - -function showPlay(event) { - if (event.currentTarget.classList.contains('active')) { - changeMap(event); - $('.map').removeClass('active'); - } else { - $('.active > .playicon').hide(0); - $('.active').removeClass('active'); - event.currentTarget.classList.add('active'); - event.currentTarget.children[1].style.display = 'block'; - } -} - -function changeMap(event) { - let map = event.currentTarget.firstElementChild.textContent; - $('#mapSelector').hide('fast'); - $('#popupCaption').text(titles['mapchange']); - $('#container-popup').css('display', 'flex'); - sendGet(`${apiPath}/control/changemap`, `map=${map}`, (data) => { - if (data.success) { - $('#popupText').html(`Changing map to ${map}`); - } else { - $('#popupText').html(`Mapchange failed!`); - window.setTimeout(() => { - $('#container-popup').css('display', 'none'); - window.location.href = './notauth.htm'; - }, 2000); - - } - }); -} - -function restartRound() { - sendGet(`${apiPath}/rcon`, `message=mp_restartgame 1`, (data) => { - $('#popupCaption').text(`Restart Round`); - $('#popupText').html(`Round Restarted!`); - $('#container-popup').css('display', 'flex'); - window.setTimeout(() => { - $('#container-popup').css('display', 'none'); - }, 1000); - }); -} - -function pauseGame() { - sendGet(`${apiPath}/control/pause`).done((data) => { - if (data.success) { - if (socket.readyState != 1) { // if websocket not connected - $('#pause-overlay').css('top', $('#serverControl').position().top); - $('#pause-overlay').css('height', $('#serverInfo').height() + $('#serverControl').height()); - $('#pause-overlay').css('display', 'flex'); - } - } else { - alert('Pausing the match failed!'); - } - }); -} - -function resumeGame() { - sendGet(`${apiPath}/control/unpause`).done((data) => { - if (data.success) { - if (socket.readyState != 1) { // if websocket not connected - $('#pause-overlay').hide(); - } - } else { - alert('Unpausing the match failed!'); - } - }); -} - -function authenticate(caller) { - sendGet(`${apiPath}/authenticate`).done((data) => { - if (data.authenticated) { - window.location.href = './gameserver.htm'; - } else { - caller.disabled = true; - $('#autherror').show('fast'); - } - }); -} - -function kill(caller) { - - sendGet(`${apiPath}/control/kill`).done((data) => { - window.location.href = './gameserver.htm'; - }).fail((error) => { - caller.disabled = true; - $('#killerror').show('fast'); - }); -} - - -// Bot Training functions -function setBotRules() { - sendGet(`${apiPath}/rcon`, `message=mp_autoteambalance 0`); - sendGet(`${apiPath}/rcon`, `message=mp_limitteams 0`); - sendGet(`${apiPath}/rcon`, `message=bot_difficulty 3`); -} - -function addBots(team, quantity) { - for (let i = 0; i < quantity; i++) { - setTimeout(sendGet(`${apiPath}/rcon`, `message=bot_add_${team}`), 100); - } -} - -function kickBots() { - sendGet(`${apiPath}/rcon`, `message=bot_kick all`); +// Change here if you don't host the webInterfae on the same host as the NodeJS API +var host = window.location.hostname; +var protocol = window.location.protocol +var address = `${protocol}//${host}:8090/csgoapi`; +var apiPath = `${address}/v1.0` +var maplistFile = './maplist.txt'; + +// Titles for throbber window. +var titles = { + 'start': 'Starting server', + 'stop': 'Stopping server', + 'auth': 'Authenticating RCON', + 'update': 'Updating server', + 'mapchange': 'Changing map', + 'pause': 'Pausing/Unpausing match' +} +var running = false; +var authenticated = false; + +// Redirect to login page. +function doLogin() { + window.location.href = `${address}/login`; +} + +// Sends a get Request with the headers needed for authentication with the seesion cookie. +function sendGet(address, data, callback) { + return $.ajax({ + type: "GET", + url: address, + data: data, + cache: false, + crossDomain: true, + xhrFields: { + withCredentials: true + }, + success: callback + }); +} + +// Load the maplist for serverstart from maplist.txt +function loadMaplist() { + // The Maplist file can be taken from the csgo folder. + $.get(maplistFile, (data) => { + let lines = data.split(/\r\n|\n/); + lines.forEach((map) => { + $("#mapAuswahl").append(``); + }); + }); +} + +// Setup the Elements according to server status. +function setupPage() { + $('#popupCaption').text('Querying Server'); + getPromise = (path) => { + return Promise.resolve(sendGet(`${address}/${path}`)); + } + + let loginCheck = getPromise('loginStatus'); + loginCheck.then((data) => { + if (data.login) { + let authenticated = getPromise('v1.0/info/rconauthstatus'); + authenticated.then((data) => { + if (data.rconauth) { + setupServerRunning(); + } else { + let serverRunning = getPromise('v1.0/info/runstatus'); + serverRunning.then((data) => { + if (data.running) { + window.location.href = './notauth.htm'; + } else { + setupServerStopped(); + } + }); + } + }).catch((error) => { + setupServerStopped(); + }); + } else { + setupNotLoggedIn(); + } + }).catch((error) => { + setupNotLoggedIn(); + }); + + $('#container-popup').css('display', 'none'); +} + +function setupNotLoggedIn() { + $('#power-image').hide(0); + $('#startMap').hide(0); + $('#buttonStop').hide(0); + $('#buttonStart').hide(0); + $('#buttonUpdate').hide(0); + $('#buttonLogin').show(0); + $('#addControl').hide(0); + $('#serverInfo').hide(0); + $('#mapControl').hide(0); +} + +function setupServerRunning() { + $('#power-image').attr('src', 'pic/power-on.png'); + if (socket.readyState != 1) { // if websocket not connected + getMaps(); + } else if ($("#mapSelector div").length < 2) { + socket.send('infoRequest'); + } + $('#startMap').hide(0); + $('#buttonStop').show(0); + $('#buttonStart').hide(0); + $('#buttonUpdate').hide(0); + $('#buttonLogin').hide(0); + $('#addControl').show(0); + $('#serverInfo').css('display', 'flex'); + $('#mapControl').show(0); +} + +function setupServerStopped() { + $('#power-image').attr('src', 'pic/power-off.png'); + $('#startMap').show(0); + $('#buttonStart').show(0); + $('#buttonStop').hide(0); + $('#buttonUpdate').show(0); + $('#buttonLogin').hide(0); + $('#serverInfo').hide(0); + $('#addControl').hide(0); + $('#mapControl').hide(0); + $('#mapSelector').hide('fast'); +} + +function clickButton(aButton) { + action = aButton.value.toLowerCase(); + $('#popupCaption').text(`${titles[action]}`); + $('#popupText').text('Moment bitte!'); + $('#container-popup').css('display', 'flex'); + startMap = document.getElementById('mapAuswahl').value; + + sendGet(`${apiPath}/control/${action}`, `startmap=${startMap}`).done((data) => { + if (socket.readyState != 1) { // if websocket not connected + if (action != 'update') { + setupPage(); + } + $('#container-popup').hide(); + } + }).fail((err) => { + let errorText = err.responseJSON.error; + if (errorText.indexOf('Another Operation is pending:') != -1) { + let operation = errorText.split(':')[1]; + alert(`${operation} running.\nTry again in a moment.`); + } else { + alert(`command ${action} failed!\nError: ${errorText}`); + window.location.href = './notauth.htm'; + } + if (socket.readyState != 1) { + $('#container-popup').css('display', 'none'); + } + }); +} + +function showPlayerMenu(event) { + $('#playerDropdown').css({ 'top': event.pageY, 'left': event.pageX, 'display': 'block' }); + $('#playerDropdown').attr('player', event.target.textContent); + // Close the dropdown menu if the user clicks outside of it + window.onclick = function(event) { + if (!event.target.matches('.dropbtn')) { + $('#playerDropdown').css('display', 'none'); + window.onclick = ''; + } + } +} + +function movePlayer(event) { + // This function uses sourcemod plugin "moveplayers" -> https://forums.alliedmods.net/showthread.php?p=2471466 + /* "sm_movect" - Move a player to the counter-terrorist team. + "sm_movespec" - Move a player to the spectators team. + "sm_movet" - Move a player to the terrorist team. */ + let player = event.target.parentElement.getAttribute('player') + let command = event.target.getAttribute('command'); + sendGet(`${apiPath}/rcon`, `message=sm_move${command} "${player}"`, (data) => { + // no actions for now. + }); +} + +function getMaps() { + function getServerInfo() { + return Promise.resolve(sendGet(`${apiPath}/info/serverInfo`)); + } + let serverInfo = getServerInfo(); + serverInfo.then((data) => { + $("#currentMap").html(`Current map: ${data.map}`); + maplist = data.mapsDetails; + $("#mapSelector").empty(); + maplist.forEach((map) => { + if ('content' in document.createElement('template')) { + var mapDiv = document.querySelector('#maptemplate'); + mapDiv.content.querySelector('.mapname').textContent = map.title; + mapDiv.content.querySelector('.mapimg').setAttribute("src", map.previewLink); + mapDiv.content.querySelector('.map').setAttribute("id", map.workshopID); + $('#mapSelector').append(document.importNode(mapDiv.content, true)); + } else { + let alttext = createElement('h2'); + text.html("Your browser does not have HTML template support - please use another browser."); + $('#mapSelector').append(alttext); + } + }); + }).catch((error) => { + // do nothing for now + }); +} + +function toggleMaplist() { + $('#mapSelector').toggle('fast'); +} + +function showPlay(event) { + if (event.currentTarget.classList.contains('active')) { + changeMap(event); + $('.map').removeClass('active'); + } else { + $('.active > .playicon').hide(0); + $('.active').removeClass('active'); + event.currentTarget.classList.add('active'); + event.currentTarget.children[1].style.display = 'block'; + } +} + +function changeMap(event) { + let map = event.currentTarget.getAttribute("id"); + $('#mapSelector').hide('fast'); + //$('#popupCaption').text(titles['mapchange']); + //$('#container-popup').css('display', 'flex'); + if (event.currentTarget.children[2].attributes["src"].nodeValue != "") { + sendGet(`${apiPath}/control/changemap`, `map=${map}`, (data) => { + if (data.success) { + $('#popupText').html(`Changing map to ${map}`); + } else { + $('#popupText').html(`Mapchange failed!`); + window.setTimeout(() => { + $('#container-popup').css('display', 'none'); + }, 2000); + + } + }); + } else { + sendGet(`${apiPath}/rcon`, `message=ds_workshop_changelevel ${map}`); + } +} + +function restartRound() { + sendGet(`${apiPath}/rcon`, `message=mp_restartgame 1`, (data) => { + $('#popupCaption').text(`Restart Round`); + $('#popupText').html(`Round Restarted!`); + $('#container-popup').css('display', 'flex'); + window.setTimeout(() => { + $('#container-popup').css('display', 'none'); + }, 1000); + }); +} + +function pauseGame() { + sendGet(`${apiPath}/control/pause`).done((data) => { + if (data.success) { + if (socket.readyState != 1) { // if websocket not connected + $('#pause-overlay').css('top', $('#serverControl').position().top); + $('#pause-overlay').css('height', $('#serverInfo').height() + $('#serverControl').height()); + $('#pause-overlay').css('display', 'flex'); + } + } else { + alert('Pausing the match failed!'); + } + }); +} + +function resumeGame() { + sendGet(`${apiPath}/control/unpause`).done((data) => { + if (data.success) { + if (socket.readyState != 1) { // if websocket not connected + $('#pause-overlay').hide(); + } + } else { + alert('Unpausing the match failed!'); + } + }); +} + +function authenticate(caller) { + sendGet(`${apiPath}/authenticate`).done((data) => { + if (data.authenticated) { + window.location.href = './gameserver.htm'; + } else { + caller.disabled = true; + $('#autherror').show('fast'); + } + }); +} + +function kill(caller) { + + sendGet(`${apiPath}/control/kill`).done((data) => { + window.location.href = './gameserver.htm'; + }).fail((error) => { + caller.disabled = true; + $('#killerror').show('fast'); + }); +} + + +// Bot Training functions +function setBotRules() { + sendGet(`${apiPath}/rcon`, `message=mp_autoteambalance 0`); + sendGet(`${apiPath}/rcon`, `message=mp_limitteams 0`); + sendGet(`${apiPath}/rcon`, `message=bot_difficulty 3`); +} + +function addBots(team, quantity) { + for (let i = 0; i < quantity; i++) { + setTimeout(sendGet(`${apiPath}/rcon`, `message=bot_add_${team}`), 100); + } +} + +function kickBots() { + sendGet(`${apiPath}/rcon`, `message=bot_kick all`); } \ No newline at end of file diff --git a/public/js/onload.js b/public/js/onload.js index 110f4c0..c1f3c90 100644 --- a/public/js/onload.js +++ b/public/js/onload.js @@ -1,107 +1,121 @@ -// what to do after document is loaded. -var socket = null; -$(document).ready(() => { - let startSocket = () => { - try { - if (protocol == 'https:') { - socket = new WebSocket(`wss://${host}:8091`); - } else { - socket = new WebSocket(`ws://${host}:8091`); - } - } catch (err) { - console.error('Connection to websocket failed:\n' + err); - } - - socket.onopen = () => { - socket.send('infoRequest'); - } - - socket.onmessage = (e) => { - let data = JSON.parse(e.data); - - if (data.type == "serverInfo") { - let serverInfo = data.payload; - - $("#currentMap").html(`Current map: ${serverInfo.map}`); - $('#scoreT').text(serverInfo.score.T); - $('#scoreCT').text(serverInfo.score.C); - $('#rounds').html( - `Rounds: ${serverInfo.maxRounds} / Left: ${serverInfo.maxRounds - (serverInfo.score.T + serverInfo.score.C)}` - ); - - $('.playerDiv ul').empty(); - $('.playerDiv').hide(0); - if (serverInfo.players.length > 0) { - for (let i = 0; i < serverInfo.players.length; i++) { - let player = serverInfo.players[i]; - $(`#${player.team.toLowerCase()}List`).append(`
  • ${player.name}
  • `); - $(`#${player.team.toLowerCase()}Players`).show(0); - } - } - if (serverInfo.pause) { - $('#pause-overlay').css('top', $('#serverControl').position().top); - $('#pause-overlay').css('height', $('#serverInfo').height() + $('#serverControl').height()); - $('#pause-overlay').css('display', 'flex'); - } else { - $('#pause-overlay').hide(); - } - if ($('#mapSelector .map').length != serverInfo.mapsDetails.length) { - if (serverInfo.mapsDetails) { - let maplist = serverInfo.mapsDetails; - $("#mapSelector").empty(); - maplist.forEach((map) => { - if ('content' in document.createElement('template')) { - var mapDiv = document.querySelector('#maptemplate'); - mapDiv.content.querySelector('.mapname').textContent = map.name; - mapDiv.content.querySelector('.mapimg').setAttribute("src", map.previewLink ? map.previewLink : ''); - $('#mapSelector').append(document.importNode(mapDiv.content, true)); - } else { - let alttext = document.createElement('h2'); - text.html("Your browser does not have HTML template support - please use another browser."); - $('#mapSelector').append(alttext); - } - }); - } - } - } else if (data.type == "commandstatus") { - if (data.payload.state == 'start') { - $('#popupCaption').text(`${titles[data.payload.operation]}`); - $('#popupText').text('Moment bitte!'); - $('#container-popup').css('display', 'flex'); - } else if (data.payload.state == 'end' && data.payload.operation != 'start') { - $('#popupText').html(`${data.payload.operation} success!`); - setTimeout(() => { - $('#container-popup').css('display', 'none'); - setupPage(); - }, 1500); - } else if (data.payload.state == 'fail') { - $('#popupText').html(`${data.payload.operation} failed!`); - setTimeout(() => { - $('#container-popup').css('display', 'none'); - if (data.payload.operation != 'update') { - window.location.href = './notauth.htm'; - } - }, 3000); - } - } else if (data.type == "progress") { - $('#popupText').html(`${data.payload.step}: ${data.payload.progress}%`); - } else if (data.type == "mapchange") { - if (data.payload.success$ && $('#popupCaption').text() == 'Changing Map') { - socket.send('infoRequest'); - $('#container-popup').css('display', 'none'); - } else if (!data.payload.success) { - $('#popupText').html(`Mapchange failed!`); - } - } - } - - socket.onclose = () => { - // connection closed, discard old websocket and create a new one in 5s - socket = null; - setTimeout(startSocket, 5000); - } - } - startSocket(); - loadMaplist(); - setupPage(); +// what to do after document is loaded. +var socket = null; +$(document).ready(() => { + let startSocket = () => { + try { + if (protocol == 'https:') { + socket = new WebSocket(`wss://${host}:8091`); + } else { + socket = new WebSocket(`ws://${host}:8091`); + } + } catch (err) { + console.error('Connection to websocket failed:\n' + err); + } + + socket.onopen = () => { + socket.send('infoRequest'); + } + + socket.onmessage = (e) => { + let data = JSON.parse(e.data); + + if (data.type == "serverInfo") { + let serverInfo = data.payload; + + $("#currentMap").html(`Current map: ${serverInfo.map}`); + $('#scoreT').text(serverInfo.score.T); + $('#scoreCT').text(serverInfo.score.C); + $('#rounds').html( + `Rounds: ${serverInfo.maxRounds} / Left: ${serverInfo.maxRounds - (serverInfo.score.T + serverInfo.score.C)}` + ); + + $('.playerDiv ul').empty(); + $('.playerDiv').hide(0); + if (serverInfo.players.length > 0) { + for (let i = 0; i < serverInfo.players.length; i++) { + let player = serverInfo.players[i]; + if (player.disconnected) { + break; + } + if ('content' in document.createElement('template')) { + var playerLi = document.querySelector('#playerTemplate'); + playerLi.content.querySelector('.playerName').textContent = player.name; + playerLi.content.querySelector('.playerKills').textContent = `K: ${player.kills}`; + playerLi.content.querySelector('.playerDeaths').textContent = `D: ${player.deaths}`; + $(`#${player.team.toLowerCase()}List`).append(document.importNode(playerLi.content, true)); + } else { + let alttext = document.createElement('li'); + alttext.html("Your browser does not have HTML template support - please use another browser."); + $(`#${player.team.toLowerCase()}List`).append(alttext); + } + $(`#${player.team.toLowerCase()}Players`).show(0); + } + } + if (serverInfo.pause) { + $('#pause-overlay').css('top', $('#serverControl').position().top); + $('#pause-overlay').css('height', $('#serverInfo').height() + $('#serverControl').height()); + $('#pause-overlay').css('display', 'flex'); + } else { + $('#pause-overlay').hide(); + } + if ($('#mapSelector .map').length != serverInfo.mapsDetails.length) { + if (serverInfo.mapsDetails) { + let maplist = serverInfo.mapsDetails; + $("#mapSelector").empty(); + maplist.forEach((map) => { + if ('content' in document.createElement('template')) { + var mapDiv = document.querySelector('#maptemplate'); + mapDiv.content.querySelector('.mapname').textContent = map.title; + mapDiv.content.querySelector('.mapimg').setAttribute("src", map.previewLink ? map.previewLink : ''); + mapDiv.content.querySelector('.map').setAttribute("id", map.workshopID); + $('#mapSelector').append(document.importNode(mapDiv.content, true)); + } else { + let alttext = document.createElement('h2'); + alttext.html("Your browser does not have HTML template support - please use another browser."); + $('#mapSelector').append(alttext); + } + }); + } + } + } else if (data.type == "commandstatus") { + if (data.payload.state == 'start') { + $('#popupCaption').text(`${titles[data.payload.operation]}`); + $('#popupText').text('Moment bitte!'); + $('#container-popup').css('display', 'flex'); + } else if (data.payload.state == 'end' && data.payload.operation != 'start') { + $('#popupText').html(`${data.payload.operation} success!`); + setTimeout(() => { + $('#container-popup').css('display', 'none'); + setupPage(); + }, 1500); + } else if (data.payload.state == 'fail') { + $('#popupText').html(`${data.payload.operation} failed!`); + setTimeout(() => { + $('#container-popup').css('display', 'none'); + if (data.payload.operation != 'update' && data.payload.operation != 'mapchange') { + window.location.href = './notauth.htm'; + } + }, 3000); + } + } else if (data.type == "progress") { + $('#popupText').html(`${data.payload.step}: ${data.payload.progress}%`); + } else if (data.type == "mapchange") { + if (data.payload.success$ && $('#popupCaption').text() == 'Changing Map') { + socket.send('infoRequest'); + $('#container-popup').css('display', 'none'); + } else if (!data.payload.success) { + $('#popupText').html(`Mapchange failed!`); + } + } + } + + socket.onclose = () => { + // connection closed, discard old websocket and create a new one in 5s + socket = null; + setTimeout(startSocket, 5000); + } + } + startSocket(); + loadMaplist(); + setupPage(); }); \ No newline at end of file diff --git a/serverControl.js b/serverControl.js old mode 100644 new mode 100755 index 7e07f46..f512bfd --- a/serverControl.js +++ b/serverControl.js @@ -97,7 +97,7 @@ controlEmitter.on('exec', (operation, action) => { sf.reloadMaplist().then((answer) => { logger.info('Maps reloaded'); }).catch((err) => { - logger.warn("Maps could not be loaded"); + logger.warn(`Maps could not be loaded: ${err}`); }); sf.queryMaxRounds(); }