From 2448fef53af92a6bc853508179639f135ad600b3 Mon Sep 17 00:00:00 2001
From: =?utf8?q?David=20N=C3=A9grier?= <d.negrier@thecodingmachine.com>
Date: Sat, 23 May 2020 17:27:49 +0200
Subject: [PATCH] Adding a notion of instances per mapAdding a notion of
 instances to room

The URL signature becomes:

https://workadventu.re/_/[instance]/[path_to_map.json]

This allows us to create many instances of the same map (and therefore to create several different worlds for different people)

An exit on a map can target another "instance" by passing the "exitInstance" property.
---
 README.md                             |  5 ++-
 back/src/Controller/MapController.ts  |  3 +-
 front/src/Phaser/Game/GameManager.ts  |  8 ++--
 front/src/Phaser/Game/GameScene.ts    | 53 +++++++++++++++++++++------
 front/src/Phaser/Login/LogincScene.ts | 33 ++++++++---------
 front/src/Phaser/Player/Player.ts     |  6 ---
 6 files changed, 66 insertions(+), 42 deletions(-)

diff --git a/README.md b/README.md
index 7883a4e..88610d0 100644
--- a/README.md
+++ b/README.md
@@ -47,8 +47,9 @@ A few things to notice:
 
 In order to place an on your scene that leads to another scene:
 
-- You must create an specific layer. When a character reaches ANY tile of that layer, it will exit the scene.
-- In layer properties, you must add "exitSceneUrl" property. It represents the map URL of the next scene. For example : `/<map folder>/<map>.json`. Be careful, if you want the next map to be correctly loaded, you must check that the map files are in folder `back/src/Assets/Maps/<your map folder>`. The files will be accessible by url `<HOST>/map/files/<your map folder>/...`.
+- You must create a specific layer. When a character reaches ANY tile of that layer, it will exit the scene.
+- In layer properties, you MUST add "exitSceneUrl" property. It represents the map URL of the next scene. For example : `/<map folder>/<map>.json`. Be careful, if you want the next map to be correctly loaded, you must check that the map files are in folder `back/src/Assets/Maps/<your map folder>`. The files will be accessible by url `<HOST>/map/files/<your map folder>/...`.
+- In layer properties, you CAN add an "exitInstance" property. If set, you will join the map of the specified instance. Otherwise, you will stay on the same instance.
 - If you want to have multiple exits, you can create many layers with name "exit". Each layer has a different key `exitSceneUrl` and have tiles that represent exits to another scene.
 
 ![](doc/images/exit_layer_map.png)
diff --git a/back/src/Controller/MapController.ts b/back/src/Controller/MapController.ts
index bc7546c..f7e78a0 100644
--- a/back/src/Controller/MapController.ts
+++ b/back/src/Controller/MapController.ts
@@ -20,7 +20,8 @@ export class MapController {
     getMaps() {
         this.App.get("/maps", (req: Request, res: Response) => {
             return res.status(OK).send({
-                mapUrlStart: req.headers.host + "/map/files" + URL_ROOM_STARTED
+                mapUrlStart: req.headers.host + "/map/files" + URL_ROOM_STARTED,
+                startInstance: "global"
             });
         });
     }
diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts
index 7a439fb..c0f8150 100644
--- a/front/src/Phaser/Game/GameManager.ts
+++ b/front/src/Phaser/Game/GameManager.ts
@@ -10,8 +10,6 @@ import {
     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";
 
@@ -169,13 +167,13 @@ export class GameManager {
         this.ConnexionInstance.sharePosition(event.x, event.y, event.direction, event.moving);
     }
 
-    loadMap(mapUrl: string, scene: ScenePlugin): string {
-        let sceneKey = getMapKeyByUrl(mapUrl);
+    loadMap(mapUrl: string, scene: Phaser.Scenes.ScenePlugin, instance: string): string {
+        let sceneKey = GameScene.getMapKeyByUrl(mapUrl);
 
         let gameIndex = scene.getIndex(sceneKey);
         let game : Phaser.Scene = null;
         if(gameIndex === -1){
-            game = new GameScene(sceneKey, mapUrl);
+            game = GameScene.createFromUrl(mapUrl, instance);
             scene.add(sceneKey, game, false);
         }
         return sceneKey;
diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts
index 2ec6ea1..4558bc7 100644
--- a/front/src/Phaser/Game/GameScene.ts
+++ b/front/src/Phaser/Game/GameScene.ts
@@ -40,10 +40,17 @@ export class GameScene extends Phaser.Scene {
 
     MapKey: string;
     MapUrlFile: string;
+    RoomId: string;
+    instance: string;
 
     PositionNextScene: Array<any> = new Array<any>();
 
-    constructor(MapKey : string = "", MapUrlFile: string = "") {
+    static createFromUrl(mapUrlFile: string, instance: string): GameScene {
+        let key = GameScene.getMapKeyByUrl(mapUrlFile);
+        return new GameScene(key, mapUrlFile, instance);
+    }
+
+    constructor(MapKey : string, MapUrlFile: string, instance: string) {
         super({
             key: MapKey
         });
@@ -51,9 +58,11 @@ export class GameScene extends Phaser.Scene {
         this.GameManager = gameManager;
         this.Terrains = [];
         this.groups = new Map<string, Sprite>();
+        this.instance = instance;
 
-        this.MapKey =  MapKey;
+        this.MapKey = MapKey;
         this.MapUrlFile = MapUrlFile;
+        this.RoomId = this.instance + '__' + this.MapKey;
     }
 
     //hook preload scene
@@ -158,7 +167,7 @@ export class GameScene extends Phaser.Scene {
 
         // Let's alter browser history
         let url = new URL(this.MapUrlFile);
-        let path = '/_/'+url.host+url.pathname;
+        let path = '/_/'+this.instance+'/'+url.host+url.pathname;
         if (url.hash) {
             // FIXME: entry should be dictated by a property passed to init()
             path += '#'+url.hash;
@@ -178,6 +187,18 @@ export class GameScene extends Phaser.Scene {
         return obj.value;
     }
 
+    private getExitSceneInstance(layer: ITiledMapLayer): string|undefined {
+        let properties : any = layer.properties;
+        if (!properties) {
+            return undefined;
+        }
+        let obj = properties.find((property:any) => property.name === "exitInstance");
+        if (obj === undefined) {
+            return undefined;
+        }
+        return obj.value;
+    }
+
     /**
      *
      * @param layer
@@ -187,10 +208,14 @@ export class GameScene extends Phaser.Scene {
      */
     private loadNextGame(layer: ITiledMapLayer, mapWidth: number, tileWidth: number, tileHeight: number){
         let exitSceneUrl = this.getExitSceneUrl(layer);
+        let instance = this.getExitSceneInstance(layer);
+        if (instance === undefined) {
+            instance = this.instance;
+        }
 
         // TODO: eventually compute a relative URL
         let absoluteExitSceneUrl = new URL(exitSceneUrl, this.MapUrlFile).href;
-        let exitSceneKey = gameManager.loadMap(absoluteExitSceneUrl, this.scene);
+        let exitSceneKey = gameManager.loadMap(absoluteExitSceneUrl, this.scene, instance);
 
         let tiles : any = layer.data;
         tiles.forEach((objectKey : number, key: number) => {
@@ -264,11 +289,6 @@ export class GameScene extends Phaser.Scene {
         });
     }
 
-    addSpite(Object : Phaser.Physics.Arcade.Sprite){
-        Object.setImmovable(true);
-        this.Objects.push(Object);
-    }
-
     createCollisionObject(){
         this.Objects.forEach((Object : Phaser.Physics.Arcade.Sprite) => {
             this.physics.add.collider(this.CurrentPlayer, Object, (object1: any, object2: any) => {
@@ -279,7 +299,7 @@ export class GameScene extends Phaser.Scene {
 
     createCurrentPlayer(){
         //initialise player
-        //TODO create animation moving between exit and strat
+        //TODO create animation moving between exit and start
         this.CurrentPlayer = new Player(
             null, // The current player is not has no id (because the id can change if connexion is lost and we should check that id using the GameManager.
             this,
@@ -296,7 +316,7 @@ export class GameScene extends Phaser.Scene {
         this.createCollisionObject();
 
         //join room
-        this.GameManager.joinRoom(this.scene.key, this.startX, this.startY, PlayerAnimationNames.WalkDown, false);
+        this.GameManager.joinRoom(this.RoomId, this.startX, this.startY, PlayerAnimationNames.WalkDown, false);
 
         //listen event to share position of user
         this.CurrentPlayer.on(hasMovedEventName, this.pushPlayerPosition.bind(this))
@@ -495,4 +515,15 @@ export class GameScene extends Phaser.Scene {
         this.groups.get(groupId).destroy();
         this.groups.delete(groupId);
     }
+
+    public static getMapKeyByUrl(mapUrlStart: string) : string {
+        // FIXME: the key should be computed from the full URL of the map.
+        let startPos = mapUrlStart.indexOf('://')+3;
+        let endPos = mapUrlStart.indexOf(".json");
+        return mapUrlStart.substring(startPos, endPos);
+
+
+        let tab = mapUrlStart.split("/");
+        return tab[tab.length -1].substr(0, tab[tab.length -1].indexOf(".json"));
+    }
 }
diff --git a/front/src/Phaser/Login/LogincScene.ts b/front/src/Phaser/Login/LogincScene.ts
index c289b99..b7199e3 100644
--- a/front/src/Phaser/Login/LogincScene.ts
+++ b/front/src/Phaser/Login/LogincScene.ts
@@ -8,17 +8,6 @@ import {PLAYER_RESOURCES} from "../Entity/PlayableCaracter";
 import {cypressAsserter} from "../../Cypress/CypressAsserter";
 import {GroupCreatedUpdatedMessageInterface, MessageUserJoined, MessageUserPositionInterface} from "../../Connexion";
 
-export function getMapKeyByUrl(mapUrlStart: string){
-    // FIXME: the key should be computed from the full URL of the map.
-    let startPos = mapUrlStart.indexOf('://')+3;
-    let endPos = mapUrlStart.indexOf(".json");
-    return mapUrlStart.substring(startPos, endPos);
-
-
-    let tab = mapUrlStart.split("/");
-    return tab[tab.length -1].substr(0, tab[tab.length -1].indexOf(".json"));
-}
-
 //todo: put this constants in a dedicated file
 export const LoginSceneName = "LoginScene";
 enum LoginTextures {
@@ -104,9 +93,10 @@ export class LogincScene extends Phaser.Scene {
     private async login(name: string) {
         return gameManager.connect(name, this.selectedPlayer.texture.key).then(() => {
             // Do we have a start URL in the address bar? If so, let's redirect to this address
-            let mapUrl = this.findMapUrl();
-            if (mapUrl !== null) {
-                let key = gameManager.loadMap(mapUrl, this.scene);
+            let instanceAndMapUrl = this.findMapUrl();
+            if (instanceAndMapUrl !== null) {
+                let [mapUrl, instance] = instanceAndMapUrl;
+                let key = gameManager.loadMap(mapUrl, this.scene, instance);
                 this.scene.start(key);
                 return mapUrl;
             } else {
@@ -115,7 +105,7 @@ export class LogincScene extends Phaser.Scene {
                     if (!scene) {
                         return;
                     }
-                    let key = gameManager.loadMap(window.location.protocol+"//"+scene.mapUrlStart, this.scene);
+                    let key = gameManager.loadMap(window.location.protocol + "//" + scene.mapUrlStart, this.scene, scene.startInstance);
                     this.scene.start(key);
                     return scene;
                 }).catch((err) => {
@@ -129,12 +119,21 @@ export class LogincScene extends Phaser.Scene {
         });
     }
 
-    private findMapUrl(): string|null {
+    /**
+     * Returns the map URL and the instance from the current URL
+     */
+    private findMapUrl(): [string, string]|null {
         let path = window.location.pathname;
         if (!path.startsWith('/_/')) {
             return null;
         }
-        return window.location.protocol+'//'+path.substr(3);
+        let instanceAndMap = path.substr(3);
+        let firstSlash = instanceAndMap.indexOf('/');
+        if (firstSlash === -1) {
+            return null;
+        }
+        let instance = instanceAndMap.substr(0, firstSlash);
+        return [window.location.protocol+'//'+instanceAndMap.substr(firstSlash+1), instance];
     }
 
     Map: Phaser.Tilemaps.Tilemap;
diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts
index 850be56..fe8e5b7 100644
--- a/front/src/Phaser/Player/Player.ts
+++ b/front/src/Phaser/Player/Player.ts
@@ -45,12 +45,6 @@ export class Player extends PlayableCaracter implements CurrentGamerInterface, G
         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);
     }
 
-- 
2.25.1