Merge pull request #1544 from thecodingmachine/property-layer-management

Implement on enters/leaves layer events
This commit is contained in:
David Négrier 2021-10-29 19:46:42 +02:00 committed by GitHub
commit 145117dc58
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 1059 additions and 77 deletions

View file

@ -3,19 +3,21 @@
The list of functions below is **deprecated**. You should not use those but. use the replacement functions. 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.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`. - 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`. - 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.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.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.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`. - 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`. - 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`. - 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`. - 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 renamed to `WA.nav.closeCoWebSite`. - 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`. - 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`. - 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`. - 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`. - 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)`. - 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).

View file

@ -68,7 +68,9 @@ The event has the following attributes :
* **moving (boolean):** **true** when the current player is moving, **false** otherwise. * **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. * **direction (string):** **"right"** | **"left"** | **"down"** | **"top"** the direction where the current player is moving.
* **x (number):** coordinate X of the current player. * **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. **callback:** the function that will be called when the current player is moving. It contains the event.

View file

@ -17,35 +17,27 @@ The name of the layers of this map are :
* `bottom/build/carpet` * `bottom/build/carpet`
* `wall` * `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.onEnterLayer(name: string): Subscription
WA.room.onLeaveZone(name: string, callback: () => void): void 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.
<div> * **name**: the name of the layer who as defined in Tiled.
<figure class="figure">
<img src="images/trigger_event.png" class="figure-img img-fluid rounded" alt="" />
<figcaption class="figure-caption">The `zone` property, applied on a layer</figcaption>
</figure>
</div>
* **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.
Example: Example:
```javascript ```javascript
WA.room.onEnterZone('myZone', () => { WA.room.onEnterLayer('myLayer').subscribe(() => {
WA.chat.sendChatMessage("Hello!", 'Mr Robot'); WA.chat.sendChatMessage("Hello!", 'Mr Robot');
}) });
WA.room.onLeaveZone('myZone', () => { WA.room.onLeaveLayer('myLayer').subscribe(() => {
WA.chat.sendChatMessage("Goodbye!", 'Mr Robot'); WA.chat.sendChatMessage("Goodbye!", 'Mr Robot');
}) });
``` ```
### Show / Hide a layer ### 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`. 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`. To unset a property from a layer, use `setProperty` with `propertyValue` set to `undefined`.
Example : 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/). 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 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
</div> </div>
</div> </div>
`TileDescriptor` has the following attributes : `TileDescriptor` has the following attributes :
* **x (number) :** The coordinate x of the tile that you want to replace. * **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. * **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. * **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`. Note: If you want to unset a tile, use `setTiles` with `tile` set to `null`.
Example : Example :
```javascript ```javascript
WA.room.setTiles([ WA.room.setTiles([
{x: 6, y: 4, tile: 'blue', layer: '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<void> WA.room.website.delete(name: string): Promise<void>
``` ```
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 ### The EmbeddedWebsite class
@ -271,7 +263,7 @@ When you modify a property of an `EmbeddedWebsite` instance, the iframe is autom
{.alert.alert-warning} {.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 to be displayed for every player, you can use [variables](api-start.md) to share a common state
between all users. between all users.

View file

@ -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<typeof isChangeLayerEvent>;

View file

@ -6,6 +6,8 @@ export const isHasPlayerMovedEvent = new tg.IsInterface()
moving: tg.isBoolean, moving: tg.isBoolean,
x: tg.isNumber, x: tg.isNumber,
y: tg.isNumber, y: tg.isNumber,
oldX: tg.isOptional(tg.isNumber),
oldY: tg.isOptional(tg.isNumber),
}) })
.get(); .get();

View file

@ -29,6 +29,7 @@ import type {
} from "./ui/TriggerActionMessageEvent"; } from "./ui/TriggerActionMessageEvent";
import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent"; import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent";
import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent"; import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent";
import type { ChangeLayerEvent } from "./ChangeLayerEvent";
export interface TypedMessageEvent<T> extends MessageEvent { export interface TypedMessageEvent<T> extends MessageEvent {
data: T; data: T;
@ -75,6 +76,8 @@ export interface IframeResponseEventMap {
userInputChat: UserInputChatEvent; userInputChat: UserInputChatEvent;
enterEvent: EnterLeaveEvent; enterEvent: EnterLeaveEvent;
leaveEvent: EnterLeaveEvent; leaveEvent: EnterLeaveEvent;
enterLayerEvent: ChangeLayerEvent;
leaveLayerEvent: ChangeLayerEvent;
buttonClickedEvent: ButtonClickedEvent; buttonClickedEvent: ButtonClickedEvent;
hasPlayerMoved: HasPlayerMovedEvent; hasPlayerMoved: HasPlayerMovedEvent;
menuItemClicked: MenuItemClickedEvent; menuItemClicked: MenuItemClickedEvent;

View file

@ -30,6 +30,7 @@ import { SetTilesEvent, isSetTilesEvent } from "./Events/SetTilesEvent";
import type { SetVariableEvent } from "./Events/SetVariableEvent"; import type { SetVariableEvent } from "./Events/SetVariableEvent";
import { ModifyEmbeddedWebsiteEvent, isEmbeddedWebsiteEvent } from "./Events/EmbeddedWebsiteEvent"; import { ModifyEmbeddedWebsiteEvent, isEmbeddedWebsiteEvent } from "./Events/EmbeddedWebsiteEvent";
import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Stores/MenuStore"; import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Stores/MenuStore";
import type { ChangeLayerEvent } from "./Events/ChangeLayerEvent";
type AnswererCallback<T extends keyof IframeQueryMap> = ( type AnswererCallback<T extends keyof IframeQueryMap> = (
query: IframeQueryMap[T]["query"], 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) { hasPlayerMoved(event: HasPlayerMovedEvent) {
if (this.sendPlayerMove) { if (this.sendPlayerMove) {
this.postMessage({ this.postMessage({

View file

@ -1,6 +1,7 @@
import { Subject } from "rxjs"; import { Subject } from "rxjs";
import { EnterLeaveEvent, isEnterLeaveEvent } from "../Events/EnterLeaveEvent"; import { EnterLeaveEvent, isEnterLeaveEvent } from "../Events/EnterLeaveEvent";
import { ChangeLayerEvent, isChangeLayerEvent } from "../Events/ChangeLayerEvent";
import { IframeApiContribution, queryWorkadventure, sendToWorkadventure } from "./IframeApiContribution"; import { IframeApiContribution, queryWorkadventure, sendToWorkadventure } from "./IframeApiContribution";
import { apiCallback } from "./registeredCallbacks"; import { apiCallback } from "./registeredCallbacks";
@ -12,6 +13,9 @@ import website from "./website";
const enterStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>(); const enterStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>();
const leaveStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>(); const leaveStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>();
const enterLayerStreams: Map<string, Subject<void>> = new Map<string, Subject<void>>();
const leaveLayerStreams: Map<string, Subject<void>> = new Map<string, Subject<void>>();
interface TileDescriptor { interface TileDescriptor {
x: number; x: number;
y: number; y: number;
@ -47,8 +51,25 @@ export class WorkadventureRoomCommands extends IframeApiContribution<Workadventu
leaveStreams.get(payloadData.name)?.next(); leaveStreams.get(payloadData.name)?.next();
}, },
}), }),
apiCallback({
type: "enterLayerEvent",
typeChecker: isChangeLayerEvent,
callback: (payloadData: ChangeLayerEvent) => {
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 { onEnterZone(name: string, callback: () => void): void {
let subject = enterStreams.get(name); let subject = enterStreams.get(name);
if (subject === undefined) { if (subject === undefined) {
@ -57,6 +78,10 @@ export class WorkadventureRoomCommands extends IframeApiContribution<Workadventu
} }
subject.subscribe(callback); subject.subscribe(callback);
} }
/**
* @deprecated Use onLeaveLayer instead
*/
onLeaveZone(name: string, callback: () => void): void { onLeaveZone(name: string, callback: () => void): void {
let subject = leaveStreams.get(name); let subject = leaveStreams.get(name);
if (subject === undefined) { if (subject === undefined) {
@ -65,12 +90,35 @@ export class WorkadventureRoomCommands extends IframeApiContribution<Workadventu
} }
subject.subscribe(callback); subject.subscribe(callback);
} }
onEnterLayer(layerName: string): Subject<void> {
let subject = enterLayerStreams.get(layerName);
if (subject === undefined) {
subject = new Subject<ChangeLayerEvent>();
enterLayerStreams.set(layerName, subject);
}
return subject;
}
onLeaveLayer(layerName: string): Subject<void> {
let subject = leaveLayerStreams.get(layerName);
if (subject === undefined) {
subject = new Subject<ChangeLayerEvent>();
leaveLayerStreams.set(layerName, subject);
}
return subject;
}
showLayer(layerName: string): void { showLayer(layerName: string): void {
sendToWorkadventure({ type: "showLayer", data: { name: layerName } }); sendToWorkadventure({ type: "showLayer", data: { name: layerName } });
} }
hideLayer(layerName: string): void { hideLayer(layerName: string): void {
sendToWorkadventure({ type: "hideLayer", data: { name: layerName } }); sendToWorkadventure({ type: "hideLayer", data: { name: layerName } });
} }
setProperty(layerName: string, propertyName: string, propertyValue: string | number | boolean | undefined): void { setProperty(layerName: string, propertyName: string, propertyValue: string | number | boolean | undefined): void {
sendToWorkadventure({ sendToWorkadventure({
type: "setProperty", type: "setProperty",
@ -81,10 +129,12 @@ export class WorkadventureRoomCommands extends IframeApiContribution<Workadventu
}, },
}); });
} }
async getTiledMap(): Promise<ITiledMap> { async getTiledMap(): Promise<ITiledMap> {
const event = await queryWorkadventure({ type: "getMapData", data: undefined }); const event = await queryWorkadventure({ type: "getMapData", data: undefined });
return event.data as ITiledMap; return event.data as ITiledMap;
} }
setTiles(tiles: TileDescriptor[]) { setTiles(tiles: TileDescriptor[]) {
sendToWorkadventure({ sendToWorkadventure({
type: "setTiles", type: "setTiles",

View file

@ -2,6 +2,7 @@ import type { ITiledMap, ITiledMapLayer, ITiledMapProperty } from "../Map/ITiled
import { flattenGroupLayersMap } from "../Map/LayersFlattener"; import { flattenGroupLayersMap } from "../Map/LayersFlattener";
import TilemapLayer = Phaser.Tilemaps.TilemapLayer; import TilemapLayer = Phaser.Tilemaps.TilemapLayer;
import { DEPTH_OVERLAY_INDEX } from "./DepthIndexes"; import { DEPTH_OVERLAY_INDEX } from "./DepthIndexes";
import { iframeListener } from "../../Api/IframeListener";
export type PropertyChangeCallback = ( export type PropertyChangeCallback = (
newValue: string | number | boolean | undefined, newValue: string | number | boolean | undefined,
@ -9,14 +10,25 @@ export type PropertyChangeCallback = (
allProps: Map<string, string | boolean | number> allProps: Map<string, string | boolean | number>
) => void; ) => void;
export type layerChangeCallback = (
layersChangedByAction: Array<ITiledMapLayer>,
allLayersOnNewPosition: Array<ITiledMapLayer>,
) => void;
/** /**
* A wrapper around a ITiledMap interface to provide additional capabilities. * A wrapper around a ITiledMap interface to provide additional capabilities.
* It is used to handle layer properties. * It is used to handle layer properties.
*/ */
export class GameMap { 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 key: number | undefined;
private lastProperties = new Map<string, string | boolean | number>(); private lastProperties = new Map<string, string | boolean | number>();
private callbacks = new Map<string, Array<PropertyChangeCallback>>(); private propertiesChangeCallbacks = new Map<string, Array<PropertyChangeCallback>>();
private enterLayerCallbacks = Array<layerChangeCallback>();
private leaveLayerCallbacks = Array<layerChangeCallback>();
private tileNameMap = new Map<string, number>(); private tileNameMap = new Map<string, number>();
private tileSetPropertyMap: { [tile_index: number]: Array<ITiledMapProperty> } = {}; private tileSetPropertyMap: { [tile_index: number]: Array<ITiledMapProperty> } = {};
@ -68,22 +80,32 @@ export class GameMap {
return []; return [];
} }
private getLayersByKey(key: number): Array<ITiledMapLayer> {
return this.flatLayers.filter(flatLayer => flatLayer.type === 'tilelayer' && flatLayer.data[key] !== 0);
}
/** /**
* Sets the position of the current player (in pixels) * Sets the position of the current player (in pixels)
* This will trigger events if properties are changing. * This will trigger events if properties are changing.
*/ */
public setPosition(x: number, y: number) { public setPosition(x: number, y: number) {
this.oldKey = this.key;
const xMap = Math.floor(x / this.map.tilewidth); const xMap = Math.floor(x / this.map.tilewidth);
const yMap = Math.floor(y / this.map.tileheight); const yMap = Math.floor(y / this.map.tileheight);
const key = xMap + yMap * this.map.width; const key = xMap + yMap * this.map.width;
if (key === this.key) { if (key === this.key) {
return; return;
} }
this.key = key; this.key = key;
this.triggerAll();
this.triggerAllProperties();
this.triggerLayersChange();
} }
private triggerAll(): void { private triggerAllProperties(): void {
const newProps = this.getProperties(this.key ?? 0); const newProps = this.getProperties(this.key ?? 0);
const oldProps = this.lastProperties; const oldProps = this.lastProperties;
this.lastProperties = newProps; 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<string, string | boolean | number> { public getCurrentProperties(): Map<string, string | boolean | number> {
return this.lastProperties; return this.lastProperties;
} }
@ -167,7 +219,7 @@ export class GameMap {
newValue: string | number | boolean | undefined, newValue: string | number | boolean | undefined,
allProps: Map<string, string | boolean | number> allProps: Map<string, string | boolean | number>
) { ) {
const callbacksArray = this.callbacks.get(propName); const callbacksArray = this.propertiesChangeCallbacks.get(propName);
if (callbacksArray !== undefined) { if (callbacksArray !== undefined) {
for (const callback of callbacksArray) { for (const callback of callbacksArray) {
callback(newValue, oldValue, allProps); 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. * 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) { public onPropertyChange(propName: string, callback: PropertyChangeCallback) {
let callbacksArray = this.callbacks.get(propName); let callbacksArray = this.propertiesChangeCallbacks.get(propName);
if (callbacksArray === undefined) { if (callbacksArray === undefined) {
callbacksArray = new Array<PropertyChangeCallback>(); callbacksArray = new Array<PropertyChangeCallback>();
this.callbacks.set(propName, callbacksArray); this.propertiesChangeCallbacks.set(propName, callbacksArray);
} }
callbacksArray.push(callback); 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 { public findLayer(layerName: string): ITiledMapLayer | undefined {
return this.flatLayers.find((layer) => layer.name === layerName); return this.flatLayers.find((layer) => layer.name === layerName);
} }
@ -284,7 +350,8 @@ export class GameMap {
} }
property.value = propertyValue; property.value = propertyValue;
this.triggerAll(); this.triggerAllProperties();
this.triggerLayersChange();
} }
/** /**

View file

@ -1,15 +1,32 @@
import type { GameScene } from "./GameScene"; import type { GameScene } from "./GameScene";
import type { GameMap } from "./GameMap"; import type { GameMap } from "./GameMap";
import { scriptUtils } from "../../Api/ScriptUtils"; import { scriptUtils } from "../../Api/ScriptUtils";
import type { CoWebsite } from "../../WebRtc/CoWebsiteManager";
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager"; import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore"; import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore";
import { get } from 'svelte/store';
import { import {
ON_ACTION_TRIGGER_BUTTON, ON_ACTION_TRIGGER_BUTTON,
TRIGGER_WEBSITE_PROPERTIES, TRIGGER_WEBSITE_PROPERTIES,
WEBSITE_MESSAGE_PROPERTIES, WEBSITE_MESSAGE_PROPERTIES,
} from "../../WebRtc/LayoutManager"; } 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 { export class GameMapPropertiesListener {
private coWebsitesOpenByLayer = new Map<ITiledMapLayer, OpenCoWebsite>();
private coWebsitesActionTriggerByLayer = new Map<ITiledMapLayer, string>();
constructor(private scene: GameScene, private gameMap: GameMap) {} constructor(private scene: GameScene, private gameMap: GameMap) {}
register() { 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 = () => { const openWebsiteFunction = () => {
coWebsiteManager.loadCoWebsite( coWebsiteManager.loadCoWebsite(
newValue as string, openWebsiteProperty as string,
this.scene.MapUrlFile, this.scene.MapUrlFile,
allProps.get("openWebsiteAllowApi") as boolean | undefined, allowApiProperty,
allProps.get("openWebsitePolicy") as string | undefined, websitePolicyProperty,
allProps.get("openWebsiteWidth") as number | undefined 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) { if (websiteTriggerProperty && websiteTriggerProperty === ON_ACTION_TRIGGER_BUTTON) {
let message = allProps.get(WEBSITE_MESSAGE_PROPERTIES); if (!websiteTriggerMessageProperty) {
if (message === undefined) { websiteTriggerMessageProperty = "Press SPACE or touch here to open web site";
message = "Press SPACE or touch here to open web site";
} }
this.coWebsitesActionTriggerByLayer.set(layer, actionUuid);
layoutManagerActionStore.addAction({ layoutManagerActionStore.addAction({
uuid: "openWebsite", uuid: actionUuid,
type: "message", type: "message",
message: message, message: websiteTriggerMessageProperty,
callback: () => openWebsiteFunction(), callback: () => openWebsiteFunction(),
userInputManager: this.scene.userInputManager, userInputManager: this.scene.userInputManager,
}); });
} else { } else {
openWebsiteFunction(); 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(); handler();

View file

@ -186,6 +186,8 @@ export class GameScene extends DirtyScene {
moving: false, moving: false,
x: -1000, x: -1000,
y: -1000, y: -1000,
oldX: -1000,
oldY: -1000,
}; };
private gameMap!: GameMap; private gameMap!: GameMap;
@ -764,6 +766,19 @@ export class GameScene extends DirtyScene {
//init user position and play trigger to check layers properties //init user position and play trigger to check layers properties
this.gameMap.setPosition(this.CurrentPlayer.x, this.CurrentPlayer.y); 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)); audioManagerVisibilityStore.set(!(newValue === undefined));
}); });
// TODO: Legacy functionnality replace by layer change
this.gameMap.onPropertyChange("zone", (newValue, oldValue) => { this.gameMap.onPropertyChange("zone", (newValue, oldValue) => {
if (oldValue) { if (oldValue) {
iframeListener.sendLeaveEvent(oldValue as string); iframeListener.sendLeaveEvent(oldValue as string);
@ -1749,7 +1765,11 @@ ${escapedMessage}
const playerMovement = new PlayerMovement( const playerMovement = new PlayerMovement(
{ x: player.x, y: player.y }, { x: player.x, y: player.y },
this.currentTick, this.currentTick,
message.position, {
...message.position,
oldX: undefined,
oldY: undefined,
},
this.currentTick + POSITION_DELAY this.currentTick + POSITION_DELAY
); );
this.playersPositionInterpolator.updatePlayerPosition(player.userId, playerMovement); this.playersPositionInterpolator.updatePlayerPosition(player.userId, playerMovement);

View file

@ -38,6 +38,8 @@ export class PlayerMovement {
return { return {
x, x,
y, y,
oldX: this.startPosition.x,
oldY: this.startPosition.y,
direction: this.endPosition.direction, direction: this.endPosition.direction,
moving: true, moving: true,
}; };

View file

@ -64,14 +64,14 @@ export class Player extends Character {
if (x !== 0 || y !== 0) { if (x !== 0 || y !== 0) {
this.move(x, y); 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) { } else if (this.wasMoving && moving) {
// slow joystick movement // slow joystick movement
this.move(0, 0); 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) { } else if (this.wasMoving && !moving) {
this.stop(); 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) { if (direction !== null) {

View file

@ -9,7 +9,9 @@ export interface LayoutManagerAction {
userInputManager: UserInputManager | undefined; userInputManager: UserInputManager | undefined;
} }
function createLayoutManagerAction() { function createLayoutManagerAction() {
const { subscribe, set, update } = writable<LayoutManagerAction[]>([]); const { subscribe, set, update } = writable<LayoutManagerAction[]>([]);
return { return {

View file

@ -7,7 +7,12 @@ describe("Interpolation / Extrapolation", () => {
x: 100, y: 200 x: 100, y: 200
}, 42000, }, 42000,
{ {
x: 200, y: 100, moving: true, direction: "up" x: 200,
y: 100,
oldX: undefined,
oldY: undefined,
moving: true,
direction: "up"
}, },
42200 42200
); );
@ -19,6 +24,8 @@ describe("Interpolation / Extrapolation", () => {
expect(playerMovement.getPosition(42100)).toEqual({ expect(playerMovement.getPosition(42100)).toEqual({
x: 150, x: 150,
y: 150, y: 150,
oldX: undefined,
oldY: undefined,
direction: 'up', direction: 'up',
moving: true moving: true
}); });
@ -26,6 +33,8 @@ describe("Interpolation / Extrapolation", () => {
expect(playerMovement.getPosition(42200)).toEqual({ expect(playerMovement.getPosition(42200)).toEqual({
x: 200, x: 200,
y: 100, y: 100,
oldX: undefined,
oldY: undefined,
direction: 'up', direction: 'up',
moving: true moving: true
}); });
@ -33,6 +42,8 @@ describe("Interpolation / Extrapolation", () => {
expect(playerMovement.getPosition(42300)).toEqual({ expect(playerMovement.getPosition(42300)).toEqual({
x: 250, x: 250,
y: 50, y: 50,
oldX: undefined,
oldY: undefined,
direction: 'up', direction: 'up',
moving: true moving: true
}); });
@ -43,7 +54,12 @@ describe("Interpolation / Extrapolation", () => {
x: 100, y: 200 x: 100, y: 200
}, 42000, }, 42000,
{ {
x: 200, y: 100, moving: false, direction: "up" x: 200,
y: 100,
oldX: undefined,
oldY: undefined,
moving: false,
direction: "up"
}, },
42200 42200
); );
@ -51,6 +67,8 @@ describe("Interpolation / Extrapolation", () => {
expect(playerMovement.getPosition(42300)).toEqual({ expect(playerMovement.getPosition(42300)).toEqual({
x: 200, x: 200,
y: 100, y: 100,
oldX: undefined,
oldY: undefined,
direction: 'up', direction: 'up',
moving: false moving: false
}); });
@ -61,7 +79,12 @@ describe("Interpolation / Extrapolation", () => {
x: 100, y: 200 x: 100, y: 200
}, 42000, }, 42000,
{ {
x: 200, y: 100, moving: false, direction: "up" x: 200,
y: 100,
oldX: undefined,
oldY: undefined,
moving: false,
direction: "up"
}, },
42200 42200
); );
@ -69,6 +92,8 @@ describe("Interpolation / Extrapolation", () => {
expect(playerMovement.getPosition(42100)).toEqual({ expect(playerMovement.getPosition(42100)).toEqual({
x: 150, x: 150,
y: 150, y: 150,
oldX: undefined,
oldY: undefined,
direction: 'up', direction: 'up',
moving: true moving: true
}); });

View file

@ -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
}

View file

@ -0,0 +1,7 @@
WA.room.onEnterLayer('myLayer').subscribe(() => {
WA.chat.sendChatMessage("Hello!", 'Wooka');
});
WA.room.onLeaveLayer('myLayer').subscribe(() => {
WA.chat.sendChatMessage("Goodbye!", 'Wooka');
});

View file

@ -235,6 +235,14 @@
<a href="#" class="testLink" data-testmap="EmbeddedWebsite/website_in_map_script.json" target="_blank">Testing scripting API for websites inside a map</a> <a href="#" class="testLink" data-testmap="EmbeddedWebsite/website_in_map_script.json" target="_blank">Testing scripting API for websites inside a map</a>
</td> </td>
</tr> </tr>
<tr>
<td>
<input type="radio" name="test-change-layer-api"> Success <input type="radio" name="test-change-layer-api"> Failure <input type="radio" name="test-change-layer-api" checked> Pending
</td>
<td>
<a href="#" class="testLink" data-testmap="ChangeLayerApi/change_layer_api.json" target="_blank">Testing scripting API for enters/leaves layer</a>
</td>
</tr>
</table> </table>
<h2>CoWebsite</h2> <h2>CoWebsite</h2>
<table class="table"> <table class="table">
@ -243,7 +251,7 @@
<input type="radio" name="test-cowebsite-property"> Success <input type="radio" name="test-cowebsite-property"> Failure <input type="radio" name="test-cowebsite-property" checked> Pending <input type="radio" name="test-cowebsite-property"> Success <input type="radio" name="test-cowebsite-property"> Failure <input type="radio" name="test-cowebsite-property" checked> Pending
</td> </td>
<td> <td>
<a href="#" class="testLink" data-testmap="cowebsite_property.json" target="_blank">Open co-websites by map property</a> <a href="#" class="testLink" data-testmap="CoWebsite/cowebsite_property.json" target="_blank">Open co-websites by map property</a>
</td> </td>
</tr> </tr>
<tr> <tr>

View file

@ -231,12 +231,12 @@ export class SocketManager implements ZoneEventListener {
try { try {
client.viewport = viewport; client.viewport = viewport;
const world = this.rooms.get(client.roomId); const room = this.rooms.get(client.roomId);
if (!world) { if (!room) {
console.error("In SET_VIEWPORT, could not find world with id '", client.roomId, "'"); console.error("In SET_VIEWPORT, could not find world with id '", client.roomId, "'");
return; return;
} }
world.setViewport(client, client.viewport); room.setViewport(client, client.viewport);
} catch (e) { } catch (e) {
console.error('An error occurred on "SET_VIEWPORT" event'); console.error('An error occurred on "SET_VIEWPORT" event');
console.error(e); console.error(e);