Skip to content

Commit

Permalink
Implement conversion from engine to tachyon events
Browse files Browse the repository at this point in the history
  • Loading branch information
p2004a committed Jan 9, 2025
1 parent 9e47c98 commit 56d5907
Show file tree
Hide file tree
Showing 2 changed files with 354 additions and 2 deletions.
217 changes: 215 additions & 2 deletions src/autohost.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,44 @@ import assert from 'node:assert/strict';
import { randomUUID } from 'node:crypto';
import { once } from 'node:events';
import { GamesManager } from './games.js';
import { Autohost, _getPlayerIds } from './autohost.js';
import { Autohost, _getPlayerIds, engineEventToTachyonUpdate } from './autohost.js';
import { fakeRunEngine, EngineRunnerFake } from './engineRunner.fake.js';
import { AutohostStartRequestData, AutohostStatusEventData } from 'tachyon-protocol/types';
import {
AutohostStartRequestData,
AutohostStatusEventData,
StartUpdate,
FinishedUpdate,
EngineMessageUpdate,
EngineWarningUpdate,
EngineQuitUpdate,
PlayerJoinedUpdate,
PlayerLeftUpdate,
PlayerChatUpdate,
PlayerDefeatedUpdate,
LuaMsgUpdate,
} from 'tachyon-protocol/types';
import { scriptGameFromStartRequest } from './startScriptGen.js';
import {
EvServerStarted,
EvServerQuit,
EvServerStartPlaying,
EvServerGameOver,
EvServerMessage,
EvServerWarning,
EvPlayerJoined,
EvPlayerLeft,
EvPlayerReady,
EvPlayerChat,
EvPlayerDefeated,
EvGameLuaMsg,
EvGameTeamStat,
EventType,
ReadyState,
LeaveReason,
ChatDestination,
LuaMsgScript,
LuaMsgUIMode,
} from './engineAutohostInterface.js';

function createStartRequest(players: { name: string; userId: string }[]): AutohostStartRequestData {
return {
Expand Down Expand Up @@ -406,3 +440,182 @@ suite('Autohost', async () => {
assert.equal(er.sendPacket.mock.callCount(), 2);
});
});

suite('engine event to tachyon event translation', async () => {
function toUserId(playerNumber: number): string {
return `id:${playerNumber}`;
}

test('SERVER_STARTED event', () => {
const ev: EvServerStarted = {
type: EventType.SERVER_STARTED,
};
assert.deepEqual(engineEventToTachyonUpdate(ev, toUserId), null);
});

test('SERVER_QUIT event', () => {
const ev: EvServerQuit = {
type: EventType.SERVER_QUIT,
};
const expected: EngineQuitUpdate = {
type: 'engine_quit',
};
assert.deepEqual(engineEventToTachyonUpdate(ev, toUserId), expected);
});

test('SERVER_STARTPLAYING event', () => {
const ev: EvServerStartPlaying = {
type: EventType.SERVER_STARTPLAYING,
gameId: 'asd',
demoPath: 'asd2',
};
const expected: StartUpdate = {
type: 'start',
};
assert.deepEqual(engineEventToTachyonUpdate(ev, toUserId), expected);
});

test('SERVER_GAMEOVER event', () => {
const ev: EvServerGameOver = {
type: EventType.SERVER_GAMEOVER,
player: 0,
winningAllyTeams: [0],
};
const expected: FinishedUpdate = {
type: 'finished',
userId: 'id:0',
winningAllyTeams: [0],
};
assert.deepEqual(engineEventToTachyonUpdate(ev, toUserId), expected);
});

test('SERVER_MESSAGE event', () => {
const ev: EvServerMessage = {
type: EventType.SERVER_MESSAGE,
message: 'some message',
};
const expected: EngineMessageUpdate = {
type: 'engine_message',
message: 'some message',
};
assert.deepEqual(engineEventToTachyonUpdate(ev, toUserId), expected);
});

test('SERVER_WARNING event', () => {
const ev: EvServerWarning = {
type: EventType.SERVER_WARNING,
message: 'warning',
};
const expected: EngineWarningUpdate = {
type: 'engine_warning',
message: 'warning',
};
assert.deepEqual(engineEventToTachyonUpdate(ev, toUserId), expected);
});

test('PLAYER_JOINED event', () => {
const ev: EvPlayerJoined = {
type: EventType.PLAYER_JOINED,
player: 1,
name: 'john',
};
const expected: PlayerJoinedUpdate = {
type: 'player_joined',
userId: 'id:1',
playerNumber: 1,
};
assert.deepEqual(engineEventToTachyonUpdate(ev, toUserId), expected);
});

test('PLAYER_LEFT event', () => {
const ev: EvPlayerLeft = {
type: EventType.PLAYER_LEFT,
player: 3,
reason: LeaveReason.KICKED,
};
const expected: PlayerLeftUpdate = {
type: 'player_left',
userId: 'id:3',
reason: 'kicked',
};
assert.deepEqual(engineEventToTachyonUpdate(ev, toUserId), expected);
});

test('PLAYER_READY event', () => {
const ev: EvPlayerReady = {
type: EventType.PLAYER_READY,
player: 0,
state: ReadyState.NOT_READY,
};
assert.deepEqual(engineEventToTachyonUpdate(ev, toUserId), null);
});

test('PLAYER_CHAT event', () => {
const ev1: EvPlayerChat = {
type: EventType.PLAYER_CHAT,
message: 'lool',
fromPlayer: 10,
destination: ChatDestination.TO_ALLIES,
};
const expected1: PlayerChatUpdate = {
type: 'player_chat',
message: 'lool',
userId: 'id:10',
destination: 'allies',
};
assert.deepEqual(engineEventToTachyonUpdate(ev1, toUserId), expected1);

const ev2: EvPlayerChat = {
type: EventType.PLAYER_CHAT,
message: 'lool',
fromPlayer: 10,
toPlayer: 11,
destination: ChatDestination.TO_PLAYER,
};
const expected2: PlayerChatUpdate = {
type: 'player_chat',
message: 'lool',
userId: 'id:10',
toUserId: 'id:11',
destination: 'player',
};
assert.deepEqual(engineEventToTachyonUpdate(ev2, toUserId), expected2);
});

test('PLAYER_DEFEATED event', () => {
const ev: EvPlayerDefeated = {
type: EventType.PLAYER_DEFEATED,
player: 1,
};
const expected: PlayerDefeatedUpdate = {
type: 'player_defeated',
userId: 'id:1',
};
assert.deepEqual(engineEventToTachyonUpdate(ev, toUserId), expected);
});

test('GAME_LUAMSG event', () => {
const ev: EvGameLuaMsg = {
type: EventType.GAME_LUAMSG,
player: 2,
script: LuaMsgScript.UI,
uiMode: LuaMsgUIMode.ALL,
data: Buffer.from('2983X7RNMQ74'),
};
const expected: LuaMsgUpdate = {
type: 'luamsg',
userId: 'id:2',
script: 'ui',
uiMode: 'all',
data: Buffer.from('2983X7RNMQ74').toString('base64'),
};
assert.deepEqual(engineEventToTachyonUpdate(ev, toUserId), expected);
});

test('GAME_TEAMSTAT event', () => {
const ev = {
type: EventType.GAME_TEAMSTAT,
} as EvGameTeamStat; // Yep, putting all in yet as we expect null anyway.
assert.deepEqual(engineEventToTachyonUpdate(ev, toUserId), null);
});
});
139 changes: 139 additions & 0 deletions src/autohost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,21 @@ import {
AutohostStartOkResponseData,
AutohostStartRequestData,
AutohostSubscribeUpdatesRequestData,
AutohostUpdateEventData,
PlayerLeftUpdate,
PlayerChatUpdate,
LuaMsgUpdate,
} from 'tachyon-protocol/types';
import {
serializeMessagePacket,
serializeCommandPacket,
PacketSerializeError,
type Event,
EventType,
LeaveReason,
ChatDestination,
LuaMsgUIMode,
LuaMsgScript,
} from './engineAutohostInterface.js';
import { type GamesManager } from './games.js';
import { MultiIndex } from './multiIndex.js';
Expand Down Expand Up @@ -212,3 +222,132 @@ export class Autohost implements TachyonAutohost {
return playerId.name;
}
}

function toTachyonLeaveReason(reason: LeaveReason): PlayerLeftUpdate['reason'] {
switch (reason) {
case LeaveReason.KICKED:
return 'kicked';
case LeaveReason.LEFT:
return 'left';
case LeaveReason.LOST_CONNECTION:
return 'lost_connection';
}
}

function toTachyonDestination(destination: ChatDestination): PlayerChatUpdate['destination'] {
switch (destination) {
case ChatDestination.TO_PLAYER:
return 'player';
case ChatDestination.TO_ALLIES:
return 'allies';
case ChatDestination.TO_EVERYONE:
return 'all';
case ChatDestination.TO_SPECTATORS:
return 'spectators';
}
}

function toTachyonLuaMsgScript(script: LuaMsgScript): LuaMsgUpdate['script'] {
switch (script) {
case LuaMsgScript.GAIA:
return 'game';
case LuaMsgScript.RULES:
return 'rules';
case LuaMsgScript.UI:
return 'ui';
}
}

function toTachyonLuaMsgUIMode(uiMode?: LuaMsgUIMode): LuaMsgUpdate['uiMode'] {
switch (uiMode) {
case undefined:
return undefined;
case LuaMsgUIMode.ALL:
return 'all';
case LuaMsgUIMode.ALLIES:
return 'allies';
case LuaMsgUIMode.SPECTATORS:
return 'spectators';
}
}

/**
* Convert the engine event to tachyon event update data.
*
* @param ev Event
* @param toUserId Function to map from player number in game to userId
* @returns Tachyon update data
*/
export function engineEventToTachyonUpdate(
ev: Event,
toUserId: (playerNumber: number) => string,
): AutohostUpdateEventData['update'] | null {
switch (ev.type) {
case EventType.GAME_LUAMSG:
return {
type: 'luamsg',
userId: toUserId(ev.player),
script: toTachyonLuaMsgScript(ev.script),
uiMode: toTachyonLuaMsgUIMode(ev.uiMode),
data: ev.data.toString('base64'),
};
case EventType.PLAYER_CHAT: {
const destination = toTachyonDestination(ev.destination);
if (destination === 'player') {
return {
type: 'player_chat',
userId: toUserId(ev.fromPlayer),
destination,
message: ev.message,
toUserId: toUserId(ev.toPlayer!),
};
} else {
return {
type: 'player_chat',
userId: toUserId(ev.fromPlayer),
destination,
message: ev.message,
};
}
}
case EventType.PLAYER_DEFEATED:
return {
type: 'player_defeated',
userId: toUserId(ev.player),
};
case EventType.PLAYER_JOINED: {
return {
type: 'player_joined',
userId: toUserId(ev.player),
playerNumber: ev.player,
};
}
case EventType.PLAYER_LEFT:
return {
type: 'player_left',
userId: toUserId(ev.player),
reason: toTachyonLeaveReason(ev.reason),
};
case EventType.SERVER_GAMEOVER:
if (ev.winningAllyTeams.length < 1) {
throw new Error('winning ally teams must be at least 1');
}
return {
type: 'finished',
userId: toUserId(ev.player),
winningAllyTeams: ev.winningAllyTeams as [number, ...number[]],
};
case EventType.SERVER_MESSAGE:
return { type: 'engine_message', message: ev.message };
case EventType.SERVER_STARTPLAYING:
return { type: 'start' };
case EventType.SERVER_WARNING:
return { type: 'engine_warning', message: ev.message };
case EventType.SERVER_QUIT:
return { type: 'engine_quit' };
case EventType.SERVER_STARTED: // The return of start call indicates that server started, Tachyon doens't have this message.
case EventType.GAME_TEAMSTAT: // At the moment Tachyon lacks definition of this message.
case EventType.PLAYER_READY: // In my testing, it didn't behave as expected. Tachyon lacks definition for this message.
return null;
}
}

0 comments on commit 56d5907

Please sign in to comment.