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.
- 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)`.
- 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).

View file

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

View file

@ -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.
<div>
<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.
* **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
</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.
* **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<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
@ -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.

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,
x: tg.isNumber,
y: tg.isNumber,
oldX: tg.isOptional(tg.isNumber),
oldY: tg.isOptional(tg.isNumber),
})
.get();

View file

@ -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<T> 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;

View file

@ -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<T extends keyof IframeQueryMap> = (
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({

View file

@ -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<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 {
x: number;
y: number;
@ -47,8 +51,25 @@ export class WorkadventureRoomCommands extends IframeApiContribution<Workadventu
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 {
let subject = enterStreams.get(name);
if (subject === undefined) {
@ -57,6 +78,10 @@ export class WorkadventureRoomCommands extends IframeApiContribution<Workadventu
}
subject.subscribe(callback);
}
/**
* @deprecated Use onLeaveLayer instead
*/
onLeaveZone(name: string, callback: () => void): void {
let subject = leaveStreams.get(name);
if (subject === undefined) {
@ -65,12 +90,35 @@ export class WorkadventureRoomCommands extends IframeApiContribution<Workadventu
}
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 {
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<Workadventu
},
});
}
async getTiledMap(): Promise<ITiledMap> {
const event = await queryWorkadventure({ type: "getMapData", data: undefined });
return event.data as ITiledMap;
}
setTiles(tiles: TileDescriptor[]) {
sendToWorkadventure({
type: "setTiles",

View file

@ -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<string, string | boolean | number>
) => void;
export type layerChangeCallback = (
layersChangedByAction: Array<ITiledMapLayer>,
allLayersOnNewPosition: Array<ITiledMapLayer>,
) => 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<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 tileSetPropertyMap: { [tile_index: number]: Array<ITiledMapProperty> } = {};
@ -68,22 +80,32 @@ export class GameMap {
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)
* 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<string, string | boolean | number> {
return this.lastProperties;
}
@ -167,7 +219,7 @@ export class GameMap {
newValue: string | number | boolean | undefined,
allProps: Map<string, string | boolean | number>
) {
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<PropertyChangeCallback>();
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();
}
/**

View file

@ -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<ITiledMapLayer, OpenCoWebsite>();
private coWebsitesActionTriggerByLayer = new Map<ITiledMapLayer, string>();
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();

View file

@ -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);

View file

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

View file

@ -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) {

View file

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

View file

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

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>
</td>
</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>
<h2>CoWebsite</h2>
<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
</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>
</tr>
<tr>

View file

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