Merge pull request #1702 from thecodingmachine/develop

Deploy 2022-01-04
This commit is contained in:
David Négrier 2022-01-05 22:35:44 +01:00 committed by GitHub
commit 88509916a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
87 changed files with 1983 additions and 1134 deletions

View File

@ -39,7 +39,7 @@ jobs:
working-directory: "messages" working-directory: "messages"
- name: "Build proto 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" working-directory: "messages"
- name: "Create index.html" - name: "Create index.html"

View File

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

View File

@ -36,7 +36,7 @@ jobs:
working-directory: "messages" working-directory: "messages"
- name: "Build proto 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" working-directory: "messages"
- name: "Create index.html" - name: "Create index.html"

View File

@ -1,15 +1,15 @@
// lib/server.ts // lib/server.ts
import App from "./src/App"; import App from "./src/App";
import grpc from "grpc"; import grpc from "grpc";
import {roomManager} from "./src/RoomManager"; import { roomManager } from "./src/RoomManager";
import {IRoomManagerServer, RoomManagerService} from "./src/Messages/generated/messages_grpc_pb"; import { IRoomManagerServer, RoomManagerService } from "./src/Messages/generated/messages_grpc_pb";
import {HTTP_PORT, GRPC_PORT} from "./src/Enum/EnvironmentVariable"; import { HTTP_PORT, GRPC_PORT } from "./src/Enum/EnvironmentVariable";
App.listen(HTTP_PORT, () => console.log(`WorkAdventure HTTP API starting on port %d!`, HTTP_PORT)) App.listen(HTTP_PORT, () => console.log(`WorkAdventure HTTP API starting on port %d!`, HTTP_PORT));
const server = new grpc.Server(); const server = new grpc.Server();
server.addService<IRoomManagerServer>(RoomManagerService, roomManager); server.addService<IRoomManagerServer>(RoomManagerService, roomManager);
server.bind(`0.0.0.0:${GRPC_PORT}`, grpc.ServerCredentials.createInsecure()); server.bind(`0.0.0.0:${GRPC_PORT}`, grpc.ServerCredentials.createInsecure());
server.start(); server.start();
console.log('WorkAdventure HTTP/2 API starting on port %d!', GRPC_PORT); console.log("WorkAdventure HTTP/2 API starting on port %d!", GRPC_PORT);

View File

@ -106,11 +106,6 @@ const roomManager: IRoomManagerServer = {
user, user,
message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage
); );
} else if (message.hasPlayglobalmessage()) {
socketManager.emitPlayGlobalMessage(
room,
message.getPlayglobalmessage() as PlayGlobalMessage
);
} else if (message.hasQueryjitsijwtmessage()) { } else if (message.hasQueryjitsijwtmessage()) {
socketManager.handleQueryJitsiJwtMessage( socketManager.handleQueryJitsiJwtMessage(
user, 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>> { public getWorlds(): Map<string, PromiseLike<GameRoom>> {
return this.roomsPromises; 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 ### Listen to player movement
``` ```
WA.player.onPlayerMove(callback: HasPlayerMovedEventCallback): void; WA.player.onPlayerMove(callback: HasPlayerMovedEventCallback): void;
@ -107,6 +128,30 @@ Example :
WA.player.onPlayerMove(console.log); 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 ### Set the outline color of the player
``` ```
WA.player.setOutlineColor(red: number, green: number, blue: number): Promise<void>; WA.player.setOutlineColor(red: number, green: number, blue: number): Promise<void>;

View File

@ -10,5 +10,6 @@
- [UI functions](api-ui.md) - [UI functions](api-ui.md)
- [Sound functions](api-sound.md) - [Sound functions](api-sound.md)
- [Controls functions](api-controls.md) - [Controls functions](api-controls.md)
- [Camera functions](api-camera.md)
- [List of deprecated functions](api-deprecated.md) - [List of deprecated functions](api-deprecated.md)

View File

@ -1,8 +1,11 @@
{.section-title.accent.text-primary} {.section-title.accent.text-primary}
# API Room functions Reference # API Room functions Reference
### Working with group layers ### 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 : Example :
<div class="row"> <div class="row">
@ -12,6 +15,7 @@ Example :
</div> </div>
The name of the layers of this map are : The name of the layers of this map are :
* `entries/start` * `entries/start`
* `bottom/ground/under` * `bottom/ground/under`
* `bottom/build/carpet` * `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. 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: Example:
```javascript ```javascript
WA.room.onEnterLayer('myLayer').subscribe(() => { WA.room.onEnterLayer('myLayer').subscribe(() => {
WA.chat.sendChatMessage("Hello!", 'Mr Robot'); WA.chat.sendChatMessage("Hello!", 'Mr Robot');
}); });
WA.room.onLeaveLayer('myLayer').subscribe(() => { WA.room.onLeaveLayer('myLayer').subscribe(() => {
WA.chat.sendChatMessage("Goodbye!", 'Mr Robot'); WA.chat.sendChatMessage("Goodbye!", 'Mr Robot');
}); });
``` ```
### Show / Hide a layer ### Show / Hide a layer
``` ```
WA.room.showLayer(layerName : string): void WA.room.showLayer(layerName : string): void
WA.room.hideLayer(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 : Example :
```javascript ```javascript
WA.room.showLayer('bottom'); 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; 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 : Note :
To unset a property from a layer, use `setProperty` with `propertyValue` set to `undefined`. To unset a property from a layer, use `setProperty` with `propertyValue` set to `undefined`.
Example : Example :
```javascript ```javascript
WA.room.setProperty('wikiLayer', 'openWebsite', 'https://www.wikipedia.org/'); 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. The ID of the current room is available from the `WA.room.id` property.
{.alert.alert-info} {.alert.alert-info} You need to wait for the end of the initialization before accessing `WA.room.id`
You need to wait for the end of the initialization before accessing `WA.room.id`
```typescript ```typescript
WA.onInit().then(() => { WA.onInit().then(() => {
console.log('Room id: ', WA.room.id); 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" // 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. The URL of the map is available from the `WA.room.mapURL` property.
{.alert.alert-info} {.alert.alert-info} You need to wait for the end of the initialization before accessing `WA.room.mapURL`
You need to wait for the end of the initialization before accessing `WA.room.mapURL`
```typescript ```typescript
WA.onInit().then(() => { WA.onInit().then(() => {
console.log('Map URL: ', WA.room.mapURL); console.log('Map URL: ', WA.room.mapURL);
// Will output something like: 'https://mymap.org/map.json" // Will output something like: 'https://mymap.org/map.json"
}) })
``` ```
### Getting map data ### Getting map data
``` ```
WA.room.getTiledMap(): Promise<ITiledMap> WA.room.getTiledMap(): Promise<ITiledMap>
``` ```
@ -121,12 +127,16 @@ const map = await WA.room.getTiledMap();
console.log("Map generated with Tiled version ", map.tiledversion); 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 ### Changing tiles
``` ```
WA.room.setTiles(tiles: TileDescriptor[]): void 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`. 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`. 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> </div>
`TileDescriptor` has the following attributes : `TileDescriptor` has the following attributes :
* **x (number) :** The coordinate x of the tile that you want to replace. * **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. * **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. * **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. * **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`. Note: If you want to unset a tile, use `setTiles` with `tile` set to `null`.
Example : Example :
```javascript ```javascript
WA.room.setTiles([ WA.room.setTiles([
{x: 6, y: 4, tile: 'blue', layer: 'setTiles'}, { x: 6, y: 4, tile: 'blue', layer: 'setTiles' },
{x: 7, y: 4, tile: 109, layer: 'setTiles'}, { x: 7, y: 4, tile: 109, layer: 'setTiles' },
{x: 8, y: 4, tile: 109, layer: 'setTiles'}, { x: 8, y: 4, tile: 109, layer: 'setTiles' },
{x: 9, y: 4, tile: 'blue', layer: 'setTiles'} { x: 9, y: 4, tile: 'blue', layer: 'setTiles' }
]); ]);
``` ```
### Loading a tileset ### Loading a tileset
``` ```
WA.room.loadTileset(url: string): Promise<number> 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. 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. You can create a tileset file in Tile Editor.
```javascript ```javascript
WA.room.loadTileset("Assets/Tileset.json").then((firstId) => { 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 ## 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 ### 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> 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. You can get an instance of an embedded website by using the `WA.room.website.get()` method. It returns a promise of
It returns a promise of an `EmbeddedWebsite` instance. an `EmbeddedWebsite` instance.
```javascript ```javascript
// Get an existing website object where 'my_website' is the name of the object (on any layer object of the map) // 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; website.visible = true;
``` ```
### Adding a new website in a map ### Adding a new website in a map
``` ```
@ -201,34 +215,38 @@ interface CreateEmbeddedWebsiteEvent {
name: string; // A unique name for this iframe name: string; // A unique name for this iframe
url: string; // The URL the iframe points to. url: string; // The URL the iframe points to.
position: { position: {
x: number, // In pixels, relative to the map coordinates x: number, // In "game" pixels, relative to the map or player coordinates, depending on origin
y: number, // In pixels, relative to the map coordinates y: number, // In "game" pixels, relative to the map or player coordinates, depending on origin
width: number, // In pixels, sensitive to zoom level width: number, // In "game" pixels
height: number, // In pixels, sensitive to zoom level height: number, // In "game" pixels
}, },
visible?: boolean, // Whether to display the iframe or not visible?: boolean, // Whether to display the iframe or not
allowApi?: boolean, // Whether the scripting API should be available to the iframe allowApi?: boolean, // Whether the scripting API should be available to the iframe
allow?: string, // The list of feature policies allowed 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. You can create an instance of an embedded website by using the `WA.room.website.create()` method. It returns
It returns an `EmbeddedWebsite` instance. an `EmbeddedWebsite` instance.
```javascript ```javascript
// Create a new website object // Create a new website object
const website = WA.room.website.create({ const website = WA.room.website.create({
name: "my_website", name: "my_website",
url: "https://example.com", url: "https://example.com",
position: { position: {
x: 64, x: 64,
y: 128, y: 128,
width: 320, width: 320,
height: 240, height: 240,
}, },
visible: true, visible: true,
allowApi: true, allowApi: true,
allow: "fullscreen", 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. Use `WA.room.website.delete` to completely remove an embedded website from your map.
### The EmbeddedWebsite class ### The EmbeddedWebsite class
Instances of the `EmbeddedWebsite` class represent the website displayed on the map. Instances of the `EmbeddedWebsite` class represent the website displayed on the map.
```typescript ```typescript
class EmbeddedWebsite { class EmbeddedWebsite {
readonly name: string; readonly name: string;
url: string; url: string;
visible: boolean; visible: boolean;
allow: string; allow: string;
allowApi: boolean; allowApi: boolean;
x: number; // In pixels, relative to the map coordinates x: number; // In "game" pixels, relative to the map or player coordinates, depending on origin
y: number; // In pixels, relative to the map coordinates y: number; // In "game" pixels, relative to the map or player coordinates, depending on origin
width: number; // In pixels, sensitive to zoom level width: number; // In "game" pixels
height: number; // In pixels, sensitive to zoom level 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. 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
{.alert.alert-warning} to be displayed for every player, you can use [variables](api-start.md) to share a common state between all users.
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

@ -35,7 +35,6 @@ module.exports = {
"no-unused-vars": "off", "no-unused-vars": "off",
"@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-explicit-any": "error",
// TODO: remove those ignored rules and write a stronger code! // TODO: remove those ignored rules and write a stronger code!
"@typescript-eslint/no-floating-promises": "off",
"@typescript-eslint/no-unsafe-call": "off", "@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/restrict-plus-operands": "off", "@typescript-eslint/restrict-plus-operands": "off",
"@typescript-eslint/no-unsafe-assignment": "off", "@typescript-eslint/no-unsafe-assignment": "off",

View File

@ -1,13 +1,14 @@
FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder
WORKDIR /usr/src WORKDIR /usr/src
COPY messages . COPY messages .
RUN yarn install && yarn proto RUN yarn install && yarn ts-proto
# we are rebuilding on each deploy to cope with the PUSHER_URL environment URL # we are rebuilding on each deploy to cope with the PUSHER_URL environment URL
FROM thecodingmachine/nodejs:14-apache FROM thecodingmachine/nodejs:14-apache
COPY --chown=docker:docker front . COPY --chown=docker:docker front .
COPY --from=builder --chown=docker:docker /usr/src/generated /var/www/html/src/Messages/generated COPY --from=builder --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=builder --chown=docker:docker /usr/src/JsonMessages /var/www/html/src/Messages/JsonMessages COPY --from=builder --chown=docker:docker /usr/src/JsonMessages /var/www/html/src/Messages/JsonMessages
# Removing the iframe.html file from the final image as this adds a XSS attack. # Removing the iframe.html file from the final image as this adds a XSS attack.

View File

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

View File

@ -18,64 +18,84 @@ class AnalyticsClient {
} }
identifyUser(uuid: string, email: string | null) { identifyUser(uuid: string, email: string | null) {
this.posthogPromise?.then((posthog) => { this.posthogPromise
posthog.identify(uuid, { uuid, email, wa: true }); ?.then((posthog) => {
}); posthog.identify(uuid, { uuid, email, wa: true });
})
.catch((e) => console.error(e));
} }
loggedWithSso() { loggedWithSso() {
this.posthogPromise?.then((posthog) => { this.posthogPromise
posthog.capture("wa-logged-sso"); ?.then((posthog) => {
}); posthog.capture("wa-logged-sso");
})
.catch((e) => console.error(e));
} }
loggedWithToken() { loggedWithToken() {
this.posthogPromise?.then((posthog) => { this.posthogPromise
posthog.capture("wa-logged-token"); ?.then((posthog) => {
}); posthog.capture("wa-logged-token");
})
.catch((e) => console.error(e));
} }
enteredRoom(roomId: string, roomGroup: string | null) { enteredRoom(roomId: string, roomGroup: string | null) {
this.posthogPromise?.then((posthog) => { this.posthogPromise
posthog.capture("$pageView", { roomId, roomGroup }); ?.then((posthog) => {
posthog.capture("enteredRoom"); posthog.capture("$pageView", { roomId, roomGroup });
}); posthog.capture("enteredRoom");
})
.catch((e) => console.error(e));
} }
openedMenu() { openedMenu() {
this.posthogPromise?.then((posthog) => { this.posthogPromise
posthog.capture("wa-opened-menu"); ?.then((posthog) => {
}); posthog.capture("wa-opened-menu");
})
.catch((e) => console.error(e));
} }
launchEmote(emote: string) { launchEmote(emote: string) {
this.posthogPromise?.then((posthog) => { this.posthogPromise
posthog.capture("wa-emote-launch", { emote }); ?.then((posthog) => {
}); posthog.capture("wa-emote-launch", { emote });
})
.catch((e) => console.error(e));
} }
enteredJitsi(roomName: string, roomId: string) { enteredJitsi(roomName: string, roomId: string) {
this.posthogPromise?.then((posthog) => { this.posthogPromise
posthog.capture("wa-entered-jitsi", { roomName, roomId }); ?.then((posthog) => {
}); posthog.capture("wa-entered-jitsi", { roomName, roomId });
})
.catch((e) => console.error(e));
} }
validationName() { validationName() {
this.posthogPromise?.then((posthog) => { this.posthogPromise
posthog.capture("wa-name-validation"); ?.then((posthog) => {
}); posthog.capture("wa-name-validation");
})
.catch((e) => console.error(e));
} }
validationWoka(scene: string) { validationWoka(scene: string) {
this.posthogPromise?.then((posthog) => { this.posthogPromise
posthog.capture("wa-woka-validation", { scene }); ?.then((posthog) => {
}); posthog.capture("wa-woka-validation", { scene });
})
.catch((e) => console.error(e));
} }
validationVideo() { validationVideo() {
this.posthogPromise?.then((posthog) => { this.posthogPromise
posthog.capture("wa-video-validation"); ?.then((posthog) => {
}); posthog.capture("wa-video-validation");
})
.catch((e) => console.error(e));
} }
} }
export const analyticsClient = new AnalyticsClient(); export const analyticsClient = new AnalyticsClient();

View File

@ -22,6 +22,8 @@ export const isEmbeddedWebsiteEvent = new tg.IsInterface()
y: tg.isNumber, y: tg.isNumber,
width: tg.isNumber, width: tg.isNumber,
height: tg.isNumber, height: tg.isNumber,
origin: tg.isSingletonStringUnion("player", "map"),
scale: tg.isNumber,
}) })
.get(); .get();
@ -35,6 +37,8 @@ export const isCreateEmbeddedWebsiteEvent = new tg.IsInterface()
visible: tg.isBoolean, visible: tg.isBoolean,
allowApi: tg.isBoolean, allowApi: tg.isBoolean,
allow: tg.isString, allow: tg.isString,
origin: tg.isSingletonStringUnion("player", "map"),
scale: tg.isNumber,
}) })
.get(); .get();

View File

@ -10,6 +10,7 @@ export const isGameStateEvent = new tg.IsInterface()
tags: tg.isArray(tg.isString), tags: tg.isArray(tg.isString),
variables: tg.isObject, variables: tg.isObject,
userRoomToken: tg.isUnion(tg.isString, tg.isUndefined), userRoomToken: tg.isUnion(tg.isString, tg.isUndefined),
playerVariables: tg.isObject,
}) })
.get(); .get();
/** /**

View File

@ -30,6 +30,8 @@ import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEv
import type { ChangeLayerEvent } from "./ChangeLayerEvent"; import type { ChangeLayerEvent } from "./ChangeLayerEvent";
import type { ChangeZoneEvent } from "./ChangeZoneEvent"; import type { ChangeZoneEvent } from "./ChangeZoneEvent";
import { isColorEvent } from "./ColorEvent"; import { isColorEvent } from "./ColorEvent";
import { isPlayerPosition } from "./PlayerPosition";
import type { WasCameraUpdatedEvent } from "./WasCameraUpdatedEvent";
export interface TypedMessageEvent<T> extends MessageEvent { export interface TypedMessageEvent<T> extends MessageEvent {
data: T; data: T;
@ -50,6 +52,7 @@ export type IframeEventMap = {
displayBubble: null; displayBubble: null;
removeBubble: null; removeBubble: null;
onPlayerMove: undefined; onPlayerMove: undefined;
onCameraUpdate: undefined;
showLayer: LayerEvent; showLayer: LayerEvent;
hideLayer: LayerEvent; hideLayer: LayerEvent;
setProperty: SetPropertyEvent; setProperty: SetPropertyEvent;
@ -82,6 +85,7 @@ export interface IframeResponseEventMap {
leaveZoneEvent: ChangeZoneEvent; leaveZoneEvent: ChangeZoneEvent;
buttonClickedEvent: ButtonClickedEvent; buttonClickedEvent: ButtonClickedEvent;
hasPlayerMoved: HasPlayerMovedEvent; hasPlayerMoved: HasPlayerMovedEvent;
wasCameraUpdated: WasCameraUpdatedEvent;
menuItemClicked: MenuItemClickedEvent; menuItemClicked: MenuItemClickedEvent;
setVariable: SetVariableEvent; setVariable: SetVariableEvent;
messageTriggered: MessageReferenceEvent; messageTriggered: MessageReferenceEvent;
@ -161,6 +165,10 @@ export const iframeQueryMapTypeGuards = {
query: tg.isUndefined, query: tg.isUndefined,
answer: tg.isUndefined, answer: tg.isUndefined,
}, },
getPlayerPosition: {
query: tg.isUndefined,
answer: isPlayerPosition,
},
}; };
type GuardedType<T> = T extends (x: unknown) => x is infer T ? T : never; 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({ .withProperties({
key: tg.isString, key: tg.isString,
value: tg.isUnknown, value: tg.isUnknown,
target: tg.isSingletonStringUnion("global", "player"),
}) })
.get(); .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 { ModifyEmbeddedWebsiteEvent, isEmbeddedWebsiteEvent } from "./Events/EmbeddedWebsiteEvent";
import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Stores/MenuStore"; import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Stores/MenuStore";
import type { ChangeLayerEvent } from "./Events/ChangeLayerEvent"; import type { ChangeLayerEvent } from "./Events/ChangeLayerEvent";
import type { WasCameraUpdatedEvent } from "./Events/WasCameraUpdatedEvent";
import type { ChangeZoneEvent } from "./Events/ChangeZoneEvent"; import type { ChangeZoneEvent } from "./Events/ChangeZoneEvent";
type AnswererCallback<T extends keyof IframeQueryMap> = ( type AnswererCallback<T extends keyof IframeQueryMap> = (
@ -85,6 +86,9 @@ class IframeListener {
private readonly _loadSoundStream: Subject<LoadSoundEvent> = new Subject(); private readonly _loadSoundStream: Subject<LoadSoundEvent> = new Subject();
public readonly loadSoundStream = this._loadSoundStream.asObservable(); 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(); private readonly _setTilesStream: Subject<SetTilesEvent> = new Subject();
public readonly setTilesStream = this._setTilesStream.asObservable(); public readonly setTilesStream = this._setTilesStream.asObservable();
@ -226,6 +230,8 @@ class IframeListener {
this._removeBubbleStream.next(); this._removeBubbleStream.next();
} else if (payload.type == "onPlayerMove") { } else if (payload.type == "onPlayerMove") {
this.sendPlayerMove = true; this.sendPlayerMove = true;
} else if (payload.type == "onCameraUpdate") {
this._trackCameraUpdateStream.next();
} else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) { } else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) {
this._setTilesStream.next(payload.data); this._setTilesStream.next(payload.data);
} else if (payload.type == "modifyEmbeddedWebsite" && isEmbeddedWebsiteEvent(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 { sendButtonClickedEvent(popupId: number, buttonId: number): void {
this.postMessage({ this.postMessage({
type: "buttonClickedEvent", type: "buttonClickedEvent",

View File

@ -12,6 +12,8 @@ export class EmbeddedWebsite {
private _allow: string; private _allow: string;
private _allowApi: boolean; private _allowApi: boolean;
private _position: Rectangle; private _position: Rectangle;
private readonly origin: "map" | "player" | undefined;
private _scale: number;
constructor(private config: CreateEmbeddedWebsiteEvent) { constructor(private config: CreateEmbeddedWebsiteEvent) {
this.name = config.name; this.name = config.name;
@ -20,6 +22,12 @@ export class EmbeddedWebsite {
this._allow = config.allow ?? ""; this._allow = config.allow ?? "";
this._allowApi = config.allowApi ?? false; this._allowApi = config.allowApi ?? false;
this._position = config.position; this._position = config.position;
this.origin = config.origin;
this._scale = config.scale ?? 1;
}
public get url() {
return this._url;
} }
public set url(url: string) { public set url(url: string) {
@ -33,6 +41,10 @@ export class EmbeddedWebsite {
}); });
} }
public get visible() {
return this._visible;
}
public set visible(visible: boolean) { public set visible(visible: boolean) {
this._visible = visible; this._visible = visible;
sendToWorkadventure({ sendToWorkadventure({
@ -44,6 +56,10 @@ export class EmbeddedWebsite {
}); });
} }
public get x() {
return this._position.x;
}
public set x(x: number) { public set x(x: number) {
this._position.x = x; this._position.x = x;
sendToWorkadventure({ sendToWorkadventure({
@ -55,6 +71,10 @@ export class EmbeddedWebsite {
}); });
} }
public get y() {
return this._position.y;
}
public set y(y: number) { public set y(y: number) {
this._position.y = y; this._position.y = y;
sendToWorkadventure({ sendToWorkadventure({
@ -66,6 +86,10 @@ export class EmbeddedWebsite {
}); });
} }
public get width() {
return this._position.width;
}
public set width(width: number) { public set width(width: number) {
this._position.width = width; this._position.width = width;
sendToWorkadventure({ sendToWorkadventure({
@ -77,6 +101,10 @@ export class EmbeddedWebsite {
}); });
} }
public get height() {
return this._position.height;
}
public set height(height: number) { public set height(height: number) {
this._position.height = height; this._position.height = height;
sendToWorkadventure({ 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

@ -26,7 +26,7 @@ export class ActionMessage {
this.message = actionMessageOptions.message; this.message = actionMessageOptions.message;
this.type = actionMessageOptions.type ?? "message"; this.type = actionMessageOptions.type ?? "message";
this.callback = actionMessageOptions.callback; this.callback = actionMessageOptions.callback;
this.create(); this.create().catch((e) => console.error(e));
} }
private async create() { private async create() {

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 { Subject } from "rxjs";
import { apiCallback } from "./registeredCallbacks"; import { apiCallback } from "./registeredCallbacks";
import { isHasPlayerMovedEvent } from "../Events/HasPlayerMovedEvent"; import { isHasPlayerMovedEvent } from "../Events/HasPlayerMovedEvent";
import { createState } from "./state";
const moveStream = new Subject<HasPlayerMovedEvent>(); const moveStream = new Subject<HasPlayerMovedEvent>();
@ -31,6 +32,8 @@ export const setUuid = (_uuid: string | undefined) => {
}; };
export class WorkadventurePlayerCommands extends IframeApiContribution<WorkadventurePlayerCommands> { export class WorkadventurePlayerCommands extends IframeApiContribution<WorkadventurePlayerCommands> {
readonly state = createState("player");
callbacks = [ callbacks = [
apiCallback({ apiCallback({
type: "hasPlayerMoved", type: "hasPlayerMoved",
@ -74,6 +77,13 @@ export class WorkadventurePlayerCommands extends IframeApiContribution<Workadven
return uuid; return uuid;
} }
async getPosition(): Promise<Position> {
return await queryWorkadventure({
type: "getPlayerPosition",
data: undefined,
});
}
get userRoomToken(): string | undefined { get userRoomToken(): string | undefined {
if (userRoomToken === undefined) { if (userRoomToken === undefined) {
throw new Error( throw new Error(
@ -102,4 +112,9 @@ export class WorkadventurePlayerCommands extends IframeApiContribution<Workadven
} }
} }
export type Position = {
x: number;
y: number;
};
export default new WorkadventurePlayerCommands(); export default new WorkadventurePlayerCommands();

View File

@ -8,93 +8,101 @@ import { isSetVariableEvent, SetVariableEvent } from "../Events/SetVariableEvent
import type { ITiledMap } from "../../Phaser/Map/ITiledMap"; 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> { 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 = [ callbacks = [
apiCallback({ apiCallback({
type: "setVariable", type: "setVariable",
typeChecker: isSetVariableEvent, typeChecker: isSetVariableEvent,
callback: (payloadData) => { 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> { saveVariable(key: string, value: unknown): Promise<void> {
variables.set(key, value); this.variables.set(key, value);
return queryWorkadventure({ return queryWorkadventure({
type: "setVariable", type: "setVariable",
data: { data: {
key, key,
value, value,
target: this.target,
}, },
}); });
} }
loadVariable(key: string): unknown { loadVariable(key: string): unknown {
return variables.get(key); return this.variables.get(key);
} }
hasVariable(key: string): boolean { hasVariable(key: string): boolean {
return variables.has(key); return this.variables.has(key);
} }
onVariableChange(key: string): Observable<unknown> { onVariableChange(key: string): Observable<unknown> {
let subject = variableSubscribers.get(key); let subject = this.variableSubscribers.get(key);
if (subject === undefined) { if (subject === undefined) {
subject = new Subject<unknown>(); subject = new Subject<unknown>();
variableSubscribers.set(key, subject); this.variableSubscribers.set(key, subject);
} }
return subject.asObservable(); return subject.asObservable();
} }
} }
const proxyCommand = new Proxy(new WorkadventureStateCommands(), { export function createState(target: "global" | "player"): WorkadventureStateCommands & { [key: string]: unknown } {
get(target: WorkadventureStateCommands, p: PropertyKey, receiver: unknown): unknown { return new Proxy(new WorkadventureStateCommands(target), {
if (p in target) { get(target: WorkadventureStateCommands, p: PropertyKey, receiver: unknown): unknown {
return Reflect.get(target, p, receiver); if (p in target) {
} return Reflect.get(target, p, receiver);
return target.loadVariable(p.toString()); }
}, 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. set(target: WorkadventureStateCommands, p: PropertyKey, value: unknown, receiver: unknown): boolean {
// User must use WA.state.saveVariable to have error message. // Note: when using "set", there is no way to wait, so we ignore the return of the promise.
target.saveVariable(p.toString(), value); // User must use WA.state.saveVariable to have error message.
return true; target.saveVariable(p.toString(), value).catch((e) => console.error(e));
},
has(target: WorkadventureStateCommands, p: PropertyKey): boolean {
if (p in target) {
return true; return true;
} },
return target.hasVariable(p.toString()); has(target: WorkadventureStateCommands, p: PropertyKey): boolean {
}, if (p in target) {
}) as WorkadventureStateCommands & { [key: string]: unknown }; return true;
}
export default proxyCommand; 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 { IframeApiContribution, queryWorkadventure, sendToWorkadventure } from "./IframeApiContribution";
import { Sound } from "./Sound/Sound";
import { EmbeddedWebsite } from "./Room/EmbeddedWebsite"; import { EmbeddedWebsite } from "./Room/EmbeddedWebsite";
import type { CreateEmbeddedWebsiteEvent } from "../Events/EmbeddedWebsiteEvent"; import type { CreateEmbeddedWebsiteEvent } from "../Events/EmbeddedWebsiteEvent";

View File

@ -23,6 +23,9 @@
import { chatVisibilityStore } from "../Stores/ChatStore"; import { chatVisibilityStore } from "../Stores/ChatStore";
import { helpCameraSettingsVisibleStore } from "../Stores/HelpCameraSettingsStore"; import { helpCameraSettingsVisibleStore } from "../Stores/HelpCameraSettingsStore";
import HelpCameraSettingsPopup from "./HelpCameraSettings/HelpCameraSettingsPopup.svelte"; import HelpCameraSettingsPopup from "./HelpCameraSettings/HelpCameraSettingsPopup.svelte";
import { showLimitRoomModalStore, showShareLinkMapModalStore } from "../Stores/ModalStore";
import LimitRoomModal from "./Modal/LimitRoomModal.svelte";
import ShareLinkMapModal from "./Modal/ShareLinkMapModal.svelte";
import AudioPlaying from "./UI/AudioPlaying.svelte"; import AudioPlaying from "./UI/AudioPlaying.svelte";
import { soundPlayingStore } from "../Stores/SoundPlayingStore"; import { soundPlayingStore } from "../Stores/SoundPlayingStore";
import ErrorDialog from "./UI/ErrorDialog.svelte"; import ErrorDialog from "./UI/ErrorDialog.svelte";
@ -136,6 +139,16 @@
<HelpCameraSettingsPopup /> <HelpCameraSettingsPopup />
</div> </div>
{/if} {/if}
{#if $showLimitRoomModalStore}
<div>
<LimitRoomModal />
</div>
{/if}
{#if $showShareLinkMapModalStore}
<div>
<ShareLinkMapModal />
</div>
{/if}
{#if $requestVisitCardsStore} {#if $requestVisitCardsStore}
<VisitCard visitCardUrl={$requestVisitCardsStore} /> <VisitCard visitCardUrl={$requestVisitCardsStore} />
{/if} {/if}

View File

@ -19,12 +19,13 @@
audioManagerVolumeStore.setVolume(volume); audioManagerVolumeStore.setVolume(volume);
audioManagerVolumeStore.setMuted(localUserStore.getAudioPlayerMuted()); audioManagerVolumeStore.setMuted(localUserStore.getAudioPlayerMuted());
unsubscriberFileStore = audioManagerFileStore.subscribe(() => { unsubscriberFileStore = audioManagerFileStore.subscribe((src) => {
HTMLAudioPlayer.pause(); HTMLAudioPlayer.pause();
HTMLAudioPlayer.src = src;
HTMLAudioPlayer.loop = get(audioManagerVolumeStore).loop; HTMLAudioPlayer.loop = get(audioManagerVolumeStore).loop;
HTMLAudioPlayer.volume = get(audioManagerVolumeStore).volume; HTMLAudioPlayer.volume = get(audioManagerVolumeStore).volume;
HTMLAudioPlayer.muted = get(audioManagerVolumeStore).muted; HTMLAudioPlayer.muted = get(audioManagerVolumeStore).muted;
HTMLAudioPlayer.play(); void HTMLAudioPlayer.play();
}); });
unsubscriberVolumeStore = audioManagerVolumeStore.subscribe((audioManager: audioManagerVolume) => { unsubscriberVolumeStore = audioManagerVolumeStore.subscribe((audioManager: audioManagerVolume) => {
const reduceVolume = audioManager.talking && audioManager.decreaseWhileTalking; const reduceVolume = audioManager.talking && audioManager.decreaseWhileTalking;
@ -148,9 +149,7 @@
</label> </label>
<section class="audio-manager-file"> <section class="audio-manager-file">
<!-- svelte-ignore a11y-media-has-caption --> <!-- svelte-ignore a11y-media-has-caption -->
<audio class="audio-manager-audioplayer" bind:this={HTMLAudioPlayer}> <audio class="audio-manager-audioplayer" bind:this={HTMLAudioPlayer} />
<source src={$audioManagerFileStore} />
</audio>
</section> </section>
</div> </div>
</div> </div>

View File

@ -67,6 +67,7 @@
.messagePart { .messagePart {
flex-grow: 1; flex-grow: 1;
max-width: 100%; max-width: 100%;
user-select: text;
span.date { span.date {
font-size: 80%; font-size: 80%;

View File

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

View File

@ -19,12 +19,12 @@
uploadAudioActive = true; uploadAudioActive = true;
} }
function send() { async function send(): Promise<void> {
if (inputSendTextActive) { if (inputSendTextActive) {
handleSendText.sendTextMessage(broadcastToWorld); return handleSendText.sendTextMessage(broadcastToWorld);
} }
if (uploadAudioActive) { if (uploadAudioActive) {
handleSendAudio.sendAudioMessage(broadcastToWorld); return handleSendAudio.sendAudioMessage(broadcastToWorld);
} }
} }
</script> </script>

View File

@ -21,12 +21,12 @@
<div class="guest-main"> <div class="guest-main">
<section class="container-overflow"> <section class="container-overflow">
<section class="share-url not-mobile"> <section class="share-url not-mobile">
<h3>Share the link of the room !</h3> <h3>Share the link of the room!</h3>
<input type="text" readonly id="input-share-link" value={location.toString()} /> <input type="text" readonly id="input-share-link" value={location.toString()} />
<button type="button" class="nes-btn is-primary" on:click={copyLink}>Copy</button> <button type="button" class="nes-btn is-primary" on:click={copyLink}>Copy</button>
</section> </section>
<section class="is-mobile"> <section class="is-mobile">
<h3>Share the link of the room !</h3> <h3>Share the link of the room!</h3>
<input type="hidden" readonly id="input-share-link" value={location.toString()} /> <input type="hidden" readonly id="input-share-link" value={location.toString()} />
<button type="button" class="nes-btn is-primary" on:click={shareLink}>Share</button> <button type="button" class="nes-btn is-primary" on:click={shareLink}>Share</button>
</section> </section>

View File

@ -1,9 +1,14 @@
<script lang="typescript"> <script lang="typescript">
import logoTalk from "../images/logo-message-pixel.png"; import logoTalk from "../images/logo-message-pixel.png";
import logoWA from "../images/logo-WA-pixel.png"; import logoWA from "../images/logo-WA-pixel.png";
import logoInvite from "../images/logo-invite-pixel.png";
import logoRegister from "../images/logo-register-pixel.png";
import { menuVisiblilityStore } from "../../Stores/MenuStore"; import { menuVisiblilityStore } from "../../Stores/MenuStore";
import { chatVisibilityStore } from "../../Stores/ChatStore"; import { chatVisibilityStore } from "../../Stores/ChatStore";
import { limitMapStore } from "../../Stores/GameStore";
import { get } from "svelte/store"; import { get } from "svelte/store";
import { ADMIN_URL } from "../../Enum/EnvironmentVariable";
import { showShareLinkMapModalStore } from "../../Stores/ModalStore";
function showMenu() { function showMenu() {
menuVisiblilityStore.set(!get(menuVisiblilityStore)); menuVisiblilityStore.set(!get(menuVisiblilityStore));
@ -11,13 +16,25 @@
function showChat() { function showChat() {
chatVisibilityStore.set(true); chatVisibilityStore.set(true);
} }
function register() {
window.open(`${ADMIN_URL}/second-step-register`, "_self");
}
function showInvite() {
showShareLinkMapModalStore.set(true);
}
</script> </script>
<svelte:window /> <svelte:window />
<main class="menuIcon"> <main class="menuIcon">
<img src={logoWA} alt="open menu" class="nes-pointer" on:click|preventDefault={showMenu} /> {#if $limitMapStore}
<img src={logoTalk} alt="open menu" class="nes-pointer" on:click|preventDefault={showChat} /> <img src={logoInvite} alt="open menu" class="nes-pointer" on:click|preventDefault={showInvite} />
<img src={logoRegister} alt="open menu" class="nes-pointer" on:click|preventDefault={register} />
{:else}
<img src={logoWA} alt="open menu" class="nes-pointer" on:click|preventDefault={showMenu} />
<img src={logoTalk} alt="open menu" class="nes-pointer" on:click|preventDefault={showChat} />
{/if}
</main> </main>
<style lang="scss"> <style lang="scss">

View File

@ -41,10 +41,10 @@
gameManager.leaveGame(SelectCharacterSceneName, new SelectCharacterScene()); gameManager.leaveGame(SelectCharacterSceneName, new SelectCharacterScene());
} }
function logOut() { async function logOut() {
disableMenuStores(); disableMenuStores();
loginSceneVisibleStore.set(true); loginSceneVisibleStore.set(true);
connectionManager.logout(); return connectionManager.logout();
} }
function getProfileUrl() { function getProfileUrl() {

View File

@ -33,9 +33,9 @@
const body = HtmlUtils.querySelectorOrFail("body"); const body = HtmlUtils.querySelectorOrFail("body");
if (body) { if (body) {
if (document.fullscreenElement !== null && !fullscreen) { if (document.fullscreenElement !== null && !fullscreen) {
document.exitFullscreen(); document.exitFullscreen().catch((e) => console.error(e));
} else { } else {
body.requestFullscreen(); body.requestFullscreen().catch((e) => console.error(e));
} }
localUserStore.setFullscreen(fullscreen); localUserStore.setFullscreen(fullscreen);
} }
@ -45,14 +45,16 @@
if (Notification.permission === "granted") { if (Notification.permission === "granted") {
localUserStore.setNotification(notification ? "granted" : "denied"); localUserStore.setNotification(notification ? "granted" : "denied");
} else { } else {
Notification.requestPermission().then((response) => { Notification.requestPermission()
if (response === "granted") { .then((response) => {
localUserStore.setNotification(notification ? "granted" : "denied"); if (response === "granted") {
} else { localUserStore.setNotification(notification ? "granted" : "denied");
localUserStore.setNotification("denied"); } else {
notification = false; localUserStore.setNotification("denied");
} notification = false;
}); }
})
.catch((e) => console.error(e));
} }
} }

View File

@ -0,0 +1,47 @@
<script lang="typescript">
import { fly } from "svelte/transition";
import { ADMIN_URL } from "../../Enum/EnvironmentVariable";
function register() {
window.open(`${ADMIN_URL}/second-step-register`, "_self");
}
</script>
<div class="limit-map nes-container" transition:fly={{ y: -900, duration: 500 }}>
<section>
<h2>Limit of your room</h2>
<p>Register your account!</p>
<p>
This map is limited in the time and to continue to use WorkAdventure, you must register your account in our
back office.
</p>
</section>
<section>
<button class="nes-btn is-primary" on:click|preventDefault={register}>Register</button>
</section>
</div>
<style lang="scss">
.limit-map {
pointer-events: auto;
background: #eceeee;
margin-left: auto;
margin-right: auto;
margin-top: 10vh;
max-height: 80vh;
max-width: 80vw;
overflow: auto;
text-align: center;
h2 {
font-family: "Press Start 2P";
}
section {
p {
margin: 15px;
font-family: "Press Start 2P";
}
}
}
</style>

View File

@ -0,0 +1,90 @@
<script lang="typescript">
import { fly } from "svelte/transition";
import { showShareLinkMapModalStore } from "../../Stores/ModalStore";
interface ExtNavigator extends Navigator {
canShare?(data?: ShareData): Promise<boolean>;
}
const myNavigator: ExtNavigator = window.navigator;
const haveNavigatorSharingFeature: boolean =
myNavigator && myNavigator.canShare != null && myNavigator.share != null;
let copied: boolean = false;
function copyLink() {
try {
const input: HTMLInputElement = document.getElementById("input-share-link") as HTMLInputElement;
input.focus();
input.select();
document.execCommand("copy");
copied = true;
} catch (e) {
console.error(e);
copied = false;
}
}
async function shareLink() {
const shareData = { url: location.toString() };
try {
await myNavigator.share(shareData);
} catch (err) {
console.error("Error: " + err);
copyLink();
}
}
function close() {
showShareLinkMapModalStore.set(false);
copied = false;
}
</script>
<div class="share-link-map nes-container" transition:fly={{ y: -900, duration: 500 }}>
<section>
<h2>Invite your friends or colleagues</h2>
<p>Share the link of the room!</p>
</section>
<section>
{#if haveNavigatorSharingFeature}
<input type="hidden" readonly id="input-share-link" value={location.toString()} />
<button type="button" class="nes-btn is-primary" on:click={shareLink}>Share</button>
{:else}
<input type="text" readonly id="input-share-link" value={location.toString()} />
<button type="button" class="nes-btn is-primary" on:click={copyLink}>Copy</button>
{/if}
{#if copied}
<p>Copied!</p>
{/if}
</section>
<section>
<button class="nes-btn" on:click|preventDefault={close}>Close</button>
</section>
</div>
<style lang="scss">
div.share-link-map {
pointer-events: auto;
background: #eceeee;
margin-left: auto;
margin-right: auto;
margin-top: 10vh;
max-height: 80vh;
max-width: 80vw;
overflow: auto;
text-align: center;
h2 {
font-family: "Press Start 2P";
}
section {
p {
margin: 15px;
font-family: "Press Start 2P";
}
}
}
</style>

View File

@ -6,12 +6,11 @@
import type { Unsubscriber } from "svelte/store"; import type { Unsubscriber } from "svelte/store";
import { playersStore } from "../../Stores/PlayersStore"; import { playersStore } from "../../Stores/PlayersStore";
import { connectionManager } from "../../Connexion/ConnectionManager"; import { connectionManager } from "../../Connexion/ConnectionManager";
import { GameConnexionTypes } from "../../Url/UrlManager";
import { get } from "svelte/store"; import { get } from "svelte/store";
let blockActive = true; let blockActive = true;
let reportActive = !blockActive; let reportActive = !blockActive;
let anonymous: boolean = false; let disableReport: boolean = false;
let userUUID: string | undefined = playersStore.getPlayerById(get(showReportScreenStore).userId)?.userUuid; let userUUID: string | undefined = playersStore.getPlayerById(get(showReportScreenStore).userId)?.userUuid;
let userName = "No name"; let userName = "No name";
let unsubscriber: Unsubscriber; let unsubscriber: Unsubscriber;
@ -26,7 +25,7 @@
} }
} }
}); });
anonymous = connectionManager.getConnexionType === GameConnexionTypes.anonymous; disableReport = !connectionManager.currentRoom?.canReport ?? true;
}); });
onDestroy(() => { onDestroy(() => {
@ -65,7 +64,7 @@
<button type="button" class="nes-btn" on:click|preventDefault={close}>X</button> <button type="button" class="nes-btn" on:click|preventDefault={close}>X</button>
</section> </section>
</section> </section>
<section class="report-menu-action {anonymous ? 'hidden' : ''}"> <section class="report-menu-action {disableReport ? 'hidden' : ''}">
<section class="justify-center"> <section class="justify-center">
<button <button
type="button" type="button"

View File

@ -12,7 +12,7 @@
} }
afterUpdate(() => { afterUpdate(() => {
audio.play(); audio.play().catch((e) => console.error(e));
}); });
</script> </script>

View File

@ -1,20 +1,26 @@
<script lang="typescript"> <script lang="typescript">
import { fly } from "svelte/transition"; import { fly } from "svelte/transition";
import { userIsAdminStore } from "../../Stores/GameStore"; import { userIsAdminStore, limitMapStore } from "../../Stores/GameStore";
import { ADMIN_URL } from "../../Enum/EnvironmentVariable"; import { ADMIN_URL } from "../../Enum/EnvironmentVariable";
const upgradeLink = ADMIN_URL + "/pricing"; const upgradeLink = ADMIN_URL + "/pricing";
const registerLink = ADMIN_URL + "/second-step-register";
</script> </script>
<main class="warningMain" transition:fly={{ y: -200, duration: 500 }}> <main class="warningMain" transition:fly={{ y: -200, duration: 500 }}>
<h2>Warning!</h2>
{#if $userIsAdminStore} {#if $userIsAdminStore}
<h2>Warning!</h2>
<p> <p>
This world is close to its limit!. You can upgrade its capacity <a href={upgradeLink} target="_blank" This world is close to its limit!. You can upgrade its capacity <a href={upgradeLink} target="_blank"
>here</a >here</a
> >
</p> </p>
{:else if $limitMapStore}
<p>
This map is available for 2 days. You can register your domain <a href={registerLink}>here</a>!
</p>
{:else} {:else}
<h2>Warning!</h2>
<p>This world is close to its limit!</p> <p>This world is close to its limit!</p>
{/if} {/if}
</main> </main>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 977 B

View File

@ -1,5 +1,5 @@
import { Subject } from "rxjs"; 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 { export enum AdminMessageEventTypes {
admin = "message", admin = "message",
@ -26,8 +26,8 @@ class AdminMessagesService {
onSendusermessage(message: SendUserMessage | BanUserMessage) { onSendusermessage(message: SendUserMessage | BanUserMessage) {
this._messageStream.next({ this._messageStream.next({
type: message.getType() as unknown as AdminMessageEventTypes, type: message.type as unknown as AdminMessageEventTypes,
text: message.getMessage(), text: message.message,
}); });
} }
} }

View File

@ -8,12 +8,14 @@ import { CharacterTexture, LocalUser } from "./LocalUser";
import { Room } from "./Room"; import { Room } from "./Room";
import { _ServiceWorker } from "../Network/ServiceWorker"; import { _ServiceWorker } from "../Network/ServiceWorker";
import { loginSceneVisibleIframeStore } from "../Stores/LoginSceneStore"; import { loginSceneVisibleIframeStore } from "../Stores/LoginSceneStore";
import { userIsConnected } from "../Stores/MenuStore"; import { userIsConnected, warningContainerStore } from "../Stores/MenuStore";
import { analyticsClient } from "../Administration/AnalyticsClient"; import { analyticsClient } from "../Administration/AnalyticsClient";
import { axiosWithRetry } from "./AxiosUtils"; import { axiosWithRetry } from "./AxiosUtils";
import axios from "axios"; import axios from "axios";
import { isRegisterData } from "../Messages/JsonMessages/RegisterData"; import { isRegisterData } from "../Messages/JsonMessages/RegisterData";
import { isAdminApiData } from "../Messages/JsonMessages/AdminApiData"; import { isAdminApiData } from "../Messages/JsonMessages/AdminApiData";
import { limitMapStore } from "../Stores/GameStore";
import { showLimitRoomModalStore } from "../Stores/ModalStore";
class ConnectionManager { class ConnectionManager {
private localUser!: LocalUser; private localUser!: LocalUser;
@ -152,11 +154,7 @@ class ConnectionManager {
) )
); );
urlManager.pushRoomIdToUrl(this._currentRoom); urlManager.pushRoomIdToUrl(this._currentRoom);
} else if ( } else if (connexionType === GameConnexionTypes.room || connexionType === GameConnexionTypes.empty) {
connexionType === GameConnexionTypes.organization ||
connexionType === GameConnexionTypes.anonymous ||
connexionType === GameConnexionTypes.empty
) {
this.authToken = localUserStore.getAuthToken(); this.authToken = localUserStore.getAuthToken();
let roomPath: string; let roomPath: string;
@ -188,7 +186,7 @@ class ConnectionManager {
//Set last room visited! (connected or nor, must to be saved in localstorage and cache API) //Set last room visited! (connected or nor, must to be saved in localstorage and cache API)
//use href to keep # value //use href to keep # value
localUserStore.setLastRoomUrl(this._currentRoom.href); await localUserStore.setLastRoomUrl(this._currentRoom.href);
//todo: add here some kind of warning if authToken has expired. //todo: add here some kind of warning if authToken has expired.
if (!this.authToken && !this._currentRoom.authenticationMandatory) { if (!this.authToken && !this._currentRoom.authenticationMandatory) {
@ -237,6 +235,17 @@ class ConnectionManager {
analyticsClient.identifyUser(this.localUser.uuid, this.localUser.email); analyticsClient.identifyUser(this.localUser.uuid, this.localUser.email);
} }
//if limit room active test headband
if (this._currentRoom.expireOn !== undefined) {
warningContainerStore.activateWarningContainer();
limitMapStore.set(true);
//check time of map
if (new Date() > this._currentRoom.expireOn) {
showLimitRoomModalStore.set(true);
}
}
this.serviceWorker = new _ServiceWorker(); this.serviceWorker = new _ServiceWorker();
return Promise.resolve(this._currentRoom); return Promise.resolve(this._currentRoom);
} }
@ -280,7 +289,7 @@ class ConnectionManager {
reject(error); reject(error);
}); });
connection.onConnectingError((event: CloseEvent) => { connection.connectionErrorStream.subscribe((event: CloseEvent) => {
console.log("An error occurred while connecting to socket server. Retrying"); console.log("An error occurred while connecting to socket server. Retrying");
reject( reject(
new Error( new Error(
@ -292,7 +301,7 @@ class ConnectionManager {
); );
}); });
connection.onConnect((connect: OnConnectInterface) => { connection.roomJoinedMessageStream.subscribe((connect: OnConnectInterface) => {
resolve(connect); resolve(connect);
}); });
}).catch((err) => { }).catch((err) => {
@ -301,7 +310,7 @@ class ConnectionManager {
this.reconnectingTimeout = setTimeout(() => { this.reconnectingTimeout = setTimeout(() => {
//todo: allow a way to break recursion? //todo: allow a way to break recursion?
//todo: find a way to avoid recursive function. Otherwise, the call stack will grow indefinitely. //todo: find a way to avoid recursive function. Otherwise, the call stack will grow indefinitely.
this.connectToRoomSocket(roomUrl, name, characterLayers, position, viewport, companion).then( void this.connectToRoomSocket(roomUrl, name, characterLayers, position, viewport, companion).then(
(connection) => resolve(connection) (connection) => resolve(connection)
); );
}, 4000 + Math.floor(Math.random() * 2000)); }, 4000 + Math.floor(Math.random() * 2000));

View File

@ -1,44 +1,12 @@
import type { SignalData } from "simple-peer"; import type { SignalData } from "simple-peer";
import type { RoomConnection } from "./RoomConnection"; import type { RoomConnection } from "./RoomConnection";
import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures"; import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures";
import { PositionMessage_Direction } from "../Messages/ts-proto-generated/messages";
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",
}
export interface PointInterface { export interface PointInterface {
x: number; x: number;
y: number; y: number;
direction: string; direction: string; // TODO: modify this to the enum from ts-proto
moving: boolean; 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 notification = "notificationPermission";
const code = "code"; const code = "code";
const cameraSetup = "cameraSetup"; const cameraSetup = "cameraSetup";
const cacheAPIIndex = "workavdenture-cache"; const cacheAPIIndex = "workavdenture-cache";
const userProperties = "user-properties";
class LocalUserStore { class LocalUserStore {
saveUser(localUser: LocalUser) { saveUser(localUser: LocalUser) {
@ -136,13 +136,12 @@ class LocalUserStore {
return localStorage.getItem(ignoreFollowRequests) === "true"; return localStorage.getItem(ignoreFollowRequests) === "true";
} }
setLastRoomUrl(roomUrl: string): void { async setLastRoomUrl(roomUrl: string): Promise<void> {
localStorage.setItem(lastRoomUrl, roomUrl.toString()); localStorage.setItem(lastRoomUrl, roomUrl.toString());
if ("caches" in window) { if ("caches" in window) {
caches.open(cacheAPIIndex).then((cache) => { const cache = await caches.open(cacheAPIIndex);
const stringResponse = new Response(JSON.stringify({ roomUrl })); const stringResponse = new Response(JSON.stringify({ roomUrl }));
cache.put(`/${lastRoomUrl}`, stringResponse); await cache.put(`/${lastRoomUrl}`, stringResponse);
});
} }
} }
getLastRoomUrl(): string { getLastRoomUrl(): string {
@ -220,6 +219,27 @@ class LocalUserStore {
const cameraSetupValues = localStorage.getItem(cameraSetup); const cameraSetupValues = localStorage.getItem(cameraSetup);
return cameraSetupValues != undefined ? JSON.parse(cameraSetupValues) : undefined; 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(); export const localUserStore = new LocalUserStore();

View File

@ -18,7 +18,10 @@ export interface RoomRedirect {
export class Room { export class Room {
public readonly id: string; public readonly id: string;
public readonly isPublic: boolean; /**
* @deprecated
*/
private readonly isPublic: boolean;
private _authenticationMandatory: boolean = DISABLE_ANONYMOUS; private _authenticationMandatory: boolean = DISABLE_ANONYMOUS;
private _iframeAuthentication?: string = OPID_LOGIN_SCREEN_PROVIDER; private _iframeAuthentication?: string = OPID_LOGIN_SCREEN_PROVIDER;
private _mapUrl: string | undefined; private _mapUrl: string | undefined;
@ -27,6 +30,8 @@ export class Room {
private readonly _search: URLSearchParams; private readonly _search: URLSearchParams;
private _contactPage: string | undefined; private _contactPage: string | undefined;
private _group: string | null = null; private _group: string | null = null;
private _expireOn: Date | undefined;
private _canReport: boolean = false;
private constructor(private roomUrl: URL) { private constructor(private roomUrl: URL) {
this.id = roomUrl.pathname; this.id = roomUrl.pathname;
@ -34,7 +39,7 @@ export class Room {
if (this.id.startsWith("/")) { if (this.id.startsWith("/")) {
this.id = this.id.substr(1); this.id = this.id.substr(1);
} }
if (this.id.startsWith("_/")) { if (this.id.startsWith("_/") || this.id.startsWith("*/")) {
this.isPublic = true; this.isPublic = true;
} else if (this.id.startsWith("@/")) { } else if (this.id.startsWith("@/")) {
this.isPublic = false; this.isPublic = false;
@ -121,6 +126,10 @@ export class Room {
data.authenticationMandatory != null ? data.authenticationMandatory : DISABLE_ANONYMOUS; data.authenticationMandatory != null ? data.authenticationMandatory : DISABLE_ANONYMOUS;
this._iframeAuthentication = data.iframeAuthentication || OPID_LOGIN_SCREEN_PROVIDER; this._iframeAuthentication = data.iframeAuthentication || OPID_LOGIN_SCREEN_PROVIDER;
this._contactPage = data.contactPage || CONTACT_URL; this._contactPage = data.contactPage || CONTACT_URL;
if (data.expireOn) {
this._expireOn = new Date(data.expireOn);
}
this._canReport = data.canReport ?? false;
return new MapDetail(data.mapUrl, data.textures); return new MapDetail(data.mapUrl, data.textures);
} else { } else {
throw new Error("Data received by the /map endpoint of the Pusher is not in a valid format."); throw new Error("Data received by the /map endpoint of the Pusher is not in a valid format.");
@ -143,6 +152,8 @@ export class Room {
* Instance name is: * Instance name is:
* - In a public URL: the second part of the URL ( _/[instance]/map.json) * - In a public URL: the second part of the URL ( _/[instance]/map.json)
* - In a private URL: [organizationId/worldId] * - In a private URL: [organizationId/worldId]
*
* @deprecated
*/ */
public getInstance(): string { public getInstance(): string {
if (this.instance !== undefined) { if (this.instance !== undefined) {
@ -150,7 +161,7 @@ export class Room {
} }
if (this.isPublic) { if (this.isPublic) {
const match = /_\/([^/]+)\/.+/.exec(this.id); const match = /[_*]\/([^/]+)\/.+/.exec(this.id);
if (!match) throw new Error('Could not extract instance from "' + this.id + '"'); if (!match) throw new Error('Could not extract instance from "' + this.id + '"');
this.instance = match[1]; this.instance = match[1];
return this.instance; return this.instance;
@ -222,4 +233,12 @@ export class Room {
get group(): string | null { get group(): string | null {
return this._group; return this._group;
} }
get expireOn(): Date | undefined {
return this._expireOn;
}
get canReport(): boolean {
return this._canReport;
}
} }

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

View File

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

View File

@ -41,13 +41,15 @@ export class Companion extends Container {
this.companionName = name; this.companionName = name;
this._pictureStore = writable(undefined); this._pictureStore = writable(undefined);
texturePromise.then((resource) => { texturePromise
this.addResource(resource); .then((resource) => {
this.invisible = false; this.addResource(resource);
return this.getSnapshot().then((htmlImageElementSrc) => { this.invisible = false;
this._pictureStore.set(htmlImageElementSrc); return this.getSnapshot().then((htmlImageElementSrc) => {
}); this._pictureStore.set(htmlImageElementSrc);
}); });
})
.catch((e) => console.error(e));
this.scene.physics.world.enableBody(this); this.scene.physics.world.enableBody(this);

View File

@ -3,7 +3,7 @@ import { COMPANION_RESOURCES, CompanionResourceDescriptionInterface } from "./Co
export const getAllCompanionResources = (loader: LoaderPlugin): CompanionResourceDescriptionInterface[] => { export const getAllCompanionResources = (loader: LoaderPlugin): CompanionResourceDescriptionInterface[] => {
COMPANION_RESOURCES.forEach((resource: CompanionResourceDescriptionInterface) => { COMPANION_RESOURCES.forEach((resource: CompanionResourceDescriptionInterface) => {
lazyLoadCompanionResource(loader, resource.name); lazyLoadCompanionResource(loader, resource.name).catch((e) => console.error(e));
}); });
return COMPANION_RESOURCES; return COMPANION_RESOURCES;

View File

@ -72,9 +72,11 @@ export class Loader {
if (this.loadingText) { if (this.loadingText) {
this.loadingText.destroy(); this.loadingText.destroy();
} }
promiseLoadLogoTexture.then((resLoadingImage: Phaser.GameObjects.Image) => { promiseLoadLogoTexture
resLoadingImage.destroy(); .then((resLoadingImage: Phaser.GameObjects.Image) => {
}); resLoadingImage.destroy();
})
.catch((e) => console.error(e));
this.progress.destroy(); this.progress.destroy();
this.progressContainer.destroy(); this.progressContainer.destroy();
if (this.scene instanceof DirtyScene) { if (this.scene instanceof DirtyScene) {

View File

@ -16,7 +16,8 @@ export class EmbeddedWebsiteManager {
if (website === undefined) { if (website === undefined) {
throw new Error('Cannot find embedded website with name "' + name + '"'); throw new Error('Cannot find embedded website with name "' + name + '"');
} }
const rect = website.iframe.getBoundingClientRect();
const scale = website.scale ?? 1;
return { return {
url: website.url, url: website.url,
name: website.name, name: website.name,
@ -26,9 +27,11 @@ export class EmbeddedWebsiteManager {
position: { position: {
x: website.phaserObject.x, x: website.phaserObject.x,
y: website.phaserObject.y, y: website.phaserObject.y,
width: rect["width"], width: website.phaserObject.width * scale,
height: rect["height"], height: website.phaserObject.height * scale,
}, },
origin: website.origin,
scale: website.scale,
}; };
}); });
@ -59,7 +62,9 @@ export class EmbeddedWebsiteManager {
createEmbeddedWebsiteEvent.position.height, createEmbeddedWebsiteEvent.position.height,
createEmbeddedWebsiteEvent.visible ?? true, createEmbeddedWebsiteEvent.visible ?? true,
createEmbeddedWebsiteEvent.allowApi ?? false, 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; website.phaserObject.y = embeddedWebsiteEvent.y;
} }
if (embeddedWebsiteEvent?.width !== undefined) { 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) { 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, height: number,
visible: boolean, visible: boolean,
allowApi: boolean, allowApi: boolean,
allow: string allow: string,
origin: "map" | "player" | undefined,
scale: number | undefined
): void { ): void {
if (this.embeddedWebsites.has(name)) { if (this.embeddedWebsites.has(name)) {
throw new Error('An embedded website with the name "' + name + '" already exists in your map'); throw new Error('An embedded website with the name "' + name + '" already exists in your map');
@ -135,9 +150,9 @@ export class EmbeddedWebsiteManager {
name, name,
url, url,
/*x, /*x,
y, y,
width, width,
height,*/ height,*/
allow, allow,
allowApi, allowApi,
visible, visible,
@ -147,6 +162,8 @@ export class EmbeddedWebsiteManager {
width, width,
height, height,
}, },
origin,
scale,
}; };
const embeddedWebsite = this.doCreateEmbeddedWebsite(embeddedWebsiteEvent, visible); const embeddedWebsite = this.doCreateEmbeddedWebsite(embeddedWebsiteEvent, visible);
@ -161,22 +178,43 @@ export class EmbeddedWebsiteManager {
const absoluteUrl = new URL(embeddedWebsiteEvent.url, this.gameScene.MapUrlFile).toString(); const absoluteUrl = new URL(embeddedWebsiteEvent.url, this.gameScene.MapUrlFile).toString();
const iframe = document.createElement("iframe"); const iframe = document.createElement("iframe");
const scale = embeddedWebsiteEvent.scale ?? 1;
iframe.src = absoluteUrl; iframe.src = absoluteUrl;
iframe.tabIndex = -1; iframe.tabIndex = -1;
iframe.style.width = embeddedWebsiteEvent.position.width + "px"; iframe.style.width = embeddedWebsiteEvent.position.width / scale + "px";
iframe.style.height = embeddedWebsiteEvent.position.height + "px"; iframe.style.height = embeddedWebsiteEvent.position.height / scale + "px";
iframe.style.margin = "0"; iframe.style.margin = "0";
iframe.style.padding = "0"; iframe.style.padding = "0";
iframe.style.border = "none"; 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 = { const embeddedWebsite = {
...embeddedWebsiteEvent, ...embeddedWebsiteEvent,
phaserObject: this.gameScene.add phaserObject: domElement,
.dom(embeddedWebsiteEvent.position.x, embeddedWebsiteEvent.position.y, iframe)
.setVisible(visible)
.setOrigin(0, 0),
iframe: iframe, iframe: iframe,
}; };
if (embeddedWebsiteEvent.allowApi) { if (embeddedWebsiteEvent.allowApi) {
iframeListener.registerIframe(iframe); iframeListener.registerIframe(iframe);
} }

View File

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

View File

@ -123,7 +123,7 @@ export class GameMapPropertiesListener {
.then((coWebsite) => { .then((coWebsite) => {
const coWebsiteOpen = this.coWebsitesOpenByLayer.get(layer); const coWebsiteOpen = this.coWebsitesOpenByLayer.get(layer);
if (coWebsiteOpen && coWebsiteOpen.state === OpenCoWebsiteState.MUST_BE_CLOSE) { if (coWebsiteOpen && coWebsiteOpen.state === OpenCoWebsiteState.MUST_BE_CLOSE) {
coWebsiteManager.closeCoWebsite(coWebsite); coWebsiteManager.closeCoWebsite(coWebsite).catch((e) => console.error(e));
this.coWebsitesOpenByLayer.delete(layer); this.coWebsitesOpenByLayer.delete(layer);
this.coWebsitesActionTriggerByLayer.delete(layer); this.coWebsitesActionTriggerByLayer.delete(layer);
} else { } else {
@ -132,7 +132,8 @@ export class GameMapPropertiesListener {
state: OpenCoWebsiteState.OPENED, state: OpenCoWebsiteState.OPENED,
}); });
} }
}); })
.catch((e) => console.error(e));
layoutManagerActionStore.removeAction(actionUuid); layoutManagerActionStore.removeAction(actionUuid);
}; };
@ -198,7 +199,7 @@ export class GameMapPropertiesListener {
} }
if (coWebsiteOpen.coWebsite !== undefined) { if (coWebsiteOpen.coWebsite !== undefined) {
coWebsiteManager.closeCoWebsite(coWebsiteOpen.coWebsite); coWebsiteManager.closeCoWebsite(coWebsiteOpen.coWebsite).catch((e) => console.error(e));
} }
this.coWebsitesOpenByLayer.delete(layer); this.coWebsitesOpenByLayer.delete(layer);

View File

@ -40,7 +40,6 @@ import { ReconnectingSceneName } from "../Reconnecting/ReconnectingScene";
import { GameMap } from "./GameMap"; import { GameMap } from "./GameMap";
import { PlayerMovement } from "./PlayerMovement"; import { PlayerMovement } from "./PlayerMovement";
import { PlayersPositionInterpolator } from "./PlayersPositionInterpolator"; import { PlayersPositionInterpolator } from "./PlayersPositionInterpolator";
import { worldFullMessageStream } from "../../Connexion/WorldFullMessageStream";
import { DirtyScene } from "./DirtyScene"; import { DirtyScene } from "./DirtyScene";
import { TextUtils } from "../Components/TextUtils"; import { TextUtils } from "../Components/TextUtils";
import { joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey } from "../Components/MobileJoystick"; import { joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey } from "../Components/MobileJoystick";
@ -60,7 +59,6 @@ import type {
PositionInterface, PositionInterface,
RoomJoinedMessageInterface, RoomJoinedMessageInterface,
} from "../../Connexion/ConnexionModels"; } from "../../Connexion/ConnexionModels";
import type { UserMovedMessage } from "../../Messages/generated/messages_pb";
import type { RoomConnection } from "../../Connexion/RoomConnection"; import type { RoomConnection } from "../../Connexion/RoomConnection";
import type { ActionableItem } from "../Items/ActionableItem"; import type { ActionableItem } from "../Items/ActionableItem";
import type { ItemFactoryInterface } from "../Items/ItemFactoryInterface"; import type { ItemFactoryInterface } from "../Items/ItemFactoryInterface";
@ -90,9 +88,10 @@ import SpriteSheetFile = Phaser.Loader.FileTypes.SpriteSheetFile;
import { deepCopy } from "deep-copy-ts"; import { deepCopy } from "deep-copy-ts";
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR; import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
import { MapStore } from "../../Stores/Utils/MapStore"; import { MapStore } from "../../Stores/Utils/MapStore";
import { SetPlayerDetailsMessage } from "../../Messages/generated/messages_pb";
import { followUsersColorStore, followUsersStore } from "../../Stores/FollowStore"; import { followUsersColorStore, followUsersStore } from "../../Stores/FollowStore";
import { getColorRgbFromHue } from "../../WebRtc/ColorGenerator"; import { getColorRgbFromHue } from "../../WebRtc/ColorGenerator";
import Camera = Phaser.Cameras.Scene2D.Camera;
import type { WasCameraUpdatedEvent } from "../../Api/Events/WasCameraUpdatedEvent";
export interface GameSceneInitInterface { export interface GameSceneInitInterface {
initPosition: PointInterface | null; initPosition: PointInterface | null;
@ -210,6 +209,8 @@ export class GameScene extends DirtyScene {
private objectsByType = new Map<string, ITiledMapObject[]>(); private objectsByType = new Map<string, ITiledMapObject[]>();
private embeddedWebsiteManager!: EmbeddedWebsiteManager; private embeddedWebsiteManager!: EmbeddedWebsiteManager;
private loader: Loader; private loader: Loader;
private lastCameraEvent: WasCameraUpdatedEvent | undefined;
private firstCameraUpdateSent: boolean = false;
constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) { constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) {
super({ super({
@ -240,7 +241,7 @@ export class GameScene extends DirtyScene {
const textures = localUser?.textures; const textures = localUser?.textures;
if (textures) { if (textures) {
for (const texture of textures) { for (const texture of textures) {
loadCustomTexture(this.load, texture); loadCustomTexture(this.load, texture).catch((e) => console.error(e));
} }
} }
@ -267,7 +268,7 @@ export class GameScene extends DirtyScene {
this.load.on( this.load.on(
"filecomplete-tilemapJSON-" + this.MapUrlFile, "filecomplete-tilemapJSON-" + this.MapUrlFile,
(key: string, type: string, data: unknown) => { (key: string, type: string, data: unknown) => {
this.onMapLoad(data); this.onMapLoad(data).catch((e) => console.error(e));
} }
); );
return; return;
@ -291,14 +292,14 @@ export class GameScene extends DirtyScene {
this.load.on( this.load.on(
"filecomplete-tilemapJSON-" + this.MapUrlFile, "filecomplete-tilemapJSON-" + this.MapUrlFile,
(key: string, type: string, data: unknown) => { (key: string, type: string, data: unknown) => {
this.onMapLoad(data); this.onMapLoad(data).catch((e) => console.error(e));
} }
); );
// If the map has already been loaded as part of another GameScene, the "on load" event will not be triggered. // If the map has already been loaded as part of another GameScene, the "on load" event will not be triggered.
// In this case, we check in the cache to see if the map is here and trigger the event manually. // In this case, we check in the cache to see if the map is here and trigger the event manually.
if (this.cache.tilemap.exists(this.MapUrlFile)) { if (this.cache.tilemap.exists(this.MapUrlFile)) {
const data = this.cache.tilemap.get(this.MapUrlFile); const data = this.cache.tilemap.get(this.MapUrlFile);
this.onMapLoad(data); this.onMapLoad(data).catch((e) => console.error(e));
} }
return; return;
} }
@ -319,7 +320,7 @@ export class GameScene extends DirtyScene {
}); });
this.load.scenePlugin("AnimatedTiles", AnimatedTiles, "animatedTiles", "animatedTiles"); this.load.scenePlugin("AnimatedTiles", AnimatedTiles, "animatedTiles", "animatedTiles");
this.load.on("filecomplete-tilemapJSON-" + this.MapUrlFile, (key: string, type: string, data: unknown) => { this.load.on("filecomplete-tilemapJSON-" + this.MapUrlFile, (key: string, type: string, data: unknown) => {
this.onMapLoad(data); this.onMapLoad(data).catch((e) => console.error(e));
}); });
//TODO strategy to add access token //TODO strategy to add access token
this.load.tilemapTiledJSON(this.MapUrlFile, this.MapUrlFile); this.load.tilemapTiledJSON(this.MapUrlFile, this.MapUrlFile);
@ -327,7 +328,7 @@ export class GameScene extends DirtyScene {
// In this case, we check in the cache to see if the map is here and trigger the event manually. // In this case, we check in the cache to see if the map is here and trigger the event manually.
if (this.cache.tilemap.exists(this.MapUrlFile)) { if (this.cache.tilemap.exists(this.MapUrlFile)) {
const data = this.cache.tilemap.get(this.MapUrlFile); const data = this.cache.tilemap.get(this.MapUrlFile);
this.onMapLoad(data); this.onMapLoad(data).catch((e) => console.error(e));
} }
//eslint-disable-next-line @typescript-eslint/no-explicit-any //eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -405,21 +406,23 @@ export class GameScene extends DirtyScene {
this.load.on("complete", () => { this.load.on("complete", () => {
// FIXME: the factory might fail because the resources might not be loaded yet... // FIXME: the factory might fail because the resources might not be loaded yet...
// We would need to add a loader ended event in addition to the createPromise // We would need to add a loader ended event in addition to the createPromise
this.createPromise.then(async () => { this.createPromise
itemFactory.create(this); .then(async () => {
itemFactory.create(this);
const roomJoinedAnswer = await this.connectionAnswerPromise; const roomJoinedAnswer = await this.connectionAnswerPromise;
for (const object of objectsOfType) { for (const object of objectsOfType) {
// TODO: we should pass here a factory to create sprites (maybe?) // TODO: we should pass here a factory to create sprites (maybe?)
// Do we have a state for this object? // Do we have a state for this object?
const state = roomJoinedAnswer.items[object.id]; const state = roomJoinedAnswer.items[object.id];
const actionableItem = itemFactory.factory(this, object, state); const actionableItem = itemFactory.factory(this, object, state);
this.actionableItems.set(actionableItem.getId(), actionableItem); this.actionableItems.set(actionableItem.getId(), actionableItem);
} }
}); })
.catch((e) => console.error(e));
}); });
} }
} }
@ -448,10 +451,6 @@ export class GameScene extends DirtyScene {
this.pinchManager = new PinchManager(this); this.pinchManager = new PinchManager(this);
} }
this.messageSubscription = worldFullMessageStream.stream.subscribe((message) =>
this.showWorldFullError(message)
);
const playerName = gameManager.getPlayerName(); const playerName = gameManager.getPlayerName();
if (!playerName) { if (!playerName) {
throw "playerName is not set"; throw "playerName is not set";
@ -489,11 +488,11 @@ export class GameScene extends DirtyScene {
if (exitSceneUrl !== undefined) { if (exitSceneUrl !== undefined) {
this.loadNextGame( this.loadNextGame(
Room.getRoomPathFromExitSceneUrl(exitSceneUrl, window.location.toString(), this.MapUrlFile) Room.getRoomPathFromExitSceneUrl(exitSceneUrl, window.location.toString(), this.MapUrlFile)
); ).catch((e) => console.error(e));
} }
const exitUrl = this.getExitUrl(layer); const exitUrl = this.getExitUrl(layer);
if (exitUrl !== undefined) { if (exitUrl !== undefined) {
this.loadNextGameFromExitUrl(exitUrl); this.loadNextGameFromExitUrl(exitUrl).catch((e) => console.error(e));
} }
} }
if (layer.type === "objectgroup") { if (layer.type === "objectgroup") {
@ -523,7 +522,9 @@ export class GameScene extends DirtyScene {
object.height, object.height,
object.visible, object.visible,
allowApi ?? false, allowApi ?? false,
"" "",
"map",
1
); );
} }
} }
@ -531,7 +532,7 @@ export class GameScene extends DirtyScene {
} }
this.gameMap.exitUrls.forEach((exitUrl) => { this.gameMap.exitUrls.forEach((exitUrl) => {
this.loadNextGameFromExitUrl(exitUrl); this.loadNextGameFromExitUrl(exitUrl).catch((e) => console.error(e));
}); });
this.startPositionCalculator = new StartPositionCalculator( this.startPositionCalculator = new StartPositionCalculator(
@ -552,7 +553,10 @@ export class GameScene extends DirtyScene {
mediaManager.setUserInputManager(this.userInputManager); mediaManager.setUserInputManager(this.userInputManager);
if (localUserStore.getFullscreen()) { if (localUserStore.getFullscreen()) {
document.querySelector("body")?.requestFullscreen(); document
.querySelector("body")
?.requestFullscreen()
.catch((e) => console.error(e));
} }
//notify game manager can to create currentUser in map //notify game manager can to create currentUser in map
@ -617,8 +621,6 @@ export class GameScene extends DirtyScene {
this.connect(); this.connect();
} }
this.emoteManager = new EmoteManager(this);
let oldPeerNumber = 0; let oldPeerNumber = 0;
this.peerStoreUnsubscribe = peerStore.subscribe((peers) => { this.peerStoreUnsubscribe = peerStore.subscribe((peers) => {
const newPeerNumber = peers.size; const newPeerNumber = peers.size;
@ -660,9 +662,16 @@ export class GameScene extends DirtyScene {
} }
}); });
Promise.all([this.connectionAnswerPromise as Promise<unknown>, ...scriptPromises]).then(() => { Promise.all([this.connectionAnswerPromise as Promise<unknown>, ...scriptPromises])
this.scene.wake(); .then(() => {
}); this.scene.wake();
})
.catch((e) =>
console.error(
"Some scripts failed to load ot the connection failed to establish to WorkAdventure server",
e
)
);
} }
/** /**
@ -693,7 +702,7 @@ export class GameScene extends DirtyScene {
playersStore.connectToRoomConnection(this.connection); playersStore.connectToRoomConnection(this.connection);
userIsAdminStore.set(this.connection.hasTag("admin")); userIsAdminStore.set(this.connection.hasTag("admin"));
this.connection.onUserJoins((message: MessageUserJoined) => { this.connection.userJoinedMessageStream.subscribe((message) => {
const userMessage: AddPlayerInterface = { const userMessage: AddPlayerInterface = {
userId: message.userId, userId: message.userId,
characterLayers: message.characterLayers, characterLayers: message.characterLayers,
@ -707,31 +716,33 @@ export class GameScene extends DirtyScene {
this.addPlayer(userMessage); this.addPlayer(userMessage);
}); });
this.connection.onUserMoved((message: UserMovedMessage) => { this.connection.userMovedMessageStream.subscribe((message) => {
const position = message.getPosition(); const position = message.position;
if (position === undefined) { if (position === undefined) {
throw new Error("Position missing from UserMovedMessage"); throw new Error("Position missing from UserMovedMessage");
} }
const messageUserMoved: MessageUserMovedInterface = { const messageUserMoved: MessageUserMovedInterface = {
userId: message.getUserid(), userId: message.userId,
position: ProtobufClientUtils.toPointInterface(position), position: ProtobufClientUtils.toPointInterface(position),
}; };
this.updatePlayerPosition(messageUserMoved); this.updatePlayerPosition(messageUserMoved);
}); });
this.connection.onUserLeft((userId: number) => { this.connection.userLeftMessageStream.subscribe((message) => {
this.removePlayer(userId); this.removePlayer(message.userId);
}); });
this.connection.onGroupUpdatedOrCreated((groupPositionMessage: GroupCreatedUpdatedMessageInterface) => { this.connection.groupUpdateMessageStream.subscribe(
this.shareGroupPosition(groupPositionMessage); (groupPositionMessage: GroupCreatedUpdatedMessageInterface) => {
}); this.shareGroupPosition(groupPositionMessage);
}
);
this.connection.onGroupDeleted((groupId: number) => { this.connection.groupDeleteMessageStream.subscribe((message) => {
try { try {
this.deleteGroup(groupId); this.deleteGroup(message.groupId);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
@ -743,7 +754,7 @@ export class GameScene extends DirtyScene {
this.createSuccessorGameScene(true, true); this.createSuccessorGameScene(true, true);
}); });
this.connection.onActionableEvent((message) => { this.connection.itemEventMessageStream.subscribe((message) => {
const item = this.actionableItems.get(message.itemId); const item = this.actionableItems.get(message.itemId);
if (item === undefined) { if (item === undefined) {
console.warn( console.warn(
@ -756,18 +767,29 @@ export class GameScene extends DirtyScene {
item.fire(message.event, message.state, message.parameters); 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({ this.pendingEvents.enqueue({
type: "PlayerDetailsUpdated", 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 * Triggered when we receive the JWT token to connect to Jitsi
*/ */
this.connection.onStartJitsiRoom((jwt, room) => { this.connection.sendJitsiJwtMessageStream.subscribe((message) => {
this.startJitsi(room, jwt); this.startJitsi(message.jitsiRoom, message.jwt);
});
this.messageSubscription = this.connection.worldFullMessageStream.subscribe((message) => {
this.showWorldFullError(message);
}); });
// When connection is performed, let's connect SimplePeer // When connection is performed, let's connect SimplePeer
@ -842,12 +864,15 @@ export class GameScene extends DirtyScene {
}); });
}); });
this.emoteManager = new EmoteManager(this, this.connection);
// this.gameMap.onLeaveLayer((layers) => { // this.gameMap.onLeaveLayer((layers) => {
// layers.forEach((layer) => { // layers.forEach((layer) => {
// iframeListener.sendLeaveLayerEvent(layer.name); // iframeListener.sendLeaveLayerEvent(layer.name);
// }); // });
// }); // });
}); })
.catch((e) => console.error(e));
} }
//todo: into dedicated classes //todo: into dedicated classes
@ -900,7 +925,7 @@ export class GameScene extends DirtyScene {
if (newValue) { if (newValue) {
this.onMapExit( this.onMapExit(
Room.getRoomPathFromExitSceneUrl(newValue as string, window.location.toString(), this.MapUrlFile) Room.getRoomPathFromExitSceneUrl(newValue as string, window.location.toString(), this.MapUrlFile)
); ).catch((e) => console.error(e));
} else { } else {
setTimeout(() => { setTimeout(() => {
layoutManagerActionStore.removeAction("roomAccessDenied"); layoutManagerActionStore.removeAction("roomAccessDenied");
@ -909,7 +934,9 @@ export class GameScene extends DirtyScene {
}); });
this.gameMap.onPropertyChange(GameMapProperties.EXIT_URL, (newValue, oldValue) => { this.gameMap.onPropertyChange(GameMapProperties.EXIT_URL, (newValue, oldValue) => {
if (newValue) { if (newValue) {
this.onMapExit(Room.getRoomPathFromExitUrl(newValue as string, window.location.toString())); this.onMapExit(Room.getRoomPathFromExitUrl(newValue as string, window.location.toString())).catch((e) =>
console.error(e)
);
} else { } else {
setTimeout(() => { setTimeout(() => {
layoutManagerActionStore.removeAction("roomAccessDenied"); layoutManagerActionStore.removeAction("roomAccessDenied");
@ -1095,21 +1122,47 @@ ${escapedMessage}
this.iframeSubscriptionList.push( this.iframeSubscriptionList.push(
iframeListener.playSoundStream.subscribe((playSoundEvent) => { iframeListener.playSoundStream.subscribe((playSoundEvent) => {
const url = new URL(playSoundEvent.url, this.MapUrlFile); const url = new URL(playSoundEvent.url, this.MapUrlFile);
soundManager.playSound(this.load, this.sound, url.toString(), playSoundEvent.config); soundManager
.playSound(this.load, this.sound, url.toString(), playSoundEvent.config)
.catch((e) => console.error(e));
}) })
); );
this.iframeSubscriptionList.push( this.iframeSubscriptionList.push(
iframeListener.stopSoundStream.subscribe((stopSoundEvent) => { iframeListener.trackCameraUpdateStream.subscribe(() => {
const url = new URL(stopSoundEvent.url, this.MapUrlFile); if (!this.firstCameraUpdateSent) {
soundManager.stopSound(this.sound, url.toString()); 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);
}
}) })
); );
this.iframeSubscriptionList.push( this.iframeSubscriptionList.push(
iframeListener.loadSoundStream.subscribe((loadSoundEvent) => { iframeListener.loadSoundStream.subscribe((loadSoundEvent) => {
const url = new URL(loadSoundEvent.url, this.MapUrlFile); const url = new URL(loadSoundEvent.url, this.MapUrlFile);
soundManager.loadSound(this.load, this.sound, url.toString()); soundManager.loadSound(this.load, this.sound, url.toString()).catch((e) => console.error(e));
}) })
); );
@ -1120,11 +1173,15 @@ ${escapedMessage}
); );
this.iframeSubscriptionList.push( this.iframeSubscriptionList.push(
iframeListener.loadPageStream.subscribe((url: string) => { iframeListener.loadPageStream.subscribe((url: string) => {
this.loadNextGameFromExitUrl(url).then(() => { this.loadNextGameFromExitUrl(url)
this.events.once(EVENT_TYPE.POST_UPDATE, () => { .then(() => {
this.onMapExit(Room.getRoomPathFromExitUrl(url, window.location.toString())); this.events.once(EVENT_TYPE.POST_UPDATE, () => {
}); this.onMapExit(Room.getRoomPathFromExitUrl(url, window.location.toString())).catch((e) =>
}); console.error(e)
);
});
})
.catch((e) => console.error(e));
}) })
); );
let scriptedBubbleSprite: Sprite; let scriptedBubbleSprite: Sprite;
@ -1165,6 +1222,12 @@ ${escapedMessage}
}) })
); );
this.iframeSubscriptionList.push(
iframeListener.setPropertyStream.subscribe((setProperty) => {
this.setPropertyLayer(setProperty.layerName, setProperty.propertyName, setProperty.propertyValue);
})
);
iframeListener.registerAnswerer("openCoWebsite", async (openCoWebsite, source) => { iframeListener.registerAnswerer("openCoWebsite", async (openCoWebsite, source) => {
if (!source) { if (!source) {
throw new Error("Unknown query source"); throw new Error("Unknown query source");
@ -1235,6 +1298,7 @@ ${escapedMessage}
roomId: this.roomUrl, roomId: this.roomUrl,
tags: this.connection ? this.connection.getAllTags() : [], tags: this.connection ? this.connection.getAllTags() : [],
variables: this.sharedVariablesManager.variables, variables: this.sharedVariablesManager.variables,
playerVariables: localUserStore.getAllUserProperties(),
userRoomToken: this.connection ? this.connection.userRoomToken : "", userRoomToken: this.connection ? this.connection.userRoomToken : "",
}; };
}); });
@ -1325,6 +1389,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) => { iframeListener.registerAnswerer("removeActionMessage", (message) => {
layoutManagerActionStore.removeAction(message.uuid); layoutManagerActionStore.removeAction(message.uuid);
}); });
@ -1343,6 +1423,13 @@ ${escapedMessage}
this.CurrentPlayer.removeOutlineColor(); this.CurrentPlayer.removeOutlineColor();
this.connection?.emitPlayerOutlineColor(null); this.connection?.emitPlayerOutlineColor(null);
}); });
iframeListener.registerAnswerer("getPlayerPosition", () => {
return {
x: this.CurrentPlayer.x,
y: this.CurrentPlayer.y,
};
});
} }
private setPropertyLayer( private setPropertyLayer(
@ -1351,7 +1438,7 @@ ${escapedMessage}
propertyValue: string | number | boolean | undefined propertyValue: string | number | boolean | undefined
): void { ): void {
if (propertyName === GameMapProperties.EXIT_URL && typeof propertyValue === "string") { if (propertyName === GameMapProperties.EXIT_URL && typeof propertyValue === "string") {
this.loadNextGameFromExitUrl(propertyValue); this.loadNextGameFromExitUrl(propertyValue).catch((e) => console.error(e));
} }
this.gameMap.setLayerProperty(layerName, propertyName, propertyValue); this.gameMap.setLayerProperty(layerName, propertyName, propertyValue);
} }
@ -1436,7 +1523,7 @@ ${escapedMessage}
public cleanupClosingScene(): void { public cleanupClosingScene(): void {
// stop playing audio, close any open website, stop any open Jitsi // stop playing audio, close any open website, stop any open Jitsi
coWebsiteManager.closeCoWebsites(); coWebsiteManager.closeCoWebsites().catch((e) => console.error(e));
// Stop the script, if any // Stop the script, if any
const scripts = this.getScriptUrls(this.mapFile); const scripts = this.getScriptUrls(this.mapFile);
for (const script of scripts) { for (const script of scripts) {
@ -1467,6 +1554,7 @@ ${escapedMessage}
iframeListener.unregisterAnswerer("openCoWebsite"); iframeListener.unregisterAnswerer("openCoWebsite");
iframeListener.unregisterAnswerer("getCoWebsites"); iframeListener.unregisterAnswerer("getCoWebsites");
iframeListener.unregisterAnswerer("setPlayerOutline"); iframeListener.unregisterAnswerer("setPlayerOutline");
iframeListener.unregisterAnswerer("setVariable");
this.sharedVariablesManager?.close(); this.sharedVariablesManager?.close();
this.embeddedWebsiteManager?.close(); this.embeddedWebsiteManager?.close();
@ -1945,6 +2033,7 @@ ${escapedMessage}
this.loader.resize(); this.loader.resize();
} }
private getObjectLayerData(objectName: string): ITiledMapObject | undefined { private getObjectLayerData(objectName: string): ITiledMapObject | undefined {
for (const layer of this.mapFile.layers) { for (const layer of this.mapFile.layers) {
if (layer.type === "objectgroup" && layer.name === "floorLayer") { if (layer.type === "objectgroup" && layer.name === "floorLayer") {
@ -1957,6 +2046,7 @@ ${escapedMessage}
} }
return undefined; return undefined;
} }
private reposition(): void { private reposition(): void {
// Recompute camera offset if needed // Recompute camera offset if needed
biggestAvailableAreaStore.recompute(); biggestAvailableAreaStore.recompute();
@ -1975,7 +2065,9 @@ ${escapedMessage}
const jitsiUrl = allProps.get(GameMapProperties.JITSI_URL) as string | undefined; const jitsiUrl = allProps.get(GameMapProperties.JITSI_URL) as string | undefined;
const jitsiWidth = allProps.get(GameMapProperties.JITSI_WIDTH) as number | undefined; const jitsiWidth = allProps.get(GameMapProperties.JITSI_WIDTH) as number | undefined;
jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl, jitsiWidth); jitsiFactory
.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl, jitsiWidth)
.catch((e) => console.error(e));
this.connection?.setSilent(true); this.connection?.setSilent(true);
mediaManager.hideGameOverlay(); mediaManager.hideGameOverlay();
analyticsClient.enteredJitsi(roomName, this.room.id); analyticsClient.enteredJitsi(roomName, this.room.id);

View File

@ -3,6 +3,7 @@ import { iframeListener } from "../../Api/IframeListener";
import type { GameMap } from "./GameMap"; import type { GameMap } from "./GameMap";
import type { ITiledMapLayer, ITiledMapObject } from "../Map/ITiledMap"; import type { ITiledMapLayer, ITiledMapObject } from "../Map/ITiledMap";
import { GameMapProperties } from "./GameMapProperties"; import { GameMapProperties } from "./GameMapProperties";
import type { SetVariableEvent } from "../../Api/Events/SetVariableEvent";
interface Variable { interface Variable {
defaultValue: unknown; defaultValue: unknown;
@ -41,58 +42,58 @@ export class SharedVariablesManager {
this._variables.set(name, value); this._variables.set(name, value);
} }
roomConnection.onSetVariable((name, value) => { roomConnection.variableMessageStream.subscribe(({ name, value }) => {
this._variables.set(name, value); this._variables.set(name, value);
// On server change, let's notify the iframes // On server change, let's notify the iframes
iframeListener.setVariable({ iframeListener.setVariable({
key: name, key: name,
value: value, value: value,
target: "global",
}); });
}); });
}
// When a variable is modified from an iFrame public setVariable(event: SetVariableEvent, source: MessageEventSource | null): void {
iframeListener.registerAnswerer("setVariable", (event, source) => { const key = event.key;
const key = event.key;
const object = this.variableObjects.get(key); const object = this.variableObjects.get(key);
if (object === undefined) { if (object === undefined) {
const errMsg = const errMsg =
'A script is trying to modify variable "' + 'A script is trying to modify variable "' +
key + key +
'" but this variable is not defined in the map.' + '" but this variable is not defined in the map.' +
'There should be an object in the map whose name is "' + 'There should be an object in the map whose name is "' +
key + key +
'" and whose type is "variable"'; '" and whose type is "variable"';
console.error(errMsg); console.error(errMsg);
throw new Error(errMsg); throw new Error(errMsg);
} }
if (object.writableBy && !this.roomConnection.hasTag(object.writableBy)) { if (object.writableBy && !this.roomConnection.hasTag(object.writableBy)) {
const errMsg = const errMsg =
'A script is trying to modify variable "' + 'A script is trying to modify variable "' +
key + key +
'" but this variable is only writable for users with tag "' + '" but this variable is only writable for users with tag "' +
object.writableBy + object.writableBy +
'".'; '".';
console.error(errMsg); console.error(errMsg);
throw new Error(errMsg); throw new Error(errMsg);
} }
// Let's stop any propagation of the value we set is the same as the existing value. // 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))) { if (JSON.stringify(event.value) === JSON.stringify(this._variables.get(key))) {
return; return;
} }
this._variables.set(key, event.value); this._variables.set(key, event.value);
// Dispatch to the room connection. // Dispatch to the room connection.
this.roomConnection.emitSetVariableEvent(key, event.value); this.roomConnection.emitSetVariableEvent(key, event.value);
// Dispatch to other iframes // Dispatch to other iframes
iframeListener.dispatchVariableToOtherIframes(key, event.value, source); iframeListener.dispatchVariableToOtherIframes(key, event.value, source);
});
} }
private static findVariablesInMap(gameMap: GameMap): Map<string, Variable> { private static findVariablesInMap(gameMap: GameMap): Map<string, Variable> {

View File

@ -40,19 +40,21 @@ export class CustomizeScene extends AbstractCharacterScene {
} }
preload() { preload() {
this.loadCustomSceneSelectCharacters().then((bodyResourceDescriptions) => { this.loadCustomSceneSelectCharacters()
bodyResourceDescriptions.forEach((bodyResourceDescription) => { .then((bodyResourceDescriptions) => {
if ( bodyResourceDescriptions.forEach((bodyResourceDescription) => {
bodyResourceDescription.level == undefined || if (
bodyResourceDescription.level < 0 || bodyResourceDescription.level == undefined ||
bodyResourceDescription.level > 5 bodyResourceDescription.level < 0 ||
) { bodyResourceDescription.level > 5
throw "Texture level is null"; ) {
} throw "Texture level is null";
this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription); }
}); this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription);
this.lazyloadingAttempt = true; });
}); this.lazyloadingAttempt = true;
})
.catch((e) => console.error(e));
this.layers = loadAllLayers(this.load); this.layers = loadAllLayers(this.load);
this.lazyloadingAttempt = false; this.lazyloadingAttempt = false;

View File

@ -41,12 +41,14 @@ export class SelectCharacterScene extends AbstractCharacterScene {
} }
preload() { preload() {
this.loadSelectSceneCharacters().then((bodyResourceDescriptions) => { this.loadSelectSceneCharacters()
bodyResourceDescriptions.forEach((bodyResourceDescription) => { .then((bodyResourceDescriptions) => {
this.playerModels.push(bodyResourceDescription); bodyResourceDescriptions.forEach((bodyResourceDescription) => {
}); this.playerModels.push(bodyResourceDescription);
this.lazyloadingAttempt = true; });
}); this.lazyloadingAttempt = true;
})
.catch((e) => console.error(e));
this.playerModels = loadAllDefaultModels(this.load); this.playerModels = loadAllDefaultModels(this.load);
this.lazyloadingAttempt = false; this.lazyloadingAttempt = false;

View File

@ -162,6 +162,7 @@ export interface ITiledTileSet {
imageheight: number; imageheight: number;
imagewidth: number; imagewidth: number;
columns: number;
margin: number; margin: number;
name: string; name: string;
properties?: ITiledMapProperty[]; properties?: ITiledMapProperty[];

View File

@ -100,10 +100,6 @@ export class Player extends Character {
return [xMovement, yMovement]; return [xMovement, yMovement];
} }
public enableFollowing() {
followStateStore.set("active");
}
public moveUser(delta: number): void { public moveUser(delta: number): void {
const activeEvents = this.userInputManager.getEventListForGameTick(); const activeEvents = this.userInputManager.getEventListForGameTick();
const state = get(followStateStore); const state = get(followStateStore);
@ -111,8 +107,7 @@ export class Player extends Character {
if (activeEvents.get(UserInputEvent.Follow)) { if (activeEvents.get(UserInputEvent.Follow)) {
if (state === "off" && this.scene.groups.size > 0) { if (state === "off" && this.scene.groups.size > 0) {
followStateStore.set("requesting"); this.sendFollowRequest();
followRoleStore.set("leader");
} else if (state === "active") { } else if (state === "active") {
followStateStore.set("ending"); followStateStore.set("ending");
} }
@ -125,4 +120,15 @@ export class Player extends Character {
} }
this.inputStep(activeEvents, x, y); 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, height: height * devicePixelRatio,
}); });
if (gameSize.width == 0) {
return;
}
this.actualZoom = realSize.width / gameSize.width / devicePixelRatio; this.actualZoom = realSize.width / gameSize.width / devicePixelRatio;
this.scaleManager.setZoom(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( export const followUsersColorStore = derived(
[followStateStore, followRoleStore, followUsersStore], [followStateStore, followRoleStore, followUsersStore],
([$followStateStore, $followRoleStore, $followUsersStore]) => { ([$followStateStore, $followRoleStore, $followUsersStore]) => {
console.log($followStateStore);
if ($followStateStore !== "active") { if ($followStateStore !== "active") {
return undefined; return undefined;
} }

View File

@ -5,3 +5,5 @@ export const userMovingStore = writable(false);
export const requestVisitCardsStore = writable<string | null>(null); export const requestVisitCardsStore = writable<string | null>(null);
export const userIsAdminStore = writable(false); export const userIsAdminStore = writable(false);
export const limitMapStore = writable(false);

View File

@ -360,32 +360,27 @@ const implementCorrectTrackBehavior = getNavigatorType() === NavigatorType.firef
/** /**
* Stops the camera from filming * Stops the camera from filming
*/ */
function applyCameraConstraints(currentStream: MediaStream | null, constraints: MediaTrackConstraints | boolean): void { async function applyCameraConstraints(
currentStream: MediaStream | null,
constraints: MediaTrackConstraints | boolean
): Promise<void[]> {
if (!currentStream) { if (!currentStream) {
return; return [];
}
for (const track of currentStream.getVideoTracks()) {
toggleConstraints(track, constraints).catch((e) =>
console.error("Error while setting new camera constraints:", e)
);
} }
return Promise.all(currentStream.getVideoTracks().map((track) => toggleConstraints(track, constraints)));
} }
/** /**
* Stops the microphone from listening * Stops the microphone from listening
*/ */
function applyMicrophoneConstraints( async function applyMicrophoneConstraints(
currentStream: MediaStream | null, currentStream: MediaStream | null,
constraints: MediaTrackConstraints | boolean constraints: MediaTrackConstraints | boolean
): void { ): Promise<void[]> {
if (!currentStream) { if (!currentStream) {
return; return [];
}
for (const track of currentStream.getAudioTracks()) {
toggleConstraints(track, constraints).catch((e) =>
console.error("Error while setting new audio constraints:", e)
);
} }
return Promise.all(currentStream.getAudioTracks().map((track) => toggleConstraints(track, constraints)));
} }
async function toggleConstraints(track: MediaStreamTrack, constraints: MediaTrackConstraints | boolean): Promise<void> { async function toggleConstraints(track: MediaStreamTrack, constraints: MediaTrackConstraints | boolean): Promise<void> {
@ -477,8 +472,8 @@ export const localStreamStore = derived<Readable<MediaStreamConstraints>, LocalS
} }
} }
applyMicrophoneConstraints(currentStream, constraints.audio || false); applyMicrophoneConstraints(currentStream, constraints.audio || false).catch((e) => console.error(e));
applyCameraConstraints(currentStream, constraints.video || false); applyCameraConstraints(currentStream, constraints.video || false).catch((e) => console.error(e));
if (implementCorrectTrackBehavior) { if (implementCorrectTrackBehavior) {
//on good navigators like firefox, we can instantiate the stream once and simply disable or enable the tracks as needed //on good navigators like firefox, we can instantiate the stream once and simply disable or enable the tracks as needed

View File

@ -0,0 +1,4 @@
import { writable } from "svelte/store";
export const showLimitRoomModalStore = writable(false);
export const showShareLinkMapModalStore = writable(false);

View File

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

View File

@ -156,7 +156,7 @@ export const screenSharingLocalStreamStore = derived<Readable<MediaStreamConstra
error: e instanceof Error ? e : new Error("An unknown error happened"), error: e instanceof Error ? e : new Error("An unknown error happened"),
}); });
} }
})(); })().catch((e) => console.error(e));
} }
); );

View File

@ -2,8 +2,7 @@ import type { Room } from "../Connexion/Room";
import { localUserStore } from "../Connexion/LocalUserStore"; import { localUserStore } from "../Connexion/LocalUserStore";
export enum GameConnexionTypes { export enum GameConnexionTypes {
anonymous = 1, room = 1,
organization,
register, register,
empty, empty,
unknown, unknown,
@ -19,10 +18,8 @@ class UrlManager {
return GameConnexionTypes.login; return GameConnexionTypes.login;
} else if (url === "/jwt") { } else if (url === "/jwt") {
return GameConnexionTypes.jwt; return GameConnexionTypes.jwt;
} else if (url.includes("_/")) { } else if (url.includes("_/") || url.includes("*/") || url.includes("@/")) {
return GameConnexionTypes.anonymous; return GameConnexionTypes.room;
} else if (url.includes("@/")) {
return GameConnexionTypes.organization;
} else if (url.includes("register/")) { } else if (url.includes("register/")) {
return GameConnexionTypes.register; return GameConnexionTypes.register;
} else if (url === "/") { } else if (url === "/") {
@ -41,7 +38,7 @@ class UrlManager {
if (window.location.pathname === room.id) return; if (window.location.pathname === room.id) return;
//Set last room visited! (connected or nor, must to be saved in localstorage and cache API) //Set last room visited! (connected or nor, must to be saved in localstorage and cache API)
//use href to keep # value //use href to keep # value
localUserStore.setLastRoomUrl(room.href); localUserStore.setLastRoomUrl(room.href).catch((e) => console.error(e));
const hash = window.location.hash; const hash = window.location.hash;
const search = room.search.toString(); const search = room.search.toString();
history.pushState({}, "WorkAdventure", room.id + (search ? "?" + search : "") + hash); history.pushState({}, "WorkAdventure", room.id + (search ? "?" + search : "") + hash);

View File

@ -149,7 +149,7 @@ class CoWebsiteManager {
} }
buttonCloseCoWebsites.blur(); buttonCloseCoWebsites.blur();
this.closeCoWebsites(); this.closeCoWebsites().catch((e) => console.error(e));
}); });
const buttonFullScreenFrame = HtmlUtils.getElementByIdOrFail(cowebsiteFullScreenButtonId); const buttonFullScreenFrame = HtmlUtils.getElementByIdOrFail(cowebsiteFullScreenButtonId);
@ -515,70 +515,72 @@ class CoWebsiteManager {
throw new Error("Too many we"); throw new Error("Too many we");
} }
Promise.resolve(callback(this.cowebsiteBufferDom)).then((iframe) => { Promise.resolve(callback(this.cowebsiteBufferDom))
iframe?.classList.add("pixel"); .then((iframe) => {
iframe?.classList.add("pixel");
if (!iframe.id) { if (!iframe.id) {
do { do {
iframe.id = "cowebsite-iframe-" + (Math.random() + 1).toString(36).substring(7); iframe.id = "cowebsite-iframe-" + (Math.random() + 1).toString(36).substring(7);
} while (this.getCoWebsiteById(iframe.id)); } while (this.getCoWebsiteById(iframe.id));
}
const onloadPromise = new Promise<void>((resolve) => {
iframe.onload = () => resolve();
});
const icon = this.generateCoWebsiteIcon(iframe);
const coWebsite = {
iframe,
icon,
position: position ?? this.coWebsites.length,
};
// Iframe management on mobile
icon.addEventListener("click", () => {
if (this.isSmallScreen()) {
this.moveRightPreviousCoWebsite(coWebsite, 0);
} }
});
this.coWebsites.push(coWebsite); const onloadPromise = new Promise<void>((resolve) => {
this.cowebsiteSubIconsDom.appendChild(icon); iframe.onload = () => resolve();
});
const onTimeoutPromise = new Promise<void>((resolve) => { const icon = this.generateCoWebsiteIcon(iframe);
setTimeout(() => resolve(), 2000);
});
this.currentOperationPromise = this.currentOperationPromise const coWebsite = {
.then(() => Promise.race([onloadPromise, onTimeoutPromise])) iframe,
.then(() => { icon,
if (coWebsite.position === 0) { position: position ?? this.coWebsites.length,
this.openMain(); };
if (widthPercent) {
this.widthPercent = widthPercent;
}
setTimeout(() => { // Iframe management on mobile
this.fire(); icon.addEventListener("click", () => {
if (this.isSmallScreen()) {
this.moveRightPreviousCoWebsite(coWebsite, 0);
}
});
this.coWebsites.push(coWebsite);
this.cowebsiteSubIconsDom.appendChild(icon);
const onTimeoutPromise = new Promise<void>((resolve) => {
setTimeout(() => resolve(), 2000);
});
this.currentOperationPromise = this.currentOperationPromise
.then(() => Promise.race([onloadPromise, onTimeoutPromise]))
.then(() => {
if (coWebsite.position === 0) {
this.openMain();
if (widthPercent) {
this.widthPercent = widthPercent;
}
setTimeout(() => {
this.fire();
position !== undefined
? this.moveRightPreviousCoWebsite(coWebsite, coWebsite.position)
: this.moveCoWebsite(coWebsite, coWebsite.position);
}, animationTime);
} else {
position !== undefined position !== undefined
? this.moveRightPreviousCoWebsite(coWebsite, coWebsite.position) ? this.moveRightPreviousCoWebsite(coWebsite, coWebsite.position)
: this.moveCoWebsite(coWebsite, coWebsite.position); : this.moveCoWebsite(coWebsite, coWebsite.position);
}, animationTime); }
} else {
position !== undefined
? this.moveRightPreviousCoWebsite(coWebsite, coWebsite.position)
: this.moveCoWebsite(coWebsite, coWebsite.position);
}
return resolve(coWebsite); return resolve(coWebsite);
}) })
.catch((err) => { .catch((err) => {
console.error("Error loadCoWebsite => ", err); console.error("Error loadCoWebsite => ", err);
this.removeCoWebsiteFromStack(coWebsite); this.removeCoWebsiteFromStack(coWebsite);
return reject(); return reject();
}); });
}); })
.catch((e) => console.error("Error loadCoWebsite >=> ", e));
}); });
} }
@ -603,17 +605,21 @@ class CoWebsiteManager {
return this.currentOperationPromise; return this.currentOperationPromise;
} }
public closeJitsi() { public async closeJitsi() {
const jitsi = this.searchJitsi(); const jitsi = this.searchJitsi();
if (jitsi) { if (jitsi) {
this.closeCoWebsite(jitsi); return this.closeCoWebsite(jitsi);
} }
} }
public closeCoWebsites(): Promise<void> { public closeCoWebsites(): Promise<void> {
this.currentOperationPromise = this.currentOperationPromise.then(() => { this.currentOperationPromise = this.currentOperationPromise.then(() => {
const promises: Promise<void>[] = [];
this.coWebsites.forEach((coWebsite: CoWebsite) => { this.coWebsites.forEach((coWebsite: CoWebsite) => {
this.closeCoWebsite(coWebsite); promises.push(this.closeCoWebsite(coWebsite));
});
return Promise.all(promises).then(() => {
return;
}); });
}); });
return this.currentOperationPromise; return this.currentOperationPromise;

View File

@ -1,5 +1,5 @@
import { JITSI_URL } from "../Enum/EnvironmentVariable"; import { JITSI_URL } from "../Enum/EnvironmentVariable";
import { coWebsiteManager } from "./CoWebsiteManager"; import { CoWebsite, coWebsiteManager } from "./CoWebsiteManager";
import { requestedCameraState, requestedMicrophoneState } from "../Stores/MediaStore"; import { requestedCameraState, requestedMicrophoneState } from "../Stores/MediaStore";
import { get } from "svelte/store"; import { get } from "svelte/store";
@ -140,8 +140,8 @@ class JitsiFactory {
interfaceConfig?: object, interfaceConfig?: object,
jitsiUrl?: string, jitsiUrl?: string,
jitsiWidth?: number jitsiWidth?: number
): void { ): Promise<CoWebsite> {
coWebsiteManager.addCoWebsite( return coWebsiteManager.addCoWebsite(
async (cowebsiteDiv) => { async (cowebsiteDiv) => {
// Jitsi meet external API maintains some data in local storage // Jitsi meet external API maintains some data in local storage
// which is sent via the appData URL parameter when joining a // which is sent via the appData URL parameter when joining a
@ -200,7 +200,7 @@ class JitsiFactory {
const jitsiCoWebsite = coWebsiteManager.searchJitsi(); const jitsiCoWebsite = coWebsiteManager.searchJitsi();
if (jitsiCoWebsite) { if (jitsiCoWebsite) {
coWebsiteManager.closeJitsi(); coWebsiteManager.closeJitsi().catch((e) => console.error(e));
} }
this.jitsiApi.removeListener("audioMuteStatusChanged", this.audioCallback); this.jitsiApi.removeListener("audioMuteStatusChanged", this.audioCallback);

View File

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

View File

@ -9,30 +9,34 @@ import {
} from "./Api/Events/IframeEvent"; } from "./Api/Events/IframeEvent";
import chat from "./Api/iframe/chat"; import chat from "./Api/iframe/chat";
import type { IframeCallback } from "./Api/iframe/IframeApiContribution"; import type { IframeCallback } from "./Api/iframe/IframeApiContribution";
import nav from "./Api/iframe/nav"; import nav, { CoWebsite } from "./Api/iframe/nav";
import controls from "./Api/iframe/controls"; import controls from "./Api/iframe/controls";
import ui from "./Api/iframe/ui"; import ui from "./Api/iframe/ui";
import sound from "./Api/iframe/sound"; import sound from "./Api/iframe/sound";
import room, { setMapURL, setRoomId } from "./Api/iframe/room"; 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 player, { setPlayerName, setTags, setUserRoomToken, setUuid } from "./Api/iframe/player";
import type { ButtonDescriptor } from "./Api/iframe/Ui/ButtonDescriptor"; import type { ButtonDescriptor } from "./Api/iframe/Ui/ButtonDescriptor";
import type { Popup } from "./Api/iframe/Ui/Popup"; import type { Popup } from "./Api/iframe/Ui/Popup";
import type { Sound } from "./Api/iframe/Sound/Sound"; import type { Sound } from "./Api/iframe/Sound/Sound";
import { answerPromises, queryWorkadventure } from "./Api/iframe/IframeApiContribution"; 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 // Notify WorkAdventure that we are ready to receive data
const initPromise = queryWorkadventure({ const initPromise = queryWorkadventure({
type: "getState", type: "getState",
data: undefined, data: undefined,
}).then((state) => { }).then((gameState) => {
setPlayerName(state.nickname); setPlayerName(gameState.nickname);
setRoomId(state.roomId); setRoomId(gameState.roomId);
setMapURL(state.mapUrl); setMapURL(gameState.mapUrl);
setTags(state.tags); setTags(gameState.tags);
setUuid(state.uuid); setUuid(gameState.uuid);
initVariables(state.variables as Map<string, unknown>); globalState.initVariables(gameState.variables as Map<string, unknown>);
setUserRoomToken(state.userRoomToken); player.state.initVariables(gameState.playerVariables as Map<string, unknown>);
setUserRoomToken(gameState.userRoomToken);
}); });
const wa = { const wa = {
@ -43,7 +47,8 @@ const wa = {
sound, sound,
room, room,
player, player,
state, camera,
state: globalState,
onInit(): Promise<void> { onInit(): Promise<void> {
return initPromise; return initPromise;
@ -131,17 +136,17 @@ const wa = {
/** /**
* @deprecated Use WA.nav.openCoWebSite instead * @deprecated Use WA.nav.openCoWebSite instead
*/ */
openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = ""): void { openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = ""): Promise<CoWebsite> {
console.warn("Method WA.openCoWebSite is deprecated. Please use WA.nav.openCoWebSite instead"); console.warn("Method WA.openCoWebSite is deprecated. Please use WA.nav.openCoWebSite instead");
nav.openCoWebSite(url, allowApi, allowPolicy); return nav.openCoWebSite(url, allowApi, allowPolicy);
}, },
/** /**
* @deprecated Use WA.nav.closeCoWebSite instead * @deprecated Use WA.nav.closeCoWebSite instead
*/ */
closeCoWebSite(): void { closeCoWebSite(): Promise<void> {
console.warn("Method WA.closeCoWebSite is deprecated. Please use WA.nav.closeCoWebSite instead"); console.warn("Method WA.closeCoWebSite is deprecated. Please use WA.nav.closeCoWebSite instead");
nav.closeCoWebSite(); return nav.closeCoWebSite();
}, },
/** /**
@ -225,7 +230,5 @@ window.addEventListener(
callback?.callback(payloadData); callback?.callback(payloadData);
} }
} }
// ...
} }
); );

View File

@ -1066,6 +1066,7 @@ div.action.danger p.action-body{
width: 100%; width: 100%;
height: 100%; height: 100%;
pointer-events: none; pointer-events: none;
user-select: none;
& > div { & > div {
position: relative; position: relative;

View File

@ -1,22 +1,24 @@
import "jasmine"; import "jasmine";
import {PlayerMovement} from "../../../src/Phaser/Game/PlayerMovement"; import { PlayerMovement } from "../../../src/Phaser/Game/PlayerMovement";
describe("Interpolation / Extrapolation", () => { describe("Interpolation / Extrapolation", () => {
it("should interpolate", () => { it("should interpolate", () => {
const playerMovement = new PlayerMovement({ const playerMovement = new PlayerMovement(
x: 100, y: 200 {
}, 42000, x: 100,
y: 200,
},
42000,
{ {
x: 200, x: 200,
y: 100, y: 100,
oldX: 100, oldX: 100,
oldY: 200, oldY: 200,
moving: true, moving: true,
direction: "up" direction: "up",
}, },
42200 42200
); );
expect(playerMovement.isOutdated(42100)).toBe(false); expect(playerMovement.isOutdated(42100)).toBe(false);
expect(playerMovement.isOutdated(43000)).toBe(true); expect(playerMovement.isOutdated(43000)).toBe(true);
@ -26,8 +28,8 @@ describe("Interpolation / Extrapolation", () => {
y: 150, y: 150,
oldX: 100, oldX: 100,
oldY: 200, oldY: 200,
direction: 'up', direction: "up",
moving: true moving: true,
}); });
expect(playerMovement.getPosition(42200)).toEqual({ expect(playerMovement.getPosition(42200)).toEqual({
@ -35,8 +37,8 @@ describe("Interpolation / Extrapolation", () => {
y: 100, y: 100,
oldX: 100, oldX: 100,
oldY: 200, oldY: 200,
direction: 'up', direction: "up",
moving: true moving: true,
}); });
expect(playerMovement.getPosition(42300)).toEqual({ expect(playerMovement.getPosition(42300)).toEqual({
@ -44,22 +46,25 @@ describe("Interpolation / Extrapolation", () => {
y: 50, y: 50,
oldX: 100, oldX: 100,
oldY: 200, oldY: 200,
direction: 'up', direction: "up",
moving: true moving: true,
}); });
}); });
it("should not extrapolate if we stop", () => { it("should not extrapolate if we stop", () => {
const playerMovement = new PlayerMovement({ const playerMovement = new PlayerMovement(
x: 100, y: 200 {
}, 42000, x: 100,
y: 200,
},
42000,
{ {
x: 200, x: 200,
y: 100, y: 100,
oldX: 100, oldX: 100,
oldY: 200, oldY: 200,
moving: false, moving: false,
direction: "up" direction: "up",
}, },
42200 42200
); );
@ -69,22 +74,25 @@ describe("Interpolation / Extrapolation", () => {
y: 100, y: 100,
oldX: 100, oldX: 100,
oldY: 200, oldY: 200,
direction: 'up', direction: "up",
moving: false moving: false,
}); });
}); });
it("should keep moving until it stops", () => { it("should keep moving until it stops", () => {
const playerMovement = new PlayerMovement({ const playerMovement = new PlayerMovement(
x: 100, y: 200 {
}, 42000, x: 100,
y: 200,
},
42000,
{ {
x: 200, x: 200,
y: 100, y: 100,
oldX: 100, oldX: 100,
oldY: 200, oldY: 200,
moving: false, moving: false,
direction: "up" direction: "up",
}, },
42200 42200
); );
@ -94,8 +102,8 @@ describe("Interpolation / Extrapolation", () => {
y: 150, y: 150,
oldX: 100, oldX: 100,
oldY: 200, oldY: 200,
direction: 'up', direction: "up",
moving: false moving: false,
}); });
}); });
}) });

View File

@ -150,6 +150,59 @@
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.2.tgz#adea7b6953cbb34651766b0548468e743c6a2353" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.2.tgz#adea7b6953cbb34651766b0548468e743c6a2353"
integrity sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q== 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": "@sentry/types@^6.11.0":
version "6.12.0" version "6.12.0"
resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.12.0.tgz#b7395688a79403c6df8d8bb8d81deb8222519853" 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" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= 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": "@types/mime@^1":
version "1.3.2" version "1.3.2"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" 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" resolved "https://registry.yarnpkg.com/@types/node/-/node-15.3.0.tgz#d6fed7d6bc6854306da3dea1af9f874b00783e26"
integrity sha512-8/bnjSZD86ZfpBsDlCIkNXIvm+h6wi9g7IqL+kmFkQ+Wvu3JrasgLElfiPgoo8V8vVfnEi0QVS12gbl94h9YsQ== 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": "@types/parse-json@^4.0.0":
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== 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": "@types/pug@^2.0.4":
version "2.0.4" version "2.0.4"
resolved "https://registry.yarnpkg.com/@types/pug/-/pug-2.0.4.tgz#8772fcd0418e3cd2cc171555d73007415051f4b2" 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" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== 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: debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
version "2.6.9" version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@ -3662,7 +3740,7 @@ lodash.merge@^4.6.2:
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== 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" version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -3695,6 +3773,11 @@ lokijs@^1.5.12:
resolved "https://registry.yarnpkg.com/lokijs/-/lokijs-1.5.12.tgz#cb55b37009bdf09ee7952a6adddd555b893653a0" resolved "https://registry.yarnpkg.com/lokijs/-/lokijs-1.5.12.tgz#cb55b37009bdf09ee7952a6adddd555b893653a0"
integrity sha512-Q5ALD6JiS6xAUWCwX3taQmgwxyveCtIIuL08+ml0nHwT3k0S/GIFJN+Hd38b1qYIMaE5X++iqsqWVksz7SYW+Q== 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: lower-case@^2.0.2:
version "2.0.2" version "2.0.2"
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28"
@ -4098,6 +4181,11 @@ object-copy@^0.1.0:
define-property "^0.2.5" define-property "^0.2.5"
kind-of "^3.0.3" 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: object-inspect@^1.9.0:
version "1.10.3" version "1.10.3"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369"
@ -4590,6 +4678,11 @@ prettier-plugin-svelte@^2.5.0:
resolved "https://registry.yarnpkg.com/prettier-plugin-svelte/-/prettier-plugin-svelte-2.5.0.tgz#7922534729f7febe59b4c56c3f5360539f0d8ab1" resolved "https://registry.yarnpkg.com/prettier-plugin-svelte/-/prettier-plugin-svelte-2.5.0.tgz#7922534729f7febe59b4c56c3f5360539f0d8ab1"
integrity sha512-+iHY2uGChOngrgKielJUnqo74gIL/EO5oeWm8MftFWjEi213lq9QYTOwm1pv4lI1nA61tdgf80CF2i5zMcu1kw== 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: prettier@^2.3.1:
version "2.3.1" version "2.3.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.1.tgz#76903c3f8c4449bc9ac597acefa24dc5ad4cbea6" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.1.tgz#76903c3f8c4449bc9ac597acefa24dc5ad4cbea6"
@ -4618,6 +4711,25 @@ progress@^2.0.0:
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== 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: proxy-addr@~2.0.5:
version "2.0.6" version "2.0.6"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
@ -5819,6 +5931,35 @@ ts-node@^10.4.0:
make-error "^1.1.1" make-error "^1.1.1"
yn "3.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: tsconfig-paths@^3.9.0:
version "3.9.0" version "3.9.0"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" 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 heightField = document.getElementById('height');
const urlField = document.getElementById('url'); const urlField = document.getElementById('url');
const visibleField = document.getElementById('visible'); const visibleField = document.getElementById('visible');
const originField = document.getElementById('origin');
const scaleField = document.getElementById('scale');
createButton.addEventListener('click', () => { createButton.addEventListener('click', () => {
console.log('CREATING NEW EMBEDDED IFRAME'); console.log('CREATING NEW EMBEDDED IFRAME');
@ -28,6 +30,8 @@
height: parseInt(heightField.value), height: parseInt(heightField.value),
}, },
visible: !!visibleField.value, visible: !!visibleField.value,
origin: originField.value,
scale: parseFloat(scaleField.value),
}); });
}); });
@ -61,6 +65,16 @@
const website = await WA.room.website.get('test'); const website = await WA.room.website.get('test');
website.visible = this.checked; 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> </script>
@ -72,6 +86,8 @@ width: <input type="text" id="width" value="600" /><br/>
height: <input type="text" id="height" value="400" /><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/> 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/> 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> <button id="createEmbeddedWebsite">Create embedded website</button>

View File

@ -20,6 +20,10 @@ export const isMapDetailsData = new tg.IsInterface()
}) })
.withOptionalProperties({ .withOptionalProperties({
iframeAuthentication: tg.isNullable(tg.isString), iframeAuthentication: tg.isNullable(tg.isString),
// The date (in ISO 8601 format) at which the room will expire
expireOn: tg.isString,
// Whether the "report" feature is enabled or not on this room
canReport: tg.isBoolean,
}) })
.get(); .get();

View File

@ -4,13 +4,14 @@
"description": "", "description": "",
"scripts": { "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", "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-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", "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-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/", "json-copy-to-front": "rm -rf ../front/src/Messages/JsonMessages/* && cp -rf JsonMessages/* ../front/src/Messages/JsonMessages/",
"precommit": "lint-staged", "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", "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": "yarn prettier --write 'JsonMessages/**/*.ts'",
"pretty-check": "yarn prettier --check 'JsonMessages/**/*.ts'" "pretty-check": "yarn prettier --check 'JsonMessages/**/*.ts'"
@ -18,7 +19,8 @@
"dependencies": { "dependencies": {
"generic-type-guard": "^3.5.0", "generic-type-guard": "^3.5.0",
"google-protobuf": "^3.13.0", "google-protobuf": "^3.13.0",
"grpc": "^1.24.4" "grpc": "^1.24.4",
"ts-proto": "^1.96.0"
}, },
"devDependencies": { "devDependencies": {
"@types/google-protobuf": "^3.7.4", "@types/google-protobuf": "^3.7.4",

View File

@ -296,8 +296,6 @@ message ServerToClientMessage {
WebRtcSignalToClientMessage webRtcSignalToClientMessage = 5; WebRtcSignalToClientMessage webRtcSignalToClientMessage = 5;
WebRtcSignalToClientMessage webRtcScreenSharingSignalToClientMessage = 6; WebRtcSignalToClientMessage webRtcScreenSharingSignalToClientMessage = 6;
WebRtcDisconnectMessage webRtcDisconnectMessage = 7; WebRtcDisconnectMessage webRtcDisconnectMessage = 7;
PlayGlobalMessage playGlobalMessage = 8;
StopGlobalMessage stopGlobalMessage = 9;
TeleportMessageMessage teleportMessageMessage = 10; TeleportMessageMessage teleportMessageMessage = 10;
SendJitsiJwtMessage sendJitsiJwtMessage = 11; SendJitsiJwtMessage sendJitsiJwtMessage = 11;
SendUserMessage sendUserMessage = 12; SendUserMessage sendUserMessage = 12;
@ -390,8 +388,6 @@ message PusherToBackMessage {
SetPlayerDetailsMessage setPlayerDetailsMessage = 5; SetPlayerDetailsMessage setPlayerDetailsMessage = 5;
WebRtcSignalToServerMessage webRtcSignalToServerMessage = 6; WebRtcSignalToServerMessage webRtcSignalToServerMessage = 6;
WebRtcSignalToServerMessage webRtcScreenSharingSignalToServerMessage = 7; WebRtcSignalToServerMessage webRtcScreenSharingSignalToServerMessage = 7;
PlayGlobalMessage playGlobalMessage = 8;
StopGlobalMessage stopGlobalMessage = 9;
ReportPlayerMessage reportPlayerMessage = 10; ReportPlayerMessage reportPlayerMessage = 10;
QueryJitsiJwtMessage queryJitsiJwtMessage = 11; QueryJitsiJwtMessage queryJitsiJwtMessage = 11;
SendUserMessage sendUserMessage = 12; 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" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.7.tgz#8ea1e8f8eae2430cf440564b98c6dfce1ec5945d"
integrity sha512-Zw1vhUSQZYw+7u5dAwNbIA9TuTotpzY/OF7sJM9FqPOF3SPjKnxrjoTktXDZgUjybf4cWVBP7O8wvKdSaGHweg== 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": "@types/node@^12.12.29":
version "12.19.4" version "12.19.4"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.19.4.tgz#cdfbb62e26c7435ed9aab9c941393cc3598e9b46" 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" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.30.tgz#1ed6e01e4ca576d5aec9cc802cc3bcf94c274192"
integrity sha512-HmqFpNzp3TSELxU/bUuRK+xzarVOAsR00hzcvM0TXrMlt/+wcSLa5q6YhTb6/cA6wqDCZLDcfd8fSL95x5h7AA== 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": "@types/parse-json@^4.0.0":
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" 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" resolved "https://registry.yarnpkg.com/@types/parsimmon/-/parsimmon-1.10.4.tgz#7639e16015440d9baf622f83c12dae47787226b7"
integrity sha512-M56NfQHfaWuaj6daSgCVs7jh8fXLI3LmxjRoQxmOvYesgIkI+9HPsDLO0vd7wX7cwA0D0ZWFEJdp0VPwLdS+bQ== 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": "@typescript-eslint/eslint-plugin@^4.7.0":
version "4.7.0" version "4.7.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.7.0.tgz#85c9bbda00c0cb604d3c241f7bc7fb171a2d3479" 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: dependencies:
assert-plus "^1.0.0" 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: date.js@^0.3.1:
version "0.3.3" version "0.3.3"
resolved "https://registry.yarnpkg.com/date.js/-/date.js-0.3.3.tgz#ef1e92332f507a638795dbb985e951882e50bbda" 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" define-property "^0.2.5"
kind-of "^3.0.3" 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: object-inspect@^1.8.0:
version "1.8.0" version "1.8.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" 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" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
prettier@^2.3.1: prettier@^2.0.2, prettier@^2.3.1:
version "2.5.1" version "2.5.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a"
integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg== integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==
@ -3467,6 +3492,25 @@ protobufjs@^6.10.1:
"@types/node" "^13.7.0" "@types/node" "^13.7.0"
long "^4.0.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: psl@^1.1.28:
version "1.8.0" version "1.8.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" 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" psl "^1.1.28"
punycode "^2.1.1" 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: tsconfig-paths@^3.9.0:
version "3.9.0" version "3.9.0"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b"