Mit dem Standard 2 trennt CS2 Spieler und CSTV, wenn deren Bestätigungen außer der Reihe kommen. Neue Container starten mit +sv_deltaticks_enforce 0, beim Einrichten jeder Map und auf Messer-Servern setzt die App den Wert zusätzlich per RCON.
83 lines
3.4 KiB
JavaScript
83 lines
3.4 KiB
JavaScript
// Erzeugt die Konsolenbefehle, mit denen ein laufender CS2-Server für eine Map der Serie konfiguriert wird.
|
|
// Alle Werte werden so bereinigt, dass sie keine weiteren Befehle einschleusen können.
|
|
|
|
import { BACKUP_PATTERN } from '../rounds.js';
|
|
|
|
export function sanitizeConsoleText(value, maxLength = 32) {
|
|
return String(value ?? '')
|
|
.replace(/["';\\\r\n\t]/g, '')
|
|
.replace(/[^\p{L}\p{N} ._\-+#!?()[\]|]/gu, '')
|
|
.trim()
|
|
.slice(0, maxLength) || 'Team';
|
|
}
|
|
|
|
/**
|
|
* Liefert zwei Befehlsgruppen:
|
|
* - beforeMapChange: Spielmodus, Passwörter, CSTV und der Mapwechsel selbst.
|
|
* game_type/game_mode und tv_enable greifen erst beim Laden einer Map.
|
|
* - afterMapChange: Match-Einstellungen. Diese werden erst nach dem Laden gesetzt,
|
|
* weil die gamemode_*.cfg des Spiels sie beim Mapwechsel sonst überschreibt.
|
|
*
|
|
* @param {object} p
|
|
* @param {object} p.match Match-Datensatz (Teamnamen)
|
|
* @param {object} p.pool Map-Pool (mode/gameType/gameMode)
|
|
* @param {object} p.seriesMap { map, ctTeam: 'A'|'B'|null }
|
|
* @param {object} p.secrets { serverPassword, tvPassword }
|
|
* @param {object} p.settings config.match
|
|
*/
|
|
export function buildMapCommands({ match, pool, seriesMap, secrets, settings, logUrl, backupPrefix }) {
|
|
const isWingman = pool.mode === 'wingman';
|
|
const ctTeam = seriesMap.ctTeam ?? 'A';
|
|
const tTeam = ctTeam === 'A' ? 'B' : 'A';
|
|
const names = { A: sanitizeConsoleText(match.team_a_name), B: sanitizeConsoleText(match.team_b_name) };
|
|
|
|
const beforeMapChange = [
|
|
`game_type ${Number(pool.gameType)}`,
|
|
`game_mode ${Number(pool.gameMode)}`,
|
|
`sv_password "${secrets.serverPassword}"`,
|
|
`tv_enable ${settings.tvEnabled ? 1 : 0}`,
|
|
`tv_delay ${Number(settings.tvDelaySeconds)}`,
|
|
`tv_password "${secrets.tvPassword}"`,
|
|
// Live-Ergebnisse: Server schickt seine Logzeilen per HTTP an das Backend
|
|
...(logUrl && /^https?:\/\/[\w.:/-]+$/.test(logUrl)
|
|
? ['log on', 'logaddress_delall_http', `logaddress_add_http "${logUrl}"`]
|
|
: []),
|
|
// Rundensicherung: CS2 speichert nach jeder Runde den Spielzustand, damit sich Runden zurückspielen lassen
|
|
...(backupPrefix ? [
|
|
`mp_backup_round_file "${backupPrefix}"`,
|
|
`mp_backup_round_file_pattern "${BACKUP_PATTERN}"`,
|
|
'mp_backup_round_auto 1',
|
|
'mp_backup_restore_load_autopause 1',
|
|
] : []),
|
|
`changelevel ${seriesMap.map}`,
|
|
];
|
|
|
|
const afterMapChange = [
|
|
// mp_teamname_1 = Team, das auf CT startet; mp_teamname_2 = Team, das auf T startet
|
|
`mp_teamname_1 "${names[ctTeam]}"`,
|
|
`mp_teamname_2 "${names[tTeam]}"`,
|
|
`mp_maxrounds ${isWingman ? settings.maxRoundsWingman : settings.maxRoundsCompetitive}`,
|
|
`mp_overtime_enable ${settings.overtime ? 1 : 0}`,
|
|
'mp_match_can_clinch 1',
|
|
// Keine Map-Abstimmung nach Spielende: die nächste Map legt das Veto fest
|
|
'mp_endmatch_votenextmap 0',
|
|
// Strenge Prüfung der Delta-Ticks (Standard 2) trennt Spieler und CSTV ohne echten Grund
|
|
'sv_deltaticks_enforce 0',
|
|
'mp_autoteambalance 0',
|
|
'mp_limitteams 0',
|
|
// Aufwärmphase läuft, bis der Organisator das Match startet (mp_warmup_end)
|
|
'mp_warmup_pausetimer 1',
|
|
// Turnierregeln (FACEIT/ESL-Richtwerte): Pausen erlauben, feste Rundenzeiten, Halbzeit
|
|
'sv_pausable 1',
|
|
'mp_halftime 1',
|
|
'mp_freezetime 20',
|
|
'mp_buytime 20',
|
|
'mp_startmoney 800',
|
|
'mp_roundtime 1.92',
|
|
...settings.extraCommands,
|
|
];
|
|
|
|
return { beforeMapChange, afterMapChange };
|
|
}
|
|
|