Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion e2e/lobby.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ test.describe('title', () => {
await expect(picker.getByRole('radio', { name: 'La VJ' })).toBeChecked();
await picker.getByRole('radio', { name: 'Le roadie' }).click();
await expect(picker.getByRole('radio', { name: 'Le roadie' })).toBeChecked();
await expect(picker).toContainText('Tank : tient la ligne');
await expect(picker).toContainText('Tient la ligne');
expect(lines).toContain('[ui] onChooseClass tank');

await expect(picker).toHaveAttribute('aria-current', 'true');
Expand Down Expand Up @@ -150,6 +150,26 @@ test.describe('local lobby', () => {
expect(lines.filter((line) => line.startsWith('[ui] onSeatClass'))).toHaveLength(2);
});

test('keeps a cursor per seat and rings only the seat in play', async ({ page }) => {
await page.goto(`${LOBBY}local-full`);

const seats = lobbyOf(page).getByRole('listitem');
const lit = (index: number) => seats.nth(index).locator('[aria-current]:not([data-quiet])');
const quiet = (index: number) => seats.nth(index).locator('[aria-current][data-quiet]');
await expect(seats.locator('[aria-current]')).toHaveCount(4);
await expect(lit(0)).toHaveCount(1);
for (const index of [1, 2, 3]) {
await expect(lit(index)).toHaveCount(0);
await expect(quiet(index)).toHaveCount(1);
}

await tap(page, 'KeyP');
await tap(page, 'ArrowDown');
await expect(lit(1)).toHaveCount(1);
await expect(lit(0)).toHaveCount(0);
await expect(quiet(0)).toHaveCount(1);
});

test('lets the first player launch from their own seat', async ({ page }) => {
const lines = logs(page);
await page.goto(`${LOBBY}local-two`);
Expand Down
19 changes: 19 additions & 0 deletions e2e/team.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,25 @@ test.describe('the HUD of a team', () => {
await expect(page.locator('.ui-band__weapon:visible')).toHaveCount(0);
});

test('shows each band the prompts of the device of its player', async ({ page }) => {
await page.goto('/dev/ui.html?screen=team&locals=3&pads=2');

const keys = page.locator('.ui-band:not(.ui-band--compact)').locator('.ui-skill__key');
await expect(keys).toHaveText(['E', 'R', 'X', 'Y', 'X', 'Y']);
});

test('keeps the traps out of the play area when several players share the screen', async ({
page,
}) => {
await page.setViewportSize({ width: 1280, height: 800 });
await page.goto('/dev/ui.html?screen=team&locals=4');

const traps = await page.getByRole('region', { name: 'Pièges' }).boundingBox();
const band = await page.locator('.ui-band').first().boundingBox();
expect(traps?.y).toBeLessThan(320);
expect(band?.height).toBeLessThan(125);
});

test('shows four full bands when four players sit at the screen', async ({ page }) => {
await page.goto('/dev/ui.html?screen=team&locals=4');

Expand Down
6 changes: 3 additions & 3 deletions src/data/classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const CLASSES: readonly ClassDefinition[] = [
{
id: 'mage',
name: 'La VJ',
role: 'Mage : dégâts de zone à distance, fragile.',
role: 'Balaie la foule de loin avec ses lasers. Fragile.',
color: '#ff2bd6',
maxHp: 80,
speed: 7.5,
Expand Down Expand Up @@ -44,7 +44,7 @@ export const CLASSES: readonly ClassDefinition[] = [
{
id: 'tank',
name: 'Le roadie',
role: 'Tank : tient la ligne, attire les bad vibes, bouclier, lent.',
role: 'Tient la ligne et attire les bad vibes. Lent.',
color: '#ff9a3d',
maxHp: 200,
speed: 6.8,
Expand Down Expand Up @@ -81,7 +81,7 @@ export const CLASSES: readonly ClassDefinition[] = [
{
id: 'healer',
name: 'Le care',
role: 'Healer : soigne, répare la scène, relève ses amis.',
role: 'Soigne, répare la scène et relève ses amis.',
color: '#7cf2b0',
maxHp: 120,
speed: 7,
Expand Down
9 changes: 8 additions & 1 deletion src/ui/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,13 @@ function playersOf(snapshot: InputSnapshot): LocalPlayer[] {
}));
}

// With `?pads=N`, the last N players of the screen play with a gamepad.
const pads = Number(params.get('pads') ?? 0);

function padSnapshot(index: number, count: number, snapshot: InputSnapshot): InputSnapshot {
return index >= count - pads ? { ...snapshot, device: 'gamepad' } : snapshot;
}

// With `?devices`, the frame lists every device: the keyboard, a first pad, and a second one that
// presses A when J is pressed.
let padPressed = false;
Expand Down Expand Up @@ -374,7 +381,7 @@ window.setInterval(() => {
lobby === null
? state.players.slice(0, localCount).map((player, index) => ({
playerId: player.id,
snapshot: index === 1 ? second : snapshot,
snapshot: padSnapshot(index, localCount, index === 1 ? second : snapshot),
}))
: playersOf(snapshot);
ui.update(state, { snapshot, players, ...devicesOf(snapshot) }, UI_FIXTURE_CONTENT);
Expand Down
6 changes: 3 additions & 3 deletions src/ui/lobby-fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ export const LOBBY_CLASSES: readonly ClassInfo[] = [
{
id: 'mage',
name: 'La VJ',
role: 'Mage : dégâts de zone à distance, fragile.',
role: 'Balaie la foule de loin avec ses lasers. Fragile.',
color: '#ff2bd6',
},
{
id: 'tank',
name: 'Le roadie',
role: 'Tank : tient la ligne, attire les bad vibes, lent.',
role: 'Tient la ligne et attire les bad vibes. Lent.',
color: '#ff9a3d',
},
{
id: 'healer',
name: 'Le care',
role: 'Healer : soigne, répare la scène, ralentit.',
role: 'Soigne, répare la scène et relève ses amis.',
color: '#7cf2b0',
},
];
Expand Down
23 changes: 22 additions & 1 deletion src/ui/lobby.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export function createLobby(actions: LobbyActions, classes: readonly ClassInfo[]
const heading = el('h2', 'ui-lobby__title');
const lead = el('p', 'ui-lobby__lead');
const header = el('header', 'ui-lobby__header');
header.append(el('p', 'ui-kicker', 'Salon'), heading, lead);
header.append(el('p', 'ui-kicker', 'À plusieurs'), heading, lead);

const error = el('p', 'ui-lobby__error');
error.setAttribute('role', 'alert');
Expand Down Expand Up @@ -291,6 +291,7 @@ export function createLobby(actions: LobbyActions, classes: readonly ClassInfo[]
let cursor = 0;
let cursorView = lobbyView(model);
const seatCursors = new Map<PlayerId, number>();
let activeSeat: PlayerId | null = null;
const seatInputs = new Map<PlayerId, MenuInput>();
const deviceInputs = new Map<DeviceId, MenuInput>();

Expand Down Expand Up @@ -320,6 +321,10 @@ export function createLobby(actions: LobbyActions, classes: readonly ClassInfo[]

function paintCursors(): void {
const marked: (HTMLElement | null)[] = [];
const typing = model.seats.find(
(seat) => cards[seat.playerId]?.input === document.activeElement,
);
const lead = typing?.playerId ?? activeSeat ?? model.seats[0]?.playerId;
if (model.mode === 'local') {
for (const seat of model.seats) {
const row = rowsOfSeat(seat)[seatCursors.get(seat.playerId) ?? 0];
Expand All @@ -346,6 +351,19 @@ export function createLobby(actions: LobbyActions, classes: readonly ClassInfo[]
} else {
node.removeAttribute('aria-current');
}
const seat = model.seats.find(
(candidate) =>
cards[candidate.playerId]?.field === node ||
cards[candidate.playerId]?.stepper.element === node,
);
setFlag(
node,
'quiet',
model.mode === 'local' &&
model.seats.length > 1 &&
seat !== undefined &&
seat.playerId !== lead,
);
}
}

Expand Down Expand Up @@ -470,6 +488,9 @@ export function createLobby(actions: LobbyActions, classes: readonly ClassInfo[]
const step = stepRow(rows.length, seatCursors.get(seat.playerId) ?? 0, own);
const row = rows[step.index];
seatCursors.set(seat.playerId, step.index);
if (Object.values(own).some(Boolean)) {
activeSeat = seat.playerId;
}
if (step.side !== 0) {
side(seat, rows[step.index], step.side);
}
Expand Down
Loading
Loading