From b20b4abb9e8ff357ec3f4d50874cac7814d094a8 Mon Sep 17 00:00:00 2001 From: jonny Date: Wed, 23 Jun 2021 12:32:36 +0200 Subject: [PATCH 01/12] allow start hashes in tiles # Conflicts: # front/src/Phaser/Game/GameScene.ts --- front/src/Phaser/Game/GameMap.ts | 7 +++++++ front/src/Phaser/Game/GameScene.ts | 21 ++++++++++++++------- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index ffcee287..b84ec477 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -35,6 +35,13 @@ export class GameMap { } + public getPropertiesForIndex(index: number): Array { + if (this.tileSetPropertyMap[index]) { + return this.tileSetPropertyMap[index] + } + return [] + } + /** * Sets the position of the current player (in pixels) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 61f1db2a..52c16648 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -971,7 +971,7 @@ ${escapedMessage} this.scene.start(roomId); } else { //if the exit points to the current map, we simply teleport the user back to the startLayer - this.initPositionFromLayerName(hash || defaultStartLayerName); + this.initPositionFromLayerName(hash || defaultStartLayerName, hash); this.CurrentPlayer.x = this.startX; this.CurrentPlayer.y = this.startY; setTimeout(() => this.mapTransitioning = false, 500); @@ -1044,11 +1044,11 @@ ${escapedMessage} } else { // Now, let's find the start layer if (this.startLayerName) { - this.initPositionFromLayerName(this.startLayerName); + this.initPositionFromLayerName(this.startLayerName, null); } if (this.startX === undefined) { // If we have no start layer specified or if the hash passed does not exist, let's go with the default start position. - this.initPositionFromLayerName(defaultStartLayerName); + this.initPositionFromLayerName(defaultStartLayerName, this.startLayerName); } } // Still no start position? Something is wrong with the map, we need a "start" layer. @@ -1060,10 +1060,10 @@ ${escapedMessage} } } - private initPositionFromLayerName(layerName: string) { + private initPositionFromLayerName(layerName: string, startLayerName: string | null) { for (const layer of this.gameMap.layersIterator) { if ((layerName === layer.name || layer.name.endsWith('/'+layerName)) && layer.type === 'tilelayer' && (layerName === defaultStartLayerName || this.isStartLayer(layer))) { - const startPosition = this.startUser(layer); + const startPosition = this.startUser(layer, startLayerName); this.startX = startPosition.x + this.mapFile.tilewidth/2; this.startY = startPosition.y + this.mapFile.tileheight/2; } @@ -1117,7 +1117,7 @@ ${escapedMessage} return gameManager.loadMap(room, this.scene).catch(() => {}); } - private startUser(layer: ITiledMapTileLayer): PositionInterface { + private startUser(layer: ITiledMapTileLayer, startName: string | null): PositionInterface { const tiles = layer.data; if (typeof(tiles) === 'string') { throw new Error('The content of a JSON map must be filled as a JSON array, not as a string'); @@ -1130,7 +1130,14 @@ ${escapedMessage} const y = Math.floor(key / layer.width); const x = key % layer.width; - possibleStartPositions.push({x: x * this.mapFile.tilewidth, y: y * this.mapFile.tilewidth}); + if (startName) { + const properties = this.gameMap.getPropertiesForIndex(objectKey); + if (!properties.length || !properties.some(property => property.name == "start" && property.value == startName)) { + return + } + } + + possibleStartPositions.push({ x: x * this.mapFile.tilewidth, y: y * this.mapFile.tilewidth }); }); // Get a value at random amongst allowed values if (possibleStartPositions.length === 0) { From 64847cd465351bb01d74779c9a9711de47e4c13c Mon Sep 17 00:00:00 2001 From: jonny Date: Wed, 23 Jun 2021 12:42:24 +0200 Subject: [PATCH 02/12] adjusted null if no hash --- front/src/Connexion/Room.ts | 39 ++- front/src/Phaser/Game/GameScene.ts | 359 ++++++++++++++-------------- front/tests/Phaser/Game/RoomTest.ts | 44 ++-- 3 files changed, 220 insertions(+), 222 deletions(-) diff --git a/front/src/Connexion/Room.ts b/front/src/Connexion/Room.ts index 3ae8d2ed..e68108cb 100644 --- a/front/src/Connexion/Room.ts +++ b/front/src/Connexion/Room.ts @@ -1,18 +1,18 @@ import Axios from "axios"; -import {PUSHER_URL} from "../Enum/EnvironmentVariable"; -import type {CharacterTexture} from "./LocalUser"; +import { PUSHER_URL } from "../Enum/EnvironmentVariable"; +import type { CharacterTexture } from "./LocalUser"; -export class MapDetail{ - constructor(public readonly mapUrl: string, public readonly textures : CharacterTexture[]|undefined) { +export class MapDetail { + constructor(public readonly mapUrl: string, public readonly textures: CharacterTexture[] | undefined) { } } export class Room { public readonly id: string; public readonly isPublic: boolean; - private mapUrl: string|undefined; - private textures: CharacterTexture[]|undefined; - private instance: string|undefined; + private mapUrl: string | undefined; + private textures: CharacterTexture[] | undefined; + private instance: string | undefined; private _search: URLSearchParams; constructor(id: string) { @@ -34,14 +34,14 @@ export class Room { this._search = new URLSearchParams(url.search); } - public static getIdFromIdentifier(identifier: string, baseUrl: string, currentInstance: string): {roomId: string, hash: string} { + public static getIdFromIdentifier(identifier: string, baseUrl: string, currentInstance: string): { roomId: string, hash: string | null } { let roomId = ''; - let hash = ''; + let hash = null; if (!identifier.startsWith('/_/') && !identifier.startsWith('/@/')) { //relative file link //Relative identifier can be deep enough to rewrite the base domain, so we cannot use the variable 'baseUrl' as the actual base url for the URL objects. //We instead use 'workadventure' as a dummy base value. const baseUrlObject = new URL(baseUrl); - const absoluteExitSceneUrl = new URL(identifier, 'http://workadventure/_/'+currentInstance+'/'+baseUrlObject.hostname+baseUrlObject.pathname); + const absoluteExitSceneUrl = new URL(identifier, 'http://workadventure/_/' + currentInstance + '/' + baseUrlObject.hostname + baseUrlObject.pathname); roomId = absoluteExitSceneUrl.pathname; //in case of a relative url, we need to create a public roomId roomId = roomId.substring(1); //remove the leading slash hash = absoluteExitSceneUrl.hash; @@ -54,7 +54,7 @@ export class Room { hash = parts[1] } } - return {roomId, hash} + return { roomId, hash } } public async getMapDetail(): Promise { @@ -66,8 +66,8 @@ export class Room { if (this.isPublic) { const match = /_\/[^/]+\/(.+)/.exec(this.id); - if (!match) throw new Error('Could not extract url from "'+this.id+'"'); - this.mapUrl = window.location.protocol+'//'+match[1]; + if (!match) throw new Error('Could not extract url from "' + this.id + '"'); + this.mapUrl = window.location.protocol + '//' + match[1]; resolve(new MapDetail(this.mapUrl, this.textures)); return; } else { @@ -76,7 +76,7 @@ export class Room { Axios.get(`${PUSHER_URL}/map`, { params: urlParts - }).then(({data}) => { + }).then(({ data }) => { console.log('Map ', this.id, ' resolves to URL ', data.mapUrl); resolve(data); return; @@ -99,13 +99,13 @@ export class Room { if (this.isPublic) { const match = /_\/([^/]+)\/.+/.exec(this.id); - if (!match) throw new Error('Could not extract instance from "'+this.id+'"'); + if (!match) throw new Error('Could not extract instance from "' + this.id + '"'); this.instance = match[1]; return this.instance; } else { const match = /@\/([^/]+)\/([^/]+)\/.+/.exec(this.id); - if (!match) throw new Error('Could not extract instance from "'+this.id+'"'); - this.instance = match[1]+'/'+match[2]; + if (!match) throw new Error('Could not extract instance from "' + this.id + '"'); + this.instance = match[1] + '/' + match[2]; return this.instance; } } @@ -114,7 +114,7 @@ export class Room { const regex = /@\/([^/]+)\/([^/]+)(?:\/([^/]*))?/gm; const match = regex.exec(url); if (!match) { - throw new Error('Invalid URL '+url); + throw new Error('Invalid URL ' + url); } const results: { organizationSlug: string, worldSlug: string, roomSlug?: string } = { organizationSlug: match[1], @@ -126,8 +126,7 @@ export class Room { return results; } - public isDisconnected(): boolean - { + public isDisconnected(): boolean { const alone = this._search.get('alone'); if (alone && alone !== '0' && alone.toLowerCase() !== 'false') { return true; diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 52c16648..30b92a91 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1,4 +1,4 @@ -import {gameManager, HasMovedEvent} from "./GameManager"; +import { gameManager, HasMovedEvent } from "./GameManager"; import type { GroupCreatedUpdatedMessageInterface, MessageUserJoined, @@ -9,7 +9,7 @@ import type { PositionInterface, RoomJoinedMessageInterface } from "../../Connexion/ConnexionModels"; -import {hasMovedEventName, Player, requestEmoteEventName} from "../Player/Player"; +import { hasMovedEventName, Player, requestEmoteEventName } from "../Player/Player"; import { DEBUG_MODE, JITSI_PRIVATE_MODE, @@ -24,15 +24,15 @@ import type { ITiledMapTileLayer, ITiledTileSet } from "../Map/ITiledMap"; -import type {AddPlayerInterface} from "./AddPlayerInterface"; -import {PlayerAnimationDirections} from "../Player/Animation"; -import {PlayerMovement} from "./PlayerMovement"; -import {PlayersPositionInterpolator} from "./PlayersPositionInterpolator"; -import {RemotePlayer} from "../Entity/RemotePlayer"; -import {Queue} from 'queue-typescript'; -import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer"; -import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene"; -import {lazyLoadPlayerCharacterTextures, loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager"; +import type { AddPlayerInterface } from "./AddPlayerInterface"; +import { PlayerAnimationDirections } from "../Player/Animation"; +import { PlayerMovement } from "./PlayerMovement"; +import { PlayersPositionInterpolator } from "./PlayersPositionInterpolator"; +import { RemotePlayer } from "../Entity/RemotePlayer"; +import { Queue } from 'queue-typescript'; +import { SimplePeer, UserSimplePeerInterface } from "../../WebRtc/SimplePeer"; +import { ReconnectingSceneName } from "../Reconnecting/ReconnectingScene"; +import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager"; import { CenterListener, JITSI_MESSAGE_PROPERTIES, @@ -45,60 +45,60 @@ import { AUDIO_VOLUME_PROPERTY, AUDIO_LOOP_PROPERTY } from "../../WebRtc/LayoutManager"; -import {GameMap} from "./GameMap"; -import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager"; -import {mediaManager} from "../../WebRtc/MediaManager"; -import type {ItemFactoryInterface} from "../Items/ItemFactoryInterface"; -import type {ActionableItem} from "../Items/ActionableItem"; -import {UserInputManager} from "../UserInput/UserInputManager"; -import {soundManager} from "./SoundManager"; -import type {UserMovedMessage} from "../../Messages/generated/messages_pb"; -import {ProtobufClientUtils} from "../../Network/ProtobufClientUtils"; -import {connectionManager} from "../../Connexion/ConnectionManager"; -import type {RoomConnection} from "../../Connexion/RoomConnection"; -import {GlobalMessageManager} from "../../Administration/GlobalMessageManager"; -import {userMessageManager} from "../../Administration/UserMessageManager"; -import {ResizableScene} from "../Login/ResizableScene"; -import {Room} from "../../Connexion/Room"; -import {jitsiFactory} from "../../WebRtc/JitsiFactory"; -import {urlManager} from "../../Url/UrlManager"; -import {audioManager} from "../../WebRtc/AudioManager"; -import {PresentationModeIcon} from "../Components/PresentationModeIcon"; -import {ChatModeIcon} from "../Components/ChatModeIcon"; -import {OpenChatIcon, openChatIconName} from "../Components/OpenChatIcon"; -import {SelectCharacterScene, SelectCharacterSceneName} from "../Login/SelectCharacterScene"; -import {TextureError} from "../../Exception/TextureError"; -import {addLoader} from "../Components/Loader"; -import {ErrorSceneName} from "../Reconnecting/ErrorScene"; -import {localUserStore} from "../../Connexion/LocalUserStore"; -import {iframeListener} from "../../Api/IframeListener"; -import {HtmlUtils} from "../../WebRtc/HtmlUtils"; +import { GameMap } from "./GameMap"; +import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager"; +import { mediaManager } from "../../WebRtc/MediaManager"; +import type { ItemFactoryInterface } from "../Items/ItemFactoryInterface"; +import type { ActionableItem } from "../Items/ActionableItem"; +import { UserInputManager } from "../UserInput/UserInputManager"; +import { soundManager } from "./SoundManager"; +import type { UserMovedMessage } from "../../Messages/generated/messages_pb"; +import { ProtobufClientUtils } from "../../Network/ProtobufClientUtils"; +import { connectionManager } from "../../Connexion/ConnectionManager"; +import type { RoomConnection } from "../../Connexion/RoomConnection"; +import { GlobalMessageManager } from "../../Administration/GlobalMessageManager"; +import { userMessageManager } from "../../Administration/UserMessageManager"; +import { ResizableScene } from "../Login/ResizableScene"; +import { Room } from "../../Connexion/Room"; +import { jitsiFactory } from "../../WebRtc/JitsiFactory"; +import { urlManager } from "../../Url/UrlManager"; +import { audioManager } from "../../WebRtc/AudioManager"; +import { PresentationModeIcon } from "../Components/PresentationModeIcon"; +import { ChatModeIcon } from "../Components/ChatModeIcon"; +import { OpenChatIcon, openChatIconName } from "../Components/OpenChatIcon"; +import { SelectCharacterScene, SelectCharacterSceneName } from "../Login/SelectCharacterScene"; +import { TextureError } from "../../Exception/TextureError"; +import { addLoader } from "../Components/Loader"; +import { ErrorSceneName } from "../Reconnecting/ErrorScene"; +import { localUserStore } from "../../Connexion/LocalUserStore"; +import { iframeListener } from "../../Api/IframeListener"; +import { HtmlUtils } from "../../WebRtc/HtmlUtils"; import Texture = Phaser.Textures.Texture; import Sprite = Phaser.GameObjects.Sprite; import CanvasTexture = Phaser.Textures.CanvasTexture; import GameObject = Phaser.GameObjects.GameObject; import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR; import DOMElement = Phaser.GameObjects.DOMElement; -import EVENT_TYPE =Phaser.Scenes.Events -import type {Subscription} from "rxjs"; -import {worldFullMessageStream} from "../../Connexion/WorldFullMessageStream"; +import EVENT_TYPE = Phaser.Scenes.Events +import type { Subscription } from "rxjs"; +import { worldFullMessageStream } from "../../Connexion/WorldFullMessageStream"; import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager"; import RenderTexture = Phaser.GameObjects.RenderTexture; import Tilemap = Phaser.Tilemaps.Tilemap; -import {DirtyScene} from "./DirtyScene"; -import {TextUtils} from "../Components/TextUtils"; -import {touchScreenManager} from "../../Touch/TouchScreenManager"; -import {PinchManager} from "../UserInput/PinchManager"; -import {joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey} from "../Components/MobileJoystick"; -import {DEPTH_OVERLAY_INDEX} from "./DepthIndexes"; -import {waScaleManager} from "../Services/WaScaleManager"; -import {peerStore} from "../../Stores/PeerStore"; -import {EmoteManager} from "./EmoteManager"; +import { DirtyScene } from "./DirtyScene"; +import { TextUtils } from "../Components/TextUtils"; +import { touchScreenManager } from "../../Touch/TouchScreenManager"; +import { PinchManager } from "../UserInput/PinchManager"; +import { joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey } from "../Components/MobileJoystick"; +import { DEPTH_OVERLAY_INDEX } from "./DepthIndexes"; +import { waScaleManager } from "../Services/WaScaleManager"; +import { peerStore } from "../../Stores/PeerStore"; +import { EmoteManager } from "./EmoteManager"; import AnimatedTiles from "phaser-animated-tiles"; export interface GameSceneInitInterface { - initPosition: PointInterface|null, + initPosition: PointInterface | null, reconnecting: boolean } @@ -135,10 +135,10 @@ interface DeleteGroupEventInterface { const defaultStartLayerName = 'start'; export class GameScene extends DirtyScene implements CenterListener { - Terrains : Array; + Terrains: Array; CurrentPlayer!: Player; MapPlayers!: Phaser.Physics.Arcade.Group; - MapPlayersByKey : Map = new Map(); + MapPlayersByKey: Map = new Map(); Map!: Phaser.Tilemaps.Tilemap; Layers!: Array; Objects!: Array; @@ -149,10 +149,10 @@ export class GameScene extends DirtyScene implements CenterListener { startY!: number; circleTexture!: CanvasTexture; circleRedTexture!: CanvasTexture; - pendingEvents: Queue = new Queue(); - private initPosition: PositionInterface|null = null; + pendingEvents: Queue = new Queue(); + private initPosition: PositionInterface | null = null; private playersPositionInterpolator = new PlayersPositionInterpolator(); - public connection: RoomConnection|undefined; + public connection: RoomConnection | undefined; private simplePeer!: SimplePeer; private GlobalMessageManager!: GlobalMessageManager; private connectionAnswerPromise: Promise; @@ -160,7 +160,7 @@ export class GameScene extends DirtyScene implements CenterListener { // A promise that will resolve when the "create" method is called (signaling loading is ended) private createPromise: Promise; private createPromiseResolve!: (value?: void | PromiseLike) => void; - private iframeSubscriptionList! : Array; + private iframeSubscriptionList!: Array; private peerStoreUnsubscribe!: () => void; MapUrlFile: string; RoomId: string; @@ -180,22 +180,22 @@ export class GameScene extends DirtyScene implements CenterListener { private gameMap!: GameMap; private actionableItems: Map = new Map(); // The item that can be selected by pressing the space key. - private outlinedItem: ActionableItem|null = null; + private outlinedItem: ActionableItem | null = null; public userInputManager!: UserInputManager; - private isReconnecting: boolean|undefined = undefined; + private isReconnecting: boolean | undefined = undefined; private startLayerName!: string | null; private openChatIcon!: OpenChatIcon; private playerName!: string; private characterLayers!: string[]; - private companion!: string|null; - private messageSubscription: Subscription|null = null; - private popUpElements : Map = new Map(); - private originalMapUrl: string|undefined; - private pinchManager: PinchManager|undefined; + private companion!: string | null; + private messageSubscription: Subscription | null = null; + private popUpElements: Map = new Map(); + private originalMapUrl: string | undefined; + private pinchManager: PinchManager | undefined; private mapTransitioning: boolean = false; //used to prevent transitions happenning at the same time. private emoteManager!: EmoteManager; - constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) { + constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) { super({ key: customKey ?? room.id }); @@ -235,13 +235,13 @@ export class GameScene extends DirtyScene implements CenterListener { //this.load.audio('audio-report-message', '/resources/objects/report-message.mp3'); this.sound.pauseOnBlur = false; - this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => { + this.load.on(FILE_LOAD_ERROR, (file: { src: string }) => { // If we happen to be in HTTP and we are trying to load a URL in HTTPS only... (this happens only in dev environments) if (window.location.protocol === 'http:' && file.src === this.MapUrlFile && file.src.startsWith('http:') && this.originalMapUrl === undefined) { this.originalMapUrl = this.MapUrlFile; this.MapUrlFile = this.MapUrlFile.replace('http://', 'https://'); this.load.tilemapTiledJSON(this.MapUrlFile, this.MapUrlFile); - this.load.on('filecomplete-tilemapJSON-'+this.MapUrlFile, (key: string, type: string, data: unknown) => { + this.load.on('filecomplete-tilemapJSON-' + this.MapUrlFile, (key: string, type: string, data: unknown) => { this.onMapLoad(data); }); return; @@ -255,7 +255,7 @@ export class GameScene extends DirtyScene implements CenterListener { this.originalMapUrl = this.MapUrlFile; this.MapUrlFile = this.MapUrlFile.replace('https://', 'http://'); this.load.tilemapTiledJSON(this.MapUrlFile, this.MapUrlFile); - this.load.on('filecomplete-tilemapJSON-'+this.MapUrlFile, (key: string, type: string, data: unknown) => { + this.load.on('filecomplete-tilemapJSON-' + this.MapUrlFile, (key: string, type: string, data: unknown) => { this.onMapLoad(data); }); return; @@ -268,7 +268,7 @@ export class GameScene extends DirtyScene implements CenterListener { }); }); this.load.scenePlugin('AnimatedTiles', AnimatedTiles, 'animatedTiles', 'animatedTiles'); - this.load.on('filecomplete-tilemapJSON-'+this.MapUrlFile, (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 @@ -280,7 +280,7 @@ export class GameScene extends DirtyScene implements CenterListener { this.onMapLoad(data); } - this.load.spritesheet('layout_modes', 'resources/objects/layout_modes.png', {frameWidth: 32, frameHeight: 32}); + this.load.spritesheet('layout_modes', 'resources/objects/layout_modes.png', { frameWidth: 32, frameHeight: 32 }); this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); //eslint-disable-next-line @typescript-eslint/no-explicit-any (this.load as any).rexWebFont({ @@ -317,7 +317,7 @@ export class GameScene extends DirtyScene implements CenterListener { for (const layer of this.mapFile.layers) { if (layer.type === 'objectgroup') { for (const object of layer.objects) { - let objectsOfType: ITiledMapObject[]|undefined; + let objectsOfType: ITiledMapObject[] | undefined; if (!objects.has(object.type)) { objectsOfType = new Array(); } else { @@ -345,7 +345,7 @@ export class GameScene extends DirtyScene implements CenterListener { } default: continue; - //throw new Error('Unsupported object type: "'+ itemType +'"'); + //throw new Error('Unsupported object type: "'+ itemType +'"'); } itemFactory.preload(this.load); @@ -380,7 +380,7 @@ export class GameScene extends DirtyScene implements CenterListener { } //hook initialisation - init(initData : GameSceneInitInterface) { + init(initData: GameSceneInitInterface) { if (initData.initPosition !== undefined) { this.initPosition = initData.initPosition; //todo: still used? } @@ -464,7 +464,7 @@ export class GameScene extends DirtyScene implements CenterListener { this.Objects = new Array(); //initialise list of other player - this.MapPlayers = this.physics.add.group({immovable: true}); + this.MapPlayers = this.physics.add.group({ immovable: true }); //create input to move @@ -573,7 +573,7 @@ export class GameScene extends DirtyScene implements CenterListener { bottom: camera.scrollY + camera.height, }, this.companion - ).then((onConnect: OnConnectInterface) => { + ).then((onConnect: OnConnectInterface) => { this.connection = onConnect.connection; this.connection.onUserJoins((message: MessageUserJoined) => { @@ -725,23 +725,23 @@ export class GameScene extends DirtyScene implements CenterListener { const contextRed = this.circleRedTexture.context; contextRed.beginPath(); contextRed.arc(48, 48, 48, 0, 2 * Math.PI, false); - //context.lineWidth = 5; + //context.lineWidth = 5; contextRed.strokeStyle = '#ff0000'; contextRed.stroke(); this.circleRedTexture.refresh(); } - private safeParseJSONstring(jsonString: string|undefined, propertyName: string) { + private safeParseJSONstring(jsonString: string | undefined, propertyName: string) { try { return jsonString ? JSON.parse(jsonString) : {}; - } catch(e) { + } catch (e) { console.warn('Invalid JSON found in property "' + propertyName + '" of the map:' + jsonString, e); return {} } } - private triggerOnMapLayerPropertyChange(){ + private triggerOnMapLayerPropertyChange() { this.gameMap.onPropertyChange('exitSceneUrl', (newValue, oldValue) => { if (newValue) this.onMapExit(newValue as string); }); @@ -752,22 +752,22 @@ export class GameScene extends DirtyScene implements CenterListener { if (newValue === undefined) { layoutManager.removeActionButton('openWebsite', this.userInputManager); coWebsiteManager.closeCoWebsite(); - }else{ + } else { const openWebsiteFunction = () => { coWebsiteManager.loadCoWebsite(newValue as string, this.MapUrlFile, allProps.get('openWebsiteAllowApi') as boolean | undefined, allProps.get('openWebsitePolicy') as string | undefined); layoutManager.removeActionButton('openWebsite', this.userInputManager); }; const openWebsiteTriggerValue = allProps.get(TRIGGER_WEBSITE_PROPERTIES); - if(openWebsiteTriggerValue && openWebsiteTriggerValue === ON_ACTION_TRIGGER_BUTTON) { + if (openWebsiteTriggerValue && openWebsiteTriggerValue === ON_ACTION_TRIGGER_BUTTON) { let message = allProps.get(WEBSITE_MESSAGE_PROPERTIES); - if(message === undefined){ + if (message === undefined) { message = 'Press SPACE or touch here to open web site'; } layoutManager.addActionButton('openWebsite', message.toString(), () => { openWebsiteFunction(); }, this.userInputManager); - }else{ + } else { openWebsiteFunction(); } } @@ -776,12 +776,12 @@ export class GameScene extends DirtyScene implements CenterListener { if (newValue === undefined) { layoutManager.removeActionButton('jitsiRoom', this.userInputManager); this.stopJitsi(); - }else{ + } else { const openJitsiRoomFunction = () => { const roomName = jitsiFactory.getRoomName(newValue.toString(), this.instance); - const jitsiUrl = allProps.get("jitsiUrl") as string|undefined; + const jitsiUrl = allProps.get("jitsiUrl") as string | undefined; if (JITSI_PRIVATE_MODE && !jitsiUrl) { - const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined; + const adminTag = allProps.get("jitsiRoomAdminTag") as string | undefined; this.connection?.emitQueryJitsiJwtMessage(roomName, adminTag); } else { @@ -791,7 +791,7 @@ export class GameScene extends DirtyScene implements CenterListener { } const jitsiTriggerValue = allProps.get(TRIGGER_JITSI_PROPERTIES); - if(jitsiTriggerValue && jitsiTriggerValue === ON_ACTION_TRIGGER_BUTTON) { + if (jitsiTriggerValue && jitsiTriggerValue === ON_ACTION_TRIGGER_BUTTON) { let message = allProps.get(JITSI_MESSAGE_PROPERTIES); if (message === undefined) { message = 'Press SPACE or touch here to enter Jitsi Meet room'; @@ -799,7 +799,7 @@ export class GameScene extends DirtyScene implements CenterListener { layoutManager.addActionButton('jitsiRoom', message.toString(), () => { openJitsiRoomFunction(); }, this.userInputManager); - }else{ + } else { openJitsiRoomFunction(); } } @@ -812,8 +812,8 @@ export class GameScene extends DirtyScene implements CenterListener { } }); this.gameMap.onPropertyChange('playAudio', (newValue, oldValue, allProps) => { - const volume = allProps.get(AUDIO_VOLUME_PROPERTY) as number|undefined; - const loop = allProps.get(AUDIO_LOOP_PROPERTY) as boolean|undefined; + const volume = allProps.get(AUDIO_VOLUME_PROPERTY) as number | undefined; + const loop = allProps.get(AUDIO_LOOP_PROPERTY) as boolean | undefined; newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl(), volume, loop); }); // TODO: This legacy property should be removed at some point @@ -832,13 +832,13 @@ export class GameScene extends DirtyScene implements CenterListener { } private listenToIframeEvents(): void { - this.iframeSubscriptionList = []; - this.iframeSubscriptionList.push(iframeListener.openPopupStream.subscribe((openPopupEvent) => { + this.iframeSubscriptionList = []; + this.iframeSubscriptionList.push(iframeListener.openPopupStream.subscribe((openPopupEvent) => { - let objectLayerSquare : ITiledMapObject; + let objectLayerSquare: ITiledMapObject; const targetObjectData = this.getObjectLayerData(openPopupEvent.targetObject); - if (targetObjectData !== undefined){ - objectLayerSquare = targetObjectData; + if (targetObjectData !== undefined) { + objectLayerSquare = targetObjectData; } else { console.error("Error while opening a popup. Cannot find an object on the map with name '" + openPopupEvent.targetObject + "'. The first parameter of WA.openPopup() must be the name of a rectangle object in your map."); return; @@ -851,14 +851,14 @@ ${escapedMessage} html += buttonContainer; let id = 0; for (const button of openPopupEvent.buttons) { - html += ``; + html += ``; id++; } html += ''; - const domElement = this.add.dom(objectLayerSquare.x , + const domElement = this.add.dom(objectLayerSquare.x, objectLayerSquare.y).createFromHTML(html); - const container : HTMLDivElement = domElement.getChildByID("container") as HTMLDivElement; + const container: HTMLDivElement = domElement.getChildByID("container") as HTMLDivElement; container.style.width = objectLayerSquare.width + "px"; domElement.scale = 0; domElement.setClassName('popUpElement'); @@ -878,73 +878,70 @@ ${escapedMessage} id++; } this.tweens.add({ - targets : domElement , - scale : 1, - ease : "EaseOut", - duration : 400, + targets: domElement, + scale: 1, + ease: "EaseOut", + duration: 400, }); this.popUpElements.set(openPopupEvent.popupId, domElement); })); - this.iframeSubscriptionList.push(iframeListener.closePopupStream.subscribe((closePopupEvent) => { + this.iframeSubscriptionList.push(iframeListener.closePopupStream.subscribe((closePopupEvent) => { const popUpElement = this.popUpElements.get(closePopupEvent.popupId); if (popUpElement === undefined) { - console.error('Could not close popup with ID ', closePopupEvent.popupId,'. Maybe it has already been closed?'); + console.error('Could not close popup with ID ', closePopupEvent.popupId, '. Maybe it has already been closed?'); } this.tweens.add({ - targets : popUpElement , - scale : 0, - ease : "EaseOut", - duration : 400, - onComplete : () => { + targets: popUpElement, + scale: 0, + ease: "EaseOut", + duration: 400, + onComplete: () => { popUpElement?.destroy(); this.popUpElements.delete(closePopupEvent.popupId); }, }); })); - this.iframeSubscriptionList.push(iframeListener.disablePlayerControlStream.subscribe(()=>{ + this.iframeSubscriptionList.push(iframeListener.disablePlayerControlStream.subscribe(() => { this.userInputManager.disableControls(); })); - this.iframeSubscriptionList.push(iframeListener.playSoundStream.subscribe((playSoundEvent)=> - { - const url = new URL(playSoundEvent.url, this.MapUrlFile); - soundManager.playSound(this.load,this.sound,url.toString(),playSoundEvent.config); - })) + this.iframeSubscriptionList.push(iframeListener.playSoundStream.subscribe((playSoundEvent) => { + const url = new URL(playSoundEvent.url, this.MapUrlFile); + soundManager.playSound(this.load, this.sound, url.toString(), playSoundEvent.config); + })) - this.iframeSubscriptionList.push(iframeListener.stopSoundStream.subscribe((stopSoundEvent)=> - { + this.iframeSubscriptionList.push(iframeListener.stopSoundStream.subscribe((stopSoundEvent) => { const url = new URL(stopSoundEvent.url, this.MapUrlFile); - soundManager.stopSound(this.sound,url.toString()); + soundManager.stopSound(this.sound, url.toString()); })) - this.iframeSubscriptionList.push(iframeListener.loadSoundStream.subscribe((loadSoundEvent)=> - { + this.iframeSubscriptionList.push(iframeListener.loadSoundStream.subscribe((loadSoundEvent) => { const url = new URL(loadSoundEvent.url, this.MapUrlFile); - soundManager.loadSound(this.load,this.sound,url.toString()); + soundManager.loadSound(this.load, this.sound, url.toString()); })) - this.iframeSubscriptionList.push(iframeListener.enablePlayerControlStream.subscribe(()=>{ + this.iframeSubscriptionList.push(iframeListener.enablePlayerControlStream.subscribe(() => { this.userInputManager.restoreControls(); })); - this.iframeSubscriptionList.push(iframeListener.loadPageStream.subscribe((url:string)=>{ - this.loadNextGame(url).then(()=>{ - this.events.once(EVENT_TYPE.POST_UPDATE,()=>{ + this.iframeSubscriptionList.push(iframeListener.loadPageStream.subscribe((url: string) => { + this.loadNextGame(url).then(() => { + this.events.once(EVENT_TYPE.POST_UPDATE, () => { this.onMapExit(url); }) }) })); - let scriptedBubbleSprite : Sprite; - this.iframeSubscriptionList.push(iframeListener.displayBubbleStream.subscribe(()=>{ - scriptedBubbleSprite = new Sprite(this,this.CurrentPlayer.x + 25,this.CurrentPlayer.y,'circleSprite-white'); + let scriptedBubbleSprite: Sprite; + this.iframeSubscriptionList.push(iframeListener.displayBubbleStream.subscribe(() => { + scriptedBubbleSprite = new Sprite(this, this.CurrentPlayer.x + 25, this.CurrentPlayer.y, 'circleSprite-white'); scriptedBubbleSprite.setDisplayOrigin(48, 48); this.add.existing(scriptedBubbleSprite); })); - this.iframeSubscriptionList.push(iframeListener.removeBubbleStream.subscribe(()=>{ + this.iframeSubscriptionList.push(iframeListener.removeBubbleStream.subscribe(() => { scriptedBubbleSprite.destroy(); })); @@ -957,9 +954,11 @@ ${escapedMessage} private onMapExit(exitKey: string) { if (this.mapTransitioning) return; this.mapTransitioning = true; - const {roomId, hash} = Room.getIdFromIdentifier(exitKey, this.MapUrlFile, this.instance); - if (!roomId) throw new Error('Could not find the room from its exit key: '+exitKey); - urlManager.pushStartLayerNameToUrl(hash); + const { roomId, hash } = Room.getIdFromIdentifier(exitKey, this.MapUrlFile, this.instance); + if (!roomId) throw new Error('Could not find the room from its exit key: ' + exitKey); + if (hash) { + urlManager.pushStartLayerNameToUrl(hash); + } if (roomId !== this.scene.key) { if (this.scene.get(roomId) === null) { console.error("next room not loaded", exitKey); @@ -1001,7 +1000,7 @@ ${escapedMessage} mediaManager.hideGameOverlay(); - for(const iframeEvents of this.iframeSubscriptionList){ + for (const iframeEvents of this.iframeSubscriptionList) { iframeEvents.unsubscribe(); } } @@ -1021,7 +1020,7 @@ ${escapedMessage} private switchLayoutMode(): void { //if discussion is activated, this layout cannot be activated - if(mediaManager.activatedDiscussion){ + if (mediaManager.activatedDiscussion) { return; } const mode = layoutManager.getLayoutMode(); @@ -1062,24 +1061,24 @@ ${escapedMessage} private initPositionFromLayerName(layerName: string, startLayerName: string | null) { for (const layer of this.gameMap.layersIterator) { - if ((layerName === layer.name || layer.name.endsWith('/'+layerName)) && layer.type === 'tilelayer' && (layerName === defaultStartLayerName || this.isStartLayer(layer))) { + if ((layerName === layer.name || layer.name.endsWith('/' + layerName)) && layer.type === 'tilelayer' && (layerName === defaultStartLayerName || this.isStartLayer(layer))) { const startPosition = this.startUser(layer, startLayerName); - this.startX = startPosition.x + this.mapFile.tilewidth/2; - this.startY = startPosition.y + this.mapFile.tileheight/2; + this.startX = startPosition.x + this.mapFile.tilewidth / 2; + this.startY = startPosition.y + this.mapFile.tileheight / 2; } } } - private getExitUrl(layer: ITiledMapLayer): string|undefined { - return this.getProperty(layer, "exitUrl") as string|undefined; + private getExitUrl(layer: ITiledMapLayer): string | undefined { + 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 getExitSceneUrl(layer: ITiledMapLayer): string | undefined { + return this.getProperty(layer, "exitSceneUrl") as string | undefined; } private isStartLayer(layer: ITiledMapLayer): boolean { @@ -1090,8 +1089,8 @@ ${escapedMessage} return (this.getProperties(map, "script") as string[]).map((script) => (new URL(script, this.MapUrlFile)).toString()); } - private getProperty(layer: ITiledMapLayer|ITiledMap, name: string): string|boolean|number|undefined { - const properties: ITiledMapLayerProperty[]|undefined = layer.properties; + private getProperty(layer: ITiledMapLayer | ITiledMap, name: string): string | boolean | number | undefined { + const properties: ITiledMapLayerProperty[] | undefined = layer.properties; if (!properties) { return undefined; } @@ -1102,8 +1101,8 @@ ${escapedMessage} return obj.value; } - private getProperties(layer: ITiledMapLayer|ITiledMap, name: string): (string|number|boolean|undefined)[] { - const properties: ITiledMapLayerProperty[]|undefined = layer.properties; + private getProperties(layer: ITiledMapLayer | ITiledMap, name: string): (string | number | boolean | undefined)[] { + const properties: ITiledMapLayerProperty[] | undefined = layer.properties; if (!properties) { return []; } @@ -1112,19 +1111,19 @@ ${escapedMessage} //todo: push that into the gameManager private loadNextGame(exitSceneIdentifier: string): Promise { - const {roomId, hash} = Room.getIdFromIdentifier(exitSceneIdentifier, this.MapUrlFile, this.instance); + const { roomId } = Room.getIdFromIdentifier(exitSceneIdentifier, this.MapUrlFile, this.instance); const room = new Room(roomId); - return gameManager.loadMap(room, this.scene).catch(() => {}); + return gameManager.loadMap(room, this.scene).catch(() => { }); } private startUser(layer: ITiledMapTileLayer, startName: string | null): PositionInterface { const tiles = layer.data; - if (typeof(tiles) === 'string') { + if (typeof (tiles) === 'string') { throw new Error('The content of a JSON map must be filled as a JSON array, not as a string'); } - const possibleStartPositions : PositionInterface[] = []; - tiles.forEach((objectKey : number, key: number) => { - if(objectKey === 0){ + const possibleStartPositions: PositionInterface[] = []; + tiles.forEach((objectKey: number, key: number) => { + if (objectKey === 0) { return; } const y = Math.floor(key / layer.width); @@ -1141,7 +1140,7 @@ ${escapedMessage} }); // Get a value at random amongst allowed values if (possibleStartPositions.length === 0) { - console.warn('The start layer "'+layer.name+'" for this map is empty.'); + console.warn('The start layer "' + layer.name + '" for this map is empty.'); return { x: 0, y: 0 @@ -1153,12 +1152,12 @@ ${escapedMessage} //todo: in a dedicated class/function? initCamera() { - this.cameras.main.setBounds(0,0, this.Map.widthInPixels, this.Map.heightInPixels); + this.cameras.main.setBounds(0, 0, this.Map.widthInPixels, this.Map.heightInPixels); this.cameras.main.startFollow(this.CurrentPlayer, true); this.updateCameraOffset(); } - addLayer(Layer : Phaser.Tilemaps.TilemapLayer){ + addLayer(Layer: Phaser.Tilemaps.TilemapLayer) { this.Layers.push(Layer); } @@ -1168,7 +1167,7 @@ ${escapedMessage} this.physics.add.collider(this.CurrentPlayer, Layer, (object1: GameObject, object2: GameObject) => { //this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name) }); - Layer.setCollisionByProperty({collides: true}); + Layer.setCollisionByProperty({ collides: true }); if (DEBUG_MODE) { //debug code to see the collision hitbox of the object in the top layer Layer.renderDebug(this.add.graphics(), { @@ -1180,7 +1179,7 @@ ${escapedMessage} }); } - createCurrentPlayer(){ + createCurrentPlayer() { //TODO create animation moving between exit and start const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.characterLayers); try { @@ -1205,8 +1204,8 @@ ${escapedMessage} this.CurrentPlayer.on(requestEmoteEventName, (emoteKey: string) => { this.connection?.emitEmoteEvent(emoteKey); }) - }catch (err){ - if(err instanceof TextureError) { + } catch (err) { + if (err instanceof TextureError) { gameManager.leaveGame(this, SelectCharacterSceneName, new SelectCharacterScene()); } throw err; @@ -1267,7 +1266,7 @@ ${escapedMessage} } let shortestDistance: number = Infinity; - let selectedItem: ActionableItem|null = null; + let selectedItem: ActionableItem | null = null; for (const item of this.actionableItems.values()) { const distance = item.actionableDistance(x, y); if (distance !== null && distance < shortestDistance) { @@ -1301,7 +1300,7 @@ ${escapedMessage} * @param time * @param delta The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate. */ - update(time: number, delta: number) : void { + update(time: number, delta: number): void { this.dirty = false; mediaManager.updateScene(); this.currentTick = time; @@ -1361,8 +1360,8 @@ ${escapedMessage} const currentPlayerId = this.connection?.getUserId(); this.removeAllRemotePlayers(); // load map - usersPosition.forEach((userPosition : MessageUserPositionInterface) => { - if(userPosition.userId === currentPlayerId){ + usersPosition.forEach((userPosition: MessageUserPositionInterface) => { + if (userPosition.userId === currentPlayerId) { return; } this.addPlayer(userPosition); @@ -1372,16 +1371,16 @@ ${escapedMessage} /** * Called by the connexion when a new player arrives on a map */ - public addPlayer(addPlayerData : AddPlayerInterface) : void { + public addPlayer(addPlayerData: AddPlayerInterface): void { this.pendingEvents.enqueue({ type: "AddPlayerEvent", event: addPlayerData }); } - private doAddPlayer(addPlayerData : AddPlayerInterface): void { + private doAddPlayer(addPlayerData: AddPlayerInterface): void { //check if exist player, if exist, move position - if(this.MapPlayersByKey.has(addPlayerData.userId)){ + if (this.MapPlayersByKey.has(addPlayerData.userId)) { this.updatePlayerPosition({ userId: addPlayerData.userId, position: addPlayerData.position @@ -1443,10 +1442,10 @@ ${escapedMessage} } private doUpdatePlayerPosition(message: MessageUserMovedInterface): void { - const player : RemotePlayer | undefined = this.MapPlayersByKey.get(message.userId); + const player: RemotePlayer | undefined = this.MapPlayersByKey.get(message.userId); if (player === undefined) { //throw new Error('Cannot find player with ID "' + message.userId +'"'); - console.error('Cannot update position of player with ID "' + message.userId +'": player not found'); + console.error('Cannot update position of player with ID "' + message.userId + '": player not found'); return; } @@ -1490,7 +1489,7 @@ ${escapedMessage} doDeleteGroup(groupId: number): void { const group = this.groups.get(groupId); - if(!group){ + if (!group) { return; } group.destroy(); @@ -1519,7 +1518,7 @@ ${escapedMessage} bottom: camera.scrollY + camera.height, }); } - private getObjectLayerData(objectName : string) : ITiledMapObject| undefined{ + private getObjectLayerData(objectName: string): ITiledMapObject | undefined { for (const layer of this.mapFile.layers) { if (layer.type === 'objectgroup' && layer.name === 'floorLayer') { for (const object of layer.objects) { @@ -1553,7 +1552,7 @@ ${escapedMessage} const game = HtmlUtils.querySelectorOrFail('#game canvas'); // Let's put this in Game coordinates by applying the zoom level: - this.cameras.main.setFollowOffset((xCenter - game.offsetWidth/2) * window.devicePixelRatio / this.scale.zoom , (yCenter - game.offsetHeight/2) * window.devicePixelRatio / this.scale.zoom); + this.cameras.main.setFollowOffset((xCenter - game.offsetWidth / 2) * window.devicePixelRatio / this.scale.zoom, (yCenter - game.offsetHeight / 2) * window.devicePixelRatio / this.scale.zoom); } public onCenterChange(): void { @@ -1562,16 +1561,16 @@ ${escapedMessage} public startJitsi(roomName: string, jwt?: string): void { const allProps = this.gameMap.getCurrentProperties(); - const jitsiConfig = this.safeParseJSONstring(allProps.get("jitsiConfig") as string|undefined, 'jitsiConfig'); - const jitsiInterfaceConfig = this.safeParseJSONstring(allProps.get("jitsiInterfaceConfig") as string|undefined, 'jitsiInterfaceConfig'); - const jitsiUrl = allProps.get("jitsiUrl") as string|undefined; + const jitsiConfig = this.safeParseJSONstring(allProps.get("jitsiConfig") as string | undefined, 'jitsiConfig'); + const jitsiInterfaceConfig = this.safeParseJSONstring(allProps.get("jitsiInterfaceConfig") as string | undefined, 'jitsiInterfaceConfig'); + const jitsiUrl = allProps.get("jitsiUrl") as string | undefined; jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl); this.connection?.setSilent(true); mediaManager.hideGameOverlay(); //permit to stop jitsi when user close iframe - mediaManager.addTriggerCloseJitsiFrameButton('close-jisi',() => { + mediaManager.addTriggerCloseJitsiFrameButton('close-jisi', () => { this.stopJitsi(); }); } @@ -1585,7 +1584,7 @@ ${escapedMessage} } //todo: put this into an 'orchestrator' scene (EntryScene?) - private bannedUser(){ + private bannedUser() { this.cleanupClosingScene(); this.userInputManager.disableControls(); this.scene.start(ErrorSceneName, { @@ -1596,22 +1595,22 @@ ${escapedMessage} } //todo: put this into an 'orchestrator' scene (EntryScene?) - private showWorldFullError(message: string|null): void { + private showWorldFullError(message: string | null): void { this.cleanupClosingScene(); this.scene.stop(ReconnectingSceneName); this.scene.remove(ReconnectingSceneName); this.userInputManager.disableControls(); //FIX ME to use status code - if(message == undefined){ + if (message == undefined) { this.scene.start(ErrorSceneName, { title: 'Connection rejected', subTitle: 'The world you are trying to join is full. Try again later.', message: 'If you want more information, you may contact us at: workadventure@thecodingmachine.com' }); - }else{ + } else { this.scene.start(ErrorSceneName, { title: 'Connection rejected', - subTitle: 'You cannot join the World. Try again later. \n\r \n\r Error: '+message+'.', + subTitle: 'You cannot join the World. Try again later. \n\r \n\r Error: ' + message + '.', message: 'If you want more information, you may contact administrator or contact us at: workadventure@thecodingmachine.com' }); } diff --git a/front/tests/Phaser/Game/RoomTest.ts b/front/tests/Phaser/Game/RoomTest.ts index 80624d64..4bd4283a 100644 --- a/front/tests/Phaser/Game/RoomTest.ts +++ b/front/tests/Phaser/Game/RoomTest.ts @@ -1,57 +1,57 @@ import "jasmine"; -import {Room} from "../../../src/Connexion/Room"; +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', '', ''); + const { roomId, hash } = Room.getIdFromIdentifier('/_/global/maps.workadventu.re/test2.json', '', ''); expect(roomId).toEqual('_/global/maps.workadventu.re/test2.json'); - expect(hash).toEqual(''); + expect(hash).toEqual(null); }); 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', '', ''); + 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'); + 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(''); + expect(hash).toEqual(null); }); - - + + 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'); + 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(''); + expect(hash).toEqual(null); }); 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'); + 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(''); + expect(hash).toEqual(null); }); 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'); + 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(''); + expect(hash).toEqual(null); }); it("should work with a relative file link that rewrite the map domain", () => { - const {roomId, hash} = Room.getIdFromIdentifier('../../maps.workadventure.localhost/Floor1/floor1.json', 'https://maps.workadventu.re/floor0/Floor0.json', 'global'); + const { roomId, hash } = Room.getIdFromIdentifier('../../maps.workadventure.localhost/Floor1/floor1.json', 'https://maps.workadventu.re/floor0/Floor0.json', 'global'); expect(roomId).toEqual('_/global/maps.workadventure.localhost/Floor1/floor1.json'); - expect(hash).toEqual(''); + expect(hash).toEqual(null); }); it("should work with a relative file link that rewrite the map instance", () => { - const {roomId, hash} = Room.getIdFromIdentifier('../../../notglobal/maps.workadventu.re/Floor1/floor1.json', 'https://maps.workadventu.re/floor0/Floor0.json', 'global'); + const { roomId, hash } = Room.getIdFromIdentifier('../../../notglobal/maps.workadventu.re/Floor1/floor1.json', 'https://maps.workadventu.re/floor0/Floor0.json', 'global'); expect(roomId).toEqual('_/notglobal/maps.workadventu.re/Floor1/floor1.json'); - expect(hash).toEqual(''); + expect(hash).toEqual(null); }); it("should work with a relative file link that change the map type", () => { - const {roomId, hash} = Room.getIdFromIdentifier('../../../../@/tcm/is/great', 'https://maps.workadventu.re/floor0/Floor0.json', 'global'); + const { roomId, hash } = Room.getIdFromIdentifier('../../../../@/tcm/is/great', 'https://maps.workadventu.re/floor0/Floor0.json', 'global'); expect(roomId).toEqual('@/tcm/is/great'); - expect(hash).toEqual(''); + expect(hash).toEqual(null); }); - + 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'); + 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"); }); From 54d392be822e11204225db0391643c7341847af9 Mon Sep 17 00:00:00 2001 From: jonny Date: Wed, 23 Jun 2021 15:06:38 +0200 Subject: [PATCH 03/12] fixed not returnin null if parsed from url --- front/package.json | 2 +- front/src/Connexion/Room.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/front/package.json b/front/package.json index 8652ba83..3204784f 100644 --- a/front/package.json +++ b/front/package.json @@ -58,7 +58,7 @@ "templater": "cross-env ./templater.sh", "serve": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" webpack serve --open", "build": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production webpack", - "test": "TS_NODE_PROJECT=\"tsconfig-for-jasmine.json\" ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json", + "test": "cross-env TS_NODE_PROJECT=\"tsconfig-for-jasmine.json\" ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json", "lint": "node_modules/.bin/eslint src/ . --ext .ts", "fix": "node_modules/.bin/eslint --fix src/ . --ext .ts", "svelte-check-watch": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore\" --watch", diff --git a/front/src/Connexion/Room.ts b/front/src/Connexion/Room.ts index e68108cb..434f9060 100644 --- a/front/src/Connexion/Room.ts +++ b/front/src/Connexion/Room.ts @@ -46,6 +46,9 @@ export class Room { roomId = roomId.substring(1); //remove the leading slash hash = absoluteExitSceneUrl.hash; hash = hash.substring(1); //remove the leading diese + if (!hash.length) { + hash = null + } } else { //absolute room Id const parts = identifier.split('#'); roomId = parts[0]; From f536d538ead53f43885c319e6393f5863ec8f3c7 Mon Sep 17 00:00:00 2001 From: jonny Date: Fri, 25 Jun 2021 17:35:42 +0200 Subject: [PATCH 04/12] added backwards compatible check and maps --- front/src/Phaser/Game/GameMap.ts | 4 ++ front/src/Phaser/Game/GameScene.ts | 21 +++---- maps/tests/function_tiles.json | 33 ++++++++++ maps/tests/function_tiles.png | Bin 0 -> 1313 bytes maps/tests/start-tile.json | 95 +++++++++++++++++++++++++++++ 5 files changed, 142 insertions(+), 11 deletions(-) create mode 100644 maps/tests/function_tiles.json create mode 100644 maps/tests/function_tiles.png create mode 100644 maps/tests/start-tile.json diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index b84ec477..7c446dbf 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -17,6 +17,8 @@ export class GameMap { public exitUrls: Array = [] + public hasStartTile = false; + public constructor(private map: ITiledMap) { this.layersIterator = new LayersIterator(map); @@ -27,6 +29,8 @@ export class GameMap { tile.properties.forEach(prop => { if (prop.name == "exitUrl" && typeof prop.value == "string") { this.exitUrls.push(prop.value); + } else if (prop.name == "start") { + this.hasStartTile = true } }) } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 30b92a91..98e990ef 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1043,7 +1043,7 @@ ${escapedMessage} } else { // Now, let's find the start layer if (this.startLayerName) { - this.initPositionFromLayerName(this.startLayerName, null); + this.initPositionFromLayerName(this.startLayerName, this.startLayerName); } if (this.startX === undefined) { // If we have no start layer specified or if the hash passed does not exist, let's go with the default start position. @@ -1059,10 +1059,10 @@ ${escapedMessage} } } - private initPositionFromLayerName(layerName: string, startLayerName: string | null) { + private initPositionFromLayerName(selectedOrdDefaultLayer: string, selectedLayer: string | null) { for (const layer of this.gameMap.layersIterator) { - if ((layerName === layer.name || layer.name.endsWith('/' + layerName)) && layer.type === 'tilelayer' && (layerName === defaultStartLayerName || this.isStartLayer(layer))) { - const startPosition = this.startUser(layer, startLayerName); + if ((selectedOrdDefaultLayer === layer.name || layer.name.endsWith('/' + selectedOrdDefaultLayer)) && layer.type === 'tilelayer' && (selectedOrdDefaultLayer === defaultStartLayerName || this.isStartLayer(layer))) { + const startPosition = this.startUser(layer, selectedLayer); this.startX = startPosition.x + this.mapFile.tilewidth / 2; this.startY = startPosition.y + this.mapFile.tileheight / 2; } @@ -1116,8 +1116,8 @@ ${escapedMessage} return gameManager.loadMap(room, this.scene).catch(() => { }); } - private startUser(layer: ITiledMapTileLayer, startName: string | null): PositionInterface { - const tiles = layer.data; + private startUser(selectedOrDefaultLayer: ITiledMapTileLayer, startName: string | null): PositionInterface { + const tiles = selectedOrDefaultLayer.data; if (typeof (tiles) === 'string') { throw new Error('The content of a JSON map must be filled as a JSON array, not as a string'); } @@ -1126,21 +1126,20 @@ ${escapedMessage} if (objectKey === 0) { return; } - const y = Math.floor(key / layer.width); - const x = key % layer.width; + const y = Math.floor(key / selectedOrDefaultLayer.width); + const x = key % selectedOrDefaultLayer.width; - if (startName) { + if (startName && this.gameMap.hasStartTile) { const properties = this.gameMap.getPropertiesForIndex(objectKey); if (!properties.length || !properties.some(property => property.name == "start" && property.value == startName)) { return } } - possibleStartPositions.push({ x: x * this.mapFile.tilewidth, y: y * this.mapFile.tilewidth }); }); // Get a value at random amongst allowed values if (possibleStartPositions.length === 0) { - console.warn('The start layer "' + layer.name + '" for this map is empty.'); + console.warn('The start layer "' + selectedOrDefaultLayer.name + '" for this map is empty.'); return { x: 0, y: 0 diff --git a/maps/tests/function_tiles.json b/maps/tests/function_tiles.json new file mode 100644 index 00000000..9bc374eb --- /dev/null +++ b/maps/tests/function_tiles.json @@ -0,0 +1,33 @@ +{ "columns":2, + "image":"function_tiles.png", + "imageheight":64, + "imagewidth":64, + "margin":0, + "name":"function_tiles", + "spacing":0, + "tilecount":4, + "tiledversion":"1.6.0", + "tileheight":32, + "tiles":[ + { + "id":0, + "properties":[ + { + "name":"start", + "type":"string", + "value":"S1" + }] + }, + { + "id":1, + "properties":[ + { + "name":"start", + "type":"string", + "value":"S2" + }] + }], + "tilewidth":32, + "type":"tileset", + "version":"1.6" +} \ No newline at end of file diff --git a/maps/tests/function_tiles.png b/maps/tests/function_tiles.png new file mode 100644 index 0000000000000000000000000000000000000000..147eb61942894f428ec11dd1bb40271a2c9d7bdd GIT binary patch literal 1313 zcmV++1>X9JP)WFU8GbZ8()Nlj2>E@cM*00f0eL_t(|+U=W7j1)x_ z$A2@7kwl1r>;Nu!@CJI&17HkXICwDxv+S3$#&}T!CyyqCgd<0ecu^B}$ujH)Tw~xu z0tXT}5hCCPBd#C7u4_yrh7Wg}L%qt>7G2%dJ>9+Y(f?%9+tu~)>Q%judi55v!XE2A z@NQYL3kHAfU)H)R&kuC7oI8!lVehkwd z$9MDh91a5Sc=G#TW@N{15PMx*TsBEu4@~${Pz@=27nt_#pk|F;iFoe?ya`-#2mjsSMatSS#X8+74a<{%(Z_ z75P0#+QAv%g`#&(|Gw5;P zKj3#@cM{*i`o8FB*H`ZHME0Ondy&R93Ph{hSTEliS#qH2;7ZNfXcNGKY@m@W38*>B z>%gc*L@s__l+n0o{5_Y~CUg;S8kh&x$;_*H`BSwB?N@Ou$uKb0HDXwzL|fuJ;4$#6 zQMMJhTEyAvCU5~*ZRz}5z#B0H3^*JXvuuAH0k?r8DSf&}z{ZI0uK`bexG)ppK1C6r zHRT3& zp+aZVMG!DA1C7anR3B6~6?HH{SRKfLR4+l{QlOoSf5k^AoPZzYxi-(Zj*Ean*^K*) zeNiX@Q^23V_r_987Da%L1#C9nr^V;%D?$;~f%ihBBA2DG31|Sn0p}y6qLHT_h%(P} z^xl17i4rABtSkm-W*-C|r)-}(zSxP1mCZ_+;Vvp>w#%ybHt-ejv(%df&H_8KV8A1p z#KshDTVcRWX?p>c8nRj(gb|u)A{tkKPk>iZNgQuUSpfXhDg#EX)x0A<*%WwJ3Vh1)E9XqnFT^~~ zkpW@YCVkF^eEU@N)e`VoRs#kRzeSoMNT2#im~jo2Gq_dcdJQVq@Urw{#%&Kv*%I)1 zw^*O;sO*Uws0@lHs4R-9kZm2u^fF7LM2QkktayN7r4t@{KGU*ffQL^#XV}z}9(|4* z^&hmm9gbtbJSsx2FRed9-Ij#qTI-t>ohL9Ur^vs$K?H{7WC-e-Xo2Rn0jS%X1)5h# z1HgyfXazQiAFj9cg!FMh-tVLGYP^%_JIxBriC)k*m1D-_$AMQ!_@4xwY6VV(l%4Gr zGdOANfcu7$nYcXi{*;t;ECaL`sFP>D2DSkIB(M@AMp;{;=HUgV!BDs9fUd Date: Fri, 25 Jun 2021 17:57:09 +0200 Subject: [PATCH 05/12] cleanup --- front/src/Phaser/Game/GameScene.ts | 94 ++------------ .../Phaser/Game/StartPositionCalculator.ts | 117 ++++++++++++++++++ 2 files changed, 129 insertions(+), 82 deletions(-) create mode 100644 front/src/Phaser/Game/StartPositionCalculator.ts diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 98e990ef..458e5f96 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -96,6 +96,7 @@ import { peerStore } from "../../Stores/PeerStore"; import { EmoteManager } from "./EmoteManager"; import AnimatedTiles from "phaser-animated-tiles"; +import { StartPositionCalculator } from './StartPositionCalculator'; export interface GameSceneInitInterface { initPosition: PointInterface | null, @@ -132,7 +133,6 @@ interface DeleteGroupEventInterface { groupId: number } -const defaultStartLayerName = 'start'; export class GameScene extends DirtyScene implements CenterListener { Terrains: Array; @@ -145,8 +145,6 @@ export class GameScene extends DirtyScene implements CenterListener { mapFile!: ITiledMap; animatedTiles!: AnimatedTiles; groups: Map; - startX!: number; - startY!: number; circleTexture!: CanvasTexture; circleRedTexture!: CanvasTexture; pendingEvents: Queue = new Queue(); @@ -183,7 +181,6 @@ export class GameScene extends DirtyScene implements CenterListener { private outlinedItem: ActionableItem | null = null; public userInputManager!: UserInputManager; private isReconnecting: boolean | undefined = undefined; - private startLayerName!: string | null; private openChatIcon!: OpenChatIcon; private playerName!: string; private characterLayers!: string[]; @@ -194,6 +191,7 @@ export class GameScene extends DirtyScene implements CenterListener { private pinchManager: PinchManager | undefined; private mapTransitioning: boolean = false; //used to prevent transitions happenning at the same time. private emoteManager!: EmoteManager; + startPositionCalculator!: StartPositionCalculator; constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) { super({ @@ -395,7 +393,6 @@ export class GameScene extends DirtyScene implements CenterListener { gameManager.gameSceneIsCreated(this); urlManager.pushRoomIdToUrl(this.room); - this.startLayerName = urlManager.getStartLayerNameFromUrl(); if (touchScreenManager.supportTouchScreen) { this.pinchManager = new PinchManager(this); @@ -458,7 +455,9 @@ export class GameScene extends DirtyScene implements CenterListener { throw new Error('Your map MUST contain a layer of type "objectgroup" whose name is "floorLayer" that represents the layer characters are drawn at. This layer cannot be contained in a group.'); } - this.initStartXAndStartY(); + this.startPositionCalculator = new StartPositionCalculator(this.gameMap, this.mapFile, this.initPosition, urlManager.getStartLayerNameFromUrl()) + + //add entities this.Objects = new Array(); @@ -563,8 +562,8 @@ export class GameScene extends DirtyScene implements CenterListener { this.playerName, this.characterLayers, { - x: this.startX, - y: this.startY + x: this.startPositionCalculator.startX, + y: this.startPositionCalculator.startY }, { left: camera.scrollX, @@ -970,9 +969,9 @@ ${escapedMessage} this.scene.start(roomId); } else { //if the exit points to the current map, we simply teleport the user back to the startLayer - this.initPositionFromLayerName(hash || defaultStartLayerName, hash); - this.CurrentPlayer.x = this.startX; - this.CurrentPlayer.y = this.startY; + this.startPositionCalculator.initPositionFromLayerName(hash, hash); + this.CurrentPlayer.x = this.startPositionCalculator.startX; + this.CurrentPlayer.y = this.startPositionCalculator.startY; setTimeout(() => this.mapTransitioning = false, 500); } } @@ -1035,40 +1034,7 @@ ${escapedMessage} } } - private initStartXAndStartY() { - // If there is an init position passed - if (this.initPosition !== null) { - this.startX = this.initPosition.x; - this.startY = this.initPosition.y; - } else { - // Now, let's find the start layer - if (this.startLayerName) { - this.initPositionFromLayerName(this.startLayerName, this.startLayerName); - } - if (this.startX === undefined) { - // If we have no start layer specified or if the hash passed does not exist, let's go with the default start position. - this.initPositionFromLayerName(defaultStartLayerName, this.startLayerName); - } - } - // Still no start position? Something is wrong with the map, we need a "start" layer. - if (this.startX === undefined) { - console.warn('This map is missing a layer named "start" that contains the available default start positions.'); - // Let's start in the middle of the map - this.startX = this.mapFile.width * 16; - this.startY = this.mapFile.height * 16; - } - } - private initPositionFromLayerName(selectedOrdDefaultLayer: string, selectedLayer: string | null) { - for (const layer of this.gameMap.layersIterator) { - if ((selectedOrdDefaultLayer === layer.name || layer.name.endsWith('/' + selectedOrdDefaultLayer)) && layer.type === 'tilelayer' && (selectedOrdDefaultLayer === defaultStartLayerName || this.isStartLayer(layer))) { - const startPosition = this.startUser(layer, selectedLayer); - this.startX = startPosition.x + this.mapFile.tilewidth / 2; - this.startY = startPosition.y + this.mapFile.tileheight / 2; - } - } - - } private getExitUrl(layer: ITiledMapLayer): string | undefined { return this.getProperty(layer, "exitUrl") as string | undefined; @@ -1081,10 +1047,6 @@ ${escapedMessage} return this.getProperty(layer, "exitSceneUrl") as string | undefined; } - private isStartLayer(layer: ITiledMapLayer): boolean { - return this.getProperty(layer, "startLayer") == true; - } - private getScriptUrls(map: ITiledMap): string[] { return (this.getProperties(map, "script") as string[]).map((script) => (new URL(script, this.MapUrlFile)).toString()); } @@ -1116,38 +1078,6 @@ ${escapedMessage} return gameManager.loadMap(room, this.scene).catch(() => { }); } - private startUser(selectedOrDefaultLayer: ITiledMapTileLayer, startName: string | null): PositionInterface { - const tiles = selectedOrDefaultLayer.data; - if (typeof (tiles) === 'string') { - throw new Error('The content of a JSON map must be filled as a JSON array, not as a string'); - } - const possibleStartPositions: PositionInterface[] = []; - tiles.forEach((objectKey: number, key: number) => { - if (objectKey === 0) { - return; - } - const y = Math.floor(key / selectedOrDefaultLayer.width); - const x = key % selectedOrDefaultLayer.width; - - if (startName && this.gameMap.hasStartTile) { - const properties = this.gameMap.getPropertiesForIndex(objectKey); - if (!properties.length || !properties.some(property => property.name == "start" && property.value == startName)) { - return - } - } - possibleStartPositions.push({ x: x * this.mapFile.tilewidth, y: y * this.mapFile.tilewidth }); - }); - // Get a value at random amongst allowed values - if (possibleStartPositions.length === 0) { - console.warn('The start layer "' + selectedOrDefaultLayer.name + '" for this map is empty.'); - return { - x: 0, - y: 0 - }; - } - // Choose one of the available start positions at random amongst the list of available start positions. - return possibleStartPositions[Math.floor(Math.random() * possibleStartPositions.length)]; - } //todo: in a dedicated class/function? initCamera() { @@ -1184,8 +1114,8 @@ ${escapedMessage} try { this.CurrentPlayer = new Player( this, - this.startX, - this.startY, + this.startPositionCalculator.startX, + this.startPositionCalculator.startY, this.playerName, texturesPromise, PlayerAnimationDirections.Down, diff --git a/front/src/Phaser/Game/StartPositionCalculator.ts b/front/src/Phaser/Game/StartPositionCalculator.ts new file mode 100644 index 00000000..5dc454aa --- /dev/null +++ b/front/src/Phaser/Game/StartPositionCalculator.ts @@ -0,0 +1,117 @@ +import type { PositionInterface } from '../../Connexion/ConnexionModels'; +import type { ITiledMap, ITiledMapLayer, ITiledMapLayerProperty, ITiledMapTileLayer } from '../Map/ITiledMap'; +import type { GameMap } from './GameMap'; + + +const defaultStartLayerName = 'start'; + +export class StartPositionCalculator { + public startX!: number; + public startY!: number; + + + + constructor( + private readonly gameMap: GameMap, + private readonly mapFile: ITiledMap, + private readonly initPosition: PositionInterface | null, + private readonly startLayerName: string | null) { + this.initStartXAndStartY(); + } + private initStartXAndStartY() { + // If there is an init position passed + if (this.initPosition !== null) { + this.startX = this.initPosition.x; + this.startY = this.initPosition.y; + } else { + // Now, let's find the start layer + if (this.startLayerName) { + this.initPositionFromLayerName(this.startLayerName, this.startLayerName); + } + if (this.startX === undefined) { + // If we have no start layer specified or if the hash passed does not exist, let's go with the default start position. + this.initPositionFromLayerName(defaultStartLayerName, this.startLayerName); + } + } + // Still no start position? Something is wrong with the map, we need a "start" layer. + if (this.startX === undefined) { + console.warn('This map is missing a layer named "start" that contains the available default start positions.'); + // Let's start in the middle of the map + this.startX = this.mapFile.width * 16; + this.startY = this.mapFile.height * 16; + } + } + + /** + * + * @param selectedLayer this is always the layer that is selected with the hash in the url + * @param selectedOrDefaultLayer this can also be the {defaultStartLayerName} if the {selectedLayer} didnt yield any start points + */ + public initPositionFromLayerName(selectedOrDefaultLayer: string | null, selectedLayer: string | null) { + if (!selectedOrDefaultLayer) { + selectedOrDefaultLayer = defaultStartLayerName + } + for (const layer of this.gameMap.layersIterator) { + if ((selectedOrDefaultLayer === layer.name || layer.name.endsWith('/' + selectedOrDefaultLayer)) && layer.type === 'tilelayer' && (selectedOrDefaultLayer === defaultStartLayerName || this.isStartLayer(layer))) { + const startPosition = this.startUser(layer, selectedLayer); + this.startX = startPosition.x + this.mapFile.tilewidth / 2; + this.startY = startPosition.y + this.mapFile.tileheight / 2; + } + } + + } + + private isStartLayer(layer: ITiledMapLayer): boolean { + return this.getProperty(layer, "startLayer") == true; + } + + /** + * + * @param selectedLayer this is always the layer that is selected with the hash in the url + * @param selectedOrDefaultLayer this can also be the default layer if the {selectedLayer} didnt yield any start points + */ + private startUser(selectedOrDefaultLayer: ITiledMapTileLayer, selectedLayer: string | null): PositionInterface { + const tiles = selectedOrDefaultLayer.data; + if (typeof (tiles) === 'string') { + throw new Error('The content of a JSON map must be filled as a JSON array, not as a string'); + } + const possibleStartPositions: PositionInterface[] = []; + tiles.forEach((objectKey: number, key: number) => { + if (objectKey === 0) { + return; + } + const y = Math.floor(key / selectedOrDefaultLayer.width); + const x = key % selectedOrDefaultLayer.width; + + if (selectedLayer && this.gameMap.hasStartTile) { + const properties = this.gameMap.getPropertiesForIndex(objectKey); + if (!properties.length || !properties.some(property => property.name == "start" && property.value == selectedLayer)) { + return + } + } + possibleStartPositions.push({ x: x * this.mapFile.tilewidth, y: y * this.mapFile.tilewidth }); + }); + // Get a value at random amongst allowed values + if (possibleStartPositions.length === 0) { + console.warn('The start layer "' + selectedOrDefaultLayer.name + '" for this map is empty.'); + return { + x: 0, + y: 0 + }; + } + // Choose one of the available start positions at random amongst the list of available start positions. + return possibleStartPositions[Math.floor(Math.random() * possibleStartPositions.length)]; + } + + private getProperty(layer: ITiledMapLayer | ITiledMap, name: string): string | boolean | number | undefined { + const properties: ITiledMapLayerProperty[] | undefined = layer.properties; + if (!properties) { + return undefined; + } + const obj = properties.find((property: ITiledMapLayerProperty) => property.name.toLowerCase() === name.toLowerCase()); + if (obj === undefined) { + return undefined; + } + return obj.value; + } +} \ No newline at end of file From 769e0fcc297b4b7ef9f9c26293462f591336897a Mon Sep 17 00:00:00 2001 From: jonny Date: Fri, 25 Jun 2021 18:03:43 +0200 Subject: [PATCH 06/12] refactor to position object --- front/src/Phaser/Game/GameScene.ts | 11 ++++----- .../Phaser/Game/StartPositionCalculator.ts | 23 ++++++++++--------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 458e5f96..3b1d0b44 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -562,8 +562,7 @@ export class GameScene extends DirtyScene implements CenterListener { this.playerName, this.characterLayers, { - x: this.startPositionCalculator.startX, - y: this.startPositionCalculator.startY + ...this.startPositionCalculator.startPosition }, { left: camera.scrollX, @@ -970,8 +969,8 @@ ${escapedMessage} } else { //if the exit points to the current map, we simply teleport the user back to the startLayer this.startPositionCalculator.initPositionFromLayerName(hash, hash); - this.CurrentPlayer.x = this.startPositionCalculator.startX; - this.CurrentPlayer.y = this.startPositionCalculator.startY; + this.CurrentPlayer.x = this.startPositionCalculator.startPosition.x; + this.CurrentPlayer.y = this.startPositionCalculator.startPosition.y; setTimeout(() => this.mapTransitioning = false, 500); } } @@ -1114,8 +1113,8 @@ ${escapedMessage} try { this.CurrentPlayer = new Player( this, - this.startPositionCalculator.startX, - this.startPositionCalculator.startY, + this.startPositionCalculator.startPosition.x, + this.startPositionCalculator.startPosition.y, this.playerName, texturesPromise, PlayerAnimationDirections.Down, diff --git a/front/src/Phaser/Game/StartPositionCalculator.ts b/front/src/Phaser/Game/StartPositionCalculator.ts index 5dc454aa..aaad5415 100644 --- a/front/src/Phaser/Game/StartPositionCalculator.ts +++ b/front/src/Phaser/Game/StartPositionCalculator.ts @@ -6,10 +6,8 @@ import type { GameMap } from './GameMap'; const defaultStartLayerName = 'start'; export class StartPositionCalculator { - public startX!: number; - public startY!: number; - + public startPosition!: PositionInterface constructor( private readonly gameMap: GameMap, @@ -21,24 +19,25 @@ export class StartPositionCalculator { private initStartXAndStartY() { // If there is an init position passed if (this.initPosition !== null) { - this.startX = this.initPosition.x; - this.startY = this.initPosition.y; + this.startPosition = this.initPosition; } else { // Now, let's find the start layer if (this.startLayerName) { this.initPositionFromLayerName(this.startLayerName, this.startLayerName); } - if (this.startX === undefined) { + if (this.startPosition === undefined) { // If we have no start layer specified or if the hash passed does not exist, let's go with the default start position. this.initPositionFromLayerName(defaultStartLayerName, this.startLayerName); } } // Still no start position? Something is wrong with the map, we need a "start" layer. - if (this.startX === undefined) { + if (this.startPosition === undefined) { console.warn('This map is missing a layer named "start" that contains the available default start positions.'); // Let's start in the middle of the map - this.startX = this.mapFile.width * 16; - this.startY = this.mapFile.height * 16; + this.startPosition = { + x: this.mapFile.width * 16, + y: this.mapFile.height * 16 + }; } } @@ -54,8 +53,10 @@ export class StartPositionCalculator { for (const layer of this.gameMap.layersIterator) { if ((selectedOrDefaultLayer === layer.name || layer.name.endsWith('/' + selectedOrDefaultLayer)) && layer.type === 'tilelayer' && (selectedOrDefaultLayer === defaultStartLayerName || this.isStartLayer(layer))) { const startPosition = this.startUser(layer, selectedLayer); - this.startX = startPosition.x + this.mapFile.tilewidth / 2; - this.startY = startPosition.y + this.mapFile.tileheight / 2; + this.startPosition = { + x: startPosition.x + this.mapFile.tilewidth / 2, + y: startPosition.y + this.mapFile.tileheight / 2 + } } } From abfa010bbf59891a1d0d8997d9729d88e5e269ea Mon Sep 17 00:00:00 2001 From: jonny Date: Fri, 25 Jun 2021 18:07:03 +0200 Subject: [PATCH 07/12] added husky to gitignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 70660058..8fa69985 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,6 @@ docker-compose.override.yaml *.DS_Store maps/yarn.lock maps/dist/computer.js -maps/dist/computer.js.map \ No newline at end of file +maps/dist/computer.js.map +node_modules +_ \ No newline at end of file From bbdf0a12897429b5edd489b65a6c2d9d67bf253c Mon Sep 17 00:00:00 2001 From: jonny Date: Fri, 25 Jun 2021 18:20:16 +0200 Subject: [PATCH 08/12] fixed merge conflict --- front/src/Phaser/Game/GameScene.ts | 1 - front/src/Phaser/Game/StartPositionCalculator.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index a3b4efb1..06a288bc 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -79,7 +79,6 @@ import { TextUtils } from "../Components/TextUtils"; import { touchScreenManager } from "../../Touch/TouchScreenManager"; import { PinchManager } from "../UserInput/PinchManager"; import { joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey } from "../Components/MobileJoystick"; -import { DEPTH_OVERLAY_INDEX } from "./DepthIndexes"; import { waScaleManager } from "../Services/WaScaleManager"; import { EmoteManager } from "./EmoteManager"; diff --git a/front/src/Phaser/Game/StartPositionCalculator.ts b/front/src/Phaser/Game/StartPositionCalculator.ts index de321615..7460c81c 100644 --- a/front/src/Phaser/Game/StartPositionCalculator.ts +++ b/front/src/Phaser/Game/StartPositionCalculator.ts @@ -51,7 +51,7 @@ export class StartPositionCalculator { if (!selectedOrDefaultLayer) { selectedOrDefaultLayer = defaultStartLayerName; } - for (const layer of this.gameMap.layersIterator) { + for (const layer of this.gameMap.flatLayers) { if ( (selectedOrDefaultLayer === layer.name || layer.name.endsWith("/" + selectedOrDefaultLayer)) && layer.type === "tilelayer" && From b0eb241fc32a1d2dacc461ca8a37f2cf3e15ed02 Mon Sep 17 00:00:00 2001 From: jonny Date: Fri, 25 Jun 2021 18:45:15 +0200 Subject: [PATCH 09/12] oO something kept movin the comment to the next line --- front/src/Connexion/RoomConnection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index ce1956f1..1b080a55 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -67,8 +67,8 @@ export class RoomConnection implements RoomConnection { private closed: boolean = false; private tags: string[] = []; + // eslint-disable-next-line @typescript-eslint/no-explicit-any public static setWebsocketFactory(websocketFactory: (url: string) => any): void { - // eslint-disable-line @typescript-eslint/no-explicit-any RoomConnection.websocketFactory = websocketFactory; } From f18291e9d2022a566a3d62e5e0ace142a070ef03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 28 Jun 2021 10:06:56 +0200 Subject: [PATCH 10/12] Referencing test in index.html and adding some text in the test map. --- maps/tests/index.html | 16 ++++ maps/tests/start-tile.json | 192 +++++++++++++++++++------------------ 2 files changed, 115 insertions(+), 93 deletions(-) diff --git a/maps/tests/index.html b/maps/tests/index.html index 0929ab83..7142060a 100644 --- a/maps/tests/index.html +++ b/maps/tests/index.html @@ -162,6 +162,22 @@ Test animated tiles + + + Success Failure Pending + + + Test start tile (S1) + + + + + Success Failure Pending + + + Test start tile (S2) + +