CSTV: Verzögerung wirkt wieder, Server läuft am Serienende bis zum Ende der Übertragung

- Das Image schrieb TV_DELAY (Standard 0) bei jedem Containerstart in alle gamemode_*.cfg, die bei
  jedem Laden einer Map laufen: tv_delay stand danach immer auf 0. Die App gibt TV_DELAY jetzt mit
  und setzt tv_delay zusätzlich nach dem Laden der Map per RCON.
- Am Serienende (automatisch oder von Hand abgeschlossen) bleibt der Server die CSTV-Verzögerung
  plus 15 s an, damit der Stream das Ende zeigt. Abgebrochene Matches stoppen sofort.
- Ingame-Chat: Hinweis, dass der Server für CSTV noch online bleibt und sich dann abschaltet;
  beim Mapwechsel die echte Wartezeit nach der CSTV-Übertragung (tv_delaymapchange).
This commit is contained in:
mregosar committed 2026-11-01 22:07:39 +01:00
1 parent 123c2f11d7
commit e8e070dfe6
9 files changed
+101 -7

No files matched your search

+10
View File
@@ -177,7 +177,11 @@ test('kompletter Ablauf: Lobby, Berechtigungen, Veto, Server-Konfiguration', asy
return s.status === 'live' && s.currentMapNumber === 2;
}, { label: 'Map 2' });
// Abschließen von Hand: der Server wartet auf das Ende der CSTV-Übertragung (hier kurz gehalten)
app.manager.config.match.tvDelaySeconds = 1;
app.manager.cstvGraceMs = 200;
assert.equal((await call('POST', `/matches/${id}/finish`, { token: org })).status, 200);
assert.notEqual(app.service.serverRow(id).status, 'stopped', 'läuft für CSTV noch');
await waitFor(() => app.service.serverRow(id).status === 'stopped', { label: 'Server gestoppt' });
const list = (await call('GET', '/matches')).body.matches;
assert.equal(list.find((m) => m.id === id).group, 'closed');
@@ -339,9 +343,15 @@ test('Nach Map-Ende lädt die nächste Map automatisch, entschiedene Serie wird
await waitFor(() => app.service.getMatch(id).current_map_number === 2 && app.service.serverRow(id).status === 'configured', { label: 'Map 2 automatisch' });
assert.equal(app.service.getMatch(id).status, 'live');
// CSTV-Verzögerung kurz halten: der Server soll erst danach gestoppt werden
app.manager.config.match.tvDelaySeconds = 1;
app.manager.cstvGraceMs = 300;
await winMapForAlpha(app.service.seriesMaps(id)[1].map);
// 2:0 im Best of 3 -> Match wird automatisch abgeschlossen, Map 3 nicht gespielt
await waitFor(() => app.service.getMatch(id).status === 'finished', { label: 'Serie abgeschlossen' });
assert.equal(app.service.serverRow(id).status, 'configured', 'Server läuft für CSTV noch weiter');
await app.manager.reap();
assert.equal(app.service.serverRow(id).status, 'configured', 'auch der Aufräum-Lauf wartet auf CSTV');
await waitFor(() => app.service.serverRow(id).status === 'stopped', { label: 'Server freigegeben' });
const snap = (await call('GET', `/matches/${id}`, { token: org })).body;
assert.deepEqual(snap.seriesScore, { A: 2, B: 0 });
+12
View File
@@ -62,3 +62,15 @@ test('Farben: CT blau, T gold, Sieger grün, Aufwärmphase gelb; Eingaben bringe
const [cmd] = sayCommands([`${COLOR.yellow}gelb ${COLOR.blue}blau"; quit`]);
assert.equal(cmd, `say ${COLOR.yellow}gelb ${COLOR.blue}blau quit`, 'nur erlaubte Codes, kein Befehl anhängbar, Farben am Anfang bleiben');
});
test('Mit CSTV-Verzögerung: Mapwechsel nach der Übertragung, Hinweis zum Abschalten am Serienende', () => {
const p = (lines) => lines.map((l) => l.replace(/[\x01-\x10]/g, ''));
const base = { teams, total: 3, autoAdvanceSeconds: 30, tvDelaySeconds: 90, map: { number: 1, mapName: 'Mirage', winner: 'A', score: { A: 13, B: 9 } } };
const next = p(mapEndLines({ ...base, decided: null, nextMapName: 'Inferno', series: { A: 1, B: 0 } }));
assert.equal(next.at(-1), 'Nächste Map: Inferno, wird in etwa 90 s geladen (nach der CSTV-Übertragung) - bleibt auf dem Server');
const end = p(mapEndLines({ ...base, decided: 'A', nextMapName: null, series: { A: 2, B: 0 }, shutdownSeconds: 135 }));
assert.equal(end.at(-1), 'Der Server bleibt noch etwa 2 Minuten für die CSTV-Übertragung online und schaltet sich dann ab.');
// Ohne CSTV-Verzögerung kein Hinweis zum Abschalten
const noTv = p(mapEndLines({ ...base, tvDelaySeconds: 0, decided: 'A', nextMapName: null, series: { A: 2, B: 0 } }));
assert.ok(!noTv.some((l) => l.includes('CSTV')));
});
+10
View File
@@ -97,3 +97,13 @@ test('Warteschlange gibt frei, sobald der Server per RCON antwortet – auch ohn
assert.equal(d.calls.filter((c) => c[0] === 'run').length, 2);
reachable.add(r2.handle.container);
});
test('TV_DELAY geht an das Image, sonst setzt es CSTV nach jedem Mapwechsel auf 0 Sekunden', async () => {
const d = fakeDocker();
const a = new DockerServerAdapter(options, { getUsedPorts: () => new Set(), runDocker: d.run });
a.isReachable = async () => false;
await a.launch({ matchId: 'tv1', mode: 'competitive', secrets, tvDelaySeconds: 90 });
const run = d.calls.find((c) => c.args?.[0] === 'run' || c[0] === 'run');
const env = envOf(run.args ?? run);
assert.equal(env.TV_DELAY, '90');
});
+1
View File
@@ -80,6 +80,7 @@ test('Log-Weiterleitung wird vor dem Mapwechsel gesetzt, unsichere URLs nicht',
assert.ok(ok.includes('logaddress_add_http "http://192.168.178.31:3000/api/logs/abc_D-1/KEY123"'));
assert.ok(buildMapCommands({ ...base, logUrl: 'http://x/api/logs/a/b' }).afterMapChange.includes('mp_endmatch_votenextmap 0'), 'keine Map-Abstimmung nach dem Match');
assert.ok(buildMapCommands({ ...base, logUrl: 'http://x/api/logs/a/b' }).afterMapChange.includes('sv_deltaticks_enforce 0'), 'keine Trennung wegen Delta-Ticks');
assert.ok(buildMapCommands({ ...base, logUrl: 'http://x/api/logs/a/b' }).afterMapChange.some((c) => /^tv_delay \d+$/.test(c)), 'tv_delay nach dem Laden der Map');
assert.ok(ok.indexOf('log on') < ok.indexOf('changelevel de_nuke'));
const bad = buildMapCommands({ ...base, logUrl: 'http://x"; quit' }).beforeMapChange;
assert.ok(!bad.some((c) => c.startsWith('logaddress')));