FIX: rewrote the way scene exits are triggered

This commit is contained in:
kharhamel 2020-11-18 18:15:57 +01:00
parent c5af6df7fa
commit 09d6d22a5d
6 changed files with 135 additions and 129 deletions

View file

@ -29,6 +29,25 @@ export class Room {
this.hash = idWithHash.substr(indexOfHash + 1);
}
}
public static getIdFromIdentifier(identifier: string, baseUrl: string, currentInstance: string): {roomId: string, hash: string} {
let roomId = '';
let hash = '';
if (!identifier.startsWith('/_/') && !identifier.startsWith('/@/')) { //relative file link
const absoluteExitSceneUrl = new URL(identifier, baseUrl);
roomId = '_/'+currentInstance+'/'+absoluteExitSceneUrl.hostname + absoluteExitSceneUrl.pathname; //in case of a relative url, we need to create a public roomId
hash = absoluteExitSceneUrl.hash;
hash = hash.substring(1); //remove the leading diese
} else { //absolute room Id
const parts = identifier.split('#');
roomId = parts[0];
roomId = roomId.substring(1); //remove the leading slash
if (parts.length > 1) {
hash = parts[1]
}
}
return {roomId, hash}
}
public async getMapUrl(): Promise<string> {
return new Promise<string>((resolve, reject) => {

View file

@ -52,6 +52,7 @@ export class RoomConnection implements RoomConnection {
private static websocketFactory: null|((url: string)=>any) = null; // eslint-disable-line @typescript-eslint/no-explicit-any
private closed: boolean = false;
private tags: string[] = [];
private intervalId!: NodeJS.Timeout;
public static setWebsocketFactory(websocketFactory: (url: string)=>any): void { // eslint-disable-line @typescript-eslint/no-explicit-any
RoomConnection.websocketFactory = websocketFactory;
@ -89,8 +90,12 @@ export class RoomConnection implements RoomConnection {
this.socket.onopen = (ev) => {
//we manually ping every 20s to not be logged out by the server, even when the game is in background.
const pingMessage = new PingMessage();
setInterval(() => this.socket.send(pingMessage.serializeBinary().buffer), manualPingDelay);
this.intervalId = setInterval(() => this.socket.send(pingMessage.serializeBinary().buffer), manualPingDelay);
};
this.socket.onclose = () => {
clearTimeout(this.intervalId);
}
this.socket.onmessage = (messageEvent) => {
const arrayBuffer: ArrayBuffer = messageEvent.data;

View file

@ -39,20 +39,16 @@ export class GameManager {
public async loadMap(room: Room, scenePlugin: Phaser.Scenes.ScenePlugin): Promise<void> {
const roomID = room.id;
const mapUrl = await room.getMapUrl();
console.log('Loading map '+roomID+' at url '+mapUrl);
const gameIndex = scenePlugin.getIndex(mapUrl);
const gameIndex = scenePlugin.getIndex(roomID);
if(gameIndex === -1){
const game : Phaser.Scene = GameScene.createFromUrl(room, mapUrl);
console.log('Adding scene '+mapUrl);
scenePlugin.add(mapUrl, game, false);
scenePlugin.add(roomID, game, false);
}
}
public async goToStartingMap(scenePlugin: Phaser.Scenes.ScenePlugin) {
const url = await this.startRoom.getMapUrl();
console.log('Starting scene '+url);
scenePlugin.start(url);
public goToStartingMap(scenePlugin: Phaser.Scenes.ScenePlugin): void {
scenePlugin.start(this.startRoom.id);
}
}

View file

@ -59,6 +59,7 @@ import {ConsoleGlobalMessageManager} from "../../Administration/ConsoleGlobalMes
import {ResizableScene} from "../Login/ResizableScene";
import {Room} from "../../Connexion/Room";
import {jitsiFactory} from "../../WebRtc/JitsiFactory";
import {urlManager} from "../../Url/UrlManager";
export interface GameSceneInitInterface {
initPosition: PointInterface|null
@ -123,7 +124,6 @@ export class GameScene extends ResizableScene implements CenterListener {
private createPromise: Promise<void>;
private createPromiseResolve!: (value?: void | PromiseLike<void>) => void;
MapKey: string;
MapUrlFile: string;
RoomId: string;
instance: string;
@ -137,7 +137,6 @@ export class GameScene extends ResizableScene implements CenterListener {
y: -1000
}
private PositionNextScene: Array<Array<{ key: string, hash: string }>> = new Array<Array<{ key: string, hash: string }>>();
private presentationModeSprite!: Sprite;
private chatModeSprite!: Sprite;
private gameMap!: GameMap;
@ -146,17 +145,14 @@ export class GameScene extends ResizableScene implements CenterListener {
private outlinedItem: ActionableItem|null = null;
private userInputManager!: UserInputManager;
static createFromUrl(room: Room, mapUrlFile: string, gameSceneKey: string|null = null): GameScene {
static createFromUrl(room: Room, mapUrlFile: string): GameScene {
// We use the map URL as a key
if (gameSceneKey === null) {
gameSceneKey = mapUrlFile;
}
return new GameScene(room, mapUrlFile, gameSceneKey);
return new GameScene(room, mapUrlFile);
}
constructor(private room: Room, MapUrlFile: string, gameSceneKey: string) {
constructor(private room: Room, MapUrlFile: string) {
super({
key: gameSceneKey
key: room.id
});
this.GameManager = gameManager;
@ -164,7 +160,6 @@ export class GameScene extends ResizableScene implements CenterListener {
this.groups = new Map<number, Sprite>();
this.instance = room.getInstance();
this.MapKey = MapUrlFile;
this.MapUrlFile = MapUrlFile;
this.RoomId = room.id;
@ -183,15 +178,15 @@ export class GameScene extends ResizableScene implements CenterListener {
file: file.src
});
});
this.load.on('filecomplete-tilemapJSON-'+this.MapKey, (key: string, type: string, data: unknown) => {
this.load.on('filecomplete-tilemapJSON-'+this.MapUrlFile, (key: string, type: string, data: unknown) => {
this.onMapLoad(data);
});
//TODO strategy to add access token
this.load.tilemapTiledJSON(this.MapKey, this.MapUrlFile);
this.load.tilemapTiledJSON(this.MapUrlFile, this.MapUrlFile);
// If the map has already been loaded as part of another GameScene, the "on load" event will not be triggered.
// In this case, we check in the cache to see if the map is here and trigger the event manually.
if (this.cache.tilemap.exists(this.MapKey)) {
const data = this.cache.tilemap.get(this.MapKey);
if (this.cache.tilemap.exists(this.MapUrlFile)) {
const data = this.cache.tilemap.get(this.MapUrlFile);
this.onMapLoad(data);
}
@ -302,14 +297,16 @@ export class GameScene extends ResizableScene implements CenterListener {
//hook initialisation
init(initData : GameSceneInitInterface) {
if (initData.initPosition !== undefined) {
this.initPosition = initData.initPosition;
this.initPosition = initData.initPosition; //todo: still used?
}
}
//hook create scene
create(): void {
urlManager.editUrlForCurrentRoom(this.room);
//initalise map
this.Map = this.add.tilemap(this.MapKey);
this.Map = this.add.tilemap(this.MapUrlFile);
this.gameMap = new GameMap(this.mapFile);
const mapDirUrl = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf('/'));
this.mapFile.tilesets.forEach((tileset: ITiledTileSet) => {
@ -319,25 +316,21 @@ export class GameScene extends ResizableScene implements CenterListener {
//permit to set bound collision
this.physics.world.setBounds(0, 0, this.Map.widthInPixels, this.Map.heightInPixels);
// Let's alter browser history
let path = this.room.id;
if (this.room.hash) {
path += '#' + this.room.hash;
}
window.history.pushState({}, 'WorkAdventure', path);
//add layer on map
this.Layers = new Array<Phaser.Tilemaps.StaticTilemapLayer>();
let depth = -2;
for (const layer of this.mapFile.layers) {
if (layer.type === 'tilelayer') {
this.addLayer(this.Map.createStaticLayer(layer.name, this.Terrains, 0, 0).setDepth(depth));
}
if (layer.type === 'tilelayer' && this.getExitSceneUrl(layer) !== undefined) {
this.loadNextGameFromExitSceneUrl(layer, this.mapFile.width);
} else if (layer.type === 'tilelayer' && this.getExitUrl(layer) !== undefined) {
console.log('Loading exitUrl ', this.getExitUrl(layer))
this.loadNextGameFromExitUrl(layer, this.mapFile.width);
const exitSceneUrl = this.getExitSceneUrl(layer);
if (exitSceneUrl !== undefined) {
this.loadNextGame(exitSceneUrl);
}
const exitUrl = this.getExitUrl(layer);
if (exitUrl !== undefined) {
this.loadNextGame(exitUrl);
}
}
if (layer.type === 'objectgroup' && layer.name === 'floorLayer') {
depth = 10000;
@ -482,7 +475,6 @@ export class GameScene extends ResizableScene implements CenterListener {
if (position === undefined) {
throw new Error('Position missing from UserMovedMessage');
}
//console.log('Received position ', position.getX(), position.getY(), "from user", message.getUserid());
const messageUserMoved: MessageUserMovedInterface = {
userId: message.getUserid(),
@ -515,7 +507,7 @@ export class GameScene extends ResizableScene implements CenterListener {
this.simplePeer.unregister();
const gameSceneKey = 'somekey' + Math.round(Math.random() * 10000);
const game: Phaser.Scene = GameScene.createFromUrl(this.room, this.MapUrlFile, gameSceneKey);
const game: Phaser.Scene = GameScene.createFromUrl(this.room, this.MapUrlFile);
this.scene.add(gameSceneKey, game, true,
{
initPosition: {
@ -581,6 +573,12 @@ export class GameScene extends ResizableScene implements CenterListener {
}
private triggerOnMapLayerPropertyChange(){
this.gameMap.onPropertyChange('exitSceneUrl', (newValue, oldValue) => {
if (newValue) this.onMapExit(newValue as string);
});
this.gameMap.onPropertyChange('exitUrl', (newValue, oldValue) => {
if (newValue) this.onMapExit(newValue as string);
});
this.gameMap.onPropertyChange('openWebsite', (newValue, oldValue, allProps) => {
if (newValue === undefined) {
layoutManager.removeActionButton('openWebsite', this.userInputManager);
@ -635,6 +633,25 @@ export class GameScene extends ResizableScene implements CenterListener {
}
});
}
private onMapExit(exitKey: string) {
const {roomId, hash} = Room.getIdFromIdentifier(exitKey, this.MapUrlFile, this.instance);
//todo: push the hash into the url
if (!roomId) throw new Error('Could not find the room from its exit key: '+exitKey);
if (roomId !== this.scene.key) {
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
this.connection.closeConnection();
this.simplePeer.unregister();
this.scene.stop();
this.scene.remove(this.scene.key);
this.scene.start(roomId, {hash});
} else {
//if the exit points to the current map, we simply teleport the user back to the startLayer
this.initPositionFromLayerName(this.room.hash || 'start');
this.CurrentPlayer.x = this.startX;
this.CurrentPlayer.y = this.startY;
}
}
private switchLayoutMode(): void {
//if discussion is activated, this layout cannot be activated
@ -691,14 +708,13 @@ export class GameScene extends ResizableScene implements CenterListener {
return this.getProperty(layer, "exitUrl") as string|undefined;
}
/**
* @deprecated the map property exitSceneUrl is deprecated
*/
private getExitSceneUrl(layer: ITiledMapLayer): string|undefined {
return this.getProperty(layer, "exitSceneUrl") as string|undefined;
}
private getExitSceneInstance(layer: ITiledMapLayer): string|undefined {
return this.getProperty(layer, "exitInstance") as string|undefined;
}
private isStartLayer(layer: ITiledMapLayer): boolean {
return this.getProperty(layer, "startLayer") == true;
}
@ -715,66 +731,11 @@ export class GameScene extends ResizableScene implements CenterListener {
return obj.value;
}
private loadNextGameFromExitSceneUrl(layer: ITiledMapLayer, mapWidth: number) {
const exitSceneUrl = this.getExitSceneUrl(layer);
if (exitSceneUrl === undefined) {
throw new Error('Layer is not an exit scene layer.');
}
let instance = this.getExitSceneInstance(layer);
if (instance === undefined) {
instance = this.instance;
}
const absoluteExitSceneUrl = new URL(exitSceneUrl, this.MapUrlFile).href;
const absoluteExitSceneUrlWithoutProtocol = absoluteExitSceneUrl.toString().substr(absoluteExitSceneUrl.toString().indexOf('://')+3);
const roomId = '_/'+instance+'/'+absoluteExitSceneUrlWithoutProtocol;
this.loadNextGame(layer, mapWidth, roomId);
}
private loadNextGameFromExitUrl(layer: ITiledMapLayer, mapWidth: number) {
const exitUrl = this.getExitUrl(layer);
if (exitUrl === undefined) {
throw new Error('Layer is not an exit layer.');
}
const fullPath = new URL(exitUrl, window.location.toString()).pathname;
this.loadNextGame(layer, mapWidth, fullPath);
}
//todo: push that into the gameManager
private loadNextGame(layer: ITiledMapLayer, mapWidth: number, roomId: string){
private async loadNextGame(exitSceneIdentifier: string){
const {roomId, hash} = Room.getIdFromIdentifier(exitSceneIdentifier, this.MapUrlFile, this.instance);
const room = new Room(roomId);
gameManager.loadMap(room, this.scene);
const exitSceneKey = roomId;
const tiles : number[] = layer.data as number[];
for (let key=0; key < tiles.length; key++) {
const objectKey = tiles[key];
if(objectKey === 0){
continue;
}
//key + 1 because the start x = 0;
const y : number = parseInt(((key + 1) / mapWidth).toString());
const x : number = key - (y * mapWidth);
let hash = new URL(roomId, this.MapUrlFile).hash;
if (hash) {
hash = hash.substr(1);
}
//push and save switching case
if (this.PositionNextScene[y] === undefined) {
this.PositionNextScene[y] = new Array<{key: string, hash: string}>();
}
room.getMapUrl().then((url: string) => {
this.PositionNextScene[y][x] = {
key: url,
hash
}
})
}
await gameManager.loadMap(room, this.scene);
}
private startUser(layer: ITiledMapLayer): PositionInterface {
@ -975,33 +936,6 @@ export class GameScene extends ResizableScene implements CenterListener {
}
player.updatePosition(moveEvent);
});
const nextSceneKey = this.checkToExit();
if (!nextSceneKey) return;
if (nextSceneKey.key !== this.scene.key) {
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
this.connection.closeConnection();
this.simplePeer.unregister();
this.scene.stop();
this.scene.remove(this.scene.key);
this.scene.start(nextSceneKey.key);
} else {
//if the exit points to the current map, we simply teleport the user back to the startLayer
this.initPositionFromLayerName(this.room.hash || 'start');
this.CurrentPlayer.x = this.startX;
this.CurrentPlayer.y = this.startY;
}
}
private checkToExit(): {key: string, hash: string} | null {
const x = Math.floor(this.CurrentPlayer.x / 32);
const y = Math.floor(this.CurrentPlayer.y / 32);
if (this.PositionNextScene[y] !== undefined && this.PositionNextScene[y][x] !== undefined) {
return this.PositionNextScene[y][x];
} else {
return null;
}
}
/**

View file

@ -1,3 +1,4 @@
import {Room} from "../Connexion/Room";
export enum GameConnexionTypes {
anonymous=1,
@ -44,6 +45,15 @@ class UrlManager {
history.pushState({}, 'WorkAdventure', newUrl);
return newUrl;
}
//todo: is it duplicated with editUrlForRoom() ?
public editUrlForCurrentRoom(room: Room): void {
let path = room.id;
if (room.hash) {
path += '#'+room.hash;
}
history.pushState({}, 'WorkAdventure', path);
}
}

View file

@ -0,0 +1,42 @@
import "jasmine";
import {Room} from "../../../src/Connexion/Room";
describe("Room getIdFromIdentifier()", () => {
it("should work with an absolute room id and no hash as parameter", () => {
const {roomId, hash} = Room.getIdFromIdentifier('/_/global/maps.workadventu.re/test2.json', '', '');
expect(roomId).toEqual('_/global/maps.workadventu.re/test2.json');
expect(hash).toEqual('');
});
it("should work with an absolute room id and a hash as parameters", () => {
const {roomId, hash} = Room.getIdFromIdentifier('/_/global/maps.workadventu.re/test2.json#start', '', '');
expect(roomId).toEqual('_/global/maps.workadventu.re/test2.json');
expect(hash).toEqual("start");
});
it("should work with an absolute room id, regardless of baseUrl or instance", () => {
const {roomId, hash} = Room.getIdFromIdentifier('/_/global/maps.workadventu.re/test2.json', 'https://another.domain/_/global/test.json', 'lol');
expect(roomId).toEqual('_/global/maps.workadventu.re/test2.json');
expect(hash).toEqual('');
});
it("should work with a relative file link and no hash as parameters", () => {
const {roomId, hash} = Room.getIdFromIdentifier('./test2.json', 'https://maps.workadventu.re/test.json', 'global');
expect(roomId).toEqual('_/global/maps.workadventu.re/test2.json');
expect(hash).toEqual('');
});
it("should work with a relative file link with no dot", () => {
const {roomId, hash} = Room.getIdFromIdentifier('test2.json', 'https://maps.workadventu.re/test.json', 'global');
expect(roomId).toEqual('_/global/maps.workadventu.re/test2.json');
expect(hash).toEqual('');
});
it("should work with a relative file link two levels deep", () => {
const {roomId, hash} = Room.getIdFromIdentifier('../floor1/Floor1.json', 'https://maps.workadventu.re/floor0/Floor0.json', 'global');
expect(roomId).toEqual('_/global/maps.workadventu.re/floor1/Floor1.json');
expect(hash).toEqual('');
});
it("should work with a relative file link and a hash as parameters", () => {
const {roomId, hash} = Room.getIdFromIdentifier('./test2.json#start', 'https://maps.workadventu.re/test.json', 'global');
expect(roomId).toEqual('_/global/maps.workadventu.re/test2.json');
expect(hash).toEqual("start");
});
});