import { DefaultInput, DefaultInputReader } from "../defaultinput"; import { NetplayPlayer, NetplayState } from "../types"; import * as log from "loglevel"; import { GameClass } from "../game"; import { assert } from "chai"; import { DEFAULT_SERVER_URL, MatchmakingClient } from "../matchmaking/client"; import { PeerConnection } from "../matchmaking/peerconnection"; import * as utils from "../utils"; import * as lit from "lit-html"; import { GameMenu } from "../ui/gamemenu"; export abstract class GameWrapper { gameClass: GameClass; /** The canvas that the game will be rendered onto. */ canvas: HTMLCanvasElement; /** The network stats UI. */ stats: HTMLDivElement; inputReader: DefaultInputReader; isChannelOrdered(channel: RTCDataChannel) { return channel.ordered; } isChannelReliable(channel: RTCDataChannel) { return ( channel.maxPacketLifeTime === null && channel.maxRetransmits === null ); } checkChannel(channel: RTCDataChannel) { assert.isTrue( this.isChannelOrdered(channel), "Data Channel must be ordered." ); assert.isTrue(this.isChannelReliable(channel), "Channel must be reliable."); } playerPausedIndicator: HTMLDivElement; constructor(gameClass: GameClass) { this.gameClass = gameClass; // Create canvas for game. this.canvas = document.createElement("canvas"); this.canvas.width = gameClass.canvasSize.width; this.canvas.height = gameClass.canvasSize.height; this.canvas.style.backgroundColor = "black"; this.canvas.style.position = "absolute"; this.canvas.style.zIndex = "0"; this.canvas.style.boxShadow = "0px 0px 10px black"; this.resize(); window.addEventListener("resize", () => this.resize()); document.body.appendChild(this.canvas); // Create stats UI this.stats = document.createElement("div"); this.stats.style.zIndex = "1"; this.stats.style.position = "absolute"; this.stats.style.backgroundColor = "rgba(0, 0, 0, 0.5)"; this.stats.style.color = "white"; this.stats.style.padding = "5px"; this.stats.style.display = "none"; document.body.appendChild(this.stats); // Create browser background info, this.playerPausedIndicator = (() => { const div = document.createElement("div"); div.style.zIndex = "1"; div.style.position = "absolute"; div.style.backgroundColor = "rgba(0, 0, 0, 0.5)"; div.style.color = "white"; div.style.padding = "10px"; div.style.left = "50%"; div.style.top = "50%"; div.style.transform = "translate(-50%, -50%)"; div.style.boxSizing = "border-box"; div.style.fontFamily = "sans-serif"; div.innerHTML = `
The other player has minimized or hidden their tab.
The game may run slowly until they return.
`; div.style.display = "none"; document.body.appendChild(div); return div; })(); if ( this.gameClass.touchControls && window.navigator.userAgent.toLowerCase().includes("mobile") ) { for (let [name, control] of Object.entries( this.gameClass.touchControls )) { control.show(); } } this.inputReader = new DefaultInputReader( document.body, this.canvas, this.gameClass.pointerLock || false, this.gameClass.touchControls || {} ); } /** * Calculate a scaling for our canvas so that it fits the whole screen. * Center the canvas with an offset. */ calculateLayout( container: { width: number; height: number }, canvas: { width: number; height: number } ): { width: number; height: number; left: number; top: number } { const widthRatio = container.width / canvas.width; const heightRatio = container.height / canvas.height; // We are constrained by the height of the canvas. const heightLimited = canvas.width * heightRatio >= container.width; const ratio = heightLimited ? widthRatio : heightRatio; let width = canvas.width * ratio; let height = canvas.height * ratio; let left = 0; let top = 0; if (heightLimited) { top = container.height / 2 - height / 2; } else { left = container.width / 2 - width / 2; } return { width, height, left, top }; } /** * Recalculate canvas scaling / offset. */ resize() { const layout = this.calculateLayout( { width: window.innerWidth, height: window.innerHeight }, this.gameClass.canvasSize ); log.debug("Calculating new layout: %o", layout); this.canvas.style.width = `${layout.width}px`; this.canvas.style.height = `${layout.height}px`; this.canvas.style.top = `${layout.top}px`; this.canvas.style.left = `${layout.left}px`; } async start() { const gameMenu = new GameMenu(); gameMenu.onClientStart.once((conn) => { const players = [ new NetplayPlayer(0, false, true), // Player 0 is our peer, the host. new NetplayPlayer(1, true, false), // Player 1 is us, a client ]; this.watchRTCStats(conn.peerConnection); this.startClient(players, conn); this.startVisibilityWatcher(conn); }); gameMenu.onHostStart.once((conn) => { // Construct the players array. const players: Array