Merge branch 'develop' of github.com:thecodingmachine/workadventure

This commit is contained in:
_Bastler 2022-01-03 19:10:22 +01:00
commit 9b62e5f4ba
52 changed files with 1449 additions and 863 deletions

View File

@ -39,7 +39,7 @@ jobs:
working-directory: "messages"
- name: "Build proto messages"
run: yarn run proto && yarn run copy-to-front && yarn run json-copy-to-front
run: yarn run ts-proto && yarn run copy-to-front-ts-proto && yarn run json-copy-to-front
working-directory: "messages"
- name: "Create index.html"

View File

@ -12,6 +12,7 @@ on:
jobs:
start-runner:
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false)
name: Start self-hosted EC2 runner
runs-on: ubuntu-latest
outputs:
@ -109,12 +110,14 @@ jobs:
if: ${{ always() }} # required to stop the runner even if the error happened in the previous jobs
steps:
- name: Configure AWS credentials
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false)
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Stop EC2 runner
if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false)
uses: machulav/ec2-github-runner@v2
with:
mode: stop

View File

@ -36,7 +36,7 @@ jobs:
working-directory: "messages"
- name: "Build proto messages"
run: yarn run proto && yarn run copy-to-front && yarn run json-copy-to-front
run: yarn run ts-proto && yarn run copy-to-front-ts-proto && yarn run json-copy-to-front
working-directory: "messages"
- name: "Create index.html"

View File

@ -106,11 +106,6 @@ const roomManager: IRoomManagerServer = {
user,
message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage
);
} else if (message.hasPlayglobalmessage()) {
socketManager.emitPlayGlobalMessage(
room,
message.getPlayglobalmessage() as PlayGlobalMessage
);
} else if (message.hasQueryjitsijwtmessage()) {
socketManager.handleQueryJitsiJwtMessage(
user,

View File

@ -531,15 +531,6 @@ export class SocketManager {
}
}
emitPlayGlobalMessage(room: GameRoom, playGlobalMessage: PlayGlobalMessage) {
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setPlayglobalmessage(playGlobalMessage);
for (const [id, user] of room.getUsers().entries()) {
user.socket.write(serverToClientMessage);
}
}
public getWorlds(): Map<string, PromiseLike<GameRoom>> {
return this.roomsPromises;
}

24
docs/maps/api-camera.md Normal file
View 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();

View File

@ -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>;

View File

@ -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)

View File

@ -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.

View File

@ -2,7 +2,7 @@
FROM node:14-buster-slim as messages
WORKDIR /usr/src
COPY messages .
RUN yarn install && yarn proto
RUN yarn install && yarn ts-proto
# webpack build
FROM node:14-buster-slim as builder
@ -10,9 +10,11 @@ WORKDIR /usr/src
COPY front/yarn.lock front/package.json ./
RUN yarn install --ignore-engines
COPY front .
COPY --from=messages /usr/src/generated src/Messages/generated
COPY --from=messages /usr/src/JsonMessages src/Messages/JsonMessages
COPY --chown=docker:docker front .
COPY --from=messages --chown=docker:docker /usr/src/ts-proto-generated/protos /var/www/html/src/Messages/ts-proto-generated
RUN sed -i 's/import { Observable } from "rxjs";/import type { Observable } from "rxjs";/g' /var/www/html/src/Messages/ts-proto-generated/messages.ts
COPY --from=messages --chown=docker:docker /usr/src/JsonMessages /var/www/html/src/Messages/JsonMessages
# passing arguments as environment
ARG DEBUG_MODE

View File

@ -64,6 +64,7 @@
"simple-peer": "^9.11.0",
"socket.io-client": "^2.3.0",
"standardized-audio-context": "^25.2.4",
"ts-proto": "^1.96.0",
"uuidv4": "^6.2.10"
},
"scripts": {

View File

@ -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();

View File

@ -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();
/**

View File

@ -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";
import { isGetPropertyEvent } from "./GetPropertyEvent";
export interface TypedMessageEvent<T> extends MessageEvent {
@ -51,6 +53,7 @@ export type IframeEventMap = {
displayBubble: null;
removeBubble: null;
onPlayerMove: undefined;
onCameraUpdate: undefined;
showLayer: LayerEvent;
hideLayer: LayerEvent;
setProperty: SetPropertyEvent;
@ -83,6 +86,7 @@ export interface IframeResponseEventMap {
leaveZoneEvent: ChangeZoneEvent;
buttonClickedEvent: ButtonClickedEvent;
hasPlayerMoved: HasPlayerMovedEvent;
wasCameraUpdated: WasCameraUpdatedEvent;
menuItemClicked: MenuItemClickedEvent;
setVariable: SetVariableEvent;
messageTriggered: MessageReferenceEvent;
@ -166,6 +170,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;

View 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>;

View File

@ -4,6 +4,7 @@ export const isSetVariableEvent = new tg.IsInterface()
.withProperties({
key: tg.isString,
value: tg.isUnknown,
target: tg.isSingletonStringUnion("global", "player"),
})
.get();
/**

View 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;

View File

@ -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> = (
@ -88,6 +89,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();
@ -229,6 +233,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)) {
@ -448,6 +454,13 @@ class IframeListener {
}
}
sendCameraUpdated(event: WasCameraUpdatedEvent) {
this.postMessage({
type: "wasCameraUpdated",
data: event,
});
}
sendButtonClickedEvent(popupId: number, buttonId: number, input : boolean, inputValue : string | null): void {
this.postMessage({
type: "buttonClickedEvent",

View File

@ -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,
},
});
}
}

View 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();

View File

@ -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();

View File

@ -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 };
}

View File

@ -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";

View File

@ -14,14 +14,11 @@ vim: ft=typescript
}
function sendFollowRequest() {
gameScene.connection?.emitFollowRequest();
followRoleStore.set("leader");
followStateStore.set("active");
gameScene.CurrentPlayer.sendFollowRequest();
}
function acceptFollowRequest() {
gameScene.CurrentPlayer.enableFollowing();
gameScene.connection?.emitFollowConfirmation();
gameScene.CurrentPlayer.startFollowing();
}
function abortEnding() {
@ -42,23 +39,15 @@ vim: ft=typescript
<svelte:window on:keydown={onKeyDown} />
{#if $followStateStore === "requesting"}
{#if $followStateStore === "requesting" && $followRoleStore === "follower"}
<div class="interact-menu nes-container is-rounded">
{#if $followRoleStore === "follower"}
<section class="interact-menu-title">
<h2>Do you want to follow {name($followUsersStore[0])}?</h2>
</section>
<section class="interact-menu-action">
<button type="button" class="nes-btn is-success" on:click|preventDefault={acceptFollowRequest}
>Yes</button
>
<button type="button" class="nes-btn is-error" on:click|preventDefault={reset}>No</button>
</section>
{:else if $followRoleStore === "leader"}
<section class="interact-menu-question">
<p>Should never be displayed</p>
</section>
{/if}
<section class="interact-menu-title">
<h2>Do you want to follow {name($followUsersStore[0])}?</h2>
</section>
<section class="interact-menu-action">
<button type="button" class="nes-btn is-success" on:click|preventDefault={acceptFollowRequest}>Yes</button>
<button type="button" class="nes-btn is-error" on:click|preventDefault={reset}>No</button>
</section>
</div>
{/if}

View File

@ -1,5 +1,5 @@
import { Subject } from "rxjs";
import type { BanUserMessage, SendUserMessage } from "../Messages/generated/messages_pb";
import type { BanUserMessage, SendUserMessage } from "../Messages/ts-proto-generated/messages";
export enum AdminMessageEventTypes {
admin = "message",
@ -26,8 +26,8 @@ class AdminMessagesService {
onSendusermessage(message: SendUserMessage | BanUserMessage) {
this._messageStream.next({
type: message.getType() as unknown as AdminMessageEventTypes,
text: message.getMessage(),
type: message.type as unknown as AdminMessageEventTypes,
text: message.message,
});
}
}

View File

@ -281,7 +281,7 @@ class ConnectionManager {
reject(error);
});
connection.onConnectingError((event: CloseEvent) => {
connection.connectionErrorStream.subscribe((event: CloseEvent) => {
console.log("An error occurred while connecting to socket server. Retrying");
reject(
new Error(
@ -293,7 +293,7 @@ class ConnectionManager {
);
});
connection.onConnect((connect: OnConnectInterface) => {
connection.roomJoinedMessageStream.subscribe((connect: OnConnectInterface) => {
resolve(connect);
});
}).catch((err) => {

View File

@ -1,44 +1,12 @@
import type { SignalData } from "simple-peer";
import type { RoomConnection } from "./RoomConnection";
import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures";
export enum EventMessage {
CONNECT = "connect",
WEBRTC_SIGNAL = "webrtc-signal",
WEBRTC_SCREEN_SHARING_SIGNAL = "webrtc-screen-sharing-signal",
WEBRTC_START = "webrtc-start",
//START_ROOM = "start-room", // From server to client: list of all room users/groups/items
JOIN_ROOM = "join-room", // bi-directional
USER_POSITION = "user-position", // From client to server
USER_MOVED = "user-moved", // From server to client
USER_LEFT = "user-left", // From server to client
MESSAGE_ERROR = "message-error",
WEBRTC_DISCONNECT = "webrtc-disconect",
GROUP_CREATE_UPDATE = "group-create-update",
GROUP_DELETE = "group-delete",
SET_PLAYER_DETAILS = "set-player-details", // Send the name and character to the server (on connect), receive back the id.
ITEM_EVENT = "item-event",
USER_DETAILS_UPDATED = "user-details-updated",
CONNECT_ERROR = "connect_error",
CONNECTING_ERROR = "connecting_error",
SET_SILENT = "set_silent", // Set or unset the silent mode for this user.
SET_VIEWPORT = "set-viewport",
BATCH = "batch",
PLAY_GLOBAL_MESSAGE = "play-global-message",
STOP_GLOBAL_MESSAGE = "stop-global-message",
TELEPORT = "teleport",
USER_MESSAGE = "user-message",
START_JITSI_ROOM = "start-jitsi-room",
SET_VARIABLE = "set-variable",
}
import { PositionMessage_Direction } from "../Messages/ts-proto-generated/messages";
export interface PointInterface {
x: number;
y: number;
direction: string;
direction: string; // TODO: modify this to the enum from ts-proto
moving: boolean;
}

View File

@ -1,17 +0,0 @@
import { Subject } from "rxjs";
interface EmoteEvent {
userId: number;
emote: string;
}
class EmoteEventStream {
private _stream: Subject<EmoteEvent> = new Subject();
public stream = this._stream.asObservable();
fire(userId: number, emote: string) {
this._stream.next({ userId, emote });
}
}
export const emoteEventStream = new EmoteEventStream();

View File

@ -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) {
@ -223,6 +223,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();

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +0,0 @@
import { Subject } from "rxjs";
class WorldFullMessageStream {
private _stream: Subject<string | null> = new Subject<string | null>();
public stream = this._stream.asObservable();
onMessage(message?: string) {
this._stream.next(message);
}
}
export const worldFullMessageStream = new WorldFullMessageStream();

View File

@ -1 +0,0 @@
/generated/

View File

@ -0,0 +1,2 @@
*
!.gitignore

View File

@ -0,0 +1 @@
*

View File

@ -1,21 +1,21 @@
import { PositionMessage } from "../Messages/generated/messages_pb";
import Direction = PositionMessage.Direction;
import { PositionMessage, PositionMessage_Direction } from "../Messages/ts-proto-generated/messages";
import type { PointInterface } from "../Connexion/ConnexionModels";
export class ProtobufClientUtils {
public static toPointInterface(position: PositionMessage): PointInterface {
let direction: string;
switch (position.getDirection()) {
case Direction.UP:
switch (position.direction) {
case PositionMessage_Direction.UP:
direction = "up";
break;
case Direction.DOWN:
case PositionMessage_Direction.DOWN:
direction = "down";
break;
case Direction.LEFT:
case PositionMessage_Direction.LEFT:
direction = "left";
break;
case Direction.RIGHT:
case PositionMessage_Direction.RIGHT:
direction = "right";
break;
default:
@ -24,10 +24,10 @@ export class ProtobufClientUtils {
// sending to all clients in room except sender
return {
x: position.getX(),
y: position.getY(),
x: position.x,
y: position.y,
direction,
moving: position.getMoving(),
moving: position.moving,
};
}
}

View File

@ -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);
}

View File

@ -1,13 +1,13 @@
import { emoteEventStream } from "../../Connexion/EmoteEventStream";
import type { GameScene } from "./GameScene";
import type { Subscription } from "rxjs";
import type { RoomConnection } from "../../Connexion/RoomConnection";
export class EmoteManager {
private subscription: Subscription;
constructor(private scene: GameScene) {
this.subscription = emoteEventStream.stream.subscribe((event) => {
const actor = this.scene.MapPlayersByKey.get(event.userId);
constructor(private scene: GameScene, private connection: RoomConnection) {
this.subscription = connection.emoteEventMessageStream.subscribe((event) => {
const actor = this.scene.MapPlayersByKey.get(event.actorUserId);
if (actor) {
actor.playEmote(event.emote);
}

View File

@ -40,7 +40,6 @@ import { ReconnectingSceneName } from "../Reconnecting/ReconnectingScene";
import { GameMap } from "./GameMap";
import { PlayerMovement } from "./PlayerMovement";
import { PlayersPositionInterpolator } from "./PlayersPositionInterpolator";
import { worldFullMessageStream } from "../../Connexion/WorldFullMessageStream";
import { DirtyScene } from "./DirtyScene";
import { TextUtils } from "../Components/TextUtils";
import { joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey } from "../Components/MobileJoystick";
@ -60,7 +59,6 @@ import type {
PositionInterface,
RoomJoinedMessageInterface,
} from "../../Connexion/ConnexionModels";
import type { UserMovedMessage } from "../../Messages/generated/messages_pb";
import type { RoomConnection } from "../../Connexion/RoomConnection";
import type { ActionableItem } from "../Items/ActionableItem";
import type { ItemFactoryInterface } from "../Items/ItemFactoryInterface";
@ -90,9 +88,10 @@ import SpriteSheetFile = Phaser.Loader.FileTypes.SpriteSheetFile;
import { deepCopy } from "deep-copy-ts";
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
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 +209,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({
@ -454,10 +455,6 @@ export class GameScene extends DirtyScene {
this.pinchManager = new PinchManager(this);
}
this.messageSubscription = worldFullMessageStream.stream.subscribe((message) =>
this.showWorldFullError(message)
);
const playerName = gameManager.getPlayerName();
if (!playerName) {
throw "playerName is not set";
@ -540,7 +537,9 @@ export class GameScene extends DirtyScene {
object.height,
object.visible,
allowApi ?? false,
""
"",
"map",
1
);
}
}
@ -634,8 +633,6 @@ export class GameScene extends DirtyScene {
this.connect();
}
this.emoteManager = new EmoteManager(this);
let oldPeerNumber = 0;
this.peerStoreUnsubscribe = peerStore.subscribe((peers) => {
const newPeerNumber = peers.size;
@ -710,7 +707,7 @@ export class GameScene extends DirtyScene {
playersStore.connectToRoomConnection(this.connection);
userIsAdminStore.set(this.connection.hasTag("admin"));
this.connection.onUserJoins((message: MessageUserJoined) => {
this.connection.userJoinedMessageStream.subscribe((message) => {
const userMessage: AddPlayerInterface = {
userId: message.userId,
characterLayers: message.characterLayers,
@ -724,31 +721,33 @@ export class GameScene extends DirtyScene {
this.addPlayer(userMessage);
});
this.connection.onUserMoved((message: UserMovedMessage) => {
const position = message.getPosition();
this.connection.userMovedMessageStream.subscribe((message) => {
const position = message.position;
if (position === undefined) {
throw new Error("Position missing from UserMovedMessage");
}
const messageUserMoved: MessageUserMovedInterface = {
userId: message.getUserid(),
userId: message.userId,
position: ProtobufClientUtils.toPointInterface(position),
};
this.updatePlayerPosition(messageUserMoved);
});
this.connection.onUserLeft((userId: number) => {
this.removePlayer(userId);
this.connection.userLeftMessageStream.subscribe((message) => {
this.removePlayer(message.userId);
});
this.connection.onGroupUpdatedOrCreated((groupPositionMessage: GroupCreatedUpdatedMessageInterface) => {
this.shareGroupPosition(groupPositionMessage);
});
this.connection.groupUpdateMessageStream.subscribe(
(groupPositionMessage: GroupCreatedUpdatedMessageInterface) => {
this.shareGroupPosition(groupPositionMessage);
}
);
this.connection.onGroupDeleted((groupId: number) => {
this.connection.groupDeleteMessageStream.subscribe((message) => {
try {
this.deleteGroup(groupId);
this.deleteGroup(message.groupId);
} catch (e) {
console.error(e);
}
@ -760,7 +759,7 @@ export class GameScene extends DirtyScene {
this.createSuccessorGameScene(true, true);
});
this.connection.onActionableEvent((message) => {
this.connection.itemEventMessageStream.subscribe((message) => {
const item = this.actionableItems.get(message.itemId);
if (item === undefined) {
console.warn(
@ -773,18 +772,29 @@ export class GameScene extends DirtyScene {
item.fire(message.event, message.state, message.parameters);
});
this.connection.onPlayerDetailsUpdated((message) => {
this.connection.playerDetailsUpdatedMessageStream.subscribe((message) => {
if (message.details === undefined) {
throw new Error("Malformed message. Missing details in PlayerDetailsUpdatedMessage");
}
this.pendingEvents.enqueue({
type: "PlayerDetailsUpdated",
details: message,
details: {
userId: message.userId,
outlineColor: message.details.outlineColor,
removeOutlineColor: message.details.removeOutlineColor,
},
});
});
/**
* Triggered when we receive the JWT token to connect to Jitsi
*/
this.connection.onStartJitsiRoom((jwt, room) => {
this.startJitsi(room, jwt);
this.connection.sendJitsiJwtMessageStream.subscribe((message) => {
this.startJitsi(message.jitsiRoom, message.jwt);
});
this.messageSubscription = this.connection.worldFullMessageStream.subscribe((message) => {
this.showWorldFullError(message);
});
// When connection is performed, let's connect SimplePeer
@ -859,6 +869,8 @@ export class GameScene extends DirtyScene {
});
});
this.emoteManager = new EmoteManager(this, this.connection);
// this.gameMap.onLeaveLayer((layers) => {
// layers.forEach((layer) => {
// iframeListener.sendLeaveLayerEvent(layer.name);
@ -1121,9 +1133,33 @@ export class GameScene extends DirtyScene {
);
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);
}
})
);
@ -1186,6 +1222,12 @@ export class GameScene extends DirtyScene {
})
);
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");
@ -1261,6 +1303,7 @@ export class GameScene extends DirtyScene {
roomId: this.roomUrl,
tags: this.connection ? this.connection.getAllTags() : [],
variables: this.sharedVariablesManager.variables,
playerVariables: localUserStore.getAllUserProperties(),
userRoomToken: this.connection ? this.connection.userRoomToken : "",
};
});
@ -1358,6 +1401,22 @@ export class GameScene extends DirtyScene {
})
);
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);
});
@ -1376,6 +1435,13 @@ export class GameScene extends DirtyScene {
this.CurrentPlayer.removeOutlineColor();
this.connection?.emitPlayerOutlineColor(null);
});
iframeListener.registerAnswerer("getPlayerPosition", () => {
return {
x: this.CurrentPlayer.x,
y: this.CurrentPlayer.y,
};
});
}
private setPropertyLayer(
@ -1500,6 +1566,7 @@ export class GameScene extends DirtyScene {
iframeListener.unregisterAnswerer("openCoWebsite");
iframeListener.unregisterAnswerer("getCoWebsites");
iframeListener.unregisterAnswerer("setPlayerOutline");
iframeListener.unregisterAnswerer("setVariable");
this.sharedVariablesManager?.close();
this.embeddedWebsiteManager?.close();
@ -1978,6 +2045,7 @@ export class GameScene extends DirtyScene {
this.loader.resize();
}
private getObjectLayerData(objectName: string): ITiledMapObject | undefined {
for (const layer of this.mapFile.layers) {
if (layer.type === "objectgroup" && layer.name === "floorLayer") {
@ -1990,6 +2058,7 @@ export class GameScene extends DirtyScene {
}
return undefined;
}
private reposition(): void {
// Recompute camera offset if needed
biggestAvailableAreaStore.recompute();

View File

@ -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;
@ -41,58 +42,58 @@ export class SharedVariablesManager {
this._variables.set(name, value);
}
roomConnection.onSetVariable((name, value) => {
roomConnection.variableMessageStream.subscribe(({ name, value }) => {
this._variables.set(name, value);
// On server change, let's notify the iframes
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> {

View File

@ -100,10 +100,6 @@ export class Player extends Character {
return [xMovement, yMovement];
}
public enableFollowing() {
followStateStore.set("active");
}
public moveUser(delta: number): void {
const activeEvents = this.userInputManager.getEventListForGameTick();
const state = get(followStateStore);
@ -111,8 +107,7 @@ export class Player extends Character {
if (activeEvents.get(UserInputEvent.Follow)) {
if (state === "off" && this.scene.groups.size > 0) {
followStateStore.set("requesting");
followRoleStore.set("leader");
this.sendFollowRequest();
} else if (state === "active") {
followStateStore.set("ending");
}
@ -125,4 +120,15 @@ export class Player extends Character {
}
this.inputStep(activeEvents, x, y);
}
public sendFollowRequest() {
this.scene.connection?.emitFollowRequest();
followRoleStore.set("leader");
followStateStore.set("active");
}
public startFollowing() {
followStateStore.set("active");
this.scene.connection?.emitFollowConfirmation();
}
}

View File

@ -31,6 +31,10 @@ export class WaScaleManager {
height: height * devicePixelRatio,
});
if (gameSize.width == 0) {
return;
}
this.actualZoom = realSize.width / gameSize.width / devicePixelRatio;
this.scaleManager.setZoom(realSize.width / gameSize.width / devicePixelRatio);

View File

@ -58,7 +58,6 @@ export const followUsersStore = createFollowUsersStore();
export const followUsersColorStore = derived(
[followStateStore, followRoleStore, followUsersStore],
([$followStateStore, $followRoleStore, $followUsersStore]) => {
console.log($followStateStore);
if ($followStateStore !== "active") {
return undefined;
}

View File

@ -3,6 +3,7 @@ import type { PlayerInterface } from "../Phaser/Game/PlayerInterface";
import type { RoomConnection } from "../Connexion/RoomConnection";
import { getRandomColor } from "../WebRtc/ColorGenerator";
import { localUserStore } from "../Connexion/LocalUserStore";
import room from "../Api/iframe/room";
let idCount = 0;
@ -19,7 +20,8 @@ function createPlayersStore() {
connectToRoomConnection: (roomConnection: RoomConnection) => {
players = new Map<number, PlayerInterface>();
set(players);
roomConnection.onUserJoins((message) => {
// TODO: it would be cool to unsubscribe properly here
roomConnection.userJoinedMessageStream.subscribe((message) => {
update((users) => {
users.set(message.userId, {
userId: message.userId,
@ -33,9 +35,9 @@ function createPlayersStore() {
return users;
});
});
roomConnection.onUserLeft((userId) => {
roomConnection.userLeftMessageStream.subscribe((message) => {
update((users) => {
users.delete(userId);
users.delete(message.userId);
return users;
});
});

View File

@ -75,23 +75,25 @@ export class SimplePeer {
*/
private initialise() {
//receive signal by gemer
this.Connection.receiveWebrtcSignal((message: WebRtcSignalReceivedMessageInterface) => {
this.Connection.webRtcSignalToClientMessageStream.subscribe((message: WebRtcSignalReceivedMessageInterface) => {
this.receiveWebrtcSignal(message);
});
//receive signal by gemer
this.Connection.receiveWebrtcScreenSharingSignal((message: WebRtcSignalReceivedMessageInterface) => {
this.receiveWebrtcScreenSharingSignal(message);
});
this.Connection.webRtcScreenSharingSignalToClientMessageStream.subscribe(
(message: WebRtcSignalReceivedMessageInterface) => {
this.receiveWebrtcScreenSharingSignal(message);
}
);
mediaManager.showGameOverlay();
//receive message start
this.Connection.receiveWebrtcStart((message: UserSimplePeerInterface) => {
this.Connection.webRtcStartMessageStream.subscribe((message: UserSimplePeerInterface) => {
this.receiveWebrtcStart(message);
});
this.Connection.disconnectMessage((data: WebRtcDisconnectMessageInterface): void => {
this.Connection.webRtcDisconnectMessageStream.subscribe((data: WebRtcDisconnectMessageInterface): void => {
this.closeConnection(data.userId);
});
}

View File

@ -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);
}
}
// ...
}
);

View File

@ -1092,3 +1092,7 @@ div.is-silent.hide {
height: 1px !important;
width: 1px !important;
}
.emote-menu .emoji-picker .emoji-picker__emoji {
font-family: 'Twemoji Mozilla' !important;
}

View File

@ -150,6 +150,59 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.2.tgz#adea7b6953cbb34651766b0548468e743c6a2353"
integrity sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78=
"@protobufjs/base64@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735"
integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==
"@protobufjs/codegen@^2.0.4":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb"
integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==
"@protobufjs/eventemitter@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70"
integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A=
"@protobufjs/fetch@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45"
integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=
dependencies:
"@protobufjs/aspromise" "^1.1.1"
"@protobufjs/inquire" "^1.1.0"
"@protobufjs/float@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1"
integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=
"@protobufjs/inquire@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089"
integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=
"@protobufjs/path@^1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d"
integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=
"@protobufjs/pool@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54"
integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=
"@protobufjs/utf8@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=
"@sentry/types@^6.11.0":
version "6.12.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.12.0.tgz#b7395688a79403c6df8d8bb8d81deb8222519853"
@ -293,6 +346,11 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
"@types/long@^4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==
"@types/mime@^1":
version "1.3.2"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a"
@ -317,11 +375,26 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-15.3.0.tgz#d6fed7d6bc6854306da3dea1af9f874b00783e26"
integrity sha512-8/bnjSZD86ZfpBsDlCIkNXIvm+h6wi9g7IqL+kmFkQ+Wvu3JrasgLElfiPgoo8V8vVfnEi0QVS12gbl94h9YsQ==
"@types/node@>=13.7.0":
version "17.0.5"
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.5.tgz#57ca67ec4e57ad9e4ef5a6bab48a15387a1c83e0"
integrity sha512-w3mrvNXLeDYV1GKTZorGJQivK6XLCoGwpnyJFbJVK/aTBQUxOCaa/GlFAAN3OTDFcb7h5tiFG+YXCO2By+riZw==
"@types/object-hash@^1.3.0":
version "1.3.4"
resolved "https://registry.yarnpkg.com/@types/object-hash/-/object-hash-1.3.4.tgz#079ba142be65833293673254831b5e3e847fe58b"
integrity sha512-xFdpkAkikBgqBdG9vIlsqffDV8GpvnPEzs0IUtr1v3BEB97ijsFQ4RXVbUZwjFThhB4MDSTUfvmxUD5PGx0wXA==
"@types/parse-json@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
"@types/prettier@^1.19.0":
version "1.19.1"
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.19.1.tgz#33509849f8e679e4add158959fdb086440e9553f"
integrity sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ==
"@types/pug@^2.0.4":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@types/pug/-/pug-2.0.4.tgz#8772fcd0418e3cd2cc171555d73007415051f4b2"
@ -1656,6 +1729,11 @@ cssesc@^3.0.0:
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
dataloader@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-1.4.0.tgz#bca11d867f5d3f1b9ed9f737bd15970c65dff5c8"
integrity sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==
debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@ -3702,7 +3780,7 @@ lodash.merge@^4.6.2:
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.20:
lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -3735,6 +3813,11 @@ lokijs@^1.5.12:
resolved "https://registry.yarnpkg.com/lokijs/-/lokijs-1.5.12.tgz#cb55b37009bdf09ee7952a6adddd555b893653a0"
integrity sha512-Q5ALD6JiS6xAUWCwX3taQmgwxyveCtIIuL08+ml0nHwT3k0S/GIFJN+Hd38b1qYIMaE5X++iqsqWVksz7SYW+Q==
long@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==
lower-case@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28"
@ -4143,6 +4226,11 @@ object-copy@^0.1.0:
define-property "^0.2.5"
kind-of "^3.0.3"
object-hash@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df"
integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==
object-inspect@^1.9.0:
version "1.10.3"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369"
@ -4649,6 +4737,11 @@ prettier-plugin-svelte@^2.5.0:
resolved "https://registry.yarnpkg.com/prettier-plugin-svelte/-/prettier-plugin-svelte-2.5.0.tgz#7922534729f7febe59b4c56c3f5360539f0d8ab1"
integrity sha512-+iHY2uGChOngrgKielJUnqo74gIL/EO5oeWm8MftFWjEi213lq9QYTOwm1pv4lI1nA61tdgf80CF2i5zMcu1kw==
prettier@^2.0.2:
version "2.5.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a"
integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==
prettier@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.1.tgz#76903c3f8c4449bc9ac597acefa24dc5ad4cbea6"
@ -4677,6 +4770,25 @@ progress@^2.0.0:
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
protobufjs@^6.8.8:
version "6.11.2"
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.2.tgz#de39fabd4ed32beaa08e9bb1e30d08544c1edf8b"
integrity sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==
dependencies:
"@protobufjs/aspromise" "^1.1.2"
"@protobufjs/base64" "^1.1.2"
"@protobufjs/codegen" "^2.0.4"
"@protobufjs/eventemitter" "^1.1.0"
"@protobufjs/fetch" "^1.1.0"
"@protobufjs/float" "^1.0.2"
"@protobufjs/inquire" "^1.1.0"
"@protobufjs/path" "^1.1.2"
"@protobufjs/pool" "^1.1.0"
"@protobufjs/utf8" "^1.1.0"
"@types/long" "^4.0.1"
"@types/node" ">=13.7.0"
long "^4.0.0"
proxy-addr@~2.0.5:
version "2.0.6"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
@ -5896,6 +6008,35 @@ ts-node@^10.4.0:
make-error "^1.1.1"
yn "3.1.1"
ts-poet@^4.5.0:
version "4.6.1"
resolved "https://registry.yarnpkg.com/ts-poet/-/ts-poet-4.6.1.tgz#015dc823d726655af9f095c900f84ed7c60e2dd3"
integrity sha512-DXJ+mBJIDp+jiaUgB4N5I/sczHHDU2FWacdbDNVAVS4Mh4hb7ckpvUWVW7m7/nAOcjR0r4Wt+7AoO7FeJKExfA==
dependencies:
"@types/prettier" "^1.19.0"
lodash "^4.17.15"
prettier "^2.0.2"
ts-proto-descriptors@^1.2.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/ts-proto-descriptors/-/ts-proto-descriptors-1.3.1.tgz#760ebaaa19475b03662f7b358ffea45b9c5348f5"
integrity sha512-Cybb3fqceMwA6JzHdC32dIo8eVGVmXrM6TWhdk1XQVVHT/6OQqk0ioyX1dIdu3rCIBhRmWUhUE4HsyK+olmgMw==
dependencies:
long "^4.0.0"
protobufjs "^6.8.8"
ts-proto@^1.96.0:
version "1.96.0"
resolved "https://registry.yarnpkg.com/ts-proto/-/ts-proto-1.96.0.tgz#63768d7da533b337aee84db065dd66773bd4cac9"
integrity sha512-fKwaGzi8EOCU9xwmcXK917jj1WhFdLbFkPRawQ+5CAZM9eSXr/mpkz/yEctXCiuei364z6jAB2Odb64KCDFTPQ==
dependencies:
"@types/object-hash" "^1.3.0"
dataloader "^1.4.0"
object-hash "^1.3.1"
protobufjs "^6.8.8"
ts-poet "^4.5.0"
ts-proto-descriptors "^1.2.1"
tsconfig-paths@^3.9.0:
version "3.9.0"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b"

View File

@ -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>

View File

@ -4,13 +4,14 @@
"description": "",
"scripts": {
"proto": "grpc_tools_node_protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --grpc_out=generated --js_out=\"import_style=commonjs,binary:generated\" --ts_out=generated -I ./protos protos/*.proto",
"ts-proto": "grpc_tools_node_protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_out=ts-proto-generated --ts_proto_opt=oneof=unions --ts_proto_opt=esModuleInterop=true protos/*.proto",
"copy-to-back": "rm -rf ../back/src/Messages/generated && cp -rf generated/ ../back/src/Messages/generated",
"copy-to-front": "rm -rf ../front/src/Messages/generated && cp -rf generated/ ../front/src/Messages/generated",
"copy-to-front-ts-proto": "sed 's/import { Observable } from \"rxjs\";/import type { Observable } from \"rxjs\";/g' ts-proto-generated/protos/messages.ts > ../front/src/Messages/ts-proto-generated/messages.ts",
"copy-to-pusher": "rm -rf ../pusher/src/Messages/generated && cp -rf generated/ ../pusher/src/Messages/generated",
"json-copy-to-pusher": "rm -rf ../pusher/src/Messages/JsonMessages/* && cp -rf JsonMessages/* ../pusher/src/Messages/JsonMessages/",
"json-copy-to-front": "rm -rf ../front/src/Messages/JsonMessages/* && cp -rf JsonMessages/* ../front/src/Messages/JsonMessages/",
"precommit": "lint-staged",
"proto-all": "yarn run proto && yarn run copy-to-back && yarn run copy-to-front && yarn run copy-to-pusher && yarn run json-copy-to-pusher && yarn run json-copy-to-front",
"proto-all": "yarn run proto && yarn run ts-proto && yarn run copy-to-back && yarn run copy-to-front-ts-proto && yarn run copy-to-pusher && yarn run json-copy-to-pusher && yarn run json-copy-to-front",
"proto:watch": "yarn run proto-all; inotifywait -q -m -e close_write protos/messages.proto JsonMessages/ | while read -r filename event; do yarn run proto-all; done",
"pretty": "yarn prettier --write 'JsonMessages/**/*.ts'",
"pretty-check": "yarn prettier --check 'JsonMessages/**/*.ts'"
@ -18,7 +19,8 @@
"dependencies": {
"generic-type-guard": "^3.5.0",
"google-protobuf": "^3.13.0",
"grpc": "^1.24.4"
"grpc": "^1.24.4",
"ts-proto": "^1.96.0"
},
"devDependencies": {
"@types/google-protobuf": "^3.7.4",

View File

@ -296,8 +296,6 @@ message ServerToClientMessage {
WebRtcSignalToClientMessage webRtcSignalToClientMessage = 5;
WebRtcSignalToClientMessage webRtcScreenSharingSignalToClientMessage = 6;
WebRtcDisconnectMessage webRtcDisconnectMessage = 7;
PlayGlobalMessage playGlobalMessage = 8;
StopGlobalMessage stopGlobalMessage = 9;
TeleportMessageMessage teleportMessageMessage = 10;
SendJitsiJwtMessage sendJitsiJwtMessage = 11;
SendUserMessage sendUserMessage = 12;
@ -390,8 +388,6 @@ message PusherToBackMessage {
SetPlayerDetailsMessage setPlayerDetailsMessage = 5;
WebRtcSignalToServerMessage webRtcSignalToServerMessage = 6;
WebRtcSignalToServerMessage webRtcScreenSharingSignalToServerMessage = 7;
PlayGlobalMessage playGlobalMessage = 8;
StopGlobalMessage stopGlobalMessage = 9;
ReportPlayerMessage reportPlayerMessage = 10;
QueryJitsiJwtMessage queryJitsiJwtMessage = 11;
SendUserMessage sendUserMessage = 12;

View File

@ -0,0 +1,2 @@
*
!.gitignore

View File

@ -174,6 +174,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.7.tgz#8ea1e8f8eae2430cf440564b98c6dfce1ec5945d"
integrity sha512-Zw1vhUSQZYw+7u5dAwNbIA9TuTotpzY/OF7sJM9FqPOF3SPjKnxrjoTktXDZgUjybf4cWVBP7O8wvKdSaGHweg==
"@types/node@>=13.7.0":
version "17.0.5"
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.5.tgz#57ca67ec4e57ad9e4ef5a6bab48a15387a1c83e0"
integrity sha512-w3mrvNXLeDYV1GKTZorGJQivK6XLCoGwpnyJFbJVK/aTBQUxOCaa/GlFAAN3OTDFcb7h5tiFG+YXCO2By+riZw==
"@types/node@^12.12.29":
version "12.19.4"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.19.4.tgz#cdfbb62e26c7435ed9aab9c941393cc3598e9b46"
@ -184,6 +189,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.30.tgz#1ed6e01e4ca576d5aec9cc802cc3bcf94c274192"
integrity sha512-HmqFpNzp3TSELxU/bUuRK+xzarVOAsR00hzcvM0TXrMlt/+wcSLa5q6YhTb6/cA6wqDCZLDcfd8fSL95x5h7AA==
"@types/object-hash@^1.3.0":
version "1.3.4"
resolved "https://registry.yarnpkg.com/@types/object-hash/-/object-hash-1.3.4.tgz#079ba142be65833293673254831b5e3e847fe58b"
integrity sha512-xFdpkAkikBgqBdG9vIlsqffDV8GpvnPEzs0IUtr1v3BEB97ijsFQ4RXVbUZwjFThhB4MDSTUfvmxUD5PGx0wXA==
"@types/parse-json@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
@ -194,6 +204,11 @@
resolved "https://registry.yarnpkg.com/@types/parsimmon/-/parsimmon-1.10.4.tgz#7639e16015440d9baf622f83c12dae47787226b7"
integrity sha512-M56NfQHfaWuaj6daSgCVs7jh8fXLI3LmxjRoQxmOvYesgIkI+9HPsDLO0vd7wX7cwA0D0ZWFEJdp0VPwLdS+bQ==
"@types/prettier@^1.19.0":
version "1.19.1"
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.19.1.tgz#33509849f8e679e4add158959fdb086440e9553f"
integrity sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ==
"@typescript-eslint/eslint-plugin@^4.7.0":
version "4.7.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.7.0.tgz#85c9bbda00c0cb604d3c241f7bc7fb171a2d3479"
@ -1156,6 +1171,11 @@ dashdash@^1.12.0:
dependencies:
assert-plus "^1.0.0"
dataloader@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-1.4.0.tgz#bca11d867f5d3f1b9ed9f737bd15970c65dff5c8"
integrity sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw==
date.js@^0.3.1:
version "0.3.3"
resolved "https://registry.yarnpkg.com/date.js/-/date.js-0.3.3.tgz#ef1e92332f507a638795dbb985e951882e50bbda"
@ -3154,6 +3174,11 @@ object-copy@^0.1.0:
define-property "^0.2.5"
kind-of "^3.0.3"
object-hash@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df"
integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==
object-inspect@^1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0"
@ -3423,7 +3448,7 @@ prelude-ls@^1.2.1:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
prettier@^2.3.1:
prettier@^2.0.2, prettier@^2.3.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a"
integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==
@ -3467,6 +3492,25 @@ protobufjs@^6.10.1:
"@types/node" "^13.7.0"
long "^4.0.0"
protobufjs@^6.8.8:
version "6.11.2"
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.2.tgz#de39fabd4ed32beaa08e9bb1e30d08544c1edf8b"
integrity sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==
dependencies:
"@protobufjs/aspromise" "^1.1.2"
"@protobufjs/base64" "^1.1.2"
"@protobufjs/codegen" "^2.0.4"
"@protobufjs/eventemitter" "^1.1.0"
"@protobufjs/fetch" "^1.1.0"
"@protobufjs/float" "^1.0.2"
"@protobufjs/inquire" "^1.1.0"
"@protobufjs/path" "^1.1.2"
"@protobufjs/pool" "^1.1.0"
"@protobufjs/utf8" "^1.1.0"
"@types/long" "^4.0.1"
"@types/node" ">=13.7.0"
long "^4.0.0"
psl@^1.1.28:
version "1.8.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
@ -4245,6 +4289,35 @@ tough-cookie@~2.5.0:
psl "^1.1.28"
punycode "^2.1.1"
ts-poet@^4.5.0:
version "4.6.1"
resolved "https://registry.yarnpkg.com/ts-poet/-/ts-poet-4.6.1.tgz#015dc823d726655af9f095c900f84ed7c60e2dd3"
integrity sha512-DXJ+mBJIDp+jiaUgB4N5I/sczHHDU2FWacdbDNVAVS4Mh4hb7ckpvUWVW7m7/nAOcjR0r4Wt+7AoO7FeJKExfA==
dependencies:
"@types/prettier" "^1.19.0"
lodash "^4.17.15"
prettier "^2.0.2"
ts-proto-descriptors@^1.2.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/ts-proto-descriptors/-/ts-proto-descriptors-1.3.1.tgz#760ebaaa19475b03662f7b358ffea45b9c5348f5"
integrity sha512-Cybb3fqceMwA6JzHdC32dIo8eVGVmXrM6TWhdk1XQVVHT/6OQqk0ioyX1dIdu3rCIBhRmWUhUE4HsyK+olmgMw==
dependencies:
long "^4.0.0"
protobufjs "^6.8.8"
ts-proto@^1.96.0:
version "1.96.0"
resolved "https://registry.yarnpkg.com/ts-proto/-/ts-proto-1.96.0.tgz#63768d7da533b337aee84db065dd66773bd4cac9"
integrity sha512-fKwaGzi8EOCU9xwmcXK917jj1WhFdLbFkPRawQ+5CAZM9eSXr/mpkz/yEctXCiuei364z6jAB2Odb64KCDFTPQ==
dependencies:
"@types/object-hash" "^1.3.0"
dataloader "^1.4.0"
object-hash "^1.3.1"
protobufjs "^6.8.8"
ts-poet "^4.5.0"
ts-proto-descriptors "^1.2.1"
tsconfig-paths@^3.9.0:
version "3.9.0"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b"