From a3ac782f17b5a5d8d59bf5fe452f6874a223db5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 3 Jun 2020 11:18:53 +0200 Subject: [PATCH 1/7] Enabling Typescript strict mode on the front This is very important otherwise, a number of useful checks (like nullable objects not propertly checked) are not performed. See https://dev.to/briwa/how-strict-is-typescript-s-strict-mode-311a --- front/tsconfig.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/front/tsconfig.json b/front/tsconfig.json index c34c1dd2..3d91f3d1 100644 --- a/front/tsconfig.json +++ b/front/tsconfig.json @@ -7,6 +7,9 @@ "module": "CommonJS", "target": "es5", "jsx": "react", - "allowJs": true + "allowJs": true, + "strict": true, /* Enable all strict type-checking options. */ + "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */ } } From a23102450276cf0be031e0456b8fceb238a0f1d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 3 Jun 2020 11:55:31 +0200 Subject: [PATCH 2/7] Fixing strict type checks --- front/src/Connection.ts | 60 ++++++++++++++++------------ front/src/Logger/MessageUI.ts | 8 ++-- front/src/Phaser/Login/LoginScene.ts | 16 ++++---- front/src/WebRtc/MediaManager.ts | 29 +++++++++----- 4 files changed, 66 insertions(+), 47 deletions(-) diff --git a/front/src/Connection.ts b/front/src/Connection.ts index fc8fa1ed..08ffb871 100644 --- a/front/src/Connection.ts +++ b/front/src/Connection.ts @@ -98,10 +98,10 @@ export interface GroupCreatedUpdatedMessageInterface { } export interface ConnectionInterface { - socket: any; - token: string; - name: string; - userId: string; + socket: Socket|null; + token: string|null; + name: string|null; + userId: string|null; createConnection(name: string, characterSelected: string): Promise; @@ -122,15 +122,15 @@ export interface ConnectionInterface { } export class Connection implements ConnectionInterface { - socket: Socket; - token: string; - name: string; // TODO: drop "name" storage here - character: string; - userId: string; + socket: Socket|null = null; + token: string|null = null; + name: string|null = null; // TODO: drop "name" storage here + character: string|null = null; + userId: string|null = null; GameManager: GameManager; - lastPositionShared: PointInterface = null; + lastPositionShared: PointInterface|null = null; lastRoom: string|null = null; constructor(GameManager: GameManager) { @@ -156,6 +156,13 @@ export class Connection implements ConnectionInterface { }); } + private getSocket(): Socket { + if (this.socket === null) { + throw new Error('Socket not initialized while using Connection') + } + return this.socket; + } + /** * * @param character @@ -171,7 +178,7 @@ export class Connection implements ConnectionInterface { this.onUserLeft(); return new Promise((resolve, reject) => { - this.socket.emit(EventMessage.SET_PLAYER_DETAILS, { + this.getSocket().emit(EventMessage.SET_PLAYER_DETAILS, { name: this.name, character: this.character } as SetPlayerDetailsMessage, (id: string) => { @@ -215,7 +222,7 @@ export class Connection implements ConnectionInterface { } joinARoom(roomId: string, startX: number, startY: number, direction: string, moving: boolean): void { - this.socket.emit(EventMessage.JOIN_ROOM, { roomId, position: {x: startX, y: startY, direction, moving }}, (userPositions: MessageUserPositionInterface[]) => { + this.getSocket().emit(EventMessage.JOIN_ROOM, { roomId, position: {x: startX, y: startY, direction, moving }}, (userPositions: MessageUserPositionInterface[]) => { this.GameManager.initUsersPosition(userPositions); }); this.lastRoom = roomId; @@ -227,42 +234,42 @@ export class Connection implements ConnectionInterface { } let point = new Point(x, y, direction, moving); this.lastPositionShared = point; - this.socket.emit(EventMessage.USER_POSITION, point); + this.getSocket().emit(EventMessage.USER_POSITION, point); } private onUserJoins(): void { - this.socket.on(EventMessage.JOIN_ROOM, (message: MessageUserJoined) => { + this.getSocket().on(EventMessage.JOIN_ROOM, (message: MessageUserJoined) => { this.GameManager.onUserJoins(message); }); } private onUserMoved(): void { - this.socket.on(EventMessage.USER_MOVED, (message: MessageUserMovedInterface) => { + this.getSocket().on(EventMessage.USER_MOVED, (message: MessageUserMovedInterface) => { this.GameManager.onUserMoved(message); }); } private onUserLeft(): void { - this.socket.on(EventMessage.USER_LEFT, (userId: string) => { + this.getSocket().on(EventMessage.USER_LEFT, (userId: string) => { this.GameManager.onUserLeft(userId); }); } private groupUpdatedOrCreated(): void { - this.socket.on(EventMessage.GROUP_CREATE_UPDATE, (groupCreateUpdateMessage: GroupCreatedUpdatedMessageInterface) => { + 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); }) } private groupDeleted(): void { - this.socket.on(EventMessage.GROUP_DELETE, (groupId: string) => { + this.getSocket().on(EventMessage.GROUP_DELETE, (groupId: string) => { this.GameManager.deleteGroup(groupId); }) } sendWebrtcSignal(signal: any, roomId: string, userId? : string, receiverId? : string) { - return this.socket.emit(EventMessage.WEBRTC_SIGNAL, { + return this.getSocket().emit(EventMessage.WEBRTC_SIGNAL, { userId: userId ? userId : this.userId, receiverId: receiverId ? receiverId : this.userId, roomId: roomId, @@ -271,31 +278,34 @@ export class Connection implements ConnectionInterface { } receiveWebrtcStart(callback: Function) { - this.socket.on(EventMessage.WEBRTC_START, callback); + this.getSocket().on(EventMessage.WEBRTC_START, callback); } receiveWebrtcSignal(callback: Function) { - return this.socket.on(EventMessage.WEBRTC_SIGNAL, callback); + return this.getSocket().on(EventMessage.WEBRTC_SIGNAL, callback); } private errorMessage(): void { - this.socket.on(EventMessage.MESSAGE_ERROR, (message: string) => { + this.getSocket().on(EventMessage.MESSAGE_ERROR, (message: string) => { console.error(EventMessage.MESSAGE_ERROR, message); }) } private disconnectServer(): void { - this.socket.on(EventMessage.CONNECT_ERROR, () => { + this.getSocket().on(EventMessage.CONNECT_ERROR, () => { this.GameManager.switchToDisconnectedScene(); }); - this.socket.on(EventMessage.RECONNECT, () => { + this.getSocket().on(EventMessage.RECONNECT, () => { this.connectSocketServer(); + if (this.lastPositionShared === null) { + throw new Error('No last position shared found while reconnecting'); + } this.GameManager.reconnectToGameScene(this.lastPositionShared); }); } disconnectMessage(callback: Function): void { - this.socket.on(EventMessage.WEBRTC_DISCONNECT, callback); + this.getSocket().on(EventMessage.WEBRTC_DISCONNECT, callback); } } diff --git a/front/src/Logger/MessageUI.ts b/front/src/Logger/MessageUI.ts index a73c2418..6011fb73 100644 --- a/front/src/Logger/MessageUI.ts +++ b/front/src/Logger/MessageUI.ts @@ -3,18 +3,18 @@ export class MessageUI { static warningMessage(text: string){ this.removeMessage(); let body = document.getElementById("body"); - body.insertAdjacentHTML('afterbegin', ` + body?.insertAdjacentHTML('afterbegin', `
${text}
`); } - static removeMessage(id : string = null) { + static removeMessage(id : string|null = null) { if(!id){ let messages = document.getElementsByClassName("message-info"); for (let i = 0; i < messages.length; i++){ - messages.item(i).remove(); + messages.item(i)?.remove(); } return; } @@ -24,4 +24,4 @@ export class MessageUI { } previousElement.remove(); } -} \ No newline at end of file +} diff --git a/front/src/Phaser/Login/LoginScene.ts b/front/src/Phaser/Login/LoginScene.ts index da684da2..f00b3879 100644 --- a/front/src/Phaser/Login/LoginScene.ts +++ b/front/src/Phaser/Login/LoginScene.ts @@ -16,12 +16,12 @@ enum LoginTextures { } export class LoginScene extends Phaser.Scene { - private nameInput: TextInput; - private textField: TextField; - private infoTextField: TextField; - private pressReturnField: TextField; - private logo: Image; - private name: string; + private nameInput: TextInput|null = null; + private textField: TextField|null = null; + private infoTextField: TextField|null = null; + private pressReturnField: TextField|null = null; + private logo: Image|null = null; + private name: string = ''; constructor() { super({ @@ -82,9 +82,9 @@ export class LoginScene extends Phaser.Scene { update(time: number, delta: number): void { if (this.name == '') { - this.pressReturnField.setVisible(false); + this.pressReturnField?.setVisible(false); } else { - this.pressReturnField.setVisible(!!(Math.floor(time / 500) % 2)); + this.pressReturnField?.setVisible(!!(Math.floor(time / 500) % 2)); } } diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index 1e183559..a80cb6af 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -4,26 +4,25 @@ const videoConstraint: {width : any, height: any, facingMode : string} = { facingMode: "user" }; export class MediaManager { - localStream: MediaStream; + localStream: MediaStream|null = null; remoteVideo: Array = new Array(); - myCamVideo: any; + myCamVideo: HTMLVideoElement; cinemaClose: any = null; cinema: any = null; microphoneClose: any = null; microphone: any = null; - webrtcInAudio: any; + webrtcInAudio: HTMLAudioElement; constraintsMedia : {audio : any, video : any} = { audio: true, video: videoConstraint }; - getCameraPromise : Promise = null; updatedLocalStreamCallBack : Function; constructor(updatedLocalStreamCallBack : Function) { this.updatedLocalStreamCallBack = updatedLocalStreamCallBack; - this.myCamVideo = document.getElementById('myCamVideo'); - this.webrtcInAudio = document.getElementById('audio-webrtc-in'); + this.myCamVideo = this.getElementByIdOrFail('myCamVideo'); + this.webrtcInAudio = this.getElementByIdOrFail('audio-webrtc-in'); this.webrtcInAudio.volume = 0.2; this.microphoneClose = document.getElementById('microphone-close'); @@ -56,7 +55,7 @@ export class MediaManager { } activeVisio(){ - let webRtc = document.getElementById('webRtc'); + let webRtc = this.getElementByIdOrFail('webRtc'); webRtc.classList.add('active'); } @@ -130,7 +129,7 @@ export class MediaManager { } catch (e) { promise = Promise.reject(false); } - return this.getCameraPromise = promise; + return promise; } /** @@ -139,7 +138,7 @@ export class MediaManager { */ addActiveVideo(userId : string, userName: string = ""){ this.webrtcInAudio.play(); - let elementRemoteVideo = document.getElementById("activeCam"); + let elementRemoteVideo = this.getElementByIdOrFail("activeCam"); userName = userName.toUpperCase(); let color = this.getColorByString(userName); elementRemoteVideo.insertAdjacentHTML('beforeend', ` @@ -247,4 +246,14 @@ export class MediaManager { } return color; } -} \ No newline at end of file + + private getElementByIdOrFail(id: string): T { + let elem = document.getElementById("activeCam"); + if (elem === null) { + throw new Error("Cannot find HTML element with id '"+id+"'"); + } + // FIXME: does not check the type of the returned type + return elem as T; + } + +} From 111bfcfe8ca3bc2bf2a69de6855b1f422a7bcfeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 3 Jun 2020 22:32:43 +0200 Subject: [PATCH 3/7] More strict typecheck fixes --- front/src/Connection.ts | 4 +-- front/src/Phaser/Game/GameManager.ts | 4 +-- front/src/WebRtc/SimplePeer.ts | 43 ++++++++++++++++------------ 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/front/src/Connection.ts b/front/src/Connection.ts index 08ffb871..cf7a3856 100644 --- a/front/src/Connection.ts +++ b/front/src/Connection.ts @@ -112,7 +112,7 @@ export interface ConnectionInterface { sharePosition(x: number, y: number, direction: string, moving: boolean): void; /*webrtc*/ - sendWebrtcSignal(signal: any, roomId: string, userId?: string, receiverId?: string): void; + sendWebrtcSignal(signal: any, roomId: string, userId?: string|null, receiverId?: string): void; receiveWebrtcSignal(callBack: Function): void; @@ -268,7 +268,7 @@ export class Connection implements ConnectionInterface { }) } - sendWebrtcSignal(signal: any, roomId: string, userId? : string, receiverId? : string) { + sendWebrtcSignal(signal: any, roomId: string, userId? : string|null, receiverId? : string) { return this.getSocket().emit(EventMessage.WEBRTC_SIGNAL, { userId: userId ? userId : this.userId, receiverId: receiverId ? receiverId : this.userId, diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index 7f895048..39ed4bd7 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -9,7 +9,7 @@ import { Point, PointInterface } from "../../Connection"; -import {SimplePeerInterface, SimplePeer} from "../../WebRtc/SimplePeer"; +import {SimplePeer} from "../../WebRtc/SimplePeer"; import {AddPlayerInterface} from "./AddPlayerInterface"; import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene"; @@ -35,7 +35,7 @@ export class GameManager { private ConnectionInstance: Connection; private currentGameScene: GameScene; private playerName: string; - SimplePeer : SimplePeerInterface; + SimplePeer : SimplePeer; private characterUserSelected: string; constructor() { diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 3693924d..68925e9c 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -1,21 +1,21 @@ import {ConnectionInterface} from "../Connection"; import {MediaManager} from "./MediaManager"; -let Peer = require('simple-peer'); +import * as SimplePeerNamespace from "simple-peer"; +let Peer: SimplePeerNamespace.SimplePeer = require('simple-peer'); -class UserSimplePear{ +class UserSimplePeer{ userId: string; name?: string; initiator?: boolean; } -export class SimplePeerInterface {} -export class SimplePeer implements SimplePeerInterface{ +export class SimplePeer { private Connection: ConnectionInterface; private WebRtcRoomId: string; - private Users: Array = new Array(); + private Users: Array = new Array(); private MediaManager: MediaManager; - private PeerConnectionArray: Map = new Map(); + private PeerConnectionArray: Map = new Map(); constructor(Connection: ConnectionInterface, WebRtcRoomId: string = "test-webrtc") { this.Connection = Connection; @@ -66,7 +66,7 @@ export class SimplePeer implements SimplePeerInterface{ * server has two person connected, start the meet */ private startWebRtc() { - this.Users.forEach((user: UserSimplePear) => { + this.Users.forEach((user: UserSimplePeer) => { //if it's not an initiator, peer connection will be created when gamer will receive offer signal if(!user.initiator){ return; @@ -78,14 +78,14 @@ export class SimplePeer implements SimplePeerInterface{ /** * create peer connection to bind users */ - private createPeerConnection(user : UserSimplePear) { + private createPeerConnection(user : UserSimplePeer) { if(this.PeerConnectionArray.has(user.userId)) { return; } let name = user.name; if(!name){ - let userSearch = this.Users.find((userSearch: UserSimplePear) => userSearch.userId === user.userId); + let userSearch = this.Users.find((userSearch: UserSimplePeer) => userSearch.userId === user.userId); if(userSearch) { name = userSearch.name; } @@ -112,11 +112,11 @@ export class SimplePeer implements SimplePeerInterface{ this.PeerConnectionArray.set(user.userId, peer); //start listen signal for the peer connection - this.PeerConnectionArray.get(user.userId).on('signal', (data: any) => { + peer.on('signal', (data: any) => { this.sendWebrtcSignal(data, user.userId); }); - this.PeerConnectionArray.get(user.userId).on('stream', (stream: MediaStream) => { + peer.on('stream', (stream: MediaStream) => { let videoActive = false; let microphoneActive = false; stream.getTracks().forEach((track : MediaStreamTrack) => { @@ -141,23 +141,23 @@ export class SimplePeer implements SimplePeerInterface{ this.stream(user.userId, stream); }); - /*this.PeerConnectionArray.get(user.userId).on('track', (track: MediaStreamTrack, stream: MediaStream) => { + /*peer.on('track', (track: MediaStreamTrack, stream: MediaStream) => { this.stream(user.userId, stream); });*/ - this.PeerConnectionArray.get(user.userId).on('close', () => { + peer.on('close', () => { this.closeConnection(user.userId); }); - this.PeerConnectionArray.get(user.userId).on('error', (err: any) => { + peer.on('error', (err: any) => { console.error(`error => ${user.userId} => ${err.code}`, err); }); - this.PeerConnectionArray.get(user.userId).on('connect', () => { + peer.on('connect', () => { console.info(`connect => ${user.userId}`); }); - this.PeerConnectionArray.get(user.userId).on('data', (chunk: Buffer) => { + peer.on('data', (chunk: Buffer) => { let data = JSON.parse(chunk.toString('utf8')); if(data.type === "stream"){ this.stream(user.userId, data.stream); @@ -174,7 +174,7 @@ export class SimplePeer implements SimplePeerInterface{ return; } // @ts-ignore - this.PeerConnectionArray.get(userId).destroy(); + this.PeerConnectionArray.get(userId)?.destroy(); this.PeerConnectionArray.delete(userId) } catch (err) { console.error("closeConnection", err) @@ -200,7 +200,12 @@ export class SimplePeer implements SimplePeerInterface{ if(data.signal.type === "offer"){ this.createPeerConnection(data); } - this.PeerConnectionArray.get(data.userId).signal(data.signal); + let peer = this.PeerConnectionArray.get(data.userId); + if (peer !== undefined) { + peer.signal(data.signal); + } else { + console.error('Could not find peer whose ID is "'+data.userId+'" in PeerConnectionArray'); + } } catch (e) { console.error(`receiveWebrtcSignal => ${data.userId}`, e); } @@ -247,7 +252,7 @@ export class SimplePeer implements SimplePeerInterface{ } updatedLocalStream(){ - this.Users.forEach((user: UserSimplePear) => { + this.Users.forEach((user: UserSimplePeer) => { this.addMedia(user.userId); }) } From 7292bc3cab1bc4dc1059a66374a0f2ee905492c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 3 Jun 2020 22:57:00 +0200 Subject: [PATCH 4/7] More strict fixes --- front/src/WebRtc/MediaManager.ts | 2 +- front/src/WebRtc/SimplePeer.ts | 17 +++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index a80cb6af..bb3f0cb9 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -248,7 +248,7 @@ export class MediaManager { } private getElementByIdOrFail(id: string): T { - let elem = document.getElementById("activeCam"); + let 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 68925e9c..2af0be27 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -232,20 +232,25 @@ export class SimplePeer { private addMedia (userId : any = null) { try { let transceiver : any = null; - if(!this.MediaManager.localStream){ + let localStream: MediaStream|null = this.MediaManager.localStream; + let peer = this.PeerConnectionArray.get(userId); + if(localStream === null) { //send fake signal - if(!this.PeerConnectionArray.has(userId)){ + if(peer === undefined){ return; } - this.PeerConnectionArray.get(userId).write(new Buffer(JSON.stringify({ + peer.write(new Buffer(JSON.stringify({ type: "stream", stream: null }))); return; } - this.MediaManager.localStream.getTracks().forEach( - transceiver = (track: MediaStreamTrack) => this.PeerConnectionArray.get(userId).addTrack(track, this.MediaManager.localStream) - ) + if (peer === undefined) { + throw new Error('While adding media, cannot find user with ID '+userId); + } + for (const track of localStream.getTracks()) { + peer.addTrack(track, localStream); + } }catch (e) { console.error(`addMedia => addMedia => ${userId}`, e); } From 082a11b0cd7cfc27afa713def9df5f9fef2a5a93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 3 Jun 2020 23:17:52 +0200 Subject: [PATCH 5/7] Allowing ill defined initializers (because of the way Phaser 3 is designed) --- front/src/Phaser/Game/GameScene.ts | 18 +++++++++--------- front/tsconfig.json | 13 +++++++++++-- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 5cf89c16..126e2369 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -30,15 +30,15 @@ export class GameScene extends Phaser.Scene { CurrentPlayer: CurrentGamerInterface; MapPlayers : Phaser.Physics.Arcade.Group; MapPlayersByKey : Map = new Map(); - Map: Phaser.Tilemaps.Tilemap; + Map: Phaser.Tilemaps.Tilemap|null = null; Layers : Array; Objects : Array; - map: ITiledMap; + mapFile: ITiledMap|null; groups: Map; startX = 704;// 22 case startY = 32; // 1 case circleTexture: CanvasTexture; - initPosition: PositionInterface; + initPosition: PositionInterface|null = null; private playersPositionInterpolator = new PlayersPositionInterpolator(); MapKey: string; @@ -107,9 +107,9 @@ export class GameScene extends Phaser.Scene { private onMapLoad(data: any): void { // Triggered when the map is loaded // Load tiles attached to the map recursively - this.map = data.data; + this.mapFile = data.data; let url = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf('/')); - this.map.tilesets.forEach((tileset) => { + this.mapFile.tilesets.forEach((tileset) => { if (typeof tileset.name === 'undefined' || typeof tileset.image === 'undefined') { console.warn("Don't know how to handle tileset ", tileset) return; @@ -128,7 +128,7 @@ export class GameScene extends Phaser.Scene { create(): void { //initalise map this.Map = this.add.tilemap(this.MapKey); - this.map.tilesets.forEach((tileset: ITiledTileSet) => { + this.mapFile.tilesets.forEach((tileset: ITiledTileSet) => { this.Terrains.push(this.Map.addTilesetImage(tileset.name, tileset.name)); }); @@ -138,12 +138,12 @@ export class GameScene extends Phaser.Scene { //add layer on map this.Layers = new Array(); let depth = -2; - this.map.layers.forEach((layer : ITiledMapLayer) => { + this.mapFile.layers.forEach((layer : ITiledMapLayer) => { if (layer.type === 'tilelayer') { this.addLayer(this.Map.createStaticLayer(layer.name, this.Terrains, 0, 0).setDepth(depth)); } if (layer.type === 'tilelayer' && this.getExitSceneUrl(layer) !== undefined) { - this.loadNextGame(layer, this.map.width, this.map.tilewidth, this.map.tileheight); + this.loadNextGame(layer, this.mapFile.width, this.mapFile.tilewidth, this.mapFile.tileheight); } if (layer.type === 'tilelayer' && layer.name === "start") { let startPosition = this.startUser(layer); @@ -265,7 +265,7 @@ export class GameScene extends Phaser.Scene { * @param layer */ private startUser(layer: ITiledMapLayer): PositionInterface { - if (this.initPosition !== undefined) { + if (this.initPosition !== null) { this.startX = this.initPosition.x; this.startY = this.initPosition.y; return { diff --git a/front/tsconfig.json b/front/tsconfig.json index 3d91f3d1..84882e74 100644 --- a/front/tsconfig.json +++ b/front/tsconfig.json @@ -8,8 +8,17 @@ "target": "es5", "jsx": "react", "allowJs": true, - "strict": true, /* Enable all strict type-checking options. */ + + "strict": false, /* Enable all strict type-checking options. */ + "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + "strictNullChecks": true, /* Enable strict null checks. */ + "strictFunctionTypes": true, /* Enable strict checking of function types. */ + "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + "strictPropertyInitialization": false, /* Enable strict checking of property initialization in classes. */ + "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */ + "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */ } } From 6f69a62d4d18601a95decec3d7e93fc381099c9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 4 Jun 2020 18:11:07 +0200 Subject: [PATCH 6/7] More strict fixes --- ...ayableCaracter.ts => PlayableCharacter.ts} | 10 +++-- front/src/Phaser/Entity/SpeechBubble.ts | 11 ++---- front/src/Phaser/Game/GameManager.ts | 3 +- front/src/Phaser/Game/GameScene.ts | 27 +++++++------ front/src/Phaser/Login/LoginScene.ts | 2 +- .../src/Phaser/Login/SelectCharacterScene.ts | 2 +- front/src/Phaser/Player/Player.ts | 8 ++-- .../Phaser/Reconnecting/ReconnectingScene.ts | 2 +- .../src/Phaser/UserInput/UserInputManager.ts | 38 +++++++++---------- 9 files changed, 52 insertions(+), 51 deletions(-) rename front/src/Phaser/Entity/{PlayableCaracter.ts => PlayableCharacter.ts} (94%) diff --git a/front/src/Phaser/Entity/PlayableCaracter.ts b/front/src/Phaser/Entity/PlayableCharacter.ts similarity index 94% rename from front/src/Phaser/Entity/PlayableCaracter.ts rename to front/src/Phaser/Entity/PlayableCharacter.ts index e1b774ef..99b4d631 100644 --- a/front/src/Phaser/Entity/PlayableCaracter.ts +++ b/front/src/Phaser/Entity/PlayableCharacter.ts @@ -24,8 +24,8 @@ export const PLAYER_RESOURCES: Array = [ {name: "Female8", img: "resources/characters/pipoya/Female 16-4.png"/*, x: 128, y: 128*/} ]; -export class PlayableCaracter extends Phaser.Physics.Arcade.Sprite { - private bubble: SpeechBubble; +export class PlayableCharacter extends Phaser.Physics.Arcade.Sprite { + private bubble: SpeechBubble|null = null; private readonly playerName: BitmapText; public PlayerValue: string; public PlayerTexture: string; @@ -91,8 +91,10 @@ export class PlayableCaracter extends Phaser.Physics.Arcade.Sprite { this.bubble = new SpeechBubble(this.scene, this, text) //todo make the bubble destroy on player movement? setTimeout(() => { - this.bubble.destroy(); - this.bubble = null; + if (this.bubble !== null) { + this.bubble.destroy(); + this.bubble = null; + } }, 3000) } diff --git a/front/src/Phaser/Entity/SpeechBubble.ts b/front/src/Phaser/Entity/SpeechBubble.ts index 51aaa169..6a696b66 100644 --- a/front/src/Phaser/Entity/SpeechBubble.ts +++ b/front/src/Phaser/Entity/SpeechBubble.ts @@ -1,5 +1,5 @@ import Scene = Phaser.Scene; -import {PlayableCaracter} from "./PlayableCaracter"; +import {PlayableCharacter} from "./PlayableCharacter"; export class SpeechBubble { private bubble: Phaser.GameObjects.Graphics; @@ -11,7 +11,7 @@ export class SpeechBubble { * @param player * @param text */ - constructor(scene: Scene, player: PlayableCaracter, text: string = "") { + constructor(scene: Scene, player: PlayableCharacter, text: string = "") { let bubbleHeight = 50; let bubblePadding = 10; @@ -76,13 +76,10 @@ export class SpeechBubble { this.content.setPosition(this.bubble.x + (bubbleWidth / 2) - (bounds.width / 2), this.bubble.y + (bubbleHeight / 2) - (bounds.height / 2)); } } - + destroy(): void { this.bubble.setVisible(false) //todo find a better way this.bubble.destroy(); - this.bubble = null; this.content.destroy(); - this.content = null; } - -} \ No newline at end of file +} diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index 39ed4bd7..c4c4c981 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -155,9 +155,8 @@ export class GameManager { let sceneKey = GameScene.getMapKeyByUrl(mapUrl); let gameIndex = scene.getIndex(sceneKey); - let game : Phaser.Scene = null; if(gameIndex === -1){ - game = GameScene.createFromUrl(mapUrl, instance); + let 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 126e2369..27052e77 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -7,7 +7,7 @@ import { import {CurrentGamerInterface, GamerInterface, 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/PlayableCaracter"; +import {PLAYER_RESOURCES} from "../Entity/PlayableCharacter"; import Texture = Phaser.Textures.Texture; import Sprite = Phaser.GameObjects.Sprite; import CanvasTexture = Phaser.Textures.CanvasTexture; @@ -30,7 +30,7 @@ export class GameScene extends Phaser.Scene { CurrentPlayer: CurrentGamerInterface; MapPlayers : Phaser.Physics.Arcade.Group; MapPlayersByKey : Map = new Map(); - Map: Phaser.Tilemaps.Tilemap|null = null; + Map: Phaser.Tilemaps.Tilemap; Layers : Array; Objects : Array; mapFile: ITiledMap|null; @@ -38,7 +38,7 @@ export class GameScene extends Phaser.Scene { startX = 704;// 22 case startY = 32; // 1 case circleTexture: CanvasTexture; - initPosition: PositionInterface|null = null; + private initPosition: PositionInterface|null = null; private playersPositionInterpolator = new PlayersPositionInterpolator(); MapKey: string; @@ -121,7 +121,9 @@ export class GameScene extends Phaser.Scene { //hook initialisation init(initData : GameSceneInitInterface) { - this.initPosition = initData.initPosition; + if (initData.initPosition !== undefined) { + this.initPosition = initData.initPosition; + } } //hook create scene @@ -338,7 +340,7 @@ export class GameScene extends Phaser.Scene { //initialise player //TODO create animation moving between exit and start this.CurrentPlayer = new Player( - null, // The current player is not has no id (because the id can change if connection is lost and we should check that id using the GameManager. + null, // The current player has no id (because the id can change if connection is lost and we should check that id using the GameManager.) this, this.startX, this.startY, @@ -503,9 +505,10 @@ export class GameScene extends Phaser.Scene { let player = this.MapPlayersByKey.get(userId); if (player === undefined) { console.error('Cannot find user with id ', userId); + } else { + player.destroy(); + this.MapPlayers.remove(player); } - player.destroy(); - this.MapPlayers.remove(player); this.MapPlayersByKey.delete(userId); this.playersPositionInterpolator.removePlayer(userId); } @@ -525,8 +528,9 @@ export class GameScene extends Phaser.Scene { shareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface) { let groupId = groupPositionMessage.groupId; - if (this.groups.has(groupId)) { - this.groups.get(groupId).setPosition(Math.round(groupPositionMessage.position.x), Math.round(groupPositionMessage.position.y)); + let 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( @@ -541,10 +545,11 @@ export class GameScene extends Phaser.Scene { } deleteGroup(groupId: string): void { - if(!this.groups.get(groupId)){ + let group = this.groups.get(groupId); + if(!group){ return; } - this.groups.get(groupId).destroy(); + group.destroy(); this.groups.delete(groupId); } diff --git a/front/src/Phaser/Login/LoginScene.ts b/front/src/Phaser/Login/LoginScene.ts index f00b3879..606b4077 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/PlayableCaracter"; +import {PLAYER_RESOURCES} from "../Entity/PlayableCharacter"; import {cypressAsserter} from "../../Cypress/CypressAsserter"; import {SelectCharacterSceneInitDataInterface, SelectCharacterSceneName} from "./SelectCharacterScene"; diff --git a/front/src/Phaser/Login/SelectCharacterScene.ts b/front/src/Phaser/Login/SelectCharacterScene.ts index 8a52afcd..f91a810c 100644 --- a/front/src/Phaser/Login/SelectCharacterScene.ts +++ b/front/src/Phaser/Login/SelectCharacterScene.ts @@ -3,7 +3,7 @@ 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/PlayableCaracter"; +import {PLAYER_RESOURCES} from "../Entity/PlayableCharacter"; //todo: put this constants in a dedicated file export const SelectCharacterSceneName = "SelectCharacterScene"; diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index ae40efa5..2e88bcec 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -2,16 +2,16 @@ import {PlayerAnimationNames} from "./Animation"; import {GameScene, Textures} from "../Game/GameScene"; import {MessageUserPositionInterface, PointInterface} from "../../Connection"; import {ActiveEventList, UserInputEvent, UserInputManager} from "../UserInput/UserInputManager"; -import {PlayableCaracter} from "../Entity/PlayableCaracter"; +import {PlayableCharacter} from "../Entity/PlayableCharacter"; export const hasMovedEventName = "hasMoved"; -export interface CurrentGamerInterface extends PlayableCaracter{ +export interface CurrentGamerInterface extends PlayableCharacter{ moveUser(delta: number) : void; say(text : string) : void; } -export interface GamerInterface extends PlayableCaracter{ +export interface GamerInterface extends PlayableCharacter{ userId : string; updatePosition(position: PointInterface): void; say(text : string) : void; @@ -27,7 +27,7 @@ interface AnimationData { } -export class Player extends PlayableCaracter implements CurrentGamerInterface, GamerInterface { +export class Player extends PlayableCharacter implements CurrentGamerInterface, GamerInterface { userId: string; userInputManager: UserInputManager; previousDirection: string; diff --git a/front/src/Phaser/Reconnecting/ReconnectingScene.ts b/front/src/Phaser/Reconnecting/ReconnectingScene.ts index 273820b7..45651ed0 100644 --- a/front/src/Phaser/Reconnecting/ReconnectingScene.ts +++ b/front/src/Phaser/Reconnecting/ReconnectingScene.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/PlayableCaracter"; +import {PLAYER_RESOURCES} from "../Entity/PlayableCharacter"; import {cypressAsserter} from "../../Cypress/CypressAsserter"; import Sprite = Phaser.GameObjects.Sprite; diff --git a/front/src/Phaser/UserInput/UserInputManager.ts b/front/src/Phaser/UserInput/UserInputManager.ts index 9c4ca660..fec4f58d 100644 --- a/front/src/Phaser/UserInput/UserInputManager.ts +++ b/front/src/Phaser/UserInput/UserInputManager.ts @@ -2,7 +2,6 @@ import Map = Phaser.Structs.Map; import {GameScene} from "../Game/GameScene"; interface UserInputManagerDatum { - keyCode: number; keyInstance: Phaser.Input.Keyboard.Key; event: UserInputEvent } @@ -33,27 +32,26 @@ export class ActiveEventList { //this class is responsible for catching user inputs and listing all active user actions at every game tick events. export class UserInputManager { - private KeysCode: UserInputManagerDatum[] = [ - {keyCode: Phaser.Input.Keyboard.KeyCodes.Z, event: UserInputEvent.MoveUp, keyInstance: null}, - {keyCode: Phaser.Input.Keyboard.KeyCodes.Q, event: UserInputEvent.MoveLeft, keyInstance: null}, - {keyCode: Phaser.Input.Keyboard.KeyCodes.S, event: UserInputEvent.MoveDown, keyInstance: null}, - {keyCode: Phaser.Input.Keyboard.KeyCodes.D, event: UserInputEvent.MoveRight, keyInstance: null}, - - {keyCode: Phaser.Input.Keyboard.KeyCodes.UP, event: UserInputEvent.MoveUp, keyInstance: null}, - {keyCode: Phaser.Input.Keyboard.KeyCodes.LEFT, event: UserInputEvent.MoveLeft, keyInstance: null}, - {keyCode: Phaser.Input.Keyboard.KeyCodes.DOWN, event: UserInputEvent.MoveDown, keyInstance: null}, - {keyCode: Phaser.Input.Keyboard.KeyCodes.RIGHT, event: UserInputEvent.MoveRight, keyInstance: null}, - - {keyCode: Phaser.Input.Keyboard.KeyCodes.SHIFT, event: UserInputEvent.SpeedUp, keyInstance: null}, - - {keyCode: Phaser.Input.Keyboard.KeyCodes.E, event: UserInputEvent.Interact, keyInstance: null}, - {keyCode: Phaser.Input.Keyboard.KeyCodes.F, event: UserInputEvent.Shout, keyInstance: null}, - ]; + private KeysCode: UserInputManagerDatum[]; constructor(Scene : GameScene) { - this.KeysCode.forEach(d => { - d.keyInstance = Scene.input.keyboard.addKey(d.keyCode); - }); + + this.KeysCode = [ + {event: UserInputEvent.MoveUp, keyInstance: Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Z) }, + {event: UserInputEvent.MoveLeft, keyInstance: Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Q) }, + {event: UserInputEvent.MoveDown, keyInstance: Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.S) }, + {event: UserInputEvent.MoveRight, keyInstance: Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.D) }, + + {event: UserInputEvent.MoveUp, keyInstance: Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.UP) }, + {event: UserInputEvent.MoveLeft, keyInstance: Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.LEFT) }, + {event: UserInputEvent.MoveDown, keyInstance: Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.DOWN) }, + {event: UserInputEvent.MoveRight, keyInstance: Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.RIGHT) }, + + {event: UserInputEvent.SpeedUp, keyInstance: Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SHIFT) }, + + {event: UserInputEvent.Interact, keyInstance: Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.E) }, + {event: UserInputEvent.Shout, keyInstance: Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.F) }, + ]; } getEventListForGameTick(): ActiveEventList { From b82b13e35195c56fd527938eaf5fc052ca5c934a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 4 Jun 2020 18:54:34 +0200 Subject: [PATCH 7/7] Finalizing strict mode fixes --- .../{PlayableCharacter.ts => Character.ts} | 79 ++++++++++++++- front/src/Phaser/Entity/RemotePlayer.ts | 38 ++++++++ front/src/Phaser/Entity/SpeechBubble.ts | 4 +- front/src/Phaser/Game/GameManager.ts | 2 +- front/src/Phaser/Game/GameScene.ts | 25 ++--- front/src/Phaser/Login/LoginScene.ts | 2 +- .../src/Phaser/Login/SelectCharacterScene.ts | 2 +- front/src/Phaser/Player/Player.ts | 95 +------------------ .../Phaser/Reconnecting/ReconnectingScene.ts | 2 +- 9 files changed, 139 insertions(+), 110 deletions(-) rename front/src/Phaser/Entity/{PlayableCharacter.ts => Character.ts} (64%) create mode 100644 front/src/Phaser/Entity/RemotePlayer.ts diff --git a/front/src/Phaser/Entity/PlayableCharacter.ts b/front/src/Phaser/Entity/Character.ts similarity index 64% rename from front/src/Phaser/Entity/PlayableCharacter.ts rename to front/src/Phaser/Entity/Character.ts index 99b4d631..ec0167eb 100644 --- a/front/src/Phaser/Entity/PlayableCharacter.ts +++ b/front/src/Phaser/Entity/Character.ts @@ -24,14 +24,31 @@ export const PLAYER_RESOURCES: Array = [ {name: "Female8", img: "resources/characters/pipoya/Female 16-4.png"/*, x: 128, y: 128*/} ]; -export class PlayableCharacter extends Phaser.Physics.Arcade.Sprite { +interface AnimationData { + key: string; + frameRate: number; + repeat: number; + frameModel: string; //todo use an enum + frameStart: number; + frameEnd: number; +} + +export abstract class Character extends Phaser.Physics.Arcade.Sprite { private bubble: SpeechBubble|null = null; private readonly playerName: BitmapText; public PlayerValue: string; public PlayerTexture: string; - constructor(scene: Phaser.Scene, x: number, y: number, texture: string, name: string, frame?: string | number) { + constructor(scene: Phaser.Scene, + x: number, + y: number, + texture: string, + name: string, + direction: string, + moving: boolean, + frame?: string | number + ) { super(scene, x, y, texture, frame); this.PlayerValue = name; @@ -51,6 +68,64 @@ export class PlayableCharacter extends Phaser.Physics.Arcade.Sprite { this.setDepth(-1); this.scene.events.on('postupdate', this.postupdate.bind(this)); + + this.initAnimation(); + this.playAnimation(direction, moving); + } + + private initAnimation(): void { + this.getPlayerAnimations(this.PlayerTexture).forEach(d => { + this.scene.anims.create({ + key: d.key, + frames: this.scene.anims.generateFrameNumbers(d.frameModel, {start: d.frameStart, end: d.frameEnd}), + frameRate: d.frameRate, + repeat: d.repeat + }); + }) + } + + private getPlayerAnimations(name: string): AnimationData[] { + return [{ + key: `${name}-${PlayerAnimationNames.WalkDown}`, + frameModel: name, + frameStart: 0, + frameEnd: 2, + frameRate: 10, + repeat: -1 + }, { + key: `${name}-${PlayerAnimationNames.WalkLeft}`, + frameModel: name, + frameStart: 3, + frameEnd: 5, + frameRate: 10, + repeat: -1 + }, { + key: `${name}-${PlayerAnimationNames.WalkRight}`, + frameModel: name, + frameStart: 6, + frameEnd: 8, + frameRate: 10, + repeat: -1 + }, { + key: `${name}-${PlayerAnimationNames.WalkUp}`, + frameModel: name, + frameStart: 9, + frameEnd: 11, + frameRate: 10, + repeat: -1 + }]; + } + + protected playAnimation(direction : string, moving: boolean): void { + if (moving && (!this.anims.currentAnim || this.anims.currentAnim.key !== direction)) { + this.play(this.PlayerTexture+'-'+direction, true); + } else if (!moving) { + /*if (this.anims.currentAnim) { + this.anims.stop(); + }*/ + this.play(this.PlayerTexture+'-'+direction, true); + this.stop(); + } } move(x: number, y: number) { diff --git a/front/src/Phaser/Entity/RemotePlayer.ts b/front/src/Phaser/Entity/RemotePlayer.ts new file mode 100644 index 00000000..36911bb6 --- /dev/null +++ b/front/src/Phaser/Entity/RemotePlayer.ts @@ -0,0 +1,38 @@ +import {GameScene} from "../Game/GameScene"; +import {PointInterface} from "../../Connection"; +import {Character} from "../Entity/Character"; + +/** + * Class representing the sprite of a remote player (a player that plays on another computer) + */ +export class RemotePlayer extends Character { + userId: string; + previousDirection: string; + wasMoving: boolean; + + constructor( + userId: string, + Scene: GameScene, + x: number, + y: number, + name: string, + PlayerTexture: string, + direction: string, + moving: boolean + ) { + super(Scene, x, y, PlayerTexture, name, direction, moving, 1); + + //set data + this.userId = userId; + + //the current player model should be push away by other players to prevent conflict + //this.setImmovable(false); + } + + updatePosition(position: PointInterface): void { + this.playAnimation(position.direction, position.moving); + this.setX(position.x); + this.setY(position.y); + this.setDepth(position.y); + } +} diff --git a/front/src/Phaser/Entity/SpeechBubble.ts b/front/src/Phaser/Entity/SpeechBubble.ts index 6a696b66..f2385290 100644 --- a/front/src/Phaser/Entity/SpeechBubble.ts +++ b/front/src/Phaser/Entity/SpeechBubble.ts @@ -1,5 +1,5 @@ import Scene = Phaser.Scene; -import {PlayableCharacter} from "./PlayableCharacter"; +import {Character} from "./Character"; export class SpeechBubble { private bubble: Phaser.GameObjects.Graphics; @@ -11,7 +11,7 @@ export class SpeechBubble { * @param player * @param text */ - constructor(scene: Scene, player: PlayableCharacter, text: string = "") { + constructor(scene: Scene, player: Character, text: string = "") { let bubbleHeight = 50; let bubblePadding = 10; diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index c4c4c981..7eef49b4 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -139,7 +139,7 @@ export class GameManager { return this.playerName; } - getPlayerId(): string { + getPlayerId(): string|null { return this.ConnectionInstance.userId; } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 27052e77..934831e1 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -4,10 +4,10 @@ import { MessageUserMovedInterface, MessageUserPositionInterface, PointInterface, PositionInterface } from "../../Connection"; -import {CurrentGamerInterface, GamerInterface, hasMovedEventName, Player} from "../Player/Player"; +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/PlayableCharacter"; +import {PLAYER_RESOURCES} from "../Entity/Character"; import Texture = Phaser.Textures.Texture; import Sprite = Phaser.GameObjects.Sprite; import CanvasTexture = Phaser.Textures.CanvasTexture; @@ -15,6 +15,7 @@ import {AddPlayerInterface} from "./AddPlayerInterface"; import {PlayerAnimationNames} from "../Player/Animation"; import {PlayerMovement} from "./PlayerMovement"; import {PlayersPositionInterpolator} from "./PlayersPositionInterpolator"; +import {RemotePlayer} from "../Entity/RemotePlayer"; export enum Textures { Player = "male1" @@ -29,11 +30,11 @@ export class GameScene extends Phaser.Scene { Terrains : Array; CurrentPlayer: CurrentGamerInterface; MapPlayers : Phaser.Physics.Arcade.Group; - MapPlayersByKey : Map = new Map(); + MapPlayersByKey : Map = new Map(); Map: Phaser.Tilemaps.Tilemap; Layers : Array; Objects : Array; - mapFile: ITiledMap|null; + mapFile: ITiledMap; groups: Map; startX = 704;// 22 case startY = 32; // 1 case @@ -198,7 +199,7 @@ export class GameScene extends Phaser.Scene { // FIXME: entry should be dictated by a property passed to init() path += '#'+url.hash; } - window.history.pushState({}, null, path); + window.history.pushState({}, 'WorkAdventure', path); } private getExitSceneUrl(layer: ITiledMapLayer): string|undefined { @@ -234,6 +235,9 @@ export class GameScene extends Phaser.Scene { */ private loadNextGame(layer: ITiledMapLayer, mapWidth: number, tileWidth: number, tileHeight: number){ let exitSceneUrl = this.getExitSceneUrl(layer); + if (exitSceneUrl === undefined) { + throw new Error('Layer is not an exit scene layer.'); + } let instance = this.getExitSceneInstance(layer); if (instance === undefined) { instance = this.instance; @@ -340,7 +344,6 @@ export class GameScene extends Phaser.Scene { //initialise player //TODO create animation moving between exit and start this.CurrentPlayer = new Player( - null, // The current player has no id (because the id can change if connection is lost and we should check that id using the GameManager.) this, this.startX, this.startY, @@ -415,7 +418,7 @@ export class GameScene extends Phaser.Scene { // Let's move all users let updatedPlayersPositions = this.playersPositionInterpolator.getUpdatedPositions(time); updatedPlayersPositions.forEach((moveEvent: HasMovedEvent, userId: string) => { - let player : GamerInterface | undefined = this.MapPlayersByKey.get(userId); + let player : RemotePlayer | undefined = this.MapPlayersByKey.get(userId); if (player === undefined) { throw new Error('Cannot find player with ID "' + userId +'"'); } @@ -452,11 +455,11 @@ export class GameScene extends Phaser.Scene { let currentPlayerId = this.GameManager.getPlayerId(); // clean map - this.MapPlayersByKey.forEach((player: GamerInterface) => { + this.MapPlayersByKey.forEach((player: RemotePlayer) => { player.destroy(); this.MapPlayers.remove(player); }); - this.MapPlayersByKey = new Map(); + this.MapPlayersByKey = new Map(); // load map usersPosition.forEach((userPosition : MessageUserPositionInterface) => { @@ -480,7 +483,7 @@ export class GameScene extends Phaser.Scene { return; } //initialise player - let player = new Player( + let player = new RemotePlayer( addPlayerData.userId, this, addPlayerData.position.x, @@ -514,7 +517,7 @@ export class GameScene extends Phaser.Scene { } updatePlayerPosition(message: MessageUserMovedInterface): void { - let player : GamerInterface | undefined = this.MapPlayersByKey.get(message.userId); + let player : RemotePlayer | undefined = this.MapPlayersByKey.get(message.userId); if (player === undefined) { throw new Error('Cannot find player with ID "' + message.userId +'"'); } diff --git a/front/src/Phaser/Login/LoginScene.ts b/front/src/Phaser/Login/LoginScene.ts index 606b4077..1b7ef76f 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/PlayableCharacter"; +import {PLAYER_RESOURCES} from "../Entity/Character"; import {cypressAsserter} from "../../Cypress/CypressAsserter"; import {SelectCharacterSceneInitDataInterface, SelectCharacterSceneName} from "./SelectCharacterScene"; diff --git a/front/src/Phaser/Login/SelectCharacterScene.ts b/front/src/Phaser/Login/SelectCharacterScene.ts index f91a810c..eb5f25e9 100644 --- a/front/src/Phaser/Login/SelectCharacterScene.ts +++ b/front/src/Phaser/Login/SelectCharacterScene.ts @@ -3,7 +3,7 @@ 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/PlayableCharacter"; +import {PLAYER_RESOURCES} from "../Entity/Character"; //todo: put this constants in a dedicated file export const SelectCharacterSceneName = "SelectCharacterScene"; diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index 2e88bcec..6794a472 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -2,39 +2,21 @@ import {PlayerAnimationNames} from "./Animation"; import {GameScene, Textures} from "../Game/GameScene"; import {MessageUserPositionInterface, PointInterface} from "../../Connection"; import {ActiveEventList, UserInputEvent, UserInputManager} from "../UserInput/UserInputManager"; -import {PlayableCharacter} from "../Entity/PlayableCharacter"; +import {Character} from "../Entity/Character"; export const hasMovedEventName = "hasMoved"; -export interface CurrentGamerInterface extends PlayableCharacter{ +export interface CurrentGamerInterface extends Character{ moveUser(delta: number) : void; say(text : string) : void; } -export interface GamerInterface extends PlayableCharacter{ - userId : string; - updatePosition(position: PointInterface): void; - say(text : string) : void; -} - -interface AnimationData { - key: string; - frameRate: number; - repeat: number; - frameModel: string; //todo use an enum - frameStart: number; - frameEnd: number; -} - - -export class Player extends PlayableCharacter implements CurrentGamerInterface, GamerInterface { - userId: string; +export class Player extends Character implements CurrentGamerInterface { userInputManager: UserInputManager; previousDirection: string; wasMoving: boolean; constructor( - userId: string, Scene: GameScene, x: number, y: number, @@ -43,62 +25,13 @@ export class Player extends PlayableCharacter implements CurrentGamerInterface, direction: string, moving: boolean ) { - super(Scene, x, y, PlayerTexture, name, 1); + super(Scene, x, y, PlayerTexture, name, direction, moving, 1); //create input to move this.userInputManager = new UserInputManager(Scene); - //set data - this.userId = userId; - //the current player model should be push away by other players to prevent conflict this.setImmovable(false); - this.initAnimation(); - - this.playAnimation(direction, moving); - } - - private initAnimation(): void { - this.getPlayerAnimations(this.PlayerTexture).forEach(d => { - this.scene.anims.create({ - key: d.key, - frames: this.scene.anims.generateFrameNumbers(d.frameModel, {start: d.frameStart, end: d.frameEnd}), - frameRate: d.frameRate, - repeat: d.repeat - }); - }) - } - - private getPlayerAnimations(name: string): AnimationData[] { - return [{ - key: `${name}-${PlayerAnimationNames.WalkDown}`, - frameModel: name, - frameStart: 0, - frameEnd: 2, - frameRate: 10, - repeat: -1 - }, { - key: `${name}-${PlayerAnimationNames.WalkLeft}`, - frameModel: name, - frameStart: 3, - frameEnd: 5, - frameRate: 10, - repeat: -1 - }, { - key: `${name}-${PlayerAnimationNames.WalkRight}`, - frameModel: name, - frameStart: 6, - frameEnd: 8, - frameRate: 10, - repeat: -1 - }, { - key: `${name}-${PlayerAnimationNames.WalkUp}`, - frameModel: name, - frameStart: 9, - frameEnd: 11, - frameRate: 10, - repeat: -1 - }]; } moveUser(delta: number): void { @@ -146,24 +79,4 @@ export class Player extends PlayableCharacter implements CurrentGamerInterface, } this.wasMoving = moving; } - - //todo: put this method into the NonPlayer class instead - updatePosition(position: PointInterface): void { - this.playAnimation(position.direction, position.moving); - this.setX(position.x); - this.setY(position.y); - this.setDepth(position.y); - } - - private playAnimation(direction : string, moving: boolean): void { - if (moving && (!this.anims.currentAnim || this.anims.currentAnim.key !== direction)) { - this.play(this.PlayerTexture+'-'+direction, true); - } else if (!moving) { - /*if (this.anims.currentAnim) { - this.anims.stop(); - }*/ - this.play(this.PlayerTexture+'-'+direction, true); - this.stop(); - } - } } diff --git a/front/src/Phaser/Reconnecting/ReconnectingScene.ts b/front/src/Phaser/Reconnecting/ReconnectingScene.ts index 45651ed0..7188b223 100644 --- a/front/src/Phaser/Reconnecting/ReconnectingScene.ts +++ b/front/src/Phaser/Reconnecting/ReconnectingScene.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/PlayableCharacter"; +import {PLAYER_RESOURCES} from "../Entity/Character"; import {cypressAsserter} from "../../Cypress/CypressAsserter"; import Sprite = Phaser.GameObjects.Sprite;