- Stream-Übersicht unter /stream/<Zuschauercode> (1920x1080, Lobby, Veto, Spielstand) - Live-Stand, Rundenverlauf und Serienstand auf Match-Seite und Übersicht - Parser mit echten AMP-Logs geprüft: Game Over mit leerer Map-Gruppe, Match_Start vor der Aufwärmphase, veraltete Seitenzuordnung nach der Halbzeit - Nach Map-Ende automatisch nächste Map bzw. Abschluss der entschiedenen Serie - LOG_DEBUG zur Fehlersuche, README und .env.example ergänzt
71 lines
2.8 KiB
JavaScript
71 lines
2.8 KiB
JavaScript
// DEMO: erzeugt einen zufälligen Matchverlauf als CS2-Logzeilen.
|
|
// Die Zeilen laufen durch denselben Parser wie echte Serverlogs, so lässt sich die
|
|
// Live-Anzeige ohne CS2-Server testen. Ergebnisse sind frei erfunden.
|
|
|
|
const REASONS = {
|
|
CT: ['CTs_Win', 'Bomb_Defused', 'Target_Saved'],
|
|
T: ['Terrorists_Win', 'Target_Bombed'],
|
|
};
|
|
|
|
const stamp = () => {
|
|
const d = new Date();
|
|
const p = (n, l = 2) => String(n).padStart(l, '0');
|
|
return `${p(d.getMonth() + 1)}/${p(d.getDate())}/${d.getFullYear()} - ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}.${p(d.getMilliseconds(), 3)} - `;
|
|
};
|
|
|
|
export class DemoMatchSimulator {
|
|
constructor({ random = Math.random } = {}) {
|
|
this.runs = new Map();
|
|
this.random = random;
|
|
}
|
|
|
|
start(matchId, { map, teamNames, ctTeam, maxRounds, roundMs, onLines }) {
|
|
this.stop(matchId);
|
|
const other = (t) => (t === 'A' ? 'B' : 'A');
|
|
const run = { sides: { CT: ctTeam, T: other(ctTeam) }, score: { A: 0, B: 0 }, played: 0 };
|
|
const toWin = maxRounds / 2 + 1;
|
|
// Leichte Stärke-Unterschiede, damit nicht jedes Demo-Match gleich aussieht
|
|
const strengthA = 0.4 + this.random() * 0.2;
|
|
|
|
const teamLines = () => [
|
|
`${stamp()}MatchStatus: Team playing "CT": ${teamNames[run.sides.CT]}`,
|
|
`${stamp()}MatchStatus: Team playing "TERRORIST": ${teamNames[run.sides.T]}`,
|
|
];
|
|
onLines([...teamLines(), `${stamp()}World triggered "Match_Start" on "${map}"`]);
|
|
|
|
run.timer = setInterval(() => {
|
|
const winner = this.random() < strengthA ? 'A' : 'B';
|
|
const side = run.sides.CT === winner ? 'CT' : 'T';
|
|
run.score[winner] += 1;
|
|
run.played += 1;
|
|
const notice = REASONS[side][Math.floor(this.random() * REASONS[side].length)];
|
|
// Reihenfolge wie bei einem echten CS2-Server: Rundenende, Seitenzuordnung der gespielten Runde, Round_End
|
|
const lines = [
|
|
`${stamp()}Team "${side === 'CT' ? 'CT' : 'TERRORIST'}" triggered "SFUI_Notice_${notice}" (CT "${run.score[run.sides.CT]}") (T "${run.score[run.sides.T]}")`,
|
|
...teamLines(),
|
|
`${stamp()}World triggered "Round_End"`,
|
|
];
|
|
if (run.score[winner] >= toWin) {
|
|
lines.push(`${stamp()}Game Over: competitive mg_active ${map} score ${run.score[run.sides.CT]}:${run.score[run.sides.T]} after 1 min`);
|
|
onLines(lines);
|
|
this.stop(matchId);
|
|
return;
|
|
}
|
|
// Halbzeit: Seiten wechseln, die nächsten Zeilen nennen die neue Zuordnung
|
|
if (run.played === maxRounds / 2) run.sides = { CT: run.sides.T, T: run.sides.CT };
|
|
onLines(lines);
|
|
}, roundMs);
|
|
this.runs.set(matchId, run);
|
|
}
|
|
|
|
stop(matchId) {
|
|
const run = this.runs.get(matchId);
|
|
if (run) clearInterval(run.timer);
|
|
this.runs.delete(matchId);
|
|
}
|
|
|
|
stopAll() {
|
|
for (const id of [...this.runs.keys()]) this.stop(id);
|
|
}
|
|
}
|