Add landing page player count updates

This commit is contained in:
schmelczerandras 2020-10-21 20:27:03 +02:00
parent c844e6c52b
commit 2509199abc
4 changed files with 111 additions and 22 deletions

View file

@ -12,6 +12,8 @@ import { createWorld } from './map/create-world';
import { DeltaTimeCalculator } from './helper/delta-time-calculator'; import { DeltaTimeCalculator } from './helper/delta-time-calculator';
import { Options } from './options'; import { Options } from './options';
const playerCountSubscribedRoom = 'playerCountUpdates';
export class GameServer { export class GameServer {
private objects = new PhysicalContainer(); private objects = new PhysicalContainer();
private players: Array<Player> = []; private players: Array<Player> = [];
@ -20,7 +22,7 @@ export class GameServer {
private serverName: string; private serverName: string;
private playerLimit: number; private playerLimit: number;
constructor(io: ioserver.Server, options: Options) { constructor(private readonly io: ioserver.Server, options: Options) {
this.serverName = options.name; this.serverName = options.name;
this.playerLimit = options.playerLimit; this.playerLimit = options.playerLimit;
@ -36,18 +38,27 @@ export class GameServer {
player.sendCommand(command); player.sendCommand(command);
}); });
this.sendPlayerCountUpdate();
socket.on('disconnect', () => { socket.on('disconnect', () => {
player.destroy(); player.destroy();
this.players = this.players.filter((p) => p !== player); this.players = this.players.filter((p) => p !== player);
this.sendPlayerCountUpdate();
}); });
}); });
socket.on('join', (room_name: string) => { socket.on(TransportEvents.SubscribeForPlayerCount, () => {
socket.join(room_name); socket.join(playerCountSubscribedRoom);
}); });
}); });
} }
public sendPlayerCountUpdate() {
this.io
.to(playerCountSubscribedRoom)
.emit(TransportEvents.PlayerCountUpdate, this.players.length);
}
public start() { public start() {
this.handlePhysics(); this.handlePhysics();
} }

View file

@ -6,6 +6,10 @@ export abstract class Configuration {
private static initialized = false; private static initialized = false;
public static async initialize(): Promise<void> { public static async initialize(): Promise<void> {
if (this.initialized) {
return;
}
const firebaseConfig = { const firebaseConfig = {
apiKey: 'AIzaSyBG85dp-AhaCW-qi_6mu77wDPSipzipIF4', apiKey: 'AIzaSyBG85dp-AhaCW-qi_6mu77wDPSipzipIF4',
authDomain: 'decla-red.firebaseapp.com', authDomain: 'decla-red.firebaseapp.com',

View file

@ -1,5 +1,5 @@
import { ServerInformation, serverInformationEndpoint } from 'shared'; import { ServerInformation, serverInformationEndpoint, TransportEvents } from 'shared';
import io from 'socket.io-client';
import { Configuration } from './config/configuration'; import { Configuration } from './config/configuration';
export type PlayerDecision = { export type PlayerDecision = {
@ -7,9 +7,11 @@ export type PlayerDecision = {
server: string; server: string;
}; };
const pollInterval = 10000;
export class JoinFormHandler { export class JoinFormHandler {
private waitingForDecision: Promise<PlayerDecision>; private waitingForDecision: Promise<PlayerDecision>;
private resolvePlayerDecision!: (d: PlayerDecision) => void; private resolvePlayerDecision!: (d: PlayerDecision) => void;
private pollServersTimer: any;
constructor(form: HTMLFormElement, private readonly container: HTMLElement) { constructor(form: HTMLFormElement, private readonly container: HTMLElement) {
this.waitingForDecision = new Promise((r) => (this.resolvePlayerDecision = r)); this.waitingForDecision = new Promise((r) => (this.resolvePlayerDecision = r));
@ -35,18 +37,51 @@ export class JoinFormHandler {
e.preventDefault(); e.preventDefault();
}; };
this.pollServersTimer = setInterval(this.loadServers.bind(this), pollInterval);
this.loadServers(); this.loadServers();
this.waitForFinish();
} }
private async waitForFinish() {
await this.waitingForDecision;
this.destroy();
}
private destroy() {
clearInterval(this.pollServersTimer);
this.servers.forEach((s) => s.destroy());
}
private servers: Array<ServerChooserOption> = [];
private async loadServers() { private async loadServers() {
await Configuration.initialize(); await Configuration.initialize();
const serverList = Configuration.servers;
const serverList = Configuration.servers.filter(
(u) => !this.servers.find((s) => s.url === u),
);
serverList.map(async (url) => { serverList.map(async (url) => {
const response = await fetch(url + serverInformationEndpoint); const controller = new AbortController();
if (response.ok) { const signal = controller.signal;
setTimeout(() => controller.abort(), pollInterval * 0.8);
let response: Response | undefined;
try {
response = await fetch(url + serverInformationEndpoint, { signal });
} catch {
// it's okay
}
if (response?.ok) {
const content: ServerInformation = await response.json(); const content: ServerInformation = await response.json();
this.displayNewServerInfo(content, url); const server = new ServerChooserOption(
content,
url,
(r) => (this.servers = this.servers.filter((s) => s !== r)),
this.servers.length === 0,
);
this.servers.push(server);
this.container.appendChild(server.element);
} }
}); });
} }
@ -54,19 +89,56 @@ export class JoinFormHandler {
public async getPlayerDecision(): Promise<PlayerDecision> { public async getPlayerDecision(): Promise<PlayerDecision> {
return this.waitingForDecision; return this.waitingForDecision;
} }
}
private isFirstServer = true; class ServerChooserOption {
private displayNewServerInfo(content: ServerInformation, url: string) { private divElement = document.createElement('div');
this.container.innerHTML += ` private inputElement = document.createElement('input');
<div> private labelElement = document.createElement('label');
<input required ${ private socket: SocketIOClient.Socket;
this.isFirstServer ? 'checked' : ''
} type="radio" id="${url}" name="server" value="${url}" /> constructor(
<label for="${url}">${content.serverName} - ${content.playerCount}/${ private content: ServerInformation,
content.playerLimit public readonly url: string,
} players</label> private onDestroy: (v: ServerChooserOption) => unknown,
</div> isFirst: boolean,
`; ) {
this.isFirstServer = false; this.inputElement.required = true;
this.inputElement.type = 'radio';
this.inputElement.id = this.inputElement.value = url;
this.inputElement.name = 'server';
this.inputElement.checked = isFirst;
this.labelElement.htmlFor = url;
this.divElement.appendChild(this.inputElement);
this.divElement.appendChild(this.labelElement);
this.setPlayerLabelText();
this.socket = io(url, {
reconnection: false,
timeout: 1500,
});
this.socket.on('connect_error', this.destroy.bind(this));
this.socket.on('connect_timeout', this.destroy.bind(this));
this.socket.on('disconnect', this.destroy.bind(this));
this.socket.emit(TransportEvents.SubscribeForPlayerCount);
this.socket.on(TransportEvents.PlayerCountUpdate, (v: number) => {
this.content.playerCount = v;
this.setPlayerLabelText();
});
}
public destroy() {
this.socket.close();
this.divElement.parentElement?.removeChild(this.divElement);
this.onDestroy(this);
}
private setPlayerLabelText() {
this.labelElement.innerText = `${this.content.serverName} - ${this.content.playerCount}/${this.content.playerLimit} players`;
}
public get element(): HTMLElement {
return this.divElement;
} }
} }

View file

@ -2,6 +2,8 @@ export enum TransportEvents {
PlayerJoining = 'PlayerJoining', PlayerJoining = 'PlayerJoining',
PlayerToServer = 'PlayerToServer', PlayerToServer = 'PlayerToServer',
ServerToPlayer = 'ServerToPlayer', ServerToPlayer = 'ServerToPlayer',
SubscribeForPlayerCount = 'SubscribeForPlayerCount',
PlayerCountUpdate = 'PlayerCountUpdate',
Ping = 'Ping', Ping = 'Ping',
Pong = 'Pong', Pong = 'Pong',
} }