Add landing page player count updates
This commit is contained in:
parent
c844e6c52b
commit
2509199abc
4 changed files with 111 additions and 22 deletions
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue