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) => {