From bdb32a29e184f0c3e2f40266fcbb23e6efa88451 Mon Sep 17 00:00:00 2001 From: GRL Date: Wed, 23 Jun 2021 11:32:11 +0200 Subject: [PATCH] New methods refactored --- CHANGELOG.md | 16 ++-- docs/maps/api-player.md | 21 +++++ docs/maps/api-reference.md | 118 +----------------------- docs/maps/api-room.md | 81 ++++++++++++++++ docs/maps/api-ui.md | 9 +- docs/maps/scripting.md | 2 +- front/src/Api/IframeListener.ts | 2 +- front/src/Api/iframe/player.ts | 29 ++++++ front/src/Api/iframe/room.ts | 91 +++++++++++++++++- front/src/Phaser/Game/GameScene.ts | 2 +- front/src/iframe_api.ts | 2 + maps/tests/Metadata/getCurrentRoom.html | 2 +- maps/tests/Metadata/getCurrentUser.html | 2 +- maps/tests/Metadata/playerMove.html | 2 +- maps/tests/Metadata/setProperty.html | 4 +- maps/tests/Metadata/showHideLayer.html | 4 +- 16 files changed, 246 insertions(+), 141 deletions(-) create mode 100644 docs/maps/api-player.md create mode 100644 front/src/Api/iframe/player.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index fa9d743e..fb4f6d27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ - Enabled outlines on actionable item again (they were disabled when migrating to Phaser 3.50) #1218 - Enabled outlines on player names (when the mouse hovers on a player you can interact with) #1219 - Migrated the admin console to Svelte, and redesigned the console #1211 +- New scripting API features : + - Use `WA.room.showLayer(): void` to show a layer + - Use `WA.room.hideLayer(): void` to hide a layer + - Use `WA.room.setProperty() : void` to add or change existing property of a layer + - Use `WA.player.onPlayerMove(): void` to track the movement of the current player + - Use `WA.room.getCurrentUser(): Promise` to get the ID, name and tags of the current player + - Use `WA.room.getCurrentRoom(): Promise` to get the ID, JSON map file, url of the map of the current room and the layer where the current player started + - Use `WA.ui.registerMenuCommand(): void` to add a custom menu ## Version 1.4.1 @@ -48,13 +56,7 @@ - Added a new `DISPLAY_TERMS_OF_USE` environment variable to trigger the display of terms of use - New scripting API features: - Use `WA.loadSound(): Sound` to load / play / stop a sound - - Use `WA.showLayer(): void` to show a layer - - Use `WA.hideLayer(): void` to hide a layer - - Use `WA.setProperty() : void` to add or change existing property of a layer - - Use `WA.onPlayerMove(): void` to track the movement of the current player - - Use `WA.getCurrentUser(): Promise` to get the ID, name and tags of the current player - - Use `WA.getCurrentRoom(): Promise` to get the ID, JSON map file, url of the map of the current room and the layer where the current player started - - Use `WA.registerMenuCommand(): void` to add a custom menu + ### Bug Fixes diff --git a/docs/maps/api-player.md b/docs/maps/api-player.md new file mode 100644 index 00000000..f483731e --- /dev/null +++ b/docs/maps/api-player.md @@ -0,0 +1,21 @@ +{.section-title.accent.text-primary} +# API Player functions Reference + +### Listen to player movement +``` +WA.player.onPlayerMove(callback: HasPlayerMovedEventCallback): void; +``` +Listens to the movement of the current user and calls the callback. Sends an event when the user stops moving, changes direction and every 200ms when moving in the same direction. + +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. + +**callback:** the function that will be called when the current player is moving. It contains the event. + +Example : +```javascript +WA.player.onPlayerMove(console.log); +``` \ No newline at end of file diff --git a/docs/maps/api-reference.md b/docs/maps/api-reference.md index a39ec1db..30a11b2a 100644 --- a/docs/maps/api-reference.md +++ b/docs/maps/api-reference.md @@ -4,123 +4,9 @@ - [Navigation functions](api-nav.md) - [Chat functions](api-chat.md) - [Room functions](api-room.md) +- [Player functions](api-player.md) - [UI functions](api-ui.md) - [Sound functions](api-sound.md) - [Controls functions](api-controls.md) -- [List of deprecated functions](api-deprecated.md) - -### Show / Hide a layer -``` -WA.showLayer(layerName : string): void -WA.hideLayer(layerName : string) : void -``` -These 2 methods can be used to show and hide a layer. - -Example : -```javascript -WA.showLayer('bottom'); -//... -WA.hideLayer('bottom'); -``` - -### Set/Create properties in a layer - -``` -WA.setProperty(layerName : string, propertyName : string, propertyValue : string | number | boolean | undefined) : void; -``` - -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`. - -Example : -```javascript -WA.setProperty('wikiLayer', 'openWebsite', 'https://www.wikipedia.org/'); -``` - -### Listen to player movement - -``` -onPlayerMove(callback: HasPlayerMovedEventCallback): void; -``` -Listens to the movement of the current user and calls the callback. Sends an event when the user stops moving, changes direction and every 200ms when moving in the same direction. - -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. - -**callback:** the function that will be called when the current player is moving. It contains the event. - -Example : -```javascript -WA.onPlayerMove(console.log); -``` - -### Getting informations on the current user -``` -getCurrentUser(): Promise -``` -Return a promise that resolves to a `User` object with the following attributes : -* **id (string) :** ID of the current user -* **nickName (string) :** name displayed above the current user -* **tags (string[]) :** list of all the tags of the current user - -Example : -```javascript -WA.getCurrentUser().then((user) => { - if (user.nickName === 'ABC') { - console.log(user.tags); - } -}) -``` - -### Getting informations on the current room -``` -getCurrentRoom(): Promise -``` -Return a promise that resolves to a `Room` object with the following attributes : -* **id (string) :** ID of the current room -* **map (ITiledMap) :** contains the JSON map file with the properties that were setted by the script if `setProperty` was called. -* **mapUrl (string) :** Url of the JSON map file -* **startLayer (string | null) :** Name of the layer where the current user started, only if different from `start` layer - -Example : -```javascript -WA.getCurrentRoom((room) => { - if (room.id === '42') { - console.log(room.map); - window.open(room.mapUrl, '_blank'); - } -}) -``` - -### Add a custom menu -``` -registerMenuCommand(commandDescriptor: string, callback: (commandDescriptor: string) => void): void -``` -Add a custom menu item containing the text `commandDescriptor`. A click on the menu will trigger the `callback`. - -Example : -```javascript -WA.registerMenuCommand('About', () => { - console.log("The About menu was clicked"); -}); -``` - - -### Working with group layers -If you use group layers in your map, to reference a layer in a group you will need to use a `/` to join layer names together. - -Example : -
-
- -
-
- -The name of the layers of this map are : -* `entries/start` -* `bottom/ground/under` -* `bottom/build/carpet` -* `wall` \ No newline at end of file +- [List of deprecated functions](api-deprecated.md) \ No newline at end of file diff --git a/docs/maps/api-room.md b/docs/maps/api-room.md index dc7a8612..d8381cc6 100644 --- a/docs/maps/api-room.md +++ b/docs/maps/api-room.md @@ -1,6 +1,22 @@ {.section-title.accent.text-primary} # API Room functions Reference +### Working with group layers +If you use group layers in your map, to reference a layer in a group you will need to use a `/` to join layer names together. + +Example : +
+
+ +
+
+ +The name of the layers of this map are : +* `entries/start` +* `bottom/ground/under` +* `bottom/build/carpet` +* `wall` + ### Detecting when the user enters/leaves a zone ``` @@ -31,3 +47,68 @@ WA.room.onLeaveZone('myZone', () => { WA.chat.sendChatMessage("Goodbye!", 'Mr Robot'); }) ``` + +### Show / Hide a layer +``` +WA.room.showLayer(layerName : string): void +WA.room.hideLayer(layerName : string) : void +``` +These 2 methods can be used to show and hide a layer. + +Example : +```javascript +WA.room.showLayer('bottom'); +//... +WA.room.hideLayer('bottom'); +``` + +### Set/Create properties in a layer + +``` +WA.room.setProperty(layerName : string, propertyName : string, propertyValue : string | number | boolean | undefined) : void; +``` + +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`. + +Example : +```javascript +WA.room.setProperty('wikiLayer', 'openWebsite', 'https://www.wikipedia.org/'); +``` + +### Getting information on the current room +``` +WA.room.getCurrentRoom(): Promise +``` +Return a promise that resolves to a `Room` object with the following attributes : +* **id (string) :** ID of the current room +* **map (ITiledMap) :** contains the JSON map file with the properties that were setted by the script if `setProperty` was called. +* **mapUrl (string) :** Url of the JSON map file +* **startLayer (string | null) :** Name of the layer where the current user started, only if different from `start` layer + +Example : +```javascript +WA.room.getCurrentRoom((room) => { + if (room.id === '42') { + console.log(room.map); + window.open(room.mapUrl, '_blank'); + } +}) +``` + +### Getting information on the current user +``` +WA.player.getCurrentUser(): Promise +``` +Return a promise that resolves to a `User` object with the following attributes : +* **id (string) :** ID of the current user +* **nickName (string) :** name displayed above the current user +* **tags (string[]) :** list of all the tags of the current user + +Example : +```javascript +WA.room.getCurrentUser().then((user) => { + if (user.nickName === 'ABC') { + console.log(user.tags); + } +}) +``` diff --git a/docs/maps/api-ui.md b/docs/maps/api-ui.md index 44248b53..fc38dc41 100644 --- a/docs/maps/api-ui.md +++ b/docs/maps/api-ui.md @@ -66,16 +66,15 @@ WA.room.onLeaveZone('myZone', () => { }); ``` -### register additional menu entries - -adds an additional Entry to the main menu , these exist until the map is unloaded - +### Add custom menu ```typescript WA.ui.registerMenuCommand(menuCommand: string, callback: (menuCommand: string) => void): void ``` -Example: +Add a custom menu item containing the text `commandDescriptor` in the main menu. A click on the menu will trigger the `callback`. +Custom menu exist only until the map is unloaded, or you leave the iframe zone of the script. +Example: ```javascript diff --git a/docs/maps/scripting.md b/docs/maps/scripting.md index b9dee484..5f645b81 100644 --- a/docs/maps/scripting.md +++ b/docs/maps/scripting.md @@ -101,7 +101,7 @@ You can now start by testing this with a simple message sent to the chat. ```html ... ... ``` diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index c4aa29b1..9311d7b6 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -204,7 +204,7 @@ class IframeListener { } - sendFrozenGameStateEvent(gameStateEvent: GameStateEvent) { + sendGameStateEvent(gameStateEvent: GameStateEvent) { this.postMessage({ 'type': 'gameState', 'data': gameStateEvent diff --git a/front/src/Api/iframe/player.ts b/front/src/Api/iframe/player.ts new file mode 100644 index 00000000..67c012f7 --- /dev/null +++ b/front/src/Api/iframe/player.ts @@ -0,0 +1,29 @@ +import {IframeApiContribution, sendToWorkadventure} from "./IframeApiContribution"; +import type {HasPlayerMovedEvent, HasPlayerMovedEventCallback} from "../Events/HasPlayerMovedEvent"; +import {Subject} from "rxjs"; +import {apiCallback} from "./registeredCallbacks"; +import {isHasPlayerMovedEvent} from "../Events/HasPlayerMovedEvent"; + +const moveStream = new Subject(); + +class WorkadventurePlayerCommands extends IframeApiContribution { + callbacks = [ + apiCallback({ + type: 'hasPlayerMoved', + typeChecker: isHasPlayerMovedEvent, + callback: (payloadData) => { + moveStream.next(payloadData); + } + }), + ] + + onPlayerMove(callback: HasPlayerMovedEventCallback): void { + moveStream.subscribe(callback); + sendToWorkadventure({ + type: 'onPlayerMove', + data: null + }) + } +} + +export default new WorkadventurePlayerCommands(); \ No newline at end of file diff --git a/front/src/Api/iframe/room.ts b/front/src/Api/iframe/room.ts index ed412166..ea990d90 100644 --- a/front/src/Api/iframe/room.ts +++ b/front/src/Api/iframe/room.ts @@ -1,10 +1,54 @@ import { Subject } from "rxjs"; import { EnterLeaveEvent, isEnterLeaveEvent } from '../Events/EnterLeaveEvent'; -import { IframeApiContribution } from './IframeApiContribution'; +import {IframeApiContribution, sendToWorkadventure} from './IframeApiContribution'; import { apiCallback } from "./registeredCallbacks"; +import type {LayerEvent} from "../Events/LayerEvent"; +import type {SetPropertyEvent} from "../Events/setPropertyEvent"; +import type {GameStateEvent} from "../Events/GameStateEvent"; +import type {ITiledMap} from "../../Phaser/Map/ITiledMap"; +import type {DataLayerEvent} from "../Events/DataLayerEvent"; +import {isGameStateEvent} from "../Events/GameStateEvent"; +import {isDataLayerEvent} from "../Events/DataLayerEvent"; const enterStreams: Map> = new Map>(); const leaveStreams: Map> = new Map>(); +const dataLayerResolver = new Subject(); +const stateResolvers = new Subject(); + +let immutableData: GameStateEvent; + +interface Room { + id: string, + mapUrl: string, + map: ITiledMap, + startLayer: string | null +} + +interface User { + id: string | undefined, + nickName: string | null, + tags: string[] +} + + +function getGameState(): Promise { + if (immutableData) { + return Promise.resolve(immutableData); + } + else { + return new Promise((resolver, thrower) => { + stateResolvers.subscribe(resolver); + sendToWorkadventure({type: "getState", data: null}); + }) + } +} + +function getDataLayer(): Promise { + return new Promise((resolver, thrower) => { + dataLayerResolver.subscribe(resolver); + sendToWorkadventure({type: "getDataLayer", data: null}) + }) +} class WorkadventureRoomCommands extends IframeApiContribution { callbacks = [ @@ -21,8 +65,21 @@ class WorkadventureRoomCommands extends IframeApiContribution { leaveStreams.get(payloadData.name)?.next(); } - }) - + }), + apiCallback({ + type: "gameState", + typeChecker: isGameStateEvent, + callback: (payloadData) => { + stateResolvers.next(payloadData); + } + }), + apiCallback({ + type: "dataLayer", + typeChecker: isDataLayerEvent, + callback: (payloadData) => { + dataLayerResolver.next(payloadData); + } + }), ] @@ -43,6 +100,34 @@ class WorkadventureRoomCommands extends IframeApiContribution { + return getGameState().then((gameState) => { + return getDataLayer().then((mapJson) => { + return {id: gameState.roomId, map: mapJson.data as ITiledMap, mapUrl: gameState.mapUrl, startLayer: gameState.startLayerName}; + }) + }) + } + getCurrentUser(): Promise { + return getGameState().then((gameState) => { + return {id: gameState.uuid, nickName: gameState.nickname, tags: gameState.tags}; + }) + } } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 8494503c..52d678ad 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -945,7 +945,7 @@ ${escapedMessage} })) this.iframeSubscriptionList.push(iframeListener.gameStateStream.subscribe(() => { - iframeListener.sendFrozenGameStateEvent({ + iframeListener.sendGameStateEvent({ mapUrl: this.MapUrlFile, startLayerName: this.startLayerName, uuid: localUserStore.getLocalUser()?.uuid, diff --git a/front/src/iframe_api.ts b/front/src/iframe_api.ts index 7b6b2db9..cc17fff2 100644 --- a/front/src/iframe_api.ts +++ b/front/src/iframe_api.ts @@ -12,6 +12,7 @@ import controls from "./Api/iframe/controls"; import ui from "./Api/iframe/ui"; import sound from "./Api/iframe/sound"; import room from "./Api/iframe/room"; +import player from "./Api/iframe/player"; import type { ButtonDescriptor } from "./Api/iframe/Ui/ButtonDescriptor"; import type { Popup } from "./Api/iframe/Ui/Popup"; import type { Sound } from "./Api/iframe/Sound/Sound"; @@ -23,6 +24,7 @@ const wa = { chat, sound, room, + player, // All methods below are deprecated and should not be used anymore. // They are kept here for backward compatibility. diff --git a/maps/tests/Metadata/getCurrentRoom.html b/maps/tests/Metadata/getCurrentRoom.html index b290c6a4..7429b2a8 100644 --- a/maps/tests/Metadata/getCurrentRoom.html +++ b/maps/tests/Metadata/getCurrentRoom.html @@ -5,7 +5,7 @@ \ No newline at end of file diff --git a/maps/tests/Metadata/setProperty.html b/maps/tests/Metadata/setProperty.html index 06b029da..5259ec0a 100644 --- a/maps/tests/Metadata/setProperty.html +++ b/maps/tests/Metadata/setProperty.html @@ -5,8 +5,8 @@ \ No newline at end of file diff --git a/maps/tests/Metadata/showHideLayer.html b/maps/tests/Metadata/showHideLayer.html index 391ec449..4677f9e5 100644 --- a/maps/tests/Metadata/showHideLayer.html +++ b/maps/tests/Metadata/showHideLayer.html @@ -10,10 +10,10 @@