2021-07-22 11:24:30 +02:00
|
|
|
import type { RoomConnection } from "../../Connexion/RoomConnection";
|
|
|
|
import { iframeListener } from "../../Api/IframeListener";
|
|
|
|
import type { GameMap } from "./GameMap";
|
2021-12-01 14:48:14 +01:00
|
|
|
import type { ITiledMapLayer, ITiledMapObject } from "../Map/ITiledMap";
|
2021-11-02 10:51:02 +01:00
|
|
|
import { GameMapProperties } from "./GameMapProperties";
|
2021-07-05 17:25:23 +02:00
|
|
|
|
|
|
|
interface Variable {
|
2021-07-22 11:24:30 +02:00
|
|
|
defaultValue: unknown;
|
|
|
|
readableBy?: string;
|
|
|
|
writableBy?: string;
|
2021-07-05 17:25:23 +02:00
|
|
|
}
|
2021-07-02 11:31:44 +02:00
|
|
|
|
2021-07-22 11:24:30 +02:00
|
|
|
/**
|
|
|
|
* Stores variables and provides a bridge between scripts and the pusher server.
|
|
|
|
*/
|
2021-07-02 11:31:44 +02:00
|
|
|
export class SharedVariablesManager {
|
|
|
|
private _variables = new Map<string, unknown>();
|
2021-07-05 17:25:23 +02:00
|
|
|
private variableObjects: Map<string, Variable>;
|
2021-07-02 11:31:44 +02:00
|
|
|
|
2021-07-22 11:24:30 +02:00
|
|
|
constructor(
|
|
|
|
private roomConnection: RoomConnection,
|
|
|
|
private gameMap: GameMap,
|
|
|
|
serverVariables: Map<string, unknown>
|
|
|
|
) {
|
2021-07-02 11:31:44 +02:00
|
|
|
// 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);
|
|
|
|
|
2021-07-05 17:25:23 +02:00
|
|
|
// Let's initialize default values
|
|
|
|
for (const [name, variableObject] of this.variableObjects.entries()) {
|
2021-07-19 10:16:43 +02:00
|
|
|
if (variableObject.readableBy && !this.roomConnection.hasTag(variableObject.readableBy)) {
|
|
|
|
// Do not initialize default value for variables that are not readable
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-07-05 17:25:23 +02:00
|
|
|
this._variables.set(name, variableObject.defaultValue);
|
|
|
|
}
|
|
|
|
|
2021-07-07 17:17:28 +02:00
|
|
|
// Override default values with the variables from the server:
|
|
|
|
for (const [name, value] of serverVariables) {
|
|
|
|
this._variables.set(name, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
roomConnection.onSetVariable((name, value) => {
|
|
|
|
this._variables.set(name, value);
|
|
|
|
|
|
|
|
// On server change, let's notify the iframes
|
|
|
|
iframeListener.setVariable({
|
|
|
|
key: name,
|
|
|
|
value: value,
|
2021-07-22 11:24:30 +02:00
|
|
|
});
|
2021-07-07 17:17:28 +02:00
|
|
|
});
|
|
|
|
|
2021-07-02 11:31:44 +02:00
|
|
|
// When a variable is modified from an iFrame
|
2021-07-23 11:50:03 +02:00
|
|
|
iframeListener.registerAnswerer("setVariable", (event, source) => {
|
2021-07-02 11:31:44 +02:00
|
|
|
const key = event.key;
|
|
|
|
|
2021-07-06 10:58:12 +02:00
|
|
|
const object = this.variableObjects.get(key);
|
|
|
|
|
|
|
|
if (object === undefined) {
|
2021-07-22 11:24:30 +02:00
|
|
|
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"';
|
2021-07-02 11:31:44 +02:00
|
|
|
console.error(errMsg);
|
|
|
|
throw new Error(errMsg);
|
|
|
|
}
|
|
|
|
|
2021-07-06 10:58:12 +02:00
|
|
|
if (object.writableBy && !this.roomConnection.hasTag(object.writableBy)) {
|
2021-07-22 11:24:30 +02:00
|
|
|
const errMsg =
|
|
|
|
'A script is trying to modify variable "' +
|
|
|
|
key +
|
|
|
|
'" but this variable is only writable for users with tag "' +
|
|
|
|
object.writableBy +
|
|
|
|
'".';
|
2021-07-06 10:58:12 +02:00
|
|
|
console.error(errMsg);
|
|
|
|
throw new Error(errMsg);
|
|
|
|
}
|
|
|
|
|
2021-07-23 11:50:03 +02:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2021-07-02 11:31:44 +02:00
|
|
|
this._variables.set(key, event.value);
|
2021-07-07 17:17:28 +02:00
|
|
|
|
|
|
|
// Dispatch to the room connection.
|
2021-07-06 10:58:12 +02:00
|
|
|
this.roomConnection.emitSetVariableEvent(key, event.value);
|
2021-07-23 11:50:03 +02:00
|
|
|
|
|
|
|
// Dispatch to other iframes
|
|
|
|
iframeListener.dispatchVariableToOtherIframes(key, event.value, source);
|
2021-07-02 11:31:44 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-07-05 17:25:23 +02:00
|
|
|
private static findVariablesInMap(gameMap: GameMap): Map<string, Variable> {
|
|
|
|
const objects = new Map<string, Variable>();
|
2021-07-02 11:31:44 +02:00
|
|
|
for (const layer of gameMap.getMap().layers) {
|
2021-09-02 17:29:29 +02:00
|
|
|
this.recursiveFindVariablesInLayer(layer, objects);
|
|
|
|
}
|
|
|
|
return objects;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static recursiveFindVariablesInLayer(layer: ITiledMapLayer, objects: Map<string, Variable>): void {
|
|
|
|
if (layer.type === "objectgroup") {
|
|
|
|
for (const object of layer.objects) {
|
|
|
|
if (object.type === "variable") {
|
|
|
|
if (object.template) {
|
|
|
|
console.warn(
|
|
|
|
'Warning, a variable object is using a Tiled "template". WorkAdventure does not support objects generated from Tiled templates.'
|
|
|
|
);
|
|
|
|
continue;
|
2021-07-02 11:31:44 +02:00
|
|
|
}
|
2021-09-02 17:29:29 +02:00
|
|
|
|
|
|
|
// We store a copy of the object (to make it immutable)
|
|
|
|
objects.set(object.name, this.iTiledObjectToVariable(object));
|
2021-07-02 11:31:44 +02:00
|
|
|
}
|
|
|
|
}
|
2021-09-02 17:29:29 +02:00
|
|
|
} else if (layer.type === "group") {
|
|
|
|
for (const innerLayer of layer.layers) {
|
|
|
|
this.recursiveFindVariablesInLayer(innerLayer, objects);
|
|
|
|
}
|
2021-07-02 11:31:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-05 17:25:23 +02:00
|
|
|
private static iTiledObjectToVariable(object: ITiledMapObject): Variable {
|
|
|
|
const variable: Variable = {
|
2021-07-22 11:24:30 +02:00
|
|
|
defaultValue: undefined,
|
2021-07-05 17:25:23 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
if (object.properties) {
|
|
|
|
for (const property of object.properties) {
|
2021-07-06 10:58:12 +02:00
|
|
|
const value = property.value;
|
|
|
|
switch (property.name) {
|
2021-11-02 10:51:02 +01:00
|
|
|
case GameMapProperties.DEFAULT:
|
2021-07-06 10:58:12 +02:00
|
|
|
variable.defaultValue = value;
|
|
|
|
break;
|
2021-11-02 10:51:02 +01:00
|
|
|
case GameMapProperties.WRITABLE_BY:
|
2021-07-22 11:24:30 +02:00
|
|
|
if (typeof value !== "string") {
|
|
|
|
throw new Error(
|
|
|
|
'The writableBy property of variable "' + object.name + '" must be a string'
|
|
|
|
);
|
2021-07-06 10:58:12 +02:00
|
|
|
}
|
|
|
|
if (value) {
|
|
|
|
variable.writableBy = value;
|
|
|
|
}
|
|
|
|
break;
|
2021-11-02 10:51:02 +01:00
|
|
|
case GameMapProperties.READABLE_BY:
|
2021-07-22 11:24:30 +02:00
|
|
|
if (typeof value !== "string") {
|
|
|
|
throw new Error(
|
|
|
|
'The readableBy property of variable "' + object.name + '" must be a string'
|
|
|
|
);
|
2021-07-06 10:58:12 +02:00
|
|
|
}
|
|
|
|
if (value) {
|
|
|
|
variable.readableBy = value;
|
|
|
|
}
|
|
|
|
break;
|
2021-07-05 17:25:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return variable;
|
|
|
|
}
|
2021-07-02 11:31:44 +02:00
|
|
|
|
|
|
|
public close(): void {
|
2021-07-22 11:24:30 +02:00
|
|
|
iframeListener.unregisterAnswerer("setVariable");
|
2021-07-02 11:31:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
get variables(): Map<string, unknown> {
|
|
|
|
return this._variables;
|
|
|
|
}
|
|
|
|
}
|