Live-Ergebnisse: Stream-Seite, Anzeige, automatischer Mapwechsel
- 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 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5fade769e7
commit
43dc5b837a
20 files changed
+901
-44
No files matched your search
@@ -416,9 +416,14 @@ export class MatchService extends EventEmitter {
|
||||
|
||||
advanceSeries(viewer) {
|
||||
this.require(viewer, 'organizer');
|
||||
return this.advanceSeriesFor(viewer.matchId);
|
||||
}
|
||||
|
||||
/** Nächste Map der Serie vormerken (Organisator oder automatisch nach Map-Ende). */
|
||||
advanceSeriesFor(matchId) {
|
||||
let nextNumber;
|
||||
this.db.transaction(() => {
|
||||
const match = this.getMatch(viewer.matchId);
|
||||
const match = this.getMatch(matchId);
|
||||
if (match.status !== 'live') throw new HttpError(409, 'Die nächste Map kann erst geladen werden, wenn das Match läuft', 'wrong_phase');
|
||||
const maps = this.seriesMaps(match.id);
|
||||
nextNumber = match.current_map_number + 1;
|
||||
@@ -430,7 +435,7 @@ export class MatchService extends EventEmitter {
|
||||
.run(nextNumber, this.now(), match.id);
|
||||
this.log(match.id, 'series', `Map ${nextNumber} der Serie wird geladen`);
|
||||
})();
|
||||
this.changed(viewer.matchId);
|
||||
this.changed(matchId);
|
||||
return nextNumber;
|
||||
}
|
||||
|
||||
@@ -443,6 +448,18 @@ export class MatchService extends EventEmitter {
|
||||
.run(matchId, number);
|
||||
}
|
||||
|
||||
/** Organisator hat die Aufwärmphase beendet: ab dem nächsten Match_Start wird gezählt. */
|
||||
markWarmupEnded(matchId) {
|
||||
const match = this.getMatch(matchId);
|
||||
const row = this.seriesMaps(matchId).find((m) => m.number === match.current_map_number);
|
||||
if (!row) return;
|
||||
const state = row.live ? JSON.parse(row.live) : initialLiveState({ map: row.map, ctTeam: row.ct_team });
|
||||
state.warmupEnded = true;
|
||||
this.db
|
||||
.prepare('UPDATE series_maps SET live = ? WHERE match_id = ? AND number = ?')
|
||||
.run(JSON.stringify(state), matchId, row.number);
|
||||
}
|
||||
|
||||
/** Logzeilen vom CS2-Server (logaddress_add_http). Der Schlüssel in der URL identifiziert den Server. */
|
||||
ingestLogs(matchId, key, body) {
|
||||
const server = this.serverRow(matchId);
|
||||
@@ -488,6 +505,10 @@ export class MatchService extends EventEmitter {
|
||||
const needed = Math.floor(BEST_OF[ended.match.format] / 2) + 1;
|
||||
const decided = score.A >= needed ? 'A' : score.B >= needed ? 'B' : null;
|
||||
if (decided) this.log(matchId, 'score', `Serie entschieden: ${this.teamName(ended.match, decided)} gewinnt ${score[decided]}:${score[decided === 'A' ? 'B' : 'A']}`);
|
||||
const hasNext = maps.some((m) => m.number === ended.row.number + 1);
|
||||
this.changed(matchId);
|
||||
this.emit('mapEnded', { matchId, number: ended.row.number, decided, hasNext });
|
||||
return;
|
||||
}
|
||||
this.changed(matchId);
|
||||
}
|
||||
@@ -505,12 +526,16 @@ export class MatchService extends EventEmitter {
|
||||
|
||||
close(viewer, status) {
|
||||
this.require(viewer, 'organizer');
|
||||
const match = this.getMatch(viewer.matchId);
|
||||
this.closeMatch(viewer.matchId, status);
|
||||
}
|
||||
|
||||
closeMatch(matchId, status, message) {
|
||||
const match = this.getMatch(matchId);
|
||||
if (CLOSED_STATUSES.includes(match.status)) throw new HttpError(409, 'Das Match ist bereits beendet', 'match_closed');
|
||||
if (status === 'finished') {
|
||||
this.db.prepare("UPDATE series_maps SET status = 'done' WHERE match_id = ? AND status IN ('live','next')").run(match.id);
|
||||
}
|
||||
this.log(match.id, 'status', status === 'finished' ? 'Match abgeschlossen' : 'Match abgebrochen');
|
||||
this.log(match.id, 'status', message ?? (status === 'finished' ? 'Match abgeschlossen' : 'Match abgebrochen'));
|
||||
this.setStatus(match.id, status);
|
||||
this.emit('closed', match.id);
|
||||
}
|
||||
@@ -639,6 +664,7 @@ export class MatchService extends EventEmitter {
|
||||
},
|
||||
canStartVeto: match.status === 'lobby' && readiness.A.ready && readiness.B.ready,
|
||||
requireFullTeams: this.config.requireFullTeams,
|
||||
autoAdvanceSeconds: this.config.autoAdvanceSeconds,
|
||||
maps: pool.maps,
|
||||
veto: publicView
|
||||
? null
|
||||
|
||||
Reference in new issue
Block a user