Adding variables (on the front side for now)
This commit is contained in:
parent
1806ef9d7e
commit
ea1460abaf
@ -145,14 +145,15 @@ WA.room.setTiles([
|
||||
]);
|
||||
```
|
||||
|
||||
### Saving / loading metadata
|
||||
### Saving / loading state
|
||||
|
||||
```
|
||||
WA.room.saveMetadata(key : string, data : any): void
|
||||
WA.room.loadMetadata(key : string) : any
|
||||
WA.room.saveVariable(key : string, data : unknown): void
|
||||
WA.room.loadVariable(key : string) : unknown
|
||||
WA.room.onVariableChange(key : string).subscribe((data: unknown) => {}) : Subscription
|
||||
```
|
||||
|
||||
These 2 methods can be used to save and load data related to the current room.
|
||||
These 3 methods can be used to save, load and track changes in variables related to the current room.
|
||||
|
||||
`data` can be any value that is serializable in JSON.
|
||||
|
||||
@ -161,17 +162,62 @@ configuration / metadatas.
|
||||
|
||||
Example :
|
||||
```javascript
|
||||
WA.room.saveMetadata('config', {
|
||||
WA.room.saveVariable('config', {
|
||||
'bottomExitUrl': '/@/org/world/castle',
|
||||
'topExitUrl': '/@/org/world/tower',
|
||||
'enableBirdSound': true
|
||||
});
|
||||
//...
|
||||
let config = WA.room.loadMetadata('config');
|
||||
let config = WA.room.loadVariable('config');
|
||||
```
|
||||
|
||||
{.alert.alert-danger}
|
||||
Important: metadata can only be saved/loaded if an administration server is attached to WorkAdventure. The `WA.room.saveMetadata`
|
||||
and `WA.room.loadMetadata` functions will therefore be available on the hosted version of WorkAdventure, but will not
|
||||
be available in the self-hosted version (unless you decide to code an administration server stub to provide storage for
|
||||
those data)
|
||||
If you are using Typescript, please note that the return type of `loadVariable` is `unknown`. This is
|
||||
for security purpose, as we don't know the type of the variable. In order to use the returned value,
|
||||
you will need to cast it to the correct type (or better, use a [Type guard](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) to actually check at runtime
|
||||
that you get the expected type).
|
||||
|
||||
{.alert.alert-warning}
|
||||
For security reasons, you cannot load or save **any** variable (otherwise, anyone on your map could set any data).
|
||||
Variables storage is subject to an authorization process. Read below to learn more.
|
||||
|
||||
#### Declaring allowed keys
|
||||
|
||||
In order to declare allowed keys related to a room, you need to add a **objects** in an "object layer" of the map.
|
||||
|
||||
Each object will represent a variable.
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<img src="https://workadventu.re/img/docs/object_variable.png" class="figure-img img-fluid rounded" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
TODO: move the image in https://workadventu.re/img/docs
|
||||
|
||||
|
||||
The name of the variable is the name of the object.
|
||||
The object **type** MUST be **variable**.
|
||||
|
||||
You can set a default value for the object in the `default` property.
|
||||
|
||||
Use the `persist` property to save the state of the variable in database. If `persist` is false, the variable will stay
|
||||
in the memory of the WorkAdventure servers but will be wiped out of the memory as soon as the room is empty (or if the
|
||||
server restarts).
|
||||
|
||||
{.alert.alert-info}
|
||||
Do not use `persist` for highly dynamic values that have a short life spawn.
|
||||
|
||||
With `readableBy` and `writableBy`, you control who can read of write in this variable. The property accepts a string
|
||||
representing a "tag". Anyone having this "tag" can read/write in the variable.
|
||||
|
||||
{.alert.alert-warning}
|
||||
`readableBy` and `writableBy` are specific to the public version of WorkAdventure because the notion of tags
|
||||
is not available unless you have an "admin" server (that is not part of the self-hosted version of WorkAdventure).
|
||||
|
||||
Finally, the `jsonSchema` property can contain [a complete JSON schema](https://json-schema.org/) to validate the content of the variable.
|
||||
Trying to set a variable to a value that is not compatible with the schema will fail.
|
||||
|
||||
|
||||
|
||||
|
||||
TODO: document tracking, unsubscriber, etc...
|
||||
|
@ -18,6 +18,9 @@ import type { MenuItemClickedEvent } from "./ui/MenuItemClickedEvent";
|
||||
import type { MenuItemRegisterEvent } from "./ui/MenuItemRegisterEvent";
|
||||
import type { HasPlayerMovedEvent } from "./HasPlayerMovedEvent";
|
||||
import type { SetTilesEvent } from "./SetTilesEvent";
|
||||
import type { SetVariableEvent } from "./SetVariableEvent";
|
||||
import type {InitEvent} from "./InitEvent";
|
||||
|
||||
|
||||
export interface TypedMessageEvent<T> extends MessageEvent {
|
||||
data: T;
|
||||
@ -50,6 +53,9 @@ export type IframeEventMap = {
|
||||
getState: undefined;
|
||||
registerMenuCommand: MenuItemRegisterEvent;
|
||||
setTiles: SetTilesEvent;
|
||||
setVariable: SetVariableEvent;
|
||||
// A script/iframe is ready to receive events
|
||||
ready: null;
|
||||
};
|
||||
export interface IframeEvent<T extends keyof IframeEventMap> {
|
||||
type: T;
|
||||
@ -68,6 +74,8 @@ export interface IframeResponseEventMap {
|
||||
hasPlayerMoved: HasPlayerMovedEvent;
|
||||
dataLayer: DataLayerEvent;
|
||||
menuItemClicked: MenuItemClickedEvent;
|
||||
setVariable: SetVariableEvent;
|
||||
init: InitEvent;
|
||||
}
|
||||
export interface IframeResponseEvent<T extends keyof IframeResponseEventMap> {
|
||||
type: T;
|
||||
|
10
front/src/Api/Events/InitEvent.ts
Normal file
10
front/src/Api/Events/InitEvent.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
export const isInitEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
variables: tg.isObject
|
||||
}).get();
|
||||
/**
|
||||
* A message sent from the game just after an iFrame opens, to send all important data (like variables)
|
||||
*/
|
||||
export type InitEvent = tg.GuardedType<typeof isInitEvent>;
|
18
front/src/Api/Events/SetVariableEvent.ts
Normal file
18
front/src/Api/Events/SetVariableEvent.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
import {isMenuItemRegisterEvent} from "./ui/MenuItemRegisterEvent";
|
||||
|
||||
export const isSetVariableEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
key: tg.isString,
|
||||
value: tg.isUnknown,
|
||||
}).get();
|
||||
/**
|
||||
* A message sent from the iFrame to the game to change the value of the property of the layer
|
||||
*/
|
||||
export type SetVariableEvent = tg.GuardedType<typeof isSetVariableEvent>;
|
||||
|
||||
export const isSetVariableIframeEvent =
|
||||
new tg.IsInterface().withProperties({
|
||||
type: tg.isSingletonString("setVariable"),
|
||||
data: isSetVariableEvent
|
||||
}).get();
|
@ -32,6 +32,7 @@ import type { HasPlayerMovedEvent } from "./Events/HasPlayerMovedEvent";
|
||||
import { isLoadPageEvent } from "./Events/LoadPageEvent";
|
||||
import { handleMenuItemRegistrationEvent, isMenuItemRegisterIframeEvent } from "./Events/ui/MenuItemRegisterEvent";
|
||||
import { SetTilesEvent, isSetTilesEvent } from "./Events/SetTilesEvent";
|
||||
import { isSetVariableIframeEvent, SetVariableEvent } from "./Events/SetVariableEvent";
|
||||
|
||||
type AnswererCallback<T extends keyof IframeQueryMap> = (query: IframeQueryMap[T]['query']) => IframeQueryMap[T]['answer']|Promise<IframeQueryMap[T]['answer']>;
|
||||
|
||||
@ -40,6 +41,9 @@ type AnswererCallback<T extends keyof IframeQueryMap> = (query: IframeQueryMap[T
|
||||
* Also allows to send messages to those iframes.
|
||||
*/
|
||||
class IframeListener {
|
||||
private readonly _readyStream: Subject<HTMLIFrameElement> = new Subject();
|
||||
public readonly readyStream = this._readyStream.asObservable();
|
||||
|
||||
private readonly _chatStream: Subject<ChatEvent> = new Subject();
|
||||
public readonly chatStream = this._chatStream.asObservable();
|
||||
|
||||
@ -106,6 +110,9 @@ class IframeListener {
|
||||
private readonly _setTilesStream: Subject<SetTilesEvent> = new Subject();
|
||||
public readonly setTilesStream = this._setTilesStream.asObservable();
|
||||
|
||||
private readonly _setVariableStream: Subject<SetVariableEvent> = new Subject();
|
||||
public readonly setVariableStream = this._setVariableStream.asObservable();
|
||||
|
||||
private readonly iframes = new Set<HTMLIFrameElement>();
|
||||
private readonly iframeCloseCallbacks = new Map<HTMLIFrameElement, (() => void)[]>();
|
||||
private readonly scripts = new Map<string, HTMLIFrameElement>();
|
||||
@ -187,62 +194,76 @@ class IframeListener {
|
||||
});
|
||||
|
||||
} else if (isIframeEventWrapper(payload)) {
|
||||
if (payload.type === "showLayer" && isLayerEvent(payload.data)) {
|
||||
this._showLayerStream.next(payload.data);
|
||||
} else if (payload.type === "hideLayer" && isLayerEvent(payload.data)) {
|
||||
this._hideLayerStream.next(payload.data);
|
||||
} else if (payload.type === "setProperty" && isSetPropertyEvent(payload.data)) {
|
||||
this._setPropertyStream.next(payload.data);
|
||||
} else if (payload.type === "chat" && isChatEvent(payload.data)) {
|
||||
this._chatStream.next(payload.data);
|
||||
} else if (payload.type === "openPopup" && isOpenPopupEvent(payload.data)) {
|
||||
this._openPopupStream.next(payload.data);
|
||||
} else if (payload.type === "closePopup" && isClosePopupEvent(payload.data)) {
|
||||
this._closePopupStream.next(payload.data);
|
||||
} else if (payload.type === "openTab" && isOpenTabEvent(payload.data)) {
|
||||
scriptUtils.openTab(payload.data.url);
|
||||
} else if (payload.type === "goToPage" && isGoToPageEvent(payload.data)) {
|
||||
scriptUtils.goToPage(payload.data.url);
|
||||
} else if (payload.type === "loadPage" && isLoadPageEvent(payload.data)) {
|
||||
this._loadPageStream.next(payload.data.url);
|
||||
} else if (payload.type === "playSound" && isPlaySoundEvent(payload.data)) {
|
||||
this._playSoundStream.next(payload.data);
|
||||
} else if (payload.type === "stopSound" && isStopSoundEvent(payload.data)) {
|
||||
this._stopSoundStream.next(payload.data);
|
||||
} else if (payload.type === "loadSound" && isLoadSoundEvent(payload.data)) {
|
||||
this._loadSoundStream.next(payload.data);
|
||||
} else if (payload.type === "openCoWebSite" && isOpenCoWebsite(payload.data)) {
|
||||
scriptUtils.openCoWebsite(
|
||||
payload.data.url,
|
||||
foundSrc,
|
||||
payload.data.allowApi,
|
||||
payload.data.allowPolicy
|
||||
);
|
||||
} else if (payload.type === "closeCoWebSite") {
|
||||
scriptUtils.closeCoWebSite();
|
||||
} else if (payload.type === "disablePlayerControls") {
|
||||
this._disablePlayerControlStream.next();
|
||||
} else if (payload.type === "restorePlayerControls") {
|
||||
this._enablePlayerControlStream.next();
|
||||
} else if (payload.type === "displayBubble") {
|
||||
this._displayBubbleStream.next();
|
||||
} else if (payload.type === "removeBubble") {
|
||||
this._removeBubbleStream.next();
|
||||
} else if (payload.type == "onPlayerMove") {
|
||||
this.sendPlayerMove = true;
|
||||
} else if (payload.type == "getDataLayer") {
|
||||
this._dataLayerChangeStream.next();
|
||||
} else if (isMenuItemRegisterIframeEvent(payload)) {
|
||||
const data = payload.data.menutItem;
|
||||
// @ts-ignore
|
||||
this.iframeCloseCallbacks.get(iframe).push(() => {
|
||||
this._unregisterMenuCommandStream.next(data);
|
||||
});
|
||||
handleMenuItemRegistrationEvent(payload.data);
|
||||
} else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) {
|
||||
this._setTilesStream.next(payload.data);
|
||||
if (payload.type === 'ready') {
|
||||
this._readyStream.next();
|
||||
} else if (payload.type === "showLayer" && isLayerEvent(payload.data)) {
|
||||
this._showLayerStream.next(payload.data);
|
||||
} else if (payload.type === "hideLayer" && isLayerEvent(payload.data)) {
|
||||
this._hideLayerStream.next(payload.data);
|
||||
} else if (payload.type === "setProperty" && isSetPropertyEvent(payload.data)) {
|
||||
this._setPropertyStream.next(payload.data);
|
||||
} else if (payload.type === "chat" && isChatEvent(payload.data)) {
|
||||
this._chatStream.next(payload.data);
|
||||
} else if (payload.type === "openPopup" && isOpenPopupEvent(payload.data)) {
|
||||
this._openPopupStream.next(payload.data);
|
||||
} else if (payload.type === "closePopup" && isClosePopupEvent(payload.data)) {
|
||||
this._closePopupStream.next(payload.data);
|
||||
} else if (payload.type === "openTab" && isOpenTabEvent(payload.data)) {
|
||||
scriptUtils.openTab(payload.data.url);
|
||||
} else if (payload.type === "goToPage" && isGoToPageEvent(payload.data)) {
|
||||
scriptUtils.goToPage(payload.data.url);
|
||||
} else if (payload.type === "loadPage" && isLoadPageEvent(payload.data)) {
|
||||
this._loadPageStream.next(payload.data.url);
|
||||
} else if (payload.type === "playSound" && isPlaySoundEvent(payload.data)) {
|
||||
this._playSoundStream.next(payload.data);
|
||||
} else if (payload.type === "stopSound" && isStopSoundEvent(payload.data)) {
|
||||
this._stopSoundStream.next(payload.data);
|
||||
} else if (payload.type === "loadSound" && isLoadSoundEvent(payload.data)) {
|
||||
this._loadSoundStream.next(payload.data);
|
||||
} else if (payload.type === "openCoWebSite" && isOpenCoWebsite(payload.data)) {
|
||||
scriptUtils.openCoWebsite(
|
||||
payload.data.url,
|
||||
foundSrc,
|
||||
payload.data.allowApi,
|
||||
payload.data.allowPolicy
|
||||
);
|
||||
} else if (payload.type === "closeCoWebSite") {
|
||||
scriptUtils.closeCoWebSite();
|
||||
} else if (payload.type === "disablePlayerControls") {
|
||||
this._disablePlayerControlStream.next();
|
||||
} else if (payload.type === "restorePlayerControls") {
|
||||
this._enablePlayerControlStream.next();
|
||||
} else if (payload.type === "displayBubble") {
|
||||
this._displayBubbleStream.next();
|
||||
} else if (payload.type === "removeBubble") {
|
||||
this._removeBubbleStream.next();
|
||||
} else if (payload.type == "onPlayerMove") {
|
||||
this.sendPlayerMove = true;
|
||||
} else if (payload.type == "getDataLayer") {
|
||||
this._dataLayerChangeStream.next();
|
||||
} else if (isMenuItemRegisterIframeEvent(payload)) {
|
||||
const data = payload.data.menutItem;
|
||||
// @ts-ignore
|
||||
this.iframeCloseCallbacks.get(iframe).push(() => {
|
||||
this._unregisterMenuCommandStream.next(data);
|
||||
});
|
||||
handleMenuItemRegistrationEvent(payload.data);
|
||||
} else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) {
|
||||
this._setTilesStream.next(payload.data);
|
||||
} else if (isSetVariableIframeEvent(payload)) {
|
||||
this._setVariableStream.next(payload.data);
|
||||
|
||||
// Let's dispatch the message to the other iframes
|
||||
for (iframe of this.iframes) {
|
||||
if (iframe.contentWindow !== message.source) {
|
||||
iframe.contentWindow?.postMessage({
|
||||
'type': 'setVariable',
|
||||
'data': payload.data
|
||||
}, '*');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
@ -394,6 +415,13 @@ class IframeListener {
|
||||
});
|
||||
}
|
||||
|
||||
setVariable(setVariableEvent: SetVariableEvent) {
|
||||
this.postMessage({
|
||||
'type': 'setVariable',
|
||||
'data': setVariableEvent
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the message... to all allowed iframes.
|
||||
*/
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Subject } from "rxjs";
|
||||
import {Observable, Subject} from "rxjs";
|
||||
|
||||
import { isDataLayerEvent } from "../Events/DataLayerEvent";
|
||||
import { EnterLeaveEvent, isEnterLeaveEvent } from "../Events/EnterLeaveEvent";
|
||||
@ -6,6 +6,9 @@ import { isGameStateEvent } from "../Events/GameStateEvent";
|
||||
|
||||
import {IframeApiContribution, queryWorkadventure, sendToWorkadventure} from "./IframeApiContribution";
|
||||
import { apiCallback } from "./registeredCallbacks";
|
||||
import type {LayerEvent} from "../Events/LayerEvent";
|
||||
import type {SetPropertyEvent} from "../Events/setPropertyEvent";
|
||||
import {isSetVariableEvent, SetVariableEvent} from "../Events/SetVariableEvent";
|
||||
|
||||
import type { ITiledMap } from "../../Phaser/Map/ITiledMap";
|
||||
import type { DataLayerEvent } from "../Events/DataLayerEvent";
|
||||
@ -15,6 +18,9 @@ const enterStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subj
|
||||
const leaveStreams: Map<string, Subject<EnterLeaveEvent>> = new Map<string, Subject<EnterLeaveEvent>>();
|
||||
const dataLayerResolver = new Subject<DataLayerEvent>();
|
||||
const stateResolvers = new Subject<GameStateEvent>();
|
||||
const setVariableResolvers = new Subject<SetVariableEvent>();
|
||||
const variables = new Map<string, unknown>();
|
||||
const variableSubscribers = new Map<string, Subject<unknown>>();
|
||||
|
||||
let immutableDataPromise: Promise<GameStateEvent> | undefined = undefined;
|
||||
|
||||
@ -52,6 +58,14 @@ function getDataLayer(): Promise<DataLayerEvent> {
|
||||
});
|
||||
}
|
||||
|
||||
setVariableResolvers.subscribe((event) => {
|
||||
variables.set(event.key, event.value);
|
||||
const subject = variableSubscribers.get(event.key);
|
||||
if (subject !== undefined) {
|
||||
subject.next(event.value);
|
||||
}
|
||||
});
|
||||
|
||||
export class WorkadventureRoomCommands extends IframeApiContribution<WorkadventureRoomCommands> {
|
||||
callbacks = [
|
||||
apiCallback({
|
||||
@ -75,6 +89,13 @@ export class WorkadventureRoomCommands extends IframeApiContribution<Workadventu
|
||||
dataLayerResolver.next(payloadData);
|
||||
},
|
||||
}),
|
||||
apiCallback({
|
||||
type: "setVariable",
|
||||
typeChecker: isSetVariableEvent,
|
||||
callback: (payloadData) => {
|
||||
setVariableResolvers.next(payloadData);
|
||||
}
|
||||
}),
|
||||
];
|
||||
|
||||
onEnterZone(name: string, callback: () => void): void {
|
||||
@ -132,6 +153,30 @@ export class WorkadventureRoomCommands extends IframeApiContribution<Workadventu
|
||||
data: tiles,
|
||||
});
|
||||
}
|
||||
|
||||
saveVariable(key : string, value : unknown): void {
|
||||
variables.set(key, value);
|
||||
sendToWorkadventure({
|
||||
type: 'setVariable',
|
||||
data: {
|
||||
key,
|
||||
value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
loadVariable(key: string): unknown {
|
||||
return variables.get(key);
|
||||
}
|
||||
|
||||
onVariableChange(key: string): Observable<unknown> {
|
||||
let subject = variableSubscribers.get(key);
|
||||
if (subject === undefined) {
|
||||
subject = new Subject<unknown>();
|
||||
variableSubscribers.set(key, subject);
|
||||
}
|
||||
return subject.asObservable();
|
||||
}
|
||||
}
|
||||
|
||||
export default new WorkadventureRoomCommands();
|
||||
|
@ -91,6 +91,8 @@ import { soundManager } from "./SoundManager";
|
||||
import { peerStore, screenSharingPeerStore } from "../../Stores/PeerStore";
|
||||
import { videoFocusStore } from "../../Stores/VideoFocusStore";
|
||||
import { biggestAvailableAreaStore } from "../../Stores/BiggestAvailableAreaStore";
|
||||
import { SharedVariablesManager } from "./SharedVariablesManager";
|
||||
import type {InitEvent} from "../../Api/Events/InitEvent";
|
||||
|
||||
export interface GameSceneInitInterface {
|
||||
initPosition: PointInterface | null;
|
||||
@ -199,7 +201,8 @@ export class GameScene extends DirtyScene {
|
||||
private mapTransitioning: boolean = false; //used to prevent transitions happenning at the same time.
|
||||
private emoteManager!: EmoteManager;
|
||||
private preloading: boolean = true;
|
||||
startPositionCalculator!: StartPositionCalculator;
|
||||
private startPositionCalculator!: StartPositionCalculator;
|
||||
private sharedVariablesManager!: SharedVariablesManager;
|
||||
|
||||
constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) {
|
||||
super({
|
||||
@ -396,6 +399,23 @@ export class GameScene extends DirtyScene {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
this.iframeSubscriptionList.push(iframeListener.readyStream.subscribe((iframe) => {
|
||||
this.connectionAnswerPromise.then(connection => {
|
||||
// Generate init message for an iframe
|
||||
// TODO: merge with GameStateEvent
|
||||
const initEvent: InitEvent = {
|
||||
variables: this.sharedVariablesManager.variables
|
||||
}
|
||||
|
||||
});
|
||||
// TODO: SEND INIT MESSAGE TO IFRAMES ONLY WHEN CONNECTION IS ESTABLISHED
|
||||
// TODO: SEND INIT MESSAGE TO IFRAMES ONLY WHEN CONNECTION IS ESTABLISHED
|
||||
// TODO: SEND INIT MESSAGE TO IFRAMES ONLY WHEN CONNECTION IS ESTABLISHED
|
||||
// TODO: SEND INIT MESSAGE TO IFRAMES ONLY WHEN CONNECTION IS ESTABLISHED
|
||||
// TODO: SEND INIT MESSAGE TO IFRAMES ONLY WHEN CONNECTION IS ESTABLISHED
|
||||
}));
|
||||
|
||||
// Now, let's load the script, if any
|
||||
const scripts = this.getScriptUrls(this.mapFile);
|
||||
for (const script of scripts) {
|
||||
@ -706,6 +726,9 @@ export class GameScene extends DirtyScene {
|
||||
this.gameMap.setPosition(event.x, event.y);
|
||||
});
|
||||
|
||||
// Set up variables manager
|
||||
this.sharedVariablesManager = new SharedVariablesManager(this.connection, this.gameMap);
|
||||
|
||||
//this.initUsersPosition(roomJoinedMessage.users);
|
||||
this.connectionAnswerPromiseResolve(onConnect.room);
|
||||
// Analyze tags to find if we are admin. If yes, show console.
|
||||
@ -1148,6 +1171,7 @@ ${escapedMessage}
|
||||
this.peerStoreUnsubscribe();
|
||||
this.biggestAvailableAreaStoreUnsubscribe();
|
||||
iframeListener.unregisterAnswerer('getState');
|
||||
this.sharedVariablesManager?.close();
|
||||
|
||||
mediaManager.hideGameOverlay();
|
||||
|
||||
|
59
front/src/Phaser/Game/SharedVariablesManager.ts
Normal file
59
front/src/Phaser/Game/SharedVariablesManager.ts
Normal file
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Handles variables shared between the scripting API and the server.
|
||||
*/
|
||||
import type {RoomConnection} from "../../Connexion/RoomConnection";
|
||||
import {iframeListener} from "../../Api/IframeListener";
|
||||
import type {Subscription} from "rxjs";
|
||||
import type {GameMap} from "./GameMap";
|
||||
import type {ITiledMapObject} from "../Map/ITiledMap";
|
||||
|
||||
export class SharedVariablesManager {
|
||||
private _variables = new Map<string, unknown>();
|
||||
private iframeListenerSubscription: Subscription;
|
||||
private variableObjects: Map<string, ITiledMapObject>;
|
||||
|
||||
constructor(private roomConnection: RoomConnection, private gameMap: GameMap) {
|
||||
// We initialize the list of variable object at room start. The objects cannot be edited later
|
||||
// (otherwise, this would cause a security issue if the scripting API can edit this list of objects)
|
||||
this.variableObjects = SharedVariablesManager.findVariablesInMap(gameMap);
|
||||
|
||||
// When a variable is modified from an iFrame
|
||||
this.iframeListenerSubscription = iframeListener.setVariableStream.subscribe((event) => {
|
||||
const key = event.key;
|
||||
|
||||
if (!this.variableObjects.has(key)) {
|
||||
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);
|
||||
}
|
||||
|
||||
this._variables.set(key, event.value);
|
||||
// TODO: dispatch to the room connection.
|
||||
});
|
||||
}
|
||||
|
||||
private static findVariablesInMap(gameMap: GameMap): Map<string, ITiledMapObject> {
|
||||
const objects = new Map<string, ITiledMapObject>();
|
||||
for (const layer of gameMap.getMap().layers) {
|
||||
if (layer.type === 'objectgroup') {
|
||||
for (const object of layer.objects) {
|
||||
if (object.type === 'variable') {
|
||||
// We store a copy of the object (to make it immutable)
|
||||
objects.set(object.name, {...object});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return objects;
|
||||
}
|
||||
|
||||
|
||||
public close(): void {
|
||||
this.iframeListenerSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
get variables(): Map<string, unknown> {
|
||||
return this._variables;
|
||||
}
|
||||
}
|
@ -206,3 +206,9 @@ window.addEventListener(
|
||||
// ...
|
||||
}
|
||||
);
|
||||
|
||||
// Notify WorkAdventure that we are ready to receive data
|
||||
sendToWorkadventure({
|
||||
type: 'ready',
|
||||
data: null
|
||||
});
|
||||
|
11
maps/tests/Variables/script.js
Normal file
11
maps/tests/Variables/script.js
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
console.log('Trying to set variable "not_exists". This should display an error in the console.')
|
||||
WA.room.saveVariable('not_exists', 'foo');
|
||||
|
||||
console.log('Trying to set variable "config". This should work.');
|
||||
WA.room.saveVariable('config', {'foo': 'bar'});
|
||||
|
||||
console.log('Trying to read variable "config". This should display a {"foo": "bar"} object.');
|
||||
console.log(WA.room.loadVariable('config'));
|
||||
|
||||
|
112
maps/tests/Variables/variables.json
Normal file
112
maps/tests/Variables/variables.json
Normal file
@ -0,0 +1,112 @@
|
||||
{ "compressionlevel":-1,
|
||||
"height":10,
|
||||
"infinite":false,
|
||||
"layers":[
|
||||
{
|
||||
"data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
"height":10,
|
||||
"id":1,
|
||||
"name":"floor",
|
||||
"opacity":1,
|
||||
"type":"tilelayer",
|
||||
"visible":true,
|
||||
"width":10,
|
||||
"x":0,
|
||||
"y":0
|
||||
},
|
||||
{
|
||||
"data":[0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 23, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
"height":10,
|
||||
"id":6,
|
||||
"name":"triggerZone",
|
||||
"opacity":1,
|
||||
"properties":[
|
||||
{
|
||||
"name":"zone",
|
||||
"type":"string",
|
||||
"value":"myTrigger"
|
||||
}],
|
||||
"type":"tilelayer",
|
||||
"visible":true,
|
||||
"width":10,
|
||||
"x":0,
|
||||
"y":0
|
||||
},
|
||||
{
|
||||
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
"height":10,
|
||||
"id":2,
|
||||
"name":"start",
|
||||
"opacity":1,
|
||||
"type":"tilelayer",
|
||||
"visible":true,
|
||||
"width":10,
|
||||
"x":0,
|
||||
"y":0
|
||||
},
|
||||
{
|
||||
"draworder":"topdown",
|
||||
"id":3,
|
||||
"name":"floorLayer",
|
||||
"objects":[
|
||||
{
|
||||
"height":67,
|
||||
"id":3,
|
||||
"name":"",
|
||||
"rotation":0,
|
||||
"text":
|
||||
{
|
||||
"fontfamily":"Sans Serif",
|
||||
"pixelsize":11,
|
||||
"text":"Test:\nTODO",
|
||||
"wrap":true
|
||||
},
|
||||
"type":"",
|
||||
"visible":true,
|
||||
"width":252.4375,
|
||||
"x":2.78125,
|
||||
"y":2.5
|
||||
},
|
||||
{
|
||||
"id":5,
|
||||
"template":"config.tx",
|
||||
"x":57.5,
|
||||
"y":111
|
||||
}],
|
||||
"opacity":1,
|
||||
"type":"objectgroup",
|
||||
"visible":true,
|
||||
"x":0,
|
||||
"y":0
|
||||
}],
|
||||
"nextlayerid":8,
|
||||
"nextobjectid":6,
|
||||
"orientation":"orthogonal",
|
||||
"properties":[
|
||||
{
|
||||
"name":"script",
|
||||
"type":"string",
|
||||
"value":"script.js"
|
||||
}],
|
||||
"renderorder":"right-down",
|
||||
"tiledversion":"2021.03.23",
|
||||
"tileheight":32,
|
||||
"tilesets":[
|
||||
{
|
||||
"columns":11,
|
||||
"firstgid":1,
|
||||
"image":"..\/tileset1.png",
|
||||
"imageheight":352,
|
||||
"imagewidth":352,
|
||||
"margin":0,
|
||||
"name":"tileset1",
|
||||
"spacing":0,
|
||||
"tilecount":121,
|
||||
"tileheight":32,
|
||||
"tilewidth":32
|
||||
}],
|
||||
"tilewidth":32,
|
||||
"type":"map",
|
||||
"version":1.5,
|
||||
"width":10
|
||||
}
|
@ -202,6 +202,14 @@
|
||||
<a href="#" class="testLink" data-testmap="Metadata/setTiles.json" target="_blank">Test set tiles</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<input type="radio" name="test-variables"> Success <input type="radio" name="test-variables"> Failure <input type="radio" name="test-variables" checked> Pending
|
||||
</td>
|
||||
<td>
|
||||
<a href="#" class="testLink" data-testmap="Variables/variables.json" target="_blank">Testing scripting variables</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
|
@ -94,6 +94,7 @@ message ClientToServerMessage {
|
||||
ReportPlayerMessage reportPlayerMessage = 11;
|
||||
QueryJitsiJwtMessage queryJitsiJwtMessage = 12;
|
||||
EmotePromptMessage emotePromptMessage = 13;
|
||||
VariableMessage variableMessage = 14;
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,6 +108,11 @@ message ItemEventMessage {
|
||||
string parametersJson = 4;
|
||||
}
|
||||
|
||||
message VariableMessage {
|
||||
string name = 1;
|
||||
string value = 2;
|
||||
}
|
||||
|
||||
message PlayGlobalMessage {
|
||||
string id = 1;
|
||||
string type = 2;
|
||||
@ -133,6 +139,7 @@ message SubMessage {
|
||||
UserLeftMessage userLeftMessage = 5;
|
||||
ItemEventMessage itemEventMessage = 6;
|
||||
EmoteEventMessage emoteEventMessage = 7;
|
||||
VariableMessage variableMessage = 8;
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,6 +186,7 @@ message RoomJoinedMessage {
|
||||
repeated ItemStateMessage item = 3;
|
||||
int32 currentUserId = 4;
|
||||
repeated string tag = 5;
|
||||
repeated VariableMessage = 6;
|
||||
}
|
||||
|
||||
message WebRtcStartMessage {
|
||||
@ -333,6 +341,7 @@ message PusherToBackMessage {
|
||||
SendUserMessage sendUserMessage = 12;
|
||||
BanUserMessage banUserMessage = 13;
|
||||
EmotePromptMessage emotePromptMessage = 14;
|
||||
VariableMessage variableMessage = 15;
|
||||
}
|
||||
}
|
||||
|
||||
@ -351,6 +360,7 @@ message SubToPusherMessage {
|
||||
SendUserMessage sendUserMessage = 7;
|
||||
BanUserMessage banUserMessage = 8;
|
||||
EmoteEventMessage emoteEventMessage = 9;
|
||||
VariableMessage variableMessage = 10;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user