diff --git a/docs/maps/entry-exit.md b/docs/maps/entry-exit.md index 6f98af93..2040beb3 100644 --- a/docs/maps/entry-exit.md +++ b/docs/maps/entry-exit.md @@ -65,3 +65,24 @@ How to use entry point : * To enter via this entry point, simply add a hash with the entry point name to the URL ("#[_entryPointName_]"). For instance: "`https://workadventu.re/_/global/mymap.com/path/map.json#my-entry-point`". * You can of course use the "#" notation in an exit scene URL (so an exit scene URL will point to a given entry scene URL) + +## Defining destination point with moveTo parameter + +We are able to direct a Woka to the desired place immediately after spawn. To make users spawn on an entry point and then, walk automatically to a meeting room, simply add `moveTo` as an additional parameter of URL: + +*Use default entry point* +``` +.../my_map.json#&moveTo=exit +``` +*Define entry point and moveTo parameter like this...* +``` +.../my_map.json#start&moveTo=meeting-room +``` +*...or like this* +``` +.../my_map.json#moveTo=meeting-room&start +``` + +For this to work, moveTo must be equal to the layer name of interest. This layer should have at least one tile defined. In case of layer having many tiles, user will go to one of them, randomly selected. + +![](images/moveTo-layer-example.png) \ No newline at end of file diff --git a/docs/maps/images/moveTo-layer-example.png b/docs/maps/images/moveTo-layer-example.png new file mode 100644 index 00000000..12e8a4ad Binary files /dev/null and b/docs/maps/images/moveTo-layer-example.png differ diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index fc16110f..54f91866 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -1,4 +1,10 @@ -import type { ITiledMap, ITiledMapLayer, ITiledMapObject, ITiledMapProperty } from "../Map/ITiledMap"; +import type { + ITiledMap, + ITiledMapLayer, + ITiledMapObject, + ITiledMapProperty, + ITiledMapTileLayer, +} from "../Map/ITiledMap"; import { flattenGroupLayersMap } from "../Map/LayersFlattener"; import TilemapLayer = Phaser.Tilemaps.TilemapLayer; import { DEPTH_OVERLAY_INDEX } from "./DepthIndexes"; @@ -291,6 +297,31 @@ export class GameMap { } } + public getRandomPositionFromLayer(layerName: string): { x: number; y: number } { + const layer = this.findLayer(layerName) as ITiledMapTileLayer; + if (!layer) { + throw new Error(`No layer "${layerName}" was found`); + } + const tiles = layer.data; + if (!tiles) { + throw new Error(`No tiles in "${layerName}" were found`); + } + 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 possiblePositions: { x: number; y: number }[] = []; + tiles.forEach((objectKey: number, key: number) => { + if (objectKey === 0) { + return; + } + possiblePositions.push({ x: key % layer.width, y: Math.floor(key / layer.width) }); + }); + if (possiblePositions.length > 0) { + return MathUtils.randomFromArray(possiblePositions); + } + throw new Error("No possible position found"); + } + private getLayersByKey(key: number): Array { return this.flatLayers.filter((flatLayer) => flatLayer.type === "tilelayer" && flatLayer.data[key] !== 0); } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 0c69d19a..7706fa72 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -560,6 +560,12 @@ export class GameScene extends DirtyScene { .catch((e) => console.error(e)); } + this.pathfindingManager = new PathfindingManager( + this, + this.gameMap.getCollisionsGrid(), + this.gameMap.getTileDimensions() + ); + //notify game manager can to create currentUser in map this.createCurrentPlayer(); this.removeAllRemotePlayers(); //cleanup the list of remote players in case the scene was rebooted @@ -570,11 +576,6 @@ export class GameScene extends DirtyScene { waScaleManager ); - this.pathfindingManager = new PathfindingManager( - this, - this.gameMap.getCollisionsGrid(), - this.gameMap.getTileDimensions() - ); biggestAvailableAreaStore.recompute(); this.cameraManager.startFollowPlayer(this.CurrentPlayer); @@ -1726,6 +1727,22 @@ ${escapedMessage} this.connection?.emitEmoteEvent(emoteKey); analyticsClient.launchEmote(emoteKey); }); + const moveToParam = urlManager.getHashParameter("moveTo"); + if (moveToParam) { + try { + const endPos = this.gameMap.getRandomPositionFromLayer(moveToParam); + this.pathfindingManager + .findPath(this.gameMap.getTileIndexAt(this.CurrentPlayer.x, this.CurrentPlayer.y), endPos) + .then((path) => { + if (path && path.length > 0) { + this.CurrentPlayer.setPathToFollow(path).catch((reason) => console.warn(reason)); + } + }) + .catch((reason) => console.warn(reason)); + } catch (err) { + console.warn(`Cannot proceed with moveTo command:\n\t-> ${err}`); + } + } } catch (err) { if (err instanceof TextureError) { gameManager.leaveGame(SelectCharacterSceneName, new SelectCharacterScene()); diff --git a/front/src/Url/UrlManager.ts b/front/src/Url/UrlManager.ts index 011efa5a..cb0e1ed0 100644 --- a/front/src/Url/UrlManager.ts +++ b/front/src/Url/UrlManager.ts @@ -45,8 +45,28 @@ class UrlManager { } public getStartLayerNameFromUrl(): string | null { - const hash = window.location.hash; - return hash.length > 1 ? hash.substring(1) : null; + const parameters = this.getHashParameters(); + for (const key in parameters) { + if (parameters[key] === undefined) { + return key; + } + } + return null; + } + + public getHashParameter(name: string): string | undefined { + return this.getHashParameters()[name]; + } + + private getHashParameters(): Record { + return window.location.hash + .substring(1) + .split("&") + .reduce((res: Record, item: string) => { + const parts = item.split("="); + res[parts[0]] = parts[1]; + return res; + }, {}); } pushStartLayerNameToUrl(startLayerName: string): void { diff --git a/front/src/Utils/MathUtils.ts b/front/src/Utils/MathUtils.ts index c2fc88a2..fc055d11 100644 --- a/front/src/Utils/MathUtils.ts +++ b/front/src/Utils/MathUtils.ts @@ -31,4 +31,8 @@ export class MathUtils { const distance = Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2); return squared ? Math.sqrt(distance) : distance; } + + public static randomFromArray(array: T[]): T { + return array[Math.floor(Math.random() * array.length)]; + } } diff --git a/maps/tests/index.html b/maps/tests/index.html index 4af51be5..d27d7804 100644 --- a/maps/tests/index.html +++ b/maps/tests/index.html @@ -56,6 +56,14 @@ Test start tile (S2) + + + Success Failure Pending + + + Test moveTo parameter + + Success Failure Pending diff --git a/maps/tests/move_to.json b/maps/tests/move_to.json new file mode 100644 index 00000000..796a9d46 --- /dev/null +++ b/maps/tests/move_to.json @@ -0,0 +1,275 @@ +{ "compressionlevel":-1, + "height":10, + "infinite":false, + "layers":[ + { + "data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + "height":10, + "id":1, + "name":"floor", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":2, + "name":"start", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17], + "height":10, + "id":7, + "name":"walls", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 0, 0, 0, 0, 0, 0, 34, 34, 34, 34, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 0, 0, 0, 0, 0, 0, 0, 0, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":8, + "name":"meeting-room", + "opacity":1, + "properties":[ + { + "name":"startLayer", + "type":"bool", + "value":true + }], + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 12, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":10, + "name":"start2", + "opacity":1, + "properties":[ + { + "name":"startLayer", + "type":"bool", + "value":true + }], + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":3, + "name":"floorLayer", + "objects":[ + { + "height":33.4788210765457, + "id":1, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":13, + "text":"Add \"moveTo\" parameter to the URL to make character move at game start.", + "wrap":true + }, + "type":"", + "visible":true, + "width":249.954975648686, + "x":35.2740564642832, + "y":34.4372323693377 + }, + { + "height":19.6921, + "id":3, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":13, + "text":"...#start2&moveTo=meeting-room", + "wrap":true + }, + "type":"", + "visible":true, + "width":223.499265952492, + "x":32, + "y":114 + }, + { + "height":19.6921, + "id":4, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":13, + "text":"start", + "wrap":true + }, + "type":"", + "visible":true, + "width":26.7596292501164, + "x":3.30880298090358, + "y":135.124359571495 + }, + { + "height":19.6921, + "id":5, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":13, + "text":"...#start2&moveTo=start", + "wrap":true + }, + "type":"", + "visible":true, + "width":158.292, + "x":32, + "y":132 + }, + { + "height":19.6921, + "id":6, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":13, + "text":"...#start&moveTo=meeting-room", + "wrap":true + }, + "type":"", + "visible":true, + "width":217.164845831393, + "x":32, + "y":93.740349627387 + }, + { + "height":19.6921, + "id":7, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":13, + "text":"start2", + "wrap":true + }, + "type":"", + "visible":true, + "width":33.0940201210992, + "x":74.556855798789, + "y":245.393819585468 + }, + { + "height":19.6921, + "id":8, + "name":"", + "rotation":0, + "text": + { + "fontfamily":"Sans Serif", + "pixelsize":13, + "text":"meeting-room", + "wrap":true + }, + "type":"", + "visible":true, + "width":92.7120717279925, + "x":233.848901257569, + "y":135.845612785282 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":11, + "nextobjectid":9, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.7.2", + "tileheight":32, + "tilesets":[ + { + "columns":11, + "firstgid":1, + "image":"tileset1.png", + "imageheight":352, + "imagewidth":352, + "margin":0, + "name":"tileset1", + "spacing":0, + "tilecount":121, + "tileheight":32, + "tiles":[ + { + "id":16, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":17, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":18, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":19, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }], + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":"1.6", + "width":10 +} \ No newline at end of file