From 73d589ad1107ccc8870f1aebfb4167dcef34ebd8 Mon Sep 17 00:00:00 2001 From: GRL Date: Wed, 28 Jul 2021 18:03:19 +0200 Subject: [PATCH] Load a json file that describe a tileset in Tile format. Then add it to the tilemap of the GameScene Then add if to the GameMap --- front/src/Api/Events/IframeEvent.ts | 55 +++++--- front/src/Api/Events/LoadTilesetEvent.ts | 12 ++ front/src/Api/IframeListener.ts | 100 +++++++++------ front/src/Api/iframe/room.ts | 10 +- front/src/Phaser/Game/GameMap.ts | 11 ++ front/src/Phaser/Game/GameScene.ts | 54 +++++++- maps/tests/LoadTileset/LoadTileset.json | 141 +++++++++++++++++++++ maps/tests/LoadTileset/Yellow.jpg | Bin 0 -> 28810 bytes maps/tests/LoadTileset/Yellow.json | 87 +++++++++++++ maps/tests/LoadTileset/scriptTileset.js | 5 + maps/tests/LoadTileset/tileset_dungeon.png | Bin 0 -> 9696 bytes 11 files changed, 407 insertions(+), 68 deletions(-) create mode 100644 front/src/Api/Events/LoadTilesetEvent.ts create mode 100644 maps/tests/LoadTileset/LoadTileset.json create mode 100644 maps/tests/LoadTileset/Yellow.jpg create mode 100644 maps/tests/LoadTileset/Yellow.json create mode 100644 maps/tests/LoadTileset/scriptTileset.js create mode 100644 maps/tests/LoadTileset/tileset_dungeon.png 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/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts index 98583cba..0a027ccd 100644 --- a/front/src/Phaser/Game/GameMap.ts +++ b/front/src/Phaser/Game/GameMap.ts @@ -61,6 +61,17 @@ export class GameMap { } } + public addLoadedTileset( + newMap: ITiledMap, + newPhaserMap: Phaser.Tilemaps.Tilemap, + newTerrains: Array + ) { + this.map = newMap; + for (const layer of this.phaserLayers) { + layer.tileset = newTerrains; + } + } + public getPropertiesForIndex(index: number): Array { if (this.tileSetPropertyMap[index]) { return this.tileSetPropertyMap[index]; diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index c5deaba5..3b102ce1 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; export interface GameSceneInitInterface { initPosition: PointInterface | null; @@ -1083,6 +1082,52 @@ ${escapedMessage} } }) ); + iframeListener.registerAnswerer("loadTileset", (eventTileset) => { + const jsonTilesetDir = eventTileset.url.substr(0, eventTileset.url.lastIndexOf("/")); + let newFirstgid = 1; + const lastTileset = this.mapFile.tilesets[this.mapFile.tilesets.length - 1]; + if (lastTileset) { + 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); + this.load.on("filecomplete-image-" + jsonTilesetDir + "/" + jsonTileset.image, () => { + jsonTileset = { ...jsonTileset, firstgid: newFirstgid }; + this.mapFile.tilesets.push(jsonTileset); + const newTileset = new Tileset( + jsonTileset.name, + jsonTileset.firstgid, + jsonTileset.tileWidth, + jsonTileset.tileHeight, + jsonTileset.margin, + jsonTileset.spacing, + jsonTileset.tiles + ); + this.Map.tilesets.push(newTileset); + this.Terrains.push( + this.Map.addTilesetImage( + jsonTileset.name, + jsonTilesetDir + "/" + jsonTileset.image, + jsonTileset.tilewidth, + jsonTileset.tileheight, + jsonTileset.margin, + jsonTileset.spacing + ) + ); + this.gameMap.addLoadedTileset(this.mapFile, this.Map, this.Terrains); + resolve(newFirstgid); + }); + this.load.image(jsonTilesetDir + "/" + jsonTileset.image, jsonTilesetDir + "/" + jsonTileset.image); + }); + 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( @@ -1150,7 +1195,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; @@ -1204,6 +1249,7 @@ ${escapedMessage} this.chatVisibilityUnsubscribe(); this.biggestAvailableAreaStoreUnsubscribe(); iframeListener.unregisterAnswerer("getState"); + iframeListener.unregisterAnswerer("loadTileset"); this.sharedVariablesManager?.close(); mediaManager.hideGameOverlay(); @@ -1276,7 +1322,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..927fa333 --- /dev/null +++ b/maps/tests/LoadTileset/LoadTileset.json @@ -0,0 +1,141 @@ +{ "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, 34, 34, 34, 34, 35, 37, 38, 39, 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, 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 + }], + "nextlayerid":3, + "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 0000000000000000000000000000000000000000..e2ee4551e6cc729bfcc51b866f121d953175995b GIT binary patch literal 28810 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 Y0j80LKPcvpC@Lr{^|$pjxBD^wKX#Erl>h($ literal 0 HcmV?d00001 diff --git a/maps/tests/LoadTileset/Yellow.json b/maps/tests/LoadTileset/Yellow.json new file mode 100644 index 00000000..ebe8ab03 --- /dev/null +++ b/maps/tests/LoadTileset/Yellow.json @@ -0,0 +1,87 @@ +{ "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 + }] + }, + { + "id":2, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "id":11, + "properties":[ + { + "name":"collides", + "type":"bool", + "value":true + }] + }, + { + "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..79430cb4 --- /dev/null +++ b/maps/tests/LoadTileset/scriptTileset.js @@ -0,0 +1,5 @@ +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'}, + ]); +}); diff --git a/maps/tests/LoadTileset/tileset_dungeon.png b/maps/tests/LoadTileset/tileset_dungeon.png new file mode 100644 index 0000000000000000000000000000000000000000..fcac082c33704b31451bc8a58af5982c06586aa6 GIT binary patch literal 9696 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