Live-Ergebnisse aus CS2-Serverlogs (Backend)

Server bekommt beim Konfigurieren logaddress_add_http mit geheimem
Match-Schlüssel. Ein Parser wertet Rundenende, Seitenzuordnung, Matchstart
und Spielende aus; Stand, Rundenverlauf und Sieger werden pro Map gespeichert
und live verteilt. Der Demo-Adapter simuliert nach der Aufwärmphase einen
Matchverlauf über dieselben Logzeilen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
SPP UserandClaude Opus 5.5 committed 2026-10-25 18:54:23 +01:00
1 parent 5ea5638115
commit 5fade769e7
11 files changed
+515 -11

No files matched your search

+87
View File
@@ -0,0 +1,87 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { applyLogLines, initialLiveState, seriesScore, splitLogBody } from '../src/liveScore.js';
const ctx = { teamNames: { A: 'MaxLAN Blau', B: 'MaxLAN Rot' }, maxRounds: 24, overtimeRounds: 6 };
const ts = '10/25/2026 - 18:00:00.000 - ';
function round(winnerSide, ct, t, notice) {
const n = notice ?? (winnerSide === 'CT' ? 'CTs_Win' : 'Terrorists_Win');
const side = winnerSide === 'CT' ? 'CT' : 'TERRORIST';
return `${ts}Team "${side}" triggered "SFUI_Notice_${n}" (CT "${ct}") (T "${t}")`;
}
test('Logzeilen ergeben Spielstand, Rundenverlauf und Gründe', () => {
let state = initialLiveState({ map: 'de_mirage', ctTeam: 'A' });
const lines = [
`${ts}World triggered "Match_Start" on "de_mirage"`,
round('T', 0, 1, 'Target_Bombed'),
round('CT', 1, 1, 'Bomb_Defused'),
round('CT', 2, 1, 'Target_Saved'),
];
const { state: next, events } = applyLogLines(state, lines, ctx);
state = next;
assert.equal(state.status, 'live');
assert.deepEqual(state.score, { A: 2, B: 1 });
assert.deepEqual(state.rounds.map((r) => [r.winner, r.reason]), [['B', 'bomb'], ['A', 'defuse'], ['A', 'time']]);
assert.equal(events.filter((e) => e.type === 'round').length, 3);
});
test('Doppelt gesendete Zeilen werden nur einmal gezählt', () => {
const state = initialLiveState({ map: 'de_nuke', ctTeam: 'A' });
const line = round('CT', 1, 0);
const { state: s } = applyLogLines(state, [line, line, line], ctx);
assert.equal(s.rounds.length, 1);
assert.deepEqual(s.score, { A: 1, B: 0 });
});
test('Seitenwechsel über "Team playing"-Zeilen', () => {
let s = initialLiveState({ map: 'de_nuke', ctTeam: 'A' });
({ state: s } = applyLogLines(s, [
`${ts}MatchStatus: Team playing "CT": MaxLAN Blau`,
`${ts}MatchStatus: Team playing "TERRORIST": MaxLAN Rot`,
`${ts}World triggered "Match_Start" on "de_nuke"`,
round('CT', 1, 0),
// Halbzeit: Rot ist jetzt CT. In den Seitenwerten steht der Stand der Teams auf der jeweiligen Seite.
`${ts}MatchStatus: Team playing "CT": MaxLAN Rot`,
`${ts}MatchStatus: Team playing "TERRORIST": MaxLAN Blau`,
round('CT', 1, 1),
], ctx));
assert.deepEqual(s.score, { A: 1, B: 1 });
assert.deepEqual(s.rounds.map((r) => r.winner), ['A', 'B']);
});
test('Ohne "Team playing": Halbzeitwechsel nach Regelwerk', () => {
let s = initialLiveState({ map: 'de_inferno', ctTeam: 'B' });
const small = { ...ctx, maxRounds: 4 };
({ state: s } = applyLogLines(s, [
`${ts}World triggered "Match_Start" on "de_inferno"`,
round('CT', 1, 0), // B gewinnt als CT
round('CT', 2, 0), // B gewinnt als CT -> Halbzeit, B ist jetzt T
round('T', 0, 3), // B gewinnt als T: CT-Seite (A) hat 0, T-Seite (B) hat 3
], small));
assert.deepEqual(s.score, { A: 0, B: 3 });
assert.deepEqual(s.rounds.map((r) => r.winner), ['B', 'B', 'B']);
});
test('Game Over beendet die Map, Aufwärmrunden vor Match_Start zählen nicht', () => {
let s = initialLiveState({ map: 'de_dust2', ctTeam: 'A' });
({ state: s } = applyLogLines(s, [
round('T', 0, 1), // Aufwärm-/Messerrunde
`${ts}World triggered "Match_Start" on "de_dust2"`,
round('CT', 1, 0),
`${ts}Game Over: competitive mg_active de_dust2 score 1:0 after 2 min`,
round('T', 1, 1), // nach Spielende ignoriert
], ctx));
assert.equal(s.status, 'finished');
assert.equal(s.winner, 'A');
assert.deepEqual(s.score, { A: 1, B: 0 });
});
test('Zeilen einer anderen Map werden ignoriert, Serienstand wird gezählt', () => {
const s = initialLiveState({ map: 'de_dust2', ctTeam: 'A' });
const { state } = applyLogLines(s, [`${ts}Game Over: competitive mg_active de_nuke score 13:2 after 30 min`], ctx);
assert.equal(state.status, 'warmup');
assert.deepEqual(seriesScore([{ winner: 'A' }, { winner: 'B' }, { winner: 'A' }, { winner: null }]), { A: 2, B: 1 });
assert.deepEqual(splitLogBody('a\r\n\r\nb\n'), ['a', 'b']);
});