Merge pull request #1525 from thecodingmachine/player-local-storage
Allows to read and write "Player properties" from LocalStorage & Adds a camera API
This commit is contained in:
commit
a28d86c16d
24
docs/maps/api-camera.md
Normal file
24
docs/maps/api-camera.md
Normal file
@ -0,0 +1,24 @@
|
||||
{.section-title.accent.text-primary}
|
||||
# API Camera functions Reference
|
||||
|
||||
### Listen to camera updates
|
||||
|
||||
```
|
||||
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.
|
||||
|
||||
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
|
||||
const subscription = WA.camera.onCameraUpdate().subscribe((worldView) => console.log(worldView));
|
||||
//later...
|
||||
subscription.unsubscribe();
|
@ -86,6 +86,27 @@ WA.onInit().then(() => {
|
||||
})
|
||||
```
|
||||
|
||||
### Get the position of the player
|
||||
```
|
||||
WA.player.getPosition(): Promise<Position>
|
||||
```
|
||||
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()`
|
||||
|
||||
```typescript
|
||||
WA.onInit().then(async () => {
|
||||
console.log('Position: ', await WA.player.getPosition());
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
### Listen to player movement
|
||||
```
|
||||
WA.player.onPlayerMove(callback: HasPlayerMovedEventCallback): void;
|
||||
@ -107,6 +128,30 @@ Example :
|
||||
WA.player.onPlayerMove(console.log);
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
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 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 variable
|
||||
```
|
||||
|
||||
### Set the outline color of the player
|
||||
```
|
||||
WA.player.setOutlineColor(red: number, green: number, blue: number): Promise<void>;
|
||||
|
@ -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)
|
||||
|
@ -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 :
|
||||
<div class="row">
|
||||
@ -12,6 +15,7 @@ Example :
|
||||
</div>
|
||||
|
||||
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<ITiledMap>
|
||||
```
|
||||
@ -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
|
||||
</div>
|
||||
|
||||
`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<number>
|
||||
```
|
||||
|
||||
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<EmbeddedWebsite>
|
||||
```
|
||||
|
||||
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, // 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, defaults to "map"
|
||||
scale: number, // A ratio used to resize the iframe
|
||||
}
|
||||
```
|
||||
|
||||
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<void>
|
||||
|
||||
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 "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;
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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();
|
||||
/**
|
||||
|
@ -30,6 +30,8 @@ import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEv
|
||||
import type { ChangeLayerEvent } from "./ChangeLayerEvent";
|
||||
import type { ChangeZoneEvent } from "./ChangeZoneEvent";
|
||||
import { isColorEvent } from "./ColorEvent";
|
||||
import { isPlayerPosition } from "./PlayerPosition";
|
||||
import type { WasCameraUpdatedEvent } from "./WasCameraUpdatedEvent";
|
||||
|
||||
export interface TypedMessageEvent<T> extends MessageEvent {
|
||||
data: T;
|
||||
@ -50,6 +52,7 @@ export type IframeEventMap = {
|
||||
displayBubble: null;
|
||||
removeBubble: null;
|
||||
onPlayerMove: undefined;
|
||||
onCameraUpdate: undefined;
|
||||
showLayer: LayerEvent;
|
||||
hideLayer: LayerEvent;
|
||||
setProperty: SetPropertyEvent;
|
||||
@ -82,6 +85,7 @@ export interface IframeResponseEventMap {
|
||||
leaveZoneEvent: ChangeZoneEvent;
|
||||
buttonClickedEvent: ButtonClickedEvent;
|
||||
hasPlayerMoved: HasPlayerMovedEvent;
|
||||
wasCameraUpdated: WasCameraUpdatedEvent;
|
||||
menuItemClicked: MenuItemClickedEvent;
|
||||
setVariable: SetVariableEvent;
|
||||
messageTriggered: MessageReferenceEvent;
|
||||
@ -161,6 +165,10 @@ export const iframeQueryMapTypeGuards = {
|
||||
query: tg.isUndefined,
|
||||
answer: tg.isUndefined,
|
||||
},
|
||||
getPlayerPosition: {
|
||||
query: tg.isUndefined,
|
||||
answer: isPlayerPosition,
|
||||
},
|
||||
};
|
||||
|
||||
type GuardedType<T> = T extends (x: unknown) => x is infer T ? T : never;
|
||||
|
10
front/src/Api/Events/PlayerPosition.ts
Normal file
10
front/src/Api/Events/PlayerPosition.ts
Normal file
@ -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<typeof isPlayerPosition>;
|
@ -4,6 +4,7 @@ export const isSetVariableEvent = new tg.IsInterface()
|
||||
.withProperties({
|
||||
key: tg.isString,
|
||||
value: tg.isUnknown,
|
||||
target: tg.isSingletonStringUnion("global", "player"),
|
||||
})
|
||||
.get();
|
||||
/**
|
||||
|
19
front/src/Api/Events/WasCameraUpdatedEvent.ts
Normal file
19
front/src/Api/Events/WasCameraUpdatedEvent.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
export const isWasCameraUpdatedEvent = new tg.IsInterface()
|
||||
.withProperties({
|
||||
x: tg.isNumber,
|
||||
y: tg.isNumber,
|
||||
width: tg.isNumber,
|
||||
height: tg.isNumber,
|
||||
zoom: tg.isNumber,
|
||||
})
|
||||
.get();
|
||||
|
||||
/**
|
||||
* A message sent from the game to the iFrame to notify a movement from the camera.
|
||||
*/
|
||||
|
||||
export type WasCameraUpdatedEvent = tg.GuardedType<typeof isWasCameraUpdatedEvent>;
|
||||
|
||||
export type WasCameraUpdatedEventCallback = (event: WasCameraUpdatedEvent) => void;
|
@ -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 { ChangeZoneEvent } from "./Events/ChangeZoneEvent";
|
||||
|
||||
type AnswererCallback<T extends keyof IframeQueryMap> = (
|
||||
@ -85,6 +86,9 @@ class IframeListener {
|
||||
private readonly _loadSoundStream: Subject<LoadSoundEvent> = new Subject();
|
||||
public readonly loadSoundStream = this._loadSoundStream.asObservable();
|
||||
|
||||
private readonly _trackCameraUpdateStream: Subject<LoadSoundEvent> = new Subject();
|
||||
public readonly trackCameraUpdateStream = this._trackCameraUpdateStream.asObservable();
|
||||
|
||||
private readonly _setTilesStream: Subject<SetTilesEvent> = new Subject();
|
||||
public readonly setTilesStream = this._setTilesStream.asObservable();
|
||||
|
||||
@ -226,6 +230,8 @@ class IframeListener {
|
||||
this._removeBubbleStream.next();
|
||||
} else if (payload.type == "onPlayerMove") {
|
||||
this.sendPlayerMove = true;
|
||||
} else if (payload.type == "onCameraUpdate") {
|
||||
this._trackCameraUpdateStream.next();
|
||||
} else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) {
|
||||
this._setTilesStream.next(payload.data);
|
||||
} else if (payload.type == "modifyEmbeddedWebsite" && isEmbeddedWebsiteEvent(payload.data)) {
|
||||
@ -442,6 +448,13 @@ class IframeListener {
|
||||
}
|
||||
}
|
||||
|
||||
sendCameraUpdated(event: WasCameraUpdatedEvent) {
|
||||
this.postMessage({
|
||||
type: "wasCameraUpdated",
|
||||
data: event,
|
||||
});
|
||||
}
|
||||
|
||||
sendButtonClickedEvent(popupId: number, buttonId: number): void {
|
||||
this.postMessage({
|
||||
type: "buttonClickedEvent",
|
||||
|
@ -12,6 +12,8 @@ export class EmbeddedWebsite {
|
||||
private _allow: string;
|
||||
private _allowApi: boolean;
|
||||
private _position: Rectangle;
|
||||
private readonly origin: "map" | "player" | undefined;
|
||||
private _scale: number;
|
||||
|
||||
constructor(private config: CreateEmbeddedWebsiteEvent) {
|
||||
this.name = config.name;
|
||||
@ -20,6 +22,12 @@ export class EmbeddedWebsite {
|
||||
this._allow = config.allow ?? "";
|
||||
this._allowApi = config.allowApi ?? false;
|
||||
this._position = config.position;
|
||||
this.origin = config.origin;
|
||||
this._scale = config.scale ?? 1;
|
||||
}
|
||||
|
||||
public get url() {
|
||||
return this._url;
|
||||
}
|
||||
|
||||
public set url(url: string) {
|
||||
@ -33,6 +41,10 @@ export class EmbeddedWebsite {
|
||||
});
|
||||
}
|
||||
|
||||
public get visible() {
|
||||
return this._visible;
|
||||
}
|
||||
|
||||
public set visible(visible: boolean) {
|
||||
this._visible = visible;
|
||||
sendToWorkadventure({
|
||||
@ -44,6 +56,10 @@ export class EmbeddedWebsite {
|
||||
});
|
||||
}
|
||||
|
||||
public get x() {
|
||||
return this._position.x;
|
||||
}
|
||||
|
||||
public set x(x: number) {
|
||||
this._position.x = x;
|
||||
sendToWorkadventure({
|
||||
@ -55,6 +71,10 @@ export class EmbeddedWebsite {
|
||||
});
|
||||
}
|
||||
|
||||
public get y() {
|
||||
return this._position.y;
|
||||
}
|
||||
|
||||
public set y(y: number) {
|
||||
this._position.y = y;
|
||||
sendToWorkadventure({
|
||||
@ -66,6 +86,10 @@ export class EmbeddedWebsite {
|
||||
});
|
||||
}
|
||||
|
||||
public get width() {
|
||||
return this._position.width;
|
||||
}
|
||||
|
||||
public set width(width: number) {
|
||||
this._position.width = width;
|
||||
sendToWorkadventure({
|
||||
@ -77,6 +101,10 @@ export class EmbeddedWebsite {
|
||||
});
|
||||
}
|
||||
|
||||
public get height() {
|
||||
return this._position.height;
|
||||
}
|
||||
|
||||
public set height(height: number) {
|
||||
this._position.height = height;
|
||||
sendToWorkadventure({
|
||||
@ -87,4 +115,19 @@ export class EmbeddedWebsite {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public get scale(): number {
|
||||
return this._scale;
|
||||
}
|
||||
|
||||
public set scale(scale: number) {
|
||||
this._scale = scale;
|
||||
sendToWorkadventure({
|
||||
type: "modifyEmbeddedWebsite",
|
||||
data: {
|
||||
name: this.name,
|
||||
scale: this._scale,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
29
front/src/Api/iframe/camera.ts
Normal file
29
front/src/Api/iframe/camera.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution";
|
||||
import { Subject } from "rxjs";
|
||||
import type { WasCameraUpdatedEvent } from "../Events/WasCameraUpdatedEvent";
|
||||
import { apiCallback } from "./registeredCallbacks";
|
||||
import { isWasCameraUpdatedEvent } from "../Events/WasCameraUpdatedEvent";
|
||||
|
||||
const moveStream = new Subject<WasCameraUpdatedEvent>();
|
||||
|
||||
export class WorkAdventureCameraCommands extends IframeApiContribution<WorkAdventureCameraCommands> {
|
||||
callbacks = [
|
||||
apiCallback({
|
||||
type: "wasCameraUpdated",
|
||||
typeChecker: isWasCameraUpdatedEvent,
|
||||
callback: (payloadData) => {
|
||||
moveStream.next(payloadData);
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
onCameraUpdate(): Subject<WasCameraUpdatedEvent> {
|
||||
sendToWorkadventure({
|
||||
type: "onCameraUpdate",
|
||||
data: null,
|
||||
});
|
||||
return moveStream;
|
||||
}
|
||||
}
|
||||
|
||||
export default new WorkAdventureCameraCommands();
|
@ -3,6 +3,7 @@ import type { HasPlayerMovedEvent, HasPlayerMovedEventCallback } from "../Events
|
||||
import { Subject } from "rxjs";
|
||||
import { apiCallback } from "./registeredCallbacks";
|
||||
import { isHasPlayerMovedEvent } from "../Events/HasPlayerMovedEvent";
|
||||
import { createState } from "./state";
|
||||
|
||||
const moveStream = new Subject<HasPlayerMovedEvent>();
|
||||
|
||||
@ -31,6 +32,8 @@ export const setUuid = (_uuid: string | undefined) => {
|
||||
};
|
||||
|
||||
export class WorkadventurePlayerCommands extends IframeApiContribution<WorkadventurePlayerCommands> {
|
||||
readonly state = createState("player");
|
||||
|
||||
callbacks = [
|
||||
apiCallback({
|
||||
type: "hasPlayerMoved",
|
||||
@ -74,6 +77,13 @@ export class WorkadventurePlayerCommands extends IframeApiContribution<Workadven
|
||||
return uuid;
|
||||
}
|
||||
|
||||
async getPosition(): Promise<Position> {
|
||||
return await queryWorkadventure({
|
||||
type: "getPlayerPosition",
|
||||
data: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
get userRoomToken(): string | undefined {
|
||||
if (userRoomToken === undefined) {
|
||||
throw new Error(
|
||||
@ -102,4 +112,9 @@ export class WorkadventurePlayerCommands extends IframeApiContribution<Workadven
|
||||
}
|
||||
}
|
||||
|
||||
export type Position = {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
|
||||
export default new WorkadventurePlayerCommands();
|
||||
|
@ -8,93 +8,101 @@ import { isSetVariableEvent, SetVariableEvent } from "../Events/SetVariableEvent
|
||||
|
||||
import type { ITiledMap } from "../../Phaser/Map/ITiledMap";
|
||||
|
||||
const setVariableResolvers = new Subject<SetVariableEvent>();
|
||||
const variables = new Map<string, unknown>();
|
||||
const variableSubscribers = new Map<string, Subject<unknown>>();
|
||||
|
||||
export const initVariables = (_variables: Map<string, unknown>): 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<WorkadventureStateCommands> {
|
||||
private setVariableResolvers = new Subject<SetVariableEvent>();
|
||||
private variables = new Map<string, unknown>();
|
||||
private variableSubscribers = new Map<string, Subject<unknown>>();
|
||||
|
||||
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<string, unknown>): 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<void> {
|
||||
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<unknown> {
|
||||
let subject = variableSubscribers.get(key);
|
||||
let subject = this.variableSubscribers.get(key);
|
||||
if (subject === undefined) {
|
||||
subject = new Subject<unknown>();
|
||||
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 };
|
||||
}
|
||||
|
@ -1,8 +1,4 @@
|
||||
import type { LoadSoundEvent } from "../Events/LoadSoundEvent";
|
||||
import type { PlaySoundEvent } from "../Events/PlaySoundEvent";
|
||||
import type { StopSoundEvent } from "../Events/StopSoundEvent";
|
||||
import { IframeApiContribution, queryWorkadventure, sendToWorkadventure } from "./IframeApiContribution";
|
||||
import { Sound } from "./Sound/Sound";
|
||||
import { EmbeddedWebsite } from "./Room/EmbeddedWebsite";
|
||||
import type { CreateEmbeddedWebsiteEvent } from "../Events/EmbeddedWebsiteEvent";
|
||||
|
||||
|
@ -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,27 @@ class LocalUserStore {
|
||||
const cameraSetupValues = localStorage.getItem(cameraSetup);
|
||||
return cameraSetupValues != undefined ? JSON.parse(cameraSetupValues) : undefined;
|
||||
}
|
||||
|
||||
getAllUserProperties(): Map<string, unknown> {
|
||||
const result = new Map<string, string>();
|
||||
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: unknown): void {
|
||||
localStorage.setItem(userProperties + "_" + name, JSON.stringify(value));
|
||||
}
|
||||
}
|
||||
|
||||
export const localUserStore = new LocalUserStore();
|
||||
|
@ -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,9 +27,11 @@ export class EmbeddedWebsiteManager {
|
||||
position: {
|
||||
x: website.phaserObject.x,
|
||||
y: website.phaserObject.y,
|
||||
width: rect["width"],
|
||||
height: rect["height"],
|
||||
width: website.phaserObject.width * scale,
|
||||
height: website.phaserObject.height * scale,
|
||||
},
|
||||
origin: website.origin,
|
||||
scale: website.scale,
|
||||
};
|
||||
});
|
||||
|
||||
@ -59,7 +62,9 @@ export class EmbeddedWebsiteManager {
|
||||
createEmbeddedWebsiteEvent.position.height,
|
||||
createEmbeddedWebsiteEvent.visible ?? true,
|
||||
createEmbeddedWebsiteEvent.allowApi ?? false,
|
||||
createEmbeddedWebsiteEvent.allow ?? ""
|
||||
createEmbeddedWebsiteEvent.allow ?? "",
|
||||
createEmbeddedWebsiteEvent.origin ?? "map",
|
||||
createEmbeddedWebsiteEvent.scale ?? 1
|
||||
);
|
||||
}
|
||||
);
|
||||
@ -107,10 +112,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";
|
||||
}
|
||||
}
|
||||
);
|
||||
@ -125,7 +138,9 @@ export class EmbeddedWebsiteManager {
|
||||
height: number,
|
||||
visible: boolean,
|
||||
allowApi: boolean,
|
||||
allow: string
|
||||
allow: string,
|
||||
origin: "map" | "player" | undefined,
|
||||
scale: number | undefined
|
||||
): void {
|
||||
if (this.embeddedWebsites.has(name)) {
|
||||
throw new Error('An embedded website with the name "' + name + '" already exists in your map');
|
||||
@ -135,9 +150,9 @@ export class EmbeddedWebsiteManager {
|
||||
name,
|
||||
url,
|
||||
/*x,
|
||||
y,
|
||||
width,
|
||||
height,*/
|
||||
y,
|
||||
width,
|
||||
height,*/
|
||||
allow,
|
||||
allowApi,
|
||||
visible,
|
||||
@ -147,6 +162,8 @@ export class EmbeddedWebsiteManager {
|
||||
width,
|
||||
height,
|
||||
},
|
||||
origin,
|
||||
scale,
|
||||
};
|
||||
|
||||
const embeddedWebsite = this.doCreateEmbeddedWebsite(embeddedWebsiteEvent, visible);
|
||||
@ -161,22 +178,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";
|
||||
|
||||
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":
|
||||
this.gameScene.CurrentPlayer.add(domElement);
|
||||
break;
|
||||
case "map":
|
||||
default:
|
||||
this.gameScene.add.existing(domElement);
|
||||
}
|
||||
|
||||
const embeddedWebsite = {
|
||||
...embeddedWebsiteEvent,
|
||||
phaserObject: this.gameScene.add
|
||||
.dom(embeddedWebsiteEvent.position.x, embeddedWebsiteEvent.position.y, iframe)
|
||||
.setVisible(visible)
|
||||
.setOrigin(0, 0),
|
||||
phaserObject: domElement,
|
||||
iframe: iframe,
|
||||
};
|
||||
|
||||
if (embeddedWebsiteEvent.allowApi) {
|
||||
iframeListener.registerIframe(iframe);
|
||||
}
|
||||
|
@ -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 { WasCameraUpdatedEvent } from "../../Api/Events/WasCameraUpdatedEvent";
|
||||
|
||||
export interface GameSceneInitInterface {
|
||||
initPosition: PointInterface | null;
|
||||
@ -210,6 +212,8 @@ export class GameScene extends DirtyScene {
|
||||
private objectsByType = new Map<string, ITiledMapObject[]>();
|
||||
private embeddedWebsiteManager!: EmbeddedWebsiteManager;
|
||||
private loader: Loader;
|
||||
private lastCameraEvent: WasCameraUpdatedEvent | undefined;
|
||||
private firstCameraUpdateSent: boolean = false;
|
||||
|
||||
constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) {
|
||||
super({
|
||||
@ -523,7 +527,9 @@ export class GameScene extends DirtyScene {
|
||||
object.height,
|
||||
object.visible,
|
||||
allowApi ?? false,
|
||||
""
|
||||
"",
|
||||
"map",
|
||||
1
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1100,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);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@ -1165,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");
|
||||
@ -1235,6 +1271,7 @@ ${escapedMessage}
|
||||
roomId: this.roomUrl,
|
||||
tags: this.connection ? this.connection.getAllTags() : [],
|
||||
variables: this.sharedVariablesManager.variables,
|
||||
playerVariables: localUserStore.getAllUserProperties(),
|
||||
userRoomToken: this.connection ? this.connection.userRoomToken : "",
|
||||
};
|
||||
});
|
||||
@ -1325,6 +1362,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);
|
||||
});
|
||||
@ -1343,6 +1396,13 @@ ${escapedMessage}
|
||||
this.CurrentPlayer.removeOutlineColor();
|
||||
this.connection?.emitPlayerOutlineColor(null);
|
||||
});
|
||||
|
||||
iframeListener.registerAnswerer("getPlayerPosition", () => {
|
||||
return {
|
||||
x: this.CurrentPlayer.x,
|
||||
y: this.CurrentPlayer.y,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private setPropertyLayer(
|
||||
@ -1467,6 +1527,7 @@ ${escapedMessage}
|
||||
iframeListener.unregisterAnswerer("openCoWebsite");
|
||||
iframeListener.unregisterAnswerer("getCoWebsites");
|
||||
iframeListener.unregisterAnswerer("setPlayerOutline");
|
||||
iframeListener.unregisterAnswerer("setVariable");
|
||||
this.sharedVariablesManager?.close();
|
||||
this.embeddedWebsiteManager?.close();
|
||||
|
||||
@ -1945,6 +2006,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") {
|
||||
@ -1957,6 +2019,7 @@ ${escapedMessage}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private reposition(): void {
|
||||
// Recompute camera offset if needed
|
||||
biggestAvailableAreaStore.recompute();
|
||||
|
@ -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<string, Variable> {
|
||||
|
@ -14,25 +14,29 @@ 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";
|
||||
import camera from "./Api/iframe/camera";
|
||||
|
||||
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<string, unknown>);
|
||||
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<string, unknown>);
|
||||
player.state.initVariables(gameState.playerVariables as Map<string, unknown>);
|
||||
setUserRoomToken(gameState.userRoomToken);
|
||||
});
|
||||
|
||||
const wa = {
|
||||
@ -43,7 +47,8 @@ const wa = {
|
||||
sound,
|
||||
room,
|
||||
player,
|
||||
state,
|
||||
camera,
|
||||
state: globalState,
|
||||
|
||||
onInit(): Promise<void> {
|
||||
return initPromise;
|
||||
@ -225,7 +230,5 @@ window.addEventListener(
|
||||
callback?.callback(payloadData);
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
);
|
||||
|
@ -15,6 +15,8 @@
|
||||
const heightField = document.getElementById('height');
|
||||
const urlField = document.getElementById('url');
|
||||
const visibleField = document.getElementById('visible');
|
||||
const originField = document.getElementById('origin');
|
||||
const scaleField = document.getElementById('scale');
|
||||
|
||||
createButton.addEventListener('click', () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
})
|
||||
</script>
|
||||
@ -72,6 +86,8 @@ width: <input type="text" id="width" value="600" /><br/>
|
||||
height: <input type="text" id="height" value="400" /><br/>
|
||||
URL: <input type="text" id="url" value="https://mensuel.framapad.org/p/rt6c904745-9oxm?lang=en" /><br/>
|
||||
Visible: <input type="checkbox" id="visible" value=1 /><br/>
|
||||
Origin: <input type="text" id="origin" value="map" /><br/>
|
||||
Scale: <input type="text" id="scale" value=1 /><br/>
|
||||
|
||||
<button id="createEmbeddedWebsite">Create embedded website</button>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user