From e934015d87fea7d83cd8641e120bb8eec767570d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 18 May 2020 18:33:04 +0200 Subject: [PATCH 01/11] Refactoring searchClientById searchClientById was scanning through all open sockets to find the right one (which is inefficient if we have many). Instead, I created a new Map that directly maps ids to sockets. Furthermore, this solves a long-standing issue (when a socket is disconnected, we cannot find it anymore in the sockets list but it is still available in the disconnect callback from the map) As a result, we should not have any remaining circles any more. --- back/src/Controller/IoSocketController.ts | 108 +++++++--------------- back/src/Model/World.ts | 1 - 2 files changed, 33 insertions(+), 76 deletions(-) diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index 2c798797..fed04c78 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -30,6 +30,7 @@ enum SockerIoEvent { export class IoSocketController { Io: socketIO.Server; Worlds: Map = new Map(); + sockets: Map = new Map(); constructor(server: http.Server) { this.Io = socketIO(server); @@ -60,10 +61,7 @@ export class IoSocketController { // Let's get the room of the group. To do this, let's get anyone in the group and find its room. // Note: this is suboptimal let userId = group.getUsers()[0].id; - let client: ExSocketInterface|null = this.searchClientById(userId); - if (client === null) { - return; - } + let client: ExSocketInterface = this.searchClientByIdOrFail(userId); let roomId = client.roomId; this.Io.in(roomId).emit(SockerIoEvent.GROUP_CREATE_UPDATE, { position: group.getPosition(), @@ -73,19 +71,15 @@ export class IoSocketController { private sendDeleteGroupEvent(uuid: string, lastUser: UserInterface): void { // Let's get the room of the group. To do this, let's get anyone in the group and find its room. - // Note: this is suboptimal let userId = lastUser.id; - let client: ExSocketInterface|null = this.searchClientById(userId); - if (client === null) { - console.warn('Could not find client ', userId, ' in group') - return; - } + let client: ExSocketInterface = this.searchClientByIdOrFail(userId); let roomId = client.roomId; this.Io.in(roomId).emit(SockerIoEvent.GROUP_DELETE, uuid); } ioConnection() { this.Io.on(SockerIoEvent.CONNECTION, (socket: Socket) => { + this.sockets.set(socket.id, socket as ExSocketInterface); /*join-rom event permit to join one room. message : userId : user identification @@ -148,20 +142,19 @@ export class IoSocketController { socket.on(SockerIoEvent.WEBRTC_SIGNAL, (data: any) => { //send only at user - let client = this.searchClientById(data.receiverId); - if (!client) { - console.error("While exchanging a WebRTC signal: client doesn't exist for ", data.receiverId); + let client = this.sockets.get(data.receiverId); + if (client === undefined) { + console.warn("While exchanging a WebRTC signal: client with id ", data.receiverId, " does not exist. This might be a race condition."); return; } return client.emit(SockerIoEvent.WEBRTC_SIGNAL, data); }); socket.on(SockerIoEvent.WEBRTC_OFFER, (data: any) => { - //send only at user - let client = this.searchClientById(data.receiverId); - if (!client) { - console.error("client doesn't exist for ", data.receiverId); + let client = this.sockets.get(data.receiverId); + if (client === undefined) { + console.warn("While exchanging a WebRTC offer: client with id ", data.receiverId, " does not exist. This might be a race condition."); return; } client.emit(SockerIoEvent.WEBRTC_OFFER, data); @@ -170,16 +163,16 @@ export class IoSocketController { socket.on(SockerIoEvent.DISCONNECT, () => { try { let Client = (socket as ExSocketInterface); - this.sendDisconnectedEvent(Client); + //this.sendDisconnectedEvent(Client); - //refresh position of all user in all rooms in real time + //refresh position of all user in all rooms in real time (to remove the disconnected user) this.refreshUserPosition(Client); //leave room this.leaveRoom(Client); //leave webrtc room - socket.leave(Client.webRtcRoomId); + //socket.leave(Client.webRtcRoomId); //delete all socket information delete Client.webRtcRoomId; @@ -190,6 +183,7 @@ export class IoSocketController { console.error('An error occurred on "disconnect"'); console.error(e); } + this.sockets.delete(socket.id); }); // Let's send the user id to the user @@ -202,54 +196,12 @@ export class IoSocketController { }); } - /** - * TODO: each call to this method is suboptimal. It means that instead of passing an ID, we should pass a client object. - * @param userId - */ - searchClientById(userId: string): ExSocketInterface | null { - let clients: Array = Object.values(this.Io.sockets.sockets); - for (let i = 0; i < clients.length; i++) { - let client: ExSocketInterface = clients[i]; - if (client.id !== userId) { - continue; - } - return client; + searchClientByIdOrFail(userId: string): ExSocketInterface { + let client: ExSocketInterface|undefined = this.sockets.get(userId); + if (client === undefined) { + throw new Error("Could not find user with id " + userId); } - console.log("Could not find user with id ", userId); - //throw new Error("Could not find user with id " + userId); - return null; - } - - /** - * @param userId - */ - searchClientByToken(userId: string): ExSocketInterface | null { - let clients: Array = Object.values(this.Io.sockets.sockets); - for (let i = 0; i < clients.length; i++) { - let client: ExSocketInterface = clients[i]; - if (client.id !== userId) { - continue - } - return client; - } - return null; - } - - /** - * - * @param Client: ExSocketInterface - */ - sendDisconnectedEvent(Client: ExSocketInterface) { - Client.broadcast.emit(SockerIoEvent.WEBRTC_DISCONNECT, { - userId: Client.id - }); - - //disconnect webrtc room - if(!Client.webRtcRoomId){ - return; - } - Client.leave(Client.webRtcRoomId); - delete Client.webRtcRoomId; + return client; } leaveRoom(Client : ExSocketInterface){ @@ -349,12 +301,11 @@ export class IoSocketController { rooms.refreshUserPosition(rooms, this.Io); // update position in the world - let messageUserPosition = new MessageUserPosition(Client.id, Client.name, Client.character, Client.position); let world = this.Worlds.get(Client.roomId); if (!world) { return; } - world.updatePosition(Client, messageUserPosition.position); + world.updatePosition(Client, Client.position); this.Worlds.set(Client.roomId, world); } @@ -412,19 +363,26 @@ export class IoSocketController { //connected user connectedUser(userId: string, group: Group) { - let Client = this.searchClientById(userId); - if (!Client) { + /*let Client = this.sockets.get(userId); + if (Client === undefined) { return; - } + }*/ + let Client = this.searchClientByIdOrFail(userId); this.joinWebRtcRoom(Client, group.getId()); } //disconnect user disConnectedUser(userId: string, group: Group) { - let Client = this.searchClientById(userId); - if (!Client) { + let Client = this.searchClientByIdOrFail(userId); + Client.to(group.getId()).emit(SockerIoEvent.WEBRTC_DISCONNECT, { + userId: userId + }); + + //disconnect webrtc room + if(!Client.webRtcRoomId){ return; } - this.sendDisconnectedEvent(Client) + Client.leave(Client.webRtcRoomId); + delete Client.webRtcRoomId; } } diff --git a/back/src/Model/World.ts b/back/src/Model/World.ts index 39233271..fb33f02c 100644 --- a/back/src/Model/World.ts +++ b/back/src/Model/World.ts @@ -60,7 +60,6 @@ export class World { public leave(user : Identificable){ let userObj = this.users.get(user.id); if (userObj === undefined) { - // FIXME: this seems always wrong. I guess user.id is different from userPosition.userId console.warn('User ', user.id, 'does not belong to world! It should!'); } if (userObj !== undefined && typeof userObj.group !== 'undefined') { From 125a4d11af8c964095da9729c4c860d15e185d05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Tue, 19 May 2020 19:11:12 +0200 Subject: [PATCH 02/11] Refactored and optimized messages Now, when a user moves, only his/her position is sent back to the other users. The position of all users is not sent each time. The messages sent to the browser are now: - the list of all users as a return to the join_room event (you can send responses to events in socket.io) - a "join_room" event sent when a new user joins the room - a "user_moved" event when a user moved - a "user_left" event when a user left the room The GameScene tracks all these events and reacts accordingly. Also, I made a number of refactoring in the classes and removed the GameSceneInterface that was useless (it was implemented by the LogincScene for no reason at all) --- back/src/Controller/IoSocketController.ts | 78 +++++++++------- back/src/Model/Websocket/MessageUserJoined.ts | 5 ++ back/src/Model/Websocket/MessageUserMoved.ts | 6 ++ back/src/Model/Websocket/PointInterface.ts | 6 +- back/src/Model/World.ts | 7 +- front/src/Connexion.ts | 45 +++++++++- front/src/Phaser/Game/AddPlayerInterface.ts | 8 ++ front/src/Phaser/Game/GameManager.ts | 40 ++++++++- front/src/Phaser/Game/GameScene.ts | 89 ++++++++++++++----- front/src/Phaser/Login/LogincScene.ts | 21 +---- front/src/Phaser/Player/Player.ts | 18 ++-- .../src/Phaser/UserInput/UserInputManager.ts | 16 ++-- 12 files changed, 238 insertions(+), 101 deletions(-) create mode 100644 back/src/Model/Websocket/MessageUserJoined.ts create mode 100644 back/src/Model/Websocket/MessageUserMoved.ts create mode 100644 front/src/Phaser/Game/AddPlayerInterface.ts diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index fed04c78..63440d24 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -11,12 +11,16 @@ import {World} from "../Model/World"; import {Group} from "_Model/Group"; import {UserInterface} from "_Model/UserInterface"; import {SetPlayerDetailsMessage} from "_Model/Websocket/SetPlayerDetailsMessage"; +import {MessageUserJoined} from "../Model/Websocket/MessageUserJoined"; +import {MessageUserMoved} from "../Model/Websocket/MessageUserMoved"; enum SockerIoEvent { CONNECTION = "connection", DISCONNECT = "disconnect", - JOIN_ROOM = "join-room", - USER_POSITION = "user-position", + JOIN_ROOM = "join-room", // bi-directional + USER_POSITION = "user-position", // bi-directional + USER_MOVED = "user-moved", // From server to client + USER_LEFT = "user-left", // From server to client WEBRTC_SIGNAL = "webrtc-signal", WEBRTC_OFFER = "webrtc-offer", WEBRTC_START = "webrtc-start", @@ -88,7 +92,7 @@ export class IoSocketController { x: user x position on map y: user y position on map */ - socket.on(SockerIoEvent.JOIN_ROOM, (roomId: any): void => { + socket.on(SockerIoEvent.JOIN_ROOM, (roomId: any, answerFn): void => { try { if (typeof(roomId) !== 'string') { socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Expected roomId as a string.'}); @@ -105,14 +109,21 @@ export class IoSocketController { this.leaveRoom(Client); //join new previous room - this.joinRoom(Client, roomId); + let world = this.joinRoom(Client, roomId); //add function to refresh position user in real time. - this.refreshUserPosition(Client); + //this.refreshUserPosition(Client); - let messageUserPosition = new MessageUserPosition(Client.id, Client.name, Client.character,new Point(0, 0, 'none')); + let messageUserJoined = new MessageUserJoined(Client.id, Client.name, Client.character); - socket.to(roomId).emit(SockerIoEvent.JOIN_ROOM, messageUserPosition); + socket.to(roomId).emit(SockerIoEvent.JOIN_ROOM, messageUserJoined); + + // The answer shall contain the list of all users of the room with their positions: + let listOfUsers = Array.from(world.getUsers(), ([key, user]) => { + let player = this.searchClientByIdOrFail(user.id); + return new MessageUserPosition(user.id, player.name, player.character, player.position); + }); + answerFn(listOfUsers); } catch (e) { console.error('An error occurred on "join_room" event'); console.error(e); @@ -133,7 +144,17 @@ export class IoSocketController { Client.position = position; //refresh position of all user in all rooms in real time - this.refreshUserPosition(Client); + //this.refreshUserPosition(Client); + + // update position in the world + let world = this.Worlds.get(Client.roomId); + if (!world) { + console.error("Could not find world with id '", Client.roomId, "'"); + return; + } + world.updatePosition(Client, Client.position); + + socket.to(Client.roomId).emit(SockerIoEvent.USER_MOVED, new MessageUserMoved(Client.id, Client.position)); } catch (e) { console.error('An error occurred on "user_position" event'); console.error(e); @@ -163,10 +184,10 @@ export class IoSocketController { socket.on(SockerIoEvent.DISCONNECT, () => { try { let Client = (socket as ExSocketInterface); - //this.sendDisconnectedEvent(Client); - //refresh position of all user in all rooms in real time (to remove the disconnected user) - this.refreshUserPosition(Client); + if (Client.roomId) { + socket.to(Client.roomId).emit(SockerIoEvent.USER_LEFT, socket.id); + } //leave room this.leaveRoom(Client); @@ -205,13 +226,14 @@ export class IoSocketController { } leaveRoom(Client : ExSocketInterface){ - //lease previous room and world + // leave previous room and world if(Client.roomId){ + Client.to(Client.roomId).emit(SockerIoEvent.USER_LEFT, Client.id); + //user leave previous world let world : World|undefined = this.Worlds.get(Client.roomId); if(world){ world.leave(Client); - //this.Worlds.set(Client.roomId, world); } //user leave previous room Client.leave(Client.roomId); @@ -219,15 +241,16 @@ export class IoSocketController { } } - joinRoom(Client : ExSocketInterface, roomId: string){ + private joinRoom(Client : ExSocketInterface, roomId: string): World { //join user in room Client.join(roomId); Client.roomId = roomId; Client.position = new Point(-1000, -1000); //check and create new world for a room - if(!this.Worlds.get(roomId)){ - let world = new World((user1: string, group: Group) => { + let world = this.Worlds.get(roomId) + if(world === undefined){ + world = new World((user1: string, group: Group) => { this.connectedUser(user1, group); }, (user1: string, group: Group) => { this.disConnectedUser(user1, group); @@ -239,20 +262,16 @@ export class IoSocketController { this.Worlds.set(roomId, world); } - let world : World|undefined = this.Worlds.get(roomId); - - if(world) { - // Dispatch groups position to newly connected user - world.getGroups().forEach((group: Group) => { - Client.emit(SockerIoEvent.GROUP_CREATE_UPDATE, { - position: group.getPosition(), - groupId: group.getId() - }); + // Dispatch groups position to newly connected user + world.getGroups().forEach((group: Group) => { + Client.emit(SockerIoEvent.GROUP_CREATE_UPDATE, { + position: group.getPosition(), + groupId: group.getId() }); - //join world - world.join(Client, Client.position); - this.Worlds.set(roomId, world); - } + }); + //join world + world.join(Client, Client.position); + return world; } /** @@ -306,7 +325,6 @@ export class IoSocketController { return; } world.updatePosition(Client, Client.position); - this.Worlds.set(Client.roomId, world); } //Hydrate and manage error diff --git a/back/src/Model/Websocket/MessageUserJoined.ts b/back/src/Model/Websocket/MessageUserJoined.ts new file mode 100644 index 00000000..e12f5dc3 --- /dev/null +++ b/back/src/Model/Websocket/MessageUserJoined.ts @@ -0,0 +1,5 @@ + +export class MessageUserJoined { + constructor(public userId: string, public name: string, public character: string) { + } +} diff --git a/back/src/Model/Websocket/MessageUserMoved.ts b/back/src/Model/Websocket/MessageUserMoved.ts new file mode 100644 index 00000000..283c011d --- /dev/null +++ b/back/src/Model/Websocket/MessageUserMoved.ts @@ -0,0 +1,6 @@ +import {PointInterface} from "./PointInterface"; + +export class MessageUserMoved { + constructor(public userId: string, public position: PointInterface) { + } +} diff --git a/back/src/Model/Websocket/PointInterface.ts b/back/src/Model/Websocket/PointInterface.ts index 9243acbe..61b02339 100644 --- a/back/src/Model/Websocket/PointInterface.ts +++ b/back/src/Model/Websocket/PointInterface.ts @@ -1,5 +1,5 @@ export interface PointInterface { - x: number; - y: number; - direction: string; + readonly x: number; + readonly y: number; + readonly direction: string; } diff --git a/back/src/Model/World.ts b/back/src/Model/World.ts index fb33f02c..0529edc3 100644 --- a/back/src/Model/World.ts +++ b/back/src/Model/World.ts @@ -48,6 +48,10 @@ export class World { return this.groups; } + public getUsers(): Map { + return this.users; + } + public join(socket : Identificable, userPosition: PointInterface): void { this.users.set(socket.id, { id: socket.id, @@ -74,8 +78,7 @@ export class World { return; } - user.position.x = userPosition.x; - user.position.y = userPosition.y; + user.position = userPosition; if (typeof user.group === 'undefined') { // If the user is not part of a group: diff --git a/front/src/Connexion.ts b/front/src/Connexion.ts index f3e4f4ce..78789fef 100644 --- a/front/src/Connexion.ts +++ b/front/src/Connexion.ts @@ -12,8 +12,10 @@ enum EventMessage{ WEBRTC_SIGNAL = "webrtc-signal", WEBRTC_START = "webrtc-start", WEBRTC_JOIN_ROOM = "webrtc-join-room", - JOIN_ROOM = "join-room", - USER_POSITION = "user-position", + JOIN_ROOM = "join-room", // bi-directional + USER_POSITION = "user-position", // bi-directional + USER_MOVED = "user-moved", // From server to client + USER_LEFT = "user-left", // From server to client MESSAGE_ERROR = "message-error", WEBRTC_DISCONNECT = "webrtc-disconect", GROUP_CREATE_UPDATE = "group-create-update", @@ -42,7 +44,7 @@ export interface PointInterface { direction : string; } -class Point implements PointInterface{ +export class Point implements PointInterface{ x: number; y: number; direction : string; @@ -64,6 +66,11 @@ export interface MessageUserPositionInterface { position: PointInterface; } +export interface MessageUserMovedInterface { + userId: string; + position: PointInterface; +} + class MessageUserPosition extends Message implements MessageUserPositionInterface{ position: PointInterface; @@ -73,6 +80,12 @@ class MessageUserPosition extends Message implements MessageUserPositionInterfac } } +export interface MessageUserJoined { + userId: string; + name: string; + character: string; +} + export interface ListMessageUserPositionInterface { roomId: string; listUsersPosition: Array; @@ -187,6 +200,9 @@ export class Connexion implements ConnexionInterface { this.errorMessage(); this.groupUpdatedOrCreated(); this.groupDeleted(); + this.onUserJoins(); + this.onUserMoved(); + this.onUserLeft(); return new Promise((resolve, reject) => { this.socket.emit(EventMessage.SET_PLAYER_DETAILS, { @@ -235,7 +251,10 @@ export class Connexion implements ConnexionInterface { * @param character */ joinARoom(roomId: string): void { - this.socket.emit(EventMessage.JOIN_ROOM, roomId); + this.socket.emit(EventMessage.JOIN_ROOM, roomId, (userPositions: MessageUserPositionInterface[]) => { + console.log("GOT AN ANSWER FROM JOIN_ROOM"); + this.GameManager.initUsersPosition(userPositions); + }); this.lastRoom = roomId; } @@ -280,6 +299,24 @@ export class Connexion implements ConnexionInterface { }); } + onUserJoins(): void { + this.socket.on(EventMessage.JOIN_ROOM, (message: MessageUserJoined) => { + this.GameManager.onUserJoins(message); + }); + } + + onUserMoved(): void { + this.socket.on(EventMessage.USER_MOVED, (message: MessageUserMovedInterface) => { + this.GameManager.onUserMoved(message); + }); + } + + onUserLeft(): void { + this.socket.on(EventMessage.USER_LEFT, (userId: string) => { + this.GameManager.onUserLeft(userId); + }); + } + private groupUpdatedOrCreated(): void { this.socket.on(EventMessage.GROUP_CREATE_UPDATE, (groupCreateUpdateMessage: GroupCreatedUpdatedMessageInterface) => { //console.log('Group ', groupCreateUpdateMessage.groupId, " position :", groupCreateUpdateMessage.position.x, groupCreateUpdateMessage.position.y) diff --git a/front/src/Phaser/Game/AddPlayerInterface.ts b/front/src/Phaser/Game/AddPlayerInterface.ts new file mode 100644 index 00000000..473fea29 --- /dev/null +++ b/front/src/Phaser/Game/AddPlayerInterface.ts @@ -0,0 +1,8 @@ +import {PointInterface} from "../../Connexion"; + +export interface AddPlayerInterface { + userId: string; + name: string; + character: string; + position: PointInterface; +} diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index a84653cd..fcb386d1 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -1,12 +1,13 @@ -import {GameScene, GameSceneInterface} from "./GameScene"; +import {GameScene} from "./GameScene"; import { Connexion, GroupCreatedUpdatedMessageInterface, - ListMessageUserPositionInterface + ListMessageUserPositionInterface, MessageUserJoined, MessageUserMovedInterface, MessageUserPositionInterface, Point } from "../../Connexion"; import {SimplePeerInterface, SimplePeer} from "../../WebRtc/SimplePeer"; import {getMapKeyByUrl} from "../Login/LogincScene"; import ScenePlugin = Phaser.Scenes.ScenePlugin; +import {AddPlayerInterface} from "./AddPlayerInterface"; export enum StatusGameManagerEnum { IN_PROGRESS = 1, @@ -27,7 +28,7 @@ export interface MapObject { export class GameManager { status: number; private ConnexionInstance: Connexion; - private currentGameScene: GameSceneInterface; + private currentGameScene: GameScene; private playerName: string; SimplePeer : SimplePeerInterface; private characterUserSelected: string; @@ -56,7 +57,7 @@ export class GameManager { }); } - setCurrentGameScene(gameScene: GameSceneInterface) { + setCurrentGameScene(gameScene: GameScene) { this.currentGameScene = gameScene; } @@ -74,9 +75,28 @@ export class GameManager { this.ConnexionInstance.joinARoom(sceneKey); } + onUserJoins(message: MessageUserJoined): void { + let userMessage: AddPlayerInterface = { + userId: message.userId, + character: message.character, + name: message.name, + position: new Point(-1000, -1000) + } + this.currentGameScene.addPlayer(userMessage); + } + + onUserMoved(message: MessageUserMovedInterface): void { + this.currentGameScene.updatePlayerPosition(message); + } + + onUserLeft(userId: string): void { + this.currentGameScene.removePlayer(userId); + } + /** * Share position in game * @param ListMessageUserPosition + * @deprecated */ shareUserPosition(ListMessageUserPosition: ListMessageUserPositionInterface): void { if (this.status === StatusGameManagerEnum.IN_PROGRESS) { @@ -89,6 +109,18 @@ export class GameManager { } } + initUsersPosition(usersPosition: MessageUserPositionInterface[]): void { + // Shall we wait for room to be loaded? + /*if (this.status === StatusGameManagerEnum.IN_PROGRESS) { + return; + }*/ + try { + this.currentGameScene.initUsersPosition(usersPosition) + } catch (e) { + console.error(e); + } + } + /** * Share group position in game */ diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 56c775ed..2f9e5b71 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1,5 +1,10 @@ import {GameManager, gameManager, HasMovedEvent, MapObject, StatusGameManagerEnum} from "./GameManager"; -import {GroupCreatedUpdatedMessageInterface, MessageUserPositionInterface} from "../../Connexion"; +import { + GroupCreatedUpdatedMessageInterface, + MessageUserJoined, + MessageUserMovedInterface, + MessageUserPositionInterface +} from "../../Connexion"; import {CurrentGamerInterface, GamerInterface, hasMovedEventName, Player} from "../Player/Player"; import { DEBUG_MODE, RESOLUTION, ROOM, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable"; import {ITiledMap, ITiledMapLayer, ITiledTileSet} from "../Map/ITiledMap"; @@ -7,25 +12,18 @@ import {PLAYER_RESOURCES} from "../Entity/PlayableCaracter"; import Texture = Phaser.Textures.Texture; import Sprite = Phaser.GameObjects.Sprite; import CanvasTexture = Phaser.Textures.CanvasTexture; -import CreateSceneFromObjectConfig = Phaser.Types.Scenes.CreateSceneFromObjectConfig; +import {AddPlayerInterface} from "./AddPlayerInterface"; export enum Textures { Player = "male1" } -export interface GameSceneInterface extends Phaser.Scene { - Map: Phaser.Tilemaps.Tilemap; - createCurrentPlayer() : void; - shareUserPosition(UsersPosition : Array): void; - shareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface): void; - updateOrCreateMapPlayer(UsersPosition : Array): void; - deleteGroup(groupId: string): void; -} -export class GameScene extends Phaser.Scene implements GameSceneInterface, CreateSceneFromObjectConfig{ +export class GameScene extends Phaser.Scene { GameManager : GameManager; Terrains : Array; CurrentPlayer: CurrentGamerInterface; MapPlayers : Phaser.Physics.Arcade.Group; + MapPlayersByKey : Map = new Map(); Map: Phaser.Tilemaps.Tilemap; Layers : Array; Objects : Array; @@ -333,6 +331,7 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface, Creat /** * Share position in scene * @param UsersPosition + * @deprecated */ shareUserPosition(UsersPosition : Array): void { this.updateOrCreateMapPlayer(UsersPosition); @@ -358,7 +357,7 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface, Creat if(!player){ this.addPlayer(userPosition); }else{ - player.updatePosition(userPosition); + player.updatePosition(userPosition.position); } }); @@ -372,31 +371,59 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface, Creat }); } + public initUsersPosition(usersPosition: MessageUserPositionInterface[]): void { + if(!this.CurrentPlayer){ + console.error('Cannot initiate users list because map is not loaded yet') + return; + } + + let currentPlayerId = this.GameManager.getPlayerId(); + + // clean map + this.MapPlayersByKey.forEach((player: GamerInterface) => { + player.destroy(); + this.MapPlayers.remove(player); + }); + this.MapPlayersByKey = new Map(); + + // load map + usersPosition.forEach((userPosition : MessageUserPositionInterface) => { + if(userPosition.userId === currentPlayerId){ + return; + } + this.addPlayer(userPosition); + console.log("Added player ", userPosition) + }); + + console.log("Initialized with ", usersPosition); + } + private findPlayerInMap(UserId : string) : GamerInterface | null{ - let player = this.MapPlayers.getChildren().find((player: Player) => UserId === player.userId); + return this.MapPlayersByKey.get(UserId); + /*let player = this.MapPlayers.getChildren().find((player: Player) => UserId === player.userId); if(!player){ return null; } - return (player as GamerInterface); + return (player as GamerInterface);*/ } /** * Create new player - * @param MessageUserPosition */ - addPlayer(MessageUserPosition : MessageUserPositionInterface) : void{ + public addPlayer(addPlayerData : AddPlayerInterface) : void{ //initialise player let player = new Player( - MessageUserPosition.userId, + addPlayerData.userId, this, - MessageUserPosition.position.x, - MessageUserPosition.position.y, - MessageUserPosition.name, - MessageUserPosition.character + addPlayerData.position.x, + addPlayerData.position.y, + addPlayerData.name, + addPlayerData.character ); player.initAnimation(); this.MapPlayers.add(player); - player.updatePosition(MessageUserPosition); + this.MapPlayersByKey.set(player.userId, player); + player.updatePosition(addPlayerData.position); //init collision /*this.physics.add.collider(this.CurrentPlayer, player, (CurrentPlayer: CurrentGamerInterface, MapPlayer: GamerInterface) => { @@ -404,6 +431,24 @@ export class GameScene extends Phaser.Scene implements GameSceneInterface, Creat });*/ } + public removePlayer(userId: string) { + let player = this.MapPlayersByKey.get(userId); + if (player === undefined) { + console.error('Cannot find user with id ', userId); + } + player.destroy(); + this.MapPlayers.remove(player); + this.MapPlayersByKey.delete(userId); + } + + updatePlayerPosition(message: MessageUserMovedInterface): void { + let player : GamerInterface | undefined = this.MapPlayersByKey.get(message.userId); + if (player === undefined) { + throw new Error('Cannot find player with ID "' + message.userId +'"'); + } + player.updatePosition(message.position); + } + shareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface) { let groupId = groupPositionMessage.groupId; diff --git a/front/src/Phaser/Login/LogincScene.ts b/front/src/Phaser/Login/LogincScene.ts index f7dd0029..c289b999 100644 --- a/front/src/Phaser/Login/LogincScene.ts +++ b/front/src/Phaser/Login/LogincScene.ts @@ -2,12 +2,11 @@ import {gameManager} from "../Game/GameManager"; import {TextField} from "../Components/TextField"; import {TextInput} from "../Components/TextInput"; import {ClickButton} from "../Components/ClickButton"; -import {GameScene, GameSceneInterface} from "../Game/GameScene"; import Image = Phaser.GameObjects.Image; import Rectangle = Phaser.GameObjects.Rectangle; import {PLAYER_RESOURCES} from "../Entity/PlayableCaracter"; import {cypressAsserter} from "../../Cypress/CypressAsserter"; -import {GroupCreatedUpdatedMessageInterface, MessageUserPositionInterface} from "../../Connexion"; +import {GroupCreatedUpdatedMessageInterface, MessageUserJoined, MessageUserPositionInterface} from "../../Connexion"; export function getMapKeyByUrl(mapUrlStart: string){ // FIXME: the key should be computed from the full URL of the map. @@ -28,7 +27,7 @@ enum LoginTextures { mainFont = "main_font" } -export class LogincScene extends Phaser.Scene implements GameSceneInterface { +export class LogincScene extends Phaser.Scene { private nameInput: TextInput; private textField: TextField; private playButton: ClickButton; @@ -168,20 +167,4 @@ export class LogincScene extends Phaser.Scene implements GameSceneInterface { this.selectedPlayer = this.players[0]; this.selectedPlayer.play(PLAYER_RESOURCES[0].name); } - - shareUserPosition(UsersPosition: import("../../Connexion").MessageUserPositionInterface[]): void { - throw new Error("Method not implemented."); - } - - deleteGroup(groupId: string): void { - throw new Error("Method not implemented."); - } - - shareGroupPosition(groupPositionMessage: GroupCreatedUpdatedMessageInterface): void { - throw new Error("Method not implemented."); - } - - updateOrCreateMapPlayer(UsersPosition: Array): void { - throw new Error("Method not implemented."); - } } diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index 73aad77e..26acb1a3 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -1,6 +1,6 @@ import {getPlayerAnimations, playAnimation, PlayerAnimationNames} from "./Animation"; -import {GameSceneInterface, Textures} from "../Game/GameScene"; -import {MessageUserPositionInterface} from "../../Connexion"; +import {GameScene, Textures} from "../Game/GameScene"; +import {MessageUserPositionInterface, PointInterface} from "../../Connexion"; import {ActiveEventList, UserInputEvent, UserInputManager} from "../UserInput/UserInputManager"; import {PlayableCaracter} from "../Entity/PlayableCaracter"; @@ -15,7 +15,7 @@ export interface CurrentGamerInterface extends PlayableCaracter{ export interface GamerInterface extends PlayableCaracter{ userId : string; initAnimation() : void; - updatePosition(MessageUserPosition : MessageUserPositionInterface) : void; + updatePosition(position: PointInterface): void; say(text : string) : void; } @@ -26,7 +26,7 @@ export class Player extends PlayableCaracter implements CurrentGamerInterface, G constructor( userId: string, - Scene: GameSceneInterface, + Scene: GameScene, x: number, y: number, name: string, @@ -96,10 +96,10 @@ export class Player extends PlayableCaracter implements CurrentGamerInterface, G } //todo: put this method into the NonPlayer class instead - updatePosition(MessageUserPosition: MessageUserPositionInterface) { - playAnimation(this, MessageUserPosition.position.direction); - this.setX(MessageUserPosition.position.x); - this.setY(MessageUserPosition.position.y); - this.setDepth(MessageUserPosition.position.y); + updatePosition(position: PointInterface): void { + playAnimation(this, position.direction); + this.setX(position.x); + this.setY(position.y); + this.setDepth(position.y); } } diff --git a/front/src/Phaser/UserInput/UserInputManager.ts b/front/src/Phaser/UserInput/UserInputManager.ts index 6c5b32c2..9c4ca660 100644 --- a/front/src/Phaser/UserInput/UserInputManager.ts +++ b/front/src/Phaser/UserInput/UserInputManager.ts @@ -1,5 +1,5 @@ import Map = Phaser.Structs.Map; -import {GameSceneInterface} from "../Game/GameScene"; +import {GameScene} from "../Game/GameScene"; interface UserInputManagerDatum { keyCode: number; @@ -29,7 +29,7 @@ export class ActiveEventList { set(event: UserInputEvent, value: boolean): boolean { return this.KeysCode[event] = true; } -} +} //this class is responsible for catching user inputs and listing all active user actions at every game tick events. export class UserInputManager { @@ -38,19 +38,19 @@ export class UserInputManager { {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}, ]; - - constructor(Scene : GameSceneInterface) { + + constructor(Scene : GameScene) { this.KeysCode.forEach(d => { d.keyInstance = Scene.input.keyboard.addKey(d.keyCode); }); @@ -65,4 +65,4 @@ export class UserInputManager { }); return eventsMap; } -} \ No newline at end of file +} From fb8d7b5d599d29a0ac802b2dc0ddf576d6f725ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Tue, 19 May 2020 19:53:26 +0200 Subject: [PATCH 03/11] Removing dead code --- back/src/Controller/IoSocketController.ts | 41 ----------------- back/src/Model/Websocket/ExtRooms.ts | 44 ------------------- back/src/Model/Websocket/ExtRoomsInterface.ts | 6 --- 3 files changed, 91 deletions(-) delete mode 100644 back/src/Model/Websocket/ExtRooms.ts delete mode 100644 back/src/Model/Websocket/ExtRoomsInterface.ts diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index 63440d24..292db31f 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -5,8 +5,6 @@ import {MessageUserPosition, Point} from "../Model/Websocket/MessageUserPosition import {ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.." import Jwt, {JsonWebTokenError} from "jsonwebtoken"; import {SECRET_KEY, MINIMUM_DISTANCE, GROUP_RADIUS} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..." -import {ExtRooms, RefreshUserPositionFunction} from "../Model/Websocket/ExtRooms"; -import {ExtRoomsInterface} from "../Model/Websocket/ExtRoomsInterface"; import {World} from "../Model/World"; import {Group} from "_Model/Group"; import {UserInterface} from "_Model/UserInterface"; @@ -58,7 +56,6 @@ export class IoSocketController { });*/ this.ioConnection(); - this.shareUsersPosition(); } private sendUpdateGroupEvent(group: Group): void { @@ -311,22 +308,6 @@ export class IoSocketController { }); } - refreshUserPosition(Client : ExSocketInterface) { - //refresh position of all user in all rooms in real time - let rooms = (this.Io.sockets.adapter.rooms as ExtRoomsInterface); - if (!rooms.refreshUserPosition) { - rooms.refreshUserPosition = RefreshUserPositionFunction; - } - rooms.refreshUserPosition(rooms, this.Io); - - // update position in the world - let world = this.Worlds.get(Client.roomId); - if (!world) { - return; - } - world.updatePosition(Client, Client.position); - } - //Hydrate and manage error hydratePositionReceive(message: any): Point | Error { try { @@ -356,28 +337,6 @@ export class IoSocketController { ... ] **/ - seTimeOutInProgress: any = null; - - shareUsersPosition() { - if (this.seTimeOutInProgress) { - clearTimeout(this.seTimeOutInProgress); - } - //send for each room, all data of position user - let arrayMap = (this.Io.sockets.adapter.rooms as ExtRooms).userPositionMapByRoom; - if (!arrayMap) { - this.seTimeOutInProgress = setTimeout(() => { - this.shareUsersPosition(); - }, 10); - return; - } - arrayMap.forEach((value: any) => { - let roomId = value[0]; - this.Io.in(roomId).emit(SockerIoEvent.USER_POSITION, value); - }); - this.seTimeOutInProgress = setTimeout(() => { - this.shareUsersPosition(); - }, 10); - } //connected user connectedUser(userId: string, group: Group) { diff --git a/back/src/Model/Websocket/ExtRooms.ts b/back/src/Model/Websocket/ExtRooms.ts deleted file mode 100644 index b5493fdb..00000000 --- a/back/src/Model/Websocket/ExtRooms.ts +++ /dev/null @@ -1,44 +0,0 @@ -import {ExtRoomsInterface} from "./ExtRoomsInterface"; -import socketIO = require('socket.io'); -import {ExSocketInterface} from "./ExSocketInterface"; - -export class ExtRooms implements ExtRoomsInterface{ - userPositionMapByRoom: any; - refreshUserPosition: any; - Worlds: any; - - [room: string]: SocketIO.Room; -} - -let RefreshUserPositionFunction = function(rooms : ExtRooms, Io: socketIO.Server) { - let clients = Io.clients(); - let socketsKey = Object.keys(Io.clients().sockets); - - //create mapping with all users in all rooms - let mapPositionUserByRoom = new Map(); - for (let i = 0; i < socketsKey.length; i++) { - let socket = clients.sockets[socketsKey[i]] as ExSocketInterface; - if (!socket.position) { - continue; - } - let data = { - userId: socket.id, - position: socket.position, - name: socket.name, - character: socket.character, - }; - let dataArray = []; - if (mapPositionUserByRoom.get(socket.roomId)) { - dataArray = mapPositionUserByRoom.get(socket.roomId); - dataArray.push(data); - } else { - dataArray = [data]; - } - mapPositionUserByRoom.set(socket.roomId, dataArray); - } - rooms.userPositionMapByRoom = Array.from(mapPositionUserByRoom); -}; - -export { - RefreshUserPositionFunction -}; diff --git a/back/src/Model/Websocket/ExtRoomsInterface.ts b/back/src/Model/Websocket/ExtRoomsInterface.ts deleted file mode 100644 index a71fcbf9..00000000 --- a/back/src/Model/Websocket/ExtRoomsInterface.ts +++ /dev/null @@ -1,6 +0,0 @@ -import {Rooms} from "socket.io"; - -export interface ExtRoomsInterface extends Rooms{ - userPositionMapByRoom: any; - refreshUserPosition: any; -} \ No newline at end of file From f44a44c109936ccbabc18bb35be2e559b99689ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 20 May 2020 12:30:17 +0200 Subject: [PATCH 04/11] Removing logs --- front/src/Connexion.ts | 1 - front/src/Phaser/Game/GameScene.ts | 3 --- 2 files changed, 4 deletions(-) diff --git a/front/src/Connexion.ts b/front/src/Connexion.ts index 78789fef..e1a35ec7 100644 --- a/front/src/Connexion.ts +++ b/front/src/Connexion.ts @@ -252,7 +252,6 @@ export class Connexion implements ConnexionInterface { */ joinARoom(roomId: string): void { this.socket.emit(EventMessage.JOIN_ROOM, roomId, (userPositions: MessageUserPositionInterface[]) => { - console.log("GOT AN ANSWER FROM JOIN_ROOM"); this.GameManager.initUsersPosition(userPositions); }); this.lastRoom = roomId; diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 2f9e5b71..0b0d08ed 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -392,10 +392,7 @@ export class GameScene extends Phaser.Scene { return; } this.addPlayer(userPosition); - console.log("Added player ", userPosition) }); - - console.log("Initialized with ", usersPosition); } private findPlayerInMap(UserId : string) : GamerInterface | null{ From ab798b1c09a9fdfbb52338c456f7214d998e4d16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 22 May 2020 22:59:43 +0200 Subject: [PATCH 05/11] Changing the "Point" notion to add a notion of "moving" in addition to the notion of direction. Also, refactoring JOIN_ROOM event to add complete position. --- back/src/Controller/IoSocketController.ts | 30 +++++----- back/src/Model/Websocket/MessageUserJoined.ts | 4 +- .../Model/Websocket/MessageUserPosition.ts | 2 +- front/src/Connexion.ts | 59 +++++++----------- front/src/Phaser/Entity/PlayableCaracter.ts | 2 +- front/src/Phaser/Game/GameManager.ts | 9 +-- front/src/Phaser/Game/GameScene.ts | 14 +++-- front/src/Phaser/Player/Animation.ts | 9 --- front/src/Phaser/Player/Player.ts | 60 ++++++++++++++----- 9 files changed, 101 insertions(+), 88 deletions(-) diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index 292db31f..8332d782 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -89,12 +89,19 @@ export class IoSocketController { x: user x position on map y: user y position on map */ - socket.on(SockerIoEvent.JOIN_ROOM, (roomId: any, answerFn): void => { + socket.on(SockerIoEvent.JOIN_ROOM, (message: any, answerFn): void => { try { + let roomId = message.roomId; + if (typeof(roomId) !== 'string') { socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Expected roomId as a string.'}); return; } + let position = this.hydratePositionReceive(message.position); + if (position instanceof Error) { + socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: position.message}); + return; + } let Client = (socket as ExSocketInterface); @@ -106,12 +113,12 @@ export class IoSocketController { this.leaveRoom(Client); //join new previous room - let world = this.joinRoom(Client, roomId); + let world = this.joinRoom(Client, roomId, position); //add function to refresh position user in real time. //this.refreshUserPosition(Client); - let messageUserJoined = new MessageUserJoined(Client.id, Client.name, Client.character); + let messageUserJoined = new MessageUserJoined(Client.id, Client.name, Client.character, Client.position); socket.to(roomId).emit(SockerIoEvent.JOIN_ROOM, messageUserJoined); @@ -140,16 +147,13 @@ export class IoSocketController { // sending to all clients in room except sender Client.position = position; - //refresh position of all user in all rooms in real time - //this.refreshUserPosition(Client); - // update position in the world let world = this.Worlds.get(Client.roomId); if (!world) { console.error("Could not find world with id '", Client.roomId, "'"); return; } - world.updatePosition(Client, Client.position); + world.updatePosition(Client, position); socket.to(Client.roomId).emit(SockerIoEvent.USER_MOVED, new MessageUserMoved(Client.id, Client.position)); } catch (e) { @@ -182,10 +186,6 @@ export class IoSocketController { try { let Client = (socket as ExSocketInterface); - if (Client.roomId) { - socket.to(Client.roomId).emit(SockerIoEvent.USER_LEFT, socket.id); - } - //leave room this.leaveRoom(Client); @@ -238,11 +238,11 @@ export class IoSocketController { } } - private joinRoom(Client : ExSocketInterface, roomId: string): World { + private joinRoom(Client : ExSocketInterface, roomId: string, position: Point): World { //join user in room Client.join(roomId); Client.roomId = roomId; - Client.position = new Point(-1000, -1000); + Client.position = position; //check and create new world for a room let world = this.Worlds.get(roomId) @@ -311,10 +311,10 @@ export class IoSocketController { //Hydrate and manage error hydratePositionReceive(message: any): Point | Error { try { - if (!message.x || !message.y || !message.direction) { + if (!message.x || !message.y || !message.direction || message.moving === undefined) { return new Error("invalid point message sent"); } - return new Point(message.x, message.y, message.direction); + return new Point(message.x, message.y, message.direction, message.moving); } catch (err) { //TODO log error return new Error(err); diff --git a/back/src/Model/Websocket/MessageUserJoined.ts b/back/src/Model/Websocket/MessageUserJoined.ts index e12f5dc3..fff9db5d 100644 --- a/back/src/Model/Websocket/MessageUserJoined.ts +++ b/back/src/Model/Websocket/MessageUserJoined.ts @@ -1,5 +1,7 @@ +import {Point} from "./MessageUserPosition"; +import {PointInterface} from "_Model/Websocket/PointInterface"; export class MessageUserJoined { - constructor(public userId: string, public name: string, public character: string) { + constructor(public userId: string, public name: string, public character: string, public position: PointInterface) { } } diff --git a/back/src/Model/Websocket/MessageUserPosition.ts b/back/src/Model/Websocket/MessageUserPosition.ts index 613e47e6..ed604940 100644 --- a/back/src/Model/Websocket/MessageUserPosition.ts +++ b/back/src/Model/Websocket/MessageUserPosition.ts @@ -1,7 +1,7 @@ import {PointInterface} from "./PointInterface"; export class Point implements PointInterface{ - constructor(public x : number, public y : number, public direction : string = "none") { + constructor(public x : number, public y : number, public direction : string = "none", public moving : boolean = false) { } } diff --git a/front/src/Connexion.ts b/front/src/Connexion.ts index e1a35ec7..1b963129 100644 --- a/front/src/Connexion.ts +++ b/front/src/Connexion.ts @@ -6,6 +6,7 @@ import {SetPlayerDetailsMessage} from "./Messages/SetPlayerDetailsMessage"; const SocketIo = require('socket.io-client'); import Socket = SocketIOClient.Socket; +import {PlayerAnimationNames} from "./Phaser/Player/Animation"; enum EventMessage{ @@ -42,20 +43,14 @@ export interface PointInterface { x: number; y: number; direction : string; + moving: boolean; } export class Point implements PointInterface{ - x: number; - y: number; - direction : string; - - constructor(x : number, y : number, direction : string = "none") { + constructor(public x : number, public y : number, public direction : string = PlayerAnimationNames.WalkDown, public moving : boolean = false) { if(x === null || y === null){ throw Error("position x and y cannot be null"); } - this.x = x; - this.y = y; - this.direction = direction; } } @@ -84,6 +79,7 @@ export interface MessageUserJoined { userId: string; name: string; character: string; + position: PointInterface } export interface ListMessageUserPositionInterface { @@ -104,7 +100,8 @@ class ListMessageUserPosition { new Point( userPosition.position.x, userPosition.position.y, - userPosition.position.direction + userPosition.position.direction, + userPosition.position.moving ), userPosition.name, userPosition.character @@ -133,9 +130,9 @@ export interface ConnexionInterface { loadMaps(): Promise; - joinARoom(roomId: string, character: string): void; + joinARoom(roomId: string, startX: number, startY: number, direction: string, moving: boolean): void; - sharePosition(x: number, y: number, direction: string, roomId: string, character: string): void; + sharePosition(x: number, y: number, direction: string, moving: boolean): void; positionOfAllUser(): void; @@ -215,20 +212,23 @@ export class Connexion implements ConnexionInterface { //if try to reconnect with last position if(this.lastRoom) { //join the room - this.joinARoom( - this.lastRoom - ); + 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) { + /*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.direction, + this.lastPositionShared.moving ); - } + }*/ resolve(this); }); @@ -245,31 +245,18 @@ export class Connexion implements ConnexionInterface { }); } - /** - * - * @param roomId - * @param character - */ - joinARoom(roomId: string): void { - this.socket.emit(EventMessage.JOIN_ROOM, roomId, (userPositions: MessageUserPositionInterface[]) => { + 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.GameManager.initUsersPosition(userPositions); }); this.lastRoom = roomId; } - /** - * - * @param x - * @param y - * @param character - * @param roomId - * @param direction - */ - sharePosition(x : number, y : number, direction : string = "none") : void{ + sharePosition(x : number, y : number, direction : string, moving: boolean) : void{ if(!this.socket){ return; } - let point = new Point(x, y, direction); + let point = new Point(x, y, direction, moving); this.lastPositionShared = point; this.socket.emit(EventMessage.USER_POSITION, point); } @@ -279,11 +266,11 @@ export class Connexion implements ConnexionInterface { * [ * { * userId: , - * roomId: , * position: { * x : , * y : , - * direction: + * direction: , + * moving: * } * }, * ... diff --git a/front/src/Phaser/Entity/PlayableCaracter.ts b/front/src/Phaser/Entity/PlayableCaracter.ts index e126dbb9..826bfc6a 100644 --- a/front/src/Phaser/Entity/PlayableCaracter.ts +++ b/front/src/Phaser/Entity/PlayableCaracter.ts @@ -26,7 +26,7 @@ export const PLAYER_RESOURCES: Array = [ export class PlayableCaracter extends Phaser.Physics.Arcade.Sprite { private bubble: SpeechBubble; - private playerName: BitmapText; + private readonly playerName: BitmapText; public PlayerValue: string; public PlayerTexture: string; diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index fcb386d1..6b848aa1 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -16,6 +16,7 @@ export enum StatusGameManagerEnum { export interface HasMovedEvent { direction: string; + moving: boolean; x: number; y: number; } @@ -71,8 +72,8 @@ export class GameManager { this.status = StatusGameManagerEnum.CURRENT_USER_CREATED; } - joinRoom(sceneKey : string){ - this.ConnexionInstance.joinARoom(sceneKey); + joinRoom(sceneKey: string, startX: number, startY: number, direction: string, moving: boolean){ + this.ConnexionInstance.joinARoom(sceneKey, startX, startY, direction, moving); } onUserJoins(message: MessageUserJoined): void { @@ -80,7 +81,7 @@ export class GameManager { userId: message.userId, character: message.character, name: message.name, - position: new Point(-1000, -1000) + position: message.position } this.currentGameScene.addPlayer(userMessage); } @@ -159,7 +160,7 @@ export class GameManager { } pushPlayerPosition(event: HasMovedEvent) { - this.ConnexionInstance.sharePosition(event.x, event.y, event.direction); + this.ConnexionInstance.sharePosition(event.x, event.y, event.direction, event.moving); } loadMap(mapUrl: string, scene: ScenePlugin): string { diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 0b0d08ed..4a003b33 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -13,6 +13,7 @@ import Texture = Phaser.Textures.Texture; import Sprite = Phaser.GameObjects.Sprite; import CanvasTexture = Phaser.Textures.CanvasTexture; import {AddPlayerInterface} from "./AddPlayerInterface"; +import {PlayerAnimationNames} from "../Player/Animation"; export enum Textures { Player = "male1" @@ -273,16 +274,17 @@ export class GameScene extends Phaser.Scene { this.startX, this.startY, this.GameManager.getPlayerName(), - this.GameManager.getCharacterSelected() + this.GameManager.getCharacterSelected(), + PlayerAnimationNames.WalkDown, + false ); - this.CurrentPlayer.initAnimation(); //create collision this.createCollisionWithPlayer(); this.createCollisionObject(); //join room - this.GameManager.joinRoom(this.scene.key); + this.GameManager.joinRoom(this.scene.key, this.startX, this.startY, PlayerAnimationNames.WalkDown, false); //listen event to share position of user this.CurrentPlayer.on(hasMovedEventName, this.pushPlayerPosition.bind(this)) @@ -415,9 +417,10 @@ export class GameScene extends Phaser.Scene { addPlayerData.position.x, addPlayerData.position.y, addPlayerData.name, - addPlayerData.character + addPlayerData.character, + addPlayerData.position.direction, + addPlayerData.position.moving ); - player.initAnimation(); this.MapPlayers.add(player); this.MapPlayersByKey.set(player.userId, player); player.updatePosition(addPlayerData.position); @@ -429,6 +432,7 @@ export class GameScene extends Phaser.Scene { } public removePlayer(userId: string) { + console.log('Removing player ', userId) let player = this.MapPlayersByKey.get(userId); if (player === undefined) { console.error('Cannot find user with id ', userId); diff --git a/front/src/Phaser/Player/Animation.ts b/front/src/Phaser/Player/Animation.ts index 332c862d..10cee1e8 100644 --- a/front/src/Phaser/Player/Animation.ts +++ b/front/src/Phaser/Player/Animation.ts @@ -14,7 +14,6 @@ export enum PlayerAnimationNames { WalkLeft = 'left', WalkUp = 'up', WalkRight = 'right', - None = 'none', } export const getPlayerAnimations = (name: string = Textures.Player): AnimationData[] => { @@ -48,11 +47,3 @@ export const getPlayerAnimations = (name: string = Textures.Player): AnimationDa repeat: -1 }]; }; - -export const playAnimation = (Player : Phaser.GameObjects.Sprite, direction : string) => { - if (direction !== PlayerAnimationNames.None && (!Player.anims.currentAnim || Player.anims.currentAnim.key !== direction)) { - Player.anims.play(direction); - } else if (direction === PlayerAnimationNames.None && Player.anims.currentAnim) { - Player.anims.stop(); - } -} diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index 26acb1a3..850be563 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -1,4 +1,4 @@ -import {getPlayerAnimations, playAnimation, PlayerAnimationNames} from "./Animation"; +import {getPlayerAnimations, PlayerAnimationNames} from "./Animation"; import {GameScene, Textures} from "../Game/GameScene"; import {MessageUserPositionInterface, PointInterface} from "../../Connexion"; import {ActiveEventList, UserInputEvent, UserInputManager} from "../UserInput/UserInputManager"; @@ -7,14 +7,12 @@ import {PlayableCaracter} from "../Entity/PlayableCaracter"; export const hasMovedEventName = "hasMoved"; export interface CurrentGamerInterface extends PlayableCaracter{ - initAnimation() : void; moveUser(delta: number) : void; say(text : string) : void; } export interface GamerInterface extends PlayableCaracter{ userId : string; - initAnimation() : void; updatePosition(position: PointInterface): void; say(text : string) : void; } @@ -22,7 +20,8 @@ export interface GamerInterface extends PlayableCaracter{ export class Player extends PlayableCaracter implements CurrentGamerInterface, GamerInterface { userId: string; userInputManager: UserInputManager; - previousMove: string; + previousDirection: string; + wasMoving: boolean; constructor( userId: string, @@ -30,7 +29,9 @@ export class Player extends PlayableCaracter implements CurrentGamerInterface, G x: number, y: number, name: string, - PlayerTexture: string = Textures.Player + PlayerTexture: string, + direction: string, + moving: boolean ) { super(Scene, x, y, PlayerTexture, name, 1); @@ -42,9 +43,18 @@ export class Player extends PlayableCaracter implements CurrentGamerInterface, G //the current player model should be push away by other players to prevent conflict this.setImmovable(false); + this.initAnimation(); + + console.warn("Start direction for added player ", direction) + console.warn("Position ", x, y) + /*this.play(`${PlayerTexture}-${direction}`, true); + if (!moving) { + this.stop(); + }*/ + this.playAnimation(direction, moving); } - initAnimation(): void { + private initAnimation(): void { getPlayerAnimations(this.PlayerTexture).forEach(d => { this.scene.anims.create({ key: d.key, @@ -58,6 +68,7 @@ export class Player extends PlayableCaracter implements CurrentGamerInterface, G moveUser(delta: number): void { //if user client on shift, camera and player speed let direction = null; + let moving = false; let activeEvents = this.userInputManager.getEventListForGameTick(); let speedMultiplier = activeEvents.get(UserInputEvent.SpeedUp) ? 25 : 9; @@ -67,39 +78,56 @@ export class Player extends PlayableCaracter implements CurrentGamerInterface, G let y = 0; if (activeEvents.get(UserInputEvent.MoveUp)) { y = - moveAmount; - direction = `${this.PlayerTexture}-${PlayerAnimationNames.WalkUp}`; + direction = PlayerAnimationNames.WalkUp; + moving = true; } else if (activeEvents.get(UserInputEvent.MoveDown)) { y = moveAmount; - direction = `${this.PlayerTexture}-${PlayerAnimationNames.WalkDown}`; + direction = PlayerAnimationNames.WalkDown; + moving = true; } if (activeEvents.get(UserInputEvent.MoveLeft)) { x = -moveAmount; - direction = `${this.PlayerTexture}-${PlayerAnimationNames.WalkLeft}`; + direction = PlayerAnimationNames.WalkLeft; + moving = true; } else if (activeEvents.get(UserInputEvent.MoveRight)) { x = moveAmount; - direction = `${this.PlayerTexture}-${PlayerAnimationNames.WalkRight}`; + direction = PlayerAnimationNames.WalkRight; + moving = true; } if (x !== 0 || y !== 0) { this.move(x, y); - this.emit(hasMovedEventName, {direction, x: this.x, y: this.y}); + this.emit(hasMovedEventName, {moving, direction, x: this.x, y: this.y}); } else { - if (this.previousMove !== PlayerAnimationNames.None) { - direction = PlayerAnimationNames.None; + if (this.wasMoving) { + //direction = PlayerAnimationNames.None; this.stop(); - this.emit(hasMovedEventName, {direction, x: this.x, y: this.y}); + this.emit(hasMovedEventName, {moving, direction: this.previousDirection, x: this.x, y: this.y}); } } if (direction !== null) { - this.previousMove = direction; + this.previousDirection = direction; } + this.wasMoving = moving; } //todo: put this method into the NonPlayer class instead updatePosition(position: PointInterface): void { - playAnimation(this, position.direction); + 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.anims.play(this.PlayerTexture+'-'+direction); + } else if (!moving) { + /*if (this.anims.currentAnim) { + this.anims.stop(); + }*/ + this.anims.play(this.PlayerTexture+'-'+direction); + this.anims.stop(); + } + } } From ab32021fc0d7754605ab9bac4b6788e5277388aa Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Sat, 23 May 2020 14:00:36 +0200 Subject: [PATCH 06/11] Fix mediam stream manage and server back down --- back/src/Controller/AuthenticateController.ts | 10 ++++---- back/src/Controller/IoSocketController.ts | 23 +++++++++++++++--- front/src/Connexion.ts | 13 ++++------ front/src/Phaser/Game/GameScene.ts | 11 ++++++++- front/src/WebRtc/SimplePeer.ts | 24 +++++++++++++++++-- 5 files changed, 61 insertions(+), 20 deletions(-) diff --git a/back/src/Controller/AuthenticateController.ts b/back/src/Controller/AuthenticateController.ts index db5fb1ce..4e617509 100644 --- a/back/src/Controller/AuthenticateController.ts +++ b/back/src/Controller/AuthenticateController.ts @@ -15,21 +15,21 @@ export class AuthenticateController { //permit to login on application. Return token to connect on Websocket IO. login(){ // For now, let's completely forget the /login route. - /*this.App.post("/login", (req: Request, res: Response) => { + this.App.post("/login", (req: Request, res: Response) => { let param = req.body; - if(!param.email){ + /*if(!param.name){ return res.status(BAD_REQUEST).send({ message: "email parameter is empty" }); - } + }*/ //TODO check user email for The Coding Machine game let userId = uuid(); - let token = Jwt.sign({email: param.email, userId: userId}, SECRET_KEY, {expiresIn: '24h'}); + let token = Jwt.sign({name: param.name, userId: userId}, SECRET_KEY, {expiresIn: '24h'}); return res.status(OK).send({ token: token, mapUrlStart: URL_ROOM_STARTED, userId: userId, }); - });*/ + }); } } diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index 8332d782..a93f7738 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -39,25 +39,42 @@ export class IoSocketController { // Authentication with token. it will be decoded and stored in the socket. // Completely commented for now, as we do not use the "/login" route at all. - /*this.Io.use((socket: Socket, next) => { + this.Io.use((socket: Socket, next) => { if (!socket.handshake.query || !socket.handshake.query.token) { return next(new Error('Authentication error')); } if(this.searchClientByToken(socket.handshake.query.token)){ return next(new Error('Authentication error')); } - Jwt.verify(socket.handshake.query.token, SECRET_KEY, (err: JsonWebTokenError, tokenDecoded: object) => { + Jwt.verify(socket.handshake.query.token, SECRET_KEY, (err: JsonWebTokenError, tokenDecoded: any) => { if (err) { return next(new Error('Authentication error')); } (socket as ExSocketInterface).token = tokenDecoded; + (socket as ExSocketInterface).id = tokenDecoded.userId; next(); }); - });*/ + }); this.ioConnection(); } + /** + * + * @param token + */ + searchClientByToken(token: string): ExSocketInterface | null { + let clients: Array = Object.values(this.Io.sockets.sockets); + for (let i = 0; i < clients.length; i++) { + let client: ExSocketInterface = clients[i]; + if (client.token !== token) { + continue + } + return client; + } + return null; + } + private sendUpdateGroupEvent(group: Group): void { // Let's get the room of the group. To do this, let's get anyone in the group and find its room. // Note: this is suboptimal diff --git a/front/src/Connexion.ts b/front/src/Connexion.ts index 1b963129..e5a73aaf 100644 --- a/front/src/Connexion.ts +++ b/front/src/Connexion.ts @@ -165,25 +165,20 @@ export class Connexion implements ConnexionInterface { createConnexion(name: string, characterSelected: string): Promise { this.name = name; this.character = characterSelected; - /*return Axios.post(`${API_URL}/login`, {email: this.email}) + return Axios.post(`${API_URL}/login`, {name: name}) .then((res) => { this.token = res.data.token; - this.userId = res.data.userId;*/ - this.socket = SocketIo(`${API_URL}`, { - /*query: { + query: { token: this.token - }*/ + } }); - return this.connectSocketServer(); - - /* return res.data; }) .catch((err) => { console.error(err); throw err; - });*/ + }); } /** diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 4a003b33..c79fb740 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -3,7 +3,7 @@ import { GroupCreatedUpdatedMessageInterface, MessageUserJoined, MessageUserMovedInterface, - MessageUserPositionInterface + MessageUserPositionInterface, PointInterface } from "../../Connexion"; import {CurrentGamerInterface, GamerInterface, hasMovedEventName, Player} from "../Player/Player"; import { DEBUG_MODE, RESOLUTION, ROOM, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable"; @@ -14,6 +14,7 @@ import Sprite = Phaser.GameObjects.Sprite; import CanvasTexture = Phaser.Textures.CanvasTexture; import {AddPlayerInterface} from "./AddPlayerInterface"; import {PlayerAnimationNames} from "../Player/Animation"; +import {MessageUserMoved} from "../../../../back/src/Model/Websocket/MessageUserMoved"; export enum Textures { Player = "male1" @@ -410,6 +411,14 @@ export class GameScene extends Phaser.Scene { * Create new player */ public addPlayer(addPlayerData : AddPlayerInterface) : void{ + //check if exist player, if exist, move position + if(this.MapPlayersByKey.has(addPlayerData.userId)){ + this.updatePlayerPosition({ + userId: addPlayerData.userId, + position: addPlayerData.position + }); + return; + } //initialise player let player = new Player( addPlayerData.userId, diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 595e83b2..b301876e 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -141,9 +141,9 @@ export class SimplePeer implements SimplePeerInterface{ this.stream(user.userId, stream); }); - this.PeerConnexionArray.get(user.userId).on('track', (track: MediaStreamTrack, stream: MediaStream) => { + /*this.PeerConnexionArray.get(user.userId).on('track', (track: MediaStreamTrack, stream: MediaStream) => { this.stream(user.userId, stream); - }); + });*/ this.PeerConnexionArray.get(user.userId).on('close', () => { this.closeConnexion(user.userId); @@ -157,6 +157,13 @@ export class SimplePeer implements SimplePeerInterface{ console.info(`connect => ${user.userId}`); }); + this.PeerConnexionArray.get(user.userId).on('data', (chunk: Buffer) => { + let data = JSON.parse(chunk.toString('utf8')); + if(data.type === "stream"){ + this.stream(user.userId, data.stream); + } + }); + this.addMedia(user.userId); } @@ -205,6 +212,11 @@ export class SimplePeer implements SimplePeerInterface{ * @param stream */ private stream(userId : any, stream: MediaStream) { + if(!stream){ + this.MediaManager.disabledVideoByUserId(userId); + this.MediaManager.disabledMicrophoneByUserId(userId); + return; + } this.MediaManager.addStreamRemoteVideo(userId, stream); } @@ -216,6 +228,14 @@ export class SimplePeer implements SimplePeerInterface{ try { let transceiver : any = null; if(!this.MediaManager.localStream){ + //send fake signal + if(!this.PeerConnexionArray.has(userId)){ + return; + } + this.PeerConnexionArray.get(userId).write(new Buffer(JSON.stringify({ + type: "stream", + stream: null + }))); return; } this.MediaManager.localStream.getTracks().forEach( From 0c9cbca7658ef05f9cfaaa3b1faf2932728b0616 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Sat, 23 May 2020 15:04:25 +0200 Subject: [PATCH 07/11] Use `userId` generated by back end in all message --- back/src/Controller/IoSocketController.ts | 22 +++++++++---------- back/src/Model/Websocket/ExSocketInterface.ts | 2 +- back/src/Model/Websocket/Identificable.ts | 2 +- back/src/Model/World.ts | 12 +++++----- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index a93f7738..f6f9f41f 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -51,7 +51,7 @@ export class IoSocketController { return next(new Error('Authentication error')); } (socket as ExSocketInterface).token = tokenDecoded; - (socket as ExSocketInterface).id = tokenDecoded.userId; + (socket as ExSocketInterface).userId = tokenDecoded.userId; next(); }); }); @@ -97,7 +97,8 @@ export class IoSocketController { ioConnection() { this.Io.on(SockerIoEvent.CONNECTION, (socket: Socket) => { - this.sockets.set(socket.id, socket as ExSocketInterface); + let client : ExSocketInterface = socket as ExSocketInterface; + this.sockets.set(client.userId, client); /*join-rom event permit to join one room. message : userId : user identification @@ -135,7 +136,7 @@ export class IoSocketController { //add function to refresh position user in real time. //this.refreshUserPosition(Client); - let messageUserJoined = new MessageUserJoined(Client.id, Client.name, Client.character, Client.position); + let messageUserJoined = new MessageUserJoined(Client.userId, Client.name, Client.character, Client.position); socket.to(roomId).emit(SockerIoEvent.JOIN_ROOM, messageUserJoined); @@ -172,7 +173,7 @@ export class IoSocketController { } world.updatePosition(Client, position); - socket.to(Client.roomId).emit(SockerIoEvent.USER_MOVED, new MessageUserMoved(Client.id, Client.position)); + socket.to(Client.roomId).emit(SockerIoEvent.USER_MOVED, new MessageUserMoved(Client.userId, Client.position)); } catch (e) { console.error('An error occurred on "user_position" event'); console.error(e); @@ -200,9 +201,8 @@ export class IoSocketController { }); socket.on(SockerIoEvent.DISCONNECT, () => { + let Client = (socket as ExSocketInterface); try { - let Client = (socket as ExSocketInterface); - //leave room this.leaveRoom(Client); @@ -218,7 +218,7 @@ export class IoSocketController { console.error('An error occurred on "disconnect"'); console.error(e); } - this.sockets.delete(socket.id); + this.sockets.delete(Client.userId); }); // Let's send the user id to the user @@ -226,7 +226,7 @@ export class IoSocketController { let Client = (socket as ExSocketInterface); Client.name = playerDetails.name; Client.character = playerDetails.character; - answerFn(socket.id); + answerFn(Client.userId); }); }); } @@ -242,7 +242,7 @@ export class IoSocketController { leaveRoom(Client : ExSocketInterface){ // leave previous room and world if(Client.roomId){ - Client.to(Client.roomId).emit(SockerIoEvent.USER_LEFT, Client.id); + Client.to(Client.roomId).emit(SockerIoEvent.USER_LEFT, Client.userId); //user leave previous world let world : World|undefined = this.Worlds.get(Client.roomId); @@ -310,11 +310,11 @@ export class IoSocketController { clients.forEach((client: ExSocketInterface, index: number) => { let clientsId = clients.reduce((tabs: Array, clientId: ExSocketInterface, indexClientId: number) => { - if (!clientId.id || clientId.id === client.id) { + if (!clientId.userId || clientId.userId === client.userId) { return tabs; } tabs.push({ - userId: clientId.id, + userId: clientId.userId, name: clientId.name, initiator: index <= indexClientId }); diff --git a/back/src/Model/Websocket/ExSocketInterface.ts b/back/src/Model/Websocket/ExSocketInterface.ts index c55a9759..df72321f 100644 --- a/back/src/Model/Websocket/ExSocketInterface.ts +++ b/back/src/Model/Websocket/ExSocketInterface.ts @@ -6,7 +6,7 @@ export interface ExSocketInterface extends Socket, Identificable { token: any; roomId: string; webRtcRoomId: string; - //userId: string; + userId: string; name: string; character: string; position: PointInterface; diff --git a/back/src/Model/Websocket/Identificable.ts b/back/src/Model/Websocket/Identificable.ts index 8c344259..4e3228ae 100644 --- a/back/src/Model/Websocket/Identificable.ts +++ b/back/src/Model/Websocket/Identificable.ts @@ -1,3 +1,3 @@ export interface Identificable { - id: string; + userId: string; } diff --git a/back/src/Model/World.ts b/back/src/Model/World.ts index 0529edc3..5f70a32f 100644 --- a/back/src/Model/World.ts +++ b/back/src/Model/World.ts @@ -53,8 +53,8 @@ export class World { } public join(socket : Identificable, userPosition: PointInterface): void { - this.users.set(socket.id, { - id: socket.id, + this.users.set(socket.userId, { + id: socket.userId, position: userPosition }); // Let's call update position to trigger the join / leave room @@ -62,18 +62,18 @@ export class World { } public leave(user : Identificable){ - let userObj = this.users.get(user.id); + let userObj = this.users.get(user.userId); if (userObj === undefined) { - console.warn('User ', user.id, 'does not belong to world! It should!'); + console.warn('User ', user.userId, 'does not belong to world! It should!'); } if (userObj !== undefined && typeof userObj.group !== 'undefined') { this.leaveGroup(userObj); } - this.users.delete(user.id); + this.users.delete(user.userId); } public updatePosition(socket : Identificable, userPosition: PointInterface): void { - let user = this.users.get(socket.id); + let user = this.users.get(socket.userId); if(typeof user === 'undefined') { return; } From 36858f8747c235ac7565c123fbb9bdb85bbbac6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Sat, 23 May 2020 15:43:26 +0200 Subject: [PATCH 08/11] Adding a special scene to wait for server reconnection --- front/src/Connexion.ts | 6 ++- front/src/Phaser/Game/GameManager.ts | 18 ++++++++- front/src/Phaser/Game/GameScene.ts | 19 ++++++++-- .../Phaser/Reconnecting/ReconnectingScene.ts | 38 +++++++++++++++++++ front/src/index.ts | 4 +- 5 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 front/src/Phaser/Reconnecting/ReconnectingScene.ts diff --git a/front/src/Connexion.ts b/front/src/Connexion.ts index 1b963129..1f4d26cd 100644 --- a/front/src/Connexion.ts +++ b/front/src/Connexion.ts @@ -210,14 +210,14 @@ export class Connexion implements ConnexionInterface { }); //if try to reconnect with last position - if(this.lastRoom) { + /*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) { @@ -342,11 +342,13 @@ export class Connexion implements ConnexionInterface { disconnectServer(): void { this.socket.on(EventMessage.CONNECT_ERROR, () => { MessageUI.warningMessage("Trying to connect!"); + this.GameManager.switchToDisconnectedScene(); }); this.socket.on(EventMessage.RECONNECT, () => { MessageUI.removeMessage(); this.connectSocketServer(); + this.GameManager.reconnectToGameScene(this.lastPositionShared); }); } diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index 6b848aa1..7a439fbe 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -2,12 +2,18 @@ import {GameScene} from "./GameScene"; import { Connexion, GroupCreatedUpdatedMessageInterface, - ListMessageUserPositionInterface, MessageUserJoined, MessageUserMovedInterface, MessageUserPositionInterface, Point + ListMessageUserPositionInterface, + MessageUserJoined, + MessageUserMovedInterface, + MessageUserPositionInterface, + Point, + PointInterface } from "../../Connexion"; import {SimplePeerInterface, SimplePeer} from "../../WebRtc/SimplePeer"; import {getMapKeyByUrl} from "../Login/LogincScene"; import ScenePlugin = Phaser.Scenes.ScenePlugin; import {AddPlayerInterface} from "./AddPlayerInterface"; +import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene"; export enum StatusGameManagerEnum { IN_PROGRESS = 1, @@ -174,6 +180,16 @@ export class GameManager { } return sceneKey; } + + private oldSceneKey : string; + switchToDisconnectedScene(): void { + this.oldSceneKey = this.currentGameScene.scene.key; + this.currentGameScene.scene.start(ReconnectingSceneName); + } + + reconnectToGameScene(lastPositionShared: PointInterface) { + this.currentGameScene.scene.start(this.oldSceneKey, { initPosition: lastPositionShared }); + } } export const gameManager = new GameManager(); diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 4a003b33..9aa6ece7 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -3,7 +3,7 @@ import { GroupCreatedUpdatedMessageInterface, MessageUserJoined, MessageUserMovedInterface, - MessageUserPositionInterface + MessageUserPositionInterface, PointInterface, PositionInterface } from "../../Connexion"; import {CurrentGamerInterface, GamerInterface, hasMovedEventName, Player} from "../Player/Player"; import { DEBUG_MODE, RESOLUTION, ROOM, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable"; @@ -19,6 +19,10 @@ export enum Textures { Player = "male1" } +interface GameSceneInitInterface { + initPosition: PointInterface|null +} + export class GameScene extends Phaser.Scene { GameManager : GameManager; Terrains : Array; @@ -33,6 +37,7 @@ export class GameScene extends Phaser.Scene { startX = 704;// 22 case startY = 32; // 1 case circleTexture: CanvasTexture; + initPosition: PositionInterface; MapKey: string; MapUrlFile: string; @@ -85,7 +90,9 @@ export class GameScene extends Phaser.Scene { } //hook initialisation - init() {} + init(initData : GameSceneInitInterface) { + this.initPosition = initData.initPosition; + } //hook create scene create(): void { @@ -209,7 +216,13 @@ export class GameScene extends Phaser.Scene { /** * @param layer */ - private startUser(layer: ITiledMapLayer){ + private startUser(layer: ITiledMapLayer): void { + if (this.initPosition !== undefined) { + this.startX = this.initPosition.x; + this.startY = this.initPosition.y; + return; + } + let tiles : any = layer.data; tiles.forEach((objectKey : number, key: number) => { if(objectKey === 0){ diff --git a/front/src/Phaser/Reconnecting/ReconnectingScene.ts b/front/src/Phaser/Reconnecting/ReconnectingScene.ts new file mode 100644 index 00000000..02e9f321 --- /dev/null +++ b/front/src/Phaser/Reconnecting/ReconnectingScene.ts @@ -0,0 +1,38 @@ +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/PlayableCaracter"; +import {cypressAsserter} from "../../Cypress/CypressAsserter"; + +export const ReconnectingSceneName = "ReconnectingScene"; +enum ReconnectingTextures { + icon = "icon", + mainFont = "main_font" +} + +export class ReconnectingScene extends Phaser.Scene { + private reconnectingField: TextField; + private logo: Image; + + constructor() { + super({ + key: ReconnectingSceneName + }); + } + + preload() { + this.load.image(ReconnectingTextures.icon, "resources/logos/tcm_full.png"); + // Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap + this.load.bitmapFont(ReconnectingTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); + } + + create() { + this.logo = new Image(this, this.game.renderer.width - 30, this.game.renderer.height - 20, ReconnectingTextures.icon); + this.add.existing(this.logo); + + this.reconnectingField = new TextField(this, 10, this.game.renderer.height - 35, "Connection lost. Reconnecting..."); + } +} diff --git a/front/src/index.ts b/front/src/index.ts index e6c8ce40..2ab8d91a 100644 --- a/front/src/index.ts +++ b/front/src/index.ts @@ -3,13 +3,15 @@ import GameConfig = Phaser.Types.Core.GameConfig; import {DEBUG_MODE, RESOLUTION} from "./Enum/EnvironmentVariable"; import {cypressAsserter} from "./Cypress/CypressAsserter"; import {LogincScene} from "./Phaser/Login/LogincScene"; +import {ReconnectingScene} from "./Phaser/Reconnecting/ReconnectingScene"; +import {gameManager} from "./Phaser/Game/GameManager"; const config: GameConfig = { title: "Office game", width: window.innerWidth / RESOLUTION, height: window.innerHeight / RESOLUTION, parent: "game", - scene: [LogincScene], + scene: [LogincScene, ReconnectingScene], zoom: RESOLUTION, physics: { default: "arcade", From e41eda9979fa37404d55ecf01754acbba8a2e660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Sat, 23 May 2020 16:13:37 +0200 Subject: [PATCH 09/11] Prettifying lost connection screen --- front/src/Connexion.ts | 2 -- .../Phaser/Reconnecting/ReconnectingScene.ts | 20 ++++++++++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/front/src/Connexion.ts b/front/src/Connexion.ts index 1f4d26cd..874ecc7d 100644 --- a/front/src/Connexion.ts +++ b/front/src/Connexion.ts @@ -341,12 +341,10 @@ export class Connexion implements ConnexionInterface { disconnectServer(): void { this.socket.on(EventMessage.CONNECT_ERROR, () => { - MessageUI.warningMessage("Trying to connect!"); this.GameManager.switchToDisconnectedScene(); }); this.socket.on(EventMessage.RECONNECT, () => { - MessageUI.removeMessage(); this.connectSocketServer(); this.GameManager.reconnectToGameScene(this.lastPositionShared); }); diff --git a/front/src/Phaser/Reconnecting/ReconnectingScene.ts b/front/src/Phaser/Reconnecting/ReconnectingScene.ts index 02e9f321..273820b7 100644 --- a/front/src/Phaser/Reconnecting/ReconnectingScene.ts +++ b/front/src/Phaser/Reconnecting/ReconnectingScene.ts @@ -6,6 +6,7 @@ import Image = Phaser.GameObjects.Image; import Rectangle = Phaser.GameObjects.Rectangle; import {PLAYER_RESOURCES} from "../Entity/PlayableCaracter"; import {cypressAsserter} from "../../Cypress/CypressAsserter"; +import Sprite = Phaser.GameObjects.Sprite; export const ReconnectingSceneName = "ReconnectingScene"; enum ReconnectingTextures { @@ -16,6 +17,7 @@ enum ReconnectingTextures { export class ReconnectingScene extends Phaser.Scene { private reconnectingField: TextField; private logo: Image; + private cat: Sprite; constructor() { super({ @@ -27,12 +29,28 @@ export class ReconnectingScene extends Phaser.Scene { this.load.image(ReconnectingTextures.icon, "resources/logos/tcm_full.png"); // Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap this.load.bitmapFont(ReconnectingTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); + this.load.spritesheet( + 'cat', + 'resources/characters/pipoya/Cat 01-1.png', + {frameWidth: 32, frameHeight: 32} + ); } create() { this.logo = new Image(this, this.game.renderer.width - 30, this.game.renderer.height - 20, ReconnectingTextures.icon); this.add.existing(this.logo); - this.reconnectingField = new TextField(this, 10, this.game.renderer.height - 35, "Connection lost. Reconnecting..."); + this.reconnectingField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height / 2, "Connection lost. Reconnecting..."); + this.reconnectingField.setOrigin(0.5, 0.5).setCenterAlign(); + + let cat = this.physics.add.sprite(this.game.renderer.width / 2, this.game.renderer.height / 2 - 32, 'cat'); + this.anims.create({ + key: 'right', + frames: this.anims.generateFrameNumbers('cat', { start: 6, end: 8 }), + frameRate: 10, + repeat: -1 + }); + cat.play('right'); + } } From 57e8c0261c94ed9cc7b335e4fac074cc54a5956e Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Sat, 23 May 2020 16:32:14 +0200 Subject: [PATCH 10/11] Fix test socket --- back/tests/WorldTest.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/back/tests/WorldTest.ts b/back/tests/WorldTest.ts index aed9b838..88c87430 100644 --- a/back/tests/WorldTest.ts +++ b/back/tests/WorldTest.ts @@ -15,19 +15,19 @@ describe("World", () => { let world = new World(connect, disconnect, 160, 160, () => {}, () => {}); - world.join({ id: "foo" }, new Point(100, 100)); + world.join({ userId: "foo" }, new Point(100, 100)); - world.join({ id: "bar" }, new Point(500, 100)); + world.join({ userId: "bar" }, new Point(500, 100)); - world.updatePosition({ id: "bar" }, new Point(261, 100)); + world.updatePosition({ userId: "bar" }, new Point(261, 100)); expect(connectCalledNumber).toBe(0); - world.updatePosition({ id: "bar" }, new Point(101, 100)); + world.updatePosition({ userId: "bar" }, new Point(101, 100)); expect(connectCalledNumber).toBe(2); - world.updatePosition({ id: "bar" }, new Point(102, 100)); + world.updatePosition({ userId: "bar" }, new Point(102, 100)); expect(connectCalledNumber).toBe(2); }); @@ -42,19 +42,19 @@ describe("World", () => { let world = new World(connect, disconnect, 160, 160, () => {}, () => {}); - world.join({ id: "foo" }, new Point(100, 100)); + world.join({ userId: "foo" }, new Point(100, 100)); - world.join({ id: "bar" }, new Point(200, 100)); + world.join({ userId: "bar" }, new Point(200, 100)); expect(connectCalled).toBe(true); connectCalled = false; // baz joins at the outer limit of the group - world.join({ id: "baz" }, new Point(311, 100)); + world.join({ userId: "baz" }, new Point(311, 100)); expect(connectCalled).toBe(false); - world.updatePosition({ id: "baz" }, new Point(309, 100)); + world.updatePosition({ userId: "baz" }, new Point(309, 100)); expect(connectCalled).toBe(true); }); @@ -71,18 +71,18 @@ describe("World", () => { let world = new World(connect, disconnect, 160, 160, () => {}, () => {}); - world.join({ id: "foo" }, new Point(100, 100)); + world.join({ userId: "foo" }, new Point(100, 100)); - world.join({ id: "bar" }, new Point(259, 100)); + world.join({ userId: "bar" }, new Point(259, 100)); expect(connectCalled).toBe(true); expect(disconnectCallNumber).toBe(0); - world.updatePosition({ id: "bar" }, new Point(100+160+160+1, 100)); + world.updatePosition({ userId: "bar" }, new Point(100+160+160+1, 100)); expect(disconnectCallNumber).toBe(2); - world.updatePosition({ id: "bar" }, new Point(262, 100)); + world.updatePosition({ userId: "bar" }, new Point(262, 100)); expect(disconnectCallNumber).toBe(2); }); From f4ed2e52bfeb305c95a6b7a5a3fd7f97622ec737 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Sat, 23 May 2020 16:50:17 +0200 Subject: [PATCH 11/11] Fix CI --- front/src/Phaser/Game/GameScene.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index c79fb740..cce0e939 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1,12 +1,11 @@ -import {GameManager, gameManager, HasMovedEvent, MapObject, StatusGameManagerEnum} from "./GameManager"; +import {GameManager, gameManager, HasMovedEvent} from "./GameManager"; import { GroupCreatedUpdatedMessageInterface, - MessageUserJoined, MessageUserMovedInterface, - MessageUserPositionInterface, PointInterface + MessageUserPositionInterface } from "../../Connexion"; import {CurrentGamerInterface, GamerInterface, hasMovedEventName, Player} from "../Player/Player"; -import { DEBUG_MODE, RESOLUTION, ROOM, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable"; +import { DEBUG_MODE, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable"; import {ITiledMap, ITiledMapLayer, ITiledTileSet} from "../Map/ITiledMap"; import {PLAYER_RESOURCES} from "../Entity/PlayableCaracter"; import Texture = Phaser.Textures.Texture; @@ -14,7 +13,6 @@ import Sprite = Phaser.GameObjects.Sprite; import CanvasTexture = Phaser.Textures.CanvasTexture; import {AddPlayerInterface} from "./AddPlayerInterface"; import {PlayerAnimationNames} from "../Player/Animation"; -import {MessageUserMoved} from "../../../../back/src/Model/Websocket/MessageUserMoved"; export enum Textures { Player = "male1"