// 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', '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 }; }