Skip to content

Commit 4653238

Browse files
committed
Add support for workshop maps in CS2
1 parent f58cf5e commit 4653238

File tree

9 files changed

+771
-643
lines changed

9 files changed

+771
-643
lines changed

config.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,20 @@
1414

1515
/* Optional settings */
1616
// Anything you want your server command line to have additional to:
17-
// -console -usercon -ip 0.0.0.0 +sv_logfile 1 -serverlogging +logaddress_add_http "http://${this._localIp}:${this.logPort}/log"
17+
// -console -usercon -ip 0.0.0.0 +sv_logfile 1 -serverlogging
18+
// +logaddress_add_http "http://${this._localIp}:${this.logPort}/log"
1819
"csgoOptionalArgs": "",
1920
// steam serverToken for public access. To get one see https://steamcommunity.com/dev/managegameservers
2021
"serverToken": "",
22+
// Steam Web API token. Needed to get mapdetails like thumbnail, etc
23+
// See https://steamcommunity.com/dev/apikey how to get one.
24+
"apiToken": "",
25+
// Workshop Collection to host on the server.
26+
"workshopCollection": "",
27+
// List of workshop ids of maps to add to available maps.
28+
"workshopMaps": [ // '23423523523525',
29+
// '37281723987123'
30+
],
2131
// If you want to use a different name / location for the update script (absolute path).
2232
"updateScript": "",
2333
// Time in minutes, after which a new login is needed.
@@ -42,7 +52,7 @@
4252
// If you use https, add the path to the certificate files here.
4353
"httpsCertificate": "",
4454
"httpsPrivateKey": "",
45-
// Optional: In case your CA is not trusted by default (e.g. letsencrypt), you can add
55+
// In case your CA is not trusted by default (e.g. letsencrypt), you can add
4656
// the CA-Cert here.
4757
"httpsCa": "",
4858
// Change this to any string of your liking to make it harder for attackers to profile your cookies.

modules/apiV10.js

100644100755
Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
* @requires node-pty
44
* @requires express
55
* @requires ./config.js
6-
* @requires ./emitters.js
6+
* @requires ./serverInfo.js
7+
* @requires ./controlEmitter.js
78
* @requires ./sharedFunctions.js
89
*/
910

@@ -601,7 +602,7 @@ router.get('/control/update', (req, res) => {
601602
* @apiName changemap
602603
* @apiGroup Control
603604
*
604-
* @apiParam {string} mapname filename of the map without extension (.bsp)
605+
* @apiParam {string/int} map name, title or workshopID of a map.
605606
* @apiParamExample {string} Map-example
606607
* cs_italy
607608
*
@@ -619,16 +620,24 @@ router.get('/control/changemap', (req, res) => {
619620
if (serverInfo.serverState.operationPending == 'none') {
620621
controlEmitter.emit('exec', 'mapchange', 'start');
621622
// only try to change map, if it exists on the server.
622-
if (serverInfo.mapsAvail.includes(args.map)) {
623-
sf.executeRcon(`map ${args.map}`).then((answer) => {
624-
// Answer on sucess:
623+
let map = sf.getMap(args.map);
624+
if (map != undefined) {
625+
let mapchangeCommand = '';
626+
if (map.official) {
627+
mapchangeCommand = `map ${map.name}`;
628+
} else {
629+
mapchangeCommand = `host_workshop_map ${map.workshopID}`
630+
}
631+
632+
sf.executeRcon(mapchangeCommand).then((answer) => {
633+
// Answer on success (unfortunately only available for official maps):
625634
// Changelevel to de_nuke
626635
// changelevel "de_nuke"
627636
// CHostStateMgr::QueueNewRequest( Changelevel (de_nuke), 5 )
628637
//
629638
// Answer on failure:
630639
// changelevel de_italy: invalid map name
631-
if (answer.indexOf(`CHostStateMgr::QueueNewRequest( Changelevel (${args.map})`) == -1) {
640+
if (map.official && answer.indexOf(`CHostStateMgr::QueueNewRequest( Changelevel (${map.name})`) == -1) {
632641
// If the mapchange command fails, return failure immediately
633642
res.status(501).json({ "error": `Mapchange failed: ${answer}` });
634643
controlEmitter.emit('exec', 'mapchange', 'fail');

modules/configClass.js

100644100755
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ class config {
1414
return `-console -usercon -ip 0.0.0.0 +sv_logfile 1 -serverlogging +logaddress_add_http "http://${this._localIp}:${this.logPort}/log" ${this._userOptions.csgoOptionalArgs}`;
1515
}
1616

17+
get apiToken() {
18+
return this._userOptions.apiToken;
19+
}
1720
get rconPass() {
1821
return this._userOptions.rconPass;
1922
}
@@ -22,6 +25,19 @@ class config {
2225
return this._userOptions.admins;
2326
}
2427

28+
get workshopCollection() {
29+
return this._userOptions.workshopCollection;
30+
}
31+
set workshopCollection(id) {
32+
this._userOptions.workshopCollection = id;
33+
}
34+
get workshopMaps() {
35+
return this._userOptions.workshopMaps;
36+
}
37+
set workshopMaps(maps) {
38+
this._userOptions.workshopMaps = maps;
39+
}
40+
2541
get redirectPage() {
2642
if (this._userOptions.redirectPage) {
2743
return this._userOptions.redirectPage;

modules/logreceive.js

100644100755
Lines changed: 122 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,119 +1,123 @@
1-
const express = require('express');
2-
var router = express.Router();
3-
const { exec } = require('child_process');
4-
var controlEmitter = require('./controlEmitter.js');
5-
const logger = require('./logger.js');
6-
var serverInfo = require('./serverInfo.js');
7-
const sf = require('./sharedFunctions.js');
8-
var cfg = require('./configClass.js');
9-
10-
router.post('/log', (req, res) => {
11-
const data = req.body;
12-
var logs = data.split(/\r\n|\r|\n/);
13-
14-
logs.forEach(line => {
15-
if (line.length >= 20) {
16-
// Start authentication, when not authenticated.
17-
if ((line.indexOf('Log file started') != -1) && !serverInfo.serverState.authenticated) {
18-
// Start of logfile
19-
// 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")
20-
logger.verbose('start authenticating RCON');
21-
// Since authentication is a vital step for the API to work, we start it automatically
22-
// once the server runs.
23-
sf.authenticate().then((data) => {
24-
logger.verbose(`authentication ${data.authenticated}`);
25-
}).catch((data) => {
26-
logger.verbose(`authentication ${data.authenticated}`);
27-
});
28-
if (cfg.script('logStart') != '') {
29-
exec(cfg.script('logStart'));
30-
}
31-
} else if (line.indexOf('Loading map ') != -1) {
32-
// Start of map.
33-
// L 10/13/2023 - 14:28:38: Loading map "de_anubis"
34-
let rex = /Loading map \"(\S+)\"/g;
35-
let matches = rex.exec(line);
36-
let mapstring = matches[1];
37-
mapstring = sf.cutMapName(mapstring);
38-
serverInfo.map = mapstring;
39-
serverInfo.pause = false;
40-
// since 'started map' is also reported on server-start, only emit on mapchange.
41-
if (serverInfo.serverState.operationPending == 'mapchange') {
42-
controlEmitter.emit('exec', 'mapchange', 'end');
43-
}
44-
logger.verbose(`Started map: ${mapstring}`);
45-
serverInfo.clearPlayers();
46-
serverInfo.newMatch();
47-
if (cfg.script('mapStart') != '') {
48-
exec(cfg.script('mapStart'));
49-
}
50-
} else if (line.indexOf('World triggered "Match_Start" on') != -1) {
51-
// Start of a new match.
52-
// L 08/13/2020 - 21:49:26: World triggered "Match_Start" on "de_nuke"
53-
logger.verbose('Detected match start.');
54-
sf.queryMaxRounds();
55-
serverInfo.newMatch();
56-
let rex = /World triggered "Match_Start" on "(.+)"/
57-
let matches = rex.exec(line)
58-
serverInfo.map = matches[1];
59-
if (cfg.script('matchStart') != '') {
60-
exec(cfg.script('matchStart'));
61-
}
62-
} else if (line.indexOf('World triggered "Round_Start"') != -1) {
63-
// Start of round.
64-
// L 08/13/2020 - 21:49:28: World triggered "Round_Start"
65-
if (cfg.script('roundStart') != '') {
66-
exec(cfg.script('roundStart'));
67-
}
68-
} else if (/Team \"\S+\" scored/.test(line)) {
69-
// Team scores at end of round.
70-
// L 02/10/2019 - 21:31:15: Team "CT" scored "1" with "2" players
71-
// L 02/10/2019 - 21:31:15: Team "TERRORIST" scored "1" with "2" players
72-
rex = /Team \"(\S)\S+\" scored \"(\d+)\"/g;
73-
let matches = rex.exec(line);
74-
serverInfo.score = matches;
75-
} else if (line.indexOf('World triggered "Round_End"') != -1) {
76-
// End of round.
77-
// L 08/13/2020 - 22:24:22: World triggered "Round_End"
78-
if (cfg.script('roundEnd') != '') {
79-
exec(cfg.script('roundEnd'));
80-
}
81-
} else if (line.indexOf("Game Over:") != -1) {
82-
// End of match.
83-
// L 08/13/2020 - 22:24:22: Game Over: competitive 131399785 de_nuke score 16:9 after 35 min
84-
if (cfg.script('matchEnd') != '') {
85-
exec(cfg.script('matchEnd'));
86-
}
87-
} else if (/\".{1,32}<\d{1,3}><\[\w:\d:\d{1,10}\]>/.test(line)) {
88-
// Player join or teamchange.
89-
// 10/12/2023 - 16:06:38: "[Klosser] Taraman<2><[U:1:12610374]><>" entered the game
90-
// 10/12/2023 - 18:57:47: "[Klosser] Taraman<2><[U:1:12610374]>" switched from team <Unassigned> to <CT>
91-
// 10/12/2023 - 18:59:25: "[Klosser] Taraman<2><[U:1:12610374]>" switched from team <TERRORIST> to <Spectator>
92-
// 10/16/2023 - 16:31:59.699 - "[Klosser] Taraman<2><[U:1:12610374]><CT>" disconnected (reason "NETWORK_DISCONNECT_DISCONNECT_BY_USER")
93-
let rex = /\"(.{1,32})<\d{1,3}><\[(\w:\d:\d{1,10})\]></g;
94-
let matches = rex.exec(line);
95-
if (line.indexOf('entered the game') != -1) {
96-
serverInfo.addPlayer({ 'name': matches[1], 'steamID': matches[2] });
97-
} else if (line.search(/disconnected \(reason/) != -1) {
98-
logger.debug(line);
99-
serverInfo.removePlayer(matches[2]);
100-
} else if (line.indexOf('switched from team') != -1) {
101-
rex = /<\[(\w:\d:\d{1,10})\]>\" switched from team <\S{1,10}> to <(\S{1,10})>/g;
102-
matches = rex.exec(line);
103-
serverInfo.assignPlayer(matches[1], matches[2]);
104-
}
105-
} else if (line.indexOf('Log file closed') != -1) {
106-
// end of current log file. (Usually on mapchange or server quit.)
107-
// L 08/13/2020 - 22:25:00: Log file closed
108-
logger.verbose('logfile closed!');
109-
if (cfg.script('logEnd') != '') {
110-
exec(cfg.script('logEnd'));
111-
}
112-
}
113-
}
114-
});
115-
116-
res.status(200).send("Receiving logs");
117-
});
118-
1+
const express = require('express');
2+
var router = express.Router();
3+
const { exec } = require('child_process');
4+
var controlEmitter = require('./controlEmitter.js');
5+
const logger = require('./logger.js');
6+
var serverInfo = require('./serverInfo.js');
7+
const sf = require('./sharedFunctions.js');
8+
var cfg = require('./configClass.js');
9+
10+
router.post('/log', (req, res) => {
11+
const data = req.body;
12+
var logs = data.split(/\r\n|\r|\n/);
13+
14+
logs.forEach(line => {
15+
if (line.length >= 20) {
16+
// Start authentication, when not authenticated.
17+
if ((line.indexOf('Log file started') != -1) && !serverInfo.serverState.authenticated) {
18+
// Start of logfile
19+
// 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")
20+
logger.verbose('start authenticating RCON');
21+
// Since authentication is a vital step for the API to work, we start it automatically
22+
// once the server runs.
23+
sf.authenticate().then((data) => {
24+
logger.verbose(`authentication ${data.authenticated}`);
25+
}).catch((data) => {
26+
logger.verbose(`authentication ${data.authenticated}`);
27+
});
28+
if (cfg.script('logStart') != '') {
29+
exec(cfg.script('logStart'));
30+
}
31+
} else if (line.indexOf('Loading map ') != -1) {
32+
// Start of map.
33+
// L 10/13/2023 - 14:28:38: Loading map "de_anubis"
34+
let rex = /Loading map \"(\S+)\"/g;
35+
let matches = rex.exec(line);
36+
let mapstring = matches[1];
37+
mapstring = sf.cutMapName(mapstring);
38+
serverInfo.map = mapstring;
39+
serverInfo.pause = false;
40+
// since 'started map' is also reported on server-start, only emit on mapchange.
41+
if (serverInfo.serverState.operationPending == 'mapchange') {
42+
controlEmitter.emit('exec', 'mapchange', 'end');
43+
}
44+
logger.verbose(`Started map: ${mapstring}`);
45+
serverInfo.clearPlayers();
46+
serverInfo.newMatch();
47+
if (cfg.script('mapStart') != '') {
48+
exec(cfg.script('mapStart'));
49+
}
50+
} else if (line.indexOf('World triggered "Match_Start" on') != -1) {
51+
// Start of a new match.
52+
// L 08/13/2020 - 21:49:26: World triggered "Match_Start" on "de_nuke"
53+
logger.verbose('Detected match start.');
54+
sf.queryMaxRounds();
55+
serverInfo.newMatch();
56+
let rex = /World triggered "Match_Start" on "(.+)"/
57+
let matches = rex.exec(line)
58+
serverInfo.map = matches[1];
59+
if (cfg.script('matchStart') != '') {
60+
exec(cfg.script('matchStart'));
61+
}
62+
} else if (line.indexOf('World triggered "Round_Start"') != -1) {
63+
// Start of round.
64+
// L 08/13/2020 - 21:49:28: World triggered "Round_Start"
65+
if (cfg.script('roundStart') != '') {
66+
exec(cfg.script('roundStart'));
67+
}
68+
} else if (/Team \"\S+\" scored/.test(line)) {
69+
// Team scores at end of round.
70+
// L 02/10/2019 - 21:31:15: Team "CT" scored "1" with "2" players
71+
// L 02/10/2019 - 21:31:15: Team "TERRORIST" scored "1" with "2" players
72+
rex = /Team \"(\S)\S+\" scored \"(\d+)\"/g;
73+
let matches = rex.exec(line);
74+
serverInfo.score = matches;
75+
} else if (line.indexOf('World triggered "Round_End"') != -1) {
76+
// End of round.
77+
// L 08/13/2020 - 22:24:22: World triggered "Round_End"
78+
if (cfg.script('roundEnd') != '') {
79+
exec(cfg.script('roundEnd'));
80+
}
81+
} else if (line.indexOf("Game Over:") != -1) {
82+
// End of match.
83+
// L 08/13/2020 - 22:24:22: Game Over: competitive 131399785 de_nuke score 16:9 after 35 min
84+
if (cfg.script('matchEnd') != '') {
85+
exec(cfg.script('matchEnd'));
86+
}
87+
} else if (/\".{1,32}<\d{1,3}><\[\w:\d:\d{1,10}\]>/.test(line)) {
88+
// Player join or teamchange.
89+
// 10/12/2023 - 16:06:38: "[Klosser] Taraman<2><[U:1:12610374]><>" entered the game
90+
// 10/12/2023 - 18:57:47: "[Klosser] Taraman<2><[U:1:12610374]>" switched from team <Unassigned> to <CT>
91+
// 10/12/2023 - 18:59:25: "[Klosser] Taraman<2><[U:1:12610374]>" switched from team <TERRORIST> to <Spectator>
92+
// 10/16/2023 - 16:31:59.699 - "[Klosser] Taraman<2><[U:1:12610374]><CT>" disconnected (reason "NETWORK_DISCONNECT_DISCONNECT_BY_USER")
93+
// "Strapper<6><BOT><TERRORIST>"
94+
let rex = /\"(.{1,32})<\d{1,3}><\[(\w:\d:\d{1,10})\]></g;
95+
let matches = rex.exec(line);
96+
if (line.indexOf('entered the game') != -1) {
97+
serverInfo.addPlayer({ 'name': matches[1], 'steamID': matches[2] });
98+
} else if (line.search(/disconnected \(reason/) != -1) {
99+
serverInfo.removePlayer(matches[2]);
100+
} else if (line.indexOf('switched from team') != -1) {
101+
rex = /\"(.{1,32})<\d{1,3}><\[(\w:\d:\d{1,10})\]>\" switched from team <\S{1,10}> to <(\S{1,10})>/g;
102+
matches = rex.exec(line);
103+
serverInfo.assignPlayer(matches[1], matches[2], matches[3]);
104+
} 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) {
105+
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})\]/
106+
matches = rex.exec(line);
107+
serverInfo.recordKill(matches[1], matches[2]);
108+
}
109+
} else if (line.indexOf('Log file closed') != -1) {
110+
// end of current log file. (Usually on mapchange or server quit.)
111+
// L 08/13/2020 - 22:25:00: Log file closed
112+
logger.verbose('logfile closed!');
113+
if (cfg.script('logEnd') != '') {
114+
exec(cfg.script('logEnd'));
115+
}
116+
}
117+
}
118+
});
119+
120+
res.status(200).send("Receiving logs");
121+
});
122+
119123
module.exports = router;

0 commit comments

Comments
 (0)