diff --git a/packages/client/src/App.tsx b/packages/client/src/App.tsx
index 6c89877..386ac76 100644
--- a/packages/client/src/App.tsx
+++ b/packages/client/src/App.tsx
@@ -208,7 +208,10 @@ function App() {
back to your game.
GLHF!
- multiplayer.joinMatch(matchId)} />
+ multiplayer.joinMatch(matchId)}
+ spectateMatch={matchId => multiplayer.spectateMatch(matchId)}
+ />
multiplayer.createMatch()}>Create
diff --git a/packages/client/src/Multiplayer.ts b/packages/client/src/Multiplayer.ts
index 9521ab3..8fbb068 100644
--- a/packages/client/src/Multiplayer.ts
+++ b/packages/client/src/Multiplayer.ts
@@ -68,6 +68,12 @@ export class Multiplayer {
this.onUpdatePacket && this.onUpdatePacket(u);
})
+ this.channel.on('spectating', (data: Data) => {
+ this.matchId = (data as {matchId: string}).matchId;
+ localStorage.setItem('matchId', this.matchId);
+ this.onMatchConnected && this.onMatchConnected(this.matchId);
+ });
+
this.channel.on('connected', (data: Data) => {
if (!this.matchId) {
throw "Server responded with connection but the multiplayer isn't initialized to a match";
@@ -139,6 +145,16 @@ export class Multiplayer {
this.channel.emit('connect', data);
});
};
+
+ spectateMatch(matchId: string) {
+ console.log(`[Multiplayer] spectating match ${matchId}`)
+ const data : IdentificationPacket = {
+ userId: this.userId,
+ matchId
+ };
+
+ this.channel.emit('spectate', data);
+ }
async leaveMatch() {
if (!this.matchId)
diff --git a/packages/client/src/components/MatchList.tsx b/packages/client/src/components/MatchList.tsx
index 0cc72e0..d11f568 100644
--- a/packages/client/src/components/MatchList.tsx
+++ b/packages/client/src/components/MatchList.tsx
@@ -4,6 +4,7 @@ import { HTTP_API_URL } from '../config'
type Props = {
joinMatch: (matchId: string) => void;
+ spectateMatch: (matchId: string) => void;
}
export function MatchList(props: Props) {
@@ -25,18 +26,21 @@ export function MatchList(props: Props) {
};
}, []);
- const matchRows = matches.map(m =>
-
+ const matchRows = matches.map(m => {
+ const joinable = m.status.id == "Lobby";
+
+ return (
{m.matchId}
{m.playerCount}
{m.status.id}
- { m.status.id == "Lobby" &&
- props.joinMatch(m.matchId)}>Join
- }
+ props.joinMatch(m.matchId)}>Join
-
- );
+
+ props.spectateMatch(m.matchId)}>Spectate
+
+ );
+ });
return (
diff --git a/packages/server/index.ts b/packages/server/index.ts
index 92a48bc..c019146 100644
--- a/packages/server/index.ts
+++ b/packages/server/index.ts
@@ -23,10 +23,16 @@ type PlayerEntry = {
channel?: ServerChannel,
}
+type SpectatorEntry = {
+ user: UserId,
+ channel: ServerChannel,
+}
+
type Match = {
game: Game,
matchId: string,
players: PlayerEntry[],
+ spectators: SpectatorEntry[],
}
const app = express()
@@ -76,7 +82,7 @@ app.post('/create', async (req, res) => {
const map = await getMap('assets/map.png');
const game = newGame(map);
const matchId = String(++lastMatchId); // TODO
- matches.push({ game, matchId, players: [] });
+ matches.push({ game, matchId, players: [], spectators: [] });
const TICK_MS = 50;
setInterval(() => {
@@ -93,6 +99,10 @@ app.post('/create', async (req, res) => {
p.channel.emit('tick', updatePackets[i]);
});
+
+ match.spectators.forEach((s, i) =>
+ s.channel.emit('tick', updatePackets[0])
+ );
// io.room(matchId).emit('tick', updatePackets[0]);
}, TICK_MS);
@@ -183,20 +193,47 @@ io.onConnection(channel => {
console.log(`${channel.id} got disconnected`)
})
+ channel.on('spectate', (data: Data) => {
+ const packet = data as IdentificationPacket;
+
+ const m = matches.find(m => m.matchId === packet.matchId);
+ if (!m) {
+ console.warn("Received a spectate request to a match that doesn't exist");
+ channel.emit('spectate failure', packet.matchId, {reliable: true});
+ return;
+ }
+
+ const spectatorEntry = {
+ channel,
+ user: packet.userId,
+ }
+
+ m.spectators.push(spectatorEntry);
+
+ channel.userData = {
+ matchId: packet.matchId
+ };
+
+ channel.join(String(packet.matchId));
+ channel.emit('spectating', {matchId: packet.matchId}, {reliable: true});
+
+ console.log(`[index] Channel of user ${packet.userId} spectating match ${packet.matchId}`);
+ });
+
channel.on('connect', (data: Data) => {
// TODO properly validate data format
const packet = data as IdentificationPacket;
const m = matches.find(m => m.matchId === packet.matchId);
if (!m) {
- console.warn("Received a connect request to a match that doesn't exist");
+ console.warn("[index] Received a connect request to a match that doesn't exist");
channel.emit('connection failure', packet.matchId, {reliable: true});
return;
}
const playerEntry = m.players.find(p => p.user === packet.userId);
if (!playerEntry) {
- console.warn(`Received a connect request to a match(${packet.matchId}) that the user(${packet.userId}) hasn't joined`);
+ console.warn(`[index] Received a connect request to a match(${packet.matchId}) that the user(${packet.userId}) hasn't joined`);
channel.emit('connection failure', packet.matchId, {reliable: true});
return;
}