diff --git a/docs/maps/api-deprecated.md b/docs/maps/api-deprecated.md
index f2b582a5..ffa8af9e 100644
--- a/docs/maps/api-deprecated.md
+++ b/docs/maps/api-deprecated.md
@@ -3,19 +3,21 @@
The list of functions below is **deprecated**. You should not use those but. use the replacement functions.
-- Method `WA.sendChatMessage` is deprecated. It has been renamed to `WA.chat.sendChatMessage`.
-- Method `WA.disablePlayerControls` is deprecated. It has been renamed to `WA.controls.disablePlayerControls`.
-- Method `WA.restorePlayerControls` is deprecated. It has been renamed to `WA.controls.restorePlayerControls`.
+- Method `WA.sendChatMessage` is deprecated. It has been renamed to [`WA.chat.sendChatMessage`](api-chat.md#sending-a-message-in-the-chat).
+- Method `WA.disablePlayerControls` is deprecated. It has been renamed to [`WA.controls.disablePlayerControls`](api-controls.md#disabling--restoring-controls).
+- Method `WA.restorePlayerControls` is deprecated. It has been renamed to [`WA.controls.restorePlayerControls`](api-controls.md#disabling--restoring-controls).
- Method `WA.displayBubble` is deprecated. It has been renamed to `WA.ui.displayBubble`.
- Method `WA.removeBubble` is deprecated. It has been renamed to `WA.ui.removeBubble`.
-- Method `WA.openTab` is deprecated. It has been renamed to `WA.nav.openTab`.
-- Method `WA.loadSound` is deprecated. It has been renamed to `WA.sound.loadSound`.
-- Method `WA.goToPage` is deprecated. It has been renamed to `WA.nav.goToPage`.
-- Method `WA.goToRoom` is deprecated. It has been renamed to `WA.nav.goToRoom`.
-- Method `WA.openCoWebSite` is deprecated. It has been renamed to `WA.nav.openCoWebSite`.
-- Method `WA.closeCoWebSite` is deprecated. It has been renamed to `WA.nav.closeCoWebSite`.
-- Method `WA.openPopup` is deprecated. It has been renamed to `WA.ui.openPopup`.
-- Method `WA.onChatMessage` is deprecated. It has been renamed to `WA.chat.onChatMessage`.
-- Method `WA.onEnterZone` is deprecated. It has been renamed to `WA.room.onEnterZone`.
-- Method `WA.onLeaveZone` is deprecated. It has been renamed to `WA.room.onLeaveZone`.
-- Method `WA.ui.registerMenuCommand` parameter `callback` is deprecated. Use `WA.ui.registerMenuCommand(commandDescriptor: string, options: MenuOptions)`.
\ No newline at end of file
+- Method `WA.openTab` is deprecated. It has been renamed to [`WA.nav.openTab`](api-nav.md#opening-a-web-page-in-a-new-tab).
+- Method `WA.loadSound` is deprecated. It has been renamed to [`WA.sound.loadSound`](api-sound.md#load-a-sound-from-an-url).
+- Method `WA.goToPage` is deprecated. It has been renamed to [`WA.nav.goToPage`](api-nav.md#opening-a-web-page-in-the-current-tab).
+- Method `WA.goToRoom` is deprecated. It has been renamed to [`WA.nav.goToRoom`](api-nav.md#going-to-a-different-map-from-the-script).
+- Method `WA.openCoWebSite` is deprecated. It has been renamed to [`WA.nav.openCoWebSite`](api-nav.md#openingclosing-web-page-in-co-websites).
+- Method `WA.closeCoWebSite` is deprecated. It has been remove and [replace by a function close](api-nav.md#openingclosing-web-page-in-co-websites).
+- Method `WA.openPopup` is deprecated. It has been renamed to [`WA.ui.openPopup`](api-ui.md#opening-a-popup).
+- Method `WA.onChatMessage` is deprecated. It has been renamed to [`WA.chat.onChatMessage`](api-chat.md#listening-to-messages-from-the-chat).
+- Method `WA.onEnterZone` is deprecated. It has been renamed to [`WA.room.onEnterZone`](api-room.md#detecting-when-the-user-entersleaves-a-layer).
+- Method `WA.onLeaveZone` is deprecated. It has been renamed to [`WA.room.onLeaveZone`](api-room.md#detecting-when-the-user-entersleaves-a-layer).
+- Method `WA.ui.registerMenuCommand` parameter `callback` is deprecated. Use [`WA.ui.registerMenuCommand(commandDescriptor: string, options: MenuOptions)`](api-ui.md#add-custom-menu).
+- Method `WA.room.onEnterZone` is deprecated. Use instead [`WA.room.onEnterLayer`](api-room.md#detecting-when-the-user-entersleaves-a-layer).
+- Method `WA.room.onLeaveZone` is deprecated. Use instead [`WA.room.onLeaveLayer`](api-room.md#detecting-when-the-user-entersleaves-a-layer).
\ No newline at end of file
diff --git a/docs/maps/api-player.md b/docs/maps/api-player.md
index ed73c32d..39a13d9e 100644
--- a/docs/maps/api-player.md
+++ b/docs/maps/api-player.md
@@ -68,7 +68,9 @@ The event has the following attributes :
* **moving (boolean):** **true** when the current player is moving, **false** otherwise.
* **direction (string):** **"right"** | **"left"** | **"down"** | **"top"** the direction where the current player is moving.
* **x (number):** coordinate X of the current player.
-* **y (number):** coordinate Y of the current player.
+* **y (number):** coordinate Y of the current player.
+* **oldX (number):** old coordinate X of the current player.
+* **oldY (number):** old coordinate Y of the current player.
**callback:** the function that will be called when the current player is moving. It contains the event.
diff --git a/docs/maps/api-room.md b/docs/maps/api-room.md
index d1a26d2f..72947df8 100644
--- a/docs/maps/api-room.md
+++ b/docs/maps/api-room.md
@@ -17,35 +17,27 @@ The name of the layers of this map are :
* `bottom/build/carpet`
* `wall`
-### Detecting when the user enters/leaves a zone
+### Detecting when the user enters/leaves a layer
```
-WA.room.onEnterZone(name: string, callback: () => void): void
-WA.room.onLeaveZone(name: string, callback: () => void): void
+WA.room.onEnterLayer(name: string): Subscription
+WA.room.onLeaveLayer(name: string): Subscription
```
-Listens to the position of the current user. The event is triggered when the user enters or leaves a given zone. The name of the zone is stored in the map, on a dedicated layer with the `zone` property.
+Listens to the position of the current user. The event is triggered when the user enters or leaves a given layer.
-
-
-
- The `zone` property, applied on a layer
-
-
-
-* **name**: the name of the zone, as defined in the `zone` property.
-* **callback**: the function that will be called when a user enters or leaves the zone.
+* **name**: the name of the layer who as defined in Tiled.
Example:
```javascript
-WA.room.onEnterZone('myZone', () => {
+WA.room.onEnterLayer('myLayer').subscribe(() => {
WA.chat.sendChatMessage("Hello!", 'Mr Robot');
-})
+});
-WA.room.onLeaveZone('myZone', () => {
+WA.room.onLeaveLayer('myLayer').subscribe(() => {
WA.chat.sendChatMessage("Goodbye!", 'Mr Robot');
-})
+});
```
### Show / Hide a layer
@@ -71,7 +63,7 @@ WA.room.setProperty(layerName : string, propertyName : string, propertyValue : s
Set the value of the `propertyName` property of the layer `layerName` at `propertyValue`. If the property doesn't exist, create the property `propertyName` and set the value of the property at `propertyValue`.
-Note :
+Note :
To unset a property from a layer, use `setProperty` with `propertyValue` set to `undefined`.
Example :
@@ -131,7 +123,7 @@ console.log("Map generated with Tiled version ", map.tiledversion);
Check the [Tiled documentation to learn more about the format of the JSON map](https://doc.mapeditor.org/en/stable/reference/json-map-format/).
-### Changing tiles
+### Changing tiles
```
WA.room.setTiles(tiles: TileDescriptor[]): void
```
@@ -144,7 +136,7 @@ If `tile` is a string, it's not the id of the tile but the value of the property
-`TileDescriptor` has the following attributes :
+`TileDescriptor` has the following attributes :
* **x (number) :** The coordinate x of the tile that you want to replace.
* **y (number) :** The coordinate y of the tile that you want to replace.
* **tile (number | string) :** The id of the tile that will be placed in the map.
@@ -154,7 +146,7 @@ If `tile` is a string, it's not the id of the tile but the value of the property
Note: If you want to unset a tile, use `setTiles` with `tile` set to `null`.
-Example :
+Example :
```javascript
WA.room.setTiles([
{x: 6, y: 4, tile: 'blue', layer: 'setTiles'},
@@ -246,7 +238,7 @@ const website = WA.room.website.create({
WA.room.website.delete(name: string): Promise
```
-Use `WA.room.website.delete` to completely remove an embedded website from your map.
+Use `WA.room.website.delete` to completely remove an embedded website from your map.
### The EmbeddedWebsite class
@@ -271,7 +263,7 @@ When you modify a property of an `EmbeddedWebsite` instance, the iframe is autom
{.alert.alert-warning}
-The websites you add/edit/delete via the scripting API are only shown locally. If you want them
+The websites you add/edit/delete via the scripting API are only shown locally. If you want them
to be displayed for every player, you can use [variables](api-start.md) to share a common state
between all users.
diff --git a/front/src/Api/Events/ChangeLayerEvent.ts b/front/src/Api/Events/ChangeLayerEvent.ts
new file mode 100644
index 00000000..77ff8ede
--- /dev/null
+++ b/front/src/Api/Events/ChangeLayerEvent.ts
@@ -0,0 +1,11 @@
+import * as tg from "generic-type-guard";
+
+export const isChangeLayerEvent = new tg.IsInterface()
+ .withProperties({
+ name: tg.isString,
+ })
+ .get();
+/**
+ * A message sent from the game to the iFrame when a user enters or leaves a layer.
+ */
+export type ChangeLayerEvent = tg.GuardedType;
diff --git a/front/src/Api/Events/HasPlayerMovedEvent.ts b/front/src/Api/Events/HasPlayerMovedEvent.ts
index 87b45482..a3f1aa21 100644
--- a/front/src/Api/Events/HasPlayerMovedEvent.ts
+++ b/front/src/Api/Events/HasPlayerMovedEvent.ts
@@ -6,6 +6,8 @@ export const isHasPlayerMovedEvent = new tg.IsInterface()
moving: tg.isBoolean,
x: tg.isNumber,
y: tg.isNumber,
+ oldX: tg.isOptional(tg.isNumber),
+ oldY: tg.isOptional(tg.isNumber),
})
.get();
diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts
index 9e31b46c..abb492c5 100644
--- a/front/src/Api/Events/IframeEvent.ts
+++ b/front/src/Api/Events/IframeEvent.ts
@@ -29,6 +29,7 @@ import type {
} from "./ui/TriggerActionMessageEvent";
import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent";
import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent";
+import type { ChangeLayerEvent } from "./ChangeLayerEvent";
export interface TypedMessageEvent extends MessageEvent {
data: T;
@@ -75,6 +76,8 @@ export interface IframeResponseEventMap {
userInputChat: UserInputChatEvent;
enterEvent: EnterLeaveEvent;
leaveEvent: EnterLeaveEvent;
+ enterLayerEvent: ChangeLayerEvent;
+ leaveLayerEvent: ChangeLayerEvent;
buttonClickedEvent: ButtonClickedEvent;
hasPlayerMoved: HasPlayerMovedEvent;
menuItemClicked: MenuItemClickedEvent;
diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts
index caa59420..d626fc05 100644
--- a/front/src/Api/IframeListener.ts
+++ b/front/src/Api/IframeListener.ts
@@ -30,6 +30,7 @@ import { SetTilesEvent, isSetTilesEvent } from "./Events/SetTilesEvent";
import type { SetVariableEvent } from "./Events/SetVariableEvent";
import { ModifyEmbeddedWebsiteEvent, isEmbeddedWebsiteEvent } from "./Events/EmbeddedWebsiteEvent";
import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Stores/MenuStore";
+import type { ChangeLayerEvent } from "./Events/ChangeLayerEvent";
type AnswererCallback = (
query: IframeQueryMap[T]["query"],
@@ -395,6 +396,24 @@ class IframeListener {
});
}
+ sendEnterLayerEvent(layerName: string) {
+ this.postMessage({
+ type: "enterLayerEvent",
+ data: {
+ name: layerName,
+ } as ChangeLayerEvent,
+ });
+ }
+
+ sendLeaveLayerEvent(layerName: string) {
+ this.postMessage({
+ type: "leaveLayerEvent",
+ data: {
+ name: layerName,
+ } as ChangeLayerEvent,
+ });
+ }
+
hasPlayerMoved(event: HasPlayerMovedEvent) {
if (this.sendPlayerMove) {
this.postMessage({
diff --git a/front/src/Api/iframe/room.ts b/front/src/Api/iframe/room.ts
index 22df49c9..cfa02807 100644
--- a/front/src/Api/iframe/room.ts
+++ b/front/src/Api/iframe/room.ts
@@ -1,6 +1,7 @@
import { Subject } from "rxjs";
import { EnterLeaveEvent, isEnterLeaveEvent } from "../Events/EnterLeaveEvent";
+import { ChangeLayerEvent, isChangeLayerEvent } from "../Events/ChangeLayerEvent";
import { IframeApiContribution, queryWorkadventure, sendToWorkadventure } from "./IframeApiContribution";
import { apiCallback } from "./registeredCallbacks";
@@ -12,6 +13,9 @@ import website from "./website";
const enterStreams: Map> = new Map>();
const leaveStreams: Map> = new Map>();
+const enterLayerStreams: Map> = new Map>();
+const leaveLayerStreams: Map> = new Map>();
+
interface TileDescriptor {
x: number;
y: number;
@@ -47,8 +51,25 @@ export class WorkadventureRoomCommands extends IframeApiContribution {
+ enterLayerStreams.get(payloadData.name)?.next();
+ },
+ }),
+ apiCallback({
+ type: "leaveLayerEvent",
+ typeChecker: isChangeLayerEvent,
+ callback: (payloadData) => {
+ leaveLayerStreams.get(payloadData.name)?.next();
+ },
+ }),
];
+ /**
+ * @deprecated Use onEnterLayer instead
+ */
onEnterZone(name: string, callback: () => void): void {
let subject = enterStreams.get(name);
if (subject === undefined) {
@@ -57,6 +78,10 @@ export class WorkadventureRoomCommands extends IframeApiContribution void): void {
let subject = leaveStreams.get(name);
if (subject === undefined) {
@@ -65,12 +90,35 @@ export class WorkadventureRoomCommands extends IframeApiContribution {
+ let subject = enterLayerStreams.get(layerName);
+ if (subject === undefined) {
+ subject = new Subject();
+ enterLayerStreams.set(layerName, subject);
+ }
+
+ return subject;
+ }
+
+ onLeaveLayer(layerName: string): Subject {
+ let subject = leaveLayerStreams.get(layerName);
+ if (subject === undefined) {
+ subject = new Subject();
+ leaveLayerStreams.set(layerName, subject);
+ }
+
+ return subject;
+ }
+
showLayer(layerName: string): void {
sendToWorkadventure({ type: "showLayer", data: { name: layerName } });
}
+
hideLayer(layerName: string): void {
sendToWorkadventure({ type: "hideLayer", data: { name: layerName } });
}
+
setProperty(layerName: string, propertyName: string, propertyValue: string | number | boolean | undefined): void {
sendToWorkadventure({
type: "setProperty",
@@ -81,10 +129,12 @@ export class WorkadventureRoomCommands extends IframeApiContribution {
const event = await queryWorkadventure({ type: "getMapData", data: undefined });
return event.data as ITiledMap;
}
+
setTiles(tiles: TileDescriptor[]) {
sendToWorkadventure({
type: "setTiles",
diff --git a/front/src/Phaser/Game/GameMap.ts b/front/src/Phaser/Game/GameMap.ts
index 0360859b..cd11c179 100644
--- a/front/src/Phaser/Game/GameMap.ts
+++ b/front/src/Phaser/Game/GameMap.ts
@@ -2,6 +2,7 @@ import type { ITiledMap, ITiledMapLayer, ITiledMapProperty } from "../Map/ITiled
import { flattenGroupLayersMap } from "../Map/LayersFlattener";
import TilemapLayer = Phaser.Tilemaps.TilemapLayer;
import { DEPTH_OVERLAY_INDEX } from "./DepthIndexes";
+import { iframeListener } from "../../Api/IframeListener";
export type PropertyChangeCallback = (
newValue: string | number | boolean | undefined,
@@ -9,14 +10,25 @@ export type PropertyChangeCallback = (
allProps: Map
) => void;
+export type layerChangeCallback = (
+ layersChangedByAction: Array,
+ allLayersOnNewPosition: Array,
+
+) => void;
+
/**
* A wrapper around a ITiledMap interface to provide additional capabilities.
* It is used to handle layer properties.
*/
export class GameMap {
+ // oldKey is the index of the previous tile.
+ private oldKey: number | undefined;
+ // key is the index of the current tile.
private key: number | undefined;
private lastProperties = new Map();
- private callbacks = new Map>();
+ private propertiesChangeCallbacks = new Map>();
+ private enterLayerCallbacks = Array();
+ private leaveLayerCallbacks = Array();
private tileNameMap = new Map();
private tileSetPropertyMap: { [tile_index: number]: Array } = {};
@@ -68,22 +80,32 @@ export class GameMap {
return [];
}
+ private getLayersByKey(key: number): Array {
+ return this.flatLayers.filter(flatLayer => flatLayer.type === 'tilelayer' && flatLayer.data[key] !== 0);
+ }
+
/**
* Sets the position of the current player (in pixels)
* This will trigger events if properties are changing.
*/
public setPosition(x: number, y: number) {
+ this.oldKey = this.key;
+
const xMap = Math.floor(x / this.map.tilewidth);
const yMap = Math.floor(y / this.map.tileheight);
const key = xMap + yMap * this.map.width;
+
if (key === this.key) {
return;
}
+
this.key = key;
- this.triggerAll();
+
+ this.triggerAllProperties();
+ this.triggerLayersChange();
}
- private triggerAll(): void {
+ private triggerAllProperties(): void {
const newProps = this.getProperties(this.key ?? 0);
const oldProps = this.lastProperties;
this.lastProperties = newProps;
@@ -105,6 +127,36 @@ export class GameMap {
}
}
+ private triggerLayersChange() {
+ const layersByOldKey = this.oldKey ? this.getLayersByKey(this.oldKey) : [];
+ const layersByNewKey = this.key ? this.getLayersByKey(this.key) : [];
+
+ const enterLayers = new Set(layersByNewKey);
+ const leaveLayers = new Set(layersByOldKey);
+
+ enterLayers.forEach(layer => {
+ if (leaveLayers.has(layer)) {
+ leaveLayers.delete(layer);
+ enterLayers.delete(layer);
+ }
+ });
+
+
+ if (enterLayers.size > 0) {
+ const layerArray = Array.from(enterLayers);
+ for (const callback of this.enterLayerCallbacks) {
+ callback(layerArray, layersByNewKey);
+ }
+ }
+
+ if (leaveLayers.size > 0) {
+ const layerArray = Array.from(leaveLayers);
+ for (const callback of this.leaveLayerCallbacks) {
+ callback(layerArray, layersByNewKey);
+ }
+ }
+ }
+
public getCurrentProperties(): Map {
return this.lastProperties;
}
@@ -167,7 +219,7 @@ export class GameMap {
newValue: string | number | boolean | undefined,
allProps: Map
) {
- const callbacksArray = this.callbacks.get(propName);
+ const callbacksArray = this.propertiesChangeCallbacks.get(propName);
if (callbacksArray !== undefined) {
for (const callback of callbacksArray) {
callback(newValue, oldValue, allProps);
@@ -179,14 +231,28 @@ export class GameMap {
* Registers a callback called when the user moves to a tile where the property propName is different from the last tile the user was on.
*/
public onPropertyChange(propName: string, callback: PropertyChangeCallback) {
- let callbacksArray = this.callbacks.get(propName);
+ let callbacksArray = this.propertiesChangeCallbacks.get(propName);
if (callbacksArray === undefined) {
callbacksArray = new Array();
- this.callbacks.set(propName, callbacksArray);
+ this.propertiesChangeCallbacks.set(propName, callbacksArray);
}
callbacksArray.push(callback);
}
+ /**
+ * Registers a callback called when the user moves inside another layer.
+ */
+ public onEnterLayer(callback: layerChangeCallback) {
+ this.enterLayerCallbacks.push(callback);
+ }
+
+ /**
+ * Registers a callback called when the user moves outside another layer.
+ */
+ public onLeaveLayer(callback: layerChangeCallback) {
+ this.leaveLayerCallbacks.push(callback);
+ }
+
public findLayer(layerName: string): ITiledMapLayer | undefined {
return this.flatLayers.find((layer) => layer.name === layerName);
}
@@ -284,7 +350,8 @@ export class GameMap {
}
property.value = propertyValue;
- this.triggerAll();
+ this.triggerAllProperties();
+ this.triggerLayersChange();
}
/**
diff --git a/front/src/Phaser/Game/GameMapPropertiesListener.ts b/front/src/Phaser/Game/GameMapPropertiesListener.ts
index 29084d2a..3ae6d714 100644
--- a/front/src/Phaser/Game/GameMapPropertiesListener.ts
+++ b/front/src/Phaser/Game/GameMapPropertiesListener.ts
@@ -1,15 +1,32 @@
import type { GameScene } from "./GameScene";
import type { GameMap } from "./GameMap";
import { scriptUtils } from "../../Api/ScriptUtils";
+import type { CoWebsite } from "../../WebRtc/CoWebsiteManager";
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore";
+import { get } from 'svelte/store';
import {
ON_ACTION_TRIGGER_BUTTON,
TRIGGER_WEBSITE_PROPERTIES,
WEBSITE_MESSAGE_PROPERTIES,
} from "../../WebRtc/LayoutManager";
+import type { ITiledMapLayer } from "../Map/ITiledMap";
+
+enum OpenCoWebsiteState {
+ LOADING,
+ OPENED,
+ MUST_BE_CLOSE,
+}
+
+interface OpenCoWebsite {
+ coWebsite: CoWebsite | undefined,
+ state: OpenCoWebsiteState
+}
export class GameMapPropertiesListener {
+ private coWebsitesOpenByLayer = new Map();
+ private coWebsitesActionTriggerByLayer = new Map();
+
constructor(private scene: GameScene, private gameMap: GameMap) {}
register() {
@@ -36,42 +53,178 @@ export class GameMapPropertiesListener {
}
}
});
- this.gameMap.onPropertyChange("openWebsite", (newValue, oldValue, allProps) => {
- const handler = async () => {
- if (newValue === undefined || newValue !== oldValue) {
- layoutManagerActionStore.removeAction("openWebsite");
- await coWebsiteManager.closeCoWebsites();
- }
- if (newValue !== undefined) {
+ // Open a new co-website by the property.
+ this.gameMap.onEnterLayer((newLayers) => {
+ const handler = () => {
+ newLayers.forEach(layer => {
+ if (!layer.properties) {
+ return;
+ }
+
+ let openWebsiteProperty: string | undefined;
+ let allowApiProperty: boolean | undefined;
+ let websitePolicyProperty: string | undefined;
+ let websiteWidthProperty: number | undefined;
+ let websitePositionProperty: number | undefined;
+ let websiteTriggerProperty: string | undefined;
+ let websiteTriggerMessageProperty: string | undefined;
+
+ layer.properties.forEach(property => {
+ switch(property.name) {
+ case 'openWebsite':
+ openWebsiteProperty = property.value as string | undefined;
+ break;
+ case 'openWebsiteAllowApi':
+ allowApiProperty = property.value as boolean | undefined;
+ break;
+ case 'openWebsitePolicy':
+ websitePolicyProperty = property.value as string | undefined;
+ break;
+ case 'openWebsiteWidth':
+ websiteWidthProperty = property.value as number | undefined;
+ break;
+ case 'openWebsitePosition':
+ websitePositionProperty = property.value as number | undefined;
+ break;
+ case TRIGGER_WEBSITE_PROPERTIES:
+ websiteTriggerProperty = property.value as string | undefined;
+ break;
+ case WEBSITE_MESSAGE_PROPERTIES:
+ websiteTriggerMessageProperty = property.value as string | undefined;
+ break;
+ }
+ });
+
+ if (!openWebsiteProperty) {
+ return;
+ }
+
+ const actionUuid = "openWebsite-" + (Math.random() + 1).toString(36).substring(7);
+
+ if (this.coWebsitesOpenByLayer.has(layer)) {
+ return;
+ }
+
+ this.coWebsitesOpenByLayer.set(layer, {
+ coWebsite: undefined,
+ state: OpenCoWebsiteState.LOADING,
+ });
+
const openWebsiteFunction = () => {
coWebsiteManager.loadCoWebsite(
- newValue as string,
+ openWebsiteProperty as string,
this.scene.MapUrlFile,
- allProps.get("openWebsiteAllowApi") as boolean | undefined,
- allProps.get("openWebsitePolicy") as string | undefined,
- allProps.get("openWebsiteWidth") as number | undefined
- );
+ allowApiProperty,
+ websitePolicyProperty,
+ websiteWidthProperty,
+ websitePositionProperty,
+ ).then(coWebsite => {
+ const coWebsiteOpen = this.coWebsitesOpenByLayer.get(layer);
+ if (coWebsiteOpen && coWebsiteOpen.state === OpenCoWebsiteState.MUST_BE_CLOSE) {
+ coWebsiteManager.closeCoWebsite(coWebsite);
+ this.coWebsitesOpenByLayer.delete(layer);
+ this.coWebsitesActionTriggerByLayer.delete(layer);
+ } else {
+ this.coWebsitesOpenByLayer.set(layer, {
+ coWebsite,
+ state: OpenCoWebsiteState.OPENED
+ });
+ }
+ });
- layoutManagerActionStore.removeAction("openWebsite");
+ layoutManagerActionStore.removeAction(actionUuid);
};
- const openWebsiteTriggerValue = allProps.get(TRIGGER_WEBSITE_PROPERTIES);
- if (openWebsiteTriggerValue && openWebsiteTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
- let message = allProps.get(WEBSITE_MESSAGE_PROPERTIES);
- if (message === undefined) {
- message = "Press SPACE or touch here to open web site";
+
+ if (websiteTriggerProperty && websiteTriggerProperty === ON_ACTION_TRIGGER_BUTTON) {
+ if (!websiteTriggerMessageProperty) {
+ websiteTriggerMessageProperty = "Press SPACE or touch here to open web site";
}
+
+ this.coWebsitesActionTriggerByLayer.set(layer, actionUuid);
+
layoutManagerActionStore.addAction({
- uuid: "openWebsite",
+ uuid: actionUuid,
type: "message",
- message: message,
+ message: websiteTriggerMessageProperty,
callback: () => openWebsiteFunction(),
userInputManager: this.scene.userInputManager,
});
} else {
openWebsiteFunction();
}
- }
+ });
+ };
+
+ handler();
+ });
+
+ // Close opened co-websites on leave the layer who contain the property.
+ this.gameMap.onLeaveLayer((oldLayers) => {
+ const handler = () => {
+ oldLayers.forEach(layer => {
+ if (!layer.properties) {
+ return;
+ }
+
+ let openWebsiteProperty: string | undefined;
+ let websiteTriggerProperty: string | undefined;
+
+ layer.properties.forEach(property => {
+ switch(property.name) {
+ case 'openWebsite':
+ openWebsiteProperty = property.value as string | undefined;
+ break;
+ case TRIGGER_WEBSITE_PROPERTIES:
+ websiteTriggerProperty = property.value as string | undefined;
+ break;
+ }
+ });
+
+ if (!openWebsiteProperty) {
+ return;
+ }
+
+ const coWebsiteOpen = this.coWebsitesOpenByLayer.get(layer);
+
+ if (!coWebsiteOpen) {
+ return;
+ }
+
+ if (coWebsiteOpen.state === OpenCoWebsiteState.LOADING) {
+ coWebsiteOpen.state = OpenCoWebsiteState.MUST_BE_CLOSE;
+ return;
+ }
+
+ if (coWebsiteOpen.state !== OpenCoWebsiteState.OPENED) {
+ return;
+ }
+
+ if (coWebsiteOpen.coWebsite !== undefined) {
+ coWebsiteManager.closeCoWebsite(coWebsiteOpen.coWebsite);
+ }
+
+ this.coWebsitesOpenByLayer.delete(layer);
+
+ if (!websiteTriggerProperty) {
+ return;
+ }
+
+ const actionStore = get(layoutManagerActionStore);
+ const actionTriggerUuid = this.coWebsitesActionTriggerByLayer.get(layer);
+
+ if (!actionTriggerUuid) {
+ return;
+ }
+
+ const action = actionStore && actionStore.length > 0 ?
+ actionStore.find(action => action.uuid === actionTriggerUuid) : undefined;
+
+
+ if (action) {
+ layoutManagerActionStore.removeAction(actionTriggerUuid);
+ }
+ });
};
handler();
diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts
index d65c4d54..c8858ad3 100644
--- a/front/src/Phaser/Game/GameScene.ts
+++ b/front/src/Phaser/Game/GameScene.ts
@@ -186,6 +186,8 @@ export class GameScene extends DirtyScene {
moving: false,
x: -1000,
y: -1000,
+ oldX: -1000,
+ oldY: -1000,
};
private gameMap!: GameMap;
@@ -764,6 +766,19 @@ export class GameScene extends DirtyScene {
//init user position and play trigger to check layers properties
this.gameMap.setPosition(this.CurrentPlayer.x, this.CurrentPlayer.y);
+
+ // Init layer change listener
+ this.gameMap.onEnterLayer(layers => {
+ layers.forEach(layer => {
+ iframeListener.sendEnterLayerEvent(layer.name);
+ });
+ });
+
+ this.gameMap.onLeaveLayer(layers => {
+ layers.forEach(layer => {
+ iframeListener.sendLeaveLayerEvent(layer.name);
+ });
+ });
});
}
@@ -895,6 +910,7 @@ export class GameScene extends DirtyScene {
audioManagerVisibilityStore.set(!(newValue === undefined));
});
+ // TODO: Legacy functionnality replace by layer change
this.gameMap.onPropertyChange("zone", (newValue, oldValue) => {
if (oldValue) {
iframeListener.sendLeaveEvent(oldValue as string);
@@ -1749,7 +1765,11 @@ ${escapedMessage}
const playerMovement = new PlayerMovement(
{ x: player.x, y: player.y },
this.currentTick,
- message.position,
+ {
+ ...message.position,
+ oldX: undefined,
+ oldY: undefined,
+ },
this.currentTick + POSITION_DELAY
);
this.playersPositionInterpolator.updatePlayerPosition(player.userId, playerMovement);
diff --git a/front/src/Phaser/Game/PlayerMovement.ts b/front/src/Phaser/Game/PlayerMovement.ts
index c3daedad..7758f010 100644
--- a/front/src/Phaser/Game/PlayerMovement.ts
+++ b/front/src/Phaser/Game/PlayerMovement.ts
@@ -38,6 +38,8 @@ export class PlayerMovement {
return {
x,
y,
+ oldX: this.startPosition.x,
+ oldY: this.startPosition.y,
direction: this.endPosition.direction,
moving: true,
};
diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts
index 3edcdcde..28a1d3bd 100644
--- a/front/src/Phaser/Player/Player.ts
+++ b/front/src/Phaser/Player/Player.ts
@@ -64,14 +64,14 @@ export class Player extends Character {
if (x !== 0 || y !== 0) {
this.move(x, y);
- this.emit(hasMovedEventName, { moving, direction, x: this.x, y: this.y });
+ this.emit(hasMovedEventName, { moving, direction, x: this.x, y: this.y, oldX: x, oldY: y });
} else if (this.wasMoving && moving) {
// slow joystick movement
this.move(0, 0);
- this.emit(hasMovedEventName, { moving, direction: this.previousDirection, x: this.x, y: this.y });
+ this.emit(hasMovedEventName, { moving, direction: this.previousDirection, x: this.x, y: this.y, oldX: x, oldY: y });
} else if (this.wasMoving && !moving) {
this.stop();
- this.emit(hasMovedEventName, { moving, direction: this.previousDirection, x: this.x, y: this.y });
+ this.emit(hasMovedEventName, { moving, direction: this.previousDirection, x: this.x, y: this.y, oldX: x, oldY: y });
}
if (direction !== null) {
diff --git a/front/src/Stores/LayoutManagerStore.ts b/front/src/Stores/LayoutManagerStore.ts
index e92cd3c4..063d45a7 100644
--- a/front/src/Stores/LayoutManagerStore.ts
+++ b/front/src/Stores/LayoutManagerStore.ts
@@ -9,7 +9,9 @@ export interface LayoutManagerAction {
userInputManager: UserInputManager | undefined;
}
+
function createLayoutManagerAction() {
+
const { subscribe, set, update } = writable([]);
return {
diff --git a/front/tests/Phaser/Game/PlayerMovementTest.ts b/front/tests/Phaser/Game/PlayerMovementTest.ts
index ce2e2767..4b9e8e99 100644
--- a/front/tests/Phaser/Game/PlayerMovementTest.ts
+++ b/front/tests/Phaser/Game/PlayerMovementTest.ts
@@ -7,7 +7,12 @@ describe("Interpolation / Extrapolation", () => {
x: 100, y: 200
}, 42000,
{
- x: 200, y: 100, moving: true, direction: "up"
+ x: 200,
+ y: 100,
+ oldX: undefined,
+ oldY: undefined,
+ moving: true,
+ direction: "up"
},
42200
);
@@ -19,6 +24,8 @@ describe("Interpolation / Extrapolation", () => {
expect(playerMovement.getPosition(42100)).toEqual({
x: 150,
y: 150,
+ oldX: undefined,
+ oldY: undefined,
direction: 'up',
moving: true
});
@@ -26,6 +33,8 @@ describe("Interpolation / Extrapolation", () => {
expect(playerMovement.getPosition(42200)).toEqual({
x: 200,
y: 100,
+ oldX: undefined,
+ oldY: undefined,
direction: 'up',
moving: true
});
@@ -33,6 +42,8 @@ describe("Interpolation / Extrapolation", () => {
expect(playerMovement.getPosition(42300)).toEqual({
x: 250,
y: 50,
+ oldX: undefined,
+ oldY: undefined,
direction: 'up',
moving: true
});
@@ -43,7 +54,12 @@ describe("Interpolation / Extrapolation", () => {
x: 100, y: 200
}, 42000,
{
- x: 200, y: 100, moving: false, direction: "up"
+ x: 200,
+ y: 100,
+ oldX: undefined,
+ oldY: undefined,
+ moving: false,
+ direction: "up"
},
42200
);
@@ -51,6 +67,8 @@ describe("Interpolation / Extrapolation", () => {
expect(playerMovement.getPosition(42300)).toEqual({
x: 200,
y: 100,
+ oldX: undefined,
+ oldY: undefined,
direction: 'up',
moving: false
});
@@ -61,7 +79,12 @@ describe("Interpolation / Extrapolation", () => {
x: 100, y: 200
}, 42000,
{
- x: 200, y: 100, moving: false, direction: "up"
+ x: 200,
+ y: 100,
+ oldX: undefined,
+ oldY: undefined,
+ moving: false,
+ direction: "up"
},
42200
);
@@ -69,6 +92,8 @@ describe("Interpolation / Extrapolation", () => {
expect(playerMovement.getPosition(42100)).toEqual({
x: 150,
y: 150,
+ oldX: undefined,
+ oldY: undefined,
direction: 'up',
moving: true
});
diff --git a/maps/tests/ChangeLayerApi/change_layer_api.json b/maps/tests/ChangeLayerApi/change_layer_api.json
new file mode 100644
index 00000000..35c25683
--- /dev/null
+++ b/maps/tests/ChangeLayerApi/change_layer_api.json
@@ -0,0 +1,617 @@
+{ "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":[0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23, 0, 0, 0, 0, 0, 23, 23, 23, 23, 23],
+ "height":10,
+ "id":5,
+ "name":"myLayer",
+ "opacity":1,
+ "type":"tilelayer",
+ "visible":true,
+ "width":10,
+ "x":0,
+ "y":0
+ },
+ {
+ "draworder":"topdown",
+ "id":3,
+ "name":"floorLayer",
+ "objects":[
+ {
+ "height":111.874771331266,
+ "id":1,
+ "name":"Tests",
+ "rotation":0,
+ "text":
+ {
+ "fontfamily":"Sans Serif",
+ "pixelsize":8,
+ "text":"Test 1:\nGo on the blue carpet.\nResult:\nA message has been sent to the chat.\n\nTest 2:\nGo outside the blue carpet.\nResult:\nAnother message has been sent to the chat.\n",
+ "wrap":true
+ },
+ "type":"",
+ "visible":true,
+ "width":316.770833333333,
+ "x":1.64026713939023,
+ "y":206.086424886945
+ }],
+ "opacity":1,
+ "type":"objectgroup",
+ "visible":true,
+ "x":0,
+ "y":0
+ },
+ {
+ "data":[0, 0, 0, 0, 0, 0, 0, 0, 82, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 19, 27, 0, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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":"objects",
+ "opacity":1,
+ "type":"tilelayer",
+ "visible":true,
+ "width":10,
+ "x":0,
+ "y":0
+ }],
+ "nextlayerid":9,
+ "nextobjectid":3,
+ "orientation":"orthogonal",
+ "properties":[
+ {
+ "name":"script",
+ "type":"string",
+ "value":"script.js"
+ }],
+ "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":1,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":2,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":3,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":4,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":5,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":6,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":7,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":8,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":9,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":10,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":12,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "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
+ }]
+ },
+ {
+ "id":20,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":21,
+ "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
+ }]
+ },
+ {
+ "id":25,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":26,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":27,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":28,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":29,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":30,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":31,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":32,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":34,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":35,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":42,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":43,
+ "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":59,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":60,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":70,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":71,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":80,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":81,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":89,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":91,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":93,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":94,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":95,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":96,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":97,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":100,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":102,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":103,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":104,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":105,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":106,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":107,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":108,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":114,
+ "properties":[
+ {
+ "name":"collides",
+ "type":"bool",
+ "value":true
+ }]
+ },
+ {
+ "id":115,
+ "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/ChangeLayerApi/script.js b/maps/tests/ChangeLayerApi/script.js
new file mode 100644
index 00000000..32f038ba
--- /dev/null
+++ b/maps/tests/ChangeLayerApi/script.js
@@ -0,0 +1,7 @@
+WA.room.onEnterLayer('myLayer').subscribe(() => {
+ WA.chat.sendChatMessage("Hello!", 'Wooka');
+});
+
+WA.room.onLeaveLayer('myLayer').subscribe(() => {
+ WA.chat.sendChatMessage("Goodbye!", 'Wooka');
+});
\ No newline at end of file
diff --git a/maps/tests/index.html b/maps/tests/index.html
index 899066ef..068136ed 100644
--- a/maps/tests/index.html
+++ b/maps/tests/index.html
@@ -235,6 +235,14 @@
Testing scripting API for websites inside a map
+