diff --git a/server/src/servers/serverManager.js b/server/src/servers/serverManager.js index 586b2c1..111725a 100644 --- a/server/src/servers/serverManager.js +++ b/server/src/servers/serverManager.js @@ -392,7 +392,8 @@ export class ServerManager { maybeConfigure(matchId) { const match = this.service.getMatch(matchId); const row = this.row(matchId); - if (match.status === 'server_setup' && row?.status === 'ready') { + // Auch im laufenden Match: nach einem Neustart des Servers die aktuelle Map samt Passwort wieder laden + if (['server_setup', 'live'].includes(match.status) && row?.status === 'ready') { this.configure(matchId).catch((err) => this.logger.error(err)); } } @@ -467,7 +468,7 @@ export class ServerManager { await this.provision(matchId); return; } - if (row.status === 'config_failed' || (row.status === 'ready' && match.status === 'server_setup')) { + if (row.status === 'config_failed' || (row.status === 'ready' && ['server_setup', 'live'].includes(match.status))) { this.service.log(matchId, 'server', 'Organisator wiederholt die Konfiguration'); this.configure(matchId).catch((err) => this.logger.error(err)); return; @@ -591,11 +592,12 @@ export class ServerManager { continue; } const webActive = this.service.presence(row.match_id).size > 0; - let lastUse = match.last_activity_at; + // Ein (neu) gestarteter Server zählt als Nutzung, sonst stoppt ein Neustart nach langer Pause sofort wieder + let lastUse = Math.max(match.last_activity_at, row.started_at ?? 0); if (row.status === 'configured' && row.player_count !== null) { // Echter Server mit bekannter Spielerzahl: entscheidend ist, ob jemand auf dem Server ist. if (row.player_count > 0) continue; - lastUse = Math.max(row.configured_at ?? 0, row.last_player_seen_at ?? 0); + lastUse = Math.max(row.configured_at ?? 0, row.last_player_seen_at ?? 0, row.started_at ?? 0); } else if (webActive) { continue; } diff --git a/server/test/api.test.js b/server/test/api.test.js index 3e2d4d0..1fb51a2 100644 --- a/server/test/api.test.js +++ b/server/test/api.test.js @@ -477,3 +477,34 @@ test('Spieler verlassen die Lobby, Captain geht über', async () => { await close(); } }); +test('Neustart im laufenden Match: Map wird wieder geladen, Server läuft die volle Frist', async () => { + const { app, call, close } = await setup({ idleTimeoutMinutes: 30 }); + try { + const { id, org, tokens } = await readyWingmanMatch(call, 'bo1'); + await call('POST', `/matches/${id}/veto/start`, { token: org }); + await playVeto(call, id, { A: tokens.a1, B: tokens.b1 }); + await app.manager.poll(); + await waitFor(() => app.service.getMatch(id).status === 'live', { label: 'live' }); + + // Lange nichts los, dann stoppt der Server + const hourAgo = Date.now() - 60 * 60_000; + app.db.prepare('UPDATE matches SET last_activity_at = ? WHERE id = ?').run(hourAgo, id); + await call('POST', `/matches/${id}/server/stop`, { token: org }); + await waitFor(() => app.service.serverRow(id).status === 'stopped', { label: 'gestoppt' }); + + // Neustart durch den Organisator: Server fährt hoch und lädt die Map wieder (Match ist weiter "live") + const res = await call('POST', `/matches/${id}/server/retry`, { token: org }); + assert.equal(res.status, 200, JSON.stringify(res.body)); + await waitFor(async () => { await app.manager.poll(); return app.service.serverRow(id).status === 'configured'; }, { label: 'neu konfiguriert' }); + assert.equal(app.service.getMatch(id).status, 'live'); + + // Die Abschaltung darf den frisch gestarteten Server nicht sofort wieder stoppen + await app.manager.reap(); + assert.equal(app.service.serverRow(id).status, 'configured'); + // Nach Ablauf der Frist ab dem Neustart dagegen schon + await app.manager.reap(Date.now() + 31 * 60_000); + assert.equal(app.service.serverRow(id).status, 'stopped'); + } finally { + await close(); + } +});