Veto: Seitenwahl per Messerrunde, freie Messer-Server in der Lobby, Organisator legt fest, wer beginnt
This commit is contained in:
1 parent
7b6ce01d05
commit
cdf41df01c
9 files changed
+138
-8
No files matched your search
@@ -195,7 +195,7 @@ export function createAdminRouter({ service, manager, config, settings, knife, p
|
||||
action('server/pause', (v) => manager.setPaused(v, true));
|
||||
action('server/resume', (v) => manager.setPaused(v, false));
|
||||
action('series/next', (v) => manager.loadNextMap(v));
|
||||
action('veto/start', (v) => service.startVeto(v));
|
||||
action('veto/start', (v, b) => service.startVeto(v, { firstTeam: b.firstTeam }));
|
||||
action('veto/action', (v, b) => service.vetoAction(
|
||||
adminViewer(v.matchId, { team: b.team, isCaptain: true, role: 'captain' }),
|
||||
{ stepIndex: b.stepIndex, map: b.map, side: b.side },
|
||||
|
||||
+1
-1
@@ -117,7 +117,7 @@ export function createApiRouter({ service, manager, config, pools, adapter, sett
|
||||
action('kick', (v, b) => service.kick(v, b.playerId));
|
||||
action('captain', (v, b) => service.setCaptain(v, b.playerId));
|
||||
action('codes/regenerate', (v, b) => service.regenerateCode(v, b.target));
|
||||
action('veto/start', (v) => service.startVeto(v));
|
||||
action('veto/start', (v, b) => service.startVeto(v, { firstTeam: b.firstTeam }));
|
||||
action('veto/action', (v, b) => service.vetoAction(v, b));
|
||||
action('server/retry', (v) => manager.retry(v));
|
||||
action('server/stop', (v) => manager.stopByOrganizer(v));
|
||||
|
||||
@@ -150,7 +150,8 @@ export class MatchService extends EventEmitter {
|
||||
if (!formatInfo?.supported) {
|
||||
throw new HttpError(400, `${String(format).toUpperCase()} wird für ${pool.label} nicht unterstützt`, 'unsupported_format');
|
||||
}
|
||||
if (!['coin', 'A', 'B'].includes(vetoStart)) throw new HttpError(400, 'Ungültige Veto-Startregel', 'invalid_input');
|
||||
// knife = Seitenwahl per Messerrunde: der Organisator legt nach der Runde fest, wer das Veto beginnt
|
||||
if (!['coin', 'A', 'B', 'knife'].includes(vetoStart)) throw new HttpError(400, 'Ungültige Veto-Startregel', 'invalid_input');
|
||||
const nameA = cleanName(teamA, 'Name von Team A');
|
||||
const nameB = cleanName(teamB, 'Name von Team B');
|
||||
if (nameA.toLowerCase() === nameB.toLowerCase()) {
|
||||
@@ -326,7 +327,11 @@ export class MatchService extends EventEmitter {
|
||||
|
||||
// ---------- Veto ----------
|
||||
|
||||
startVeto(viewer) {
|
||||
/**
|
||||
* Veto starten. Bei Seitenwahl per Messerrunde (veto_start = knife) gibt der Organisator mit
|
||||
* `firstTeam` an, welches Team das Veto beginnt.
|
||||
*/
|
||||
startVeto(viewer, { firstTeam: chosen } = {}) {
|
||||
this.require(viewer, 'organizer');
|
||||
this.db.transaction(() => {
|
||||
const match = this.getMatch(viewer.matchId);
|
||||
@@ -335,11 +340,16 @@ export class MatchService extends EventEmitter {
|
||||
if (!readiness.A.ready || !readiness.B.ready) {
|
||||
throw new HttpError(409, 'Beide Teams müssen vollständig und bereit sein', 'teams_not_ready');
|
||||
}
|
||||
const firstTeam = match.first_team ?? (Math.random() < 0.5 ? 'A' : 'B');
|
||||
const knife = match.veto_start === 'knife';
|
||||
if (knife && !['A', 'B'].includes(chosen)) {
|
||||
throw new HttpError(400, 'Nach der Messerrunde festlegen, welches Team das Veto beginnt', 'first_team_required');
|
||||
}
|
||||
const firstTeam = knife ? chosen : match.first_team ?? (Math.random() < 0.5 ? 'A' : 'B');
|
||||
this.db
|
||||
.prepare("UPDATE matches SET status = 'veto', first_team = ?, veto_started_at = ?, updated_at = ? WHERE id = ?")
|
||||
.run(firstTeam, this.now(), this.now(), match.id);
|
||||
const how = match.veto_start === 'coin' ? 'per Münzwurf ausgelost' : 'vom Organisator festgelegt';
|
||||
const how = knife ? 'nach der Messerrunde vom Organisator festgelegt'
|
||||
: match.veto_start === 'coin' ? 'per Münzwurf ausgelost' : 'vom Organisator festgelegt';
|
||||
this.log(match.id, 'veto', `Veto gestartet – ${this.teamName(match, firstTeam)} beginnt (${how})`);
|
||||
this.notify('veto_start', match.id, {
|
||||
title: '🎮 Veto gestartet',
|
||||
|
||||
@@ -607,3 +607,44 @@ test('Map-Pools im Admin: Auswahl gilt für neue Matches, Veto passt sich an, la
|
||||
await close();
|
||||
}
|
||||
});
|
||||
test('Seitenwahl per Messerrunde: Organisator legt fest, wer das Veto beginnt', async () => {
|
||||
const { app, call, close } = await setup();
|
||||
try {
|
||||
const created = await call('POST', '/matches', { body: { teamA: 'Alpha', teamB: 'Bravo', mode: 'wingman', format: 'bo3', vetoStart: 'knife' } });
|
||||
assert.equal(created.status, 200, JSON.stringify(created.body));
|
||||
const { id, organizerToken: org } = created.body;
|
||||
const snap = (await call('GET', `/matches/${id}`, { token: org })).body;
|
||||
assert.equal(snap.vetoStart, 'knife');
|
||||
const codes = snap.organizer.codes;
|
||||
for (const [name, team] of [['a1', 'A'], ['a2', 'A'], ['b1', 'B'], ['b2', 'B']]) {
|
||||
const r = await call('POST', `/matches/${id}/join`, { body: { code: codes[team], nickname: name } });
|
||||
await call('POST', `/matches/${id}/ready`, { token: r.body.playerToken, body: { ready: true } });
|
||||
}
|
||||
// Ohne Angabe, wer beginnt, startet das Veto nicht
|
||||
const missing = await call('POST', `/matches/${id}/veto/start`, { token: org });
|
||||
assert.equal(missing.status, 400);
|
||||
assert.equal(missing.body.code, 'first_team_required');
|
||||
assert.equal((await call('POST', `/matches/${id}/veto/start`, { token: org, body: { firstTeam: 'X' } })).status, 400);
|
||||
|
||||
const ok = await call('POST', `/matches/${id}/veto/start`, { token: org, body: { firstTeam: 'B' } });
|
||||
assert.equal(ok.status, 200, JSON.stringify(ok.body));
|
||||
const after = (await call('GET', `/matches/${id}`, { token: org })).body;
|
||||
assert.equal(after.status, 'veto');
|
||||
assert.equal(after.veto.current.team, 'B', 'das gewählte Team beginnt');
|
||||
assert.ok(app.service.db.prepare("SELECT message FROM events WHERE match_id = ? AND message LIKE '%Messerrunde%'").get(id));
|
||||
} finally {
|
||||
await close();
|
||||
}
|
||||
});
|
||||
|
||||
test('Ohne Messerrunde bleibt firstTeam wirkungslos', async () => {
|
||||
const { call, close } = await setup();
|
||||
try {
|
||||
const { id, org, tokens } = await readyWingmanMatch(call, 'bo1');
|
||||
await call('POST', `/matches/${id}/veto/start`, { token: org, body: { firstTeam: 'B' } });
|
||||
const snap = (await call('GET', `/matches/${id}`, { token: tokens.a1 })).body;
|
||||
assert.equal(snap.veto.current.team, 'A', 'feste Startregel A gilt');
|
||||
} finally {
|
||||
await close();
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user