Add character emotes across bot friend and ranked matches
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createEmotes } from '../src/shared/emotes.js';
|
||||
import { EMOTE_COOLDOWN_MS, EMOTE_DURATION_MS } from '../constants/emotes.js';
|
||||
|
||||
test('emotes validate fixed choices, rate limit each player and expire without changing battle state', () => {
|
||||
const emotes = createEmotes();
|
||||
assert.equal(emotes.send(0, '<script>', 1000), false);
|
||||
assert.equal(emotes.send(0, 'greet', 1000), true);
|
||||
assert.equal(emotes.send(0, 'taunt', 1001), false);
|
||||
assert.equal(emotes.send(1, 'thanks', 1001), true);
|
||||
assert.equal(emotes.view(1001)[0]?.id, 'greet');
|
||||
const snapshot = emotes.view(1001);
|
||||
snapshot[0]!.id = 'threaten';
|
||||
assert.equal(emotes.view(1001)[0]?.id, 'greet');
|
||||
assert.equal(emotes.view(1000 + EMOTE_DURATION_MS)[0], null);
|
||||
assert.equal(emotes.send(0, 'taunt', 1000 + EMOTE_COOLDOWN_MS), true);
|
||||
});
|
||||
@@ -32,6 +32,24 @@ class Wire {
|
||||
close() { this.connected = false; }
|
||||
disconnect() { this.connected = false; for (const h of this.states) h('disconnected'); }
|
||||
}
|
||||
test('peer emotes reach the correct slot without exposing choices or changing the turn', async () => {
|
||||
const h = setup(), [a, b] = h.controllers;
|
||||
try {
|
||||
await Promise.all(h.controllers.map(c => c.start())); await h.clock.advance(4000);
|
||||
await a.submitAction('charge'); await h.clock.advance(1);
|
||||
const before = b.view();
|
||||
assert.equal(a.sendEmote('greet'), true);
|
||||
assert.equal(a.sendEmote('taunt'), false);
|
||||
await h.clock.advance(1);
|
||||
assert.equal(b.view().emotes?.[0]?.id, 'greet');
|
||||
assert.equal(b.view().ownAction, null);
|
||||
assert.equal(b.view().turnId, before.turnId);
|
||||
assert.deepEqual(b.view().energy, before.energy);
|
||||
assert.equal(b.view().phase, 'selecting');
|
||||
await b.leave();
|
||||
assert.equal(b.sendEmote('threaten'), false);
|
||||
} finally { h.close(); }
|
||||
});
|
||||
function setup(onRematch?: () => Promise<void>, guestClockOffset = 0, guestStartDelay = 0) {
|
||||
const clock = new ManualClock(); clock.time = 1000;
|
||||
const a = new Wire(), b = new Wire(); a.remote = b; b.remote = a;
|
||||
|
||||
@@ -17,6 +17,26 @@ import { EQUIPMENT_IDS } from '../constants/cosmetics.js';
|
||||
import { createRoomStore } from '../src/server/rooms.js';
|
||||
|
||||
const integration = { skip: !process.env.TEST_MONGODB_URI };
|
||||
test('ranked emotes authenticate membership, reach both players and preserve hidden actions', integration, async () => {
|
||||
const f = await fixture();
|
||||
try {
|
||||
await f.service.join('player-a', f.a.gameSessionId);
|
||||
const paired = await f.service.join('player-b', f.b.gameSessionId);
|
||||
const matchId = paired.match!.matchId;
|
||||
await f.clock.advance(paired.match!.startsAt - f.clock.now());
|
||||
await f.service.action('player-a', f.a.gameSessionId, matchId, 1, 'charge');
|
||||
const sent = await f.service.emote('player-a', f.a.gameSessionId, matchId, 'greet');
|
||||
const received = await f.service.status('player-b', f.b.gameSessionId);
|
||||
assert.equal(received.match!.emotes?.[0]?.id, 'greet');
|
||||
assert.deepEqual(received.match!.emotes, sent.match!.emotes);
|
||||
assert.equal(received.match!.ownAction, null);
|
||||
assert.deepEqual(received.match!.energy, [0, 0]);
|
||||
await assert.rejects(f.service.emote('player-a', f.a.gameSessionId, matchId, 'thanks'), /INVALID_REQUEST/);
|
||||
await assert.rejects(f.service.emote('player-b', f.b.gameSessionId, matchId, 'arbitrary text'), /INVALID_REQUEST/);
|
||||
await assert.rejects(f.service.emote('player-a', f.b.gameSessionId, matchId, 'thanks'), /INVALID_SESSION/);
|
||||
await assert.rejects(f.service.emote('player-b', f.b.gameSessionId, 'other-match', 'thanks'), /NO_ACTIVE_MATCH/);
|
||||
} finally { await f.close(); }
|
||||
});
|
||||
function observeResultReads(db: Db, afterRead: (filter?: Record<string, unknown>) => Promise<void>) {
|
||||
const records = db.collection('ranked_matches');
|
||||
return new Proxy(db, { get(target, key) {
|
||||
|
||||
Reference in New Issue
Block a user