From 54f2518b5e0d4909092260932afd9f4fbc7a7376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Tue, 9 Jun 2020 23:12:54 +0200 Subject: [PATCH 01/15] Enabling stricter lint on front --- front/.eslintrc.json | 11 +++++++---- front/src/Phaser/Game/GameScene.ts | 3 ++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/front/.eslintrc.json b/front/.eslintrc.json index 0cee14a3..3aab37d9 100644 --- a/front/.eslintrc.json +++ b/front/.eslintrc.json @@ -7,7 +7,8 @@ }, "extends": [ "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended" + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended-requiring-type-checking" ], "globals": { "Atomics": "readonly", @@ -16,12 +17,14 @@ "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaVersion": 2018, - "sourceType": "module" + "sourceType": "module", + "project": "./tsconfig.json" }, "plugins": [ "@typescript-eslint" ], "rules": { - "no-unused-vars": "off" + "no-unused-vars": "off", + "@typescript-eslint/no-explicit-any": "error" } -} \ No newline at end of file +} diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 2b60fd62..1c979259 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -16,6 +16,7 @@ import {PlayerAnimationNames} from "../Player/Animation"; import {PlayerMovement} from "./PlayerMovement"; import {PlayersPositionInterpolator} from "./PlayersPositionInterpolator"; import {RemotePlayer} from "../Entity/RemotePlayer"; +import GameObject = Phaser.GameObjects.GameObject; export enum Textures { Player = "male1" @@ -347,7 +348,7 @@ export class GameScene extends Phaser.Scene { createCollisionWithPlayer() { //add collision layer this.Layers.forEach((Layer: Phaser.Tilemaps.StaticTilemapLayer) => { - this.physics.add.collider(this.CurrentPlayer, Layer, (object1: any, object2: any) => { + this.physics.add.collider(this.CurrentPlayer, Layer, (object1: GameObject, object2: GameObject) => { //this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name) }); Layer.setCollisionByProperty({collides: true}); From 8348d13bfe788e848ceace63acfc2e7ba9850906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Tue, 9 Jun 2020 23:13:26 +0200 Subject: [PATCH 02/15] Fixing use const instead of let --- front/src/Connection.ts | 2 +- front/src/Logger/MessageUI.ts | 6 +- front/src/Phaser/Components/TextInput.ts | 4 +- front/src/Phaser/Entity/SpeechBubble.ts | 30 ++++---- front/src/Phaser/Game/GameManager.ts | 8 +- front/src/Phaser/Game/GameScene.ts | 74 +++++++++---------- front/src/Phaser/Game/PlayerMovement.ts | 4 +- .../Game/PlayersPositionInterpolator.ts | 2 +- front/src/Phaser/Login/LoginScene.ts | 2 +- .../src/Phaser/Login/SelectCharacterScene.ts | 38 +++++----- front/src/Phaser/Player/Player.ts | 6 +- .../Phaser/Reconnecting/ReconnectingScene.ts | 2 +- .../src/Phaser/UserInput/UserInputManager.ts | 2 +- front/src/WebRtc/MediaManager.ts | 28 +++---- front/src/WebRtc/SimplePeer.ts | 16 ++-- front/src/index.ts | 2 +- front/tests/Phaser/Game/PlayerMovementTest.ts | 6 +- 17 files changed, 116 insertions(+), 116 deletions(-) diff --git a/front/src/Connection.ts b/front/src/Connection.ts index baa9c8a4..45d39841 100644 --- a/front/src/Connection.ts +++ b/front/src/Connection.ts @@ -248,7 +248,7 @@ export class Connection implements ConnectionInterface { if(!this.socket){ return; } - let point = new Point(x, y, direction, moving); + const point = new Point(x, y, direction, moving); this.lastPositionShared = point; this.getSocket().emit(EventMessage.USER_POSITION, point); } diff --git a/front/src/Logger/MessageUI.ts b/front/src/Logger/MessageUI.ts index 6011fb73..2a581091 100644 --- a/front/src/Logger/MessageUI.ts +++ b/front/src/Logger/MessageUI.ts @@ -2,7 +2,7 @@ export class MessageUI { static warningMessage(text: string){ this.removeMessage(); - let body = document.getElementById("body"); + const body = document.getElementById("body"); body?.insertAdjacentHTML('afterbegin', `
${text} @@ -12,13 +12,13 @@ export class MessageUI { static removeMessage(id : string|null = null) { if(!id){ - let messages = document.getElementsByClassName("message-info"); + const messages = document.getElementsByClassName("message-info"); for (let i = 0; i < messages.length; i++){ messages.item(i)?.remove(); } return; } - let previousElement = document.getElementById(id); + const previousElement = document.getElementById(id); if (!previousElement) { return; } diff --git a/front/src/Phaser/Components/TextInput.ts b/front/src/Phaser/Components/TextInput.ts index 92ddcb56..e1de42e9 100644 --- a/front/src/Phaser/Components/TextInput.ts +++ b/front/src/Phaser/Components/TextInput.ts @@ -10,8 +10,8 @@ export class TextInput extends Phaser.GameObjects.BitmapText { this.underLine = this.scene.add.text(x, y+1, '_______', { fontFamily: 'Arial', fontSize: "32px", color: '#ffffff'}) - let keySpace = this.scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE); - let keyBackspace = this.scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.BACKSPACE); + const keySpace = this.scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE); + const keyBackspace = this.scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.BACKSPACE); this.scene.input.keyboard.on('keydown', (event: any) => { if (event.keyCode === 8 && this.text.length > 0) { this.deleteLetter(); diff --git a/front/src/Phaser/Entity/SpeechBubble.ts b/front/src/Phaser/Entity/SpeechBubble.ts index f2385290..30518890 100644 --- a/front/src/Phaser/Entity/SpeechBubble.ts +++ b/front/src/Phaser/Entity/SpeechBubble.ts @@ -13,10 +13,10 @@ export class SpeechBubble { */ constructor(scene: Scene, player: Character, text: string = "") { - let bubbleHeight = 50; - let bubblePadding = 10; - let bubbleWidth = bubblePadding * 2 + text.length * 10; - let arrowHeight = bubbleHeight / 4; + const bubbleHeight = 50; + const bubblePadding = 10; + const bubbleWidth = bubblePadding * 2 + text.length * 10; + const arrowHeight = bubbleHeight / 4; this.bubble = scene.add.graphics({ x: player.x + 16, y: player.y - 80 }); @@ -35,12 +35,12 @@ export class SpeechBubble { this.bubble.fillRoundedRect(0, 0, bubbleWidth, bubbleHeight, 16); // Calculate arrow coordinates - let point1X = Math.floor(bubbleWidth / 7); - let point1Y = bubbleHeight; - let point2X = Math.floor((bubbleWidth / 7) * 2); - let point2Y = bubbleHeight; - let point3X = Math.floor(bubbleWidth / 7); - let point3Y = Math.floor(bubbleHeight + arrowHeight); + const point1X = Math.floor(bubbleWidth / 7); + const point1Y = bubbleHeight; + const point2X = Math.floor((bubbleWidth / 7) * 2); + const point2Y = bubbleHeight; + const point3X = Math.floor(bubbleWidth / 7); + const point3Y = Math.floor(bubbleHeight + arrowHeight); // bubble arrow shadow this.bubble.lineStyle(4, 0x222222, 0.5); @@ -54,7 +54,7 @@ export class SpeechBubble { this.content = scene.add.text(0, 0, text, { fontFamily: 'Arial', fontSize: 20, color: '#000000', align: 'center', wordWrap: { width: bubbleWidth - (bubblePadding * 2) } }); - let bounds = this.content.getBounds(); + const bounds = this.content.getBounds(); this.content.setPosition(this.bubble.x + (bubbleWidth / 2) - (bounds.width / 2), this.bubble.y + (bubbleHeight / 2) - (bounds.height / 2)); } @@ -68,10 +68,10 @@ export class SpeechBubble { this.bubble.setPosition((x + 16), (y - 80)); } if (this.content) { - let bubbleHeight = 50; - let bubblePadding = 10; - let bubbleWidth = bubblePadding * 2 + this.content.text.length * 10; - let bounds = this.content.getBounds(); + const bubbleHeight = 50; + const bubblePadding = 10; + const bubbleWidth = bubblePadding * 2 + this.content.text.length * 10; + const bounds = this.content.getBounds(); //this.content.setPosition(x, y); this.content.setPosition(this.bubble.x + (bubbleWidth / 2) - (bounds.width / 2), this.bubble.y + (bubbleHeight / 2) - (bounds.height / 2)); } diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index 7ed0137f..e4f77d8a 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -83,7 +83,7 @@ export class GameManager { } onUserJoins(message: MessageUserJoined): void { - let userMessage: AddPlayerInterface = { + const userMessage: AddPlayerInterface = { userId: message.userId, character: message.character, name: message.name, @@ -154,11 +154,11 @@ export class GameManager { } loadMap(mapUrl: string, scene: Phaser.Scenes.ScenePlugin, instance: string): string { - let sceneKey = GameScene.getMapKeyByUrl(mapUrl); + const sceneKey = GameScene.getMapKeyByUrl(mapUrl); - let gameIndex = scene.getIndex(sceneKey); + const gameIndex = scene.getIndex(sceneKey); if(gameIndex === -1){ - let game : Phaser.Scene = GameScene.createFromUrl(mapUrl, instance); + const game : Phaser.Scene = GameScene.createFromUrl(mapUrl, instance); scene.add(sceneKey, game, false); } return sceneKey; diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 1c979259..ed5d8956 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -62,7 +62,7 @@ export class GameScene extends Phaser.Scene { private startLayerName: string|undefined; static createFromUrl(mapUrlFile: string, instance: string): GameScene { - let key = GameScene.getMapKeyByUrl(mapUrlFile); + const key = GameScene.getMapKeyByUrl(mapUrlFile); return new GameScene(key, mapUrlFile, instance); } @@ -92,7 +92,7 @@ export class GameScene extends Phaser.Scene { // If the map has already been loaded as part of another GameScene, the "on load" event will not be triggered. // In this case, we check in the cache to see if the map is here and trigger the event manually. if (this.cache.tilemap.exists(this.MapKey)) { - let data = this.cache.tilemap.get(this.MapKey); + const data = this.cache.tilemap.get(this.MapKey); this.onMapLoad(data); } @@ -112,7 +112,7 @@ export class GameScene extends Phaser.Scene { // Triggered when the map is loaded // Load tiles attached to the map recursively this.mapFile = data.data; - let url = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf('/')); + const url = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf('/')); this.mapFile.tilesets.forEach((tileset) => { if (typeof tileset.name === 'undefined' || typeof tileset.image === 'undefined') { console.warn("Don't know how to handle tileset ", tileset) @@ -146,7 +146,7 @@ export class GameScene extends Phaser.Scene { //add layer on map this.Layers = new Array(); let depth = -2; - for (let layer of this.mapFile.layers) { + for (const layer of this.mapFile.layers) { if (layer.type === 'tilelayer') { this.addLayer(this.Map.createStaticLayer(layer.name, this.Terrains, 0, 0).setDepth(depth)); } @@ -168,9 +168,9 @@ export class GameScene extends Phaser.Scene { } else { // Now, let's find the start layer if (this.startLayerName) { - for (let layer of this.mapFile.layers) { + for (const layer of this.mapFile.layers) { if (this.startLayerName === layer.name && layer.type === 'tilelayer' && this.isStartLayer(layer)) { - let startPosition = this.startUser(layer); + const startPosition = this.startUser(layer); this.startX = startPosition.x; this.startY = startPosition.y; } @@ -178,9 +178,9 @@ export class GameScene extends Phaser.Scene { } if (this.startX === undefined) { // If we have no start layer specified or if the hash passed does not exist, let's go with the default start position. - for (let layer of this.mapFile.layers) { + for (const layer of this.mapFile.layers) { if (layer.type === 'tilelayer' && layer.name === "start") { - let startPosition = this.startUser(layer); + const startPosition = this.startUser(layer); this.startX = startPosition.x; this.startY = startPosition.y; } @@ -212,12 +212,12 @@ export class GameScene extends Phaser.Scene { // Let's generate the circle for the group delimiter - let circleElement = Object.values(this.textures.list).find((object: Texture) => object.key === 'circleSprite'); + const circleElement = Object.values(this.textures.list).find((object: Texture) => object.key === 'circleSprite'); if(circleElement) { this.textures.remove('circleSprite'); } this.circleTexture = this.textures.createCanvas('circleSprite', 96, 96); - let context = this.circleTexture.context; + const context = this.circleTexture.context; context.beginPath(); context.arc(48, 48, 48, 0, 2 * Math.PI, false); // context.lineWidth = 5; @@ -226,7 +226,7 @@ export class GameScene extends Phaser.Scene { this.circleTexture.refresh(); // Let's alter browser history - let url = new URL(this.MapUrlFile); + const url = new URL(this.MapUrlFile); let path = '/_/'+this.instance+'/'+url.host+url.pathname; if (this.startLayerName) { path += '#'+this.startLayerName; @@ -247,11 +247,11 @@ export class GameScene extends Phaser.Scene { } private getProperty(layer: ITiledMapLayer, name: string): string|boolean|number|undefined { - let properties : any = layer.properties; + const properties : any = layer.properties; if (!properties) { return undefined; } - let obj = properties.find((property:any) => property.name === name); + const obj = properties.find((property:any) => property.name === name); if (obj === undefined) { return undefined; } @@ -266,7 +266,7 @@ export class GameScene extends Phaser.Scene { * @param tileHeight */ private loadNextGame(layer: ITiledMapLayer, mapWidth: number, tileWidth: number, tileHeight: number){ - let exitSceneUrl = this.getExitSceneUrl(layer); + const exitSceneUrl = this.getExitSceneUrl(layer); if (exitSceneUrl === undefined) { throw new Error('Layer is not an exit scene layer.'); } @@ -276,18 +276,18 @@ export class GameScene extends Phaser.Scene { } // TODO: eventually compute a relative URL - let absoluteExitSceneUrl = new URL(exitSceneUrl, this.MapUrlFile).href; - let exitSceneKey = gameManager.loadMap(absoluteExitSceneUrl, this.scene, instance); + const absoluteExitSceneUrl = new URL(exitSceneUrl, this.MapUrlFile).href; + const exitSceneKey = gameManager.loadMap(absoluteExitSceneUrl, this.scene, instance); - let tiles : number[] = layer.data as number[]; + const tiles : number[] = layer.data as number[]; for (let key=0; key < tiles.length; key++) { - let objectKey = tiles[key]; + const objectKey = tiles[key]; if(objectKey === 0){ continue; } //key + 1 because the start x = 0; - let y : number = parseInt(((key + 1) / mapWidth).toString()); - let x : number = key - (y * mapWidth); + const y : number = parseInt(((key + 1) / mapWidth).toString()); + const x : number = key - (y * mapWidth); let hash = new URL(exitSceneUrl, this.MapUrlFile).hash; if (hash) { @@ -317,8 +317,8 @@ export class GameScene extends Phaser.Scene { if(objectKey === 0){ return; } - let y = Math.floor(key / layer.width); - let x = key % layer.width; + const y = Math.floor(key / layer.width); + const x = key % layer.width; possibleStartPositions.push({x: x*32, y: y*32}); }); @@ -431,7 +431,7 @@ export class GameScene extends Phaser.Scene { // debug code to get a tile properties by clicking on it this.input.on("pointerdown", (pointer: Phaser.Input.Pointer)=>{ //pixel position toz tile position - let tile = this.Map.getTileAt(this.Map.worldToTileX(pointer.worldX), this.Map.worldToTileY(pointer.worldY)); + const tile = this.Map.getTileAt(this.Map.worldToTileX(pointer.worldX), this.Map.worldToTileY(pointer.worldY)); if(tile){ this.CurrentPlayer.say("Your touch " + tile.layer.name); } @@ -447,16 +447,16 @@ export class GameScene extends Phaser.Scene { this.CurrentPlayer.moveUser(delta); // Let's move all users - let updatedPlayersPositions = this.playersPositionInterpolator.getUpdatedPositions(time); + const updatedPlayersPositions = this.playersPositionInterpolator.getUpdatedPositions(time); updatedPlayersPositions.forEach((moveEvent: HasMovedEvent, userId: string) => { - let player : RemotePlayer | undefined = this.MapPlayersByKey.get(userId); + const player : RemotePlayer | undefined = this.MapPlayersByKey.get(userId); if (player === undefined) { throw new Error('Cannot find player with ID "' + userId +'"'); } player.updatePosition(moveEvent); }); - let nextSceneKey = this.checkToExit(); + const nextSceneKey = this.checkToExit(); if(nextSceneKey){ // We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map. this.scene.remove(this.scene.key); @@ -485,7 +485,7 @@ export class GameScene extends Phaser.Scene { return; } - let currentPlayerId = this.GameManager.getPlayerId(); + const currentPlayerId = this.GameManager.getPlayerId(); // clean map this.MapPlayersByKey.forEach((player: RemotePlayer) => { @@ -516,7 +516,7 @@ export class GameScene extends Phaser.Scene { return; } //initialise player - let player = new RemotePlayer( + const player = new RemotePlayer( addPlayerData.userId, this, addPlayerData.position.x, @@ -538,7 +538,7 @@ export class GameScene extends Phaser.Scene { public removePlayer(userId: string) { console.log('Removing player ', userId) - let player = this.MapPlayersByKey.get(userId); + const player = this.MapPlayersByKey.get(userId); if (player === undefined) { console.error('Cannot find user with id ', userId); } else { @@ -550,26 +550,26 @@ export class GameScene extends Phaser.Scene { } updatePlayerPosition(message: MessageUserMovedInterface): void { - let player : RemotePlayer | undefined = this.MapPlayersByKey.get(message.userId); + const player : RemotePlayer | undefined = this.MapPlayersByKey.get(message.userId); if (player === undefined) { throw new Error('Cannot find player with ID "' + message.userId +'"'); } // We do not update the player position directly (because it is sent only every 200ms). // Instead we use the PlayersPositionInterpolator that will do a smooth animation over the next 200ms. - let playerMovement = new PlayerMovement({ x: player.x, y: player.y }, this.currentTick, message.position, this.currentTick + POSITION_DELAY); + const playerMovement = new PlayerMovement({ x: player.x, y: player.y }, this.currentTick, message.position, this.currentTick + POSITION_DELAY); this.playersPositionInterpolator.updatePlayerPosition(player.userId, playerMovement); } shareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface) { - let groupId = groupPositionMessage.groupId; + const groupId = groupPositionMessage.groupId; - let group = this.groups.get(groupId); + const group = this.groups.get(groupId); if (group !== undefined) { group.setPosition(Math.round(groupPositionMessage.position.x), Math.round(groupPositionMessage.position.y)); } else { // TODO: circle radius should not be hard stored - let sprite = new Sprite( + const sprite = new Sprite( this, Math.round(groupPositionMessage.position.x), Math.round(groupPositionMessage.position.y), @@ -581,7 +581,7 @@ export class GameScene extends Phaser.Scene { } deleteGroup(groupId: string): void { - let group = this.groups.get(groupId); + const group = this.groups.get(groupId); if(!group){ return; } @@ -591,8 +591,8 @@ export class GameScene extends Phaser.Scene { public static getMapKeyByUrl(mapUrlStart: string) : string { // FIXME: the key should be computed from the full URL of the map. - let startPos = mapUrlStart.indexOf('://')+3; - let endPos = mapUrlStart.indexOf(".json"); + const startPos = mapUrlStart.indexOf('://')+3; + const endPos = mapUrlStart.indexOf(".json"); return mapUrlStart.substring(startPos, endPos); } } diff --git a/front/src/Phaser/Game/PlayerMovement.ts b/front/src/Phaser/Game/PlayerMovement.ts index 1ed2b745..1458335d 100644 --- a/front/src/Phaser/Game/PlayerMovement.ts +++ b/front/src/Phaser/Game/PlayerMovement.ts @@ -23,8 +23,8 @@ export class PlayerMovement { return this.endPosition; } - let x = (this.endPosition.x - this.startPosition.x) * ((tick - this.startTick) / (this.endTick - this.startTick)) + this.startPosition.x; - let y = (this.endPosition.y - this.startPosition.y) * ((tick - this.startTick) / (this.endTick - this.startTick)) + this.startPosition.y; + const x = (this.endPosition.x - this.startPosition.x) * ((tick - this.startTick) / (this.endTick - this.startTick)) + this.startPosition.x; + const y = (this.endPosition.y - this.startPosition.y) * ((tick - this.startTick) / (this.endTick - this.startTick)) + this.startPosition.y; return { x, diff --git a/front/src/Phaser/Game/PlayersPositionInterpolator.ts b/front/src/Phaser/Game/PlayersPositionInterpolator.ts index 19e0f7bc..080c8a17 100644 --- a/front/src/Phaser/Game/PlayersPositionInterpolator.ts +++ b/front/src/Phaser/Game/PlayersPositionInterpolator.ts @@ -17,7 +17,7 @@ export class PlayersPositionInterpolator { } getUpdatedPositions(tick: number) : Map { - let positions = new Map(); + const positions = new Map(); this.playerMovements.forEach((playerMovement: PlayerMovement, userId: string) => { if (playerMovement.isOutdated(tick)) { //console.log("outdated") diff --git a/front/src/Phaser/Login/LoginScene.ts b/front/src/Phaser/Login/LoginScene.ts index 1b7ef76f..e82ece8e 100644 --- a/front/src/Phaser/Login/LoginScene.ts +++ b/front/src/Phaser/Login/LoginScene.ts @@ -67,7 +67,7 @@ export class LoginScene extends Phaser.Scene { this.logo = new Image(this, this.game.renderer.width - 30, this.game.renderer.height - 20, LoginTextures.icon); this.add.existing(this.logo); - let infoText = "Commands: \n - Arrows or Z,Q,S,D to move\n - SHIFT to run"; + const infoText = "Commands: \n - Arrows or Z,Q,S,D to move\n - SHIFT to run"; this.infoTextField = new TextField(this, 10, this.game.renderer.height - 35, infoText); this.input.keyboard.on('keyup-ENTER', () => { diff --git a/front/src/Phaser/Login/SelectCharacterScene.ts b/front/src/Phaser/Login/SelectCharacterScene.ts index dab9d61f..0270a9f5 100644 --- a/front/src/Phaser/Login/SelectCharacterScene.ts +++ b/front/src/Phaser/Login/SelectCharacterScene.ts @@ -63,7 +63,7 @@ export class SelectCharacterScene extends Phaser.Scene { this.pressReturnField = new TextField(this, this.game.renderer.width / 2, 230, 'Press enter to start'); this.pressReturnField.setOrigin(0.5).setCenterAlign() - let rectangleXStart = this.game.renderer.width / 2 - (this.nbCharactersPerRow / 2) * 32 + 16; + const rectangleXStart = this.game.renderer.width / 2 - (this.nbCharactersPerRow / 2) * 32 + 16; this.selectedRectangle = this.add.rectangle(rectangleXStart, 90, 32, 32).setStrokeStyle(2, 0xFFFFFF); @@ -103,8 +103,8 @@ export class SelectCharacterScene extends Phaser.Scene { this.createCurrentPlayer(); if (window.localStorage) { - let playerNumberStr: string = window.localStorage.getItem('selectedPlayer') ?? '0'; - let playerNumber: number = Number(playerNumberStr); + const playerNumberStr: string = window.localStorage.getItem('selectedPlayer') ?? '0'; + const playerNumber: number = Number(playerNumberStr); this.selectedRectangleXPos = playerNumber % this.nbCharactersPerRow; this.selectedRectangleYPos = Math.floor(playerNumber / this.nbCharactersPerRow); this.updateSelectedPlayer(); @@ -118,10 +118,10 @@ export class SelectCharacterScene extends Phaser.Scene { private async login(name: string) { return gameManager.connect(name, this.selectedPlayer.texture.key).then(() => { // Do we have a start URL in the address bar? If so, let's redirect to this address - let instanceAndMapUrl = this.findMapUrl(); + const instanceAndMapUrl = this.findMapUrl(); if (instanceAndMapUrl !== null) { - let [mapUrl, instance] = instanceAndMapUrl; - let key = gameManager.loadMap(mapUrl, this.scene, instance); + const [mapUrl, instance] = instanceAndMapUrl; + const key = gameManager.loadMap(mapUrl, this.scene, instance); this.scene.start(key, { startLayerName: window.location.hash ? window.location.hash.substr(1) : undefined } as GameSceneInitInterface); @@ -132,7 +132,7 @@ export class SelectCharacterScene extends Phaser.Scene { if (!scene) { return; } - let key = gameManager.loadMap(window.location.protocol + "//" + scene.mapUrlStart, this.scene, scene.startInstance); + const key = gameManager.loadMap(window.location.protocol + "//" + scene.mapUrlStart, this.scene, scene.startInstance); this.scene.start(key); return scene; }).catch((err) => { @@ -150,28 +150,28 @@ export class SelectCharacterScene extends Phaser.Scene { * Returns the map URL and the instance from the current URL */ private findMapUrl(): [string, string]|null { - let path = window.location.pathname; + const path = window.location.pathname; if (!path.startsWith('/_/')) { return null; } - let instanceAndMap = path.substr(3); - let firstSlash = instanceAndMap.indexOf('/'); + const instanceAndMap = path.substr(3); + const firstSlash = instanceAndMap.indexOf('/'); if (firstSlash === -1) { return null; } - let instance = instanceAndMap.substr(0, firstSlash); + const instance = instanceAndMap.substr(0, firstSlash); return [window.location.protocol+'//'+instanceAndMap.substr(firstSlash+1), instance]; } createCurrentPlayer(): void { for (let i = 0; i { if (d. keyInstance.isDown) { eventsMap.set(d.event, true); diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 359eac67..80be3f47 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -55,7 +55,7 @@ export class MediaManager { } activeVisio(){ - let webRtc = this.getElementByIdOrFail('webRtc'); + const webRtc = this.getElementByIdOrFail('webRtc'); webRtc.classList.add('active'); } @@ -138,9 +138,9 @@ export class MediaManager { */ addActiveVideo(userId : string, userName: string = ""){ this.webrtcInAudio.play(); - let elementRemoteVideo = this.getElementByIdOrFail("activeCam"); + const elementRemoteVideo = this.getElementByIdOrFail("activeCam"); userName = userName.toUpperCase(); - let color = this.getColorByString(userName); + const color = this.getColorByString(userName); elementRemoteVideo.insertAdjacentHTML('beforeend', `
@@ -158,7 +158,7 @@ export class MediaManager { * @param userId */ disabledMicrophoneByUserId(userId: string){ - let element = document.getElementById(`microphone-${userId}`); + const element = document.getElementById(`microphone-${userId}`); if(!element){ return; } @@ -170,7 +170,7 @@ export class MediaManager { * @param userId */ enabledMicrophoneByUserId(userId: string){ - let element = document.getElementById(`microphone-${userId}`); + const element = document.getElementById(`microphone-${userId}`); if(!element){ return; } @@ -223,7 +223,7 @@ export class MediaManager { * @param userId */ removeActiveVideo(userId : string){ - let element = document.getElementById(`div-${userId}`); + const element = document.getElementById(`div-${userId}`); if(!element){ return; } @@ -231,7 +231,7 @@ export class MediaManager { } isConnecting(userId : string): void { - let connectingSpinnerDiv = this.getSpinner(userId); + const connectingSpinnerDiv = this.getSpinner(userId); if (connectingSpinnerDiv === null) { return; } @@ -239,7 +239,7 @@ export class MediaManager { } isConnected(userId : string): void { - let connectingSpinnerDiv = this.getSpinner(userId); + const connectingSpinnerDiv = this.getSpinner(userId); if (connectingSpinnerDiv === null) { return; } @@ -247,11 +247,11 @@ export class MediaManager { } isError(userId : string): void { - let element = document.getElementById(`div-${userId}`); + const element = document.getElementById(`div-${userId}`); if(!element){ return; } - let errorDiv = element.getElementsByClassName('rtc-error').item(0) as HTMLDivElement|null; + const errorDiv = element.getElementsByClassName('rtc-error').item(0) as HTMLDivElement|null; if (errorDiv === null) { return; } @@ -259,11 +259,11 @@ export class MediaManager { } private getSpinner(userId : string): HTMLDivElement|null { - let element = document.getElementById(`div-${userId}`); + const element = document.getElementById(`div-${userId}`); if(!element){ return null; } - let connnectingSpinnerDiv = element.getElementsByClassName('connecting-spinner').item(0) as HTMLDivElement|null; + const connnectingSpinnerDiv = element.getElementsByClassName('connecting-spinner').item(0) as HTMLDivElement|null; return connnectingSpinnerDiv; } @@ -280,14 +280,14 @@ export class MediaManager { } let color = '#'; for (let i = 0; i < 3; i++) { - let value = (hash >> (i * 8)) & 255; + const value = (hash >> (i * 8)) & 255; color += ('00' + value.toString(16)).substr(-2); } return color; } private getElementByIdOrFail(id: string): T { - let elem = document.getElementById(id); + const elem = document.getElementById(id); if (elem === null) { throw new Error("Cannot find HTML element with id '"+id+"'"); } diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index da1ae3db..3f471253 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -1,7 +1,7 @@ import {ConnectionInterface, WebRtcDisconnectMessageInterface, WebRtcStartMessageInterface} from "../Connection"; import {MediaManager} from "./MediaManager"; import * as SimplePeerNamespace from "simple-peer"; -let Peer: SimplePeerNamespace.SimplePeer = require('simple-peer'); +const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer'); export interface UserSimplePeer{ userId: string; @@ -95,7 +95,7 @@ export class SimplePeer { let name = user.name; if(!name){ - let userSearch = this.Users.find((userSearch: UserSimplePeer) => userSearch.userId === user.userId); + const userSearch = this.Users.find((userSearch: UserSimplePeer) => userSearch.userId === user.userId); if(userSearch) { name = userSearch.name; } @@ -103,7 +103,7 @@ export class SimplePeer { this.MediaManager.removeActiveVideo(user.userId); this.MediaManager.addActiveVideo(user.userId, name); - let peer : SimplePeerNamespace.Instance = new Peer({ + const peer : SimplePeerNamespace.Instance = new Peer({ initiator: user.initiator ? user.initiator : false, reconnectTimer: 10000, config: { @@ -170,7 +170,7 @@ export class SimplePeer { }); peer.on('data', (chunk: Buffer) => { - let data = JSON.parse(chunk.toString('utf8')); + const data = JSON.parse(chunk.toString('utf8')); if(data.type === "stream"){ this.stream(user.userId, data.stream); } @@ -187,7 +187,7 @@ export class SimplePeer { private closeConnection(userId : string) { try { this.MediaManager.removeActiveVideo(userId); - let peer = this.PeerConnectionArray.get(userId); + const peer = this.PeerConnectionArray.get(userId); if (peer === undefined) { console.warn("Tried to close connection for user "+userId+" but could not find user") return; @@ -222,7 +222,7 @@ export class SimplePeer { if(data.signal.type === "offer"){ this.createPeerConnection(data); } - let peer = this.PeerConnectionArray.get(data.userId); + const peer = this.PeerConnectionArray.get(data.userId); if (peer !== undefined) { peer.signal(data.signal); } else { @@ -253,8 +253,8 @@ export class SimplePeer { */ private addMedia (userId : any = null) { try { - let localStream: MediaStream|null = this.MediaManager.localStream; - let peer = this.PeerConnectionArray.get(userId); + const localStream: MediaStream|null = this.MediaManager.localStream; + const peer = this.PeerConnectionArray.get(userId); if(localStream === null) { //send fake signal if(peer === undefined){ diff --git a/front/src/index.ts b/front/src/index.ts index 843925ac..6221d3ad 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -24,7 +24,7 @@ const config: GameConfig = { cypressAsserter.gameStarted(); -let game = new Phaser.Game(config); +const game = new Phaser.Game(config); window.addEventListener('resize', function (event) { game.scale.resize(window.innerWidth / RESOLUTION, window.innerHeight / RESOLUTION); diff --git a/front/tests/Phaser/Game/PlayerMovementTest.ts b/front/tests/Phaser/Game/PlayerMovementTest.ts index e65dbec8..ce2e2767 100644 --- a/front/tests/Phaser/Game/PlayerMovementTest.ts +++ b/front/tests/Phaser/Game/PlayerMovementTest.ts @@ -3,7 +3,7 @@ import {PlayerMovement} from "../../../src/Phaser/Game/PlayerMovement"; describe("Interpolation / Extrapolation", () => { it("should interpolate", () => { - let playerMovement = new PlayerMovement({ + const playerMovement = new PlayerMovement({ x: 100, y: 200 }, 42000, { @@ -39,7 +39,7 @@ describe("Interpolation / Extrapolation", () => { }); it("should not extrapolate if we stop", () => { - let playerMovement = new PlayerMovement({ + const playerMovement = new PlayerMovement({ x: 100, y: 200 }, 42000, { @@ -57,7 +57,7 @@ describe("Interpolation / Extrapolation", () => { }); it("should should keep moving until it stops", () => { - let playerMovement = new PlayerMovement({ + const playerMovement = new PlayerMovement({ x: 100, y: 200 }, 42000, { From 39928b46f9982d5db3e8916d917bb82c6e77e774 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 10 Jun 2020 12:15:25 +0200 Subject: [PATCH 03/15] Removing any in the front --- front/src/Cypress/CypressAsserter.ts | 12 ++++-- front/src/Phaser/Game/GameManager.ts | 6 +-- front/src/Phaser/Game/GameScene.ts | 27 ++++++------- .../src/Phaser/UserInput/UserInputManager.ts | 13 +++--- front/src/WebRtc/MediaManager.ts | 40 +++++++++++-------- 5 files changed, 52 insertions(+), 46 deletions(-) diff --git a/front/src/Cypress/CypressAsserter.ts b/front/src/Cypress/CypressAsserter.ts index 95adb156..82eeab1f 100644 --- a/front/src/Cypress/CypressAsserter.ts +++ b/front/src/Cypress/CypressAsserter.ts @@ -1,13 +1,17 @@ -declare let window:any; +declare let window:WindowWithCypressAsserter; + +interface WindowWithCypressAsserter extends Window { + cypressAsserter: CypressAsserter; +} //this class is used to communicate with cypress, our e2e testing client //Since cypress cannot manipulate canvas, we notified it with console logs class CypressAsserter { - + constructor() { window.cypressAsserter = this } - + gameStarted() { console.log('Started the game') } @@ -29,4 +33,4 @@ class CypressAsserter { } } -export const cypressAsserter = new CypressAsserter() \ No newline at end of file +export const cypressAsserter = new CypressAsserter() diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index e4f77d8a..ec20411f 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -1,6 +1,6 @@ import {GameScene} from "./GameScene"; import { - Connection, + Connection, ConnectionInterface, GroupCreatedUpdatedMessageInterface, ListMessageUserPositionInterface, MessageUserJoined, @@ -44,11 +44,11 @@ export class GameManager { //this.status = StatusGameManagerEnum.IN_PROGRESS; } - connect(name: string, characterUserSelected : string) { + public connect(name: string, characterUserSelected : string): Promise { this.playerName = name; this.characterUserSelected = characterUserSelected; this.ConnectionInstance = new Connection(this); - return this.ConnectionInstance.createConnection(name, characterUserSelected).then((data : any) => { + return this.ConnectionInstance.createConnection(name, characterUserSelected).then((data : ConnectionInterface) => { this.SimplePeer = new SimplePeer(this.ConnectionInstance); return data; }).catch((err) => { diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index ed5d8956..42115915 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -58,7 +58,7 @@ export class GameScene extends Phaser.Scene { y: -1000 } - PositionNextScene: Array = new Array(); + private PositionNextScene: Array> = new Array>(); private startLayerName: string|undefined; static createFromUrl(mapUrlFile: string, instance: string): GameScene { @@ -295,15 +295,13 @@ export class GameScene extends Phaser.Scene { } //push and save switching case - // TODO: this is not efficient. We should refactor that to enable a search by key. For instance: this.PositionNextScene[y][x] = exitSceneKey - this.PositionNextScene.push({ - xStart: (x * tileWidth), - yStart: (y * tileWidth), - xEnd: ((x +1) * tileHeight), - yEnd: ((y + 1) * tileHeight), + if (this.PositionNextScene[y] === undefined) { + this.PositionNextScene[y] = new Array<{key: string, hash: string}>(); + } + this.PositionNextScene[y][x] = { key: exitSceneKey, hash - }) + } } } @@ -469,14 +467,15 @@ export class GameScene extends Phaser.Scene { /** * */ - checkToExit(){ - if(this.PositionNextScene.length === 0){ + checkToExit(): {key: string, hash: string} | null { + const x = Math.floor(this.CurrentPlayer.x / 32); + const y = Math.floor(this.CurrentPlayer.y / 32); + + if (this.PositionNextScene[y] !== undefined && this.PositionNextScene[y][x] !== undefined) { + return this.PositionNextScene[y][x]; + } else { return null; } - return this.PositionNextScene.find((position : any) => { - return position.xStart <= this.CurrentPlayer.x && this.CurrentPlayer.x <= position.xEnd - && position.yStart <= this.CurrentPlayer.y && this.CurrentPlayer.y <= position.yEnd - }) } public initUsersPosition(usersPosition: MessageUserPositionInterface[]): void { diff --git a/front/src/Phaser/UserInput/UserInputManager.ts b/front/src/Phaser/UserInput/UserInputManager.ts index 1b68feff..eddbbf74 100644 --- a/front/src/Phaser/UserInput/UserInputManager.ts +++ b/front/src/Phaser/UserInput/UserInputManager.ts @@ -1,4 +1,3 @@ -import Map = Phaser.Structs.Map; import {GameScene} from "../Game/GameScene"; interface UserInputManagerDatum { @@ -18,15 +17,13 @@ export enum UserInputEvent { //we cannot the map structure so we have to create a replacment export class ActiveEventList { - private KeysCode : any; - constructor() { - this.KeysCode = {}; - } + private KeysCode : Map = new Map(); + get(event: UserInputEvent): boolean { - return this.KeysCode[event] || false; + return this.KeysCode.get(event) || false; } - set(event: UserInputEvent, value: boolean): boolean { - return this.KeysCode[event] = true; + set(event: UserInputEvent, value: boolean): void { + this.KeysCode.set(event, value); } } diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 80be3f47..b70ea9de 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -1,18 +1,18 @@ -const videoConstraint: {width : any, height: any, facingMode : string} = { +const videoConstraint: boolean|MediaTrackConstraints = { width: { ideal: 1280 }, height: { ideal: 720 }, facingMode: "user" }; export class MediaManager { localStream: MediaStream|null = null; - remoteVideo: Array = new Array(); + private remoteVideo: Map = new Map(); myCamVideo: HTMLVideoElement; - cinemaClose: any = null; - cinema: any = null; - microphoneClose: any = null; - microphone: any = null; + cinemaClose: HTMLImageElement; + cinema: HTMLImageElement; + microphoneClose: HTMLImageElement; + microphone: HTMLImageElement; webrtcInAudio: HTMLAudioElement; - constraintsMedia : {audio : any, video : any} = { + constraintsMedia : MediaStreamConstraints = { audio: true, video: videoConstraint }; @@ -25,29 +25,29 @@ export class MediaManager { this.webrtcInAudio = this.getElementByIdOrFail('audio-webrtc-in'); this.webrtcInAudio.volume = 0.2; - this.microphoneClose = document.getElementById('microphone-close'); + this.microphoneClose = this.getElementByIdOrFail('microphone-close'); this.microphoneClose.style.display = "none"; - this.microphoneClose.addEventListener('click', (e: any) => { + this.microphoneClose.addEventListener('click', (e: MouseEvent) => { e.preventDefault(); this.enabledMicrophone(); //update tracking }); - this.microphone = document.getElementById('microphone'); - this.microphone.addEventListener('click', (e: any) => { + this.microphone = this.getElementByIdOrFail('microphone'); + this.microphone.addEventListener('click', (e: MouseEvent) => { e.preventDefault(); this.disabledMicrophone(); //update tracking }); - this.cinemaClose = document.getElementById('cinema-close'); + this.cinemaClose = this.getElementByIdOrFail('cinema-close'); this.cinemaClose.style.display = "none"; - this.cinemaClose.addEventListener('click', (e: any) => { + this.cinemaClose.addEventListener('click', (e: MouseEvent) => { e.preventDefault(); this.enabledCamera(); //update tracking }); - this.cinema = document.getElementById('cinema'); - this.cinema.addEventListener('click', (e: any) => { + this.cinema = this.getElementByIdOrFail('cinema'); + this.cinema.addEventListener('click', (e: MouseEvent) => { e.preventDefault(); this.disabledCamera(); //update tracking @@ -150,7 +150,7 @@ export class MediaManager {
`); - this.remoteVideo[(userId as any)] = document.getElementById(userId); + this.remoteVideo.set(userId, this.getElementByIdOrFail(userId)); } /** @@ -215,7 +215,12 @@ export class MediaManager { * @param stream */ addStreamRemoteVideo(userId : string, stream : MediaStream){ - this.remoteVideo[(userId as any)].srcObject = stream; + const remoteVideo = this.remoteVideo.get(userId); + if (remoteVideo === undefined) { + console.error('Unable to find video for ', userId); + return; + } + remoteVideo.srcObject = stream; } /** @@ -228,6 +233,7 @@ export class MediaManager { return; } element.remove(); + this.remoteVideo.delete(userId); } isConnecting(userId : string): void { From e2be99490bdb4ea59ac77f33c5771ed5d0690a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 19 Jun 2020 16:36:40 +0200 Subject: [PATCH 04/15] Finishing removing any reference to "any" in the front. --- front/src/Connection.ts | 27 +++++++++++---- front/src/Enum/EnvironmentVariable.ts | 2 +- front/src/Phaser/Components/TextInput.ts | 2 +- front/src/Phaser/Entity/Character.ts | 7 +++- front/src/Phaser/Game/GameScene.ts | 34 ++++++++++++------- front/src/Phaser/Login/LoginScene.ts | 4 +-- .../src/Phaser/Login/SelectCharacterScene.ts | 21 ++++++------ front/src/Phaser/Map/ITiledMap.ts | 14 +++++++- .../Phaser/Reconnecting/ReconnectingScene.ts | 6 ---- front/src/WebRtc/SimplePeer.ts | 19 +++++++---- 10 files changed, 89 insertions(+), 47 deletions(-) diff --git a/front/src/Connection.ts b/front/src/Connection.ts index 45d39841..b6b251f0 100644 --- a/front/src/Connection.ts +++ b/front/src/Connection.ts @@ -8,6 +8,7 @@ const SocketIo = require('socket.io-client'); import Socket = SocketIOClient.Socket; import {PlayerAnimationNames} from "./Phaser/Player/Animation"; import {UserSimplePeer} from "./WebRtc/SimplePeer"; +import {SignalData} from "simple-peer"; enum EventMessage{ @@ -110,22 +111,29 @@ export interface WebRtcDisconnectMessageInterface { userId: string } +export interface WebRtcSignalMessageInterface { + userId: string, + receiverId: string, + roomId: string, + signal: SignalData +} + export interface ConnectionInterface { socket: Socket|null; token: string|null; name: string|null; userId: string|null; - createConnection(name: string, characterSelected: string): Promise; + createConnection(name: string, characterSelected: string): Promise; - loadStartMap(): Promise; + loadStartMap(): Promise; joinARoom(roomId: string, startX: number, startY: number, direction: string, moving: boolean): void; sharePosition(x: number, y: number, direction: string, moving: boolean): void; /*webrtc*/ - sendWebrtcSignal(signal: any, roomId: string, userId?: string|null, receiverId?: string): void; + sendWebrtcSignal(signal: unknown, roomId: string, userId?: string|null, receiverId?: string): void; receiveWebrtcSignal(callBack: Function): void; @@ -134,6 +142,11 @@ export interface ConnectionInterface { disconnectMessage(callBack: (message: WebRtcDisconnectMessageInterface) => void): void; } +export interface StartMapInterface { + mapUrlStart: string, + startInstance: string +} + export class Connection implements ConnectionInterface { socket: Socket|null = null; token: string|null = null; @@ -225,7 +238,7 @@ export class Connection implements ConnectionInterface { } //TODO add middleware with access token to secure api - loadStartMap() : Promise { + loadStartMap() : Promise { return Axios.get(`${API_URL}/start-map`) .then((res) => { return res.data; @@ -236,7 +249,7 @@ export class Connection implements ConnectionInterface { } joinARoom(roomId: string, startX: number, startY: number, direction: string, moving: boolean): void { - let point = new Point(startX, startY, direction, moving); + const point = new Point(startX, startY, direction, moving); this.lastPositionShared = point; this.getSocket().emit(EventMessage.JOIN_ROOM, { roomId, position: {x: startX, y: startY, direction, moving }}, (userPositions: MessageUserPositionInterface[]) => { this.GameManager.initUsersPosition(userPositions); @@ -284,7 +297,7 @@ export class Connection implements ConnectionInterface { }) } - sendWebrtcSignal(signal: any, roomId: string, userId? : string|null, receiverId? : string) { + sendWebrtcSignal(signal: unknown, roomId: string, userId? : string|null, receiverId? : string) { return this.getSocket().emit(EventMessage.WEBRTC_SIGNAL, { userId: userId ? userId : this.userId, receiverId: receiverId ? receiverId : this.userId, @@ -297,7 +310,7 @@ export class Connection implements ConnectionInterface { this.getSocket().on(EventMessage.WEBRTC_START, callback); } - receiveWebrtcSignal(callback: Function) { + receiveWebrtcSignal(callback: (message: WebRtcSignalMessageInterface) => void) { return this.getSocket().on(EventMessage.WEBRTC_SIGNAL, callback); } diff --git a/front/src/Enum/EnvironmentVariable.ts b/front/src/Enum/EnvironmentVariable.ts index 2fbf7979..6e0edd8f 100644 --- a/front/src/Enum/EnvironmentVariable.ts +++ b/front/src/Enum/EnvironmentVariable.ts @@ -1,4 +1,4 @@ -const DEBUG_MODE: boolean = process.env.DEBUG_MODE as any === true; +const DEBUG_MODE: boolean = process.env.DEBUG_MODE == "true"; const API_URL = process.env.API_URL || "http://api.workadventure.localhost"; const RESOLUTION = 3; const ZOOM_LEVEL = 1/*3/4*/; diff --git a/front/src/Phaser/Components/TextInput.ts b/front/src/Phaser/Components/TextInput.ts index e1de42e9..3a20eadf 100644 --- a/front/src/Phaser/Components/TextInput.ts +++ b/front/src/Phaser/Components/TextInput.ts @@ -12,7 +12,7 @@ export class TextInput extends Phaser.GameObjects.BitmapText { const keySpace = this.scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE); const keyBackspace = this.scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.BACKSPACE); - this.scene.input.keyboard.on('keydown', (event: any) => { + this.scene.input.keyboard.on('keydown', (event: KeyboardEvent) => { if (event.keyCode === 8 && this.text.length > 0) { this.deleteLetter(); } else if ((event.keyCode === 32 || (event.keyCode >= 48 && event.keyCode <= 90)) && this.text.length < maxLength) { diff --git a/front/src/Phaser/Entity/Character.ts b/front/src/Phaser/Entity/Character.ts index ba6a8228..7453dc75 100644 --- a/front/src/Phaser/Entity/Character.ts +++ b/front/src/Phaser/Entity/Character.ts @@ -2,7 +2,12 @@ import {PlayerAnimationNames} from "../Player/Animation"; import {SpeechBubble} from "./SpeechBubble"; import BitmapText = Phaser.GameObjects.BitmapText; -export const PLAYER_RESOURCES: Array = [ +export interface PlayerResourceDescriptionInterface { + name: string, + img: string +} + +export const PLAYER_RESOURCES: Array = [ {name: "male1", img: "resources/characters/pipoya/Male 01-1.png" /*, x: 32, y: 32*/}, {name: "male2", img: "resources/characters/pipoya/Male 02-2.png"/*, x: 64, y: 32*/}, {name: "male3", img: "resources/characters/pipoya/Male 03-4.png"/*, x: 96, y: 32*/}, diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 42115915..21e0d0b8 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -6,8 +6,13 @@ import { } from "../../Connection"; import {CurrentGamerInterface, hasMovedEventName, Player} from "../Player/Player"; import { DEBUG_MODE, ZOOM_LEVEL, POSITION_DELAY } from "../../Enum/EnvironmentVariable"; -import {ITiledMap, ITiledMapLayer, ITiledTileSet} from "../Map/ITiledMap"; -import {PLAYER_RESOURCES} from "../Entity/Character"; +import { + ITiledMap, + ITiledMapLayer, + ITiledMapLayerProperty, + ITiledTileSet +} from "../Map/ITiledMap"; +import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "../Entity/Character"; import Texture = Phaser.Textures.Texture; import Sprite = Phaser.GameObjects.Sprite; import CanvasTexture = Phaser.Textures.CanvasTexture; @@ -84,7 +89,7 @@ export class GameScene extends Phaser.Scene { //hook preload scene preload(): void { this.GameManager.setCurrentGameScene(this); - this.load.on('filecomplete-tilemapJSON-'+this.MapKey, (key: string, type: string, data: any) => { + this.load.on('filecomplete-tilemapJSON-'+this.MapKey, (key: string, type: string, data: unknown) => { this.onMapLoad(data); }); //TODO strategy to add access token @@ -97,7 +102,7 @@ export class GameScene extends Phaser.Scene { } //add player png - PLAYER_RESOURCES.forEach((playerResource: any) => { + PLAYER_RESOURCES.forEach((playerResource: PlayerResourceDescriptionInterface) => { this.load.spritesheet( playerResource.name, playerResource.img, @@ -108,6 +113,8 @@ export class GameScene extends Phaser.Scene { this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); } + // FIXME: we need to put a "unknown" instead of a "any" and validate the structure of the JSON we are receiving. + // eslint-disable-next-line @typescript-eslint/no-explicit-any private onMapLoad(data: any): void { // Triggered when the map is loaded // Load tiles attached to the map recursively @@ -247,11 +254,11 @@ export class GameScene extends Phaser.Scene { } private getProperty(layer: ITiledMapLayer, name: string): string|boolean|number|undefined { - const properties : any = layer.properties; + const properties = layer.properties; if (!properties) { return undefined; } - const obj = properties.find((property:any) => property.name === name); + const obj = properties.find((property: ITiledMapLayerProperty) => property.name === name); if (obj === undefined) { return undefined; } @@ -309,8 +316,11 @@ export class GameScene extends Phaser.Scene { * @param layer */ private startUser(layer: ITiledMapLayer): PositionInterface { - let tiles : any = layer.data; - let possibleStartPositions : PositionInterface[] = []; + const tiles = layer.data; + if (typeof(tiles) === 'string') { + throw new Error('The content of a JSON map must be filled as a JSON array, not as a string'); + } + const possibleStartPositions : PositionInterface[] = []; tiles.forEach((objectKey : number, key: number) => { if(objectKey === 0){ return; @@ -362,11 +372,11 @@ export class GameScene extends Phaser.Scene { } createCollisionObject(){ - this.Objects.forEach((Object : Phaser.Physics.Arcade.Sprite) => { - this.physics.add.collider(this.CurrentPlayer, Object, (object1: any, object2: any) => { - //this.CurrentPlayer.say("Collision with object : " + (object2 as Phaser.Physics.Arcade.Sprite).texture.key) + /*this.Objects.forEach((Object : Phaser.Physics.Arcade.Sprite) => { + this.physics.add.collider(this.CurrentPlayer, Object, (object1, object2) => { + this.CurrentPlayer.say("Collision with object : " + (object2 as Phaser.Physics.Arcade.Sprite).texture.key) }); - }) + })*/ } createCurrentPlayer(){ diff --git a/front/src/Phaser/Login/LoginScene.ts b/front/src/Phaser/Login/LoginScene.ts index e82ece8e..5177659b 100644 --- a/front/src/Phaser/Login/LoginScene.ts +++ b/front/src/Phaser/Login/LoginScene.ts @@ -4,7 +4,7 @@ import {TextInput} from "../Components/TextInput"; import {ClickButton} from "../Components/ClickButton"; import Image = Phaser.GameObjects.Image; import Rectangle = Phaser.GameObjects.Rectangle; -import {PLAYER_RESOURCES} from "../Entity/Character"; +import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "../Entity/Character"; import {cypressAsserter} from "../../Cypress/CypressAsserter"; import {SelectCharacterSceneInitDataInterface, SelectCharacterSceneName} from "./SelectCharacterScene"; @@ -40,7 +40,7 @@ export class LoginScene extends Phaser.Scene { this.load.bitmapFont(LoginTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); cypressAsserter.preloadFinished(); //add player png - PLAYER_RESOURCES.forEach((playerResource: any) => { + PLAYER_RESOURCES.forEach((playerResource: PlayerResourceDescriptionInterface) => { this.load.spritesheet( playerResource.name, playerResource.img, diff --git a/front/src/Phaser/Login/SelectCharacterScene.ts b/front/src/Phaser/Login/SelectCharacterScene.ts index 0270a9f5..316ee897 100644 --- a/front/src/Phaser/Login/SelectCharacterScene.ts +++ b/front/src/Phaser/Login/SelectCharacterScene.ts @@ -3,8 +3,9 @@ import {TextField} from "../Components/TextField"; import {ClickButton} from "../Components/ClickButton"; import Image = Phaser.GameObjects.Image; import Rectangle = Phaser.GameObjects.Rectangle; -import {PLAYER_RESOURCES} from "../Entity/Character"; +import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "../Entity/Character"; import {GameSceneInitInterface} from "../Game/GameScene"; +import {StartMapInterface} from "../../Connection"; //todo: put this constants in a dedicated file export const SelectCharacterSceneName = "SelectCharacterScene"; @@ -47,7 +48,7 @@ export class SelectCharacterScene extends Phaser.Scene { // Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap this.load.bitmapFont(LoginTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); //add player png - PLAYER_RESOURCES.forEach((playerResource: any) => { + PLAYER_RESOURCES.forEach((playerResource: PlayerResourceDescriptionInterface) => { this.load.spritesheet( playerResource.name, playerResource.img, @@ -115,7 +116,7 @@ export class SelectCharacterScene extends Phaser.Scene { this.pressReturnField.setVisible(!!(Math.floor(time / 500) % 2)); } - private async login(name: string) { + private async login(name: string): Promise { return gameManager.connect(name, this.selectedPlayer.texture.key).then(() => { // Do we have a start URL in the address bar? If so, let's redirect to this address const instanceAndMapUrl = this.findMapUrl(); @@ -125,16 +126,16 @@ export class SelectCharacterScene extends Phaser.Scene { this.scene.start(key, { startLayerName: window.location.hash ? window.location.hash.substr(1) : undefined } as GameSceneInitInterface); - return mapUrl; + return { + mapUrlStart: mapUrl, + startInstance: instance + }; } else { // If we do not have a map address in the URL, let's ask the server for a start map. - return gameManager.loadStartMap().then((scene : any) => { - if (!scene) { - return; - } - const key = gameManager.loadMap(window.location.protocol + "//" + scene.mapUrlStart, this.scene, scene.startInstance); + return gameManager.loadStartMap().then((startMap: StartMapInterface) => { + const key = gameManager.loadMap(window.location.protocol + "//" + startMap.mapUrlStart, this.scene, startMap.startInstance); this.scene.start(key); - return scene; + return startMap; }).catch((err) => { console.error(err); throw err; diff --git a/front/src/Phaser/Map/ITiledMap.ts b/front/src/Phaser/Map/ITiledMap.ts index ca10f218..2a82e93a 100644 --- a/front/src/Phaser/Map/ITiledMap.ts +++ b/front/src/Phaser/Map/ITiledMap.ts @@ -26,12 +26,24 @@ export interface ITiledMap { version: number; } +export interface ITiledMapLayerProperty { + name: string; + type: string; + value: string|boolean|number|undefined; +} + +/*export interface ITiledMapLayerBooleanProperty { + name: string, + type: 'bool', + value: boolean +}*/ + export interface ITiledMapLayer { data: number[]|string; height: number; name: string; opacity: number; - properties: {[key: string]: string}; + properties: ITiledMapLayerProperty[]; encoding: string; compression?: string; diff --git a/front/src/Phaser/Reconnecting/ReconnectingScene.ts b/front/src/Phaser/Reconnecting/ReconnectingScene.ts index 2ee91358..7a377b66 100644 --- a/front/src/Phaser/Reconnecting/ReconnectingScene.ts +++ b/front/src/Phaser/Reconnecting/ReconnectingScene.ts @@ -1,11 +1,5 @@ -import {gameManager} from "../Game/GameManager"; import {TextField} from "../Components/TextField"; -import {TextInput} from "../Components/TextInput"; -import {ClickButton} from "../Components/ClickButton"; import Image = Phaser.GameObjects.Image; -import Rectangle = Phaser.GameObjects.Rectangle; -import {PLAYER_RESOURCES} from "../Entity/Character"; -import {cypressAsserter} from "../../Cypress/CypressAsserter"; import Sprite = Phaser.GameObjects.Sprite; export const ReconnectingSceneName = "ReconnectingScene"; diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 3f471253..f5c8e7ef 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -1,4 +1,9 @@ -import {ConnectionInterface, WebRtcDisconnectMessageInterface, WebRtcStartMessageInterface} from "../Connection"; +import { + ConnectionInterface, + WebRtcDisconnectMessageInterface, + WebRtcSignalMessageInterface, + WebRtcStartMessageInterface +} from "../Connection"; import {MediaManager} from "./MediaManager"; import * as SimplePeerNamespace from "simple-peer"; const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer'); @@ -36,7 +41,7 @@ export class SimplePeer { private initialise() { //receive signal by gemer - this.Connection.receiveWebrtcSignal((message: any) => { + this.Connection.receiveWebrtcSignal((message: WebRtcSignalMessageInterface) => { this.receiveWebrtcSignal(message); }); @@ -122,7 +127,7 @@ export class SimplePeer { this.PeerConnectionArray.set(user.userId, peer); //start listen signal for the peer connection - peer.on('signal', (data: any) => { + peer.on('signal', (data: unknown) => { this.sendWebrtcSignal(data, user.userId); }); @@ -159,6 +164,7 @@ export class SimplePeer { this.closeConnection(user.userId); }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any peer.on('error', (err: any) => { console.error(`error => ${user.userId} => ${err.code}`, err); this.MediaManager.isError(user.userId); @@ -208,7 +214,7 @@ export class SimplePeer { * @param userId * @param data */ - private sendWebrtcSignal(data: any, userId : string) { + private sendWebrtcSignal(data: unknown, userId : string) { try { this.Connection.sendWebrtcSignal(data, this.WebRtcRoomId, null, userId); }catch (e) { @@ -216,7 +222,8 @@ export class SimplePeer { } } - private receiveWebrtcSignal(data: any) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + private receiveWebrtcSignal(data: WebRtcSignalMessageInterface) { try { //if offer type, create peer connection if(data.signal.type === "offer"){ @@ -251,7 +258,7 @@ export class SimplePeer { * * @param userId */ - private addMedia (userId : any = null) { + private addMedia (userId : string) { try { const localStream: MediaStream|null = this.MediaManager.localStream; const peer = this.PeerConnectionArray.get(userId); From 2b820c7d56e82d296ba5df87e44b6a6252cfed01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 19 Jun 2020 18:18:43 +0200 Subject: [PATCH 05/15] Adding an event queue in the GameScene. Events are processed only in the 'update' method. We hope this will solve a number of null pointer errors. See #194 --- front/package.json | 1 + front/src/Phaser/Game/GameScene.ts | 128 ++++++++++++++++++++++++++--- front/yarn.lock | 12 +++ 3 files changed, 131 insertions(+), 10 deletions(-) diff --git a/front/package.json b/front/package.json index dba6ea03..d73eb50d 100644 --- a/front/package.json +++ b/front/package.json @@ -22,6 +22,7 @@ "@types/simple-peer": "^9.6.0", "@types/socket.io-client": "^1.4.32", "phaser": "^3.22.0", + "queue-typescript": "^1.0.1", "simple-peer": "^9.6.2", "socket.io-client": "^2.3.0" }, diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 21e0d0b8..889cab16 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -22,6 +22,8 @@ import {PlayerMovement} from "./PlayerMovement"; import {PlayersPositionInterpolator} from "./PlayersPositionInterpolator"; import {RemotePlayer} from "../Entity/RemotePlayer"; import GameObject = Phaser.GameObjects.GameObject; +import { Queue } from 'queue-typescript'; + export enum Textures { Player = "male1" @@ -32,6 +34,36 @@ export interface GameSceneInitInterface { startLayerName: string|undefined } +interface InitUserPositionEventInterface { + type: 'InitUserPositionEvent' + event: MessageUserPositionInterface[] +} + +interface AddPlayerEventInterface { + type: 'AddPlayerEvent' + event: AddPlayerInterface +} + +interface RemovePlayerEventInterface { + type: 'RemovePlayerEvent' + userId: string +} + +interface UserMovedEventInterface { + type: 'UserMovedEvent' + event: MessageUserMovedInterface +} + +interface GroupCreatedUpdatedEventInterface { + type: 'GroupCreatedUpdatedEvent' + event: GroupCreatedUpdatedMessageInterface +} + +interface DeleteGroupEventInterface { + type: 'DeleteGroupEvent' + groupId: string +} + export class GameScene extends Phaser.Scene { GameManager : GameManager; Terrains : Array; @@ -46,6 +78,7 @@ export class GameScene extends Phaser.Scene { startX: number; startY: number; circleTexture: CanvasTexture; + pendingEvents: Queue = new Queue(); private initPosition: PositionInterface|null = null; private playersPositionInterpolator = new PlayersPositionInterpolator(); @@ -437,13 +470,13 @@ export class GameScene extends Phaser.Scene { EventToClickOnTile(){ // debug code to get a tile properties by clicking on it - this.input.on("pointerdown", (pointer: Phaser.Input.Pointer)=>{ + /*this.input.on("pointerdown", (pointer: Phaser.Input.Pointer)=>{ //pixel position toz tile position const tile = this.Map.getTileAt(this.Map.worldToTileX(pointer.worldX), this.Map.worldToTileY(pointer.worldY)); if(tile){ this.CurrentPlayer.say("Your touch " + tile.layer.name); } - }); + });*/ } /** @@ -454,6 +487,31 @@ export class GameScene extends Phaser.Scene { this.currentTick = time; this.CurrentPlayer.moveUser(delta); + // Let's handle all events + let event = null; + while (event = this.pendingEvents.dequeue()) { + switch (event.type) { + case "InitUserPositionEvent": + this.doInitUsersPosition(event.event); + break; + case "AddPlayerEvent": + this.doAddPlayer(event.event); + break; + case "RemovePlayerEvent": + this.doRemovePlayer(event.userId); + break; + case "UserMovedEvent": + this.doUpdatePlayerPosition(event.event); + break; + case "GroupCreatedUpdatedEvent": + this.doShareGroupPosition(event.event); + break; + case "DeleteGroupEvent": + this.doDeleteGroup(event.groupId); + break; + } + } + // Let's move all users const updatedPlayersPositions = this.playersPositionInterpolator.getUpdatedPositions(time); updatedPlayersPositions.forEach((moveEvent: HasMovedEvent, userId: string) => { @@ -488,12 +546,21 @@ export class GameScene extends Phaser.Scene { } } + /** + * Called by the connexion when the full list of user position is received. + */ public initUsersPosition(usersPosition: MessageUserPositionInterface[]): void { - if(!this.CurrentPlayer){ - console.error('Cannot initiate users list because map is not loaded yet') - return; - } + this.pendingEvents.enqueue({ + type: "InitUserPositionEvent", + event: usersPosition + }); + } + + /** + * Put all the players on the map on map load. + */ + private doInitUsersPosition(usersPosition: MessageUserPositionInterface[]): void { const currentPlayerId = this.GameManager.getPlayerId(); // clean map @@ -512,10 +579,20 @@ export class GameScene extends Phaser.Scene { }); } + /** + * Called by the connexion when a new player arrives on a map + */ + public addPlayer(addPlayerData : AddPlayerInterface) : void { + this.pendingEvents.enqueue({ + type: "AddPlayerEvent", + event: addPlayerData + }); + } + /** * Create new player */ - public addPlayer(addPlayerData : AddPlayerInterface) : void{ + private doAddPlayer(addPlayerData : AddPlayerInterface) : void { //check if exist player, if exist, move position if(this.MapPlayersByKey.has(addPlayerData.userId)){ this.updatePlayerPosition({ @@ -545,8 +622,18 @@ export class GameScene extends Phaser.Scene { });*/ } + /** + * Called by the connexion when a player is removed from the map + */ public removePlayer(userId: string) { - console.log('Removing player ', userId) + this.pendingEvents.enqueue({ + type: "RemovePlayerEvent", + userId + }); + } + + private doRemovePlayer(userId: string) { + //console.log('Removing player ', userId) const player = this.MapPlayersByKey.get(userId); if (player === undefined) { console.error('Cannot find user with id ', userId); @@ -558,7 +645,14 @@ export class GameScene extends Phaser.Scene { this.playersPositionInterpolator.removePlayer(userId); } - updatePlayerPosition(message: MessageUserMovedInterface): void { + public updatePlayerPosition(message: MessageUserMovedInterface): void { + this.pendingEvents.enqueue({ + type: "UserMovedEvent", + event: message + }); + } + + private doUpdatePlayerPosition(message: MessageUserMovedInterface): void { const player : RemotePlayer | undefined = this.MapPlayersByKey.get(message.userId); if (player === undefined) { throw new Error('Cannot find player with ID "' + message.userId +'"'); @@ -570,7 +664,14 @@ export class GameScene extends Phaser.Scene { this.playersPositionInterpolator.updatePlayerPosition(player.userId, playerMovement); } - shareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface) { + public shareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface) { + this.pendingEvents.enqueue({ + type: "GroupCreatedUpdatedEvent", + event: groupPositionMessage + }); + } + + private doShareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface) { const groupId = groupPositionMessage.groupId; const group = this.groups.get(groupId); @@ -590,6 +691,13 @@ export class GameScene extends Phaser.Scene { } deleteGroup(groupId: string): void { + this.pendingEvents.enqueue({ + type: "DeleteGroupEvent", + groupId + }); + } + + doDeleteGroup(groupId: string): void { const group = this.groups.get(groupId); if(!group){ return; diff --git a/front/yarn.lock b/front/yarn.lock index 7ed8c19a..126536e0 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -2602,6 +2602,11 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +linked-list-typescript@^1.0.11: + version "1.0.15" + resolved "https://registry.yarnpkg.com/linked-list-typescript/-/linked-list-typescript-1.0.15.tgz#faeed93cf9203f102e2158c29edcddda320abe82" + integrity sha512-RIyUu9lnJIyIaMe63O7/aFv/T2v3KsMFuXMBbUQCHX+cgtGro86ETDj5ed0a8gQL2+DFjzYYsgVG4I36/cUwgw== + loader-runner@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" @@ -3400,6 +3405,13 @@ queue-microtask@^1.1.0: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.1.2.tgz#139bf8186db0c545017ec66c2664ac646d5c571e" integrity sha512-F9wwNePtXrzZenAB3ax0Y8TSKGvuB7Qw16J30hspEUTbfUM+H827XyN3rlpwhVmtm5wuZtbKIHjOnwDn7MUxWQ== +queue-typescript@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/queue-typescript/-/queue-typescript-1.0.1.tgz#2d7842fc3b3e0e3f33d077887a8f2a5bb0baf460" + integrity sha512-tkK08uPfmpPl0cX1WRSU3EoNb/T5zSoZPGkkpfGX4E8QayWvEmLS2cI3pFngNPkNTCU5pCDQ1IwlzN0L5gdFPg== + dependencies: + linked-list-typescript "^1.0.11" + randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.3, randombytes@^2.0.5: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" From 4496c4962173e6ad7fee422353c5f4de76996d59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 19 Jun 2020 18:24:32 +0200 Subject: [PATCH 06/15] Fix linting --- front/src/Phaser/Game/GameScene.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 889cab16..c6647cd9 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -488,8 +488,8 @@ export class GameScene extends Phaser.Scene { this.CurrentPlayer.moveUser(delta); // Let's handle all events - let event = null; - while (event = this.pendingEvents.dequeue()) { + while (this.pendingEvents.length !== 0) { + const event = this.pendingEvents.dequeue(); switch (event.type) { case "InitUserPositionEvent": this.doInitUsersPosition(event.event); From d785a8a1bff7382be6bb0ea0fd1974b5d329b4ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 22 Jun 2020 11:58:07 +0200 Subject: [PATCH 07/15] Refactoring connection to be part of a GameScene Most of the refactoring issues we are seeing are probably due to the fact that we are trying to manipulate a ScenePlugin out of a Scene (the GameManager is not a Scene and holds a reference to a ScenePlugin coming from a Scene that might get invalidated by Phaser 3). Furthermore, if we want in the future to be able to scale, scenes could be hosted on different servers. Therefore, it makes no sense to have one connexion for the whole application. Instead, we should have one connexion for each scene. --- front/src/Connection.ts | 123 ++++++------------ front/src/Phaser/Game/GameManager.ts | 97 +++----------- front/src/Phaser/Game/GameScene.ts | 80 ++++++++++-- .../src/Phaser/Login/SelectCharacterScene.ts | 53 ++++---- front/src/WebRtc/SimplePeer.ts | 6 +- 5 files changed, 157 insertions(+), 202 deletions(-) diff --git a/front/src/Connection.ts b/front/src/Connection.ts index b6b251f0..931f7737 100644 --- a/front/src/Connection.ts +++ b/front/src/Connection.ts @@ -1,4 +1,4 @@ -import {GameManager} from "./Phaser/Game/GameManager"; +import {gameManager, GameManager} from "./Phaser/Game/GameManager"; import Axios from "axios"; import {API_URL} from "./Enum/EnvironmentVariable"; import {MessageUI} from "./Logger/MessageUI"; @@ -118,36 +118,12 @@ export interface WebRtcSignalMessageInterface { signal: SignalData } -export interface ConnectionInterface { - socket: Socket|null; - token: string|null; - name: string|null; - userId: string|null; - - createConnection(name: string, characterSelected: string): Promise; - - loadStartMap(): Promise; - - joinARoom(roomId: string, startX: number, startY: number, direction: string, moving: boolean): void; - - sharePosition(x: number, y: number, direction: string, moving: boolean): void; - - /*webrtc*/ - sendWebrtcSignal(signal: unknown, roomId: string, userId?: string|null, receiverId?: string): void; - - receiveWebrtcSignal(callBack: Function): void; - - receiveWebrtcStart(callBack: (message: WebRtcStartMessageInterface) => void): void; - - disconnectMessage(callBack: (message: WebRtcDisconnectMessageInterface) => void): void; -} - export interface StartMapInterface { mapUrlStart: string, startInstance: string } -export class Connection implements ConnectionInterface { +export class Connection implements Connection { socket: Socket|null = null; token: string|null = null; name: string|null = null; // TODO: drop "name" storage here @@ -159,32 +135,28 @@ export class Connection implements ConnectionInterface { lastPositionShared: PointInterface|null = null; lastRoom: string|null = null; - constructor(GameManager: GameManager) { + private constructor(GameManager: GameManager) { this.GameManager = GameManager; } - createConnection(name: string, characterSelected: string): Promise { - this.name = name; - this.character = characterSelected; + public static createConnection(name: string, characterSelected: string): Promise { + let connection = new Connection(gameManager); + connection.name = name; + connection.character = characterSelected; return Axios.post(`${API_URL}/login`, {name: name}) .then((res) => { - this.token = res.data.token; - this.socket = SocketIo(`${API_URL}`, { + connection.token = res.data.token; + connection.socket = SocketIo(`${API_URL}`, { query: { - token: this.token + token: connection.token } }); //listen event - this.disconnectServer(); - this.errorMessage(); - this.groupUpdatedOrCreated(); - this.groupDeleted(); - this.onUserJoins(); - this.onUserMoved(); - this.onUserLeft(); + connection.disconnectServer(); + connection.errorMessage(); - return this.connectSocketServer(); + return connection.connectSocketServer(); }) .catch((err) => { console.error(err); @@ -192,6 +164,14 @@ export class Connection implements ConnectionInterface { }); } + public closeConnection(): void { + this.socket?.close(); + this.socket = null; + this.lastPositionShared = null; + this.lastRoom = null; + + } + private getSocket(): Socket { if (this.socket === null) { throw new Error('Socket not initialized while using Connection') @@ -199,12 +179,8 @@ export class Connection implements ConnectionInterface { return this.socket; } - /** - * - * @param character - */ - connectSocketServer(): Promise{ - return new Promise((resolve, reject) => { + connectSocketServer(): Promise{ + return new Promise((resolve, reject) => { this.getSocket().emit(EventMessage.SET_PLAYER_DETAILS, { name: this.name, character: this.character @@ -237,24 +213,18 @@ export class Connection implements ConnectionInterface { }); } - //TODO add middleware with access token to secure api - loadStartMap() : Promise { - return Axios.get(`${API_URL}/start-map`) - .then((res) => { - return res.data; - }).catch((err) => { - console.error(err); - throw err; - }); - } - joinARoom(roomId: string, startX: number, startY: number, direction: string, moving: boolean): void { + joinARoom(roomId: string, startX: number, startY: number, direction: string, moving: boolean): Promise { const point = new Point(startX, startY, direction, moving); this.lastPositionShared = point; - this.getSocket().emit(EventMessage.JOIN_ROOM, { roomId, position: {x: startX, y: startY, direction, moving }}, (userPositions: MessageUserPositionInterface[]) => { - this.GameManager.initUsersPosition(userPositions); - }); + let promise = new Promise((resolve, reject) => { + this.getSocket().emit(EventMessage.JOIN_ROOM, { roomId, position: {x: startX, y: startY, direction, moving }}, (userPositions: MessageUserPositionInterface[]) => { + //this.GameManager.initUsersPosition(userPositions); + resolve(userPositions); + }); + }) this.lastRoom = roomId; + return promise; } sharePosition(x : number, y : number, direction : string, moving: boolean) : void{ @@ -266,35 +236,24 @@ export class Connection implements ConnectionInterface { this.getSocket().emit(EventMessage.USER_POSITION, point); } - private onUserJoins(): void { - this.getSocket().on(EventMessage.JOIN_ROOM, (message: MessageUserJoined) => { - this.GameManager.onUserJoins(message); - }); + public onUserJoins(callback: (message: MessageUserJoined) => void): void { + this.getSocket().on(EventMessage.JOIN_ROOM, callback); } - private onUserMoved(): void { - this.getSocket().on(EventMessage.USER_MOVED, (message: MessageUserMovedInterface) => { - this.GameManager.onUserMoved(message); - }); + public onUserMoved(callback: (message: MessageUserMovedInterface) => void): void { + this.getSocket().on(EventMessage.USER_MOVED, callback); } - private onUserLeft(): void { - this.getSocket().on(EventMessage.USER_LEFT, (userId: string) => { - this.GameManager.onUserLeft(userId); - }); + public onUserLeft(callback: (userId: string) => void): void { + this.getSocket().on(EventMessage.USER_LEFT, callback); } - private groupUpdatedOrCreated(): void { - this.getSocket().on(EventMessage.GROUP_CREATE_UPDATE, (groupCreateUpdateMessage: GroupCreatedUpdatedMessageInterface) => { - //console.log('Group ', groupCreateUpdateMessage.groupId, " position :", groupCreateUpdateMessage.position.x, groupCreateUpdateMessage.position.y) - this.GameManager.shareGroupPosition(groupCreateUpdateMessage); - }) + public onGroupUpdatedOrCreated(callback: (groupCreateUpdateMessage: GroupCreatedUpdatedMessageInterface) => void): void { + this.getSocket().on(EventMessage.GROUP_CREATE_UPDATE, callback); } - private groupDeleted(): void { - this.getSocket().on(EventMessage.GROUP_DELETE, (groupId: string) => { - this.GameManager.deleteGroup(groupId); - }) + public onGroupDeleted(callback: (groupId: string) => void): void { + this.getSocket().on(EventMessage.GROUP_DELETE, callback) } sendWebrtcSignal(signal: unknown, roomId: string, userId? : string|null, receiverId? : string) { diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index ec20411f..6c91972f 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -1,19 +1,21 @@ import {GameScene} from "./GameScene"; import { - Connection, ConnectionInterface, + Connection, GroupCreatedUpdatedMessageInterface, ListMessageUserPositionInterface, MessageUserJoined, MessageUserMovedInterface, MessageUserPositionInterface, Point, - PointInterface + PointInterface, StartMapInterface } from "../../Connection"; import {SimplePeer} from "../../WebRtc/SimplePeer"; import {AddPlayerInterface} from "./AddPlayerInterface"; import {ReconnectingScene, ReconnectingSceneName} from "../Reconnecting/ReconnectingScene"; import ScenePlugin = Phaser.Scenes.ScenePlugin; import {Scene} from "phaser"; +import Axios from "axios"; +import {API_URL} from "../../Enum/EnvironmentVariable"; /*export enum StatusGameManagerEnum { IN_PROGRESS = 1, @@ -34,7 +36,7 @@ export interface MapObject { export class GameManager { //status: number; - private ConnectionInstance: Connection; + //private ConnectionInstance: Connection; private currentGameScene: GameScene|null = null; private playerName: string; SimplePeer : SimplePeer; @@ -44,24 +46,26 @@ export class GameManager { //this.status = StatusGameManagerEnum.IN_PROGRESS; } - public connect(name: string, characterUserSelected : string): Promise { + public storePlayerDetails(name: string, characterUserSelected : string) /*: Promise*/ { this.playerName = name; this.characterUserSelected = characterUserSelected; - this.ConnectionInstance = new Connection(this); - return this.ConnectionInstance.createConnection(name, characterUserSelected).then((data : ConnectionInterface) => { + /*this.ConnectionInstance = new Connection(this); + return this.ConnectionInstance.createConnection(name, characterUserSelected).then((data : Connection) => { this.SimplePeer = new SimplePeer(this.ConnectionInstance); return data; }).catch((err) => { throw err; - }); + });*/ } - loadStartMap(){ - return this.ConnectionInstance.loadStartMap().then((data) => { - return data; - }).catch((err) => { - throw err; - }); + loadStartMap() : Promise { + return Axios.get(`${API_URL}/start-map`) + .then((res) => { + return res.data; + }).catch((err) => { + console.error(err); + throw err; + }); } setCurrentGameScene(gameScene: GameScene) { @@ -78,81 +82,14 @@ export class GameManager { //this.status = StatusGameManagerEnum.CURRENT_USER_CREATED; }*/ - joinRoom(sceneKey: string, startX: number, startY: number, direction: string, moving: boolean){ - this.ConnectionInstance.joinARoom(sceneKey, startX, startY, direction, moving); - } - - onUserJoins(message: MessageUserJoined): void { - const userMessage: AddPlayerInterface = { - userId: message.userId, - character: message.character, - name: message.name, - position: message.position - } - this.getCurrentGameScene().addPlayer(userMessage); - } - - onUserMoved(message: MessageUserMovedInterface): void { - this.getCurrentGameScene().updatePlayerPosition(message); - } - - onUserLeft(userId: string): void { - this.getCurrentGameScene().removePlayer(userId); - } - - initUsersPosition(usersPosition: MessageUserPositionInterface[]): void { - // Shall we wait for room to be loaded? - /*if (this.status === StatusGameManagerEnum.IN_PROGRESS) { - return; - }*/ - try { - this.getCurrentGameScene().initUsersPosition(usersPosition) - } catch (e) { - console.error(e); - } - } - - /** - * Share group position in game - */ - shareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface): void { - /*if (this.status === StatusGameManagerEnum.IN_PROGRESS) { - return; - }*/ - try { - this.getCurrentGameScene().shareGroupPosition(groupPositionMessage) - } catch (e) { - console.error(e); - } - } - - deleteGroup(groupId: string): void { - /*if (this.status === StatusGameManagerEnum.IN_PROGRESS) { - return; - }*/ - try { - this.getCurrentGameScene().deleteGroup(groupId) - } catch (e) { - console.error(e); - } - } - getPlayerName(): string { return this.playerName; } - getPlayerId(): string|null { - return this.ConnectionInstance.userId; - } - getCharacterSelected(): string { return this.characterUserSelected; } - pushPlayerPosition(event: HasMovedEvent) { - this.ConnectionInstance.sharePosition(event.x, event.y, event.direction, event.moving); - } - loadMap(mapUrl: string, scene: Phaser.Scenes.ScenePlugin, instance: string): string { const sceneKey = GameScene.getMapKeyByUrl(mapUrl); diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index c6647cd9..a1181c0e 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1,6 +1,7 @@ import {GameManager, gameManager, HasMovedEvent} from "./GameManager"; import { - GroupCreatedUpdatedMessageInterface, + Connection, + GroupCreatedUpdatedMessageInterface, MessageUserJoined, MessageUserMovedInterface, MessageUserPositionInterface, PointInterface, PositionInterface } from "../../Connection"; @@ -23,6 +24,7 @@ import {PlayersPositionInterpolator} from "./PlayersPositionInterpolator"; import {RemotePlayer} from "../Entity/RemotePlayer"; import GameObject = Phaser.GameObjects.GameObject; import { Queue } from 'queue-typescript'; +import {SimplePeer} from "../../WebRtc/SimplePeer"; export enum Textures { @@ -81,6 +83,9 @@ export class GameScene extends Phaser.Scene { pendingEvents: Queue = new Queue(); private initPosition: PositionInterface|null = null; private playersPositionInterpolator = new PlayersPositionInterpolator(); + private connection: Connection; + private simplePeer : SimplePeer; + private connectionPromise: Promise MapKey: string; MapUrlFile: string; @@ -99,8 +104,10 @@ export class GameScene extends Phaser.Scene { private PositionNextScene: Array> = new Array>(); private startLayerName: string|undefined; - static createFromUrl(mapUrlFile: string, instance: string): GameScene { - const key = GameScene.getMapKeyByUrl(mapUrlFile); + static createFromUrl(mapUrlFile: string, instance: string, key: string|null = null): GameScene { + if (key === null) { + key = GameScene.getMapKeyByUrl(mapUrlFile); + } return new GameScene(key, mapUrlFile, instance); } @@ -117,6 +124,7 @@ export class GameScene extends Phaser.Scene { this.MapKey = MapKey; this.MapUrlFile = MapUrlFile; this.RoomId = this.instance + '__' + this.MapKey; + } //hook preload scene @@ -144,6 +152,49 @@ export class GameScene extends Phaser.Scene { }); this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); + + this.connectionPromise = Connection.createConnection(gameManager.getPlayerName(), gameManager.getCharacterSelected()).then((connection : Connection) => { + this.connection = connection; + + connection.onUserJoins((message: MessageUserJoined) => { + const userMessage: AddPlayerInterface = { + userId: message.userId, + character: message.character, + name: message.name, + position: message.position + } + this.addPlayer(userMessage); + }); + + connection.onUserMoved((message: MessageUserMovedInterface) => { + this.updatePlayerPosition(message); + }); + + connection.onUserLeft((userId: string) => { + this.removePlayer(userId); + }); + + connection.onGroupUpdatedOrCreated((groupPositionMessage: GroupCreatedUpdatedMessageInterface) => { + this.shareGroupPosition(groupPositionMessage); + }) + + connection.onGroupDeleted((groupId: string) => { + try { + this.deleteGroup(groupId); + } catch (e) { + console.error(e); + } + }) + + // When connection is performed, let's connect SimplePeer + this.simplePeer = new SimplePeer(this.connection); + + if (this.scene.isPaused()) { + this.scene.resume(); + } + + return connection; + }); } // FIXME: we need to put a "unknown" instead of a "any" and validate the structure of the JSON we are receiving. @@ -272,6 +323,11 @@ export class GameScene extends Phaser.Scene { path += '#'+this.startLayerName; } window.history.pushState({}, 'WorkAdventure', path); + + // Let's pause the scene if the connection is not established yet + if (this.connection === undefined) { + this.scene.pause(); + } } private getExitSceneUrl(layer: ITiledMapLayer): string|undefined { @@ -430,10 +486,14 @@ export class GameScene extends Phaser.Scene { this.createCollisionObject(); //join room - this.GameManager.joinRoom(this.RoomId, this.startX, this.startY, PlayerAnimationNames.WalkDown, false); + this.connectionPromise.then((connection: Connection) => { + connection.joinARoom(this.RoomId, this.startX, this.startY, PlayerAnimationNames.WalkDown, false).then((userPositions: MessageUserPositionInterface[]) => { + this.initUsersPosition(userPositions); + }); - //listen event to share position of user - this.CurrentPlayer.on(hasMovedEventName, this.pushPlayerPosition.bind(this)) + //listen event to share position of user + this.CurrentPlayer.on(hasMovedEventName, this.pushPlayerPosition.bind(this)) + }); } pushPlayerPosition(event: HasMovedEvent) { @@ -465,7 +525,7 @@ export class GameScene extends Phaser.Scene { private doPushPlayerPosition(event: HasMovedEvent): void { this.lastMoveEventSent = event; this.lastSentTick = this.currentTick; - this.GameManager.pushPlayerPosition(event); + this.connection.sharePosition(event.x, event.y, event.direction, event.moving); } EventToClickOnTile(){ @@ -525,6 +585,8 @@ export class GameScene extends Phaser.Scene { const nextSceneKey = this.checkToExit(); if(nextSceneKey){ // We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map. + this.connection.closeConnection(); + this.scene.stop(); this.scene.remove(this.scene.key); this.scene.start(nextSceneKey.key, { startLayerName: nextSceneKey.hash @@ -549,7 +611,7 @@ export class GameScene extends Phaser.Scene { /** * Called by the connexion when the full list of user position is received. */ - public initUsersPosition(usersPosition: MessageUserPositionInterface[]): void { + private initUsersPosition(usersPosition: MessageUserPositionInterface[]): void { this.pendingEvents.enqueue({ type: "InitUserPositionEvent", event: usersPosition @@ -561,7 +623,7 @@ export class GameScene extends Phaser.Scene { * Put all the players on the map on map load. */ private doInitUsersPosition(usersPosition: MessageUserPositionInterface[]): void { - const currentPlayerId = this.GameManager.getPlayerId(); + const currentPlayerId = this.connection.userId; // clean map this.MapPlayersByKey.forEach((player: RemotePlayer) => { diff --git a/front/src/Phaser/Login/SelectCharacterScene.ts b/front/src/Phaser/Login/SelectCharacterScene.ts index 316ee897..5175a7b8 100644 --- a/front/src/Phaser/Login/SelectCharacterScene.ts +++ b/front/src/Phaser/Login/SelectCharacterScene.ts @@ -117,34 +117,31 @@ export class SelectCharacterScene extends Phaser.Scene { } private async login(name: string): Promise { - return gameManager.connect(name, this.selectedPlayer.texture.key).then(() => { - // Do we have a start URL in the address bar? If so, let's redirect to this address - const instanceAndMapUrl = this.findMapUrl(); - if (instanceAndMapUrl !== null) { - const [mapUrl, instance] = instanceAndMapUrl; - const key = gameManager.loadMap(mapUrl, this.scene, instance); - this.scene.start(key, { - startLayerName: window.location.hash ? window.location.hash.substr(1) : undefined - } as GameSceneInitInterface); - return { - mapUrlStart: mapUrl, - startInstance: instance - }; - } else { - // If we do not have a map address in the URL, let's ask the server for a start map. - return gameManager.loadStartMap().then((startMap: StartMapInterface) => { - const key = gameManager.loadMap(window.location.protocol + "//" + startMap.mapUrlStart, this.scene, startMap.startInstance); - this.scene.start(key); - return startMap; - }).catch((err) => { - console.error(err); - throw err; - }); - } - }).catch((err) => { - console.error(err); - throw err; - }); + gameManager.storePlayerDetails(name, this.selectedPlayer.texture.key); + + // Do we have a start URL in the address bar? If so, let's redirect to this address + const instanceAndMapUrl = this.findMapUrl(); + if (instanceAndMapUrl !== null) { + const [mapUrl, instance] = instanceAndMapUrl; + const key = gameManager.loadMap(mapUrl, this.scene, instance); + this.scene.start(key, { + startLayerName: window.location.hash ? window.location.hash.substr(1) : undefined + } as GameSceneInitInterface); + return { + mapUrlStart: mapUrl, + startInstance: instance + }; + } else { + // If we do not have a map address in the URL, let's ask the server for a start map. + return gameManager.loadStartMap().then((startMap: StartMapInterface) => { + const key = gameManager.loadMap(window.location.protocol + "//" + startMap.mapUrlStart, this.scene, startMap.startInstance); + this.scene.start(key); + return startMap; + }).catch((err) => { + console.error(err); + throw err; + }); + } } /** diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index f5c8e7ef..4adb1b18 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -1,5 +1,5 @@ import { - ConnectionInterface, + Connection, WebRtcDisconnectMessageInterface, WebRtcSignalMessageInterface, WebRtcStartMessageInterface @@ -18,7 +18,7 @@ export interface UserSimplePeer{ * This class manages connections to all the peers in the same group as me. */ export class SimplePeer { - private Connection: ConnectionInterface; + private Connection: Connection; private WebRtcRoomId: string; private Users: Array = new Array(); @@ -26,7 +26,7 @@ export class SimplePeer { private PeerConnectionArray: Map = new Map(); - constructor(Connection: ConnectionInterface, WebRtcRoomId: string = "test-webrtc") { + constructor(Connection: Connection, WebRtcRoomId: string = "test-webrtc") { this.Connection = Connection; this.WebRtcRoomId = WebRtcRoomId; this.MediaManager = new MediaManager((stream : MediaStream) => { From f88f28db3f6ba637b4c0b174a531390c999bb282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 22 Jun 2020 15:00:23 +0200 Subject: [PATCH 08/15] Refactoring reconnection: putting it into the GameScene directly. --- front/src/Connection.ts | 114 +++++++++++++++------------ front/src/Phaser/Game/GameManager.ts | 4 +- front/src/Phaser/Game/GameScene.ts | 34 ++++++-- front/src/WebRtc/SimplePeer.ts | 6 ++ front/tsconfig.json | 1 + 5 files changed, 101 insertions(+), 58 deletions(-) diff --git a/front/src/Connection.ts b/front/src/Connection.ts index 931f7737..ea382365 100644 --- a/front/src/Connection.ts +++ b/front/src/Connection.ts @@ -124,7 +124,7 @@ export interface StartMapInterface { } export class Connection implements Connection { - socket: Socket|null = null; + socket: Socket; token: string|null = null; name: string|null = null; // TODO: drop "name" storage here character: string|null = null; @@ -135,53 +135,59 @@ export class Connection implements Connection { lastPositionShared: PointInterface|null = null; lastRoom: string|null = null; - private constructor(GameManager: GameManager) { + private constructor(GameManager: GameManager, name: string, character: string, token: string) { this.GameManager = GameManager; + this.name = name; + this.character = character; + this.token = token; + + this.socket = SocketIo(`${API_URL}`, { + query: { + token: this.token + }, + reconnection: false // Reconnection is handled by the application itself + }); + + this.socket.on(EventMessage.CONNECT_ERROR, () => { + console.error("Connection failed") + }); + + this.socket.on(EventMessage.MESSAGE_ERROR, (message: string) => { + console.error(EventMessage.MESSAGE_ERROR, message); + }) } public static createConnection(name: string, characterSelected: string): Promise { - let connection = new Connection(gameManager); - connection.name = name; - connection.character = characterSelected; return Axios.post(`${API_URL}/login`, {name: name}) .then((res) => { - connection.token = res.data.token; - connection.socket = SocketIo(`${API_URL}`, { - query: { - token: connection.token - } - }); - - //listen event - connection.disconnectServer(); - connection.errorMessage(); + let connection = new Connection(gameManager, name, characterSelected, res.data.token); + // FIXME: we should wait for the complete connexion here (i.e. the "connected" message from socket.io)! + // Otherwise, the connection MAY fail and we will never know! return connection.connectSocketServer(); }) .catch((err) => { - console.error(err); - throw err; + // Let's retry in 4-6 seconds + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve(Connection.createConnection(name, characterSelected)); + }, 4000 + Math.floor(Math.random() * 2000) ); + }); + + //console.error(err); + //throw err; }); } public closeConnection(): void { this.socket?.close(); - this.socket = null; this.lastPositionShared = null; this.lastRoom = null; - - } - - private getSocket(): Socket { - if (this.socket === null) { - throw new Error('Socket not initialized while using Connection') - } - return this.socket; } connectSocketServer(): Promise{ return new Promise((resolve, reject) => { - this.getSocket().emit(EventMessage.SET_PLAYER_DETAILS, { + this.socket.emit(EventMessage.SET_PLAYER_DETAILS, { name: this.name, character: this.character } as SetPlayerDetailsMessage, (id: string) => { @@ -218,7 +224,7 @@ export class Connection implements Connection { const point = new Point(startX, startY, direction, moving); this.lastPositionShared = point; let promise = new Promise((resolve, reject) => { - this.getSocket().emit(EventMessage.JOIN_ROOM, { roomId, position: {x: startX, y: startY, direction, moving }}, (userPositions: MessageUserPositionInterface[]) => { + this.socket.emit(EventMessage.JOIN_ROOM, { roomId, position: {x: startX, y: startY, direction, moving }}, (userPositions: MessageUserPositionInterface[]) => { //this.GameManager.initUsersPosition(userPositions); resolve(userPositions); }); @@ -233,31 +239,31 @@ export class Connection implements Connection { } const point = new Point(x, y, direction, moving); this.lastPositionShared = point; - this.getSocket().emit(EventMessage.USER_POSITION, point); + this.socket.emit(EventMessage.USER_POSITION, point); } public onUserJoins(callback: (message: MessageUserJoined) => void): void { - this.getSocket().on(EventMessage.JOIN_ROOM, callback); + this.socket.on(EventMessage.JOIN_ROOM, callback); } public onUserMoved(callback: (message: MessageUserMovedInterface) => void): void { - this.getSocket().on(EventMessage.USER_MOVED, callback); + this.socket.on(EventMessage.USER_MOVED, callback); } public onUserLeft(callback: (userId: string) => void): void { - this.getSocket().on(EventMessage.USER_LEFT, callback); + this.socket.on(EventMessage.USER_LEFT, callback); } public onGroupUpdatedOrCreated(callback: (groupCreateUpdateMessage: GroupCreatedUpdatedMessageInterface) => void): void { - this.getSocket().on(EventMessage.GROUP_CREATE_UPDATE, callback); + this.socket.on(EventMessage.GROUP_CREATE_UPDATE, callback); } public onGroupDeleted(callback: (groupId: string) => void): void { - this.getSocket().on(EventMessage.GROUP_DELETE, callback) + this.socket.on(EventMessage.GROUP_DELETE, callback) } sendWebrtcSignal(signal: unknown, roomId: string, userId? : string|null, receiverId? : string) { - return this.getSocket().emit(EventMessage.WEBRTC_SIGNAL, { + return this.socket.emit(EventMessage.WEBRTC_SIGNAL, { userId: userId ? userId : this.userId, receiverId: receiverId ? receiverId : this.userId, roomId: roomId, @@ -266,47 +272,53 @@ export class Connection implements Connection { } receiveWebrtcStart(callback: (message: WebRtcStartMessageInterface) => void) { - this.getSocket().on(EventMessage.WEBRTC_START, callback); + this.socket.on(EventMessage.WEBRTC_START, callback); } receiveWebrtcSignal(callback: (message: WebRtcSignalMessageInterface) => void) { - return this.getSocket().on(EventMessage.WEBRTC_SIGNAL, callback); + return this.socket.on(EventMessage.WEBRTC_SIGNAL, callback); } - private errorMessage(): void { - this.getSocket().on(EventMessage.MESSAGE_ERROR, (message: string) => { - console.error(EventMessage.MESSAGE_ERROR, message); - }) - } + public onServerDisconnected(callback: (reason: string) => void): void { + /*this.socket.on(EventMessage.CONNECT_ERROR, (error: object) => { + callback(error); + });*/ - private disconnectServer(): void { - this.getSocket().on(EventMessage.CONNECT_ERROR, () => { - this.GameManager.switchToDisconnectedScene(); + this.socket.on('disconnect', (reason: string) => { + if (reason === 'io client disconnect') { + // The client asks for disconnect, let's not trigger any event. + return; + } + callback(reason); }); - this.getSocket().on(EventMessage.RECONNECTING, () => { + /*this.socket.on(EventMessage.CONNECT_ERROR, (error: object) => { + this.GameManager.switchToDisconnectedScene(); + });*/ + + /*this.socket.on(EventMessage.RECONNECTING, () => { console.log('Trying to reconnect'); }); - this.getSocket().on(EventMessage.RECONNECT_ERROR, () => { + this.socket.on(EventMessage.RECONNECT_ERROR, () => { console.log('Error while trying to reconnect.'); }); - this.getSocket().on(EventMessage.RECONNECT_FAILED, () => { + this.socket.on(EventMessage.RECONNECT_FAILED, () => { console.error('Reconnection failed. Giving up.'); }); - this.getSocket().on(EventMessage.RECONNECT, () => { + this.socket.on(EventMessage.RECONNECT, () => { console.log('Reconnect event triggered'); this.connectSocketServer(); if (this.lastPositionShared === null) { throw new Error('No last position shared found while reconnecting'); } this.GameManager.reconnectToGameScene(this.lastPositionShared); - }); + });*/ } disconnectMessage(callback: (message: WebRtcDisconnectMessageInterface) => void): void { - this.getSocket().on(EventMessage.WEBRTC_DISCONNECT, callback); + this.socket.on(EventMessage.WEBRTC_DISCONNECT, callback); } } diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index 6c91972f..5d77e6a7 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -122,7 +122,7 @@ export class GameManager { this.currentGameScene = null; } - private timeoutCallback: NodeJS.Timeout|null = null; + /*private timeoutCallback: NodeJS.Timeout|null = null; reconnectToGameScene(lastPositionShared: PointInterface): void { if (this.timeoutCallback !== null) { console.log('Reconnect called but setTimeout in progress for the reconnection'); @@ -150,7 +150,7 @@ export class GameManager { const game : Phaser.Scene = GameScene.createFromUrl(this.oldMapUrlFile, this.oldInstance); this.reconnectScene.scene.add(this.oldSceneKey, game, true, { initPosition: lastPositionShared }); this.reconnectScene = null; - } + }*/ private getCurrentGameScene(): GameScene { if (this.currentGameScene === null) { diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index a1181c0e..3374fc08 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -25,6 +25,7 @@ import {RemotePlayer} from "../Entity/RemotePlayer"; import GameObject = Phaser.GameObjects.GameObject; import { Queue } from 'queue-typescript'; import {SimplePeer} from "../../WebRtc/SimplePeer"; +import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene"; export enum Textures { @@ -123,8 +124,7 @@ export class GameScene extends Phaser.Scene { this.MapKey = MapKey; this.MapUrlFile = MapUrlFile; - this.RoomId = this.instance + '__' + this.MapKey; - + this.RoomId = this.instance + '__' + GameScene.getMapKeyByUrl(MapUrlFile); } //hook preload scene @@ -186,11 +186,29 @@ export class GameScene extends Phaser.Scene { } }) + connection.onServerDisconnected(() => { + console.log('Player disconnected from server. Reloading scene.'); + + this.simplePeer.closeAllConnections(); + + let key = 'somekey'+Math.round(Math.random()*10000); + const game : Phaser.Scene = GameScene.createFromUrl(this.MapUrlFile, this.instance, key); + this.scene.add(key, game, false, + { + initPosition: { + x: this.CurrentPlayer.x, + y: this.CurrentPlayer.y + } + }); + this.scene.start(key); + }) + // When connection is performed, let's connect SimplePeer this.simplePeer = new SimplePeer(this.connection); - if (this.scene.isPaused()) { - this.scene.resume(); + if (this.scene.isSleeping()) { + this.scene.wake(); + this.scene.sleep(ReconnectingSceneName); } return connection; @@ -326,7 +344,13 @@ export class GameScene extends Phaser.Scene { // Let's pause the scene if the connection is not established yet if (this.connection === undefined) { - this.scene.pause(); + // Let's wait 0.5 seconds before printing the "connecting" screen to avoid blinking + setTimeout(() => { + if (this.connection === undefined) { + this.scene.sleep(); + this.scene.launch(ReconnectingSceneName); + } + }, 500); } } diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 4adb1b18..381b3ac2 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -209,6 +209,12 @@ export class SimplePeer { } } + public closeAllConnections() { + for (const userId of this.PeerConnectionArray.keys()) { + this.closeConnection(userId); + } + } + /** * * @param userId diff --git a/front/tsconfig.json b/front/tsconfig.json index 84882e74..1661efa2 100644 --- a/front/tsconfig.json +++ b/front/tsconfig.json @@ -6,6 +6,7 @@ "noImplicitAny": true, "module": "CommonJS", "target": "es5", + "downlevelIteration": true, "jsx": "react", "allowJs": true, From 403ea223a844b5d51901e7d67018dccd3080b6de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 22 Jun 2020 16:10:18 +0200 Subject: [PATCH 09/15] Reconnecting also on socket error --- front/src/Connection.ts | 66 ++++++++++++++--------------------------- 1 file changed, 23 insertions(+), 43 deletions(-) diff --git a/front/src/Connection.ts b/front/src/Connection.ts index ea382365..1bd11948 100644 --- a/front/src/Connection.ts +++ b/front/src/Connection.ts @@ -148,10 +148,6 @@ export class Connection implements Connection { reconnection: false // Reconnection is handled by the application itself }); - this.socket.on(EventMessage.CONNECT_ERROR, () => { - console.error("Connection failed") - }); - this.socket.on(EventMessage.MESSAGE_ERROR, (message: string) => { console.error(EventMessage.MESSAGE_ERROR, message); }) @@ -160,17 +156,31 @@ export class Connection implements Connection { public static createConnection(name: string, characterSelected: string): Promise { return Axios.post(`${API_URL}/login`, {name: name}) .then((res) => { - let connection = new Connection(gameManager, name, characterSelected, res.data.token); - // FIXME: we should wait for the complete connexion here (i.e. the "connected" message from socket.io)! - // Otherwise, the connection MAY fail and we will never know! - return connection.connectSocketServer(); + return new Promise((resolve, reject) => { + let connection = new Connection(gameManager, name, characterSelected, res.data.token); + + connection.onConnectError((error: object) => { + console.log('An error occurred while connecting to socket server. Retrying'); + reject(error); + }); + + connection.socket.emit(EventMessage.SET_PLAYER_DETAILS, { + name: connection.name, + character: connection.character + } as SetPlayerDetailsMessage, (id: string) => { + connection.userId = id; + }); + + resolve(connection); + }); }) .catch((err) => { // Let's retry in 4-6 seconds return new Promise((resolve, reject) => { setTimeout(() => { - resolve(Connection.createConnection(name, characterSelected)); + Connection.createConnection(name, characterSelected).then((connection) => resolve(connection)) + .catch((error) => reject(error)); }, 4000 + Math.floor(Math.random() * 2000) ); }); @@ -185,40 +195,6 @@ export class Connection implements Connection { this.lastRoom = null; } - connectSocketServer(): Promise{ - return new Promise((resolve, reject) => { - this.socket.emit(EventMessage.SET_PLAYER_DETAILS, { - name: this.name, - character: this.character - } as SetPlayerDetailsMessage, (id: string) => { - this.userId = id; - }); - - //if try to reconnect with last position - /*if(this.lastRoom) { - //join the room - this.joinARoom(this.lastRoom, - this.lastPositionShared ? this.lastPositionShared.x : 0, - this.lastPositionShared ? this.lastPositionShared.y : 0, - this.lastPositionShared ? this.lastPositionShared.direction : PlayerAnimationNames.WalkDown, - this.lastPositionShared ? this.lastPositionShared.moving : false); - }*/ - - /*if(this.lastPositionShared) { - - //share your first position - this.sharePosition( - this.lastPositionShared ? this.lastPositionShared.x : 0, - this.lastPositionShared ? this.lastPositionShared.y : 0, - this.lastPositionShared.direction, - this.lastPositionShared.moving - ); - }*/ - - resolve(this); - }); - } - joinARoom(roomId: string, startX: number, startY: number, direction: string, moving: boolean): Promise { const point = new Point(startX, startY, direction, moving); @@ -262,6 +238,10 @@ export class Connection implements Connection { this.socket.on(EventMessage.GROUP_DELETE, callback) } + public onConnectError(callback: (error: object) => void): void { + this.socket.on(EventMessage.CONNECT_ERROR, callback) + } + sendWebrtcSignal(signal: unknown, roomId: string, userId? : string|null, receiverId? : string) { return this.socket.emit(EventMessage.WEBRTC_SIGNAL, { userId: userId ? userId : this.userId, From 1e4ffa20abba2ac0df883f5c1ef2eac816aefd33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 22 Jun 2020 16:10:34 +0200 Subject: [PATCH 10/15] Cleaning GameManager --- front/src/Phaser/Game/GameManager.ts | 94 +--------------------------- 1 file changed, 1 insertion(+), 93 deletions(-) diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index 5d77e6a7..071f1780 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -17,11 +17,6 @@ import {Scene} from "phaser"; import Axios from "axios"; import {API_URL} from "../../Enum/EnvironmentVariable"; -/*export enum StatusGameManagerEnum { - IN_PROGRESS = 1, - CURRENT_USER_CREATED = 2 -}*/ - export interface HasMovedEvent { direction: string; moving: boolean; @@ -35,27 +30,12 @@ export interface MapObject { } export class GameManager { - //status: number; - //private ConnectionInstance: Connection; - private currentGameScene: GameScene|null = null; private playerName: string; - SimplePeer : SimplePeer; private characterUserSelected: string; - constructor() { - //this.status = StatusGameManagerEnum.IN_PROGRESS; - } - - public storePlayerDetails(name: string, characterUserSelected : string) /*: Promise*/ { + public storePlayerDetails(name: string, characterUserSelected : string): void { this.playerName = name; this.characterUserSelected = characterUserSelected; - /*this.ConnectionInstance = new Connection(this); - return this.ConnectionInstance.createConnection(name, characterUserSelected).then((data : Connection) => { - this.SimplePeer = new SimplePeer(this.ConnectionInstance); - return data; - }).catch((err) => { - throw err; - });*/ } loadStartMap() : Promise { @@ -68,20 +48,6 @@ export class GameManager { }); } - setCurrentGameScene(gameScene: GameScene) { - this.currentGameScene = gameScene; - } - - - /** - * Permit to create player in started room - */ - /*createCurrentPlayer(): void { - //Get started room send by the backend - this.currentGameScene.createCurrentPlayer(); - //this.status = StatusGameManagerEnum.CURRENT_USER_CREATED; - }*/ - getPlayerName(): string { return this.playerName; } @@ -100,64 +66,6 @@ export class GameManager { } return sceneKey; } - - private oldSceneKey : string; - private oldMapUrlFile : string; - private oldInstance : string; - private scenePlugin: ScenePlugin; - private reconnectScene: Scene|null = null; - switchToDisconnectedScene(): void { - if (this.currentGameScene === null) { - return; - } - console.log('Switching to disconnected scene'); - this.oldSceneKey = this.currentGameScene.scene.key; - this.oldMapUrlFile = this.currentGameScene.MapUrlFile; - this.oldInstance = this.currentGameScene.instance; - this.currentGameScene.scene.start(ReconnectingSceneName); - this.reconnectScene = this.currentGameScene.scene.get(ReconnectingSceneName); - // Let's completely delete an purge the disconnected scene. We will start again from 0. - this.currentGameScene.scene.remove(this.oldSceneKey); - this.scenePlugin = this.currentGameScene.scene; - this.currentGameScene = null; - } - - /*private timeoutCallback: NodeJS.Timeout|null = null; - reconnectToGameScene(lastPositionShared: PointInterface): void { - if (this.timeoutCallback !== null) { - console.log('Reconnect called but setTimeout in progress for the reconnection'); - return; - } - if (this.reconnectScene === null) { - console.log('Reconnect called without switchToDisconnectedScene called first'); - - if (!this.currentGameScene) { - console.error('Reconnect called but we are not on a GameScene'); - return; - } - - // In case we are asked to reconnect even if switchToDisconnectedScene was not triggered (can happen when a laptop goes to sleep) - this.switchToDisconnectedScene(); - // Wait a bit for scene to load. Otherwise, starting ReconnectingSceneName and then starting GameScene one after the other fails for some reason. - this.timeoutCallback = setTimeout(() => { - console.log('Reconnecting to game scene from setTimeout'); - this.timeoutCallback = null; - this.reconnectToGameScene(lastPositionShared); - }, 500); - return; - } - console.log('Reconnecting to game scene'); - const game : Phaser.Scene = GameScene.createFromUrl(this.oldMapUrlFile, this.oldInstance); - this.reconnectScene.scene.add(this.oldSceneKey, game, true, { initPosition: lastPositionShared }); - this.reconnectScene = null; - }*/ - - private getCurrentGameScene(): GameScene { - if (this.currentGameScene === null) { - throw new Error('No current game scene enabled'); - } - return this.currentGameScene; - } } export const gameManager = new GameManager(); From 9b174836cdbc398cce27c7a311e50095cece3779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 22 Jun 2020 16:11:02 +0200 Subject: [PATCH 11/15] Making sure connection is established again EVEN if the tab is not displayed --- front/src/Phaser/Game/GameScene.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 3374fc08..503de384 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -129,7 +129,6 @@ export class GameScene extends Phaser.Scene { //hook preload scene preload(): void { - this.GameManager.setCurrentGameScene(this); this.load.on('filecomplete-tilemapJSON-'+this.MapKey, (key: string, type: string, data: unknown) => { this.onMapLoad(data); }); @@ -193,23 +192,23 @@ export class GameScene extends Phaser.Scene { let key = 'somekey'+Math.round(Math.random()*10000); const game : Phaser.Scene = GameScene.createFromUrl(this.MapUrlFile, this.instance, key); - this.scene.add(key, game, false, + this.scene.add(key, game, true, { initPosition: { x: this.CurrentPlayer.x, y: this.CurrentPlayer.y } }); - this.scene.start(key); + + this.scene.stop(this.scene.key); + this.scene.remove(this.scene.key); }) // When connection is performed, let's connect SimplePeer this.simplePeer = new SimplePeer(this.connection); - if (this.scene.isSleeping()) { - this.scene.wake(); - this.scene.sleep(ReconnectingSceneName); - } + this.scene.wake(); + this.scene.sleep(ReconnectingSceneName); return connection; }); @@ -719,7 +718,6 @@ export class GameScene extends Phaser.Scene { } private doRemovePlayer(userId: string) { - //console.log('Removing player ', userId) const player = this.MapPlayersByKey.get(userId); if (player === undefined) { console.error('Cannot find user with id ', userId); From 3f927280a6a82c0421b15d8b2102d35c4a72cbc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 22 Jun 2020 16:11:48 +0200 Subject: [PATCH 12/15] Lint --- front/src/Connection.ts | 4 ++-- front/src/Phaser/Game/GameScene.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/front/src/Connection.ts b/front/src/Connection.ts index 1bd11948..cad639f9 100644 --- a/front/src/Connection.ts +++ b/front/src/Connection.ts @@ -158,7 +158,7 @@ export class Connection implements Connection { .then((res) => { return new Promise((resolve, reject) => { - let connection = new Connection(gameManager, name, characterSelected, res.data.token); + const connection = new Connection(gameManager, name, characterSelected, res.data.token); connection.onConnectError((error: object) => { console.log('An error occurred while connecting to socket server. Retrying'); @@ -199,7 +199,7 @@ export class Connection implements Connection { joinARoom(roomId: string, startX: number, startY: number, direction: string, moving: boolean): Promise { const point = new Point(startX, startY, direction, moving); this.lastPositionShared = point; - let promise = new Promise((resolve, reject) => { + const promise = new Promise((resolve, reject) => { this.socket.emit(EventMessage.JOIN_ROOM, { roomId, position: {x: startX, y: startY, direction, moving }}, (userPositions: MessageUserPositionInterface[]) => { //this.GameManager.initUsersPosition(userPositions); resolve(userPositions); diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 503de384..4a23a7c4 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -190,7 +190,7 @@ export class GameScene extends Phaser.Scene { this.simplePeer.closeAllConnections(); - let key = 'somekey'+Math.round(Math.random()*10000); + const key = 'somekey'+Math.round(Math.random()*10000); const game : Phaser.Scene = GameScene.createFromUrl(this.MapUrlFile, this.instance, key); this.scene.add(key, game, true, { From 407c6db070e48abf1f78754a22dd115e663b51db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 22 Jun 2020 17:14:56 +0200 Subject: [PATCH 13/15] Fixing difference between MapKey and scene key (prevented proper caching of resources) --- front/src/Phaser/Game/GameScene.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 4a23a7c4..67e953f2 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -106,15 +106,16 @@ export class GameScene extends Phaser.Scene { private startLayerName: string|undefined; static createFromUrl(mapUrlFile: string, instance: string, key: string|null = null): GameScene { + const mapKey = GameScene.getMapKeyByUrl(mapUrlFile); if (key === null) { - key = GameScene.getMapKeyByUrl(mapUrlFile); + key = mapKey; } - return new GameScene(key, mapUrlFile, instance); + return new GameScene(mapKey, mapUrlFile, instance, key); } - constructor(MapKey : string, MapUrlFile: string, instance: string) { + constructor(MapKey : string, MapUrlFile: string, instance: string, key: string) { super({ - key: MapKey + key: key }); this.GameManager = gameManager; @@ -124,7 +125,7 @@ export class GameScene extends Phaser.Scene { this.MapKey = MapKey; this.MapUrlFile = MapUrlFile; - this.RoomId = this.instance + '__' + GameScene.getMapKeyByUrl(MapUrlFile); + this.RoomId = this.instance + '__' + MapKey; } //hook preload scene From a5514ce78a2e7ab4711c16502106c4401a61ef94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 22 Jun 2020 18:42:54 +0200 Subject: [PATCH 14/15] Code cleanup and removing exception in favor of console error --- front/src/Connection.ts | 108 ++++----------------------- front/src/Phaser/Game/GameManager.ts | 19 +---- front/src/Phaser/Game/GameScene.ts | 6 +- 3 files changed, 20 insertions(+), 113 deletions(-) diff --git a/front/src/Connection.ts b/front/src/Connection.ts index cad639f9..c4ac92c6 100644 --- a/front/src/Connection.ts +++ b/front/src/Connection.ts @@ -1,4 +1,3 @@ -import {gameManager, GameManager} from "./Phaser/Game/GameManager"; import Axios from "axios"; import {API_URL} from "./Enum/EnvironmentVariable"; import {MessageUI} from "./Logger/MessageUI"; @@ -14,7 +13,6 @@ import {SignalData} from "simple-peer"; enum EventMessage{ WEBRTC_SIGNAL = "webrtc-signal", WEBRTC_START = "webrtc-start", - WEBRTC_JOIN_ROOM = "webrtc-join-room", JOIN_ROOM = "join-room", // bi-directional USER_POSITION = "user-position", // bi-directional USER_MOVED = "user-moved", // From server to client @@ -26,22 +24,6 @@ enum EventMessage{ SET_PLAYER_DETAILS = "set-player-details", // Send the name and character to the server (on connect), receive back the id. CONNECT_ERROR = "connect_error", - RECONNECT = "reconnect", - RECONNECTING = "reconnecting", - RECONNECT_ERROR = "reconnect_error", - RECONNECT_FAILED = "reconnect_failed" -} - -class Message { - userId: string; - name: string; - character: string; - - constructor(userId : string, name: string, character: string) { - this.userId = userId; - this.name = name; - this.character = character; - } } export interface PointInterface { @@ -71,15 +53,6 @@ export interface MessageUserMovedInterface { position: PointInterface; } -class MessageUserPosition extends Message implements MessageUserPositionInterface{ - position: PointInterface; - - constructor(userId : string, point : Point, name: string, character: string) { - super(userId, name, character); - this.position = point; - } -} - export interface MessageUserJoined { userId: string; name: string; @@ -87,11 +60,6 @@ export interface MessageUserJoined { position: PointInterface } -export interface ListMessageUserPositionInterface { - roomId: string; - listUsersPosition: Array; -} - export interface PositionInterface { x: number, y: number @@ -124,26 +92,14 @@ export interface StartMapInterface { } export class Connection implements Connection { - socket: Socket; - token: string|null = null; - name: string|null = null; // TODO: drop "name" storage here - character: string|null = null; - userId: string|null = null; + private readonly socket: Socket; + private userId: string|null = null; - GameManager: GameManager; - - lastPositionShared: PointInterface|null = null; - lastRoom: string|null = null; - - private constructor(GameManager: GameManager, name: string, character: string, token: string) { - this.GameManager = GameManager; - this.name = name; - this.character = character; - this.token = token; + private constructor(token: string) { this.socket = SocketIo(`${API_URL}`, { query: { - token: this.token + token: token }, reconnection: false // Reconnection is handled by the application itself }); @@ -158,7 +114,7 @@ export class Connection implements Connection { .then((res) => { return new Promise((resolve, reject) => { - const connection = new Connection(gameManager, name, characterSelected, res.data.token); + const connection = new Connection(res.data.token); connection.onConnectError((error: object) => { console.log('An error occurred while connecting to socket server. Retrying'); @@ -166,8 +122,8 @@ export class Connection implements Connection { }); connection.socket.emit(EventMessage.SET_PLAYER_DETAILS, { - name: connection.name, - character: connection.character + name: name, + character: characterSelected } as SetPlayerDetailsMessage, (id: string) => { connection.userId = id; }); @@ -183,38 +139,28 @@ export class Connection implements Connection { .catch((error) => reject(error)); }, 4000 + Math.floor(Math.random() * 2000) ); }); - - //console.error(err); - //throw err; }); } public closeConnection(): void { this.socket?.close(); - this.lastPositionShared = null; - this.lastRoom = null; } - joinARoom(roomId: string, startX: number, startY: number, direction: string, moving: boolean): Promise { - const point = new Point(startX, startY, direction, moving); - this.lastPositionShared = point; + public joinARoom(roomId: string, startX: number, startY: number, direction: string, moving: boolean): Promise { const promise = new Promise((resolve, reject) => { this.socket.emit(EventMessage.JOIN_ROOM, { roomId, position: {x: startX, y: startY, direction, moving }}, (userPositions: MessageUserPositionInterface[]) => { - //this.GameManager.initUsersPosition(userPositions); resolve(userPositions); }); }) - this.lastRoom = roomId; return promise; } - sharePosition(x : number, y : number, direction : string, moving: boolean) : void{ + public sharePosition(x : number, y : number, direction : string, moving: boolean) : void{ if(!this.socket){ return; } const point = new Point(x, y, direction, moving); - this.lastPositionShared = point; this.socket.emit(EventMessage.USER_POSITION, point); } @@ -242,7 +188,7 @@ export class Connection implements Connection { this.socket.on(EventMessage.CONNECT_ERROR, callback) } - sendWebrtcSignal(signal: unknown, roomId: string, userId? : string|null, receiverId? : string) { + public sendWebrtcSignal(signal: unknown, roomId: string, userId? : string|null, receiverId? : string) { return this.socket.emit(EventMessage.WEBRTC_SIGNAL, { userId: userId ? userId : this.userId, receiverId: receiverId ? receiverId : this.userId, @@ -251,19 +197,15 @@ export class Connection implements Connection { }); } - receiveWebrtcStart(callback: (message: WebRtcStartMessageInterface) => void) { + public receiveWebrtcStart(callback: (message: WebRtcStartMessageInterface) => void) { this.socket.on(EventMessage.WEBRTC_START, callback); } - receiveWebrtcSignal(callback: (message: WebRtcSignalMessageInterface) => void) { + public receiveWebrtcSignal(callback: (message: WebRtcSignalMessageInterface) => void) { return this.socket.on(EventMessage.WEBRTC_SIGNAL, callback); } public onServerDisconnected(callback: (reason: string) => void): void { - /*this.socket.on(EventMessage.CONNECT_ERROR, (error: object) => { - callback(error); - });*/ - this.socket.on('disconnect', (reason: string) => { if (reason === 'io client disconnect') { // The client asks for disconnect, let's not trigger any event. @@ -272,30 +214,10 @@ export class Connection implements Connection { callback(reason); }); - /*this.socket.on(EventMessage.CONNECT_ERROR, (error: object) => { - this.GameManager.switchToDisconnectedScene(); - });*/ + } - /*this.socket.on(EventMessage.RECONNECTING, () => { - console.log('Trying to reconnect'); - }); - - this.socket.on(EventMessage.RECONNECT_ERROR, () => { - console.log('Error while trying to reconnect.'); - }); - - this.socket.on(EventMessage.RECONNECT_FAILED, () => { - console.error('Reconnection failed. Giving up.'); - }); - - this.socket.on(EventMessage.RECONNECT, () => { - console.log('Reconnect event triggered'); - this.connectSocketServer(); - if (this.lastPositionShared === null) { - throw new Error('No last position shared found while reconnecting'); - } - this.GameManager.reconnectToGameScene(this.lastPositionShared); - });*/ + public getUserId(): string|null { + return this.userId; } disconnectMessage(callback: (message: WebRtcDisconnectMessageInterface) => void): void { diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index 071f1780..04cb1bbe 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -1,19 +1,7 @@ import {GameScene} from "./GameScene"; import { - Connection, - GroupCreatedUpdatedMessageInterface, - ListMessageUserPositionInterface, - MessageUserJoined, - MessageUserMovedInterface, - MessageUserPositionInterface, - Point, - PointInterface, StartMapInterface + StartMapInterface } from "../../Connection"; -import {SimplePeer} from "../../WebRtc/SimplePeer"; -import {AddPlayerInterface} from "./AddPlayerInterface"; -import {ReconnectingScene, ReconnectingSceneName} from "../Reconnecting/ReconnectingScene"; -import ScenePlugin = Phaser.Scenes.ScenePlugin; -import {Scene} from "phaser"; import Axios from "axios"; import {API_URL} from "../../Enum/EnvironmentVariable"; @@ -24,11 +12,6 @@ export interface HasMovedEvent { y: number; } -export interface MapObject { - key: string, - url: string -} - export class GameManager { private playerName: string; private characterUserSelected: string; diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 67e953f2..8763f913 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -647,7 +647,7 @@ export class GameScene extends Phaser.Scene { * Put all the players on the map on map load. */ private doInitUsersPosition(usersPosition: MessageUserPositionInterface[]): void { - const currentPlayerId = this.connection.userId; + const currentPlayerId = this.connection.getUserId(); // clean map this.MapPlayersByKey.forEach((player: RemotePlayer) => { @@ -740,7 +740,9 @@ export class GameScene extends Phaser.Scene { private doUpdatePlayerPosition(message: MessageUserMovedInterface): void { const player : RemotePlayer | undefined = this.MapPlayersByKey.get(message.userId); if (player === undefined) { - throw new Error('Cannot find player with ID "' + message.userId +'"'); + //throw new Error('Cannot find player with ID "' + message.userId +'"'); + console.error('Cannot update position of player with ID "' + message.userId +'": player not found'); + return; } // We do not update the player position directly (because it is sent only every 200ms). From 74af7d52c348563bca05e2401c185244e8d9de14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 22 Jun 2020 22:55:28 +0200 Subject: [PATCH 15/15] Improving error handling in MediaManager --- front/src/WebRtc/MediaManager.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index b70ea9de..03736e6e 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -16,9 +16,9 @@ export class MediaManager { audio: true, video: videoConstraint }; - updatedLocalStreamCallBack : Function; + updatedLocalStreamCallBack : (media: MediaStream) => void; - constructor(updatedLocalStreamCallBack : Function) { + constructor(updatedLocalStreamCallBack : (media: MediaStream) => void) { this.updatedLocalStreamCallBack = updatedLocalStreamCallBack; this.myCamVideo = this.getElementByIdOrFail('myCamVideo'); @@ -63,7 +63,7 @@ export class MediaManager { this.cinemaClose.style.display = "none"; this.cinema.style.display = "block"; this.constraintsMedia.video = videoConstraint; - this.getCamera().then((stream) => { + this.getCamera().then((stream: MediaStream) => { this.updatedLocalStreamCallBack(stream); }); } @@ -107,8 +107,13 @@ export class MediaManager { } //get camera - getCamera() { + getCamera(): Promise { let promise = null; + + if (navigator.mediaDevices === undefined) { + return Promise.reject(new Error('Unable to access your camera or microphone. Your browser is too old (or you are running a development version of WorkAdventure on Firefox)')); + } + try { promise = navigator.mediaDevices.getUserMedia(this.constraintsMedia) .then((stream: MediaStream) => { @@ -123,11 +128,12 @@ export class MediaManager { return stream; }).catch((err) => { - console.info(`error get media {video: ${this.constraintsMedia.video}},{audio: ${this.constraintsMedia.audio}}`,err); + console.info("error get media ", this.constraintsMedia.video, this.constraintsMedia.audio, err); this.localStream = null; + throw err; }); } catch (e) { - promise = Promise.reject(false); + promise = Promise.reject(e); } return promise; }