diff --git a/CHANGELOG.md b/CHANGELOG.md index a9af163a..febe0e43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,10 @@ ## Version develop ### Updates - +- New scripting API features : + - Use `WA.room.loadTileset(url: string) : Promise` to load a tileset from a JSON file. - Rewrote the way authentification works: the auth jwt token can now contains an email instead of an uuid - Added an OpenId login flow than can be plugged to any OIDC provider. -- ## Version 1.4.10 diff --git a/docs/maps/api-room.md b/docs/maps/api-room.md index ca708b29..579b3f58 100644 --- a/docs/maps/api-room.md +++ b/docs/maps/api-room.md @@ -163,3 +163,17 @@ WA.room.setTiles([ {x: 9, y: 4, tile: 'blue', layer: 'setTiles'} ]); ``` + +### Loading a tileset +``` +WA.room.loadTileset(url: string): Promise +``` +Load a tileset in JSON format from an url and return the id of the first tile of the loaded tileset. + +You can create a tileset file in Tile Editor. + +```javascript +WA.room.loadTileset("Assets/Tileset.json").then((firstId) => { + WA.room.setTiles([{x: 4, y: 4, tile: firstId, layer: 'bottom'}]); +}) +``` \ No newline at end of file diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 0d995255..0590939b 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -1,5 +1,4 @@ import * as tg from "generic-type-guard"; -import type { GameStateEvent } from "./GameStateEvent"; import type { ButtonClickedEvent } from "./ButtonClickedEvent"; import type { ChatEvent } from "./ChatEvent"; import type { ClosePopupEvent } from "./ClosePopupEvent"; @@ -10,7 +9,6 @@ import type { OpenCoWebSiteEvent } from "./OpenCoWebSiteEvent"; import type { OpenPopupEvent } from "./OpenPopupEvent"; import type { OpenTabEvent } from "./OpenTabEvent"; import type { UserInputChatEvent } from "./UserInputChatEvent"; -import type { MapDataEvent } from "./MapDataEvent"; import type { LayerEvent } from "./LayerEvent"; import type { SetPropertyEvent } from "./setPropertyEvent"; import type { LoadSoundEvent } from "./LoadSoundEvent"; @@ -20,9 +18,11 @@ import type { MenuItemRegisterEvent } from "./ui/MenuItemRegisterEvent"; import type { HasPlayerMovedEvent } from "./HasPlayerMovedEvent"; import type { SetTilesEvent } from "./SetTilesEvent"; import type { SetVariableEvent } from "./SetVariableEvent"; -import {isGameStateEvent} from "./GameStateEvent"; -import {isMapDataEvent} from "./MapDataEvent"; -import {isSetVariableEvent} from "./SetVariableEvent"; +import { isGameStateEvent } from "./GameStateEvent"; +import { isMapDataEvent } from "./MapDataEvent"; +import { isSetVariableEvent } from "./SetVariableEvent"; +import type { LoadTilesetEvent } from "./LoadTilesetEvent"; +import { isLoadTilesetEvent } from "./LoadTilesetEvent"; export interface TypedMessageEvent extends MessageEvent { data: T; @@ -52,6 +52,7 @@ export type IframeEventMap = { playSound: PlaySoundEvent; stopSound: null; getState: undefined; + loadTileset: LoadTilesetEvent; registerMenuCommand: MenuItemRegisterEvent; setTiles: SetTilesEvent; }; @@ -83,7 +84,6 @@ export const isIframeResponseEventWrapper = (event: { type?: string; }): event is IframeResponseEvent => typeof event.type === "string"; - /** * List event types sent from an iFrame to WorkAdventure that expect a unique answer from WorkAdventure along the type for the answer from WorkAdventure to the iFrame. * Types are defined using Type guards that will actually bused to enforce and check types. @@ -101,22 +101,26 @@ export const iframeQueryMapTypeGuards = { query: isSetVariableEvent, answer: tg.isUndefined, }, -} + loadTileset: { + query: isLoadTilesetEvent, + answer: tg.isNumber, + }, +}; -type GuardedType = T extends (x: unknown) => x is (infer T) ? T : never; +type GuardedType = T extends (x: unknown) => x is infer T ? T : never; type IframeQueryMapTypeGuardsType = typeof iframeQueryMapTypeGuards; -type UnknownToVoid = undefined extends T ? void : T; +type UnknownToVoid = undefined extends T ? void : T; export type IframeQueryMap = { [key in keyof IframeQueryMapTypeGuardsType]: { - query: GuardedType - answer: UnknownToVoid> - } -} + query: GuardedType; + answer: UnknownToVoid>; + }; +}; export interface IframeQuery { type: T; - data: IframeQueryMap[T]['query']; + data: IframeQueryMap[T]["query"]; } export interface IframeQueryWrapper { @@ -126,30 +130,34 @@ export interface IframeQueryWrapper { export const isIframeQueryKey = (type: string): type is keyof IframeQueryMap => { return type in iframeQueryMapTypeGuards; -} +}; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const isIframeQuery = (event: any): event is IframeQuery => { const type = event.type; - if (typeof type !== 'string') { + if (typeof type !== "string") { return false; } if (!isIframeQueryKey(type)) { return false; } return iframeQueryMapTypeGuards[type].query(event.data); -} +}; // eslint-disable-next-line @typescript-eslint/no-explicit-any -export const isIframeQueryWrapper = (event: any): event is IframeQueryWrapper => typeof event.id === 'number' && isIframeQuery(event.query); +export const isIframeQueryWrapper = (event: any): event is IframeQueryWrapper => + typeof event.id === "number" && isIframeQuery(event.query); export interface IframeAnswerEvent { id: number; type: T; - data: IframeQueryMap[T]['answer']; + data: IframeQueryMap[T]["answer"]; } -export const isIframeAnswerEvent = (event: { type?: string, id?: number }): event is IframeAnswerEvent => typeof event.type === 'string' && typeof event.id === 'number'; +export const isIframeAnswerEvent = (event: { + type?: string; + id?: number; +}): event is IframeAnswerEvent => typeof event.type === "string" && typeof event.id === "number"; export interface IframeErrorAnswerEvent { id: number; @@ -157,4 +165,9 @@ export interface IframeErrorAnswerEvent { error: string; } -export const isIframeErrorAnswerEvent = (event: { type?: string, id?: number, error?: string }): event is IframeErrorAnswerEvent => typeof event.type === 'string' && typeof event.id === 'number' && typeof event.error === 'string'; +export const isIframeErrorAnswerEvent = (event: { + type?: string; + id?: number; + error?: string; +}): event is IframeErrorAnswerEvent => + typeof event.type === "string" && typeof event.id === "number" && typeof event.error === "string"; diff --git a/front/src/Api/Events/LoadTilesetEvent.ts b/front/src/Api/Events/LoadTilesetEvent.ts new file mode 100644 index 00000000..ecaf93be --- /dev/null +++ b/front/src/Api/Events/LoadTilesetEvent.ts @@ -0,0 +1,12 @@ +import * as tg from "generic-type-guard"; + +export const isLoadTilesetEvent = new tg.IsInterface() + .withProperties({ + url: tg.isString, + }) + .get(); + +/** + * A message sent from the iFrame to the game to add a message in the chat. + */ +export type LoadTilesetEvent = tg.GuardedType; diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index d0c82253..d9286ef0 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -12,7 +12,8 @@ import { isOpenCoWebsite, OpenCoWebSiteEvent } from "./Events/OpenCoWebSiteEvent import { IframeErrorAnswerEvent, IframeEvent, - IframeEventMap, IframeQueryMap, + IframeEventMap, + IframeQueryMap, IframeResponseEvent, IframeResponseEventMap, isIframeEventWrapper, @@ -25,16 +26,16 @@ import { isStopSoundEvent, StopSoundEvent } from "./Events/StopSoundEvent"; import { isLoadSoundEvent, LoadSoundEvent } from "./Events/LoadSoundEvent"; import { isSetPropertyEvent, SetPropertyEvent } from "./Events/setPropertyEvent"; import { isLayerEvent, LayerEvent } from "./Events/LayerEvent"; -import { isMenuItemRegisterEvent } from "./Events/ui/MenuItemRegisterEvent"; -import type { MapDataEvent } from "./Events/MapDataEvent"; -import type { GameStateEvent } from "./Events/GameStateEvent"; import type { HasPlayerMovedEvent } from "./Events/HasPlayerMovedEvent"; import { isLoadPageEvent } from "./Events/LoadPageEvent"; import { handleMenuItemRegistrationEvent, isMenuItemRegisterIframeEvent } from "./Events/ui/MenuItemRegisterEvent"; import { SetTilesEvent, isSetTilesEvent } from "./Events/SetTilesEvent"; import type { SetVariableEvent } from "./Events/SetVariableEvent"; -type AnswererCallback = (query: IframeQueryMap[T]["query"], source: MessageEventSource | null) => IframeQueryMap[T]["answer"] | PromiseLike; +type AnswererCallback = ( + query: IframeQueryMap[T]["query"], + source: MessageEventSource | null +) => IframeQueryMap[T]["answer"] | PromiseLike; /** * Listens to messages from iframes and turn those messages into easy to use observables. @@ -112,13 +113,11 @@ class IframeListener { private readonly scripts = new Map(); private sendPlayerMove: boolean = false; - // Note: we are forced to type this in unknown and later cast with "as" because of https://github.com/microsoft/TypeScript/issues/31904 private answerers: { - [str in keyof IframeQueryMap]?: unknown + [str in keyof IframeQueryMap]?: unknown; } = {}; - init() { window.addEventListener( "message", @@ -158,42 +157,56 @@ class IframeListener { const answerer = this.answerers[query.type] as AnswererCallback | undefined; if (answerer === undefined) { - const errorMsg = 'The iFrame sent a message of type "'+query.type+'" but there is no service configured to answer these messages.'; + const errorMsg = + 'The iFrame sent a message of type "' + + query.type + + '" but there is no service configured to answer these messages.'; console.error(errorMsg); - iframe.contentWindow?.postMessage({ - id: queryId, - type: query.type, - error: errorMsg - } as IframeErrorAnswerEvent, '*'); + iframe.contentWindow?.postMessage( + { + id: queryId, + type: query.type, + error: errorMsg, + } as IframeErrorAnswerEvent, + "*" + ); return; } const errorHandler = (reason: unknown) => { - console.error('An error occurred while responding to an iFrame query.', reason); - let reasonMsg: string = ''; + console.error("An error occurred while responding to an iFrame query.", reason); + let reasonMsg: string = ""; if (reason instanceof Error) { reasonMsg = reason.message; - } else if (typeof reason === 'object') { - reasonMsg = reason ? reason.toString() : ''; - } else if (typeof reason === 'string') { + } else if (typeof reason === "object") { + reasonMsg = reason ? reason.toString() : ""; + } else if (typeof reason === "string") { reasonMsg = reason; } - iframe?.contentWindow?.postMessage({ - id: queryId, - type: query.type, - error: reasonMsg - } as IframeErrorAnswerEvent, '*'); + iframe?.contentWindow?.postMessage( + { + id: queryId, + type: query.type, + error: reasonMsg, + } as IframeErrorAnswerEvent, + "*" + ); }; try { - Promise.resolve(answerer(query.data, message.source)).then((value) => { - iframe?.contentWindow?.postMessage({ - id: queryId, - type: query.type, - data: value - }, '*'); - }).catch(errorHandler); + Promise.resolve(answerer(query.data, message.source)) + .then((value) => { + iframe?.contentWindow?.postMessage( + { + id: queryId, + type: query.type, + data: value, + }, + "*" + ); + }) + .catch(errorHandler); } catch (reason) { errorHandler(reason); } @@ -238,7 +251,7 @@ class IframeListener { } else if (payload.type === "displayBubble") { this._displayBubbleStream.next(); } else if (payload.type === "removeBubble") { - this._removeBubbleStream.next(); + this._removeBubbleStream.next(); } else if (payload.type == "onPlayerMove") { this.sendPlayerMove = true; } else if (isMenuItemRegisterIframeEvent(payload)) { @@ -398,8 +411,8 @@ class IframeListener { setVariable(setVariableEvent: SetVariableEvent) { this.postMessage({ - 'type': 'setVariable', - 'data': setVariableEvent + type: "setVariable", + data: setVariableEvent, }); } @@ -420,7 +433,7 @@ class IframeListener { * @param key The "type" of the query we are answering * @param callback */ - public registerAnswerer(key: T, callback: AnswererCallback ): void { + public registerAnswerer(key: T, callback: AnswererCallback): void { this.answerers[key] = callback; } @@ -432,13 +445,16 @@ class IframeListener { // Let's dispatch the message to the other iframes for (const iframe of this.iframes) { if (iframe.contentWindow !== source) { - iframe.contentWindow?.postMessage({ - 'type': 'setVariable', - 'data': { - key, - value, - } - }, '*'); + iframe.contentWindow?.postMessage( + { + type: "setVariable", + data: { + key, + value, + }, + }, + "*" + ); } } } diff --git a/front/src/Api/iframe/room.ts b/front/src/Api/iframe/room.ts index b5b5c0dd..9c0be9be 100644 --- a/front/src/Api/iframe/room.ts +++ b/front/src/Api/iframe/room.ts @@ -1,4 +1,4 @@ -import { Observable, Subject } from "rxjs"; +import { Subject } from "rxjs"; import { EnterLeaveEvent, isEnterLeaveEvent } from "../Events/EnterLeaveEvent"; @@ -105,6 +105,14 @@ export class WorkadventureRoomCommands extends IframeApiContribution { + return await queryWorkadventure({ + type: "loadTileset", + data: { + url: url, + }, + }); + } } export default new WorkadventureRoomCommands(); diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts index aeaddc34..11f03a9e 100644 --- a/front/src/Connexion/ConnectionManager.ts +++ b/front/src/Connexion/ConnectionManager.ts @@ -64,11 +64,8 @@ class ConnectionManager { ); localUserStore.setAuthToken(authToken); this.authToken = authToken; - room = await Room.createRoom( - new URL(localUserStore.getLastRoomUrl()) - ); + room = await Room.createRoom(new URL(localUserStore.getLastRoomUrl())); urlManager.pushRoomIdToUrl(room); - } else if (connexionType === GameConnexionTypes.register) { //@deprecated const organizationMemberToken = urlManager.getOrganizationToken(); diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 31acb83a..5c3628f5 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -75,8 +75,6 @@ import { joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey } import { waScaleManager } from "../Services/WaScaleManager"; import { EmoteManager } from "./EmoteManager"; import EVENT_TYPE = Phaser.Scenes.Events; -import RenderTexture = Phaser.GameObjects.RenderTexture; -import Tilemap = Phaser.Tilemaps.Tilemap; import type { HasPlayerMovedEvent } from "../../Api/Events/HasPlayerMovedEvent"; import AnimatedTiles from "phaser-animated-tiles"; @@ -88,6 +86,7 @@ import { biggestAvailableAreaStore } from "../../Stores/BiggestAvailableAreaStor import { SharedVariablesManager } from "./SharedVariablesManager"; import { playersStore } from "../../Stores/PlayersStore"; import { chatVisibilityStore } from "../../Stores/ChatStore"; +import Tileset = Phaser.Tilemaps.Tileset; import { userIsAdminStore } from "../../Stores/GameStore"; export interface GameSceneInitInterface { @@ -222,6 +221,9 @@ export class GameScene extends DirtyScene { //hook preload scene preload(): void { + //initialize frame event of scripting API + this.listenToIframeEvents(); + const localUser = localUserStore.getLocalUser(); const textures = localUser?.textures; if (textures) { @@ -550,7 +552,6 @@ export class GameScene extends DirtyScene { ); this.triggerOnMapLayerPropertyChange(); - this.listenToIframeEvents(); if (!this.room.isDisconnected()) { this.connect(); @@ -1084,8 +1085,74 @@ ${escapedMessage} for (const eventTile of eventTiles) { this.gameMap.putTile(eventTile.tile, eventTile.x, eventTile.y, eventTile.layer); } + this.markDirty(); }) ); + iframeListener.registerAnswerer("loadTileset", (eventTileset) => { + return this.connectionAnswerPromise.then(() => { + const jsonTilesetDir = eventTileset.url.substr(0, eventTileset.url.lastIndexOf("/")); + //Initialise the firstgid to 1 because if there is no tileset in the tilemap, the firstgid will be 1 + let newFirstgid = 1; + const lastTileset = this.mapFile.tilesets[this.mapFile.tilesets.length - 1]; + if (lastTileset) { + //If there is at least one tileset in the tilemap then calculate the firstgid of the new tileset + newFirstgid = lastTileset.firstgid + lastTileset.tilecount; + } + return new Promise((resolve, reject) => { + this.load.on("filecomplete-json-" + eventTileset.url, () => { + let jsonTileset = this.cache.json.get(eventTileset.url); + const imageUrl = jsonTilesetDir + "/" + jsonTileset.image; + this.load.image(imageUrl, imageUrl); + this.load.on("filecomplete-image-" + imageUrl, () => { + //Add the firstgid of the tileset to the json file + jsonTileset = { ...jsonTileset, firstgid: newFirstgid }; + this.mapFile.tilesets.push(jsonTileset); + this.Map.tilesets.push( + new Tileset( + jsonTileset.name, + jsonTileset.firstgid, + jsonTileset.tileWidth, + jsonTileset.tileHeight, + jsonTileset.margin, + jsonTileset.spacing, + jsonTileset.tiles + ) + ); + this.Terrains.push( + this.Map.addTilesetImage( + jsonTileset.name, + imageUrl, + jsonTileset.tilewidth, + jsonTileset.tileheight, + jsonTileset.margin, + jsonTileset.spacing + ) + ); + //destroy the tilemapayer because they are unique and we need to reuse their key and layerdData + for (const layer of this.Map.layers) { + layer.tilemapLayer.destroy(false); + } + //Create a new GameMap with the changed file + this.gameMap = new GameMap(this.mapFile, this.Map, this.Terrains); + //Destroy the colliders of the old tilemapLayer + this.physics.add.world.colliders.destroy(); + //Create new colliders with the new GameMap + this.createCollisionWithPlayer(); + //Create new trigger with the new GameMap + this.triggerOnMapLayerPropertyChange(); + resolve(newFirstgid); + }); + }); + this.load.on("loaderror", () => { + console.error("Error while loading " + eventTileset.url + "."); + reject(-1); + }); + + this.load.json(eventTileset.url, eventTileset.url); + this.load.start(); + }); + }); + }); } private setPropertyLayer( @@ -1153,7 +1220,7 @@ ${escapedMessage} let targetRoom: Room; try { targetRoom = await Room.createRoom(roomUrl); - } catch (e: unknown) { + } catch (e) { console.error('Error while fetching new room "' + roomUrl.toString() + '"', e); this.mapTransitioning = false; return; @@ -1207,6 +1274,7 @@ ${escapedMessage} this.chatVisibilityUnsubscribe(); this.biggestAvailableAreaStoreUnsubscribe(); iframeListener.unregisterAnswerer("getState"); + iframeListener.unregisterAnswerer("loadTileset"); this.sharedVariablesManager?.close(); mediaManager.hideGameOverlay(); @@ -1279,7 +1347,7 @@ ${escapedMessage} try { const room = await Room.createRoom(exitRoomPath); return gameManager.loadMap(room, this.scene); - } catch (e: unknown) { + } catch (e) { console.warn('Error while pre-loading exit room "' + exitRoomPath.toString() + '"', e); } } diff --git a/maps/tests/LoadTileset/LoadTileset.json b/maps/tests/LoadTileset/LoadTileset.json new file mode 100644 index 00000000..7358a7b9 --- /dev/null +++ b/maps/tests/LoadTileset/LoadTileset.json @@ -0,0 +1,159 @@ +{ "compressionlevel":-1, + "height":10, + "infinite":false, + "layers":[ + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 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, 0, 0, 0, 0, 0], + "height":10, + "id":1, + "name":"start", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[33, 34, 33, 34, 34, 34, 35, 37, 38, 39, 41, 42, 41, 42, 42, 42, 43, 45, 46, 47, 33, 34, 60, 42, 42, 42, 43, 45, 46, 47, 41, 42, 42, 42, 42, 42, 43, 45, 46, 47, 41, 42, 42, 42, 42, 42, 43, 45, 46, 47, 41, 42, 42, 42, 42, 42, 43, 45, 46, 47, 41, 42, 42, 42, 42, 42, 43, 45, 46, 47, 41, 42, 42, 42, 42, 42, 43, 45, 46, 47, 41, 42, 42, 42, 42, 42, 43, 45, 46, 47, 49, 50, 50, 50, 50, 50, 51, 53, 54, 55], + "height":10, + "id":2, + "name":"bottom", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }, + { + "data":[57, 58, 0, 0, 0, 0, 0, 0, 0, 0, 59, 60, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":10, + "id":3, + "name":"openwebsite", + "opacity":1, + "properties":[ + { + "name":"openWebsite", + "type":"string", + "value":"https:\/\/fr.wikipedia.org\/wiki\/Wikip%C3%A9dia:Accueil_principal" + }], + "type":"tilelayer", + "visible":true, + "width":10, + "x":0, + "y":0 + }], + "nextlayerid":4, + "nextobjectid":1, + "orientation":"orthogonal", + "properties":[ + { + "name":"script", + "type":"string", + "value":"scriptTileset.js" + }], + "renderorder":"right-down", + "tiledversion":"1.7.0", + "tileheight":32, + "tilesets":[ + { + "columns":8, + "firstgid":1, + "image":"tileset_dungeon.png", + "imageheight":256, + "imagewidth":256, + "margin":0, + "name":"Dungeon", + "spacing":0, + "tilecount":64, + "tileheight":32, + "tiles":[ + { + "id":36, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":37, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":38, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":44, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":45, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":46, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":52, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":53, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":54, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }], + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":"1.6", + "width":10 +} \ No newline at end of file diff --git a/maps/tests/LoadTileset/Yellow.jpg b/maps/tests/LoadTileset/Yellow.jpg new file mode 100644 index 00000000..e2ee4551 Binary files /dev/null and b/maps/tests/LoadTileset/Yellow.jpg differ diff --git a/maps/tests/LoadTileset/Yellow.json b/maps/tests/LoadTileset/Yellow.json new file mode 100644 index 00000000..50cda1fd --- /dev/null +++ b/maps/tests/LoadTileset/Yellow.json @@ -0,0 +1,106 @@ +{ "columns":11, + "image":"Yellow.jpg", + "imageheight":128, + "imagewidth":352, + "margin":0, + "name":"Yellow", + "spacing":0, + "tilecount":44, + "tiledversion":"1.7.0", + "tileheight":32, + "tiles":[ + { + "id":0, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":1, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }, + { + "name":"name", + "type":"string", + "value":"Mur" + }] + }, + { + "id":2, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":11, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":12, + "properties":[ + { + "name":"name", + "type":"string", + "value":"sol" + }, + { + "name":"openWebsite", + "type":"string", + "value":"https:\/\/fr.wikipedia.org\/wiki\/Wikip%C3%A9dia:Accueil_principal" + }] + }, + { + "id":13, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":22, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":23, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":24, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }], + "tilewidth":32, + "type":"tileset", + "version":"1.6" +} \ No newline at end of file diff --git a/maps/tests/LoadTileset/scriptTileset.js b/maps/tests/LoadTileset/scriptTileset.js new file mode 100644 index 00000000..b7950ed9 --- /dev/null +++ b/maps/tests/LoadTileset/scriptTileset.js @@ -0,0 +1,6 @@ +WA.room.loadTileset("http://maps.workadventure.localhost/tests/LoadTileset/Yellow.json").then((firstgid) => { + WA.room.setTiles([ + {x: 5, y: 5, tile: firstgid + 1, layer: 'bottom'}, + {x: 5, y: 3, tile: 'sol', layer: 'bottom'} + ]); +}); diff --git a/maps/tests/LoadTileset/tileset_dungeon.png b/maps/tests/LoadTileset/tileset_dungeon.png new file mode 100644 index 00000000..fcac082c Binary files /dev/null and b/maps/tests/LoadTileset/tileset_dungeon.png differ