Merge remote-tracking branch 'remotes/workadventure-main/develop' into gamestate-api-read

# Conflicts:
#	front/src/Api/IframeListener.ts
This commit is contained in:
jonny 2021-05-10 21:48:11 +02:00
commit 8cef4f6e90
17 changed files with 9120 additions and 362 deletions

View File

@ -1,6 +1,8 @@
name: Build, push and deploy Docker image name: Build, push and deploy Docker image
on: on:
push:
branch: [master]
release: release:
types: [created] types: [created]
pull_request: pull_request:
@ -14,7 +16,7 @@ env:
jobs: jobs:
build-front: build-front:
if: ${{ github.event.release || contains(github.event.pull_request.labels.*.name, 'deploy') }} if: ${{ github.event.release || github.event.push || contains(github.event.pull_request.labels.*.name, 'deploy') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -34,11 +36,11 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
repository: thecodingmachine/workadventure-front repository: thecodingmachine/workadventure-front
tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || github.event.release && env.GITHUB_REF_SLUG || 'master' }}
add_git_labels: true add_git_labels: true
build-back: build-back:
if: ${{ github.event.release || contains(github.event.pull_request.labels.*.name, 'deploy') }} if: ${{ github.event.release || github.event.push || contains(github.event.pull_request.labels.*.name, 'deploy') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -57,11 +59,11 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
repository: thecodingmachine/workadventure-back repository: thecodingmachine/workadventure-back
tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || github.event.release && env.GITHUB_REF_SLUG || 'master' }}
add_git_labels: true add_git_labels: true
build-pusher: build-pusher:
if: ${{ github.event.release || contains(github.event.pull_request.labels.*.name, 'deploy') }} if: ${{ github.event.release || github.event.push || contains(github.event.pull_request.labels.*.name, 'deploy') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -80,11 +82,11 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
repository: thecodingmachine/workadventure-pusher repository: thecodingmachine/workadventure-pusher
tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || github.event.release && env.GITHUB_REF_SLUG || 'master' }}
add_git_labels: true add_git_labels: true
build-uploader: build-uploader:
if: ${{ github.event.release || contains(github.event.pull_request.labels.*.name, 'deploy') }} if: ${{ github.event.release || github.event.push || contains(github.event.pull_request.labels.*.name, 'deploy') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -103,11 +105,11 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
repository: thecodingmachine/workadventure-uploader repository: thecodingmachine/workadventure-uploader
tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || github.event.release && env.GITHUB_REF_SLUG || 'master' }}
add_git_labels: true add_git_labels: true
build-maps: build-maps:
if: ${{ github.event.release || contains(github.event.pull_request.labels.*.name, 'deploy') }} if: ${{ github.event.release || github.event.push || contains(github.event.pull_request.labels.*.name, 'deploy') }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -127,7 +129,7 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
repository: thecodingmachine/workadventure-maps repository: thecodingmachine/workadventure-maps
tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }} tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || github.event.release && env.GITHUB_REF_SLUG || 'master' }}
add_git_labels: true add_git_labels: true
deeploy: deeploy:
@ -138,7 +140,7 @@ jobs:
- build-maps - build-maps
- build-uploader - build-uploader
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ contains(github.event.pull_request.labels.*.name, 'deploy') }} if: ${{ github.event.push || contains(github.event.pull_request.labels.*.name, 'deploy') }}
steps: steps:
- name: Checkout - name: Checkout
@ -156,14 +158,14 @@ jobs:
JITSI_URL: ${{ secrets.JITSI_URL }} JITSI_URL: ${{ secrets.JITSI_URL }}
SECRET_JITSI_KEY: ${{ secrets.SECRET_JITSI_KEY }} SECRET_JITSI_KEY: ${{ secrets.SECRET_JITSI_KEY }}
TURN_STATIC_AUTH_SECRET: ${{ secrets.TURN_STATIC_AUTH_SECRET }} TURN_STATIC_AUTH_SECRET: ${{ secrets.TURN_STATIC_AUTH_SECRET }}
DEPLOY_REF: ${{ env.GITHUB_HEAD_REF_SLUG }} DEPLOY_REF: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || 'master' }}
with: with:
namespace: workadventure-${{ env.GITHUB_HEAD_REF_SLUG }} namespace: workadventure-${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || 'master' }}
- name: Add a comment in PR - name: Add a comment in PR
uses: unsplash/comment-on-pr@v1.2.0 uses: unsplash/comment-on-pr@v1.2.0
if: ${{ github.event.pull_request }}
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
msg: Environment deployed at https://play.${{ env.GITHUB_HEAD_REF_SLUG }}.test.workadventu.re msg: Environment deployed at https://play.${{ env.GITHUB_HEAD_REF_SLUG }}.test.workadventu.re
check_for_duplicate_msg: true

View File

@ -1,5 +1,11 @@
## Version 1.3.9 - in dev ## Version 1.3.9 - in dev
### BREAKING CHANGES
- Scripting API:
- Changed function names: `restorePlayerControl` => `restorePlayerControls`, `disablePlayerControl` => `disablePlayerControls`.
Please keep in mind that the scripting API is still experimental. Some breaking changes can occur in it until we mark it as stable.
### Updates ### Updates
- Mobile support has been improved - Mobile support has been improved
@ -8,6 +14,7 @@
- Pinch support on mobile to zoom in / out - Pinch support on mobile to zoom in / out
- Improved virtual joystick size (adapts to the zoom level) - Improved virtual joystick size (adapts to the zoom level)
### Bug Fixes ### Bug Fixes
- Pinch gesture does no longer move the character - Pinch gesture does no longer move the character

View File

@ -1704,9 +1704,9 @@ lodash.once@^4.0.0:
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19: lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19:
version "4.17.20" version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
long@~3: long@~3:
version "3.2.0" version "3.2.0"

237
docs/maps/api-reference.md Normal file
View File

@ -0,0 +1,237 @@
{.section-title.accent.text-primary}
# API Reference
### Sending a message in the chat
```
sendChatMessage(message: string, author: string): void
```
Sends a message in the chat. The message is only visible in the browser of the current user.
* **message**: the message to be displayed in the chat
* **author**: the name displayed for the author of the message. It does not have to be a real user.
Example:
```javascript
WA.sendChatMessage('Hello world', 'Mr Robot');
```
### Listening to messages from the chat
```javascript
onChatMessage(callback: (message: string) => void): void
```
Listens to messages typed by the current user and calls the callback. Messages from other users in the chat cannot be listened to.
* **callback**: the function that will be called when a message is received. It contains the message typed by the user.
Example:
```javascript
WA.onChatMessage((message => {
console.log('The user typed a message', message);
}));
```
### Detecting when the user enters/leaves a zone
```
onEnterZone(name: string, callback: () => void): void
onLeaveZone(name: string, callback: () => void): void
```
Listens to the position of the current user. The event is triggered when the user enters or leaves a given zone. The name of the zone is stored in the map, on a dedicated layer with the `zone` property.
<div>
<figure class="figure">
<img src="https://workadventu.re/img/docs/trigger_event.png" class="figure-img img-fluid rounded" alt="" />
<figcaption class="figure-caption">The `zone` property, applied on a layer</figcaption>
</figure>
</div>
* **name**: the name of the zone, as defined in the `zone` property.
* **callback**: the function that will be called when a user enters or leaves the zone.
Example:
```javascript
WA.onEnterZone('myZone', () => {
WA.sendChatMessage("Hello!", 'Mr Robot');
})
WA.onLeaveZone('myZone', () => {
WA.sendChatMessage("Goodbye!", 'Mr Robot');
})
```
### Opening a popup
In order to open a popup window, you must first define the position of the popup on your map.
You can position this popup by using a "rectangle" object in Tiled that you will place on an "object" layer.
<div class="row">
<div class="col">
<img src="https://workadventu.re/img/docs/screen_popup_tiled.png" class="figure-img img-fluid rounded" alt="" />
</div>
<div class="col">
<img src="https://workadventu.re/img/docs/screen_popup_in_game.png" class="figure-img img-fluid rounded" alt="" />
</div>
</div>
```
openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup
```
* **targetObject**: the name of the rectangle object defined in Tiled.
* **message**: the message to display in the popup.
* **buttons**: an array of action buttons defined underneath the popup.
Action buttons are `ButtonDescriptor` objects containing these properties.
* **label (_string_)**: The label of the button.
* **className (_string_)**: The visual type of the button. Can be one of "normal", "primary", "success", "warning", "error", "disabled".
* **callback (_(popup: Popup)=>void_)**: Callback called when the button is pressed.
Please note that `openPopup` returns an object of the `Popup` class. Also, the callback called when a button is clicked is passed a `Popup` object.
The `Popup` class that represents an open popup contains a single method: `close()`. This will obviously close the popup when called.
```javascript
class Popup {
/**
* Closes the popup
*/
close() {};
}
```
Example:
```javascript
let helloWorldPopup;
// Open the popup when we enter a given zone
helloWorldPopup = WA.onEnterZone('myZone', () => {
WA.openPopup("popupRectangle", 'Hello world!', [{
label: "Close",
className: "primary",
callback: (popup) => {
// Close the popup when the "Close" button is pressed.
popup.close();
}
});
}]);
// Close the popup when we leave the zone.
WA.onLeaveZone('myZone', () => {
helloWorldPopup.close();
});
```
### Disabling / restoring controls
```
disablePlayerControls(): void
restorePlayerControls(): void
```
These 2 methods can be used to completely disable player controls and to enable them again.
When controls are disabled, the user cannot move anymore using keyboard input. This can be useful in a "First Time User Experience" part, to display an important message to a user before letting him/her move again.
Example:
```javascript
WA.onEnterZone('myZone', () => {
WA.disablePlayerControls();
WA.openPopup("popupRectangle", 'This is an imporant message!', [{
label: "Got it!",
className: "primary",
callback: (popup) => {
WA.restorePlayerControls();
popup.close();
}
}]);
});
```
### Opening a web page in a new tab
```
openTab(url: string): void
```
Opens the webpage at "url" in your browser, in a new tab.
Example:
```javascript
WA.openTab('https://www.wikipedia.org/');
```
### Opening a web page in the current tab
```
goToPage(url: string): void
```
Opens the webpage at "url" in your browser in place of WorkAdventure. WorkAdventure will be completely unloaded.
Example:
```javascript
WA.goToPage('https://www.wikipedia.org/');
```
### Opening/closing a web page in an iFrame
```
openCoWebSite(url: string): void
closeCoWebSite(): void
```
Opens the webpage at "url" in an iFrame (on the right side of the screen) or close that iFrame.
Example:
```javascript
WA.openCoWebSite('https://www.wikipedia.org/');
// ...
WA.closeCoWebSite();
```
### Load a sound from an url
```
loadSound(url: string): Sound
```
Load a sound from an url
Please note that `loadSound` returns an object of the `Sound` class
The `Sound` class that represents a loaded sound contains two methods: `play(soundConfig : SoundConfig|undefined)` and `stop()`
The parameter soundConfig is optional, if you call play without a Sound config the sound will be played with the basic configuration.
Example:
```javascript
var mySound = WA.loadSound("Sound.ogg");
var config = {
volume : 0.5,
loop : false,
rate : 1,
detune : 1,
delay : 0,
seek : 0,
mute : false
}
mySound.play(config);
// ...
mySound.stop();
```

117
docs/maps/scripting.md Normal file
View File

@ -0,0 +1,117 @@
{.alert.alert-danger style="width:80%"}
This feature is "_experimental_". We may apply changes in the near future to the way it works when we gather some feedback.
{.section-title.accent.text-primary}
# Scripting WorkAdventure maps
Do you want to add a bit of intelligence to your map? Scripts allow you to create maps with special features.
You can for instance:
* Create FTUE (First Time User Experience) scenarios where a first-time user will be displayed a notification popup.
* Create NPC (non playing characters) and interact with those characters using the chat.
* Organize interactions between an iframe and your map (for instance, walking on a special zone might add a product in the cart of an eCommerce website...)
* etc...
Please note that scripting in WorkAdventure is at an early stage of development and that more features might be added in the future. You can actually voice your opinion about useful features by adding [an issue on Github](https://github.com/thecodingmachine/workadventure/issues).
{.alert.alert-warning}
**Beware:** Scripts are executed in the browser of the current user only. Generally speaking, scripts cannot be used to trigger a change that will be displayed on other users screen.
## Scripting language
Client-side scripting is done in **Javascript** (or any language that transpiles to Javascript like _Typescript_).
There are 2 ways you can use the scripting language:
* **In the map**: By directly referring a Javascript file inside your map, in the `script` property of your map.
* **In an iFrame**: By placing your Javascript script into an iFrame, your script can communicate with the WorkAdventure game
## Adding a script in the map
Create a `script` property in your map.
In Tiled, in order to access your map properties, you can click on _"Map > Map properties"_.
<div>
<figure class="figure">
<img src="https://workadventu.re/img/docs/admin/map_properties.png" class="figure-img img-fluid rounded" alt="" />
<figcaption class="figure-caption">The Map properties menu</figcaption>
</figure>
</div>
Create a `script` property (a "string"), and put the URL of your script.
You can put relative URLs. If your script file is next to your map, you can simply write the name of the script file here.
<div>
<figure class="figure">
<img src="https://workadventu.re/img/docs/script_property.png" class="figure-img img-fluid rounded" alt="" />
<figcaption class="figure-caption">The script property</figcaption>
</figure>
</div>
Start by testing this with a simple message sent to the chat.
**script.js**
```javascript
WA.sendChatMessage('Hello world', 'Mr Robot');
```
The `WA` objects contains a number of useful methods enabling you to interact with the WorkAdventure game. For instance, `WA.sendChatMessage` opens the chat and adds a message in it.
In your browser console, when you open the map, the chat message should be displayed right away.
## Adding a script in an iFrame
In WorkAdventure, you can easily [open an iFrame using the `openWebsite` property on a layer](special-zones). However, by default, the iFrame is not allowed to communicate with WorkAdventure.
This is done to improve security. In order to be able to execute a script that communicates with WorkAdventure inside an iFrame, you have to **explicitly allow the iFrame to use the "iFrame API"**.
In order to allow communication with WorkAdventure, you need to add an additional property: `openWebsiteAllowApi`. This property must be _boolean_ and you must set it to "true".
<div>
<figure class="figure">
<img src="https://workadventu.re/img/docs/open_website_allow_api.png" class="figure-img img-fluid rounded" alt="" />
<figcaption class="figure-caption">The `openWebsiteAllowApi` property</figcaption>
</figure>
</div>
In your iFrame HTML page, you now need to import the _WorkAdventure client API Javascript library_. This library contains the `WA` object that you can use to communicate with WorkAdventure.
The library is available at `https://play.workadventu.re/iframe_api.js`.
_Note:_ if you are using a self-hosted version of WorkAdventure, use `https://[front_domain]/iframe_api.js`
**iframe.html**
```html
<!doctype html>
<html lang="en">
<head>
<script src="https://play.workadventu.re/iframe_api.js"></script>
</head>
<body>
</body>
</html>
```
You can now start by testing this with a simple message sent to the chat.
**iframe.html**
```html
...
<script>
WA.sendChatMessage('Hello world', 'Mr Robot');
</script>
...
```
Let's now review the complete list of methods available in this `WA` object.
## Using Typescript
View the dedicated page about [using Typescript with the scripting API](using-typescript).
## Available features in the client API
The list of available functions and features is [available in the API Reference page, with examples](api-reference).

8393
front/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -26,8 +26,8 @@ export type IframeEventMap = {
goToPage: GoToPageEvent goToPage: GoToPageEvent
openCoWebSite: OpenCoWebSiteEvent openCoWebSite: OpenCoWebSiteEvent
closeCoWebSite: null closeCoWebSite: null
disablePlayerControl: null disablePlayerControls: null
restorePlayerControl: null restorePlayerControls: null
displayBubble: null displayBubble: null
removeBubble: null removeBubble: null
enableMoveEvents: undefined enableMoveEvents: undefined

View File

@ -107,9 +107,11 @@ class IframeListener {
} }
else if (payload.type === 'closeCoWebSite') { else if (payload.type === 'closeCoWebSite') {
scriptUtils.closeCoWebSite(); scriptUtils.closeCoWebSite();
} else if (payload.type === 'disablePlayerControl') { }
else if (payload.type === 'disablePlayerControls') {
this._disablePlayerControlStream.next(); this._disablePlayerControlStream.next();
} else if (payload.type === 'restorePlayerControl') { }
else if (payload.type === 'restorePlayerControls') {
this._enablePlayerControlStream.next(); this._enablePlayerControlStream.next();
} else if (payload.type === 'displayBubble') { } else if (payload.type === 'displayBubble') {
this._displayBubbleStream.next(); this._displayBubbleStream.next();

View File

@ -1,4 +1,4 @@
import {PUSHER_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable"; import { PUSHER_URL, UPLOADER_URL } from "../Enum/EnvironmentVariable";
import Axios from "axios"; import Axios from "axios";
import { import {
BatchMessage, BatchMessage,
@ -31,9 +31,9 @@ import {
BanUserMessage BanUserMessage
} from "../Messages/generated/messages_pb" } from "../Messages/generated/messages_pb"
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer"; import { UserSimplePeerInterface } from "../WebRtc/SimplePeer";
import Direction = PositionMessage.Direction; import Direction = PositionMessage.Direction;
import {ProtobufClientUtils} from "../Network/ProtobufClientUtils"; import { ProtobufClientUtils } from "../Network/ProtobufClientUtils";
import { import {
EventMessage, EventMessage,
GroupCreatedUpdatedMessageInterface, ItemEventMessageInterface, GroupCreatedUpdatedMessageInterface, ItemEventMessageInterface,
@ -42,23 +42,23 @@ import {
ViewportInterface, WebRtcDisconnectMessageInterface, ViewportInterface, WebRtcDisconnectMessageInterface,
WebRtcSignalReceivedMessageInterface, WebRtcSignalReceivedMessageInterface,
} from "./ConnexionModels"; } from "./ConnexionModels";
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures"; import { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures";
import {adminMessagesService} from "./AdminMessagesService"; import { adminMessagesService } from "./AdminMessagesService";
import {worldFullMessageStream} from "./WorldFullMessageStream"; import { worldFullMessageStream } from "./WorldFullMessageStream";
import {worldFullWarningStream} from "./WorldFullWarningStream"; import { worldFullWarningStream } from "./WorldFullWarningStream";
import {connectionManager} from "./ConnectionManager"; import { connectionManager } from "./ConnectionManager";
const manualPingDelay = 20000; const manualPingDelay = 20000;
export class RoomConnection implements RoomConnection { export class RoomConnection implements RoomConnection {
private readonly socket: WebSocket; private readonly socket: WebSocket;
private userId: number|null = null; private userId: number | null = null;
private listeners: Map<string, Function[]> = new Map<string, Function[]>(); private listeners: Map<string, Function[]> = new Map<string, Function[]>();
private static websocketFactory: null|((url: string)=>any) = null; // eslint-disable-line @typescript-eslint/no-explicit-any private static websocketFactory: null | ((url: string) => any) = null; // eslint-disable-line @typescript-eslint/no-explicit-any
private closed: boolean = false; private closed: boolean = false;
private tags: string[] = []; private tags: string[] = [];
public static setWebsocketFactory(websocketFactory: (url: string)=>any): void { // eslint-disable-line @typescript-eslint/no-explicit-any public static setWebsocketFactory(websocketFactory: (url: string) => any): void { // eslint-disable-line @typescript-eslint/no-explicit-any
RoomConnection.websocketFactory = websocketFactory; RoomConnection.websocketFactory = websocketFactory;
} }
@ -67,28 +67,28 @@ export class RoomConnection implements RoomConnection {
* @param token A JWT token containing the UUID of the user * @param token A JWT token containing the UUID of the user
* @param roomId The ID of the room in the form "_/[instance]/[map_url]" or "@/[org]/[event]/[map]" * @param roomId The ID of the room in the form "_/[instance]/[map_url]" or "@/[org]/[event]/[map]"
*/ */
public constructor(token: string|null, roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface, companion: string|null) { public constructor(token: string | null, roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface, companion: string | null) {
let url = new URL(PUSHER_URL, window.location.toString()).toString(); let url = new URL(PUSHER_URL, window.location.toString()).toString();
url = url.replace('http://', 'ws://').replace('https://', 'wss://'); url = url.replace('http://', 'ws://').replace('https://', 'wss://');
if (!url.endsWith('/')) { if (!url.endsWith('/')) {
url += '/'; url += '/';
} }
url += 'room'; url += 'room';
url += '?roomId='+(roomId ?encodeURIComponent(roomId):''); url += '?roomId=' + (roomId ? encodeURIComponent(roomId) : '');
url += '&token='+(token ?encodeURIComponent(token):''); url += '&token=' + (token ? encodeURIComponent(token) : '');
url += '&name='+encodeURIComponent(name); url += '&name=' + encodeURIComponent(name);
for (const layer of characterLayers) { for (const layer of characterLayers) {
url += '&characterLayers='+encodeURIComponent(layer); url += '&characterLayers=' + encodeURIComponent(layer);
} }
url += '&x='+Math.floor(position.x); url += '&x=' + Math.floor(position.x);
url += '&y='+Math.floor(position.y); url += '&y=' + Math.floor(position.y);
url += '&top='+Math.floor(viewport.top); url += '&top=' + Math.floor(viewport.top);
url += '&bottom='+Math.floor(viewport.bottom); url += '&bottom=' + Math.floor(viewport.bottom);
url += '&left='+Math.floor(viewport.left); url += '&left=' + Math.floor(viewport.left);
url += '&right='+Math.floor(viewport.right); url += '&right=' + Math.floor(viewport.right);
if (typeof companion === 'string') { if (typeof companion === 'string') {
url += '&companion='+encodeURIComponent(companion); url += '&companion=' + encodeURIComponent(companion);
} }
if (RoomConnection.websocketFactory) { if (RoomConnection.websocketFactory) {
@ -99,7 +99,7 @@ export class RoomConnection implements RoomConnection {
this.socket.binaryType = 'arraybuffer'; this.socket.binaryType = 'arraybuffer';
let interval: ReturnType<typeof setInterval>|undefined = undefined; let interval: ReturnType<typeof setInterval> | undefined = undefined;
this.socket.onopen = (ev) => { this.socket.onopen = (ev) => {
//we manually ping every 20s to not be logged out by the server, even when the game is in background. //we manually ping every 20s to not be logged out by the server, even when the game is in background.
@ -153,7 +153,7 @@ export class RoomConnection implements RoomConnection {
} else if (message.hasRoomjoinedmessage()) { } else if (message.hasRoomjoinedmessage()) {
const roomJoinedMessage = message.getRoomjoinedmessage() as RoomJoinedMessage; const roomJoinedMessage = message.getRoomjoinedmessage() as RoomJoinedMessage;
const items: { [itemId: number] : unknown } = {}; const items: { [itemId: number]: unknown } = {};
for (const item of roomJoinedMessage.getItemList()) { for (const item of roomJoinedMessage.getItemList()) {
items[item.getItemid()] = JSON.parse(item.getStatejson()); items[item.getItemid()] = JSON.parse(item.getStatejson());
} }
@ -170,10 +170,10 @@ export class RoomConnection implements RoomConnection {
} else if (message.hasWorldfullmessage()) { } else if (message.hasWorldfullmessage()) {
worldFullMessageStream.onMessage(); worldFullMessageStream.onMessage();
this.closed = true; this.closed = true;
} else if (message.hasWorldconnexionmessage()) { // // } else if (message.hasWorldconnexionmessage()) {
worldFullMessageStream.onMessage(message.getWorldconnexionmessage()?.getMessage()); // worldFullMessageStream.onMessage(message.getWorldconnexionmessage()?.getMessage());
this.closed = true; // this.closed = true;
}else if (message.hasWebrtcsignaltoclientmessage()) { } else if (message.hasWebrtcsignaltoclientmessage()) {
this.dispatch(EventMessage.WEBRTC_SIGNAL, message.getWebrtcsignaltoclientmessage()); this.dispatch(EventMessage.WEBRTC_SIGNAL, message.getWebrtcsignaltoclientmessage());
} else if (message.hasWebrtcscreensharingsignaltoclientmessage()) { } else if (message.hasWebrtcscreensharingsignaltoclientmessage()) {
this.dispatch(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, message.getWebrtcscreensharingsignaltoclientmessage()); this.dispatch(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, message.getWebrtcscreensharingsignaltoclientmessage());
@ -230,7 +230,7 @@ export class RoomConnection implements RoomConnection {
this.closed = true; this.closed = true;
} }
private toPositionMessage(x : number, y : number, direction : string, moving: boolean): PositionMessage { private toPositionMessage(x: number, y: number, direction: string, moving: boolean): PositionMessage {
const positionMessage = new PositionMessage(); const positionMessage = new PositionMessage();
positionMessage.setX(Math.floor(x)); positionMessage.setX(Math.floor(x));
positionMessage.setY(Math.floor(y)); positionMessage.setY(Math.floor(y));
@ -267,8 +267,8 @@ export class RoomConnection implements RoomConnection {
return viewportMessage; return viewportMessage;
} }
public sharePosition(x : number, y : number, direction : string, moving: boolean, viewport: ViewportInterface) : void{ public sharePosition(x: number, y: number, direction: string, moving: boolean, viewport: ViewportInterface): void {
if(!this.socket){ if (!this.socket) {
return; return;
} }
@ -472,7 +472,7 @@ export class RoomConnection implements RoomConnection {
if (this.closed === true || connectionManager.unloading) { if (this.closed === true || connectionManager.unloading) {
return; return;
} }
console.log('Socket closed with code '+event.code+". Reason: "+event.reason); console.log('Socket closed with code ' + event.code + ". Reason: " + event.reason);
if (event.code === 1000) { if (event.code === 1000) {
// Normal closure case // Normal closure case
return; return;
@ -518,8 +518,8 @@ export class RoomConnection implements RoomConnection {
}); });
} }
public uploadAudio(file : FormData){ public uploadAudio(file: FormData) {
return Axios.post(`${UPLOADER_URL}/upload-audio-message`, file).then((res: {data:{}}) => { return Axios.post(`${UPLOADER_URL}/upload-audio-message`, file).then((res: { data: {} }) => {
return res.data; return res.data;
}).catch((err) => { }).catch((err) => {
console.error(err); console.error(err);
@ -550,7 +550,7 @@ export class RoomConnection implements RoomConnection {
}); });
} }
public emitGlobalMessage(message: PlayGlobalMessageInterface){ public emitGlobalMessage(message: PlayGlobalMessageInterface) {
const playGlobalMessage = new PlayGlobalMessage(); const playGlobalMessage = new PlayGlobalMessage();
playGlobalMessage.setId(message.id); playGlobalMessage.setId(message.id);
playGlobalMessage.setType(message.type); playGlobalMessage.setType(message.type);
@ -562,7 +562,7 @@ export class RoomConnection implements RoomConnection {
this.socket.send(clientToServerMessage.serializeBinary().buffer); this.socket.send(clientToServerMessage.serializeBinary().buffer);
} }
public emitReportPlayerMessage(reportedUserId: number, reportComment: string ): void { public emitReportPlayerMessage(reportedUserId: number, reportComment: string): void {
const reportPlayerMessage = new ReportPlayerMessage(); const reportPlayerMessage = new ReportPlayerMessage();
reportPlayerMessage.setReporteduserid(reportedUserId); reportPlayerMessage.setReporteduserid(reportedUserId);
reportPlayerMessage.setReportcomment(reportComment); reportPlayerMessage.setReportcomment(reportComment);
@ -573,7 +573,7 @@ export class RoomConnection implements RoomConnection {
this.socket.send(clientToServerMessage.serializeBinary().buffer); this.socket.send(clientToServerMessage.serializeBinary().buffer);
} }
public emitQueryJitsiJwtMessage(jitsiRoom: string, tag: string|undefined ): void { public emitQueryJitsiJwtMessage(jitsiRoom: string, tag: string | undefined): void {
const queryJitsiJwtMessage = new QueryJitsiJwtMessage(); const queryJitsiJwtMessage = new QueryJitsiJwtMessage();
queryJitsiJwtMessage.setJitsiroom(jitsiRoom); queryJitsiJwtMessage.setJitsiroom(jitsiRoom);
if (tag !== undefined) { if (tag !== undefined) {

View File

@ -9,7 +9,7 @@ import {
PositionInterface, PositionInterface,
RoomJoinedMessageInterface RoomJoinedMessageInterface
} from "../../Connexion/ConnexionModels"; } from "../../Connexion/ConnexionModels";
import {CurrentGamerInterface, hasMovedEventName, Player} from "../Player/Player"; import { CurrentGamerInterface, hasMovedEventName, Player } from "../Player/Player";
import { import {
DEBUG_MODE, DEBUG_MODE,
JITSI_PRIVATE_MODE, JITSI_PRIVATE_MODE,
@ -25,15 +25,15 @@ import {
ITiledMapTileLayer, ITiledMapTileLayer,
ITiledTileSet ITiledTileSet
} from "../Map/ITiledMap"; } from "../Map/ITiledMap";
import {AddPlayerInterface} from "./AddPlayerInterface"; import { AddPlayerInterface } from "./AddPlayerInterface";
import {PlayerAnimationDirections} from "../Player/Animation"; import { PlayerAnimationDirections } from "../Player/Animation";
import {PlayerMovement} from "./PlayerMovement"; import { PlayerMovement } from "./PlayerMovement";
import {PlayersPositionInterpolator} from "./PlayersPositionInterpolator"; import { PlayersPositionInterpolator } from "./PlayersPositionInterpolator";
import {RemotePlayer} from "../Entity/RemotePlayer"; import { RemotePlayer } from "../Entity/RemotePlayer";
import {Queue} from 'queue-typescript'; import { Queue } from 'queue-typescript';
import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer"; import { SimplePeer, UserSimplePeerInterface } from "../../WebRtc/SimplePeer";
import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene"; import { ReconnectingSceneName } from "../Reconnecting/ReconnectingScene";
import {lazyLoadPlayerCharacterTextures, loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager"; import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager";
import { import {
CenterListener, CenterListener,
JITSI_MESSAGE_PROPERTIES, JITSI_MESSAGE_PROPERTIES,
@ -46,56 +46,56 @@ import {
AUDIO_VOLUME_PROPERTY, AUDIO_VOLUME_PROPERTY,
AUDIO_LOOP_PROPERTY AUDIO_LOOP_PROPERTY
} from "../../WebRtc/LayoutManager"; } from "../../WebRtc/LayoutManager";
import {GameMap} from "./GameMap"; import { GameMap } from "./GameMap";
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager"; import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
import {mediaManager} from "../../WebRtc/MediaManager"; import { mediaManager } from "../../WebRtc/MediaManager";
import {ItemFactoryInterface} from "../Items/ItemFactoryInterface"; import { ItemFactoryInterface } from "../Items/ItemFactoryInterface";
import {ActionableItem} from "../Items/ActionableItem"; import { ActionableItem } from "../Items/ActionableItem";
import {UserInputManager} from "../UserInput/UserInputManager"; import { UserInputManager } from "../UserInput/UserInputManager";
import {UserMovedMessage} from "../../Messages/generated/messages_pb"; import { UserMovedMessage } from "../../Messages/generated/messages_pb";
import {ProtobufClientUtils} from "../../Network/ProtobufClientUtils"; import { ProtobufClientUtils } from "../../Network/ProtobufClientUtils";
import {connectionManager} from "../../Connexion/ConnectionManager"; import { connectionManager } from "../../Connexion/ConnectionManager";
import {RoomConnection} from "../../Connexion/RoomConnection"; import { RoomConnection } from "../../Connexion/RoomConnection";
import {GlobalMessageManager} from "../../Administration/GlobalMessageManager"; import { GlobalMessageManager } from "../../Administration/GlobalMessageManager";
import {userMessageManager} from "../../Administration/UserMessageManager"; import { userMessageManager } from "../../Administration/UserMessageManager";
import {ConsoleGlobalMessageManager} from "../../Administration/ConsoleGlobalMessageManager"; import { ConsoleGlobalMessageManager } from "../../Administration/ConsoleGlobalMessageManager";
import {ResizableScene} from "../Login/ResizableScene"; import { ResizableScene } from "../Login/ResizableScene";
import {Room} from "../../Connexion/Room"; import { Room } from "../../Connexion/Room";
import {jitsiFactory} from "../../WebRtc/JitsiFactory"; import { jitsiFactory } from "../../WebRtc/JitsiFactory";
import {urlManager} from "../../Url/UrlManager"; import { urlManager } from "../../Url/UrlManager";
import {audioManager} from "../../WebRtc/AudioManager"; import { audioManager } from "../../WebRtc/AudioManager";
import {PresentationModeIcon} from "../Components/PresentationModeIcon"; import { PresentationModeIcon } from "../Components/PresentationModeIcon";
import {ChatModeIcon} from "../Components/ChatModeIcon"; import { ChatModeIcon } from "../Components/ChatModeIcon";
import {OpenChatIcon, openChatIconName} from "../Components/OpenChatIcon"; import { OpenChatIcon, openChatIconName } from "../Components/OpenChatIcon";
import {SelectCharacterScene, SelectCharacterSceneName} from "../Login/SelectCharacterScene"; import { SelectCharacterScene, SelectCharacterSceneName } from "../Login/SelectCharacterScene";
import {TextureError} from "../../Exception/TextureError"; import { TextureError } from "../../Exception/TextureError";
import {addLoader} from "../Components/Loader"; import { addLoader } from "../Components/Loader";
import {ErrorSceneName} from "../Reconnecting/ErrorScene"; import { ErrorSceneName } from "../Reconnecting/ErrorScene";
import {localUserStore} from "../../Connexion/LocalUserStore"; import { localUserStore } from "../../Connexion/LocalUserStore";
import {iframeListener} from "../../Api/IframeListener"; import { iframeListener } from "../../Api/IframeListener";
import {HtmlUtils} from "../../WebRtc/HtmlUtils"; import { HtmlUtils } from "../../WebRtc/HtmlUtils";
import Texture = Phaser.Textures.Texture; import Texture = Phaser.Textures.Texture;
import Sprite = Phaser.GameObjects.Sprite; import Sprite = Phaser.GameObjects.Sprite;
import CanvasTexture = Phaser.Textures.CanvasTexture; import CanvasTexture = Phaser.Textures.CanvasTexture;
import GameObject = Phaser.GameObjects.GameObject; import GameObject = Phaser.GameObjects.GameObject;
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR; import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
import DOMElement = Phaser.GameObjects.DOMElement; import DOMElement = Phaser.GameObjects.DOMElement;
import {Subscription} from "rxjs"; import { Subscription } from "rxjs";
import {worldFullMessageStream} from "../../Connexion/WorldFullMessageStream"; import { worldFullMessageStream } from "../../Connexion/WorldFullMessageStream";
import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager"; import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager";
import RenderTexture = Phaser.GameObjects.RenderTexture; import RenderTexture = Phaser.GameObjects.RenderTexture;
import Tilemap = Phaser.Tilemaps.Tilemap; import Tilemap = Phaser.Tilemaps.Tilemap;
import {DirtyScene} from "./DirtyScene"; import { DirtyScene } from "./DirtyScene";
import {TextUtils} from "../Components/TextUtils"; import { TextUtils } from "../Components/TextUtils";
import {touchScreenManager} from "../../Touch/TouchScreenManager"; import { touchScreenManager } from "../../Touch/TouchScreenManager";
import {PinchManager} from "../UserInput/PinchManager"; import { PinchManager } from "../UserInput/PinchManager";
import {joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey} from "../Components/MobileJoystick"; import { joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey } from "../Components/MobileJoystick";
import { PlayerStateObject } from '../../Api/Events/ApiGameStateEvent'; import { PlayerStateObject } from '../../Api/Events/ApiGameStateEvent';
import { waScaleManager } from "../Services/WaScaleManager"; import { waScaleManager } from "../Services/WaScaleManager";
import { HasMovedEvent } from '../../Api/Events/HasMovedEvent'; import { HasMovedEvent } from '../../Api/Events/HasMovedEvent';
export interface GameSceneInitInterface { export interface GameSceneInitInterface {
initPosition: PointInterface|null, initPosition: PointInterface | null,
reconnecting: boolean reconnecting: boolean
} }
@ -132,10 +132,10 @@ interface DeleteGroupEventInterface {
const defaultStartLayerName = 'start'; const defaultStartLayerName = 'start';
export class GameScene extends DirtyScene implements CenterListener { export class GameScene extends DirtyScene implements CenterListener {
Terrains : Array<Phaser.Tilemaps.Tileset>; Terrains: Array<Phaser.Tilemaps.Tileset>;
CurrentPlayer!: CurrentGamerInterface; CurrentPlayer!: CurrentGamerInterface;
MapPlayers!: Phaser.Physics.Arcade.Group; MapPlayers!: Phaser.Physics.Arcade.Group;
MapPlayersByKey : Map<number, RemotePlayer> = new Map<number, RemotePlayer>(); MapPlayersByKey: Map<number, RemotePlayer> = new Map<number, RemotePlayer>();
Map!: Phaser.Tilemaps.Tilemap; Map!: Phaser.Tilemaps.Tilemap;
Layers!: Array<Phaser.Tilemaps.TilemapLayer>; Layers!: Array<Phaser.Tilemaps.TilemapLayer>;
Objects!: Array<Phaser.Physics.Arcade.Sprite>; Objects!: Array<Phaser.Physics.Arcade.Sprite>;
@ -145,10 +145,10 @@ export class GameScene extends DirtyScene implements CenterListener {
startY!: number; startY!: number;
circleTexture!: CanvasTexture; circleTexture!: CanvasTexture;
circleRedTexture!: CanvasTexture; circleRedTexture!: CanvasTexture;
pendingEvents: Queue<InitUserPositionEventInterface|AddPlayerEventInterface|RemovePlayerEventInterface|UserMovedEventInterface|GroupCreatedUpdatedEventInterface|DeleteGroupEventInterface> = new Queue<InitUserPositionEventInterface|AddPlayerEventInterface|RemovePlayerEventInterface|UserMovedEventInterface|GroupCreatedUpdatedEventInterface|DeleteGroupEventInterface>(); pendingEvents: Queue<InitUserPositionEventInterface | AddPlayerEventInterface | RemovePlayerEventInterface | UserMovedEventInterface | GroupCreatedUpdatedEventInterface | DeleteGroupEventInterface> = new Queue<InitUserPositionEventInterface | AddPlayerEventInterface | RemovePlayerEventInterface | UserMovedEventInterface | GroupCreatedUpdatedEventInterface | DeleteGroupEventInterface>();
private initPosition: PositionInterface|null = null; private initPosition: PositionInterface | null = null;
private playersPositionInterpolator = new PlayersPositionInterpolator(); private playersPositionInterpolator = new PlayersPositionInterpolator();
public connection: RoomConnection|undefined; public connection: RoomConnection | undefined;
private simplePeer!: SimplePeer; private simplePeer!: SimplePeer;
private GlobalMessageManager!: GlobalMessageManager; private GlobalMessageManager!: GlobalMessageManager;
public ConsoleGlobalMessageManager!: ConsoleGlobalMessageManager; public ConsoleGlobalMessageManager!: ConsoleGlobalMessageManager;
@ -157,7 +157,7 @@ export class GameScene extends DirtyScene implements CenterListener {
// A promise that will resolve when the "create" method is called (signaling loading is ended) // A promise that will resolve when the "create" method is called (signaling loading is ended)
private createPromise: Promise<void>; private createPromise: Promise<void>;
private createPromiseResolve!: (value?: void | PromiseLike<void>) => void; private createPromiseResolve!: (value?: void | PromiseLike<void>) => void;
private iframeSubscriptionList! : Array<Subscription>; private iframeSubscriptionList!: Array<Subscription>;
MapUrlFile: string; MapUrlFile: string;
RoomId: string; RoomId: string;
instance: string; instance: string;
@ -176,20 +176,20 @@ export class GameScene extends DirtyScene implements CenterListener {
private gameMap!: GameMap; private gameMap!: GameMap;
private actionableItems: Map<number, ActionableItem> = new Map<number, ActionableItem>(); private actionableItems: Map<number, ActionableItem> = new Map<number, ActionableItem>();
// The item that can be selected by pressing the space key. // The item that can be selected by pressing the space key.
private outlinedItem: ActionableItem|null = null; private outlinedItem: ActionableItem | null = null;
public userInputManager!: UserInputManager; public userInputManager!: UserInputManager;
private isReconnecting: boolean|undefined = undefined; private isReconnecting: boolean | undefined = undefined;
private startLayerName!: string | null; private startLayerName!: string | null;
private openChatIcon!: OpenChatIcon; private openChatIcon!: OpenChatIcon;
private playerName!: string; private playerName!: string;
private characterLayers!: string[]; private characterLayers!: string[];
private companion!: string|null; private companion!: string | null;
private messageSubscription: Subscription|null = null; private messageSubscription: Subscription | null = null;
private popUpElements : Map<number, DOMElement> = new Map<number, Phaser.GameObjects.DOMElement>(); private popUpElements: Map<number, DOMElement> = new Map<number, Phaser.GameObjects.DOMElement>();
private originalMapUrl: string|undefined; private originalMapUrl: string | undefined;
private pinchManager: PinchManager|undefined; private pinchManager: PinchManager | undefined;
constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) { constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) {
super({ super({
key: customKey ?? room.id key: customKey ?? room.id
}); });
@ -224,13 +224,13 @@ export class GameScene extends DirtyScene implements CenterListener {
this.load.image(joystickBaseKey, joystickBaseImg); this.load.image(joystickBaseKey, joystickBaseImg);
this.load.image(joystickThumbKey, joystickThumbImg); this.load.image(joystickThumbKey, joystickThumbImg);
} }
this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => { this.load.on(FILE_LOAD_ERROR, (file: { src: string }) => {
// If we happen to be in HTTP and we are trying to load a URL in HTTPS only... (this happens only in dev environments) // If we happen to be in HTTP and we are trying to load a URL in HTTPS only... (this happens only in dev environments)
if (window.location.protocol === 'http:' && file.src === this.MapUrlFile && file.src.startsWith('http:') && this.originalMapUrl === undefined) { if (window.location.protocol === 'http:' && file.src === this.MapUrlFile && file.src.startsWith('http:') && this.originalMapUrl === undefined) {
this.originalMapUrl = this.MapUrlFile; this.originalMapUrl = this.MapUrlFile;
this.MapUrlFile = this.MapUrlFile.replace('http://', 'https://'); this.MapUrlFile = this.MapUrlFile.replace('http://', 'https://');
this.load.tilemapTiledJSON(this.MapUrlFile, this.MapUrlFile); this.load.tilemapTiledJSON(this.MapUrlFile, this.MapUrlFile);
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);
}); });
return; return;
@ -244,7 +244,7 @@ export class GameScene extends DirtyScene implements CenterListener {
this.originalMapUrl = this.MapUrlFile; this.originalMapUrl = this.MapUrlFile;
this.MapUrlFile = this.MapUrlFile.replace('https://', 'http://'); this.MapUrlFile = this.MapUrlFile.replace('https://', 'http://');
this.load.tilemapTiledJSON(this.MapUrlFile, this.MapUrlFile); this.load.tilemapTiledJSON(this.MapUrlFile, this.MapUrlFile);
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);
}); });
return; return;
@ -256,7 +256,7 @@ export class GameScene extends DirtyScene implements CenterListener {
message: this.originalMapUrl ?? file.src message: this.originalMapUrl ?? file.src
}); });
}); });
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);
}); });
//TODO strategy to add access token //TODO strategy to add access token
@ -268,7 +268,7 @@ export class GameScene extends DirtyScene implements CenterListener {
this.onMapLoad(data); this.onMapLoad(data);
} }
this.load.spritesheet('layout_modes', 'resources/objects/layout_modes.png', {frameWidth: 32, frameHeight: 32}); this.load.spritesheet('layout_modes', 'resources/objects/layout_modes.png', { frameWidth: 32, frameHeight: 32 });
this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml'); this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
//this function must stay at the end of preload function //this function must stay at the end of preload function
@ -297,7 +297,7 @@ export class GameScene extends DirtyScene implements CenterListener {
for (const layer of this.mapFile.layers) { for (const layer of this.mapFile.layers) {
if (layer.type === 'objectgroup') { if (layer.type === 'objectgroup') {
for (const object of layer.objects) { for (const object of layer.objects) {
let objectsOfType: ITiledMapObject[]|undefined; let objectsOfType: ITiledMapObject[] | undefined;
if (!objects.has(object.type)) { if (!objects.has(object.type)) {
objectsOfType = new Array<ITiledMapObject>(); objectsOfType = new Array<ITiledMapObject>();
} else { } else {
@ -360,7 +360,7 @@ export class GameScene extends DirtyScene implements CenterListener {
} }
//hook initialisation //hook initialisation
init(initData : GameSceneInitInterface) { init(initData: GameSceneInitInterface) {
if (initData.initPosition !== undefined) { if (initData.initPosition !== undefined) {
this.initPosition = initData.initPosition; //todo: still used? this.initPosition = initData.initPosition; //todo: still used?
} }
@ -439,7 +439,7 @@ export class GameScene extends DirtyScene implements CenterListener {
this.Objects = new Array<Phaser.Physics.Arcade.Sprite>(); this.Objects = new Array<Phaser.Physics.Arcade.Sprite>();
//initialise list of other player //initialise list of other player
this.MapPlayers = this.physics.add.group({immovable: true}); this.MapPlayers = this.physics.add.group({ immovable: true });
//create input to move //create input to move
@ -689,16 +689,16 @@ export class GameScene extends DirtyScene implements CenterListener {
} }
private safeParseJSONstring(jsonString: string|undefined, propertyName: string) { private safeParseJSONstring(jsonString: string | undefined, propertyName: string) {
try { try {
return jsonString ? JSON.parse(jsonString) : {}; return jsonString ? JSON.parse(jsonString) : {};
} catch(e) { } catch (e) {
console.warn('Invalid JSON found in property "' + propertyName + '" of the map:' + jsonString, e); console.warn('Invalid JSON found in property "' + propertyName + '" of the map:' + jsonString, e);
return {} return {}
} }
} }
private triggerOnMapLayerPropertyChange(){ private triggerOnMapLayerPropertyChange() {
this.gameMap.onPropertyChange('exitSceneUrl', (newValue, oldValue) => { this.gameMap.onPropertyChange('exitSceneUrl', (newValue, oldValue) => {
if (newValue) this.onMapExit(newValue as string); if (newValue) this.onMapExit(newValue as string);
}); });
@ -709,22 +709,22 @@ export class GameScene extends DirtyScene implements CenterListener {
if (newValue === undefined) { if (newValue === undefined) {
layoutManager.removeActionButton('openWebsite', this.userInputManager); layoutManager.removeActionButton('openWebsite', this.userInputManager);
coWebsiteManager.closeCoWebsite(); coWebsiteManager.closeCoWebsite();
}else{ } else {
const openWebsiteFunction = () => { const openWebsiteFunction = () => {
coWebsiteManager.loadCoWebsite(newValue as string, this.MapUrlFile, allProps.get('openWebsiteAllowApi') as boolean | undefined, allProps.get('openWebsitePolicy') as string | undefined); coWebsiteManager.loadCoWebsite(newValue as string, this.MapUrlFile, allProps.get('openWebsiteAllowApi') as boolean | undefined, allProps.get('openWebsitePolicy') as string | undefined);
layoutManager.removeActionButton('openWebsite', this.userInputManager); layoutManager.removeActionButton('openWebsite', this.userInputManager);
}; };
const openWebsiteTriggerValue = allProps.get(TRIGGER_WEBSITE_PROPERTIES); const openWebsiteTriggerValue = allProps.get(TRIGGER_WEBSITE_PROPERTIES);
if(openWebsiteTriggerValue && openWebsiteTriggerValue === ON_ACTION_TRIGGER_BUTTON) { if (openWebsiteTriggerValue && openWebsiteTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
let message = allProps.get(WEBSITE_MESSAGE_PROPERTIES); let message = allProps.get(WEBSITE_MESSAGE_PROPERTIES);
if(message === undefined){ if (message === undefined) {
message = 'Press SPACE or touch here to open web site'; message = 'Press SPACE or touch here to open web site';
} }
layoutManager.addActionButton('openWebsite', message.toString(), () => { layoutManager.addActionButton('openWebsite', message.toString(), () => {
openWebsiteFunction(); openWebsiteFunction();
}, this.userInputManager); }, this.userInputManager);
}else{ } else {
openWebsiteFunction(); openWebsiteFunction();
} }
} }
@ -733,12 +733,12 @@ export class GameScene extends DirtyScene implements CenterListener {
if (newValue === undefined) { if (newValue === undefined) {
layoutManager.removeActionButton('jitsiRoom', this.userInputManager); layoutManager.removeActionButton('jitsiRoom', this.userInputManager);
this.stopJitsi(); this.stopJitsi();
}else{ } else {
const openJitsiRoomFunction = () => { const openJitsiRoomFunction = () => {
const roomName = jitsiFactory.getRoomName(newValue.toString(), this.instance); const roomName = jitsiFactory.getRoomName(newValue.toString(), this.instance);
const jitsiUrl = allProps.get("jitsiUrl") as string|undefined; const jitsiUrl = allProps.get("jitsiUrl") as string | undefined;
if (JITSI_PRIVATE_MODE && !jitsiUrl) { if (JITSI_PRIVATE_MODE && !jitsiUrl) {
const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined; const adminTag = allProps.get("jitsiRoomAdminTag") as string | undefined;
this.connection?.emitQueryJitsiJwtMessage(roomName, adminTag); this.connection?.emitQueryJitsiJwtMessage(roomName, adminTag);
} else { } else {
@ -748,7 +748,7 @@ export class GameScene extends DirtyScene implements CenterListener {
} }
const jitsiTriggerValue = allProps.get(TRIGGER_JITSI_PROPERTIES); const jitsiTriggerValue = allProps.get(TRIGGER_JITSI_PROPERTIES);
if(jitsiTriggerValue && jitsiTriggerValue === ON_ACTION_TRIGGER_BUTTON) { if (jitsiTriggerValue && jitsiTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
let message = allProps.get(JITSI_MESSAGE_PROPERTIES); let message = allProps.get(JITSI_MESSAGE_PROPERTIES);
if (message === undefined) { if (message === undefined) {
message = 'Press SPACE or touch here to enter Jitsi Meet room'; message = 'Press SPACE or touch here to enter Jitsi Meet room';
@ -756,7 +756,7 @@ export class GameScene extends DirtyScene implements CenterListener {
layoutManager.addActionButton('jitsiRoom', message.toString(), () => { layoutManager.addActionButton('jitsiRoom', message.toString(), () => {
openJitsiRoomFunction(); openJitsiRoomFunction();
}, this.userInputManager); }, this.userInputManager);
}else{ } else {
openJitsiRoomFunction(); openJitsiRoomFunction();
} }
} }
@ -769,8 +769,8 @@ export class GameScene extends DirtyScene implements CenterListener {
} }
}); });
this.gameMap.onPropertyChange('playAudio', (newValue, oldValue, allProps) => { this.gameMap.onPropertyChange('playAudio', (newValue, oldValue, allProps) => {
const volume = allProps.get(AUDIO_VOLUME_PROPERTY) as number|undefined; const volume = allProps.get(AUDIO_VOLUME_PROPERTY) as number | undefined;
const loop = allProps.get(AUDIO_LOOP_PROPERTY) as boolean|undefined; const loop = allProps.get(AUDIO_LOOP_PROPERTY) as boolean | undefined;
newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl(), volume, loop); newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl(), volume, loop);
}); });
// TODO: This legacy property should be removed at some point // TODO: This legacy property should be removed at some point
@ -792,9 +792,9 @@ export class GameScene extends DirtyScene implements CenterListener {
this.iframeSubscriptionList = []; this.iframeSubscriptionList = [];
this.iframeSubscriptionList.push(iframeListener.openPopupStream.subscribe((openPopupEvent) => { this.iframeSubscriptionList.push(iframeListener.openPopupStream.subscribe((openPopupEvent) => {
let objectLayerSquare : ITiledMapObject; let objectLayerSquare: ITiledMapObject;
const targetObjectData = this.getObjectLayerData(openPopupEvent.targetObject); const targetObjectData = this.getObjectLayerData(openPopupEvent.targetObject);
if (targetObjectData !== undefined){ if (targetObjectData !== undefined) {
objectLayerSquare = targetObjectData; objectLayerSquare = targetObjectData;
} else { } else {
console.error("Error while opening a popup. Cannot find an object on the map with name '" + openPopupEvent.targetObject + "'. The first parameter of WA.openPopup() must be the name of a rectangle object in your map."); console.error("Error while opening a popup. Cannot find an object on the map with name '" + openPopupEvent.targetObject + "'. The first parameter of WA.openPopup() must be the name of a rectangle object in your map.");
@ -812,10 +812,10 @@ ${escapedMessage}
id++; id++;
} }
html += '</div>'; html += '</div>';
const domElement = this.add.dom(objectLayerSquare.x , const domElement = this.add.dom(objectLayerSquare.x,
objectLayerSquare.y).createFromHTML(html); objectLayerSquare.y).createFromHTML(html);
const container : HTMLDivElement = domElement.getChildByID("container") as HTMLDivElement; const container: HTMLDivElement = domElement.getChildByID("container") as HTMLDivElement;
container.style.width = objectLayerSquare.width + "px"; container.style.width = objectLayerSquare.width + "px";
domElement.scale = 0; domElement.scale = 0;
domElement.setClassName('popUpElement'); domElement.setClassName('popUpElement');
@ -835,10 +835,10 @@ ${escapedMessage}
id++; id++;
} }
this.tweens.add({ this.tweens.add({
targets : domElement , targets: domElement,
scale : 1, scale: 1,
ease : "EaseOut", ease: "EaseOut",
duration : 400, duration: 400,
}); });
this.popUpElements.set(openPopupEvent.popupId, domElement); this.popUpElements.set(openPopupEvent.popupId, domElement);
@ -847,26 +847,26 @@ ${escapedMessage}
this.iframeSubscriptionList.push(iframeListener.closePopupStream.subscribe((closePopupEvent) => { this.iframeSubscriptionList.push(iframeListener.closePopupStream.subscribe((closePopupEvent) => {
const popUpElement = this.popUpElements.get(closePopupEvent.popupId); const popUpElement = this.popUpElements.get(closePopupEvent.popupId);
if (popUpElement === undefined) { if (popUpElement === undefined) {
console.error('Could not close popup with ID ', closePopupEvent.popupId,'. Maybe it has already been closed?'); console.error('Could not close popup with ID ', closePopupEvent.popupId, '. Maybe it has already been closed?');
} }
this.tweens.add({ this.tweens.add({
targets : popUpElement , targets: popUpElement,
scale : 0, scale: 0,
ease : "EaseOut", ease: "EaseOut",
duration : 400, duration: 400,
onComplete : () => { onComplete: () => {
popUpElement?.destroy(); popUpElement?.destroy();
this.popUpElements.delete(closePopupEvent.popupId); this.popUpElements.delete(closePopupEvent.popupId);
}, },
}); });
})); }));
this.iframeSubscriptionList.push(iframeListener.disablePlayerControlStream.subscribe(()=>{ this.iframeSubscriptionList.push(iframeListener.disablePlayerControlStream.subscribe(() => {
this.userInputManager.disableControls(); this.userInputManager.disableControls();
})); }));
this.iframeSubscriptionList.push(iframeListener.enablePlayerControlStream.subscribe(()=>{ this.iframeSubscriptionList.push(iframeListener.enablePlayerControlStream.subscribe(() => {
this.userInputManager.restoreControls(); this.userInputManager.restoreControls();
})); }));
this.iframeSubscriptionList.push(iframeListener.gameStateStream.subscribe(() => { this.iframeSubscriptionList.push(iframeListener.gameStateStream.subscribe(() => {
@ -901,14 +901,14 @@ ${escapedMessage}
}) })
})); }));
let scriptedBubbleSprite : Sprite; let scriptedBubbleSprite: Sprite;
this.iframeSubscriptionList.push(iframeListener.displayBubbleStream.subscribe(()=>{ this.iframeSubscriptionList.push(iframeListener.displayBubbleStream.subscribe(() => {
scriptedBubbleSprite = new Sprite(this,this.CurrentPlayer.x + 25,this.CurrentPlayer.y,'circleSprite-white'); scriptedBubbleSprite = new Sprite(this, this.CurrentPlayer.x + 25, this.CurrentPlayer.y, 'circleSprite-white');
scriptedBubbleSprite.setDisplayOrigin(48, 48); scriptedBubbleSprite.setDisplayOrigin(48, 48);
this.add.existing(scriptedBubbleSprite); this.add.existing(scriptedBubbleSprite);
})); }));
this.iframeSubscriptionList.push(iframeListener.removeBubbleStream.subscribe(()=>{ this.iframeSubscriptionList.push(iframeListener.removeBubbleStream.subscribe(() => {
scriptedBubbleSprite.destroy(); scriptedBubbleSprite.destroy();
})); }));
@ -919,8 +919,8 @@ ${escapedMessage}
} }
private onMapExit(exitKey: string) { private onMapExit(exitKey: string) {
const {roomId, hash} = Room.getIdFromIdentifier(exitKey, this.MapUrlFile, this.instance); const { roomId, hash } = Room.getIdFromIdentifier(exitKey, this.MapUrlFile, this.instance);
if (!roomId) throw new Error('Could not find the room from its exit key: '+exitKey); if (!roomId) throw new Error('Could not find the room from its exit key: ' + exitKey);
urlManager.pushStartLayerNameToUrl(hash); urlManager.pushStartLayerNameToUrl(hash);
if (roomId !== this.scene.key) { if (roomId !== this.scene.key) {
if (this.scene.get(roomId) === null) { if (this.scene.get(roomId) === null) {
@ -958,7 +958,7 @@ ${escapedMessage}
this.userInputManager.destroy(); this.userInputManager.destroy();
this.pinchManager?.destroy(); this.pinchManager?.destroy();
for(const iframeEvents of this.iframeSubscriptionList){ for (const iframeEvents of this.iframeSubscriptionList) {
iframeEvents.unsubscribe(); iframeEvents.unsubscribe();
} }
} }
@ -978,7 +978,7 @@ ${escapedMessage}
private switchLayoutMode(): void { private switchLayoutMode(): void {
//if discussion is activated, this layout cannot be activated //if discussion is activated, this layout cannot be activated
if(mediaManager.activatedDiscussion){ if (mediaManager.activatedDiscussion) {
return; return;
} }
const mode = layoutManager.getLayoutMode(); const mode = layoutManager.getLayoutMode();
@ -1019,24 +1019,24 @@ ${escapedMessage}
private initPositionFromLayerName(layerName: string) { private initPositionFromLayerName(layerName: string) {
for (const layer of this.gameMap.layersIterator) { for (const layer of this.gameMap.layersIterator) {
if ((layerName === layer.name || layer.name.endsWith('/'+layerName)) && layer.type === 'tilelayer' && (layerName === defaultStartLayerName || this.isStartLayer(layer))) { if ((layerName === layer.name || layer.name.endsWith('/' + layerName)) && layer.type === 'tilelayer' && (layerName === defaultStartLayerName || this.isStartLayer(layer))) {
const startPosition = this.startUser(layer); const startPosition = this.startUser(layer);
this.startX = startPosition.x + this.mapFile.tilewidth/2; this.startX = startPosition.x + this.mapFile.tilewidth / 2;
this.startY = startPosition.y + this.mapFile.tileheight/2; this.startY = startPosition.y + this.mapFile.tileheight / 2;
} }
} }
} }
private getExitUrl(layer: ITiledMapLayer): string|undefined { private getExitUrl(layer: ITiledMapLayer): string | undefined {
return this.getProperty(layer, "exitUrl") as string|undefined; return this.getProperty(layer, "exitUrl") as string | undefined;
} }
/** /**
* @deprecated the map property exitSceneUrl is deprecated * @deprecated the map property exitSceneUrl is deprecated
*/ */
private getExitSceneUrl(layer: ITiledMapLayer): string|undefined { private getExitSceneUrl(layer: ITiledMapLayer): string | undefined {
return this.getProperty(layer, "exitSceneUrl") as string|undefined; return this.getProperty(layer, "exitSceneUrl") as string | undefined;
} }
private isStartLayer(layer: ITiledMapLayer): boolean { private isStartLayer(layer: ITiledMapLayer): boolean {
@ -1047,8 +1047,8 @@ ${escapedMessage}
return (this.getProperties(map, "script") as string[]).map((script) => (new URL(script, this.MapUrlFile)).toString()); return (this.getProperties(map, "script") as string[]).map((script) => (new URL(script, this.MapUrlFile)).toString());
} }
private getProperty(layer: ITiledMapLayer|ITiledMap, name: string): string|boolean|number|undefined { private getProperty(layer: ITiledMapLayer | ITiledMap, name: string): string | boolean | number | undefined {
const properties: ITiledMapLayerProperty[]|undefined = layer.properties; const properties: ITiledMapLayerProperty[] | undefined = layer.properties;
if (!properties) { if (!properties) {
return undefined; return undefined;
} }
@ -1059,8 +1059,8 @@ ${escapedMessage}
return obj.value; return obj.value;
} }
private getProperties(layer: ITiledMapLayer|ITiledMap, name: string): (string|number|boolean|undefined)[] { private getProperties(layer: ITiledMapLayer | ITiledMap, name: string): (string | number | boolean | undefined)[] {
const properties: ITiledMapLayerProperty[]|undefined = layer.properties; const properties: ITiledMapLayerProperty[] | undefined = layer.properties;
if (!properties) { if (!properties) {
return []; return [];
} }
@ -1068,30 +1068,30 @@ ${escapedMessage}
} }
//todo: push that into the gameManager //todo: push that into the gameManager
private async loadNextGame(exitSceneIdentifier: string){ private async loadNextGame(exitSceneIdentifier: string) {
const {roomId, hash} = Room.getIdFromIdentifier(exitSceneIdentifier, this.MapUrlFile, this.instance); const { roomId, hash } = Room.getIdFromIdentifier(exitSceneIdentifier, this.MapUrlFile, this.instance);
const room = new Room(roomId); const room = new Room(roomId);
await gameManager.loadMap(room, this.scene); await gameManager.loadMap(room, this.scene);
} }
private startUser(layer: ITiledMapTileLayer): PositionInterface { private startUser(layer: ITiledMapTileLayer): PositionInterface {
const tiles = layer.data; const tiles = layer.data;
if (typeof(tiles) === 'string') { if (typeof (tiles) === 'string') {
throw new Error('The content of a JSON map must be filled as a JSON array, not as a string'); throw new Error('The content of a JSON map must be filled as a JSON array, not as a string');
} }
const possibleStartPositions : PositionInterface[] = []; const possibleStartPositions: PositionInterface[] = [];
tiles.forEach((objectKey : number, key: number) => { tiles.forEach((objectKey: number, key: number) => {
if(objectKey === 0){ if (objectKey === 0) {
return; return;
} }
const y = Math.floor(key / layer.width); const y = Math.floor(key / layer.width);
const x = key % layer.width; const x = key % layer.width;
possibleStartPositions.push({x: x * this.mapFile.tilewidth, y: y * this.mapFile.tilewidth}); possibleStartPositions.push({ x: x * this.mapFile.tilewidth, y: y * this.mapFile.tilewidth });
}); });
// Get a value at random amongst allowed values // Get a value at random amongst allowed values
if (possibleStartPositions.length === 0) { if (possibleStartPositions.length === 0) {
console.warn('The start layer "'+layer.name+'" for this map is empty.'); console.warn('The start layer "' + layer.name + '" for this map is empty.');
return { return {
x: 0, x: 0,
y: 0 y: 0
@ -1103,12 +1103,12 @@ ${escapedMessage}
//todo: in a dedicated class/function? //todo: in a dedicated class/function?
initCamera() { initCamera() {
this.cameras.main.setBounds(0,0, this.Map.widthInPixels, this.Map.heightInPixels); this.cameras.main.setBounds(0, 0, this.Map.widthInPixels, this.Map.heightInPixels);
this.cameras.main.startFollow(this.CurrentPlayer, true); this.cameras.main.startFollow(this.CurrentPlayer, true);
this.updateCameraOffset(); this.updateCameraOffset();
} }
addLayer(Layer : Phaser.Tilemaps.TilemapLayer){ addLayer(Layer: Phaser.Tilemaps.TilemapLayer) {
this.Layers.push(Layer); this.Layers.push(Layer);
} }
@ -1119,7 +1119,7 @@ ${escapedMessage}
this.physics.add.collider(this.CurrentPlayer, Layer, (object1: GameObject, object2: GameObject) => { this.physics.add.collider(this.CurrentPlayer, Layer, (object1: GameObject, object2: GameObject) => {
//this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name) //this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name)
}); });
Layer.setCollisionByProperty({collides: true}); Layer.setCollisionByProperty({ collides: true });
if (DEBUG_MODE) { if (DEBUG_MODE) {
//debug code to see the collision hitbox of the object in the top layer //debug code to see the collision hitbox of the object in the top layer
Layer.renderDebug(this.add.graphics(), { Layer.renderDebug(this.add.graphics(), {
@ -1131,7 +1131,7 @@ ${escapedMessage}
}); });
} }
createCurrentPlayer(){ createCurrentPlayer() {
//TODO create animation moving between exit and start //TODO create animation moving between exit and start
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.characterLayers); const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.characterLayers);
try { try {
@ -1147,8 +1147,8 @@ ${escapedMessage}
this.companion, this.companion,
this.companion !== null ? lazyLoadCompanionResource(this.load, this.companion) : undefined this.companion !== null ? lazyLoadCompanionResource(this.load, this.companion) : undefined
); );
}catch (err){ } catch (err) {
if(err instanceof TextureError) { if (err instanceof TextureError) {
gameManager.leaveGame(this, SelectCharacterSceneName, new SelectCharacterScene()); gameManager.leaveGame(this, SelectCharacterSceneName, new SelectCharacterScene());
} }
throw err; throw err;
@ -1209,7 +1209,7 @@ ${escapedMessage}
} }
let shortestDistance: number = Infinity; let shortestDistance: number = Infinity;
let selectedItem: ActionableItem|null = null; let selectedItem: ActionableItem | null = null;
for (const item of this.actionableItems.values()) { for (const item of this.actionableItems.values()) {
const distance = item.actionableDistance(x, y); const distance = item.actionableDistance(x, y);
if (distance !== null && distance < shortestDistance) { if (distance !== null && distance < shortestDistance) {
@ -1243,7 +1243,7 @@ ${escapedMessage}
* @param time * @param time
* @param delta The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate. * @param delta The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate.
*/ */
update(time: number, delta: number) : void { update(time: number, delta: number): void {
this.dirty = false; this.dirty = false;
mediaManager.updateScene(); mediaManager.updateScene();
this.currentTick = time; this.currentTick = time;
@ -1313,8 +1313,8 @@ ${escapedMessage}
const currentPlayerId = this.connection?.getUserId(); const currentPlayerId = this.connection?.getUserId();
this.removeAllRemotePlayers(); this.removeAllRemotePlayers();
// load map // load map
usersPosition.forEach((userPosition : MessageUserPositionInterface) => { usersPosition.forEach((userPosition: MessageUserPositionInterface) => {
if(userPosition.userId === currentPlayerId){ if (userPosition.userId === currentPlayerId) {
return; return;
} }
this.addPlayer(userPosition); this.addPlayer(userPosition);
@ -1324,16 +1324,16 @@ ${escapedMessage}
/** /**
* Called by the connexion when a new player arrives on a map * Called by the connexion when a new player arrives on a map
*/ */
public addPlayer(addPlayerData : AddPlayerInterface) : void { public addPlayer(addPlayerData: AddPlayerInterface): void {
this.pendingEvents.enqueue({ this.pendingEvents.enqueue({
type: "AddPlayerEvent", type: "AddPlayerEvent",
event: addPlayerData event: addPlayerData
}); });
} }
private doAddPlayer(addPlayerData : AddPlayerInterface): void { private doAddPlayer(addPlayerData: AddPlayerInterface): void {
//check if exist player, if exist, move position //check if exist player, if exist, move position
if(this.MapPlayersByKey.has(addPlayerData.userId)){ if (this.MapPlayersByKey.has(addPlayerData.userId)) {
this.updatePlayerPosition({ this.updatePlayerPosition({
userId: addPlayerData.userId, userId: addPlayerData.userId,
position: addPlayerData.position position: addPlayerData.position
@ -1394,10 +1394,10 @@ ${escapedMessage}
} }
private doUpdatePlayerPosition(message: MessageUserMovedInterface): void { private doUpdatePlayerPosition(message: MessageUserMovedInterface): void {
const player : RemotePlayer | undefined = this.MapPlayersByKey.get(message.userId); const player: RemotePlayer | undefined = this.MapPlayersByKey.get(message.userId);
if (player === undefined) { if (player === undefined) {
//throw new Error('Cannot find player with ID "' + message.userId +'"'); //throw new Error('Cannot find player with ID "' + message.userId +'"');
console.error('Cannot update position of player with ID "' + message.userId +'": player not found'); console.error('Cannot update position of player with ID "' + message.userId + '": player not found');
return; return;
} }
@ -1441,7 +1441,7 @@ ${escapedMessage}
doDeleteGroup(groupId: number): void { doDeleteGroup(groupId: number): void {
const group = this.groups.get(groupId); const group = this.groups.get(groupId);
if(!group){ if (!group) {
return; return;
} }
group.destroy(); group.destroy();
@ -1470,7 +1470,7 @@ ${escapedMessage}
bottom: camera.scrollY + camera.height, bottom: camera.scrollY + camera.height,
}); });
} }
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') {
for (const object of layer.objects) { for (const object of layer.objects) {
@ -1504,7 +1504,7 @@ ${escapedMessage}
const game = HtmlUtils.querySelectorOrFail<HTMLCanvasElement>('#game canvas'); const game = HtmlUtils.querySelectorOrFail<HTMLCanvasElement>('#game canvas');
// Let's put this in Game coordinates by applying the zoom level: // Let's put this in Game coordinates by applying the zoom level:
this.cameras.main.setFollowOffset((xCenter - game.offsetWidth/2) * window.devicePixelRatio / this.scale.zoom , (yCenter - game.offsetHeight/2) * window.devicePixelRatio / this.scale.zoom); this.cameras.main.setFollowOffset((xCenter - game.offsetWidth / 2) * window.devicePixelRatio / this.scale.zoom, (yCenter - game.offsetHeight / 2) * window.devicePixelRatio / this.scale.zoom);
} }
public onCenterChange(): void { public onCenterChange(): void {
@ -1513,16 +1513,16 @@ ${escapedMessage}
public startJitsi(roomName: string, jwt?: string): void { public startJitsi(roomName: string, jwt?: string): void {
const allProps = this.gameMap.getCurrentProperties(); const allProps = this.gameMap.getCurrentProperties();
const jitsiConfig = this.safeParseJSONstring(allProps.get("jitsiConfig") as string|undefined, 'jitsiConfig'); const jitsiConfig = this.safeParseJSONstring(allProps.get("jitsiConfig") as string | undefined, 'jitsiConfig');
const jitsiInterfaceConfig = this.safeParseJSONstring(allProps.get("jitsiInterfaceConfig") as string|undefined, 'jitsiInterfaceConfig'); const jitsiInterfaceConfig = this.safeParseJSONstring(allProps.get("jitsiInterfaceConfig") as string | undefined, 'jitsiInterfaceConfig');
const jitsiUrl = allProps.get("jitsiUrl") as string|undefined; const jitsiUrl = allProps.get("jitsiUrl") as string | undefined;
jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl); jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl);
this.connection?.setSilent(true); this.connection?.setSilent(true);
mediaManager.hideGameOverlay(); mediaManager.hideGameOverlay();
//permit to stop jitsi when user close iframe //permit to stop jitsi when user close iframe
mediaManager.addTriggerCloseJitsiFrameButton('close-jisi',() => { mediaManager.addTriggerCloseJitsiFrameButton('close-jisi', () => {
this.stopJitsi(); this.stopJitsi();
}); });
} }
@ -1536,7 +1536,7 @@ ${escapedMessage}
} }
//todo: put this into an 'orchestrator' scene (EntryScene?) //todo: put this into an 'orchestrator' scene (EntryScene?)
private bannedUser(){ private bannedUser() {
this.cleanupClosingScene(); this.cleanupClosingScene();
this.userInputManager.disableControls(); this.userInputManager.disableControls();
this.scene.start(ErrorSceneName, { this.scene.start(ErrorSceneName, {
@ -1547,22 +1547,22 @@ ${escapedMessage}
} }
//todo: put this into an 'orchestrator' scene (EntryScene?) //todo: put this into an 'orchestrator' scene (EntryScene?)
private showWorldFullError(message: string|null): void { private showWorldFullError(message: string | null): void {
this.cleanupClosingScene(); this.cleanupClosingScene();
this.scene.stop(ReconnectingSceneName); this.scene.stop(ReconnectingSceneName);
this.scene.remove(ReconnectingSceneName); this.scene.remove(ReconnectingSceneName);
this.userInputManager.disableControls(); this.userInputManager.disableControls();
//FIX ME to use status code //FIX ME to use status code
if(message == undefined){ if (message == undefined) {
this.scene.start(ErrorSceneName, { this.scene.start(ErrorSceneName, {
title: 'Connection rejected', title: 'Connection rejected',
subTitle: 'The world you are trying to join is full. Try again later.', subTitle: 'The world you are trying to join is full. Try again later.',
message: 'If you want more information, you may contact us at: workadventure@thecodingmachine.com' message: 'If you want more information, you may contact us at: workadventure@thecodingmachine.com'
}); });
}else{ } else {
this.scene.start(ErrorSceneName, { this.scene.start(ErrorSceneName, {
title: 'Connection rejected', title: 'Connection rejected',
subTitle: 'You cannot join the World. Try again later. \n\r \n\r Error: '+message+'.', subTitle: 'You cannot join the World. Try again later. \n\r \n\r Error: ' + message + '.',
message: 'If you want more information, you may contact administrator or contact us at: workadventure@thecodingmachine.com' message: 'If you want more information, you may contact administrator or contact us at: workadventure@thecodingmachine.com'
}); });
} }

View File

@ -1,14 +1,14 @@
import {DivImportance, layoutManager} from "./LayoutManager"; import { DivImportance, layoutManager } from "./LayoutManager";
import {HtmlUtils} from "./HtmlUtils"; import { HtmlUtils } from "./HtmlUtils";
import {discussionManager, SendMessageCallback} from "./DiscussionManager"; import { discussionManager, SendMessageCallback } from "./DiscussionManager";
import {UserInputManager} from "../Phaser/UserInput/UserInputManager"; import { UserInputManager } from "../Phaser/UserInput/UserInputManager";
import {localUserStore} from "../Connexion/LocalUserStore"; import { localUserStore } from "../Connexion/LocalUserStore";
import {UserSimplePeerInterface} from "./SimplePeer"; import { UserSimplePeerInterface } from "./SimplePeer";
import {SoundMeter} from "../Phaser/Components/SoundMeter"; import { SoundMeter } from "../Phaser/Components/SoundMeter";
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any declare const navigator: any; // eslint-disable-line @typescript-eslint/no-explicit-any
let videoConstraint: boolean|MediaTrackConstraints = { let videoConstraint: boolean | MediaTrackConstraints = {
width: { min: 640, ideal: 1280, max: 1920 }, width: { min: 640, ideal: 1280, max: 1920 },
height: { min: 400, ideal: 720 }, height: { min: 400, ideal: 720 },
frameRate: { ideal: localUserStore.getVideoQualityValue() }, frameRate: { ideal: localUserStore.getVideoQualityValue() },
@ -16,24 +16,24 @@ let videoConstraint: boolean|MediaTrackConstraints = {
resizeMode: 'crop-and-scale', resizeMode: 'crop-and-scale',
aspectRatio: 1.777777778 aspectRatio: 1.777777778
}; };
const audioConstraint: boolean|MediaTrackConstraints = { const audioConstraint: boolean | MediaTrackConstraints = {
//TODO: make these values configurable in the game settings menu and store them in localstorage //TODO: make these values configurable in the game settings menu and store them in localstorage
autoGainControl: false, autoGainControl: false,
echoCancellation: true, echoCancellation: true,
noiseSuppression: false noiseSuppression: false
}; };
export type UpdatedLocalStreamCallback = (media: MediaStream|null) => void; export type UpdatedLocalStreamCallback = (media: MediaStream | null) => void;
export type StartScreenSharingCallback = (media: MediaStream) => void; export type StartScreenSharingCallback = (media: MediaStream) => void;
export type StopScreenSharingCallback = (media: MediaStream) => void; export type StopScreenSharingCallback = (media: MediaStream) => void;
export type ReportCallback = (message: string) => void; export type ReportCallback = (message: string) => void;
export type ShowReportCallBack = (userId: string, userName: string|undefined) => void; export type ShowReportCallBack = (userId: string, userName: string | undefined) => void;
export type HelpCameraSettingsCallBack = () => void; export type HelpCameraSettingsCallBack = () => void;
// TODO: Split MediaManager in 2 classes: MediaManagerUI (in charge of HTML) and MediaManager (singleton in charge of the camera only) // TODO: Split MediaManager in 2 classes: MediaManagerUI (in charge of HTML) and MediaManager (singleton in charge of the camera only)
export class MediaManager { export class MediaManager {
localStream: MediaStream|null = null; localStream: MediaStream | null = null;
localScreenCapture: MediaStream|null = null; localScreenCapture: MediaStream | null = null;
private remoteVideo: Map<string, HTMLVideoElement> = new Map<string, HTMLVideoElement>(); private remoteVideo: Map<string, HTMLVideoElement> = new Map<string, HTMLVideoElement>();
myCamVideo: HTMLVideoElement; myCamVideo: HTMLVideoElement;
cinemaClose: HTMLImageElement; cinemaClose: HTMLImageElement;
@ -43,35 +43,35 @@ export class MediaManager {
microphoneClose: HTMLImageElement; microphoneClose: HTMLImageElement;
microphone: HTMLImageElement; microphone: HTMLImageElement;
webrtcInAudio: HTMLAudioElement; webrtcInAudio: HTMLAudioElement;
mySoundMeterElement: HTMLDivElement; // mySoundMeterElement: HTMLDivElement;
private webrtcOutAudio: HTMLAudioElement; private webrtcOutAudio: HTMLAudioElement;
constraintsMedia : MediaStreamConstraints = { constraintsMedia: MediaStreamConstraints = {
audio: audioConstraint, audio: audioConstraint,
video: videoConstraint video: videoConstraint
}; };
updatedLocalStreamCallBacks : Set<UpdatedLocalStreamCallback> = new Set<UpdatedLocalStreamCallback>(); updatedLocalStreamCallBacks: Set<UpdatedLocalStreamCallback> = new Set<UpdatedLocalStreamCallback>();
startScreenSharingCallBacks : Set<StartScreenSharingCallback> = new Set<StartScreenSharingCallback>(); startScreenSharingCallBacks: Set<StartScreenSharingCallback> = new Set<StartScreenSharingCallback>();
stopScreenSharingCallBacks : Set<StopScreenSharingCallback> = new Set<StopScreenSharingCallback>(); stopScreenSharingCallBacks: Set<StopScreenSharingCallback> = new Set<StopScreenSharingCallback>();
showReportModalCallBacks : Set<ShowReportCallBack> = new Set<ShowReportCallBack>(); showReportModalCallBacks: Set<ShowReportCallBack> = new Set<ShowReportCallBack>();
helpCameraSettingsCallBacks : Set<HelpCameraSettingsCallBack> = new Set<HelpCameraSettingsCallBack>(); helpCameraSettingsCallBacks: Set<HelpCameraSettingsCallBack> = new Set<HelpCameraSettingsCallBack>();
private microphoneBtn: HTMLDivElement; private microphoneBtn: HTMLDivElement;
private cinemaBtn: HTMLDivElement; private cinemaBtn: HTMLDivElement;
private monitorBtn: HTMLDivElement; private monitorBtn: HTMLDivElement;
private previousConstraint : MediaStreamConstraints; private previousConstraint: MediaStreamConstraints;
private focused : boolean = true; private focused: boolean = true;
private lastUpdateScene : Date = new Date(); private lastUpdateScene: Date = new Date();
private setTimeOutlastUpdateScene? : NodeJS.Timeout; private setTimeOutlastUpdateScene?: NodeJS.Timeout;
private hasCamera = true; private hasCamera = true;
private triggerCloseJistiFrame : Map<String, Function> = new Map<String, Function>(); private triggerCloseJistiFrame: Map<String, Function> = new Map<String, Function>();
private userInputManager?: UserInputManager; private userInputManager?: UserInputManager;
private mySoundMeter?: SoundMeter|null; private mySoundMeter?: SoundMeter | null;
private soundMeters: Map<string, SoundMeter> = new Map<string, SoundMeter>(); private soundMeters: Map<string, SoundMeter> = new Map<string, SoundMeter>();
private soundMeterElements: Map<string, HTMLDivElement> = new Map<string, HTMLDivElement>(); private soundMeterElements: Map<string, HTMLDivElement> = new Map<string, HTMLDivElement>();
@ -134,19 +134,19 @@ export class MediaManager {
this.checkActiveUser(); //todo: desactivated in case of bug this.checkActiveUser(); //todo: desactivated in case of bug
this.mySoundMeterElement = (HtmlUtils.getElementByIdOrFail('mySoundMeter')); /* this.mySoundMeterElement = (HtmlUtils.getElementByIdOrFail('mySoundMeter'));
this.mySoundMeterElement.childNodes.forEach((value: ChildNode, index) => { this.mySoundMeterElement.childNodes.forEach((value: ChildNode, index) => {
this.mySoundMeterElement.children.item(index)?.classList.remove('active'); this.mySoundMeterElement.children.item(index)?.classList.remove('active');
}); });*/
} }
public updateScene(){ public updateScene() {
this.lastUpdateScene = new Date(); this.lastUpdateScene = new Date();
this.updateSoudMeter(); this.updateSoudMeter();
} }
public blurCamera() { public blurCamera() {
if(!this.focused){ if (!this.focused) {
return; return;
} }
this.focused = false; this.focused = false;
@ -155,7 +155,7 @@ export class MediaManager {
} }
public focusCamera() { public focusCamera() {
if(this.focused){ if (this.focused) {
return; return;
} }
this.focused = true; this.focused = true;
@ -178,7 +178,7 @@ export class MediaManager {
this.updatedLocalStreamCallBacks.delete(callback); this.updatedLocalStreamCallBacks.delete(callback);
} }
private triggerUpdatedLocalStreamCallbacks(stream: MediaStream|null): void { private triggerUpdatedLocalStreamCallbacks(stream: MediaStream | null): void {
for (const callback of this.updatedLocalStreamCallBacks) { for (const callback of this.updatedLocalStreamCallBacks) {
callback(stream); callback(stream);
} }
@ -196,7 +196,7 @@ export class MediaManager {
} }
} }
public showGameOverlay(){ public showGameOverlay() {
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay'); const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
gameOverlay.classList.add('active'); gameOverlay.classList.add('active');
@ -207,7 +207,7 @@ export class MediaManager {
buttonCloseFrame.removeEventListener('click', functionTrigger); buttonCloseFrame.removeEventListener('click', functionTrigger);
} }
public hideGameOverlay(){ public hideGameOverlay() {
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay'); const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
gameOverlay.classList.remove('active'); gameOverlay.classList.remove('active');
@ -221,7 +221,7 @@ export class MediaManager {
public updateCameraQuality(value: number) { public updateCameraQuality(value: number) {
this.enableCameraStyle(); this.enableCameraStyle();
const newVideoConstraint = JSON.parse(JSON.stringify(videoConstraint)); const newVideoConstraint = JSON.parse(JSON.stringify(videoConstraint));
newVideoConstraint.frameRate = {exact: value, ideal: value}; newVideoConstraint.frameRate = { exact: value, ideal: value };
videoConstraint = newVideoConstraint; videoConstraint = newVideoConstraint;
this.constraintsMedia.video = videoConstraint; this.constraintsMedia.video = videoConstraint;
this.getCamera().then((stream: MediaStream) => { this.getCamera().then((stream: MediaStream) => {
@ -235,7 +235,7 @@ export class MediaManager {
this.getCamera().then((stream: MediaStream) => { this.getCamera().then((stream: MediaStream) => {
//TODO show error message tooltip upper of camera button //TODO show error message tooltip upper of camera button
//TODO message : please check camera permission of your navigator //TODO message : please check camera permission of your navigator
if(stream.getVideoTracks().length === 0) { if (stream.getVideoTracks().length === 0) {
throw Error('Video track is empty, please check camera permission of your navigator') throw Error('Video track is empty, please check camera permission of your navigator')
} }
this.enableCameraStyle(); this.enableCameraStyle();
@ -267,7 +267,7 @@ export class MediaManager {
this.getCamera().then((stream) => { this.getCamera().then((stream) => {
//TODO show error message tooltip upper of camera button //TODO show error message tooltip upper of camera button
//TODO message : please check microphone permission of your navigator //TODO message : please check microphone permission of your navigator
if(stream.getAudioTracks().length === 0) { if (stream.getAudioTracks().length === 0) {
throw Error('Audio track is empty, please check microphone permission of your navigator') throw Error('Audio track is empty, please check microphone permission of your navigator')
} }
this.enableMicrophoneStyle(); this.enableMicrophoneStyle();
@ -296,14 +296,14 @@ export class MediaManager {
private applyPreviousConfig() { private applyPreviousConfig() {
this.constraintsMedia = this.previousConstraint; this.constraintsMedia = this.previousConstraint;
if(!this.constraintsMedia.video){ if (!this.constraintsMedia.video) {
this.disableCameraStyle(); this.disableCameraStyle();
}else{ } else {
this.enableCameraStyle(); this.enableCameraStyle();
} }
if(!this.constraintsMedia.audio){ if (!this.constraintsMedia.audio) {
this.disableMicrophoneStyle() this.disableMicrophoneStyle()
}else{ } else {
this.enableMicrophoneStyle() this.enableMicrophoneStyle()
} }
@ -312,13 +312,13 @@ export class MediaManager {
}); });
} }
private enableCameraStyle(){ private enableCameraStyle() {
this.cinemaClose.style.display = "none"; this.cinemaClose.style.display = "none";
this.cinemaBtn.classList.remove("disabled"); this.cinemaBtn.classList.remove("disabled");
this.cinema.style.display = "block"; this.cinema.style.display = "block";
} }
private disableCameraStyle(){ private disableCameraStyle() {
this.cinemaClose.style.display = "block"; this.cinemaClose.style.display = "block";
this.cinema.style.display = "none"; this.cinema.style.display = "none";
this.cinemaBtn.classList.add("disabled"); this.cinemaBtn.classList.add("disabled");
@ -327,13 +327,13 @@ export class MediaManager {
this.stopCamera(); this.stopCamera();
} }
private enableMicrophoneStyle(){ private enableMicrophoneStyle() {
this.microphoneClose.style.display = "none"; this.microphoneClose.style.display = "none";
this.microphone.style.display = "block"; this.microphone.style.display = "block";
this.microphoneBtn.classList.remove("disabled"); this.microphoneBtn.classList.remove("disabled");
} }
private disableMicrophoneStyle(){ private disableMicrophoneStyle() {
this.microphoneClose.style.display = "block"; this.microphoneClose.style.display = "block";
this.microphone.style.display = "none"; this.microphone.style.display = "none";
this.microphoneBtn.classList.add("disabled"); this.microphoneBtn.classList.add("disabled");
@ -381,7 +381,7 @@ export class MediaManager {
} }
//get screen //get screen
getScreenMedia() : Promise<MediaStream>{ getScreenMedia(): Promise<MediaStream> {
try { try {
return this._startScreenCapture() return this._startScreenCapture()
.then((stream: MediaStream) => { .then((stream: MediaStream) => {
@ -403,7 +403,7 @@ export class MediaManager {
console.error("Error => getScreenMedia => ", err); console.error("Error => getScreenMedia => ", err);
throw err; throw err;
}); });
}catch (err) { } catch (err) {
return new Promise((resolve, reject) => { // eslint-disable-line no-unused-vars return new Promise((resolve, reject) => { // eslint-disable-line no-unused-vars
reject(err); reject(err);
}); });
@ -412,9 +412,9 @@ export class MediaManager {
private _startScreenCapture() { private _startScreenCapture() {
if (navigator.getDisplayMedia) { if (navigator.getDisplayMedia) {
return navigator.getDisplayMedia({video: true}); return navigator.getDisplayMedia({ video: true });
} else if (navigator.mediaDevices.getDisplayMedia) { } else if (navigator.mediaDevices.getDisplayMedia) {
return navigator.mediaDevices.getDisplayMedia({video: true}); return navigator.mediaDevices.getDisplayMedia({ video: true });
} else { } else {
return new Promise((resolve, reject) => { // eslint-disable-line no-unused-vars return new Promise((resolve, reject) => { // eslint-disable-line no-unused-vars
reject("error sharing screen"); reject("error sharing screen");
@ -435,7 +435,7 @@ export class MediaManager {
return this.getLocalStream().catch((err) => { return this.getLocalStream().catch((err) => {
console.info('Error get camera, trying with video option at null =>', err); console.info('Error get camera, trying with video option at null =>', err);
this.disableCameraStyle(); this.disableCameraStyle();
return this.getLocalStream().then((stream : MediaStream) => { return this.getLocalStream().then((stream: MediaStream) => {
this.hasCamera = false; this.hasCamera = false;
return stream; return stream;
}).catch((err) => { }).catch((err) => {
@ -452,14 +452,14 @@ export class MediaManager {
console.info(`${width}x${height}`); // 6*/ console.info(`${width}x${height}`); // 6*/
} }
private getLocalStream() : Promise<MediaStream> { private getLocalStream(): Promise<MediaStream> {
return navigator.mediaDevices.getUserMedia(this.constraintsMedia).then((stream : MediaStream) => { return navigator.mediaDevices.getUserMedia(this.constraintsMedia).then((stream: MediaStream) => {
this.localStream = stream; this.localStream = stream;
this.myCamVideo.srcObject = this.localStream; this.myCamVideo.srcObject = this.localStream;
//init sound meter //init sound meter
this.mySoundMeter = null; this.mySoundMeter = null;
if(this.constraintsMedia.audio){ if (this.constraintsMedia.audio) {
this.mySoundMeter = new SoundMeter(); this.mySoundMeter = new SoundMeter();
this.mySoundMeter.connectToSource(stream, new AudioContext()); this.mySoundMeter.connectToSource(stream, new AudioContext());
} }
@ -494,7 +494,7 @@ export class MediaManager {
setCamera(id: string): Promise<MediaStream> { setCamera(id: string): Promise<MediaStream> {
let video = this.constraintsMedia.video; let video = this.constraintsMedia.video;
if (typeof(video) === 'boolean' || video === undefined) { if (typeof (video) === 'boolean' || video === undefined) {
video = {} video = {}
} }
video.deviceId = { video.deviceId = {
@ -506,7 +506,7 @@ export class MediaManager {
setMicrophone(id: string): Promise<MediaStream> { setMicrophone(id: string): Promise<MediaStream> {
let audio = this.constraintsMedia.audio; let audio = this.constraintsMedia.audio;
if (typeof(audio) === 'boolean' || audio === undefined) { if (typeof (audio) === 'boolean' || audio === undefined) {
audio = {} audio = {}
} }
audio.deviceId = { audio.deviceId = {
@ -516,9 +516,9 @@ export class MediaManager {
return this.getCamera(); return this.getCamera();
} }
addActiveVideo(user: UserSimplePeerInterface, userName: string = ""){ addActiveVideo(user: UserSimplePeerInterface, userName: string = "") {
this.webrtcInAudio.play(); this.webrtcInAudio.play();
const userId = ''+user.userId const userId = '' + user.userId
userName = userName.toUpperCase(); userName = userName.toUpperCase();
const color = this.getColorByString(userName); const color = this.getColorByString(userName);
@ -551,7 +551,7 @@ export class MediaManager {
//permit to create participant in discussion part //permit to create participant in discussion part
const showReportUser = () => { const showReportUser = () => {
for(const callBack of this.showReportModalCallBacks){ for (const callBack of this.showReportModalCallBacks) {
callBack(userId, userName); callBack(userId, userName);
} }
}; };
@ -565,7 +565,7 @@ export class MediaManager {
}); });
} }
addScreenSharingActiveVideo(userId: string, divImportance: DivImportance = DivImportance.Important){ addScreenSharingActiveVideo(userId: string, divImportance: DivImportance = DivImportance.Important) {
userId = this.getScreenSharingId(userId); userId = this.getScreenSharingId(userId);
const html = ` const html = `
@ -583,17 +583,17 @@ export class MediaManager {
return `screen-sharing-${userId}`; return `screen-sharing-${userId}`;
} }
disabledMicrophoneByUserId(userId: number){ disabledMicrophoneByUserId(userId: number) {
const element = document.getElementById(`microphone-${userId}`); const element = document.getElementById(`microphone-${userId}`);
if(!element){ if (!element) {
return; return;
} }
element.classList.add('active') //todo: why does a method 'disable' add a class 'active'? element.classList.add('active') //todo: why does a method 'disable' add a class 'active'?
} }
enabledMicrophoneByUserId(userId: number){ enabledMicrophoneByUserId(userId: number) {
const element = document.getElementById(`microphone-${userId}`); const element = document.getElementById(`microphone-${userId}`);
if(!element){ if (!element) {
return; return;
} }
element.classList.remove('active') //todo: why does a method 'enable' remove a class 'active'? element.classList.remove('active') //todo: why does a method 'enable' remove a class 'active'?
@ -610,22 +610,22 @@ export class MediaManager {
} }
} }
enabledVideoByUserId(userId: number){ enabledVideoByUserId(userId: number) {
let element = document.getElementById(`${userId}`); let element = document.getElementById(`${userId}`);
if(element){ if (element) {
element.style.opacity = "1"; element.style.opacity = "1";
} }
element = document.getElementById(`name-${userId}`); element = document.getElementById(`name-${userId}`);
if(element){ if (element) {
element.style.display = "none"; element.style.display = "none";
} }
} }
toggleBlockLogo(userId: number, show: boolean): void { toggleBlockLogo(userId: number, show: boolean): void {
const blockLogoElement = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('blocking-'+userId); const blockLogoElement = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('blocking-' + userId);
show ? blockLogoElement.classList.add('active') : blockLogoElement.classList.remove('active'); show ? blockLogoElement.classList.add('active') : blockLogoElement.classList.remove('active');
} }
addStreamRemoteVideo(userId: string, stream : MediaStream): void { addStreamRemoteVideo(userId: string, stream: MediaStream): void {
const remoteVideo = this.remoteVideo.get(userId); const remoteVideo = this.remoteVideo.get(userId);
if (remoteVideo === undefined) { if (remoteVideo === undefined) {
throw `Unable to find video for ${userId}`; throw `Unable to find video for ${userId}`;
@ -636,9 +636,9 @@ export class MediaManager {
const soundMeter = new SoundMeter(); const soundMeter = new SoundMeter();
soundMeter.connectToSource(stream, new AudioContext()); soundMeter.connectToSource(stream, new AudioContext());
this.soundMeters.set(userId, soundMeter); this.soundMeters.set(userId, soundMeter);
this.soundMeterElements.set(userId, HtmlUtils.getElementByIdOrFail<HTMLImageElement>('soundMeter-'+userId)); this.soundMeterElements.set(userId, HtmlUtils.getElementByIdOrFail<HTMLImageElement>('soundMeter-' + userId));
} }
addStreamRemoteScreenSharing(userId: string, stream : MediaStream){ addStreamRemoteScreenSharing(userId: string, stream: MediaStream) {
// In the case of screen sharing (going both ways), we may need to create the HTML element if it does not exist yet // In the case of screen sharing (going both ways), we may need to create the HTML element if it does not exist yet
const remoteVideo = this.remoteVideo.get(this.getScreenSharingId(userId)); const remoteVideo = this.remoteVideo.get(this.getScreenSharingId(userId));
if (remoteVideo === undefined) { if (remoteVideo === undefined) {
@ -648,7 +648,7 @@ export class MediaManager {
this.addStreamRemoteVideo(this.getScreenSharingId(userId), stream); this.addStreamRemoteVideo(this.getScreenSharingId(userId), stream);
} }
removeActiveVideo(userId: string){ removeActiveVideo(userId: string) {
layoutManager.remove(userId); layoutManager.remove(userId);
this.remoteVideo.delete(userId); this.remoteVideo.delete(userId);
@ -686,10 +686,10 @@ export class MediaManager {
isError(userId: string): void { isError(userId: string): void {
console.info("isError", `div-${userId}`); console.info("isError", `div-${userId}`);
const element = document.getElementById(`div-${userId}`); const element = document.getElementById(`div-${userId}`);
if(!element){ if (!element) {
return; return;
} }
const errorDiv = element.getElementsByClassName('rtc-error').item(0) as HTMLDivElement|null; const errorDiv = element.getElementsByClassName('rtc-error').item(0) as HTMLDivElement | null;
if (errorDiv === null) { if (errorDiv === null) {
return; return;
} }
@ -700,16 +700,16 @@ export class MediaManager {
} }
private getSpinner(userId: string): HTMLDivElement|null { private getSpinner(userId: string): HTMLDivElement | null {
const element = document.getElementById(`div-${userId}`); const element = document.getElementById(`div-${userId}`);
if(!element){ if (!element) {
return null; return null;
} }
const connnectingSpinnerDiv = element.getElementsByClassName('connecting-spinner').item(0) as HTMLDivElement|null; const connnectingSpinnerDiv = element.getElementsByClassName('connecting-spinner').item(0) as HTMLDivElement | null;
return connnectingSpinnerDiv; return connnectingSpinnerDiv;
} }
private getColorByString(str: String) : String|null { private getColorByString(str: String): String | null {
let hash = 0; let hash = 0;
if (str.length === 0) return null; if (str.length === 0) return null;
for (let i = 0; i < str.length; i++) { for (let i = 0; i < str.length; i++) {
@ -724,18 +724,18 @@ export class MediaManager {
return color; return color;
} }
public addNewParticipant(userId: number|string, name: string|undefined, img?: string, showReportUserCallBack?: ShowReportCallBack){ public addNewParticipant(userId: number | string, name: string | undefined, img?: string, showReportUserCallBack?: ShowReportCallBack) {
discussionManager.addParticipant(userId, name, img, false, showReportUserCallBack); discussionManager.addParticipant(userId, name, img, false, showReportUserCallBack);
} }
public removeParticipant(userId: number|string){ public removeParticipant(userId: number | string) {
discussionManager.removeParticipant(userId); discussionManager.removeParticipant(userId);
} }
public addTriggerCloseJitsiFrameButton(id: String, Function: Function){ public addTriggerCloseJitsiFrameButton(id: String, Function: Function) {
this.triggerCloseJistiFrame.set(id, Function); this.triggerCloseJistiFrame.set(id, Function);
} }
public removeTriggerCloseJitsiFrameButton(id: String){ public removeTriggerCloseJitsiFrameButton(id: String) {
this.triggerCloseJistiFrame.delete(id); this.triggerCloseJistiFrame.delete(id);
} }
@ -748,100 +748,100 @@ export class MediaManager {
* For some reasons, the microphone muted icon or the stream is not always up to date. * For some reasons, the microphone muted icon or the stream is not always up to date.
* Here, every 30 seconds, we are "reseting" the streams and sending again the constraints to the other peers via the data channel again (see SimplePeer::pushVideoToRemoteUser) * Here, every 30 seconds, we are "reseting" the streams and sending again the constraints to the other peers via the data channel again (see SimplePeer::pushVideoToRemoteUser)
**/ **/
private pingCameraStatus(){ private pingCameraStatus() {
/*setInterval(() => { /*setInterval(() => {
console.log('ping camera status'); console.log('ping camera status');
this.triggerUpdatedLocalStreamCallbacks(this.localStream); this.triggerUpdatedLocalStreamCallbacks(this.localStream);
}, 30000);*/ }, 30000);*/
} }
public addNewMessage(name: string, message: string, isMe: boolean = false){ public addNewMessage(name: string, message: string, isMe: boolean = false) {
discussionManager.addMessage(name, message, isMe); discussionManager.addMessage(name, message, isMe);
//when there are new message, show discussion //when there are new message, show discussion
if(!discussionManager.activatedDiscussion) { if (!discussionManager.activatedDiscussion) {
discussionManager.showDiscussionPart(); discussionManager.showDiscussionPart();
} }
} }
public addSendMessageCallback(userId: string|number, callback: SendMessageCallback){ public addSendMessageCallback(userId: string | number, callback: SendMessageCallback) {
discussionManager.onSendMessageCallback(userId, callback); discussionManager.onSendMessageCallback(userId, callback);
} }
get activatedDiscussion(){ get activatedDiscussion() {
return discussionManager.activatedDiscussion; return discussionManager.activatedDiscussion;
} }
public setUserInputManager(userInputManager : UserInputManager){ public setUserInputManager(userInputManager: UserInputManager) {
this.userInputManager = userInputManager; this.userInputManager = userInputManager;
discussionManager.setUserInputManager(userInputManager); discussionManager.setUserInputManager(userInputManager);
} }
//check if user is active //check if user is active
private checkActiveUser(){ private checkActiveUser() {
if(this.setTimeOutlastUpdateScene){ if (this.setTimeOutlastUpdateScene) {
clearTimeout(this.setTimeOutlastUpdateScene); clearTimeout(this.setTimeOutlastUpdateScene);
} }
this.setTimeOutlastUpdateScene = setTimeout(() => { this.setTimeOutlastUpdateScene = setTimeout(() => {
const now = new Date(); const now = new Date();
//if last update is more of 10 sec //if last update is more of 10 sec
if( (now.getTime() - this.lastUpdateScene.getTime()) > 10000) { if ((now.getTime() - this.lastUpdateScene.getTime()) > 10000) {
this.blurCamera(); this.blurCamera();
}else{ } else {
this.focusCamera(); this.focusCamera();
} }
this.checkActiveUser(); this.checkActiveUser();
}, this.focused ? 10000 : 1000); }, this.focused ? 10000 : 1000);
} }
public setShowReportModalCallBacks(callback: ShowReportCallBack){ public setShowReportModalCallBacks(callback: ShowReportCallBack) {
this.showReportModalCallBacks.add(callback); this.showReportModalCallBacks.add(callback);
} }
public setHelpCameraSettingsCallBack(callback: HelpCameraSettingsCallBack){ public setHelpCameraSettingsCallBack(callback: HelpCameraSettingsCallBack) {
this.helpCameraSettingsCallBacks.add(callback); this.helpCameraSettingsCallBacks.add(callback);
} }
private showHelpCameraSettingsCallBack(){ private showHelpCameraSettingsCallBack() {
for(const callBack of this.helpCameraSettingsCallBacks){ for (const callBack of this.helpCameraSettingsCallBacks) {
callBack(); callBack();
} }
} }
updateSoudMeter(){ updateSoudMeter() {
try{ try {
const volume = parseInt(((this.mySoundMeter ? this.mySoundMeter.getVolume() : 0) / 10).toFixed(0)); const volume = parseInt(((this.mySoundMeter ? this.mySoundMeter.getVolume() : 0) / 10).toFixed(0));
this.setVolumeSoundMeter(volume, this.mySoundMeterElement); //this.setVolumeSoundMeter(volume, this.mySoundMeterElement);
for(const indexUserId of this.soundMeters.keys()){ for (const indexUserId of this.soundMeters.keys()) {
const soundMeter = this.soundMeters.get(indexUserId); const soundMeter = this.soundMeters.get(indexUserId);
const soundMeterElement = this.soundMeterElements.get(indexUserId); const soundMeterElement = this.soundMeterElements.get(indexUserId);
if(!soundMeter || !soundMeterElement){ if (!soundMeter || !soundMeterElement) {
return; return;
} }
const volumeByUser = parseInt((soundMeter.getVolume() / 10).toFixed(0)); const volumeByUser = parseInt((soundMeter.getVolume() / 10).toFixed(0));
this.setVolumeSoundMeter(volumeByUser, soundMeterElement); this.setVolumeSoundMeter(volumeByUser, soundMeterElement);
} }
}catch(err){ } catch (err) {
//console.error(err); //console.error(err);
} }
} }
private setVolumeSoundMeter(volume: number, element: HTMLDivElement){ private setVolumeSoundMeter(volume: number, element: HTMLDivElement) {
if(volume <= 0 && !element.classList.contains('active')){ if (volume <= 0 && !element.classList.contains('active')) {
return; return;
} }
element.classList.remove('active'); element.classList.remove('active');
if(volume <= 0){ if (volume <= 0) {
return; return;
} }
element.classList.add('active'); element.classList.add('active');
element.childNodes.forEach((value: ChildNode, index) => { element.childNodes.forEach((value: ChildNode, index) => {
const elementChildre = element.children.item(index); const elementChildre = element.children.item(index);
if(!elementChildre){ if (!elementChildre) {
return; return;
} }
elementChildre.classList.remove('active'); elementChildre.classList.remove('active');
if((index +1) > volume){ if ((index + 1) > volume) {
return; return;
} }
elementChildre.classList.add('active'); elementChildre.classList.add('active');

View File

@ -22,8 +22,8 @@ interface WorkAdventureApi {
goToPage(url: string): void; goToPage(url: string): void;
openCoWebSite(url: string): void; openCoWebSite(url: string): void;
closeCoWebSite(): void; closeCoWebSite(): void;
disablePlayerControl(): void; disablePlayerControls(): void;
restorePlayerControl(): void; restorePlayerControls(): void;
displayBubble(): void; displayBubble(): void;
removeBubble(): void; removeBubble(): void;
getGameState(): Promise<GameStateEvent> getGameState(): Promise<GameStateEvent>
@ -133,12 +133,12 @@ window.WA = {
} as ChatEvent } as ChatEvent
}, '*'); }, '*');
}, },
disablePlayerControl(): void { disablePlayerControls(): void {
window.parent.postMessage({ 'type': 'disablePlayerControl' }, '*'); window.parent.postMessage({ 'type': 'disablePlayerControls' }, '*');
}, },
restorePlayerControl(): void { restorePlayerControls(): void {
window.parent.postMessage({ 'type': 'restorePlayerControl' }, '*'); window.parent.postMessage({ 'type': 'restorePlayerControls' }, '*');
}, },
displayBubble(): void { displayBubble(): void {

View File

@ -25,7 +25,7 @@ function launchTuto (){
label: "Got it!", label: "Got it!",
className : "success",callback:(popup2 => { className : "success",callback:(popup2 => {
popup2.close(); popup2.close();
WA.restorePlayerControl(); WA.restorePlayerControls();
}) })
} }
]) ])
@ -36,7 +36,7 @@ function launchTuto (){
} }
} }
]); ]);
WA.disablePlayerControl(); WA.disablePlayerControls();
} }

View File

@ -21,7 +21,7 @@ function launchTuto (){
callback: (popup1) => { callback: (popup1) => {
WA.sendChatMessage("Hey you can talk here too ! ", 'WA Guide'); WA.sendChatMessage("Hey you can talk here too ! ", 'WA Guide');
popup1.close(); popup1.close();
WA.restorePlayerControl(); WA.restorePlayerControls();
} }
} }
@ -29,7 +29,7 @@ function launchTuto (){
} }
} }
]); ]);
WA.disablePlayerControl(); WA.disablePlayerControls();
} }
WA.onChatMessage((message => { WA.onChatMessage((message => {

View File

@ -881,9 +881,9 @@ loader-utils@^2.0.0:
json5 "^2.1.2" json5 "^2.1.2"
lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19: lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19:
version "4.17.20" version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
loud-rejection@^1.0.0: loud-rejection@^1.0.0:
version "1.6.0" version "1.6.0"

View File

@ -2606,9 +2606,9 @@ lodash.templatesettings@^4.0.0:
lodash._reinterpolate "^3.0.0" lodash._reinterpolate "^3.0.0"
lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19: lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19:
version "4.17.20" version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
log-ok@^0.1.1: log-ok@^0.1.1:
version "0.1.1" version "0.1.1"

View File

@ -1105,9 +1105,9 @@ lodash.once@^4.0.0:
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19: lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19:
version "4.17.20" version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
loud-rejection@^1.0.0: loud-rejection@^1.0.0:
version "1.6.0" version "1.6.0"