From b20b4abb9e8ff357ec3f4d50874cac7814d094a8 Mon Sep 17 00:00:00 2001 From: jonny Date: Wed, 23 Jun 2021 12:32:36 +0200 Subject: [PATCH 01/48] 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/48] 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/48] 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/48] 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/48] 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/48] 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/48] 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/48] 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/48] 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/48] 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) + + + + + + + + + \ No newline at end of file diff --git a/front/src/Components/Chat/ChatElement.svelte b/front/src/Components/Chat/ChatElement.svelte new file mode 100644 index 00000000..95a050eb --- /dev/null +++ b/front/src/Components/Chat/ChatElement.svelte @@ -0,0 +1,74 @@ + + +
+
+ {#if message.type === ChatMessageTypes.userIncoming} + ➡️: {#each targets as target}{/each} ({renderDate(message.date)}) + {:else if message.type === ChatMessageTypes.userOutcoming} + ⬅️: {#each targets as target}{/each} ({renderDate(message.date)}) + {:else if message.type === ChatMessageTypes.me} +

Me: ({renderDate(message.date)})

+ {#each texts as text} +

{@html urlifyText(text)}

+ {/each} + {:else} +

: ({renderDate(message.date)})

+ {#each texts as text} +

{@html urlifyText(text)}

+ {/each} + {/if} +
+
+ + \ No newline at end of file diff --git a/front/src/Components/Chat/ChatMessageForm.svelte b/front/src/Components/Chat/ChatMessageForm.svelte new file mode 100644 index 00000000..acfd68ae --- /dev/null +++ b/front/src/Components/Chat/ChatMessageForm.svelte @@ -0,0 +1,55 @@ + + +
+ + +
+ + \ No newline at end of file diff --git a/front/src/Components/Chat/ChatPlayerName.svelte b/front/src/Components/Chat/ChatPlayerName.svelte new file mode 100644 index 00000000..f0fbe8cd --- /dev/null +++ b/front/src/Components/Chat/ChatPlayerName.svelte @@ -0,0 +1,37 @@ + + + showMenu = !showMenu}> + {player.name} + + +{#if showMenu} +
    +
  • +
+{/if} + + + \ No newline at end of file diff --git a/front/src/Phaser/Components/OpenChatIcon.ts b/front/src/Phaser/Components/OpenChatIcon.ts index ab07a80c..8c648bc1 100644 --- a/front/src/Phaser/Components/OpenChatIcon.ts +++ b/front/src/Phaser/Components/OpenChatIcon.ts @@ -1,7 +1,7 @@ -import {discussionManager} from "../../WebRtc/DiscussionManager"; -import {DEPTH_INGAME_TEXT_INDEX} from "../Game/DepthIndexes"; +import { DEPTH_INGAME_TEXT_INDEX } from "../Game/DepthIndexes"; +import { chatVisibilityStore } from "../../Stores/ChatStore"; -export const openChatIconName = 'openChatIcon'; +export const openChatIconName = "openChatIcon"; export class OpenChatIcon extends Phaser.GameObjects.Image { constructor(scene: Phaser.Scene, x: number, y: number) { super(scene, x, y, openChatIconName, 3); @@ -9,9 +9,9 @@ export class OpenChatIcon extends Phaser.GameObjects.Image { this.setScrollFactor(0, 0); this.setOrigin(0, 1); this.setInteractive(); - this.setVisible(false); + //this.setVisible(false); this.setDepth(DEPTH_INGAME_TEXT_INDEX); - this.on("pointerup", () => discussionManager.showDiscussionPart()); + this.on("pointerup", () => chatVisibilityStore.set(true)); } -} \ No newline at end of file +} diff --git a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts index d2a659ec..3c47c9d9 100644 --- a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts +++ b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts @@ -101,7 +101,6 @@ export const createLoadingPromise = ( frameConfig: FrameConfig ) => { return new Promise((res, rej) => { - console.log("count", loadPlugin.listenerCount("loaderror")); if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) { return res(playerResourceDescriptor); } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index d6df242f..3ccc9fd9 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -692,12 +692,12 @@ export class GameScene extends DirtyScene { const self = this; this.simplePeer.registerPeerConnectionListener({ onConnect(peer) { - self.openChatIcon.setVisible(true); + //self.openChatIcon.setVisible(true); audioManager.decreaseVolume(); }, onDisconnect(userId: number) { if (self.simplePeer.getNbConnections() === 0) { - self.openChatIcon.setVisible(false); + //self.openChatIcon.setVisible(false); audioManager.restoreVolume(); } }, diff --git a/front/src/Phaser/Game/PlayerInterface.ts b/front/src/Phaser/Game/PlayerInterface.ts index 5a81c89a..6ab439df 100644 --- a/front/src/Phaser/Game/PlayerInterface.ts +++ b/front/src/Phaser/Game/PlayerInterface.ts @@ -7,4 +7,5 @@ export interface PlayerInterface { visitCardUrl: string | null; companion: string | null; userUuid: string; + color?: string; } diff --git a/front/src/Stores/ChatStore.ts b/front/src/Stores/ChatStore.ts new file mode 100644 index 00000000..344a424e --- /dev/null +++ b/front/src/Stores/ChatStore.ts @@ -0,0 +1,102 @@ +import { writable } from "svelte/store"; +import { playersStore } from "./PlayersStore"; +import type { PlayerInterface } from "../Phaser/Game/PlayerInterface"; + +export const chatVisibilityStore = writable(false); +export const chatInputFocusStore = writable(false); + +export const newChatMessageStore = writable(null); + +export enum ChatMessageTypes { + text = 1, + me, + userIncoming, + userOutcoming, +} + +export interface ChatMessage { + type: ChatMessageTypes; + date: Date; + author?: PlayerInterface; + targets?: PlayerInterface[]; + text?: string[]; +} + +function getAuthor(authorId: number): PlayerInterface { + const author = playersStore.getPlayerById(authorId); + if (!author) { + throw "Could not find data for author " + authorId; + } + return author; +} + +function createChatMessagesStore() { + const { subscribe, update } = writable([]); + + return { + subscribe, + addIncomingUser(authorId: number) { + update((list) => { + const lastMessage = list[list.length - 1]; + if (lastMessage && lastMessage.type === ChatMessageTypes.userIncoming && lastMessage.targets) { + lastMessage.targets.push(getAuthor(authorId)); + } else { + list.push({ + type: ChatMessageTypes.userIncoming, + targets: [getAuthor(authorId)], + date: new Date(), + }); + } + return list; + }); + }, + addOutcomingUser(authorId: number) { + update((list) => { + const lastMessage = list[list.length - 1]; + if (lastMessage && lastMessage.type === ChatMessageTypes.userOutcoming && lastMessage.targets) { + lastMessage.targets.push(getAuthor(authorId)); + } else { + list.push({ + type: ChatMessageTypes.userOutcoming, + targets: [getAuthor(authorId)], + date: new Date(), + }); + } + return list; + }); + }, + addPersonnalMessage(text: string) { + newChatMessageStore.set(text); + update((list) => { + const lastMessage = list[list.length - 1]; + if (lastMessage && lastMessage.type === ChatMessageTypes.me && lastMessage.text) { + lastMessage.text.push(text); + } else { + list.push({ + type: ChatMessageTypes.me, + text: [text], + date: new Date(), + }); + } + return list; + }); + }, + addExternalMessage(authorId: number, text: string) { + update((list) => { + const lastMessage = list[list.length - 1]; + if (lastMessage && lastMessage.type === ChatMessageTypes.text && lastMessage.text) { + lastMessage.text.push(text); + } else { + list.push({ + type: ChatMessageTypes.text, + text: [text], + author: getAuthor(authorId), + date: new Date(), + }); + } + return list; + }); + }, + }; +} +export const chatMessagesStore = createChatMessagesStore(); diff --git a/front/src/Stores/PlayersStore.ts b/front/src/Stores/PlayersStore.ts index 6c21de7a..2ea988bb 100644 --- a/front/src/Stores/PlayersStore.ts +++ b/front/src/Stores/PlayersStore.ts @@ -1,6 +1,7 @@ import { writable } from "svelte/store"; import type { PlayerInterface } from "../Phaser/Game/PlayerInterface"; import type { RoomConnection } from "../Connexion/RoomConnection"; +import { getRandomColor } from "../WebRtc/ColorGenerator"; /** * A store that contains the list of players currently known. @@ -24,6 +25,7 @@ function createPlayersStore() { visitCardUrl: message.visitCardUrl, companion: message.companion, userUuid: message.userUuid, + color: getRandomColor(), }); return users; }); diff --git a/front/src/Stores/UserInputStore.ts b/front/src/Stores/UserInputStore.ts index cbb7f0c3..993d8795 100644 --- a/front/src/Stores/UserInputStore.ts +++ b/front/src/Stores/UserInputStore.ts @@ -1,10 +1,11 @@ -import {derived} from "svelte/store"; -import {consoleGlobalMessageManagerFocusStore} from "./ConsoleGlobalMessageManagerStore"; +import { derived } from "svelte/store"; +import { consoleGlobalMessageManagerFocusStore } from "./ConsoleGlobalMessageManagerStore"; +import { chatInputFocusStore } from "./ChatStore"; //derived from the focus on Menu, ConsoleGlobal, Chat and ... export const enableUserInputsStore = derived( - consoleGlobalMessageManagerFocusStore, - ($consoleGlobalMessageManagerFocusStore) => { - return !$consoleGlobalMessageManagerFocusStore; + [consoleGlobalMessageManagerFocusStore, chatInputFocusStore], + ([$consoleGlobalMessageManagerFocusStore, $chatInputFocusStore]) => { + return !$consoleGlobalMessageManagerFocusStore && !$chatInputFocusStore; } -); \ No newline at end of file +); diff --git a/front/src/WebRtc/ColorGenerator.ts b/front/src/WebRtc/ColorGenerator.ts new file mode 100644 index 00000000..a42aee85 --- /dev/null +++ b/front/src/WebRtc/ColorGenerator.ts @@ -0,0 +1,48 @@ +export function getRandomColor(): string { + return hsv_to_rgb(Math.random(), 0.5, 0.95); +} + +//todo: test this. +function hsv_to_rgb(hue: number, saturation: number, brightness: number): string { + const h_i = Math.floor(hue * 6); + const f = hue * 6 - h_i; + const p = brightness * (1 - saturation); + const q = brightness * (1 - f * saturation); + const t = brightness * (1 - (1 - f) * saturation); + let r: number, g: number, b: number; + switch (h_i) { + case 0: + r = brightness; + g = t; + b = p; + break; + case 1: + r = q; + g = brightness; + b = p; + break; + case 2: + r = p; + g = brightness; + b = t; + break; + case 3: + r = p; + g = q; + b = brightness; + break; + case 4: + r = t; + g = p; + b = brightness; + break; + case 5: + r = brightness; + g = p; + b = q; + break; + default: + throw "h_i cannot be " + h_i; + } + return "#" + Math.floor(r * 256).toString(16) + Math.floor(g * 256).toString(16) + Math.floor(b * 256).toString(16); +} diff --git a/front/src/WebRtc/DiscussionManager.ts b/front/src/WebRtc/DiscussionManager.ts index ae351f76..a3c928f4 100644 --- a/front/src/WebRtc/DiscussionManager.ts +++ b/front/src/WebRtc/DiscussionManager.ts @@ -1,232 +1,12 @@ -import { HtmlUtils } from "./HtmlUtils"; -import type { UserInputManager } from "../Phaser/UserInput/UserInputManager"; -import { connectionManager } from "../Connexion/ConnectionManager"; -import { GameConnexionTypes } from "../Url/UrlManager"; import { iframeListener } from "../Api/IframeListener"; -import { showReportScreenStore } from "../Stores/ShowReportScreenStore"; - -export type SendMessageCallback = (message: string) => void; +import { chatMessagesStore, chatVisibilityStore } from "../Stores/ChatStore"; export class DiscussionManager { - private mainContainer: HTMLDivElement; - - private divDiscuss?: HTMLDivElement; - private divParticipants?: HTMLDivElement; - private nbpParticipants?: HTMLParagraphElement; - private divMessages?: HTMLParagraphElement; - - private participants: Map = new Map(); - - private activeDiscussion: boolean = false; - - private sendMessageCallBack: Map = new Map< - number | string, - SendMessageCallback - >(); - - private userInputManager?: UserInputManager; - constructor() { - this.mainContainer = HtmlUtils.getElementByIdOrFail("main-container"); - this.createDiscussPart(""); //todo: why do we always use empty string? - iframeListener.chatStream.subscribe((chatEvent) => { - this.addMessage(chatEvent.author, chatEvent.message, false); - this.showDiscussion(); + chatMessagesStore.addExternalMessage(parseInt(chatEvent.author), chatEvent.message); + chatVisibilityStore.set(true); }); - this.onSendMessageCallback("iframe_listener", (message) => { - iframeListener.sendUserInputChat(message); - }); - } - - private createDiscussPart(name: string) { - this.divDiscuss = document.createElement("div"); - this.divDiscuss.classList.add("discussion"); - - const buttonCloseDiscussion: HTMLButtonElement = document.createElement("button"); - buttonCloseDiscussion.classList.add("close-btn"); - buttonCloseDiscussion.innerHTML = ``; - buttonCloseDiscussion.addEventListener("click", () => { - this.hideDiscussion(); - }); - this.divDiscuss.appendChild(buttonCloseDiscussion); - - const myName: HTMLParagraphElement = document.createElement("p"); - myName.innerText = name.toUpperCase(); - this.nbpParticipants = document.createElement("p"); - this.nbpParticipants.innerText = "PARTICIPANTS (1)"; - - this.divParticipants = document.createElement("div"); - this.divParticipants.classList.add("participants"); - - this.divMessages = document.createElement("div"); - this.divMessages.classList.add("messages"); - this.divMessages.innerHTML = "

Local messages

"; - - this.divDiscuss.appendChild(myName); - this.divDiscuss.appendChild(this.nbpParticipants); - this.divDiscuss.appendChild(this.divParticipants); - this.divDiscuss.appendChild(this.divMessages); - - const sendDivMessage: HTMLDivElement = document.createElement("div"); - sendDivMessage.classList.add("send-message"); - const inputMessage: HTMLInputElement = document.createElement("input"); - inputMessage.onfocus = () => { - if (this.userInputManager) { - this.userInputManager.disableControls(); - } - }; - inputMessage.onblur = () => { - if (this.userInputManager) { - this.userInputManager.restoreControls(); - } - }; - inputMessage.type = "text"; - inputMessage.addEventListener("keyup", (event: KeyboardEvent) => { - if (event.key === "Enter") { - event.preventDefault(); - if (inputMessage.value === null || inputMessage.value === "" || inputMessage.value === undefined) { - return; - } - this.addMessage(name, inputMessage.value, true); - for (const callback of this.sendMessageCallBack.values()) { - callback(inputMessage.value); - } - inputMessage.value = ""; - } - }); - sendDivMessage.appendChild(inputMessage); - this.divDiscuss.appendChild(sendDivMessage); - - //append in main container - this.mainContainer.appendChild(this.divDiscuss); - - this.addParticipant("me", "Moi", undefined, true); - } - - public addParticipant( - userId: number | "me", - name: string | undefined, - img?: string | undefined, - isMe: boolean = false - ) { - const divParticipant: HTMLDivElement = document.createElement("div"); - divParticipant.classList.add("participant"); - divParticipant.id = `participant-${userId}`; - - const divImgParticipant: HTMLImageElement = document.createElement("img"); - divImgParticipant.src = "resources/logos/boy.svg"; - if (img !== undefined) { - divImgParticipant.src = img; - } - const divPParticipant: HTMLParagraphElement = document.createElement("p"); - if (!name) { - name = "Anonymous"; - } - divPParticipant.innerText = name; - - divParticipant.appendChild(divImgParticipant); - divParticipant.appendChild(divPParticipant); - - if ( - !isMe && - connectionManager.getConnexionType && - connectionManager.getConnexionType !== GameConnexionTypes.anonymous && - userId !== "me" - ) { - const reportBanUserAction: HTMLButtonElement = document.createElement("button"); - reportBanUserAction.classList.add("report-btn"); - reportBanUserAction.innerText = "Report"; - reportBanUserAction.addEventListener("click", () => { - showReportScreenStore.set({ userId: userId, userName: name ? name : "" }); - }); - divParticipant.appendChild(reportBanUserAction); - } - - this.divParticipants?.appendChild(divParticipant); - - this.participants.set(userId, divParticipant); - - this.updateParticipant(this.participants.size); - } - - public updateParticipant(nb: number) { - if (!this.nbpParticipants) { - return; - } - this.nbpParticipants.innerText = `PARTICIPANTS (${nb})`; - } - - public addMessage(name: string, message: string, isMe: boolean = false) { - const divMessage: HTMLDivElement = document.createElement("div"); - divMessage.classList.add("message"); - if (isMe) { - divMessage.classList.add("me"); - } - - const pMessage: HTMLParagraphElement = document.createElement("p"); - const date = new Date(); - if (isMe) { - name = "Me"; - } else { - name = HtmlUtils.escapeHtml(name); - } - pMessage.innerHTML = `${name} - - ${date.getHours()}:${date.getMinutes()} - `; - divMessage.appendChild(pMessage); - - const userMessage: HTMLParagraphElement = document.createElement("p"); - userMessage.innerHTML = HtmlUtils.urlify(message); - userMessage.classList.add("body"); - divMessage.appendChild(userMessage); - this.divMessages?.appendChild(divMessage); - - //automatic scroll when there are new message - setTimeout(() => { - this.divMessages?.scroll({ - top: this.divMessages?.scrollTop + divMessage.getBoundingClientRect().y, - behavior: "smooth", - }); - }, 200); - } - - public removeParticipant(userId: number | string) { - const element = this.participants.get(userId); - if (element) { - element.remove(); - this.participants.delete(userId); - } - //if all participant leave, hide discussion button - - this.sendMessageCallBack.delete(userId); - } - - public onSendMessageCallback(userId: string | number, callback: SendMessageCallback): void { - this.sendMessageCallBack.set(userId, callback); - } - - get activatedDiscussion() { - return this.activeDiscussion; - } - - private showDiscussion() { - this.activeDiscussion = true; - this.divDiscuss?.classList.add("active"); - } - - private hideDiscussion() { - this.activeDiscussion = false; - this.divDiscuss?.classList.remove("active"); - } - - public setUserInputManager(userInputManager: UserInputManager) { - this.userInputManager = userInputManager; - } - - public showDiscussionPart() { - this.showDiscussion(); } } diff --git a/front/src/WebRtc/MediaManager.ts b/front/src/WebRtc/MediaManager.ts index d9847f44..126bf1a8 100644 --- a/front/src/WebRtc/MediaManager.ts +++ b/front/src/WebRtc/MediaManager.ts @@ -1,16 +1,11 @@ -import { DivImportance, layoutManager } from "./LayoutManager"; +import { layoutManager } from "./LayoutManager"; import { HtmlUtils } from "./HtmlUtils"; -import { discussionManager, SendMessageCallback } from "./DiscussionManager"; import type { UserInputManager } from "../Phaser/UserInput/UserInputManager"; -import { localUserStore } from "../Connexion/LocalUserStore"; -import type { UserSimplePeerInterface } from "./SimplePeer"; -import { SoundMeter } from "../Phaser/Components/SoundMeter"; import { DISABLE_NOTIFICATIONS } from "../Enum/EnvironmentVariable"; import { localStreamStore } from "../Stores/MediaStore"; import { screenSharingLocalStreamStore } from "../Stores/ScreenSharingStore"; import { helpCameraSettingsVisibleStore } from "../Stores/HelpCameraSettingsStore"; -export type UpdatedLocalStreamCallback = (media: MediaStream | null) => void; export type StartScreenSharingCallback = (media: MediaStream) => void; export type StopScreenSharingCallback = (media: MediaStream) => void; @@ -182,22 +177,8 @@ export class MediaManager { } } - public addNewMessage(name: string, message: string, isMe: boolean = false) { - discussionManager.addMessage(name, message, isMe); - - //when there are new message, show discussion - if (!discussionManager.activatedDiscussion) { - discussionManager.showDiscussionPart(); - } - } - - public addSendMessageCallback(userId: string | number, callback: SendMessageCallback) { - discussionManager.onSendMessageCallback(userId, callback); - } - public setUserInputManager(userInputManager: UserInputManager) { this.userInputManager = userInputManager; - discussionManager.setUserInputManager(userInputManager); } public getNotification() { diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 5045a5a3..e30f1b1f 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -12,6 +12,7 @@ import { localStreamStore, LocalStreamStoreValue, obtainedMediaConstraintStore } import { screenSharingLocalStreamStore } from "../Stores/ScreenSharingStore"; import { discussionManager } from "./DiscussionManager"; import { playersStore } from "../Stores/PlayersStore"; +import { newChatMessageStore } from "../Stores/ChatStore"; export interface UserSimplePeerInterface { userId: number; @@ -155,27 +156,11 @@ export class SimplePeer { const name = this.getName(user.userId); - discussionManager.removeParticipant(user.userId); - this.lastWebrtcUserName = user.webRtcUser; this.lastWebrtcPassword = user.webRtcPassword; const peer = new VideoPeer(user, user.initiator ? user.initiator : false, name, this.Connection, localStream); - //permit to send message - mediaManager.addSendMessageCallback(user.userId, (message: string) => { - peer.write( - new Buffer( - JSON.stringify({ - type: MESSAGE_TYPE_MESSAGE, - name: this.myName.toUpperCase(), - userId: this.userId, - message: message, - }) - ) - ); - }); - peer.toClose = false; // When a connection is established to a video stream, and if a screen sharing is taking place, // the user sharing screen should also initiate a connection to the remote user! diff --git a/front/src/WebRtc/VideoPeer.ts b/front/src/WebRtc/VideoPeer.ts index bde0bcde..45118b5f 100644 --- a/front/src/WebRtc/VideoPeer.ts +++ b/front/src/WebRtc/VideoPeer.ts @@ -5,10 +5,11 @@ import type { RoomConnection } from "../Connexion/RoomConnection"; import { blackListManager } from "./BlackListManager"; import type { Subscription } from "rxjs"; import type { UserSimplePeerInterface } from "./SimplePeer"; -import { get, readable, Readable } from "svelte/store"; +import { get, readable, Readable, Unsubscriber } from "svelte/store"; import { obtainedMediaConstraintStore } from "../Stores/MediaStore"; import { discussionManager } from "./DiscussionManager"; import { playersStore } from "../Stores/PlayersStore"; +import { chatMessagesStore, chatVisibilityStore, newChatMessageStore } from "../Stores/ChatStore"; const Peer: SimplePeerNamespace.SimplePeer = require("simple-peer"); @@ -34,6 +35,7 @@ export class VideoPeer extends Peer { public readonly streamStore: Readable; public readonly statusStore: Readable; public readonly constraintsStore: Readable; + private newMessageunsubscriber: Unsubscriber | null = null; constructor( public user: UserSimplePeerInterface, @@ -147,6 +149,20 @@ export class VideoPeer extends Peer { this.on("connect", () => { this._connected = true; + chatMessagesStore.addIncomingUser(this.userId); + + this.newMessageunsubscriber = newChatMessageStore.subscribe((newMessage) => { + if (!newMessage) return; + this.write( + new Buffer( + JSON.stringify({ + type: MESSAGE_TYPE_MESSAGE, + message: newMessage, + }) + ) + ); //send more data + newChatMessageStore.set(null); //This is to prevent a newly created SimplePeer to send an old message a 2nd time. Is there a better way? + }); }); this.on("data", (chunk: Buffer) => { @@ -164,8 +180,9 @@ export class VideoPeer extends Peer { mediaManager.disabledVideoByUserId(this.userId); } } else if (message.type === MESSAGE_TYPE_MESSAGE) { - if (!blackListManager.isBlackListed(message.userId)) { - mediaManager.addNewMessage(message.name, message.message); + if (!blackListManager.isBlackListed(this.userUuid)) { + chatMessagesStore.addExternalMessage(this.userId, message.message); + chatVisibilityStore.set(true); } } else if (message.type === MESSAGE_TYPE_BLOCKED) { //FIXME when A blacklists B, the output stream from A is muted in B's js client. This is insecure since B can manipulate the code to unmute A stream. @@ -253,7 +270,9 @@ export class VideoPeer extends Peer { } this.onBlockSubscribe.unsubscribe(); this.onUnBlockSubscribe.unsubscribe(); - discussionManager.removeParticipant(this.userId); + if (this.newMessageunsubscriber) this.newMessageunsubscriber(); + chatMessagesStore.addOutcomingUser(this.userId); + //discussionManager.removeParticipant(this.userId); // FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray" // I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel. super.destroy(error); diff --git a/front/style/fonts.scss b/front/style/fonts.scss index a49d3967..526f6615 100644 --- a/front/style/fonts.scss +++ b/front/style/fonts.scss @@ -1,9 +1,5 @@ @import "~@fontsource/press-start-2p/index.css"; -*{ - font-family: PixelFont-7,monospace; -} - .nes-btn { font-family: "Press Start 2P"; } diff --git a/front/webpack.config.ts b/front/webpack.config.ts index b6efb389..37362baf 100644 --- a/front/webpack.config.ts +++ b/front/webpack.config.ts @@ -7,7 +7,6 @@ import MiniCssExtractPlugin from "mini-css-extract-plugin"; import sveltePreprocess from "svelte-preprocess"; import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin"; import NodePolyfillPlugin from "node-polyfill-webpack-plugin"; -import { DISPLAY_TERMS_OF_USE } from "./src/Enum/EnvironmentVariable"; const mode = process.env.NODE_ENV ?? "development"; const buildNpmTypingsForApi = !!process.env.BUILD_TYPINGS; From b9a2433283766e2ff61a1f753d956a41abf37b86 Mon Sep 17 00:00:00 2001 From: GRL Date: Mon, 12 Jul 2021 11:59:05 +0200 Subject: [PATCH 26/48] Upgrade graphic of the chat --- front/src/Components/Chat/Chat.svelte | 12 ++++++++++-- front/src/Components/Chat/ChatElement.svelte | 4 ++-- front/src/Components/Chat/ChatMessageForm.svelte | 2 ++ front/src/WebRtc/ColorGenerator.ts | 6 +++++- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/front/src/Components/Chat/Chat.svelte b/front/src/Components/Chat/Chat.svelte index c4f9075d..093d01a5 100644 --- a/front/src/Components/Chat/Chat.svelte +++ b/front/src/Components/Chat/Chat.svelte @@ -32,7 +32,7 @@