From e025c1dc8e8587fe13c403f63fe80e01a1135170 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Fri, 15 Oct 2021 17:01:38 +0200 Subject: [PATCH 01/26] Allows to read and write "Player properties" from LocalStorage --- front/src/Api/Events/IframeEvent.ts | 11 ++++++++++- front/src/Api/Events/PlayerPropertyEvent.ts | 13 +++++++++++++ front/src/Api/iframe/player.ts | 15 +++++++++++++++ front/src/Connexion/LocalUserStore.ts | 9 ++++++++- front/src/Phaser/Game/GameScene.ts | 13 +++++++++++++ 5 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 front/src/Api/Events/PlayerPropertyEvent.ts diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 2871b93c..6b57bb48 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -28,6 +28,7 @@ import type { MessageReferenceEvent } from "./ui/TriggerActionMessageEvent"; import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent"; import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent"; import type { ChangeLayerEvent } from "./ChangeLayerEvent"; +import { isPlayerPropertyEvent } from "./PlayerPropertyEvent"; import type { ChangeZoneEvent } from "./ChangeZoneEvent"; import { isColorEvent } from "./ColorEvent"; @@ -61,7 +62,7 @@ export type IframeEventMap = { registerMenu: MenuRegisterEvent; unregisterMenu: UnregisterMenuEvent; setTiles: SetTilesEvent; - modifyEmbeddedWebsite: Partial; // Note: name should be compulsory in fact + modifyEmbeddedWebsite: Partial; // Note: name should be compulsory in fact; }; export interface IframeEvent { type: T; @@ -153,6 +154,14 @@ export const iframeQueryMapTypeGuards = { query: isCreateEmbeddedWebsiteEvent, answer: tg.isUndefined, }, + getPlayerProperty: { + query: tg.isString, + answer: isPlayerPropertyEvent, + }, + setPlayerProperty: { + query: isPlayerPropertyEvent, + answer: tg.isUndefined, + }, setPlayerOutline: { query: isColorEvent, answer: tg.isUndefined, diff --git a/front/src/Api/Events/PlayerPropertyEvent.ts b/front/src/Api/Events/PlayerPropertyEvent.ts new file mode 100644 index 00000000..fe85d9ea --- /dev/null +++ b/front/src/Api/Events/PlayerPropertyEvent.ts @@ -0,0 +1,13 @@ +import * as tg from "generic-type-guard"; + +export const isPlayerPropertyEvent = new tg.IsInterface() + .withProperties({ + propertyName: tg.isString, + propertyValue: tg.isUnknown, + }) + .get(); + +/** + * A message sent from the iFrame to set player-related properties. + */ +export type PlayerPropertyEvent = tg.GuardedType; diff --git a/front/src/Api/iframe/player.ts b/front/src/Api/iframe/player.ts index 2d187bf5..d74c4aa3 100644 --- a/front/src/Api/iframe/player.ts +++ b/front/src/Api/iframe/player.ts @@ -3,6 +3,7 @@ import type { HasPlayerMovedEvent, HasPlayerMovedEventCallback } from "../Events import { Subject } from "rxjs"; import { apiCallback } from "./registeredCallbacks"; import { isHasPlayerMovedEvent } from "../Events/HasPlayerMovedEvent"; +import type { PlayerPropertyEvent } from "../Events/PlayerPropertyEvent"; const moveStream = new Subject(); @@ -100,6 +101,20 @@ export class WorkadventurePlayerCommands extends IframeApiContribution { + return queryWorkadventure({ + type: "getPlayerProperty", + data: name, + }); + } + + setPlayerProperty(property: PlayerPropertyEvent) { + queryWorkadventure({ + type: "setPlayerProperty", + data: property, + }).catch((e) => console.error(e)); + } } export default new WorkadventurePlayerCommands(); diff --git a/front/src/Connexion/LocalUserStore.ts b/front/src/Connexion/LocalUserStore.ts index 4dce6924..4e445aa8 100644 --- a/front/src/Connexion/LocalUserStore.ts +++ b/front/src/Connexion/LocalUserStore.ts @@ -22,8 +22,8 @@ const nonce = "nonce"; const notification = "notificationPermission"; const code = "code"; const cameraSetup = "cameraSetup"; - const cacheAPIIndex = "workavdenture-cache"; +const userProperties = "user-properties"; class LocalUserStore { saveUser(localUser: LocalUser) { @@ -220,6 +220,13 @@ class LocalUserStore { const cameraSetupValues = localStorage.getItem(cameraSetup); return cameraSetupValues != undefined ? JSON.parse(cameraSetupValues) : undefined; } + getUserProperty(name: string): string | null { + return localStorage.getItem(userProperties + "_" + name); + } + + setUserProperty(name: string, value: string): void { + localStorage.setItem(userProperties + "_" + name, value); + } } export const localUserStore = new LocalUserStore(); diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 4800e259..4bb08faa 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1223,6 +1223,19 @@ ${escapedMessage} }; }); + //TODO : move Player Properties related-code + iframeListener.registerAnswerer("setPlayerProperty", (event) => { + localUserStore.setUserProperty(event.propertyName, event.propertyValue as string); + }); + + iframeListener.registerAnswerer("getPlayerProperty", (event) => { + return { + propertyName: event, + propertyValue: localUserStore.getUserProperty(event), + }; + }); + //END TODO + iframeListener.registerAnswerer("getState", async () => { // The sharedVariablesManager is not instantiated before the connection is established. So we need to wait // for the connection to send back the answer. From 3490daed6b0c594fda0b3972bd1e508bcd51ab16 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Mon, 25 Oct 2021 14:43:36 +0200 Subject: [PATCH 02/26] Creates player state and uses it to get and set values from local storage --- front/src/Api/Events/GameStateEvent.ts | 1 + front/src/Api/Events/PlayerPropertyEvent.ts | 13 -- front/src/Api/Events/SetVariableEvent.ts | 1 + front/src/Api/iframe/player.ts | 4 +- front/src/Api/iframe/state.ts | 118 ++++++++++-------- front/src/Connexion/LocalUserStore.ts | 22 +++- front/src/Phaser/Game/GameScene.ts | 31 +++-- .../src/Phaser/Game/SharedVariablesManager.ts | 75 ++++++----- front/src/iframe_api.ts | 25 ++-- 9 files changed, 153 insertions(+), 137 deletions(-) delete mode 100644 front/src/Api/Events/PlayerPropertyEvent.ts diff --git a/front/src/Api/Events/GameStateEvent.ts b/front/src/Api/Events/GameStateEvent.ts index 1f0f36ed..9755ba9e 100644 --- a/front/src/Api/Events/GameStateEvent.ts +++ b/front/src/Api/Events/GameStateEvent.ts @@ -10,6 +10,7 @@ export const isGameStateEvent = new tg.IsInterface() tags: tg.isArray(tg.isString), variables: tg.isObject, userRoomToken: tg.isUnion(tg.isString, tg.isUndefined), + playerVariables: tg.isObject, }) .get(); /** diff --git a/front/src/Api/Events/PlayerPropertyEvent.ts b/front/src/Api/Events/PlayerPropertyEvent.ts deleted file mode 100644 index fe85d9ea..00000000 --- a/front/src/Api/Events/PlayerPropertyEvent.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as tg from "generic-type-guard"; - -export const isPlayerPropertyEvent = new tg.IsInterface() - .withProperties({ - propertyName: tg.isString, - propertyValue: tg.isUnknown, - }) - .get(); - -/** - * A message sent from the iFrame to set player-related properties. - */ -export type PlayerPropertyEvent = tg.GuardedType; diff --git a/front/src/Api/Events/SetVariableEvent.ts b/front/src/Api/Events/SetVariableEvent.ts index 3e2303b3..80ac6f6e 100644 --- a/front/src/Api/Events/SetVariableEvent.ts +++ b/front/src/Api/Events/SetVariableEvent.ts @@ -4,6 +4,7 @@ export const isSetVariableEvent = new tg.IsInterface() .withProperties({ key: tg.isString, value: tg.isUnknown, + target: tg.isSingletonStringUnion("global", "player"), }) .get(); /** diff --git a/front/src/Api/iframe/player.ts b/front/src/Api/iframe/player.ts index d74c4aa3..ce865642 100644 --- a/front/src/Api/iframe/player.ts +++ b/front/src/Api/iframe/player.ts @@ -3,7 +3,7 @@ import type { HasPlayerMovedEvent, HasPlayerMovedEventCallback } from "../Events import { Subject } from "rxjs"; import { apiCallback } from "./registeredCallbacks"; import { isHasPlayerMovedEvent } from "../Events/HasPlayerMovedEvent"; -import type { PlayerPropertyEvent } from "../Events/PlayerPropertyEvent"; +import { createState } from "./state"; const moveStream = new Subject(); @@ -32,6 +32,8 @@ export const setUuid = (_uuid: string | undefined) => { }; export class WorkadventurePlayerCommands extends IframeApiContribution { + readonly state = createState("player"); + callbacks = [ apiCallback({ type: "hasPlayerMoved", diff --git a/front/src/Api/iframe/state.ts b/front/src/Api/iframe/state.ts index a875f3e0..7021b251 100644 --- a/front/src/Api/iframe/state.ts +++ b/front/src/Api/iframe/state.ts @@ -8,93 +8,101 @@ import { isSetVariableEvent, SetVariableEvent } from "../Events/SetVariableEvent import type { ITiledMap } from "../../Phaser/Map/ITiledMap"; -const setVariableResolvers = new Subject(); -const variables = new Map(); -const variableSubscribers = new Map>(); - -export const initVariables = (_variables: Map): void => { - for (const [name, value] of _variables.entries()) { - // In case the user already decided to put values in the variables (before onInit), let's make sure onInit does not override this. - if (!variables.has(name)) { - variables.set(name, value); - } - } -}; - -setVariableResolvers.subscribe((event) => { - const oldValue = variables.get(event.key); - // If we are setting the same value, no need to do anything. - // No need to do this check since it is already performed in SharedVariablesManager - /*if (JSON.stringify(oldValue) === JSON.stringify(event.value)) { - return; - }*/ - - variables.set(event.key, event.value); - const subject = variableSubscribers.get(event.key); - if (subject !== undefined) { - subject.next(event.value); - } -}); - export class WorkadventureStateCommands extends IframeApiContribution { + private setVariableResolvers = new Subject(); + private variables = new Map(); + private variableSubscribers = new Map>(); + + constructor(private target: "global" | "player") { + super(); + + this.setVariableResolvers.subscribe((event) => { + const oldValue = this.variables.get(event.key); + // If we are setting the same value, no need to do anything. + // No need to do this check since it is already performed in SharedVariablesManager + /*if (JSON.stringify(oldValue) === JSON.stringify(event.value)) { + return; + }*/ + + this.variables.set(event.key, event.value); + const subject = this.variableSubscribers.get(event.key); + if (subject !== undefined) { + subject.next(event.value); + } + }); + } + callbacks = [ apiCallback({ type: "setVariable", typeChecker: isSetVariableEvent, callback: (payloadData) => { - setVariableResolvers.next(payloadData); + if (payloadData.target === this.target) { + this.setVariableResolvers.next(payloadData); + } }, }), ]; + // TODO: see how we can remove this method from types exposed to WA.state object + initVariables(_variables: Map): void { + for (const [name, value] of _variables.entries()) { + // In case the user already decided to put values in the variables (before onInit), let's make sure onInit does not override this. + if (!this.variables.has(name)) { + this.variables.set(name, value); + } + } + } + saveVariable(key: string, value: unknown): Promise { - variables.set(key, value); + this.variables.set(key, value); return queryWorkadventure({ type: "setVariable", data: { key, value, + target: this.target, }, }); } loadVariable(key: string): unknown { - return variables.get(key); + return this.variables.get(key); } hasVariable(key: string): boolean { - return variables.has(key); + return this.variables.has(key); } onVariableChange(key: string): Observable { - let subject = variableSubscribers.get(key); + let subject = this.variableSubscribers.get(key); if (subject === undefined) { subject = new Subject(); - variableSubscribers.set(key, subject); + this.variableSubscribers.set(key, subject); } return subject.asObservable(); } } -const proxyCommand = new Proxy(new WorkadventureStateCommands(), { - get(target: WorkadventureStateCommands, p: PropertyKey, receiver: unknown): unknown { - if (p in target) { - return Reflect.get(target, p, receiver); - } - return target.loadVariable(p.toString()); - }, - set(target: WorkadventureStateCommands, p: PropertyKey, value: unknown, receiver: unknown): boolean { - // Note: when using "set", there is no way to wait, so we ignore the return of the promise. - // User must use WA.state.saveVariable to have error message. - target.saveVariable(p.toString(), value); - return true; - }, - has(target: WorkadventureStateCommands, p: PropertyKey): boolean { - if (p in target) { +export function createState(target: "global" | "player"): WorkadventureStateCommands & { [key: string]: unknown } { + return new Proxy(new WorkadventureStateCommands(target), { + get(target: WorkadventureStateCommands, p: PropertyKey, receiver: unknown): unknown { + if (p in target) { + return Reflect.get(target, p, receiver); + } + return target.loadVariable(p.toString()); + }, + set(target: WorkadventureStateCommands, p: PropertyKey, value: unknown, receiver: unknown): boolean { + // Note: when using "set", there is no way to wait, so we ignore the return of the promise. + // User must use WA.state.saveVariable to have error message. + target.saveVariable(p.toString(), value); return true; - } - return target.hasVariable(p.toString()); - }, -}) as WorkadventureStateCommands & { [key: string]: unknown }; - -export default proxyCommand; + }, + has(target: WorkadventureStateCommands, p: PropertyKey): boolean { + if (p in target) { + return true; + } + return target.hasVariable(p.toString()); + }, + }) as WorkadventureStateCommands & { [key: string]: unknown }; +} diff --git a/front/src/Connexion/LocalUserStore.ts b/front/src/Connexion/LocalUserStore.ts index 4e445aa8..4f03a546 100644 --- a/front/src/Connexion/LocalUserStore.ts +++ b/front/src/Connexion/LocalUserStore.ts @@ -220,12 +220,26 @@ class LocalUserStore { const cameraSetupValues = localStorage.getItem(cameraSetup); return cameraSetupValues != undefined ? JSON.parse(cameraSetupValues) : undefined; } - getUserProperty(name: string): string | null { - return localStorage.getItem(userProperties + "_" + name); + + getAllUserProperties(): Map { + const result = new Map(); + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key) { + if (key.startsWith(userProperties + "_")) { + const value = localStorage.getItem(key); + if (value) { + const userKey = key.substr((userProperties + "_").length); + result.set(userKey, JSON.parse(value)); + } + } + } + } + return result; } - setUserProperty(name: string, value: string): void { - localStorage.setItem(userProperties + "_" + name, value); + setUserProperty(name: string, value: unknown): void { + localStorage.setItem(userProperties + "_" + name, JSON.stringify(value)); } } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 4bb08faa..9fbb255b 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1223,19 +1223,6 @@ ${escapedMessage} }; }); - //TODO : move Player Properties related-code - iframeListener.registerAnswerer("setPlayerProperty", (event) => { - localUserStore.setUserProperty(event.propertyName, event.propertyValue as string); - }); - - iframeListener.registerAnswerer("getPlayerProperty", (event) => { - return { - propertyName: event, - propertyValue: localUserStore.getUserProperty(event), - }; - }); - //END TODO - iframeListener.registerAnswerer("getState", async () => { // The sharedVariablesManager is not instantiated before the connection is established. So we need to wait // for the connection to send back the answer. @@ -1248,6 +1235,7 @@ ${escapedMessage} roomId: this.roomUrl, tags: this.connection ? this.connection.getAllTags() : [], variables: this.sharedVariablesManager.variables, + playerVariables: localUserStore.getAllUserProperties(), userRoomToken: this.connection ? this.connection.userRoomToken : "", }; }); @@ -1338,6 +1326,22 @@ ${escapedMessage} }) ); + iframeListener.registerAnswerer("setVariable", (event, source) => { + switch (event.target) { + case "global": { + this.sharedVariablesManager.setVariable(event, source); + break; + } + case "player": { + localUserStore.setUserProperty(event.key, event.value); + break; + } + default: { + const _exhaustiveCheck: never = event.target; + } + } + }); + iframeListener.registerAnswerer("removeActionMessage", (message) => { layoutManagerActionStore.removeAction(message.uuid); }); @@ -1480,6 +1484,7 @@ ${escapedMessage} iframeListener.unregisterAnswerer("openCoWebsite"); iframeListener.unregisterAnswerer("getCoWebsites"); iframeListener.unregisterAnswerer("setPlayerOutline"); + iframeListener.unregisterAnswerer("setVariable"); this.sharedVariablesManager?.close(); this.embeddedWebsiteManager?.close(); diff --git a/front/src/Phaser/Game/SharedVariablesManager.ts b/front/src/Phaser/Game/SharedVariablesManager.ts index 5b5867dc..0619b6cc 100644 --- a/front/src/Phaser/Game/SharedVariablesManager.ts +++ b/front/src/Phaser/Game/SharedVariablesManager.ts @@ -3,6 +3,7 @@ import { iframeListener } from "../../Api/IframeListener"; import type { GameMap } from "./GameMap"; import type { ITiledMapLayer, ITiledMapObject } from "../Map/ITiledMap"; import { GameMapProperties } from "./GameMapProperties"; +import type { SetVariableEvent } from "../../Api/Events/SetVariableEvent"; interface Variable { defaultValue: unknown; @@ -48,51 +49,51 @@ export class SharedVariablesManager { iframeListener.setVariable({ key: name, value: value, + target: "global", }); }); + } - // When a variable is modified from an iFrame - iframeListener.registerAnswerer("setVariable", (event, source) => { - const key = event.key; + public setVariable(event: SetVariableEvent, source: MessageEventSource | null): void { + const key = event.key; - const object = this.variableObjects.get(key); + const object = this.variableObjects.get(key); - if (object === undefined) { - const errMsg = - 'A script is trying to modify variable "' + - key + - '" but this variable is not defined in the map.' + - 'There should be an object in the map whose name is "' + - key + - '" and whose type is "variable"'; - console.error(errMsg); - throw new Error(errMsg); - } + if (object === undefined) { + const errMsg = + 'A script is trying to modify variable "' + + key + + '" but this variable is not defined in the map.' + + 'There should be an object in the map whose name is "' + + key + + '" and whose type is "variable"'; + console.error(errMsg); + throw new Error(errMsg); + } - if (object.writableBy && !this.roomConnection.hasTag(object.writableBy)) { - const errMsg = - 'A script is trying to modify variable "' + - key + - '" but this variable is only writable for users with tag "' + - object.writableBy + - '".'; - console.error(errMsg); - throw new Error(errMsg); - } + if (object.writableBy && !this.roomConnection.hasTag(object.writableBy)) { + const errMsg = + 'A script is trying to modify variable "' + + key + + '" but this variable is only writable for users with tag "' + + object.writableBy + + '".'; + console.error(errMsg); + throw new Error(errMsg); + } - // Let's stop any propagation of the value we set is the same as the existing value. - if (JSON.stringify(event.value) === JSON.stringify(this._variables.get(key))) { - return; - } + // Let's stop any propagation of the value we set is the same as the existing value. + if (JSON.stringify(event.value) === JSON.stringify(this._variables.get(key))) { + return; + } - this._variables.set(key, event.value); + this._variables.set(key, event.value); - // Dispatch to the room connection. - this.roomConnection.emitSetVariableEvent(key, event.value); + // Dispatch to the room connection. + this.roomConnection.emitSetVariableEvent(key, event.value); - // Dispatch to other iframes - iframeListener.dispatchVariableToOtherIframes(key, event.value, source); - }); + // Dispatch to other iframes + iframeListener.dispatchVariableToOtherIframes(key, event.value, source); } private static findVariablesInMap(gameMap: GameMap): Map { @@ -164,10 +165,6 @@ export class SharedVariablesManager { return variable; } - public close(): void { - iframeListener.unregisterAnswerer("setVariable"); - } - get variables(): Map { return this._variables; } diff --git a/front/src/iframe_api.ts b/front/src/iframe_api.ts index 93415b0d..f33bc8d7 100644 --- a/front/src/iframe_api.ts +++ b/front/src/iframe_api.ts @@ -14,25 +14,28 @@ import controls from "./Api/iframe/controls"; import ui from "./Api/iframe/ui"; import sound from "./Api/iframe/sound"; import room, { setMapURL, setRoomId } from "./Api/iframe/room"; -import state, { initVariables } from "./Api/iframe/state"; +import { createState } from "./Api/iframe/state"; import player, { setPlayerName, setTags, setUserRoomToken, setUuid } 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"; import { answerPromises, queryWorkadventure } from "./Api/iframe/IframeApiContribution"; +const globalState = createState("global"); + // Notify WorkAdventure that we are ready to receive data const initPromise = queryWorkadventure({ type: "getState", data: undefined, -}).then((state) => { - setPlayerName(state.nickname); - setRoomId(state.roomId); - setMapURL(state.mapUrl); - setTags(state.tags); - setUuid(state.uuid); - initVariables(state.variables as Map); - setUserRoomToken(state.userRoomToken); +}).then((gameState) => { + setPlayerName(gameState.nickname); + setRoomId(gameState.roomId); + setMapURL(gameState.mapUrl); + setTags(gameState.tags); + setUuid(gameState.uuid); + globalState.initVariables(gameState.variables as Map); + player.state.initVariables(gameState.playerVariables as Map); + setUserRoomToken(gameState.userRoomToken); }); const wa = { @@ -43,7 +46,7 @@ const wa = { sound, room, player, - state, + state: globalState, onInit(): Promise { return initPromise; @@ -225,7 +228,5 @@ window.addEventListener( callback?.callback(payloadData); } } - - // ... } ); From e39e341ab704d6612c3ff7896706d7c495c2a665 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Mon, 25 Oct 2021 17:23:02 +0200 Subject: [PATCH 03/26] Adds documentation on player properties --- docs/maps/api-player.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/maps/api-player.md b/docs/maps/api-player.md index 35d5f464..904a3000 100644 --- a/docs/maps/api-player.md +++ b/docs/maps/api-player.md @@ -107,6 +107,27 @@ Example : WA.player.onPlayerMove(console.log); ``` +## Player specific properties +Similarly to maps (see [API state related functions](api-state.md)), it is possible to store data **related to a specific player** in a "state". Such data will be stored using the local storage from the user's browser. + +Any value that is serializable in JSON can be stored. + +### Setting a property +A player property can be set simply by assigning a value. + +Example: +```javascript +WA.player.state.toto = "value" //will set the "toto" key to "value" +``` + +### Reading a property +A player property can be read by calling its key from the player's state. + +Example: +```javascript +WA.player.state.toto //will retrieve the property +``` + ### Set the outline color of the player ``` WA.player.setOutlineColor(red: number, green: number, blue: number): Promise; From 516d756db131e4e31492826499ef237aa3f5aefe Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Fri, 29 Oct 2021 12:01:07 +0200 Subject: [PATCH 04/26] Uses the current player position rather than the starting one to position iframe --- front/src/Api/Events/IframeEvent.ts | 5 +++++ front/src/Api/Events/PlayerPosition.ts | 10 ++++++++++ front/src/Api/iframe/player.ts | 13 +++++++++++++ front/src/Phaser/Game/GameScene.ts | 8 ++++++++ 4 files changed, 36 insertions(+) create mode 100644 front/src/Api/Events/PlayerPosition.ts diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 6b57bb48..e7031894 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -31,6 +31,7 @@ import type { ChangeLayerEvent } from "./ChangeLayerEvent"; import { isPlayerPropertyEvent } from "./PlayerPropertyEvent"; import type { ChangeZoneEvent } from "./ChangeZoneEvent"; import { isColorEvent } from "./ColorEvent"; +import { isPlayerPosition } from "./PlayerPosition"; export interface TypedMessageEvent extends MessageEvent { data: T; @@ -170,6 +171,10 @@ export const iframeQueryMapTypeGuards = { query: tg.isUndefined, answer: tg.isUndefined, }, + getPlayerPosition: { + query: tg.isUndefined, + answer: isPlayerPosition, + }, }; type GuardedType = T extends (x: unknown) => x is infer T ? T : never; diff --git a/front/src/Api/Events/PlayerPosition.ts b/front/src/Api/Events/PlayerPosition.ts new file mode 100644 index 00000000..54fac6fe --- /dev/null +++ b/front/src/Api/Events/PlayerPosition.ts @@ -0,0 +1,10 @@ +import * as tg from "generic-type-guard"; + +export const isPlayerPosition = new tg.IsInterface() + .withProperties({ + x: tg.isNumber, + y: tg.isNumber, + }) + .get(); + +export type PlayerPosition = tg.GuardedType; diff --git a/front/src/Api/iframe/player.ts b/front/src/Api/iframe/player.ts index ce865642..001ceb18 100644 --- a/front/src/Api/iframe/player.ts +++ b/front/src/Api/iframe/player.ts @@ -77,6 +77,13 @@ export class WorkadventurePlayerCommands extends IframeApiContribution { + return await queryWorkadventure({ + type: "getPlayerPosition", + data: undefined, + }); + } + get userRoomToken(): string | undefined { if (userRoomToken === undefined) { throw new Error( @@ -119,4 +126,10 @@ export class WorkadventurePlayerCommands extends IframeApiContribution { + return { + x: this.CurrentPlayer.x, + y: this.CurrentPlayer.y, + }; + }); } private setPropertyLayer( From 8bc75e1ccbf2a364fd60f29f0b7785967ae6b41f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dicte=20Q?= <37311765+HimeShaman@users.noreply.github.com> Date: Fri, 29 Oct 2021 18:34:54 +0200 Subject: [PATCH 05/26] Update docs/maps/api-player.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Négrier --- docs/maps/api-player.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/maps/api-player.md b/docs/maps/api-player.md index 904a3000..ef91ee42 100644 --- a/docs/maps/api-player.md +++ b/docs/maps/api-player.md @@ -110,6 +110,9 @@ WA.player.onPlayerMove(console.log); ## Player specific properties Similarly to maps (see [API state related functions](api-state.md)), it is possible to store data **related to a specific player** in a "state". Such data will be stored using the local storage from the user's browser. +{.alert.alert-info} +In the future, player-related variables will be stored on the WorkAdventure server if the current player is logged. + Any value that is serializable in JSON can be stored. ### Setting a property From cbf7cdfe29c6aaa60958658ac40ae230fd68d554 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Fri, 29 Oct 2021 18:53:55 +0200 Subject: [PATCH 06/26] Cleans forgotten useless commentaries --- front/src/Api/iframe/player.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/front/src/Api/iframe/player.ts b/front/src/Api/iframe/player.ts index 001ceb18..2a642d75 100644 --- a/front/src/Api/iframe/player.ts +++ b/front/src/Api/iframe/player.ts @@ -126,8 +126,7 @@ export class WorkadventurePlayerCommands extends IframeApiContribution Date: Fri, 29 Oct 2021 18:56:04 +0200 Subject: [PATCH 07/26] Updates documentation --- docs/maps/api-player.md | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/docs/maps/api-player.md b/docs/maps/api-player.md index ef91ee42..4375dca6 100644 --- a/docs/maps/api-player.md +++ b/docs/maps/api-player.md @@ -86,6 +86,22 @@ WA.onInit().then(() => { }) ``` +### Get the position of the player +``` +WA.player.getPosition(): Promise +``` +The player's current position is available using the `WA.player.getPosition()` function. + +{.alert.alert-info} +You need to wait for the end of the initialization before calling `WA.player.getPosition()` + +```typescript +WA.onInit().then(() => { + console.log('Tags: ', WA.player.getPosition()); +}) +``` + + ### Listen to player movement ``` WA.player.onPlayerMove(callback: HasPlayerMovedEventCallback): void; @@ -107,8 +123,8 @@ Example : WA.player.onPlayerMove(console.log); ``` -## Player specific properties -Similarly to maps (see [API state related functions](api-state.md)), it is possible to store data **related to a specific player** in a "state". Such data will be stored using the local storage from the user's browser. +## Player specific variables +Similarly to maps (see [API state related functions](api-state.md)), it is possible to store data **related to a specific player** in a "state". Such data will be stored using the local storage from the user's browser. Any value that is serializable in JSON can be stored. {.alert.alert-info} In the future, player-related variables will be stored on the WorkAdventure server if the current player is logged. @@ -123,12 +139,12 @@ Example: WA.player.state.toto = "value" //will set the "toto" key to "value" ``` -### Reading a property -A player property can be read by calling its key from the player's state. +### Reading a variable +A player variable can be read by calling its key from the player's state. Example: ```javascript -WA.player.state.toto //will retrieve the property +WA.player.state.toto //will retrieve the variable ``` ### Set the outline color of the player From c53f0c6c8cf95d20051ef5e6f0c009ee93a331ad Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Fri, 29 Oct 2021 19:27:14 +0200 Subject: [PATCH 08/26] Fixes deleting SharedVariablesManager's close() function --- front/src/Phaser/Game/SharedVariablesManager.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/front/src/Phaser/Game/SharedVariablesManager.ts b/front/src/Phaser/Game/SharedVariablesManager.ts index 0619b6cc..72149473 100644 --- a/front/src/Phaser/Game/SharedVariablesManager.ts +++ b/front/src/Phaser/Game/SharedVariablesManager.ts @@ -165,6 +165,10 @@ export class SharedVariablesManager { return variable; } + public close(): void { + iframeListener.unregisterAnswerer("setVariable"); + } + get variables(): Map { return this._variables; } From d672a2dead4d87b94f776112312b8bc809eb8ec7 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Wed, 10 Nov 2021 19:04:06 +0100 Subject: [PATCH 09/26] WIP API updates for tutorial and more --- front/src/Api/Events/EmbeddedWebsiteEvent.ts | 4 ++ front/src/Api/Events/IframeEvent.ts | 1 + front/src/Api/iframe/Room/EmbeddedWebsite.ts | 43 ++++++++++++++ front/src/Api/iframe/website.ts | 4 -- .../src/Phaser/Game/EmbeddedWebsiteManager.ts | 58 +++++++++++++++---- front/src/Phaser/Game/GameScene.ts | 4 +- .../EmbeddedWebsite/website_in_map_script.php | 16 +++++ 7 files changed, 115 insertions(+), 15 deletions(-) diff --git a/front/src/Api/Events/EmbeddedWebsiteEvent.ts b/front/src/Api/Events/EmbeddedWebsiteEvent.ts index 42630be1..57c24853 100644 --- a/front/src/Api/Events/EmbeddedWebsiteEvent.ts +++ b/front/src/Api/Events/EmbeddedWebsiteEvent.ts @@ -22,6 +22,8 @@ export const isEmbeddedWebsiteEvent = new tg.IsInterface() y: tg.isNumber, width: tg.isNumber, height: tg.isNumber, + origin: tg.isSingletonStringUnion("player", "map"), + scale: tg.isNumber, }) .get(); @@ -35,6 +37,8 @@ export const isCreateEmbeddedWebsiteEvent = new tg.IsInterface() visible: tg.isBoolean, allowApi: tg.isBoolean, allow: tg.isString, + origin: tg.isSingletonStringUnion("player", "map"), + scale: tg.isNumber, }) .get(); diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index e7031894..2744eaa9 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -215,6 +215,7 @@ export const isIframeQuery = (event: any): event is IframeQuery { console.log('CREATING NEW EMBEDDED IFRAME'); @@ -28,6 +30,8 @@ height: parseInt(heightField.value), }, visible: !!visibleField.value, + origin: originField.value, + scale: parseFloat(scaleField.value), }); }); @@ -61,6 +65,16 @@ const website = await WA.room.website.get('test'); website.visible = this.checked; }); + + originField.addEventListener('change', async function() { + const website = await WA.room.website.get('test'); + website.origin = this.value; + }); + + scaleField.addEventListener('change', async function() { + const website = await WA.room.website.get('test'); + website.scale = parseFloat(this.value); + }); }); }) @@ -72,6 +86,8 @@ width:
height:
URL:
Visible:
+Origin:
+Scale:
From 435676773990fdf31cc8c2cbf655937fd49fe11c Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Tue, 23 Nov 2021 16:51:39 +0100 Subject: [PATCH 10/26] Adds the camera to available APIs with retrieving of the worldView --- docs/maps/api-player.md | 2 +- front/src/Api/Events/HasCameraMovedEvent.ts | 18 ++++++++++++ front/src/Api/Events/IframeEvent.ts | 3 ++ front/src/Api/IframeListener.ts | 13 +++++++++ front/src/Api/iframe/Room/EmbeddedWebsite.ts | 6 ++-- front/src/Api/iframe/camera.ts | 29 +++++++++++++++++++ .../src/Phaser/Game/EmbeddedWebsiteManager.ts | 7 +++-- front/src/Phaser/Game/GameScene.ts | 13 +++++++++ front/src/iframe_api.ts | 2 ++ 9 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 front/src/Api/Events/HasCameraMovedEvent.ts create mode 100644 front/src/Api/iframe/camera.ts diff --git a/docs/maps/api-player.md b/docs/maps/api-player.md index 4375dca6..ad08448b 100644 --- a/docs/maps/api-player.md +++ b/docs/maps/api-player.md @@ -97,7 +97,7 @@ You need to wait for the end of the initialization before calling `WA.player.get ```typescript WA.onInit().then(() => { - console.log('Tags: ', WA.player.getPosition()); + console.log('Position: ', WA.player.getPosition()); }) ``` diff --git a/front/src/Api/Events/HasCameraMovedEvent.ts b/front/src/Api/Events/HasCameraMovedEvent.ts new file mode 100644 index 00000000..23f85385 --- /dev/null +++ b/front/src/Api/Events/HasCameraMovedEvent.ts @@ -0,0 +1,18 @@ +import * as tg from "generic-type-guard"; + +export const isHasCameraMovedEvent = new tg.IsInterface() + .withProperties({ + x: tg.isNumber, + y: tg.isNumber, + width: tg.isNumber, + height: tg.isNumber, + }) + .get(); + +/** + * A message sent from the game to the iFrame to notify a movement from the camera. + */ + +export type HasCameraMovedEvent = tg.GuardedType; + +export type HasCameraMovedEventCallback = (event: HasCameraMovedEvent) => void; diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 2744eaa9..5b1c0f02 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -32,6 +32,7 @@ import { isPlayerPropertyEvent } from "./PlayerPropertyEvent"; import type { ChangeZoneEvent } from "./ChangeZoneEvent"; import { isColorEvent } from "./ColorEvent"; import { isPlayerPosition } from "./PlayerPosition"; +import type { HasCameraMovedEvent } from "./HasCameraMovedEvent"; export interface TypedMessageEvent extends MessageEvent { data: T; @@ -52,6 +53,7 @@ export type IframeEventMap = { displayBubble: null; removeBubble: null; onPlayerMove: undefined; + onCameraMove: undefined; showLayer: LayerEvent; hideLayer: LayerEvent; setProperty: SetPropertyEvent; @@ -84,6 +86,7 @@ export interface IframeResponseEventMap { leaveZoneEvent: ChangeZoneEvent; buttonClickedEvent: ButtonClickedEvent; hasPlayerMoved: HasPlayerMovedEvent; + hasCameraMoved: HasCameraMovedEvent; menuItemClicked: MenuItemClickedEvent; setVariable: SetVariableEvent; messageTriggered: MessageReferenceEvent; diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 67b49344..093cacc5 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -31,6 +31,7 @@ import type { SetVariableEvent } from "./Events/SetVariableEvent"; import { ModifyEmbeddedWebsiteEvent, isEmbeddedWebsiteEvent } from "./Events/EmbeddedWebsiteEvent"; import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Stores/MenuStore"; import type { ChangeLayerEvent } from "./Events/ChangeLayerEvent"; +import type { HasCameraMovedEvent } from "./Events/HasCameraMovedEvent"; import type { ChangeZoneEvent } from "./Events/ChangeZoneEvent"; type AnswererCallback = ( @@ -95,6 +96,7 @@ class IframeListener { private readonly iframeCloseCallbacks = new Map void)[]>(); private readonly scripts = new Map(); private sendPlayerMove: boolean = false; + private sendCameraMove: boolean = false; // Note: we are forced to type this in unknown and later cast with "as" because of https://github.com/microsoft/TypeScript/issues/31904 private answerers: { @@ -226,6 +228,8 @@ class IframeListener { this._removeBubbleStream.next(); } else if (payload.type == "onPlayerMove") { this.sendPlayerMove = true; + } else if (payload.type == "onCameraMove") { + this.sendCameraMove = true; } else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) { this._setTilesStream.next(payload.data); } else if (payload.type == "modifyEmbeddedWebsite" && isEmbeddedWebsiteEvent(payload.data)) { @@ -442,6 +446,15 @@ class IframeListener { } } + hasCameraMoved(event: HasCameraMovedEvent) { + if (this.sendCameraMove) { + this.postMessage({ + type: "hasCameraMoved", + data: event, + }); + } + } + sendButtonClickedEvent(popupId: number, buttonId: number): void { this.postMessage({ type: "buttonClickedEvent", diff --git a/front/src/Api/iframe/Room/EmbeddedWebsite.ts b/front/src/Api/iframe/Room/EmbeddedWebsite.ts index 1a2761bd..d9c2d986 100644 --- a/front/src/Api/iframe/Room/EmbeddedWebsite.ts +++ b/front/src/Api/iframe/Room/EmbeddedWebsite.ts @@ -13,7 +13,7 @@ export class EmbeddedWebsite { private _allowApi: boolean; private _position: Rectangle; private readonly origin: "map" | "player" | undefined; - private _scale: number | undefined; + private _scale: number; constructor(private config: CreateEmbeddedWebsiteEvent) { this.name = config.name; @@ -23,7 +23,7 @@ export class EmbeddedWebsite { this._allowApi = config.allowApi ?? false; this._position = config.position; this.origin = config.origin; - this._scale = config.scale; + this._scale = config.scale ?? 1; } public get url() { @@ -116,7 +116,7 @@ export class EmbeddedWebsite { }); } - public get scale() { + public get scale(): number { return this._scale; } diff --git a/front/src/Api/iframe/camera.ts b/front/src/Api/iframe/camera.ts new file mode 100644 index 00000000..e2fb258e --- /dev/null +++ b/front/src/Api/iframe/camera.ts @@ -0,0 +1,29 @@ +import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution"; +import { Subject } from "rxjs"; +import type { HasCameraMovedEvent, HasCameraMovedEventCallback } from "../Events/HasCameraMovedEvent"; +import { apiCallback } from "./registeredCallbacks"; +import { isHasCameraMovedEvent } from "../Events/HasCameraMovedEvent"; + +const moveStream = new Subject(); + +export class WorkAdventureCameraCommands extends IframeApiContribution { + callbacks = [ + apiCallback({ + type: "hasCameraMoved", + typeChecker: isHasCameraMovedEvent, + callback: (payloadData) => { + moveStream.next(payloadData); + }, + }), + ]; + + onCameraMove(callback: HasCameraMovedEventCallback): void { + moveStream.subscribe(callback); + sendToWorkadventure({ + type: "onCameraMove", + data: null, + }); + } +} + +export default new WorkAdventureCameraCommands(); diff --git a/front/src/Phaser/Game/EmbeddedWebsiteManager.ts b/front/src/Phaser/Game/EmbeddedWebsiteManager.ts index 99c4bf5f..36e6b305 100644 --- a/front/src/Phaser/Game/EmbeddedWebsiteManager.ts +++ b/front/src/Phaser/Game/EmbeddedWebsiteManager.ts @@ -30,6 +30,7 @@ export class EmbeddedWebsiteManager { height: rect["height"], }, origin: website.origin, + scale: website.scale, }; }); @@ -144,9 +145,9 @@ export class EmbeddedWebsiteManager { name, url, /*x, - y, - width, - height,*/ + y, + width, + height,*/ allow, allowApi, visible, diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 417fab75..9febe432 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -93,6 +93,8 @@ import { MapStore } from "../../Stores/Utils/MapStore"; import { SetPlayerDetailsMessage } from "../../Messages/generated/messages_pb"; import { followUsersColorStore, followUsersStore } from "../../Stores/FollowStore"; import { getColorRgbFromHue } from "../../WebRtc/ColorGenerator"; +import Camera = Phaser.Cameras.Scene2D.Camera; +import type { HasCameraMovedEvent } from "../../Api/Events/HasCameraMovedEvent"; export interface GameSceneInitInterface { initPosition: PointInterface | null; @@ -783,6 +785,17 @@ export class GameScene extends DirtyScene { this.gameMap.setPosition(event.x, event.y); }); + //listen event to share the actual worldView when the camera is updated + this.cameras.main.on("followupdate", (camera: Camera) => { + const worldView: HasCameraMovedEvent = { + x: camera.worldView.x, + y: camera.worldView.y, + width: camera.worldView.width, + height: camera.worldView.height, + }; + iframeListener.hasCameraMoved(worldView); + }); + // Set up variables manager this.sharedVariablesManager = new SharedVariablesManager( this.connection, diff --git a/front/src/iframe_api.ts b/front/src/iframe_api.ts index f33bc8d7..6b3ec8c3 100644 --- a/front/src/iframe_api.ts +++ b/front/src/iframe_api.ts @@ -20,6 +20,7 @@ import type { ButtonDescriptor } from "./Api/iframe/Ui/ButtonDescriptor"; import type { Popup } from "./Api/iframe/Ui/Popup"; import type { Sound } from "./Api/iframe/Sound/Sound"; import { answerPromises, queryWorkadventure } from "./Api/iframe/IframeApiContribution"; +import camera from "./Api/iframe/camera"; const globalState = createState("global"); @@ -46,6 +47,7 @@ const wa = { sound, room, player, + camera, state: globalState, onInit(): Promise { From 1e073d8a0e5294923fe79d6343fa29a6233b6ab0 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Tue, 23 Nov 2021 17:39:45 +0100 Subject: [PATCH 11/26] Refactoring and documentation update --- docs/maps/api-camera.md | 23 +++++++++++++++++++ docs/maps/api-reference.md | 1 + front/src/Api/Events/IframeEvent.ts | 6 ++--- ...MovedEvent.ts => WasCameraUpdatedEvent.ts} | 6 ++--- front/src/Api/IframeListener.ts | 13 ++++++----- front/src/Api/iframe/camera.ts | 14 +++++------ front/src/Phaser/Game/GameScene.ts | 6 ++--- 7 files changed, 47 insertions(+), 22 deletions(-) create mode 100644 docs/maps/api-camera.md rename front/src/Api/Events/{HasCameraMovedEvent.ts => WasCameraUpdatedEvent.ts} (55%) diff --git a/docs/maps/api-camera.md b/docs/maps/api-camera.md new file mode 100644 index 00000000..b5b85b64 --- /dev/null +++ b/docs/maps/api-camera.md @@ -0,0 +1,23 @@ +{.section-title.accent.text-primary} +# API Camera functions Reference + +### Listen to the camera update + +``` +WA.camera.onCameraUpdate(callback: WasCameraUpdatedEventCallback): void +``` + +Listens to the updating of the camera linked to the player. It will trigger for every update of the camera's properties (position or scale for instance) or of the Game Object it is linked to (undestand: if the player moves). An event will then be sent. + +The event has the following attributes : +* **x (number):** coordinate X of the camera's world view (the area looked at by the camera). +* **y (number):** coordinate Y of the camera's world view. +* **width (number):** the width of the camera's world view. +* **height (number):** the height of the camera's world view. + +**callback:** the function that will be called when the camera is updated. + +Example : +```javascript +WA.camera.onCameraUpdate((worldView) => console.log(worldView)); +``` \ No newline at end of file diff --git a/docs/maps/api-reference.md b/docs/maps/api-reference.md index d044668f..a0869075 100644 --- a/docs/maps/api-reference.md +++ b/docs/maps/api-reference.md @@ -10,5 +10,6 @@ - [UI functions](api-ui.md) - [Sound functions](api-sound.md) - [Controls functions](api-controls.md) +- [Camera functions](api-camera.md) - [List of deprecated functions](api-deprecated.md) diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 5b1c0f02..ee7b0f64 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -32,7 +32,7 @@ import { isPlayerPropertyEvent } from "./PlayerPropertyEvent"; import type { ChangeZoneEvent } from "./ChangeZoneEvent"; import { isColorEvent } from "./ColorEvent"; import { isPlayerPosition } from "./PlayerPosition"; -import type { HasCameraMovedEvent } from "./HasCameraMovedEvent"; +import type { WasCameraUpdatedEvent } from "./WasCameraUpdatedEvent"; export interface TypedMessageEvent extends MessageEvent { data: T; @@ -53,7 +53,7 @@ export type IframeEventMap = { displayBubble: null; removeBubble: null; onPlayerMove: undefined; - onCameraMove: undefined; + onCameraUpdate: undefined; showLayer: LayerEvent; hideLayer: LayerEvent; setProperty: SetPropertyEvent; @@ -86,7 +86,7 @@ export interface IframeResponseEventMap { leaveZoneEvent: ChangeZoneEvent; buttonClickedEvent: ButtonClickedEvent; hasPlayerMoved: HasPlayerMovedEvent; - hasCameraMoved: HasCameraMovedEvent; + wasCameraUpdated: WasCameraUpdatedEvent; menuItemClicked: MenuItemClickedEvent; setVariable: SetVariableEvent; messageTriggered: MessageReferenceEvent; diff --git a/front/src/Api/Events/HasCameraMovedEvent.ts b/front/src/Api/Events/WasCameraUpdatedEvent.ts similarity index 55% rename from front/src/Api/Events/HasCameraMovedEvent.ts rename to front/src/Api/Events/WasCameraUpdatedEvent.ts index 23f85385..8f37753c 100644 --- a/front/src/Api/Events/HasCameraMovedEvent.ts +++ b/front/src/Api/Events/WasCameraUpdatedEvent.ts @@ -1,6 +1,6 @@ import * as tg from "generic-type-guard"; -export const isHasCameraMovedEvent = new tg.IsInterface() +export const isWasCameraUpdatedEvent = new tg.IsInterface() .withProperties({ x: tg.isNumber, y: tg.isNumber, @@ -13,6 +13,6 @@ export const isHasCameraMovedEvent = new tg.IsInterface() * A message sent from the game to the iFrame to notify a movement from the camera. */ -export type HasCameraMovedEvent = tg.GuardedType; +export type WasCameraUpdatedEvent = tg.GuardedType; -export type HasCameraMovedEventCallback = (event: HasCameraMovedEvent) => void; +export type WasCameraUpdatedEventCallback = (event: WasCameraUpdatedEvent) => void; diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 093cacc5..b790c0ca 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -31,6 +31,7 @@ import type { SetVariableEvent } from "./Events/SetVariableEvent"; import { ModifyEmbeddedWebsiteEvent, isEmbeddedWebsiteEvent } from "./Events/EmbeddedWebsiteEvent"; import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Stores/MenuStore"; import type { ChangeLayerEvent } from "./Events/ChangeLayerEvent"; +import type { WasCameraUpdatedEvent } from "./Events/WasCameraUpdatedEvent"; import type { HasCameraMovedEvent } from "./Events/HasCameraMovedEvent"; import type { ChangeZoneEvent } from "./Events/ChangeZoneEvent"; @@ -96,7 +97,7 @@ class IframeListener { private readonly iframeCloseCallbacks = new Map void)[]>(); private readonly scripts = new Map(); private sendPlayerMove: boolean = false; - private sendCameraMove: boolean = false; + private sendCameraUpdate: boolean = false; // Note: we are forced to type this in unknown and later cast with "as" because of https://github.com/microsoft/TypeScript/issues/31904 private answerers: { @@ -228,8 +229,8 @@ class IframeListener { this._removeBubbleStream.next(); } else if (payload.type == "onPlayerMove") { this.sendPlayerMove = true; - } else if (payload.type == "onCameraMove") { - this.sendCameraMove = true; + } else if (payload.type == "onCameraUpdate") { + this.sendCameraUpdate = true; } else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) { this._setTilesStream.next(payload.data); } else if (payload.type == "modifyEmbeddedWebsite" && isEmbeddedWebsiteEvent(payload.data)) { @@ -446,10 +447,10 @@ class IframeListener { } } - hasCameraMoved(event: HasCameraMovedEvent) { - if (this.sendCameraMove) { + sendCameraUpdated(event: WasCameraUpdatedEvent) { + if (this.sendCameraUpdate) { this.postMessage({ - type: "hasCameraMoved", + type: "wasCameraUpdated", data: event, }); } diff --git a/front/src/Api/iframe/camera.ts b/front/src/Api/iframe/camera.ts index e2fb258e..4f62b94c 100644 --- a/front/src/Api/iframe/camera.ts +++ b/front/src/Api/iframe/camera.ts @@ -1,26 +1,26 @@ import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution"; import { Subject } from "rxjs"; -import type { HasCameraMovedEvent, HasCameraMovedEventCallback } from "../Events/HasCameraMovedEvent"; +import type { WasCameraUpdatedEvent, WasCameraUpdatedEventCallback } from "../Events/WasCameraUpdatedEvent"; import { apiCallback } from "./registeredCallbacks"; -import { isHasCameraMovedEvent } from "../Events/HasCameraMovedEvent"; +import { isWasCameraUpdatedEvent } from "../Events/WasCameraUpdatedEvent"; -const moveStream = new Subject(); +const moveStream = new Subject(); export class WorkAdventureCameraCommands extends IframeApiContribution { callbacks = [ apiCallback({ - type: "hasCameraMoved", - typeChecker: isHasCameraMovedEvent, + type: "wasCameraUpdated", + typeChecker: isWasCameraUpdatedEvent, callback: (payloadData) => { moveStream.next(payloadData); }, }), ]; - onCameraMove(callback: HasCameraMovedEventCallback): void { + onCameraUpdate(callback: WasCameraUpdatedEventCallback): void { moveStream.subscribe(callback); sendToWorkadventure({ - type: "onCameraMove", + type: "onCameraUpdate", data: null, }); } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 9febe432..7ae2707f 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -94,7 +94,7 @@ import { SetPlayerDetailsMessage } from "../../Messages/generated/messages_pb"; import { followUsersColorStore, followUsersStore } from "../../Stores/FollowStore"; import { getColorRgbFromHue } from "../../WebRtc/ColorGenerator"; import Camera = Phaser.Cameras.Scene2D.Camera; -import type { HasCameraMovedEvent } from "../../Api/Events/HasCameraMovedEvent"; +import type { WasCameraUpdatedEvent } from "../../Api/Events/WasCameraUpdatedEvent"; export interface GameSceneInitInterface { initPosition: PointInterface | null; @@ -787,13 +787,13 @@ export class GameScene extends DirtyScene { //listen event to share the actual worldView when the camera is updated this.cameras.main.on("followupdate", (camera: Camera) => { - const worldView: HasCameraMovedEvent = { + const worldView: WasCameraUpdatedEvent = { x: camera.worldView.x, y: camera.worldView.y, width: camera.worldView.width, height: camera.worldView.height, }; - iframeListener.hasCameraMoved(worldView); + iframeListener.sendCameraUpdated(worldView); }); // Set up variables manager From 2a7c8f3786d54838caff63afd70dda53141650a6 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Fri, 26 Nov 2021 14:46:02 +0100 Subject: [PATCH 12/26] Reverts adding scale to the camera updated event and uses it directly from the website manager --- .../src/Phaser/Game/EmbeddedWebsiteManager.ts | 68 +++++++++---------- front/src/Phaser/Game/GameScene.ts | 4 +- 2 files changed, 35 insertions(+), 37 deletions(-) diff --git a/front/src/Phaser/Game/EmbeddedWebsiteManager.ts b/front/src/Phaser/Game/EmbeddedWebsiteManager.ts index 36e6b305..d58d7eaf 100644 --- a/front/src/Phaser/Game/EmbeddedWebsiteManager.ts +++ b/front/src/Phaser/Game/EmbeddedWebsiteManager.ts @@ -26,8 +26,8 @@ export class EmbeddedWebsiteManager { position: { x: website.phaserObject.x, y: website.phaserObject.y, - width: rect["width"], - height: rect["height"], + width: website.phaserObject.width, + height: website.phaserObject.height, }, origin: website.origin, scale: website.scale, @@ -111,14 +111,18 @@ export class EmbeddedWebsiteManager { website.phaserObject.y = embeddedWebsiteEvent.y; } if (embeddedWebsiteEvent?.width !== undefined) { - website.iframe.style.width = embeddedWebsiteEvent.width + "px"; + website.position.width = embeddedWebsiteEvent.width; + website.iframe.style.width = embeddedWebsiteEvent.width / website.phaserObject.scale + "px"; } if (embeddedWebsiteEvent?.height !== undefined) { - website.iframe.style.height = embeddedWebsiteEvent.height + "px"; + website.position.height = embeddedWebsiteEvent.height; + website.iframe.style.height = embeddedWebsiteEvent.height / website.phaserObject.scale + "px"; } if (embeddedWebsiteEvent?.scale !== undefined) { website.phaserObject.scale = embeddedWebsiteEvent.scale; + website.iframe.style.width = website.position.width / embeddedWebsiteEvent.scale + "px"; + website.iframe.style.height = website.position.height / embeddedWebsiteEvent.scale + "px"; } } ); @@ -145,9 +149,9 @@ export class EmbeddedWebsiteManager { name, url, /*x, - y, - width, - height,*/ +y, +width, +height,*/ allow, allowApi, visible, @@ -173,49 +177,43 @@ export class EmbeddedWebsiteManager { const absoluteUrl = new URL(embeddedWebsiteEvent.url, this.gameScene.MapUrlFile).toString(); const iframe = document.createElement("iframe"); + const scale = embeddedWebsiteEvent.scale ?? 1; + iframe.src = absoluteUrl; iframe.tabIndex = -1; - iframe.style.width = embeddedWebsiteEvent.position.width + "px"; - iframe.style.height = embeddedWebsiteEvent.position.height + "px"; + iframe.style.width = embeddedWebsiteEvent.position.width / scale + "px"; + iframe.style.height = embeddedWebsiteEvent.position.height / scale + "px"; iframe.style.margin = "0"; iframe.style.padding = "0"; iframe.style.border = "none"; - let embeddedWebsite; - let domElement; + const domElement = new DOMElement( + this.gameScene, + embeddedWebsiteEvent.position.x, + embeddedWebsiteEvent.position.y, + iframe + ); + domElement.setOrigin(0, 0); + if (embeddedWebsiteEvent.scale) { + domElement.scale = embeddedWebsiteEvent.scale; + } + domElement.setVisible(visible); switch (embeddedWebsiteEvent.origin) { case "player": - domElement = new DOMElement( - this.gameScene, - embeddedWebsiteEvent.position.x, - embeddedWebsiteEvent.position.y, - iframe - ); - if (embeddedWebsiteEvent.scale) { - domElement.scale = embeddedWebsiteEvent.scale; - } this.gameScene.CurrentPlayer.add(domElement); - - embeddedWebsite = { - ...embeddedWebsiteEvent, - phaserObject: domElement, - iframe: iframe, - }; - break; case "map": default: - embeddedWebsite = { - ...embeddedWebsiteEvent, - phaserObject: this.gameScene.add - .dom(embeddedWebsiteEvent.position.x, embeddedWebsiteEvent.position.y, iframe) - .setVisible(visible) - .setOrigin(0, 0), - iframe: iframe, - }; + this.gameScene.add.existing(domElement); } + const embeddedWebsite = { + ...embeddedWebsiteEvent, + phaserObject: domElement, + iframe: iframe, + }; + if (embeddedWebsiteEvent.allowApi) { iframeListener.registerIframe(iframe); } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 7ae2707f..55599510 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -787,13 +787,13 @@ export class GameScene extends DirtyScene { //listen event to share the actual worldView when the camera is updated this.cameras.main.on("followupdate", (camera: Camera) => { - const worldView: WasCameraUpdatedEvent = { + const cameraEvent: WasCameraUpdatedEvent = { x: camera.worldView.x, y: camera.worldView.y, width: camera.worldView.width, height: camera.worldView.height, }; - iframeListener.sendCameraUpdated(worldView); + iframeListener.sendCameraUpdated(cameraEvent); }); // Set up variables manager From 3abc571e791fb24787336f8f5c8e86ecc81a67e9 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Mon, 6 Dec 2021 18:44:37 +0100 Subject: [PATCH 13/26] Corrects scale managing and camera event listening --- front/src/Api/Events/WasCameraUpdatedEvent.ts | 1 + front/src/Api/IframeListener.ts | 16 +++--- .../src/Phaser/Game/EmbeddedWebsiteManager.ts | 7 +-- front/src/Phaser/Game/GameScene.ts | 51 ++++++++++++++----- 4 files changed, 50 insertions(+), 25 deletions(-) diff --git a/front/src/Api/Events/WasCameraUpdatedEvent.ts b/front/src/Api/Events/WasCameraUpdatedEvent.ts index 8f37753c..34e39a84 100644 --- a/front/src/Api/Events/WasCameraUpdatedEvent.ts +++ b/front/src/Api/Events/WasCameraUpdatedEvent.ts @@ -6,6 +6,7 @@ export const isWasCameraUpdatedEvent = new tg.IsInterface() y: tg.isNumber, width: tg.isNumber, height: tg.isNumber, + zoom: tg.isNumber, }) .get(); diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index b790c0ca..3c79bbe2 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -87,6 +87,9 @@ class IframeListener { private readonly _loadSoundStream: Subject = new Subject(); public readonly loadSoundStream = this._loadSoundStream.asObservable(); + private readonly _trackCameraUpdateStream: Subject = new Subject(); + public readonly trackCameraUpdateStream = this._trackCameraUpdateStream.asObservable(); + private readonly _setTilesStream: Subject = new Subject(); public readonly setTilesStream = this._setTilesStream.asObservable(); @@ -97,7 +100,6 @@ class IframeListener { private readonly iframeCloseCallbacks = new Map void)[]>(); private readonly scripts = new Map(); private sendPlayerMove: boolean = false; - private sendCameraUpdate: boolean = false; // Note: we are forced to type this in unknown and later cast with "as" because of https://github.com/microsoft/TypeScript/issues/31904 private answerers: { @@ -230,7 +232,7 @@ class IframeListener { } else if (payload.type == "onPlayerMove") { this.sendPlayerMove = true; } else if (payload.type == "onCameraUpdate") { - this.sendCameraUpdate = true; + this._trackCameraUpdateStream.next(); } else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) { this._setTilesStream.next(payload.data); } else if (payload.type == "modifyEmbeddedWebsite" && isEmbeddedWebsiteEvent(payload.data)) { @@ -448,12 +450,10 @@ class IframeListener { } sendCameraUpdated(event: WasCameraUpdatedEvent) { - if (this.sendCameraUpdate) { - this.postMessage({ - type: "wasCameraUpdated", - data: event, - }); - } + this.postMessage({ + type: "wasCameraUpdated", + data: event, + }); } sendButtonClickedEvent(popupId: number, buttonId: number): void { diff --git a/front/src/Phaser/Game/EmbeddedWebsiteManager.ts b/front/src/Phaser/Game/EmbeddedWebsiteManager.ts index d58d7eaf..387940c7 100644 --- a/front/src/Phaser/Game/EmbeddedWebsiteManager.ts +++ b/front/src/Phaser/Game/EmbeddedWebsiteManager.ts @@ -16,7 +16,8 @@ export class EmbeddedWebsiteManager { if (website === undefined) { throw new Error('Cannot find embedded website with name "' + name + '"'); } - const rect = website.iframe.getBoundingClientRect(); + + const scale = website.scale ?? 1; return { url: website.url, name: website.name, @@ -26,8 +27,8 @@ export class EmbeddedWebsiteManager { position: { x: website.phaserObject.x, y: website.phaserObject.y, - width: website.phaserObject.width, - height: website.phaserObject.height, + width: website.phaserObject.width * scale, + height: website.phaserObject.height * scale, }, origin: website.origin, scale: website.scale, diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 55599510..ec12aae8 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -212,6 +212,8 @@ export class GameScene extends DirtyScene { private objectsByType = new Map(); private embeddedWebsiteManager!: EmbeddedWebsiteManager; private loader: Loader; + private lastCameraEvent: WasCameraUpdatedEvent | undefined; + private firstCameraUpdateSent: boolean = false; constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) { super({ @@ -785,17 +787,6 @@ export class GameScene extends DirtyScene { this.gameMap.setPosition(event.x, event.y); }); - //listen event to share the actual worldView when the camera is updated - this.cameras.main.on("followupdate", (camera: Camera) => { - const cameraEvent: WasCameraUpdatedEvent = { - x: camera.worldView.x, - y: camera.worldView.y, - width: camera.worldView.width, - height: camera.worldView.height, - }; - iframeListener.sendCameraUpdated(cameraEvent); - }); - // Set up variables manager this.sharedVariablesManager = new SharedVariablesManager( this.connection, @@ -1115,9 +1106,33 @@ ${escapedMessage} ); this.iframeSubscriptionList.push( - iframeListener.stopSoundStream.subscribe((stopSoundEvent) => { - const url = new URL(stopSoundEvent.url, this.MapUrlFile); - soundManager.stopSound(this.sound, url.toString()); + iframeListener.trackCameraUpdateStream.subscribe(() => { + if (!this.firstCameraUpdateSent) { + this.cameras.main.on("followupdate", (camera: Camera) => { + const cameraEvent: WasCameraUpdatedEvent = { + x: camera.worldView.x, + y: camera.worldView.y, + width: camera.worldView.width, + height: camera.worldView.height, + zoom: camera.scaleManager.zoom, + }; + if ( + this.lastCameraEvent?.x == cameraEvent.x && + this.lastCameraEvent?.y == cameraEvent.y && + this.lastCameraEvent?.width == cameraEvent.width && + this.lastCameraEvent?.height == cameraEvent.height && + this.lastCameraEvent?.zoom == cameraEvent.zoom + ) { + return; + } + + this.lastCameraEvent = cameraEvent; + iframeListener.sendCameraUpdated(cameraEvent); + this.firstCameraUpdateSent = true; + }); + + iframeListener.sendCameraUpdated(this.cameras.main); + } }) ); @@ -1180,6 +1195,12 @@ ${escapedMessage} }) ); + this.iframeSubscriptionList.push( + iframeListener.setPropertyStream.subscribe((setProperty) => { + this.setPropertyLayer(setProperty.layerName, setProperty.propertyName, setProperty.propertyValue); + }) + ); + iframeListener.registerAnswerer("openCoWebsite", async (openCoWebsite, source) => { if (!source) { throw new Error("Unknown query source"); @@ -1986,6 +2007,7 @@ ${escapedMessage} this.loader.resize(); } + private getObjectLayerData(objectName: string): ITiledMapObject | undefined { for (const layer of this.mapFile.layers) { if (layer.type === "objectgroup" && layer.name === "floorLayer") { @@ -1998,6 +2020,7 @@ ${escapedMessage} } return undefined; } + private reposition(): void { // Recompute camera offset if needed biggestAvailableAreaStore.recompute(); From ddb4ae88239f456970b7682aacf624935f84402a Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Tue, 7 Dec 2021 18:47:40 +0100 Subject: [PATCH 14/26] Documentation --- docs/maps/api-camera.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/maps/api-camera.md b/docs/maps/api-camera.md index b5b85b64..7ae9ce2b 100644 --- a/docs/maps/api-camera.md +++ b/docs/maps/api-camera.md @@ -7,7 +7,7 @@ WA.camera.onCameraUpdate(callback: WasCameraUpdatedEventCallback): void ``` -Listens to the updating of the camera linked to the player. It will trigger for every update of the camera's properties (position or scale for instance) or of the Game Object it is linked to (undestand: if the player moves). An event will then be sent. +Listens to the updating of the camera linked to the player. It will trigger for every update of the camera's properties (position or scale for instance). An event will then be sent. The event has the following attributes : * **x (number):** coordinate X of the camera's world view (the area looked at by the camera). From 9cf64c3c765a9c97cad55c8049172cc164755569 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Wed, 8 Dec 2021 11:48:16 +0100 Subject: [PATCH 15/26] Documentation on type Position --- docs/maps/api-player.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/maps/api-player.md b/docs/maps/api-player.md index ad08448b..2bfad463 100644 --- a/docs/maps/api-player.md +++ b/docs/maps/api-player.md @@ -92,6 +92,11 @@ WA.player.getPosition(): Promise ``` The player's current position is available using the `WA.player.getPosition()` function. +`Position` has the following attributes : +* **x (number) :** The coordinate x of the current player's position. +* **y (number) :** The coordinate y of the current player's position. + + {.alert.alert-info} You need to wait for the end of the initialization before calling `WA.player.getPosition()` From 864ff49af58e0540ff37a86be4a6d5a0cfea8454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dicte=20Q?= <37311765+HimeShaman@users.noreply.github.com> Date: Wed, 8 Dec 2021 10:05:46 +0100 Subject: [PATCH 16/26] Update docs/maps/api-camera.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Négrier --- docs/maps/api-camera.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/maps/api-camera.md b/docs/maps/api-camera.md index 7ae9ce2b..405de418 100644 --- a/docs/maps/api-camera.md +++ b/docs/maps/api-camera.md @@ -1,7 +1,7 @@ {.section-title.accent.text-primary} # API Camera functions Reference -### Listen to the camera update +### Listen to camera updates ``` WA.camera.onCameraUpdate(callback: WasCameraUpdatedEventCallback): void From 73efdab52f5307a5a45fcd0db5e216d993a09897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dicte=20Q?= <37311765+HimeShaman@users.noreply.github.com> Date: Wed, 8 Dec 2021 10:06:07 +0100 Subject: [PATCH 17/26] Update docs/maps/api-camera.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Négrier --- docs/maps/api-camera.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/maps/api-camera.md b/docs/maps/api-camera.md index 405de418..e0b5d7f7 100644 --- a/docs/maps/api-camera.md +++ b/docs/maps/api-camera.md @@ -7,7 +7,7 @@ WA.camera.onCameraUpdate(callback: WasCameraUpdatedEventCallback): void ``` -Listens to the updating of the camera linked to the player. It will trigger for every update of the camera's properties (position or scale for instance). An event will then be sent. +Listens to updates of the camera viewport. It will trigger for every update of the camera's properties (position or scale for instance). An event will be sent. The event has the following attributes : * **x (number):** coordinate X of the camera's world view (the area looked at by the camera). From adc71b5695977452b666435c616b5748b9e11749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dicte=20Q?= <37311765+HimeShaman@users.noreply.github.com> Date: Wed, 8 Dec 2021 10:06:34 +0100 Subject: [PATCH 18/26] Update front/src/Api/Events/IframeEvent.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Négrier --- front/src/Api/Events/IframeEvent.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index ee7b0f64..4e87d4d4 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -218,7 +218,6 @@ export const isIframeQuery = (event: any): event is IframeQuery Date: Wed, 8 Dec 2021 10:14:31 +0100 Subject: [PATCH 19/26] Update front/src/Api/Events/IframeEvent.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Négrier --- front/src/Api/Events/IframeEvent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 4e87d4d4..dac8509d 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -65,7 +65,7 @@ export type IframeEventMap = { registerMenu: MenuRegisterEvent; unregisterMenu: UnregisterMenuEvent; setTiles: SetTilesEvent; - modifyEmbeddedWebsite: Partial; // Note: name should be compulsory in fact; + modifyEmbeddedWebsite: Partial; // Note: name should be compulsory in fact }; export interface IframeEvent { type: T; From 99f9d56c5ce27be73c7f4d1e840f6d3b1b454a97 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Tue, 28 Dec 2021 15:05:58 +0100 Subject: [PATCH 20/26] Updates room documentation for embeddedWebsite properties --- docs/maps/api-room.md | 140 ++++++++++++++++------------ front/src/Api/Events/IframeEvent.ts | 9 -- front/src/Api/IframeListener.ts | 1 - front/src/Api/iframe/player.ts | 14 --- 4 files changed, 78 insertions(+), 86 deletions(-) diff --git a/docs/maps/api-room.md b/docs/maps/api-room.md index 72947df8..bcc53332 100644 --- a/docs/maps/api-room.md +++ b/docs/maps/api-room.md @@ -1,8 +1,11 @@ {.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. + +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 :
@@ -12,6 +15,7 @@ Example :
The name of the layers of this map are : + * `entries/start` * `bottom/ground/under` * `bottom/build/carpet` @@ -26,29 +30,32 @@ 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 layer. -* **name**: the name of the layer who as defined in Tiled. +* **name**: the name of the layer who as defined in Tiled. Example: ```javascript WA.room.onEnterLayer('myLayer').subscribe(() => { - WA.chat.sendChatMessage("Hello!", 'Mr Robot'); + WA.chat.sendChatMessage("Hello!", 'Mr Robot'); }); WA.room.onLeaveLayer('myLayer').subscribe(() => { - WA.chat.sendChatMessage("Goodbye!", 'Mr Robot'); + 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. -if `layerName` is the name of a group layer, show/hide all the layer in that group layer. + +These 2 methods can be used to show and hide a layer. if `layerName` is the name of a group layer, show/hide all the +layer in that group layer. Example : + ```javascript WA.room.showLayer('bottom'); //... @@ -61,12 +68,14 @@ WA.room.hideLayer('bottom'); 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`. +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 : To unset a property from a layer, use `setProperty` with `propertyValue` set to `undefined`. Example : + ```javascript WA.room.setProperty('wikiLayer', 'openWebsite', 'https://www.wikipedia.org/'); ``` @@ -79,13 +88,12 @@ WA.room.id: string; The ID of the current room is available from the `WA.room.id` property. -{.alert.alert-info} -You need to wait for the end of the initialization before accessing `WA.room.id` +{.alert.alert-info} You need to wait for the end of the initialization before accessing `WA.room.id` ```typescript WA.onInit().then(() => { - console.log('Room id: ', WA.room.id); - // Will output something like: 'https://play.workadventu.re/@/myorg/myworld/myroom', or 'https://play.workadventu.re/_/global/mymap.org/map.json" + console.log('Room id: ', WA.room.id); + // Will output something like: 'https://play.workadventu.re/@/myorg/myworld/myroom', or 'https://play.workadventu.re/_/global/mymap.org/map.json" }) ``` @@ -97,19 +105,17 @@ WA.room.mapURL: string; The URL of the map is available from the `WA.room.mapURL` property. -{.alert.alert-info} -You need to wait for the end of the initialization before accessing `WA.room.mapURL` +{.alert.alert-info} You need to wait for the end of the initialization before accessing `WA.room.mapURL` ```typescript WA.onInit().then(() => { - console.log('Map URL: ', WA.room.mapURL); - // Will output something like: 'https://mymap.org/map.json" + console.log('Map URL: ', WA.room.mapURL); + // Will output something like: 'https://mymap.org/map.json" }) ``` - - ### Getting map data + ``` WA.room.getTiledMap(): Promise ``` @@ -121,12 +127,16 @@ const map = await WA.room.getTiledMap(); 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 + ``` WA.room.setTiles(tiles: TileDescriptor[]): void ``` + Replace the tile at the `x` and `y` coordinates in the layer named `layer` by the tile with the id `tile`. If `tile` is a string, it's not the id of the tile but the value of the property `name`. @@ -137,43 +147,48 @@ If `tile` is a string, it's not the id of the tile but the value of the property `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. * **layer (string) :** The name of the layer where the tile will be placed. -**Important !** : If you use `tile` as a number, be sure to add the `firstgid` of the tileset of the tile that you want to the id of the tile in Tiled Editor. +**Important !** : If you use `tile` as a number, be sure to add the `firstgid` of the tileset of the tile that you want +to the id of the tile in Tiled Editor. Note: If you want to unset a tile, use `setTiles` with `tile` set to `null`. Example : + ```javascript WA.room.setTiles([ - {x: 6, y: 4, tile: 'blue', layer: 'setTiles'}, - {x: 7, y: 4, tile: 109, layer: 'setTiles'}, - {x: 8, y: 4, tile: 109, layer: 'setTiles'}, - {x: 9, y: 4, tile: 'blue', layer: 'setTiles'} - ]); + { x: 6, y: 4, tile: 'blue', layer: 'setTiles' }, + { x: 7, y: 4, tile: 109, layer: 'setTiles' }, + { x: 8, y: 4, tile: 109, layer: 'setTiles' }, + { x: 9, y: 4, tile: 'blue', layer: 'setTiles' } +]); ``` ### Loading a tileset + ``` WA.room.loadTileset(url: string): Promise ``` + Load a tileset in JSON format from an url and return the id of the first tile of the loaded tileset. You can create a tileset file in Tile Editor. ```javascript WA.room.loadTileset("Assets/Tileset.json").then((firstId) => { - WA.room.setTiles([{x: 4, y: 4, tile: firstId, layer: 'bottom'}]); + WA.room.setTiles([{ x: 4, y: 4, tile: firstId, layer: 'bottom' }]); }) ``` - ## Embedding websites in a map -You can use the scripting API to embed websites in a map, or to edit websites that are already embedded (using the ["website" objects](website-in-map.md)). +You can use the scripting API to embed websites in a map, or to edit websites that are already embedded (using +the ["website" objects](website-in-map.md)). ### Getting an instance of a website already embedded in the map @@ -181,8 +196,8 @@ You can use the scripting API to embed websites in a map, or to edit websites th WA.room.website.get(objectName: string): Promise ``` -You can get an instance of an embedded website by using the `WA.room.website.get()` method. -It returns a promise of an `EmbeddedWebsite` instance. +You can get an instance of an embedded website by using the `WA.room.website.get()` method. It returns a promise of +an `EmbeddedWebsite` instance. ```javascript // Get an existing website object where 'my_website' is the name of the object (on any layer object of the map) @@ -191,7 +206,6 @@ website.url = 'https://example.com'; website.visible = true; ``` - ### Adding a new website in a map ``` @@ -201,34 +215,38 @@ interface CreateEmbeddedWebsiteEvent { name: string; // A unique name for this iframe url: string; // The URL the iframe points to. position: { - x: number, // In pixels, relative to the map coordinates - y: number, // In pixels, relative to the map coordinates - width: number, // In pixels, sensitive to zoom level - height: number, // In pixels, sensitive to zoom level + x: number, // relative to the map or player coordinates, depending on origin + y: number, // relative to the map or player coordinates, depending on origin + width: number, // In pixels, sensitive to zoom level and scale + height: number, // In pixels, sensitive to zoom level and scale }, visible?: boolean, // Whether to display the iframe or not allowApi?: boolean, // Whether the scripting API should be available to the iframe allow?: string, // The list of feature policies allowed + origin: "player" | "map" // The origin used to place the x and y coordinates of the iframe's top-left corner + scale: number, // A ratio used to resize the iframe (will affect the iframe's width and height when it is rendered } ``` -You can create an instance of an embedded website by using the `WA.room.website.create()` method. -It returns an `EmbeddedWebsite` instance. +You can create an instance of an embedded website by using the `WA.room.website.create()` method. It returns +an `EmbeddedWebsite` instance. ```javascript // Create a new website object const website = WA.room.website.create({ - name: "my_website", - url: "https://example.com", - position: { - x: 64, - y: 128, - width: 320, - height: 240, - }, - visible: true, - allowApi: true, - allow: "fullscreen", + name: "my_website", + url: "https://example.com", + position: { + x: 64, + y: 128, + width: 320, + height: 240, + }, + visible: true, + allowApi: true, + allow: "fullscreen", + origin: "map", + scale: 1, }); ``` @@ -240,30 +258,28 @@ WA.room.website.delete(name: string): Promise Use `WA.room.website.delete` to completely remove an embedded website from your map. - ### The EmbeddedWebsite class Instances of the `EmbeddedWebsite` class represent the website displayed on the map. ```typescript class EmbeddedWebsite { - readonly name: string; - url: string; - visible: boolean; - allow: string; - allowApi: boolean; - x: number; // In pixels, relative to the map coordinates - y: number; // In pixels, relative to the map coordinates - width: number; // In pixels, sensitive to zoom level - height: number; // In pixels, sensitive to zoom level + readonly name: string; + url: string; + visible: boolean; + allow: string; + allowApi: boolean; + x: number; // In pixels, relative to the map or player coordinates, depending on origin + y: number; // In pixels, relative to the map or player coordinates, depending on origin + width: number; // In pixels, sensitive to zoom level and scale + height: number; // In pixels, sensitive to zoom level and scale + origin: "player" | "map"; + scale: number; } ``` When you modify a property of an `EmbeddedWebsite` instance, the iframe is automatically modified in the map. - -{.alert.alert-warning} -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. +{.alert.alert-warning} The websites you add/edit/delete via the scripting API are only shown locally. If you want them +to be displayed for every player, you can use [variables](api-start.md) to share a common state between all users. diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index dac8509d..8fb488dc 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -28,7 +28,6 @@ import type { MessageReferenceEvent } from "./ui/TriggerActionMessageEvent"; import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent"; import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent"; import type { ChangeLayerEvent } from "./ChangeLayerEvent"; -import { isPlayerPropertyEvent } from "./PlayerPropertyEvent"; import type { ChangeZoneEvent } from "./ChangeZoneEvent"; import { isColorEvent } from "./ColorEvent"; import { isPlayerPosition } from "./PlayerPosition"; @@ -158,14 +157,6 @@ export const iframeQueryMapTypeGuards = { query: isCreateEmbeddedWebsiteEvent, answer: tg.isUndefined, }, - getPlayerProperty: { - query: tg.isString, - answer: isPlayerPropertyEvent, - }, - setPlayerProperty: { - query: isPlayerPropertyEvent, - answer: tg.isUndefined, - }, setPlayerOutline: { query: isColorEvent, answer: tg.isUndefined, diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 3c79bbe2..216a9510 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -32,7 +32,6 @@ import { ModifyEmbeddedWebsiteEvent, isEmbeddedWebsiteEvent } from "./Events/Emb import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Stores/MenuStore"; import type { ChangeLayerEvent } from "./Events/ChangeLayerEvent"; import type { WasCameraUpdatedEvent } from "./Events/WasCameraUpdatedEvent"; -import type { HasCameraMovedEvent } from "./Events/HasCameraMovedEvent"; import type { ChangeZoneEvent } from "./Events/ChangeZoneEvent"; type AnswererCallback = ( diff --git a/front/src/Api/iframe/player.ts b/front/src/Api/iframe/player.ts index 2a642d75..0c71ae33 100644 --- a/front/src/Api/iframe/player.ts +++ b/front/src/Api/iframe/player.ts @@ -110,20 +110,6 @@ export class WorkadventurePlayerCommands extends IframeApiContribution { - return queryWorkadventure({ - type: "getPlayerProperty", - data: name, - }); - } - - setPlayerProperty(property: PlayerPropertyEvent) { - queryWorkadventure({ - type: "setPlayerProperty", - data: property, - }).catch((e) => console.error(e)); - } } export type Position = { From d9482d484b575c656808de696beb3f249094beee Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Wed, 29 Dec 2021 17:46:46 +0100 Subject: [PATCH 21/26] WIP enable/disable tutorial according to the map 'tutorial' property --- front/src/Phaser/Game/GameMapProperties.ts | 1 + front/src/Phaser/Game/GameScene.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/front/src/Phaser/Game/GameMapProperties.ts b/front/src/Phaser/Game/GameMapProperties.ts index 65ce6ab8..2da56ef8 100644 --- a/front/src/Phaser/Game/GameMapProperties.ts +++ b/front/src/Phaser/Game/GameMapProperties.ts @@ -31,6 +31,7 @@ export enum GameMapProperties { SILENT = "silent", START = "start", START_LAYER = "startLayer", + TUTORIAL = "tutorial", URL = "url", WRITABLE_BY = "writableBy", ZONE = "zone", diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index ec12aae8..e32fa98a 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -603,6 +603,10 @@ export class GameScene extends DirtyScene { for (const script of scripts) { scriptPromises.push(iframeListener.registerScript(script)); } + if (this.isTutorialEnabled()) { + //TODO: split tutorial and bundle scripts + scriptPromises.push(iframeListener.registerScript(new URL("bundle.js", this.MapUrlFile).toString())); + } this.userInputManager.spaceEvent(() => { this.outlinedItem?.activate(); @@ -1397,7 +1401,6 @@ ${escapedMessage} this.connection?.emitPlayerOutlineColor(null); }); - iframeListener.registerAnswerer("getPlayerPosition", () => { return { x: this.CurrentPlayer.x, @@ -1569,6 +1572,10 @@ ${escapedMessage} ); } + private isTutorialEnabled(): boolean { + return this.getProperty(this.mapFile, GameMapProperties.TUTORIAL) as boolean; + } + private getProperty(layer: ITiledMapLayer | ITiledMap, name: string): string | boolean | number | undefined { const properties: ITiledMapProperty[] | undefined = layer.properties; if (!properties) { From 85d45071fa3a78ac79f43c70dc8fb41a5daf8c83 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Thu, 30 Dec 2021 15:50:52 +0100 Subject: [PATCH 22/26] Makes onCameraUpdate subscribe-able --- front/src/Api/iframe/camera.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/front/src/Api/iframe/camera.ts b/front/src/Api/iframe/camera.ts index 4f62b94c..a832290e 100644 --- a/front/src/Api/iframe/camera.ts +++ b/front/src/Api/iframe/camera.ts @@ -1,6 +1,6 @@ import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution"; import { Subject } from "rxjs"; -import type { WasCameraUpdatedEvent, WasCameraUpdatedEventCallback } from "../Events/WasCameraUpdatedEvent"; +import type { WasCameraUpdatedEvent } from "../Events/WasCameraUpdatedEvent"; import { apiCallback } from "./registeredCallbacks"; import { isWasCameraUpdatedEvent } from "../Events/WasCameraUpdatedEvent"; @@ -17,12 +17,12 @@ export class WorkAdventureCameraCommands extends IframeApiContribution { sendToWorkadventure({ type: "onCameraUpdate", data: null, }); + return moveStream; } } From 8157ee4603df3db2db3a5a37b537fe2b92557c06 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Mon, 3 Jan 2022 10:36:23 +0100 Subject: [PATCH 23/26] Completes documentation --- docs/maps/api-camera.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/maps/api-camera.md b/docs/maps/api-camera.md index e0b5d7f7..f78df51f 100644 --- a/docs/maps/api-camera.md +++ b/docs/maps/api-camera.md @@ -4,7 +4,7 @@ ### Listen to camera updates ``` -WA.camera.onCameraUpdate(callback: WasCameraUpdatedEventCallback): void +WA.camera.onCameraUpdate: Subscription ``` Listens to updates of the camera viewport. It will trigger for every update of the camera's properties (position or scale for instance). An event will be sent. @@ -19,5 +19,7 @@ The event has the following attributes : Example : ```javascript -WA.camera.onCameraUpdate((worldView) => console.log(worldView)); +WA.camera.onCameraUpdate.subscribe((worldView) => console.log(worldView)); +//later... +WA.camera.onCameraUpdate().unsubscribe(); ``` \ No newline at end of file From ac27ab7e3e91febbbfef4907d6cd2d066fe6fd81 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Mon, 3 Jan 2022 11:22:01 +0100 Subject: [PATCH 24/26] Revert "WIP enable/disable tutorial according to the map 'tutorial' property" This reverts commit 47a6710b60e3856bf57c0700fe33bec95c6fc6dd. --- front/src/Phaser/Game/GameMapProperties.ts | 1 - front/src/Phaser/Game/GameScene.ts | 8 -------- 2 files changed, 9 deletions(-) diff --git a/front/src/Phaser/Game/GameMapProperties.ts b/front/src/Phaser/Game/GameMapProperties.ts index 2da56ef8..65ce6ab8 100644 --- a/front/src/Phaser/Game/GameMapProperties.ts +++ b/front/src/Phaser/Game/GameMapProperties.ts @@ -31,7 +31,6 @@ export enum GameMapProperties { SILENT = "silent", START = "start", START_LAYER = "startLayer", - TUTORIAL = "tutorial", URL = "url", WRITABLE_BY = "writableBy", ZONE = "zone", diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index e32fa98a..69683e25 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -603,10 +603,6 @@ export class GameScene extends DirtyScene { for (const script of scripts) { scriptPromises.push(iframeListener.registerScript(script)); } - if (this.isTutorialEnabled()) { - //TODO: split tutorial and bundle scripts - scriptPromises.push(iframeListener.registerScript(new URL("bundle.js", this.MapUrlFile).toString())); - } this.userInputManager.spaceEvent(() => { this.outlinedItem?.activate(); @@ -1572,10 +1568,6 @@ ${escapedMessage} ); } - private isTutorialEnabled(): boolean { - return this.getProperty(this.mapFile, GameMapProperties.TUTORIAL) as boolean; - } - private getProperty(layer: ITiledMapLayer | ITiledMap, name: string): string | boolean | number | undefined { const properties: ITiledMapProperty[] | undefined = layer.properties; if (!properties) { From 7c34e0a435240ed08e1f4576446844a2b33a7971 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dicte=20Q?= <37311765+HimeShaman@users.noreply.github.com> Date: Mon, 3 Jan 2022 14:19:55 +0100 Subject: [PATCH 25/26] Update docs/maps/api-camera.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Négrier --- docs/maps/api-camera.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/maps/api-camera.md b/docs/maps/api-camera.md index f78df51f..c9dc413f 100644 --- a/docs/maps/api-camera.md +++ b/docs/maps/api-camera.md @@ -4,7 +4,7 @@ ### Listen to camera updates ``` -WA.camera.onCameraUpdate: Subscription +WA.camera.onCameraUpdate(): Subscription ``` Listens to updates of the camera viewport. It will trigger for every update of the camera's properties (position or scale for instance). An event will be sent. From 5d0aa835a2c5d55a870a36743ffe48f3877869f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dicte=20Q?= <37311765+HimeShaman@users.noreply.github.com> Date: Mon, 3 Jan 2022 14:21:59 +0100 Subject: [PATCH 26/26] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Négrier --- docs/maps/api-camera.md | 5 ++--- docs/maps/api-player.md | 4 ++-- docs/maps/api-room.md | 20 ++++++++++---------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/docs/maps/api-camera.md b/docs/maps/api-camera.md index c9dc413f..cb1fe72d 100644 --- a/docs/maps/api-camera.md +++ b/docs/maps/api-camera.md @@ -19,7 +19,6 @@ The event has the following attributes : Example : ```javascript -WA.camera.onCameraUpdate.subscribe((worldView) => console.log(worldView)); +const subscription = WA.camera.onCameraUpdate().subscribe((worldView) => console.log(worldView)); //later... -WA.camera.onCameraUpdate().unsubscribe(); -``` \ No newline at end of file +subscription.unsubscribe(); \ No newline at end of file diff --git a/docs/maps/api-player.md b/docs/maps/api-player.md index 2bfad463..58d5701a 100644 --- a/docs/maps/api-player.md +++ b/docs/maps/api-player.md @@ -101,8 +101,8 @@ The player's current position is available using the `WA.player.getPosition()` f You need to wait for the end of the initialization before calling `WA.player.getPosition()` ```typescript -WA.onInit().then(() => { - console.log('Position: ', WA.player.getPosition()); +WA.onInit().then(async () => { + console.log('Position: ', await WA.player.getPosition()); }) ``` diff --git a/docs/maps/api-room.md b/docs/maps/api-room.md index bcc53332..7d438a1f 100644 --- a/docs/maps/api-room.md +++ b/docs/maps/api-room.md @@ -215,16 +215,16 @@ interface CreateEmbeddedWebsiteEvent { name: string; // A unique name for this iframe url: string; // The URL the iframe points to. position: { - x: number, // relative to the map or player coordinates, depending on origin - y: number, // relative to the map or player coordinates, depending on origin - width: number, // In pixels, sensitive to zoom level and scale - height: number, // In pixels, sensitive to zoom level and scale + x: number, // In "game" pixels, relative to the map or player coordinates, depending on origin + y: number, // In "game" pixels, relative to the map or player coordinates, depending on origin + width: number, // In "game" pixels + height: number, // In "game" pixels }, visible?: boolean, // Whether to display the iframe or not allowApi?: boolean, // Whether the scripting API should be available to the iframe allow?: string, // The list of feature policies allowed - origin: "player" | "map" // The origin used to place the x and y coordinates of the iframe's top-left corner - scale: number, // A ratio used to resize the iframe (will affect the iframe's width and height when it is rendered + origin: "player" | "map" // The origin used to place the x and y coordinates of the iframe's top-left corner, defaults to "map" + scale: number, // A ratio used to resize the iframe } ``` @@ -269,10 +269,10 @@ class EmbeddedWebsite { visible: boolean; allow: string; allowApi: boolean; - x: number; // In pixels, relative to the map or player coordinates, depending on origin - y: number; // In pixels, relative to the map or player coordinates, depending on origin - width: number; // In pixels, sensitive to zoom level and scale - height: number; // In pixels, sensitive to zoom level and scale + x: number; // In "game" pixels, relative to the map or player coordinates, depending on origin + y: number; // In "game" pixels, relative to the map or player coordinates, depending on origin + width: number; // In "game" pixels + height: number; // In "game" pixels origin: "player" | "map"; scale: number; }