Merge branch 'develop' of github.com:thecodingmachine/workadventure into menu-command-api
# Conflicts: # front/src/Api/Events/IframeEvent.ts # front/src/Api/IframeListener.ts # front/src/iframe_api.ts
This commit is contained in:
commit
127a4759ac
@ -10,6 +10,8 @@ START_ROOM_URL=/_/global/maps.workadventure.localhost/Floor0/floor0.json
|
||||
# If you are using Coturn, this is the value of the "static-auth-secret" parameter in your coturn config file.
|
||||
# Keep empty if you are sharing hard coded / clear text credentials.
|
||||
TURN_STATIC_AUTH_SECRET=
|
||||
DISABLE_NOTIFICATIONS=true
|
||||
SKIP_RENDER_OPTIMIZATIONS=false
|
||||
|
||||
# The email address used by Let's encrypt to send renewal warnings (compulsory)
|
||||
ACME_EMAIL=
|
||||
|
30
.github/workflows/build-and-deploy.yml
vendored
30
.github/workflows/build-and-deploy.yml
vendored
@ -1,6 +1,8 @@
|
||||
name: Build, push and deploy Docker image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
release:
|
||||
types: [created]
|
||||
pull_request:
|
||||
@ -14,7 +16,7 @@ env:
|
||||
jobs:
|
||||
|
||||
build-front:
|
||||
if: ${{ github.event.release || contains(github.event.pull_request.labels.*.name, 'deploy') }}
|
||||
if: ${{ github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@ -34,11 +36,11 @@ jobs:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
repository: thecodingmachine/workadventure-front
|
||||
tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
|
||||
tags: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
|
||||
add_git_labels: true
|
||||
|
||||
build-back:
|
||||
if: ${{ github.event.release || contains(github.event.pull_request.labels.*.name, 'deploy') }}
|
||||
if: ${{ github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@ -57,11 +59,11 @@ jobs:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
repository: thecodingmachine/workadventure-back
|
||||
tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
|
||||
tags: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
|
||||
add_git_labels: true
|
||||
|
||||
build-pusher:
|
||||
if: ${{ github.event.release || contains(github.event.pull_request.labels.*.name, 'deploy') }}
|
||||
if: ${{ github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@ -80,11 +82,11 @@ jobs:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
repository: thecodingmachine/workadventure-pusher
|
||||
tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
|
||||
tags: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
|
||||
add_git_labels: true
|
||||
|
||||
build-uploader:
|
||||
if: ${{ github.event.release || contains(github.event.pull_request.labels.*.name, 'deploy') }}
|
||||
if: ${{ github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@ -103,11 +105,11 @@ jobs:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
repository: thecodingmachine/workadventure-uploader
|
||||
tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
|
||||
tags: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
|
||||
add_git_labels: true
|
||||
|
||||
build-maps:
|
||||
if: ${{ github.event.release || contains(github.event.pull_request.labels.*.name, 'deploy') }}
|
||||
if: ${{ github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@ -127,7 +129,7 @@ jobs:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
repository: thecodingmachine/workadventure-maps
|
||||
tags: ${{ github.event.pull_request && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
|
||||
tags: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
|
||||
add_git_labels: true
|
||||
|
||||
deeploy:
|
||||
@ -138,7 +140,7 @@ jobs:
|
||||
- build-maps
|
||||
- build-uploader
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'deploy') }}
|
||||
if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'deploy') }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@ -156,14 +158,14 @@ jobs:
|
||||
JITSI_URL: ${{ secrets.JITSI_URL }}
|
||||
SECRET_JITSI_KEY: ${{ secrets.SECRET_JITSI_KEY }}
|
||||
TURN_STATIC_AUTH_SECRET: ${{ secrets.TURN_STATIC_AUTH_SECRET }}
|
||||
DEPLOY_REF: ${{ env.GITHUB_HEAD_REF_SLUG }}
|
||||
DEPLOY_REF: ${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
|
||||
with:
|
||||
namespace: workadventure-${{ env.GITHUB_HEAD_REF_SLUG }}
|
||||
namespace: workadventure-${{ github.event_name == 'pull_request' && env.GITHUB_HEAD_REF_SLUG || env.GITHUB_REF_SLUG }}
|
||||
|
||||
- name: Add a comment in PR
|
||||
uses: unsplash/comment-on-pr@v1.2.0
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
msg: Environment deployed at https://play.${{ env.GITHUB_HEAD_REF_SLUG }}.test.workadventu.re
|
||||
check_for_duplicate_msg: true
|
||||
|
2
.github/workflows/continuous_integration.yml
vendored
2
.github/workflows/continuous_integration.yml
vendored
@ -49,7 +49,7 @@ jobs:
|
||||
- name: "Build"
|
||||
run: yarn run build
|
||||
env:
|
||||
API_URL: "localhost:8080"
|
||||
PUSHER_URL: "//localhost:8080"
|
||||
working-directory: "front"
|
||||
|
||||
- name: "Lint"
|
||||
|
@ -1,5 +1,11 @@
|
||||
## 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
|
||||
|
||||
- Mobile support has been improved
|
||||
@ -8,6 +14,7 @@
|
||||
- Pinch support on mobile to zoom in / out
|
||||
- Improved virtual joystick size (adapts to the zoom level)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Pinch gesture does no longer move the character
|
||||
|
@ -1704,9 +1704,9 @@ lodash.once@^4.0.0:
|
||||
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
|
||||
|
||||
lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19:
|
||||
version "4.17.20"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
|
||||
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
||||
long@~3:
|
||||
version "3.2.0"
|
||||
|
@ -2,7 +2,7 @@
|
||||
local env = std.extVar("env"),
|
||||
local namespace = env.DEPLOY_REF,
|
||||
local tag = namespace,
|
||||
local url = if namespace == "master" then "workadventu.re" else namespace+".test.workadventu.re",
|
||||
local url = namespace+".test.workadventu.re",
|
||||
// develop branch does not use admin because of issue with SSL certificate of admin as of now.
|
||||
local adminUrl = if namespace == "master" || namespace == "develop" || std.startsWith(namespace, "admin") then "https://"+url else null,
|
||||
"$schema": "https://raw.githubusercontent.com/thecodingmachine/deeployer/master/deeployer.schema.json",
|
||||
@ -25,10 +25,7 @@
|
||||
"TURN_STATIC_AUTH_SECRET": env.TURN_STATIC_AUTH_SECRET,
|
||||
} + (if adminUrl != null then {
|
||||
"ADMIN_API_URL": adminUrl,
|
||||
} else {}) + if namespace != "master" then {
|
||||
// Absolutely ugly WorkAround to circumvent broken certificates on the K8S test cluster. Don't do this in production kids!
|
||||
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
|
||||
}
|
||||
} else {})
|
||||
},
|
||||
"back2": {
|
||||
"image": "thecodingmachine/workadventure-back:"+tag,
|
||||
@ -47,10 +44,7 @@
|
||||
"TURN_STATIC_AUTH_SECRET": env.TURN_STATIC_AUTH_SECRET,
|
||||
} + (if adminUrl != null then {
|
||||
"ADMIN_API_URL": adminUrl,
|
||||
} else {}) + if namespace != "master" then {
|
||||
// Absolutely ugly WorkAround to circumvent broken certificates on the K8S test cluster. Don't do this in production kids!
|
||||
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
|
||||
}
|
||||
} else {})
|
||||
},
|
||||
"pusher": {
|
||||
"replicas": 2,
|
||||
@ -69,10 +63,7 @@
|
||||
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
|
||||
} + (if adminUrl != null then {
|
||||
"ADMIN_API_URL": adminUrl,
|
||||
} else {}) + if namespace != "master" then {
|
||||
// Absolutely ugly WorkAround to circumvent broken certificates on the K8S test cluster. Don't do this in production kids!
|
||||
"NODE_TLS_REJECT_UNAUTHORIZED": "0"
|
||||
}
|
||||
} else {})
|
||||
},
|
||||
"front": {
|
||||
"image": "thecodingmachine/workadventure-front:"+tag,
|
||||
|
@ -33,6 +33,8 @@ services:
|
||||
STARTUP_COMMAND_1: ./templater.sh
|
||||
STARTUP_COMMAND_2: yarn install
|
||||
TURN_SERVER: "turn:localhost:3478,turns:localhost:5349"
|
||||
DISABLE_NOTIFICATIONS: "$DISABLE_NOTIFICATIONS"
|
||||
SKIP_RENDER_OPTIMIZATIONS: "$SKIP_RENDER_OPTIMIZATIONS"
|
||||
# Use TURN_USER/TURN_PASSWORD if your Coturn server is secured via hard coded credentials.
|
||||
# Advice: you should instead use Coturn REST API along the TURN_STATIC_AUTH_SECRET in the Back container
|
||||
TURN_USER: ""
|
||||
|
@ -33,6 +33,8 @@ services:
|
||||
STARTUP_COMMAND_2: yarn install
|
||||
STUN_SERVER: "stun:stun.l.google.com:19302"
|
||||
TURN_SERVER: "turn:coturn.workadventure.localhost:3478,turns:coturn.workadventure.localhost:5349"
|
||||
DISABLE_NOTIFICATIONS: "$DISABLE_NOTIFICATIONS"
|
||||
SKIP_RENDER_OPTIMIZATIONS: "$SKIP_RENDER_OPTIMIZATIONS"
|
||||
# Use TURN_USER/TURN_PASSWORD if your Coturn server is secured via hard coded credentials.
|
||||
# Advice: you should instead use Coturn REST API along the TURN_STATIC_AUTH_SECRET in the Back container
|
||||
TURN_USER: ""
|
||||
|
237
docs/maps/api-reference.md
Normal file
237
docs/maps/api-reference.md
Normal 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
117
docs/maps/scripting.md
Normal 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).
|
@ -25,6 +25,15 @@
|
||||
],
|
||||
"rules": {
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-explicit-any": "error"
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
|
||||
// TODO: remove those ignored rules and write a stronger code!
|
||||
"@typescript-eslint/no-floating-promises": "off",
|
||||
"@typescript-eslint/no-unsafe-call": "off",
|
||||
"@typescript-eslint/restrict-plus-operands": "off",
|
||||
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||
"@typescript-eslint/no-unsafe-return": "off",
|
||||
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||
"@typescript-eslint/restrict-template-expressions": "off"
|
||||
}
|
||||
}
|
||||
|
BIN
front/dist/resources/logos/logo-WA-min.png
vendored
Normal file
BIN
front/dist/resources/logos/logo-WA-min.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
@ -7,22 +7,23 @@
|
||||
"@types/google-protobuf": "^3.7.3",
|
||||
"@types/jasmine": "^3.5.10",
|
||||
"@types/quill": "^1.3.7",
|
||||
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
||||
"@typescript-eslint/parser": "^2.26.0",
|
||||
"css-loader": "^5.1.3",
|
||||
"eslint": "^6.8.0",
|
||||
"html-webpack-plugin": "^4.3.0",
|
||||
"@types/webpack-dev-server": "^3.11.4",
|
||||
"@typescript-eslint/eslint-plugin": "^4.23.0",
|
||||
"@typescript-eslint/parser": "^4.23.0",
|
||||
"css-loader": "^5.2.4",
|
||||
"eslint": "^7.26.0",
|
||||
"html-webpack-plugin": "^5.3.1",
|
||||
"jasmine": "^3.5.0",
|
||||
"mini-css-extract-plugin": "^1.3.9",
|
||||
"sass": "^1.32.8",
|
||||
"sass-loader": "10.1.1",
|
||||
"ts-loader": "^6.2.2",
|
||||
"ts-node": "^8.10.2",
|
||||
"typescript": "^3.8.3",
|
||||
"webpack": "^4.42.1",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"webpack-dev-server": "^3.10.3",
|
||||
"webpack-merge": "^4.2.2"
|
||||
"mini-css-extract-plugin": "^1.6.0",
|
||||
"node-polyfill-webpack-plugin": "^1.1.2",
|
||||
"sass": "^1.32.12",
|
||||
"sass-loader": "^11.1.0",
|
||||
"ts-loader": "^9.1.2",
|
||||
"ts-node": "^9.1.1",
|
||||
"typescript": "^4.2.4",
|
||||
"webpack": "^5.37.0",
|
||||
"webpack-cli": "^4.7.0",
|
||||
"webpack-dev-server": "^3.11.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/simple-peer": "^9.6.0",
|
||||
@ -36,12 +37,11 @@
|
||||
"quill": "1.3.6",
|
||||
"rxjs": "^6.6.3",
|
||||
"simple-peer": "^9.6.2",
|
||||
"socket.io-client": "^2.3.0",
|
||||
"webpack-require-http": "^0.4.3"
|
||||
"socket.io-client": "^2.3.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "webpack-dev-server --open",
|
||||
"build": "webpack --config webpack.prod.js",
|
||||
"start": "webpack serve --open",
|
||||
"build": "NODE_ENV=production webpack",
|
||||
"test": "ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json",
|
||||
"lint": "node_modules/.bin/eslint src/ . --ext .ts",
|
||||
"fix": "node_modules/.bin/eslint --fix src/ . --ext .ts"
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {HtmlUtils} from "../WebRtc/HtmlUtils";
|
||||
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
||||
import type {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||
import type {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import type {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
||||
import {AdminMessageEventTypes} from "../Connexion/AdminMessagesService";
|
||||
|
||||
export const CLASS_CONSOLE_MESSAGE = 'main-console';
|
||||
|
@ -1,8 +1,8 @@
|
||||
import {HtmlUtils} from "./../WebRtc/HtmlUtils";
|
||||
import {AUDIO_TYPE, MESSAGE_TYPE} from "./ConsoleGlobalMessageManager";
|
||||
import {PUSHER_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable";
|
||||
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
||||
import type {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import type {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
||||
|
||||
export class GlobalMessageManager {
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {TypeMessageInterface} from "./UserMessageManager";
|
||||
import type {TypeMessageInterface} from "./UserMessageManager";
|
||||
import {HtmlUtils} from "../WebRtc/HtmlUtils";
|
||||
|
||||
let modalTimeOut : NodeJS.Timeout;
|
||||
|
@ -1,16 +1,16 @@
|
||||
|
||||
|
||||
import { ButtonClickedEvent } from './ButtonClickedEvent';
|
||||
import { ChatEvent } from './ChatEvent';
|
||||
import { ClosePopupEvent } from './ClosePopupEvent';
|
||||
import { EnterLeaveEvent } from './EnterLeaveEvent';
|
||||
import { GoToPageEvent } from './GoToPageEvent';
|
||||
import { MenuItemClickedEvent } from './MenuItemClickedEvent';
|
||||
import { MenuItemRegisterEvent } from './MenuItemRegisterEvent';
|
||||
import { OpenCoWebSiteEvent } from './OpenCoWebSiteEvent';
|
||||
import { OpenPopupEvent } from './OpenPopupEvent';
|
||||
import { OpenTabEvent } from './OpenTabEvent';
|
||||
import { UserInputChatEvent } from './UserInputChatEvent';
|
||||
import type { ButtonClickedEvent } from './ButtonClickedEvent';
|
||||
import type { ChatEvent } from './ChatEvent';
|
||||
import type { ClosePopupEvent } from './ClosePopupEvent';
|
||||
import type { EnterLeaveEvent } from './EnterLeaveEvent';
|
||||
import type { GoToPageEvent } from './GoToPageEvent';
|
||||
import type { MenuItemClickedEvent } from './MenuItemClickedEvent';
|
||||
import type { MenuItemRegisterEvent } from './MenuItemRegisterEvent';
|
||||
import type { OpenCoWebSiteEvent } from './OpenCoWebSiteEvent';
|
||||
import type { OpenPopupEvent } from './OpenPopupEvent';
|
||||
import type { OpenTabEvent } from './OpenTabEvent';
|
||||
import type { UserInputChatEvent } from './UserInputChatEvent';
|
||||
|
||||
|
||||
export interface TypedMessageEvent<T> extends MessageEvent {
|
||||
@ -28,8 +28,8 @@ export type IframeEventMap = {
|
||||
goToPage: GoToPageEvent
|
||||
openCoWebSite: OpenCoWebSiteEvent
|
||||
closeCoWebSite: null
|
||||
disablePlayerControl: null
|
||||
restorePlayerControl: null
|
||||
disablePlayerControls: null
|
||||
restorePlayerControls: null
|
||||
displayBubble: null
|
||||
removeBubble: null
|
||||
}
|
||||
|
@ -1,19 +1,18 @@
|
||||
import { Subject } from "rxjs";
|
||||
import { ChatEvent, isChatEvent } from "./Events/ChatEvent";
|
||||
import * as crypto from "crypto";
|
||||
import { HtmlUtils } from "../WebRtc/HtmlUtils";
|
||||
import { EnterLeaveEvent } from "./Events/EnterLeaveEvent";
|
||||
import type { EnterLeaveEvent } from "./Events/EnterLeaveEvent";
|
||||
import { isOpenPopupEvent, OpenPopupEvent } from "./Events/OpenPopupEvent";
|
||||
import { isOpenTabEvent, OpenTabEvent } from "./Events/OpenTabEvent";
|
||||
import { ButtonClickedEvent } from "./Events/ButtonClickedEvent";
|
||||
import type { ButtonClickedEvent } from "./Events/ButtonClickedEvent";
|
||||
import { ClosePopupEvent, isClosePopupEvent } from "./Events/ClosePopupEvent";
|
||||
import { scriptUtils } from "./ScriptUtils";
|
||||
import { GoToPageEvent, isGoToPageEvent } from "./Events/GoToPageEvent";
|
||||
import { isOpenCoWebsite, OpenCoWebSiteEvent } from "./Events/OpenCoWebSiteEvent";
|
||||
import { IframeEventMap, IframeEvent, IframeResponseEvent, IframeResponseEventMap, isIframeEventWrapper, TypedMessageEvent } from "./Events/IframeEvent";
|
||||
import { UserInputChatEvent } from "./Events/UserInputChatEvent";
|
||||
import type { UserInputChatEvent } from "./Events/UserInputChatEvent";
|
||||
import { isMenuItemRegisterEvent } from './Events/MenuItemRegisterEvent';
|
||||
import { MenuItemClickedEvent } from './Events/MenuItemClickedEvent';
|
||||
import type { MenuItemClickedEvent } from './Events/MenuItemClickedEvent';
|
||||
|
||||
|
||||
/**
|
||||
@ -100,10 +99,10 @@ class IframeListener {
|
||||
else if (payload.type === 'closeCoWebSite') {
|
||||
scriptUtils.closeCoWebSite();
|
||||
}
|
||||
else if (payload.type === 'disablePlayerControl') {
|
||||
else if (payload.type === 'disablePlayerControls') {
|
||||
this._disablePlayerControlStream.next();
|
||||
}
|
||||
else if (payload.type === 'restorePlayerControl') {
|
||||
else if (payload.type === 'restorePlayerControls') {
|
||||
this._enablePlayerControlStream.next();
|
||||
}
|
||||
else if (payload.type === 'displayBubble') {
|
||||
@ -182,7 +181,7 @@ class IframeListener {
|
||||
}
|
||||
|
||||
private getIFrameId(scriptUrl: string): string {
|
||||
return 'script' + crypto.createHash('md5').update(scriptUrl).digest("hex");
|
||||
return 'script' + btoa(scriptUrl);
|
||||
}
|
||||
|
||||
unregisterScript(scriptUrl: string): void {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {Subject} from "rxjs";
|
||||
import {BanUserMessage, SendUserMessage} from "../Messages/generated/messages_pb";
|
||||
import type {BanUserMessage, SendUserMessage} from "../Messages/generated/messages_pb";
|
||||
|
||||
export enum AdminMessageEventTypes {
|
||||
admin = 'message',
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Axios from "axios";
|
||||
import {PUSHER_URL, START_ROOM_URL} from "../Enum/EnvironmentVariable";
|
||||
import {RoomConnection} from "./RoomConnection";
|
||||
import {OnConnectInterface, PositionInterface, ViewportInterface} from "./ConnexionModels";
|
||||
import type {OnConnectInterface, PositionInterface, ViewportInterface} from "./ConnexionModels";
|
||||
import {GameConnexionTypes, urlManager} from "../Url/UrlManager";
|
||||
import {localUserStore} from "./LocalUserStore";
|
||||
import {LocalUser} from "./LocalUser";
|
||||
|
@ -1,8 +1,8 @@
|
||||
import {PlayerAnimationDirections} from "../Phaser/Player/Animation";
|
||||
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
|
||||
import {SignalData} from "simple-peer";
|
||||
import {RoomConnection} from "./RoomConnection";
|
||||
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
|
||||
import type {SignalData} from "simple-peer";
|
||||
import type {RoomConnection} from "./RoomConnection";
|
||||
import type {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
|
||||
|
||||
export enum EventMessage{
|
||||
CONNECT = "connect",
|
||||
|
@ -31,7 +31,7 @@ import {
|
||||
BanUserMessage
|
||||
} from "../Messages/generated/messages_pb"
|
||||
|
||||
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
|
||||
import type {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
|
||||
import Direction = PositionMessage.Direction;
|
||||
import {ProtobufClientUtils} from "../Network/ProtobufClientUtils";
|
||||
import {
|
||||
@ -42,7 +42,7 @@ import {
|
||||
ViewportInterface, WebRtcDisconnectMessageInterface,
|
||||
WebRtcSignalReceivedMessageInterface,
|
||||
} from "./ConnexionModels";
|
||||
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
|
||||
import type {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
|
||||
import {adminMessagesService} from "./AdminMessagesService";
|
||||
import {worldFullMessageStream} from "./WorldFullMessageStream";
|
||||
import {worldFullWarningStream} from "./WorldFullWarningStream";
|
||||
|
@ -1,11 +1,11 @@
|
||||
const DEBUG_MODE: boolean = process.env.DEBUG_MODE == "true";
|
||||
const START_ROOM_URL : string = process.env.START_ROOM_URL || '/_/global/maps.workadventure.localhost/Floor0/floor0.json';
|
||||
// For compatibility reasons with older versions, API_URL is the old host name of PUSHER_URL
|
||||
const PUSHER_URL = process.env.PUSHER_URL || (process.env.API_URL ? '//'+process.env.API_URL : "//pusher.workadventure.localhost");
|
||||
const PUSHER_URL = process.env.PUSHER_URL || '//pusher.workadventure.localhost';
|
||||
const UPLOADER_URL = process.env.UPLOADER_URL || '//uploader.workadventure.localhost';
|
||||
const ADMIN_URL = process.env.ADMIN_URL || "//workadventure.localhost";
|
||||
const STUN_SERVER: string = process.env.STUN_SERVER || "stun:stun.l.google.com:19302";
|
||||
const TURN_SERVER: string = process.env.TURN_SERVER || "";
|
||||
const SKIP_RENDER_OPTIMIZATIONS: boolean = process.env.SKIP_RENDER_OPTIMIZATIONS == "true";
|
||||
const DISABLE_NOTIFICATIONS: boolean = process.env.DISABLE_NOTIFICATIONS == "true";
|
||||
const TURN_USER: string = process.env.TURN_USER || '';
|
||||
const TURN_PASSWORD: string = process.env.TURN_PASSWORD || '';
|
||||
const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL;
|
||||
@ -20,9 +20,10 @@ export const isMobile = ():boolean => ( ( window.innerWidth <= 800 ) || ( window
|
||||
export {
|
||||
DEBUG_MODE,
|
||||
START_ROOM_URL,
|
||||
SKIP_RENDER_OPTIMIZATIONS,
|
||||
DISABLE_NOTIFICATIONS,
|
||||
PUSHER_URL,
|
||||
UPLOADER_URL,
|
||||
ADMIN_URL,
|
||||
POSITION_DELAY,
|
||||
MAX_EXTRAPOLATION_TIME,
|
||||
STUN_SERVER,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {PositionMessage} from "../Messages/generated/messages_pb";
|
||||
import Direction = PositionMessage.Direction;
|
||||
import {PointInterface} from "../Connexion/ConnexionModels";
|
||||
import type {PointInterface} from "../Connexion/ConnexionModels";
|
||||
|
||||
export class ProtobufClientUtils {
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import Sprite = Phaser.GameObjects.Sprite;
|
||||
import Container = Phaser.GameObjects.Container;
|
||||
import { lazyLoadCompanionResource } from "./CompanionTexturesLoadingManager";
|
||||
import { PlayerAnimationDirections, PlayerAnimationTypes } from "../Player/Animation";
|
||||
|
||||
export interface CompanionStatus {
|
||||
|
@ -1,5 +1,4 @@
|
||||
import VirtualJoystick from 'phaser3-rex-plugins/plugins/virtualjoystick.js';
|
||||
import ScaleManager = Phaser.Scale.ScaleManager;
|
||||
import {waScaleManager} from "../Services/WaScaleManager";
|
||||
|
||||
//the assets were found here: https://hannemann.itch.io/virtual-joystick-pack-free
|
||||
|
@ -17,7 +17,6 @@ export class SoundMeter {
|
||||
}
|
||||
|
||||
private init(context: AudioContext) {
|
||||
if (this.context === undefined) {
|
||||
this.context = context;
|
||||
this.analyser = this.context.createAnalyser();
|
||||
|
||||
@ -25,7 +24,6 @@ export class SoundMeter {
|
||||
const bufferLength = this.analyser.fftSize;
|
||||
this.dataArray = new Uint8Array(bufferLength);
|
||||
}
|
||||
}
|
||||
|
||||
public connectToSource(stream: MediaStream, context: AudioContext): void
|
||||
{
|
||||
|
@ -1,5 +1,5 @@
|
||||
import Container = Phaser.GameObjects.Container;
|
||||
import {Scene} from "phaser";
|
||||
import type {Scene} from "phaser";
|
||||
import GameObject = Phaser.GameObjects.GameObject;
|
||||
import Rectangle = Phaser.GameObjects.Rectangle;
|
||||
|
||||
|
@ -1,7 +1,5 @@
|
||||
import {ITiledMapObject} from "../Map/ITiledMap";
|
||||
import Text = Phaser.GameObjects.Text;
|
||||
import {GameScene} from "../Game/GameScene";
|
||||
import TextStyle = Phaser.GameObjects.TextStyle;
|
||||
import type {ITiledMapObject} from "../Map/ITiledMap";
|
||||
import type {GameScene} from "../Game/GameScene";
|
||||
|
||||
export class TextUtils {
|
||||
public static createTextFromITiledMapObject(scene: GameScene, object: ITiledMapObject): void {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
|
||||
import TextureManager = Phaser.Textures.TextureManager;
|
||||
import {CharacterTexture} from "../../Connexion/LocalUser";
|
||||
import type {CharacterTexture} from "../../Connexion/LocalUser";
|
||||
import {BodyResourceDescriptionInterface, LAYERS, PLAYER_RESOURCES} from "./PlayerTextures";
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {GameScene} from "../Game/GameScene";
|
||||
import {PointInterface} from "../../Connexion/ConnexionModels";
|
||||
import type {GameScene} from "../Game/GameScene";
|
||||
import type {PointInterface} from "../../Connexion/ConnexionModels";
|
||||
import {Character} from "../Entity/Character";
|
||||
import {PlayerAnimationDirections} from "../Player/Animation";
|
||||
import type {PlayerAnimationDirections} from "../Player/Animation";
|
||||
|
||||
/**
|
||||
* Class representing the sprite of a remote player (a player that plays on another computer)
|
||||
|
@ -1,5 +1,5 @@
|
||||
import Scene = Phaser.Scene;
|
||||
import {Character} from "./Character";
|
||||
import type {Character} from "./Character";
|
||||
|
||||
//todo: improve this WIP
|
||||
export class SpeechBubble {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {PointInterface} from "../../Connexion/ConnexionModels";
|
||||
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||
import type {PointInterface} from "../../Connexion/ConnexionModels";
|
||||
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||
|
||||
export interface AddPlayerInterface {
|
||||
userId: number;
|
||||
|
@ -2,6 +2,8 @@ import {ResizableScene} from "../Login/ResizableScene";
|
||||
import GameObject = Phaser.GameObjects.GameObject;
|
||||
import Events = Phaser.Scenes.Events;
|
||||
import AnimationEvents = Phaser.Animations.Events;
|
||||
import StructEvents = Phaser.Structs.Events;
|
||||
import {SKIP_RENDER_OPTIMIZATIONS} from "../../Enum/EnvironmentVariable";
|
||||
|
||||
/**
|
||||
* A scene that can track its dirty/pristine state.
|
||||
@ -18,17 +20,16 @@ export abstract class DirtyScene extends ResizableScene {
|
||||
* Note: this does not work with animations from sprites inside containers.
|
||||
*/
|
||||
protected trackDirtyAnims(): void {
|
||||
if (this.isAlreadyTracking) {
|
||||
if (this.isAlreadyTracking || SKIP_RENDER_OPTIMIZATIONS) {
|
||||
return;
|
||||
}
|
||||
this.isAlreadyTracking = true;
|
||||
const trackAnimationFunction = this.trackAnimation.bind(this);
|
||||
this.events.on(Events.ADDED_TO_SCENE, (gameObject: GameObject) => {
|
||||
this.sys.updateList.on(StructEvents.PROCESS_QUEUE_ADD, (gameObject: GameObject) => {
|
||||
this.objectListChanged = true;
|
||||
gameObject.on(AnimationEvents.ANIMATION_UPDATE, trackAnimationFunction);
|
||||
});
|
||||
|
||||
this.events.on(Events.REMOVED_FROM_SCENE, (gameObject: GameObject) => {
|
||||
this.sys.updateList.on(StructEvents.PROCESS_QUEUE_REMOVE, (gameObject: GameObject) => {
|
||||
this.objectListChanged = true;
|
||||
gameObject.removeListener(AnimationEvents.ANIMATION_UPDATE, trackAnimationFunction);
|
||||
});
|
||||
|
@ -1,3 +1,8 @@
|
||||
import {SKIP_RENDER_OPTIMIZATIONS} from "../../Enum/EnvironmentVariable";
|
||||
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
|
||||
import {waScaleManager} from "../Services/WaScaleManager";
|
||||
import {ResizableScene} from "../Login/ResizableScene";
|
||||
|
||||
const Events = Phaser.Core.Events;
|
||||
|
||||
/**
|
||||
@ -5,8 +10,27 @@ const Events = Phaser.Core.Events;
|
||||
* It comes with an optimization to skip rendering.
|
||||
*
|
||||
* Beware, the "step" function might vary in future versions of Phaser.
|
||||
*
|
||||
* It also automatically calls "onResize" on any scenes extending ResizableScene.
|
||||
*/
|
||||
export class Game extends Phaser.Game {
|
||||
|
||||
private _isDirty = false;
|
||||
|
||||
|
||||
constructor(GameConfig: Phaser.Types.Core.GameConfig) {
|
||||
super(GameConfig);
|
||||
|
||||
window.addEventListener('resize', (event) => {
|
||||
// Let's trigger the onResize method of any active scene that is a ResizableScene
|
||||
for (const scene of this.scene.getScenes(true)) {
|
||||
if (scene instanceof ResizableScene) {
|
||||
scene.onResize(event);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public step(time: number, delta: number)
|
||||
{
|
||||
// @ts-ignore
|
||||
@ -35,7 +59,7 @@ export class Game extends Phaser.Game {
|
||||
eventEmitter.emit(Events.POST_STEP, time, delta);
|
||||
|
||||
// This "if" is the changed introduced by the new "Game" class to avoid rendering unnecessarily.
|
||||
if (this.isDirty()) {
|
||||
if (SKIP_RENDER_OPTIMIZATIONS || this.isDirty()) {
|
||||
const renderer = this.renderer;
|
||||
|
||||
// Run the Pre-render (clearing the canvas, setting background colors, etc)
|
||||
@ -62,6 +86,11 @@ export class Game extends Phaser.Game {
|
||||
}
|
||||
|
||||
private isDirty(): boolean {
|
||||
if (this._isDirty) {
|
||||
this._isDirty = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Loop through the scenes in forward order
|
||||
for (let i = 0; i < this.scene.scenes.length; i++)
|
||||
{
|
||||
@ -85,4 +114,11 @@ export class Game extends Phaser.Game {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the game as needing to be redrawn.
|
||||
*/
|
||||
public markDirty(): void {
|
||||
this._isDirty = true;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {GameScene} from "./GameScene";
|
||||
import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||
import {Room} from "../../Connexion/Room";
|
||||
import type {Room} from "../../Connexion/Room";
|
||||
import {MenuScene, MenuSceneName} from "../Menu/MenuScene";
|
||||
import {HelpCameraSettingsScene, HelpCameraSettingsSceneName} from "../Menu/HelpCameraSettingsScene";
|
||||
import {LoginSceneName} from "../Login/LoginScene";
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {ITiledMap, ITiledMapLayer} from "../Map/ITiledMap";
|
||||
import type {ITiledMap, ITiledMapLayer} from "../Map/ITiledMap";
|
||||
import {LayersIterator} from "../Map/LayersIterator";
|
||||
|
||||
export type PropertyChangeCallback = (newValue: string | number | boolean | undefined, oldValue: string | number | boolean | undefined, allProps: Map<string, string | boolean | number>) => void;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {gameManager, HasMovedEvent} from "./GameManager";
|
||||
import {
|
||||
import type {
|
||||
GroupCreatedUpdatedMessageInterface,
|
||||
MessageUserJoined,
|
||||
MessageUserMovedInterface,
|
||||
@ -16,7 +16,7 @@ import {
|
||||
MAX_PER_GROUP,
|
||||
POSITION_DELAY,
|
||||
} from "../../Enum/EnvironmentVariable";
|
||||
import {
|
||||
import type {
|
||||
ITiledMap,
|
||||
ITiledMapLayer,
|
||||
ITiledMapLayerProperty,
|
||||
@ -25,7 +25,7 @@ import {
|
||||
ITiledMapTileLayer,
|
||||
ITiledTileSet
|
||||
} from "../Map/ITiledMap";
|
||||
import {AddPlayerInterface} from "./AddPlayerInterface";
|
||||
import type {AddPlayerInterface} from "./AddPlayerInterface";
|
||||
import {PlayerAnimationDirections} from "../Player/Animation";
|
||||
import {PlayerMovement} from "./PlayerMovement";
|
||||
import {PlayersPositionInterpolator} from "./PlayersPositionInterpolator";
|
||||
@ -49,13 +49,13 @@ import {
|
||||
import {GameMap} from "./GameMap";
|
||||
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
|
||||
import {mediaManager} from "../../WebRtc/MediaManager";
|
||||
import {ItemFactoryInterface} from "../Items/ItemFactoryInterface";
|
||||
import {ActionableItem} from "../Items/ActionableItem";
|
||||
import type {ItemFactoryInterface} from "../Items/ItemFactoryInterface";
|
||||
import type {ActionableItem} from "../Items/ActionableItem";
|
||||
import {UserInputManager} from "../UserInput/UserInputManager";
|
||||
import {UserMovedMessage} from "../../Messages/generated/messages_pb";
|
||||
import type {UserMovedMessage} from "../../Messages/generated/messages_pb";
|
||||
import {ProtobufClientUtils} from "../../Network/ProtobufClientUtils";
|
||||
import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||
import {RoomConnection} from "../../Connexion/RoomConnection";
|
||||
import type {RoomConnection} from "../../Connexion/RoomConnection";
|
||||
import {GlobalMessageManager} from "../../Administration/GlobalMessageManager";
|
||||
import {userMessageManager} from "../../Administration/UserMessageManager";
|
||||
import {ConsoleGlobalMessageManager} from "../../Administration/ConsoleGlobalMessageManager";
|
||||
@ -80,7 +80,7 @@ import CanvasTexture = Phaser.Textures.CanvasTexture;
|
||||
import GameObject = Phaser.GameObjects.GameObject;
|
||||
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
|
||||
import DOMElement = Phaser.GameObjects.DOMElement;
|
||||
import {Subscription} from "rxjs";
|
||||
import type {Subscription} from "rxjs";
|
||||
import {worldFullMessageStream} from "../../Connexion/WorldFullMessageStream";
|
||||
import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager";
|
||||
import RenderTexture = Phaser.GameObjects.RenderTexture;
|
||||
@ -153,7 +153,7 @@ export class GameScene extends DirtyScene implements CenterListener {
|
||||
private GlobalMessageManager!: GlobalMessageManager;
|
||||
public ConsoleGlobalMessageManager!: ConsoleGlobalMessageManager;
|
||||
private connectionAnswerPromise: Promise<RoomJoinedMessageInterface>;
|
||||
private connectionAnswerPromiseResolve!: (value?: RoomJoinedMessageInterface | PromiseLike<RoomJoinedMessageInterface>) => void;
|
||||
private connectionAnswerPromiseResolve!: (value: RoomJoinedMessageInterface | PromiseLike<RoomJoinedMessageInterface>) => void;
|
||||
// A promise that will resolve when the "create" method is called (signaling loading is ended)
|
||||
private createPromise: Promise<void>;
|
||||
private createPromiseResolve!: (value?: void | PromiseLike<void>) => void;
|
||||
@ -188,6 +188,8 @@ export class GameScene extends DirtyScene implements CenterListener {
|
||||
private popUpElements : Map<number, DOMElement> = new Map<number, Phaser.GameObjects.DOMElement>();
|
||||
private originalMapUrl: string|undefined;
|
||||
private pinchManager: PinchManager|undefined;
|
||||
private mapTransitioning: boolean = false; //used to prevent transitions happenning at the same time.
|
||||
private onVisibilityChangeCallback: () => void;
|
||||
|
||||
constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) {
|
||||
super({
|
||||
@ -203,10 +205,11 @@ export class GameScene extends DirtyScene implements CenterListener {
|
||||
|
||||
this.createPromise = new Promise<void>((resolve, reject): void => {
|
||||
this.createPromiseResolve = resolve;
|
||||
})
|
||||
});
|
||||
this.connectionAnswerPromise = new Promise<RoomJoinedMessageInterface>((resolve, reject): void => {
|
||||
this.connectionAnswerPromiseResolve = resolve;
|
||||
});
|
||||
this.onVisibilityChangeCallback = this.onVisibilityChange.bind(this);
|
||||
}
|
||||
|
||||
//hook preload scene
|
||||
@ -505,6 +508,8 @@ export class GameScene extends DirtyScene implements CenterListener {
|
||||
if (!this.room.isDisconnected()) {
|
||||
this.connect();
|
||||
}
|
||||
|
||||
document.addEventListener('visibilitychange', this.onVisibilityChangeCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -626,6 +631,7 @@ export class GameScene extends DirtyScene implements CenterListener {
|
||||
self.chatModeSprite.setVisible(false);
|
||||
self.openChatIcon.setVisible(false);
|
||||
audioManager.restoreVolume();
|
||||
self.onVisibilityChange();
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -884,6 +890,8 @@ ${escapedMessage}
|
||||
}
|
||||
|
||||
private onMapExit(exitKey: string) {
|
||||
if (this.mapTransitioning) return;
|
||||
this.mapTransitioning = true;
|
||||
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);
|
||||
urlManager.pushStartLayerNameToUrl(hash);
|
||||
@ -903,6 +911,7 @@ ${escapedMessage}
|
||||
this.initPositionFromLayerName(hash || defaultStartLayerName);
|
||||
this.CurrentPlayer.x = this.startX;
|
||||
this.CurrentPlayer.y = this.startY;
|
||||
setTimeout(() => this.mapTransitioning = false, 500);
|
||||
}
|
||||
}
|
||||
|
||||
@ -928,6 +937,8 @@ ${escapedMessage}
|
||||
for(const iframeEvents of this.iframeSubscriptionList){
|
||||
iframeEvents.unsubscribe();
|
||||
}
|
||||
|
||||
document.removeEventListener('visibilitychange', this.onVisibilityChangeCallback);
|
||||
}
|
||||
|
||||
private removeAllRemotePlayers(): void {
|
||||
@ -1492,6 +1503,8 @@ ${escapedMessage}
|
||||
mediaManager.addTriggerCloseJitsiFrameButton('close-jisi',() => {
|
||||
this.stopJitsi();
|
||||
});
|
||||
|
||||
this.onVisibilityChange();
|
||||
}
|
||||
|
||||
public stopJitsi(): void {
|
||||
@ -1500,6 +1513,7 @@ ${escapedMessage}
|
||||
mediaManager.showGameOverlay();
|
||||
|
||||
mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi');
|
||||
this.onVisibilityChange();
|
||||
}
|
||||
|
||||
//todo: put this into an 'orchestrator' scene (EntryScene?)
|
||||
@ -1539,4 +1553,20 @@ ${escapedMessage}
|
||||
waScaleManager.zoomModifier *= zoomFactor;
|
||||
this.updateCameraOffset();
|
||||
}
|
||||
|
||||
private onVisibilityChange(): void {
|
||||
// If the overlay is not displayed, we are in Jitsi. We don't need the webcam.
|
||||
if (!mediaManager.isGameOverlayVisible()) {
|
||||
mediaManager.blurCamera();
|
||||
return;
|
||||
}
|
||||
|
||||
if (document.visibilityState === 'visible') {
|
||||
mediaManager.focusCamera();
|
||||
} else {
|
||||
if (this.simplePeer.getNbConnections() === 0) {
|
||||
mediaManager.blurCamera();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {HasMovedEvent} from "./GameManager";
|
||||
import type {HasMovedEvent} from "./GameManager";
|
||||
import {MAX_EXTRAPOLATION_TIME} from "../../Enum/EnvironmentVariable";
|
||||
import {PositionInterface} from "../../Connexion/ConnexionModels";
|
||||
import type {PositionInterface} from "../../Connexion/ConnexionModels";
|
||||
|
||||
export class PlayerMovement {
|
||||
public constructor(private startPosition: PositionInterface, private startTick: number, private endPosition: HasMovedEvent, private endTick: number) {
|
||||
|
@ -2,8 +2,8 @@
|
||||
* This class is in charge of computing the position of all players.
|
||||
* Player movement is delayed by 200ms so position depends on ticks.
|
||||
*/
|
||||
import {PlayerMovement} from "./PlayerMovement";
|
||||
import {HasMovedEvent} from "./GameManager";
|
||||
import type {PlayerMovement} from "./PlayerMovement";
|
||||
import type {HasMovedEvent} from "./GameManager";
|
||||
|
||||
export class PlayersPositionInterpolator {
|
||||
playerMovements: Map<number, PlayerMovement> = new Map<number, PlayerMovement>();
|
||||
|
@ -4,7 +4,7 @@
|
||||
*/
|
||||
import Sprite = Phaser.GameObjects.Sprite;
|
||||
import {OutlinePipeline} from "../Shaders/OutlinePipeline";
|
||||
import {GameScene} from "../Game/GameScene";
|
||||
import type {GameScene} from "../Game/GameScene";
|
||||
|
||||
type EventCallback = (state: unknown, parameters: unknown) => void;
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import * as Phaser from 'phaser';
|
||||
import {Scene} from "phaser";
|
||||
import Sprite = Phaser.GameObjects.Sprite;
|
||||
import {ITiledMapObject} from "../../Map/ITiledMap";
|
||||
import {ItemFactoryInterface} from "../ItemFactoryInterface";
|
||||
import {GameScene} from "../../Game/GameScene";
|
||||
import type {ITiledMapObject} from "../../Map/ITiledMap";
|
||||
import type {ItemFactoryInterface} from "../ItemFactoryInterface";
|
||||
import type {GameScene} from "../../Game/GameScene";
|
||||
import {ActionableItem} from "../ActionableItem";
|
||||
import * as tg from "generic-type-guard";
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import type {GameScene} from "../Game/GameScene";
|
||||
import type {ITiledMapObject} from "../Map/ITiledMap";
|
||||
import type {ActionableItem} from "./ActionableItem";
|
||||
import LoaderPlugin = Phaser.Loader.LoaderPlugin;
|
||||
import {GameScene} from "../Game/GameScene";
|
||||
import {ITiledMapObject} from "../Map/ITiledMap";
|
||||
import {ActionableItem} from "./ActionableItem";
|
||||
|
||||
export interface ItemFactoryInterface {
|
||||
preload: (loader: LoaderPlugin) => void;
|
||||
|
@ -1,8 +1,8 @@
|
||||
import {ResizableScene} from "./ResizableScene";
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||
import {loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager";
|
||||
import {CharacterTexture} from "../../Connexion/LocalUser";
|
||||
import type {CharacterTexture} from "../../Connexion/LocalUser";
|
||||
|
||||
export abstract class AbstractCharacterScene extends ResizableScene {
|
||||
|
||||
|
@ -6,7 +6,7 @@ import Container = Phaser.GameObjects.Container;
|
||||
import {gameManager} from "../Game/GameManager";
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
import {addLoader} from "../Components/Loader";
|
||||
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||
import {AbstractCharacterScene} from "./AbstractCharacterScene";
|
||||
import {areCharacterLayersValid} from "../../Connexion/LocalUser";
|
||||
import { MenuScene } from "../Menu/MenuScene";
|
||||
|
@ -252,7 +252,6 @@ export class EnableCameraScene extends ResizableScene {
|
||||
|
||||
update(time: number, delta: number): void {
|
||||
this.soundMeterSprite.setVolume(this.soundMeter.getVolume());
|
||||
mediaManager.updateScene();
|
||||
|
||||
this.centerXDomElement(this.enableCameraSceneElement, 300);
|
||||
}
|
||||
|
@ -1,18 +1,4 @@
|
||||
import {gameManager} from "../Game/GameManager";
|
||||
import {TextField} from "../Components/TextField";
|
||||
import Image = Phaser.GameObjects.Image;
|
||||
import Rectangle = Phaser.GameObjects.Rectangle;
|
||||
import {EnableCameraSceneName} from "./EnableCameraScene";
|
||||
import {CustomizeSceneName} from "./CustomizeScene";
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
import {loadAllDefaultModels} from "../Entity/PlayerTexturesLoadingManager";
|
||||
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||
import {AbstractCharacterScene} from "./AbstractCharacterScene";
|
||||
import {areCharacterLayersValid} from "../../Connexion/LocalUser";
|
||||
import {touchScreenManager} from "../../Touch/TouchScreenManager";
|
||||
import {PinchManager} from "../UserInput/PinchManager";
|
||||
import {MenuScene} from "../Menu/MenuScene";
|
||||
import { SelectCharacterScene, SelectCharacterSceneName } from "./SelectCharacterScene";
|
||||
import { SelectCharacterScene } from "./SelectCharacterScene";
|
||||
|
||||
export class SelectCharacterMobileScene extends SelectCharacterScene {
|
||||
|
||||
|
@ -1,18 +1,16 @@
|
||||
import {gameManager} from "../Game/GameManager";
|
||||
import Image = Phaser.GameObjects.Image;
|
||||
import Rectangle = Phaser.GameObjects.Rectangle;
|
||||
import {EnableCameraSceneName} from "./EnableCameraScene";
|
||||
import {CustomizeSceneName} from "./CustomizeScene";
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
import {loadAllDefaultModels} from "../Entity/PlayerTexturesLoadingManager";
|
||||
import {addLoader} from "../Components/Loader";
|
||||
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||
import type {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||
import {AbstractCharacterScene} from "./AbstractCharacterScene";
|
||||
import {areCharacterLayersValid} from "../../Connexion/LocalUser";
|
||||
import {touchScreenManager} from "../../Touch/TouchScreenManager";
|
||||
import {PinchManager} from "../UserInput/PinchManager";
|
||||
import {MenuScene} from "../Menu/MenuScene";
|
||||
import { SelectCharacterMobileScene } from "./SelectCharacterMobileScene";
|
||||
|
||||
//todo: put this constants in a dedicated file
|
||||
export const SelectCharacterSceneName = "SelectCharacterScene";
|
||||
|
@ -5,7 +5,7 @@ import { gameManager} from "../Game/GameManager";
|
||||
import { ResizableScene } from "./ResizableScene";
|
||||
import { EnableCameraSceneName } from "./EnableCameraScene";
|
||||
import { localUserStore } from "../../Connexion/LocalUserStore";
|
||||
import { CompanionResourceDescriptionInterface } from "../Companion/CompanionTextures";
|
||||
import type { CompanionResourceDescriptionInterface } from "../Companion/CompanionTextures";
|
||||
import { getAllCompanionResources } from "../Companion/CompanionTexturesLoadingManager";
|
||||
import {touchScreenManager} from "../../Touch/TouchScreenManager";
|
||||
import {PinchManager} from "../UserInput/PinchManager";
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {ITiledMap, ITiledMapLayer} from "./ITiledMap";
|
||||
import type {ITiledMap, ITiledMapLayer} from "./ITiledMap";
|
||||
|
||||
/**
|
||||
* Iterates over the layers of a map, flattening the grouped layers
|
||||
|
@ -109,6 +109,7 @@ export class HelpCameraSettingsScene extends DirtyScene {
|
||||
|
||||
public onResize(ev: UIEvent): void {
|
||||
super.onResize(ev);
|
||||
if (this.helpCameraSettingsOpened) {
|
||||
const middleX = this.getMiddleX();
|
||||
const middleY = this.getMiddleY();
|
||||
this.tweens.add({
|
||||
@ -118,6 +119,8 @@ export class HelpCameraSettingsScene extends DirtyScene {
|
||||
duration: 1000,
|
||||
ease: 'Power3'
|
||||
});
|
||||
this.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
private getMiddleX() : number{
|
||||
|
@ -1,5 +1,5 @@
|
||||
import {PlayerAnimationDirections} from "./Animation";
|
||||
import {GameScene} from "../Game/GameScene";
|
||||
import type {GameScene} from "../Game/GameScene";
|
||||
import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager";
|
||||
import {Character} from "../Entity/Character";
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import ScaleManager = Phaser.Scale.ScaleManager;
|
||||
|
||||
interface Size {
|
||||
width: number;
|
||||
@ -13,8 +12,7 @@ export class HdpiManager {
|
||||
* @param minRecommendedGamePixelsNumber The minimum number of pixels we want to display "by default" to the user
|
||||
* @param absoluteMinPixelNumber The very minimum of game pixels to display. Below, we forbid zooming more
|
||||
*/
|
||||
public constructor(private minRecommendedGamePixelsNumber: number, private absoluteMinPixelNumber: number) {
|
||||
}
|
||||
public constructor(private minRecommendedGamePixelsNumber: number, private absoluteMinPixelNumber: number) {}
|
||||
|
||||
/**
|
||||
* Returns the optimal size in "game pixels" based on the screen size in "real pixels".
|
||||
@ -36,16 +34,12 @@ export class HdpiManager {
|
||||
};
|
||||
}
|
||||
|
||||
let i = 1;
|
||||
|
||||
while (realPixelNumber > this.minRecommendedGamePixelsNumber * i * i) {
|
||||
i++;
|
||||
}
|
||||
const optimalZoomLevel = this.getOptimalZoomLevel(realPixelNumber);
|
||||
|
||||
// Has the canvas more pixels than the screen? This is forbidden
|
||||
if ((i - 1) * this._zoomModifier < 1) {
|
||||
if (optimalZoomLevel * this._zoomModifier < 1) {
|
||||
// Let's reset the zoom modifier (WARNING this is a SIDE EFFECT in a getter)
|
||||
this._zoomModifier = 1 / (i - 1);
|
||||
this._zoomModifier = 1 / optimalZoomLevel;
|
||||
|
||||
return {
|
||||
game: {
|
||||
@ -59,8 +53,8 @@ export class HdpiManager {
|
||||
}
|
||||
}
|
||||
|
||||
const gameWidth = Math.ceil(realPixelScreenSize.width / (i - 1) / this._zoomModifier);
|
||||
const gameHeight = Math.ceil(realPixelScreenSize.height / (i - 1) / this._zoomModifier);
|
||||
const gameWidth = Math.ceil(realPixelScreenSize.width / optimalZoomLevel / this._zoomModifier);
|
||||
const gameHeight = Math.ceil(realPixelScreenSize.height / optimalZoomLevel / this._zoomModifier);
|
||||
|
||||
// Let's ensure we display a minimum of pixels, even if crazily zoomed in.
|
||||
if (gameWidth * gameHeight < this.absoluteMinPixelNumber) {
|
||||
@ -68,7 +62,7 @@ export class HdpiManager {
|
||||
const minGameWidth = Math.sqrt(this.absoluteMinPixelNumber * realPixelScreenSize.width / realPixelScreenSize.height);
|
||||
|
||||
// Let's reset the zoom modifier (WARNING this is a SIDE EFFECT in a getter)
|
||||
this._zoomModifier = realPixelScreenSize.width / minGameWidth / (i - 1);
|
||||
this._zoomModifier = realPixelScreenSize.width / minGameWidth / optimalZoomLevel;
|
||||
|
||||
return {
|
||||
game: {
|
||||
@ -89,12 +83,24 @@ export class HdpiManager {
|
||||
height: gameHeight,
|
||||
},
|
||||
real: {
|
||||
width: Math.ceil(realPixelScreenSize.width / (i - 1)) * (i - 1),
|
||||
height: Math.ceil(realPixelScreenSize.height / (i - 1)) * (i - 1),
|
||||
width: Math.ceil(realPixelScreenSize.width / optimalZoomLevel) * optimalZoomLevel,
|
||||
height: Math.ceil(realPixelScreenSize.height / optimalZoomLevel) * optimalZoomLevel,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We only accept integer but we make an exception for 1.5
|
||||
*/
|
||||
private getOptimalZoomLevel(realPixelNumber: number): number {
|
||||
const result = Math.sqrt(realPixelNumber / this.minRecommendedGamePixelsNumber);
|
||||
if (1.5 <= result && result < 2) {
|
||||
return 1.5
|
||||
} else {
|
||||
return Math.floor(result);
|
||||
}
|
||||
}
|
||||
|
||||
public get zoomModifier(): number {
|
||||
return this._zoomModifier;
|
||||
}
|
||||
|
@ -1,18 +1,21 @@
|
||||
import {HdpiManager} from "./HdpiManager";
|
||||
import ScaleManager = Phaser.Scale.ScaleManager;
|
||||
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
|
||||
import type {Game} from "../Game/Game";
|
||||
|
||||
|
||||
class WaScaleManager {
|
||||
private hdpiManager: HdpiManager;
|
||||
private scaleManager!: ScaleManager;
|
||||
private game!: Game;
|
||||
|
||||
public constructor(private minGamePixelsNumber: number, private absoluteMinPixelNumber: number) {
|
||||
this.hdpiManager = new HdpiManager(minGamePixelsNumber, absoluteMinPixelNumber);
|
||||
}
|
||||
|
||||
public setScaleManager(scaleManager: ScaleManager) {
|
||||
this.scaleManager = scaleManager;
|
||||
public setGame(game: Game): void {
|
||||
this.scaleManager = game.scale;
|
||||
this.game = game;
|
||||
}
|
||||
|
||||
public applyNewSize() {
|
||||
@ -32,6 +35,8 @@ class WaScaleManager {
|
||||
const style = this.scaleManager.canvas.style;
|
||||
style.width = Math.ceil(realSize.width / devicePixelRatio) + 'px';
|
||||
style.height = Math.ceil(realSize.height / devicePixelRatio) + 'px';
|
||||
|
||||
this.game.markDirty();
|
||||
}
|
||||
|
||||
public get zoomModifier(): number {
|
||||
@ -42,6 +47,7 @@ class WaScaleManager {
|
||||
this.hdpiManager.zoomModifier = zoomModifier;
|
||||
this.applyNewSize();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const waScaleManager = new WaScaleManager(640*480, 196*196);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Direction } from "../../types";
|
||||
import {GameScene} from "../Game/GameScene";
|
||||
import type { Direction } from "../../types";
|
||||
import type {GameScene} from "../Game/GameScene";
|
||||
import {touchScreenManager} from "../../Touch/TouchScreenManager";
|
||||
import {MobileJoystick} from "../Components/MobileJoystick";
|
||||
|
||||
@ -173,7 +173,7 @@ export class UserInputManager {
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.joystick.destroy();
|
||||
this.joystick?.destroy();
|
||||
}
|
||||
|
||||
private initMouseWheel() {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {Room} from "../Connexion/Room";
|
||||
import type {Room} from "../Connexion/Room";
|
||||
|
||||
export enum GameConnexionTypes {
|
||||
anonymous=1,
|
||||
|
@ -137,14 +137,14 @@ class CoWebsiteManager {
|
||||
if (allowPolicy) {
|
||||
iframe.allow = allowPolicy;
|
||||
}
|
||||
const onloadPromise = new Promise((resolve) => {
|
||||
const onloadPromise = new Promise<void>((resolve) => {
|
||||
iframe.onload = () => resolve();
|
||||
});
|
||||
if (allowApi) {
|
||||
iframeListener.registerIframe(iframe);
|
||||
}
|
||||
this.cowebsiteMainDom.appendChild(iframe);
|
||||
const onTimeoutPromise = new Promise((resolve) => {
|
||||
const onTimeoutPromise = new Promise<void>((resolve) => {
|
||||
setTimeout(() => resolve(), 2000);
|
||||
});
|
||||
this.currentOperationPromise = this.currentOperationPromise.then(() =>Promise.race([onloadPromise, onTimeoutPromise])).then(() => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import {HtmlUtils} from "./HtmlUtils";
|
||||
import {mediaManager, ReportCallback, ShowReportCallBack} from "./MediaManager";
|
||||
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||
import type {ShowReportCallBack} from "./MediaManager";
|
||||
import type {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||
import {connectionManager} from "../Connexion/ConnectionManager";
|
||||
import {GameConnexionTypes} from "../Url/UrlManager";
|
||||
import {iframeListener} from "../Api/IframeListener";
|
||||
|
@ -10,9 +10,10 @@ interface jitsiConfigInterface {
|
||||
}
|
||||
|
||||
const getDefaultConfig = () : jitsiConfigInterface => {
|
||||
const constraints = mediaManager.getConstraintRequestedByUser();
|
||||
return {
|
||||
startWithAudioMuted: !mediaManager.constraintsMedia.audio,
|
||||
startWithVideoMuted: mediaManager.constraintsMedia.video === false,
|
||||
startWithAudioMuted: !constraints.audio,
|
||||
startWithVideoMuted: constraints.video === false,
|
||||
prejoinPageEnabled: false
|
||||
}
|
||||
}
|
||||
@ -71,7 +72,7 @@ class JitsiFactory {
|
||||
private jitsiApi: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
private audioCallback = this.onAudioChange.bind(this);
|
||||
private videoCallback = this.onVideoChange.bind(this);
|
||||
private previousConfigMeet? : jitsiConfigInterface;
|
||||
private previousConfigMeet! : jitsiConfigInterface;
|
||||
private jitsiScriptLoaded: boolean = false;
|
||||
|
||||
/**
|
||||
@ -136,32 +137,24 @@ class JitsiFactory {
|
||||
|
||||
//restore previous config
|
||||
if(this.previousConfigMeet?.startWithAudioMuted){
|
||||
mediaManager.disableMicrophone();
|
||||
await mediaManager.disableMicrophone();
|
||||
}else{
|
||||
mediaManager.enableMicrophone();
|
||||
await mediaManager.enableMicrophone();
|
||||
}
|
||||
|
||||
if(this.previousConfigMeet?.startWithVideoMuted){
|
||||
mediaManager.disableCamera();
|
||||
await mediaManager.disableCamera();
|
||||
}else{
|
||||
mediaManager.enableCamera();
|
||||
await mediaManager.enableCamera();
|
||||
}
|
||||
}
|
||||
|
||||
private onAudioChange({muted}: {muted: boolean}): void {
|
||||
if (muted && mediaManager.constraintsMedia.audio === true) {
|
||||
mediaManager.disableMicrophone();
|
||||
} else if(!muted && mediaManager.constraintsMedia.audio === false) {
|
||||
mediaManager.enableMicrophone();
|
||||
}
|
||||
this.previousConfigMeet.startWithAudioMuted = muted;
|
||||
}
|
||||
|
||||
private onVideoChange({muted}: {muted: boolean}): void {
|
||||
if (muted && mediaManager.constraintsMedia.video !== false) {
|
||||
mediaManager.disableCamera();
|
||||
} else if(!muted && mediaManager.constraintsMedia.video === false) {
|
||||
mediaManager.enableCamera();
|
||||
}
|
||||
this.previousConfigMeet.startWithVideoMuted = muted;
|
||||
}
|
||||
|
||||
private async loadJitsiScript(domain: string): Promise<void> {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { UserInputManager } from "../Phaser/UserInput/UserInputManager";
|
||||
import type { UserInputManager } from "../Phaser/UserInput/UserInputManager";
|
||||
import {HtmlUtils} from "./HtmlUtils";
|
||||
|
||||
export enum LayoutMode {
|
||||
|
@ -1,10 +1,11 @@
|
||||
import {DivImportance, layoutManager} from "./LayoutManager";
|
||||
import {HtmlUtils} from "./HtmlUtils";
|
||||
import {discussionManager, SendMessageCallback} from "./DiscussionManager";
|
||||
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||
import type {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||
import {localUserStore} from "../Connexion/LocalUserStore";
|
||||
import {UserSimplePeerInterface} from "./SimplePeer";
|
||||
import type {UserSimplePeerInterface} from "./SimplePeer";
|
||||
import {SoundMeter} from "../Phaser/Components/SoundMeter";
|
||||
import {DISABLE_NOTIFICATIONS} from "../Enum/EnvironmentVariable";
|
||||
|
||||
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
|
||||
@ -20,7 +21,7 @@ const audioConstraint: boolean|MediaTrackConstraints = {
|
||||
//TODO: make these values configurable in the game settings menu and store them in localstorage
|
||||
autoGainControl: false,
|
||||
echoCancellation: true,
|
||||
noiseSuppression: false
|
||||
noiseSuppression: true
|
||||
};
|
||||
|
||||
export type UpdatedLocalStreamCallback = (media: MediaStream|null) => void;
|
||||
@ -43,7 +44,8 @@ export class MediaManager {
|
||||
microphoneClose: HTMLImageElement;
|
||||
microphone: HTMLImageElement;
|
||||
webrtcInAudio: HTMLAudioElement;
|
||||
mySoundMeterElement: HTMLDivElement;
|
||||
//FIX ME SOUNDMETER: check stalability of sound meter calculation
|
||||
//mySoundMeterElement: HTMLDivElement;
|
||||
private webrtcOutAudio: HTMLAudioElement;
|
||||
constraintsMedia : MediaStreamConstraints = {
|
||||
audio: audioConstraint,
|
||||
@ -62,18 +64,16 @@ export class MediaManager {
|
||||
private previousConstraint : MediaStreamConstraints;
|
||||
private focused : boolean = true;
|
||||
|
||||
private lastUpdateScene : Date = new Date();
|
||||
private setTimeOutlastUpdateScene? : NodeJS.Timeout;
|
||||
|
||||
private hasCamera = true;
|
||||
|
||||
private triggerCloseJistiFrame : Map<String, Function> = new Map<String, Function>();
|
||||
|
||||
private userInputManager?: UserInputManager;
|
||||
|
||||
private mySoundMeter?: SoundMeter|null;
|
||||
//FIX ME SOUNDMETER: check stalability of sound meter calculation
|
||||
/*private mySoundMeter?: SoundMeter|null;
|
||||
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>();*/
|
||||
|
||||
constructor() {
|
||||
|
||||
@ -132,17 +132,19 @@ export class MediaManager {
|
||||
this.previousConstraint = JSON.parse(JSON.stringify(this.constraintsMedia));
|
||||
this.pingCameraStatus();
|
||||
|
||||
this.checkActiveUser(); //todo: desactivated in case of bug
|
||||
|
||||
this.mySoundMeterElement = (HtmlUtils.getElementByIdOrFail('mySoundMeter'));
|
||||
//FIX ME SOUNDMETER: check stalability of sound meter calculation
|
||||
/*this.mySoundMeterElement = (HtmlUtils.getElementByIdOrFail('mySoundMeter'));
|
||||
this.mySoundMeterElement.childNodes.forEach((value: ChildNode, index) => {
|
||||
this.mySoundMeterElement.children.item(index)?.classList.remove('active');
|
||||
});
|
||||
});*/
|
||||
|
||||
//Check of ask notification navigator permission
|
||||
this.getNotification();
|
||||
}
|
||||
|
||||
public updateScene(){
|
||||
this.lastUpdateScene = new Date();
|
||||
this.updateSoudMeter();
|
||||
//FIX ME SOUNDMETER: check stalability of sound meter calculation
|
||||
//this.updateSoudMeter();
|
||||
}
|
||||
|
||||
public blurCamera() {
|
||||
@ -154,6 +156,13 @@ export class MediaManager {
|
||||
this.disableCamera();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the constraint that the user wants (independently of the visibility / jitsi state...)
|
||||
*/
|
||||
public getConstraintRequestedByUser(): MediaStreamConstraints {
|
||||
return this.previousConstraint ?? this.constraintsMedia;
|
||||
}
|
||||
|
||||
public focusCamera() {
|
||||
if(this.focused){
|
||||
return;
|
||||
@ -196,7 +205,7 @@ export class MediaManager {
|
||||
}
|
||||
}
|
||||
|
||||
public showGameOverlay(){
|
||||
public showGameOverlay(): void {
|
||||
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
|
||||
gameOverlay.classList.add('active');
|
||||
|
||||
@ -207,7 +216,7 @@ export class MediaManager {
|
||||
buttonCloseFrame.removeEventListener('click', functionTrigger);
|
||||
}
|
||||
|
||||
public hideGameOverlay(){
|
||||
public hideGameOverlay(): void {
|
||||
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
|
||||
gameOverlay.classList.remove('active');
|
||||
|
||||
@ -218,6 +227,11 @@ export class MediaManager {
|
||||
buttonCloseFrame.addEventListener('click', functionTrigger);
|
||||
}
|
||||
|
||||
public isGameOverlayVisible(): boolean {
|
||||
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
|
||||
return gameOverlay.classList.contains('active');
|
||||
}
|
||||
|
||||
public updateCameraQuality(value: number) {
|
||||
this.enableCameraStyle();
|
||||
const newVideoConstraint = JSON.parse(JSON.stringify(videoConstraint));
|
||||
@ -229,29 +243,32 @@ export class MediaManager {
|
||||
});
|
||||
}
|
||||
|
||||
public enableCamera() {
|
||||
public async enableCamera() {
|
||||
this.constraintsMedia.video = videoConstraint;
|
||||
|
||||
this.getCamera().then((stream: MediaStream) => {
|
||||
try {
|
||||
const stream = await this.getCamera()
|
||||
//TODO show error message tooltip upper of camera button
|
||||
//TODO message : please check camera permission of your navigator
|
||||
if(stream.getVideoTracks().length === 0) {
|
||||
throw Error('Video track is empty, please check camera permission of your navigator')
|
||||
throw new Error('Video track is empty, please check camera permission of your navigator')
|
||||
}
|
||||
this.enableCameraStyle();
|
||||
this.triggerUpdatedLocalStreamCallbacks(stream);
|
||||
}).catch((err) => {
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
this.disableCameraStyle();
|
||||
this.stopCamera();
|
||||
|
||||
layoutManager.addInformation('warning', 'Camera access denied. Click here and check navigators permissions.', () => {
|
||||
this.showHelpCameraSettingsCallBack();
|
||||
}, this.userInputManager);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public async disableCamera() {
|
||||
this.disableCameraStyle();
|
||||
this.stopCamera();
|
||||
|
||||
if (this.constraintsMedia.audio !== false) {
|
||||
const stream = await this.getCamera();
|
||||
@ -261,10 +278,12 @@ export class MediaManager {
|
||||
}
|
||||
}
|
||||
|
||||
public enableMicrophone() {
|
||||
public async enableMicrophone() {
|
||||
this.constraintsMedia.audio = audioConstraint;
|
||||
|
||||
this.getCamera().then((stream) => {
|
||||
try {
|
||||
const stream = await this.getCamera();
|
||||
|
||||
//TODO show error message tooltip upper of camera button
|
||||
//TODO message : please check microphone permission of your navigator
|
||||
if (stream.getAudioTracks().length === 0) {
|
||||
@ -272,14 +291,14 @@ export class MediaManager {
|
||||
}
|
||||
this.enableMicrophoneStyle();
|
||||
this.triggerUpdatedLocalStreamCallbacks(stream);
|
||||
}).catch((err) => {
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
this.disableMicrophoneStyle();
|
||||
|
||||
layoutManager.addInformation('warning', 'Microphone access denied. Click here and check navigators permissions.', () => {
|
||||
this.showHelpCameraSettingsCallBack();
|
||||
}, this.userInputManager);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public async disableMicrophone() {
|
||||
@ -324,7 +343,6 @@ export class MediaManager {
|
||||
this.cinemaBtn.classList.add("disabled");
|
||||
this.constraintsMedia.video = false;
|
||||
this.myCamVideo.srcObject = null;
|
||||
this.stopCamera();
|
||||
}
|
||||
|
||||
private enableMicrophoneStyle(){
|
||||
@ -435,6 +453,8 @@ export class MediaManager {
|
||||
return this.getLocalStream().catch((err) => {
|
||||
console.info('Error get camera, trying with video option at null =>', err);
|
||||
this.disableCameraStyle();
|
||||
this.stopCamera();
|
||||
|
||||
return this.getLocalStream().then((stream : MediaStream) => {
|
||||
this.hasCamera = false;
|
||||
return stream;
|
||||
@ -457,12 +477,12 @@ export class MediaManager {
|
||||
this.localStream = stream;
|
||||
this.myCamVideo.srcObject = this.localStream;
|
||||
|
||||
//init sound meter
|
||||
this.mySoundMeter = null;
|
||||
//FIX ME SOUNDMETER: check stalability of sound meter calculation
|
||||
/*this.mySoundMeter = null;
|
||||
if(this.constraintsMedia.audio){
|
||||
this.mySoundMeter = new SoundMeter();
|
||||
this.mySoundMeter.connectToSource(stream, new AudioContext());
|
||||
}
|
||||
}*/
|
||||
return stream;
|
||||
}).catch((err: Error) => {
|
||||
throw err;
|
||||
@ -489,7 +509,7 @@ export class MediaManager {
|
||||
track.stop();
|
||||
}
|
||||
}
|
||||
this.mySoundMeter?.stop();
|
||||
//this.mySoundMeter?.stop();
|
||||
}
|
||||
|
||||
setCamera(id: string): Promise<MediaStream> {
|
||||
@ -632,11 +652,12 @@ export class MediaManager {
|
||||
}
|
||||
remoteVideo.srcObject = stream;
|
||||
|
||||
//FIX ME SOUNDMETER: check stalability of sound meter calculation
|
||||
//sound metter
|
||||
const soundMeter = new SoundMeter();
|
||||
/*const soundMeter = new SoundMeter();
|
||||
soundMeter.connectToSource(stream, new AudioContext());
|
||||
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){
|
||||
// In the case of screen sharing (going both ways), we may need to create the HTML element if it does not exist yet
|
||||
@ -652,9 +673,10 @@ export class MediaManager {
|
||||
layoutManager.remove(userId);
|
||||
this.remoteVideo.delete(userId);
|
||||
|
||||
this.soundMeters.get(userId)?.stop();
|
||||
//FIX ME SOUNDMETER: check stalability of sound meter calculation
|
||||
/*this.soundMeters.get(userId)?.stop();
|
||||
this.soundMeters.delete(userId);
|
||||
this.soundMeterElements.delete(userId);
|
||||
this.soundMeterElements.delete(userId);*/
|
||||
|
||||
//permit to remove user in discussion part
|
||||
this.removeParticipant(userId);
|
||||
@ -776,22 +798,6 @@ export class MediaManager {
|
||||
this.userInputManager = userInputManager;
|
||||
discussionManager.setUserInputManager(userInputManager);
|
||||
}
|
||||
//check if user is active
|
||||
private checkActiveUser(){
|
||||
if(this.setTimeOutlastUpdateScene){
|
||||
clearTimeout(this.setTimeOutlastUpdateScene);
|
||||
}
|
||||
this.setTimeOutlastUpdateScene = setTimeout(() => {
|
||||
const now = new Date();
|
||||
//if last update is more of 10 sec
|
||||
if( (now.getTime() - this.lastUpdateScene.getTime()) > 10000) {
|
||||
this.blurCamera();
|
||||
}else{
|
||||
this.focusCamera();
|
||||
}
|
||||
this.checkActiveUser();
|
||||
}, this.focused ? 10000 : 1000);
|
||||
}
|
||||
|
||||
public setShowReportModalCallBacks(callback: ShowReportCallBack){
|
||||
this.showReportModalCallBacks.add(callback);
|
||||
@ -807,7 +813,8 @@ export class MediaManager {
|
||||
}
|
||||
}
|
||||
|
||||
updateSoudMeter(){
|
||||
//FIX ME SOUNDMETER: check stalability of sound meter calculation
|
||||
/*updateSoudMeter(){
|
||||
try{
|
||||
const volume = parseInt(((this.mySoundMeter ? this.mySoundMeter.getVolume() : 0) / 10).toFixed(0));
|
||||
this.setVolumeSoundMeter(volume, this.mySoundMeterElement);
|
||||
@ -824,7 +831,7 @@ export class MediaManager {
|
||||
}catch(err){
|
||||
//console.error(err);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
private setVolumeSoundMeter(volume: number, element: HTMLDivElement){
|
||||
if(volume <= 0 && !element.classList.contains('active')){
|
||||
@ -847,6 +854,32 @@ export class MediaManager {
|
||||
elementChildre.classList.add('active');
|
||||
});
|
||||
}
|
||||
|
||||
public getNotification(){
|
||||
//Get notification
|
||||
if (!DISABLE_NOTIFICATIONS && window.Notification && Notification.permission !== "granted") {
|
||||
Notification.requestPermission().catch((err) => {
|
||||
console.error(`Notification permission error`, err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public createNotification(userName: string){
|
||||
if(this.focused){
|
||||
return;
|
||||
}
|
||||
if (window.Notification && Notification.permission === "granted") {
|
||||
const title = 'WorkAdventure';
|
||||
const options = {
|
||||
body: `Hi! ${userName} wants to discuss with you, don't be afraid!`,
|
||||
icon: '/resources/logos/logo-WA-min.png',
|
||||
image: '/resources/logos/logo-WA-min.png',
|
||||
badge: '/resources/logos/logo-WA-min.png',
|
||||
};
|
||||
new Notification(title, options);
|
||||
//new Notification(`Hi! ${userName} wants to discuss with you, don't be afraid!`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const mediaManager = new MediaManager();
|
||||
|
@ -1,9 +1,9 @@
|
||||
import * as SimplePeerNamespace from "simple-peer";
|
||||
import type * as SimplePeerNamespace from "simple-peer";
|
||||
import {mediaManager} from "./MediaManager";
|
||||
import {STUN_SERVER, TURN_SERVER, TURN_USER, TURN_PASSWORD} from "../Enum/EnvironmentVariable";
|
||||
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import type {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import {MESSAGE_TYPE_CONSTRAINT} from "./VideoPeer";
|
||||
import {UserSimplePeerInterface} from "./SimplePeer";
|
||||
import type {UserSimplePeerInterface} from "./SimplePeer";
|
||||
|
||||
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {
|
||||
import type {
|
||||
WebRtcDisconnectMessageInterface,
|
||||
WebRtcSignalReceivedMessageInterface,
|
||||
} from "../Connexion/ConnexionModels";
|
||||
@ -10,7 +10,7 @@ import {
|
||||
} from "./MediaManager";
|
||||
import {ScreenSharingPeer} from "./ScreenSharingPeer";
|
||||
import {MESSAGE_TYPE_BLOCKED, MESSAGE_TYPE_CONSTRAINT, MESSAGE_TYPE_MESSAGE, VideoPeer} from "./VideoPeer";
|
||||
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import type {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import {connectionManager} from "../Connexion/ConnectionManager";
|
||||
import {GameConnexionTypes} from "../Url/UrlManager";
|
||||
import {blackListManager} from "./BlackListManager";
|
||||
@ -158,6 +158,11 @@ export class SimplePeer {
|
||||
this.sendLocalScreenSharingStreamToUser(user.userId);
|
||||
}
|
||||
});
|
||||
|
||||
//Create a notification for first user in circle discussion
|
||||
if(this.PeerConnectionArray.size === 0){
|
||||
mediaManager.createNotification(user.name??'');
|
||||
}
|
||||
this.PeerConnectionArray.set(user.userId, peer);
|
||||
|
||||
for (const peerConnectionListener of this.peerConnectionListeners) {
|
||||
|
@ -1,10 +1,10 @@
|
||||
import * as SimplePeerNamespace from "simple-peer";
|
||||
import type * as SimplePeerNamespace from "simple-peer";
|
||||
import {mediaManager} from "./MediaManager";
|
||||
import {STUN_SERVER, TURN_PASSWORD, TURN_SERVER, TURN_USER} from "../Enum/EnvironmentVariable";
|
||||
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import type {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import {blackListManager} from "./BlackListManager";
|
||||
import {Subscription} from "rxjs";
|
||||
import {UserSimplePeerInterface} from "./SimplePeer";
|
||||
import type {Subscription} from "rxjs";
|
||||
import type {UserSimplePeerInterface} from "./SimplePeer";
|
||||
|
||||
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
|
||||
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { ChatEvent } from "./Api/Events/ChatEvent";
|
||||
import type { ChatEvent } from "./Api/Events/ChatEvent";
|
||||
import { isIframeResponseEventWrapper } from "./Api/Events/IframeEvent";
|
||||
import { isUserInputChatEvent, UserInputChatEvent } from "./Api/Events/UserInputChatEvent";
|
||||
import { Subject } from "rxjs";
|
||||
import { EnterLeaveEvent, isEnterLeaveEvent } from "./Api/Events/EnterLeaveEvent";
|
||||
import { OpenPopupEvent } from "./Api/Events/OpenPopupEvent";
|
||||
import type { OpenPopupEvent } from "./Api/Events/OpenPopupEvent";
|
||||
import { isButtonClickedEvent } from "./Api/Events/ButtonClickedEvent";
|
||||
import { ClosePopupEvent } from "./Api/Events/ClosePopupEvent";
|
||||
import { OpenTabEvent } from "./Api/Events/OpenTabEvent";
|
||||
import { GoToPageEvent } from "./Api/Events/GoToPageEvent";
|
||||
import { OpenCoWebSiteEvent } from "./Api/Events/OpenCoWebSiteEvent";
|
||||
import type { ClosePopupEvent } from "./Api/Events/ClosePopupEvent";
|
||||
import type { OpenTabEvent } from "./Api/Events/OpenTabEvent";
|
||||
import type { GoToPageEvent } from "./Api/Events/GoToPageEvent";
|
||||
import type { OpenCoWebSiteEvent } from "./Api/Events/OpenCoWebSiteEvent";
|
||||
import { isMenuItemClickedEvent } from './Api/Events/MenuItemClickedEvent';
|
||||
import { MenuItemRegisterEvent } from './Api/Events/MenuItemRegisterEvent';
|
||||
import type { MenuItemRegisterEvent } from './Api/Events/MenuItemRegisterEvent';
|
||||
|
||||
interface WorkAdventureApi {
|
||||
sendChatMessage(message: string, author: string): void;
|
||||
@ -22,8 +22,8 @@ interface WorkAdventureApi {
|
||||
goToPage(url : string): void;
|
||||
openCoWebSite(url : string): void;
|
||||
closeCoWebSite(): void;
|
||||
disablePlayerControl(): void;
|
||||
restorePlayerControl(): void;
|
||||
disablePlayerControls(): void;
|
||||
restorePlayerControls(): void;
|
||||
displayBubble(): void;
|
||||
removeBubble(): void;
|
||||
registerMenuCommand(commandDescriptor: string, callback: (commandDescriptor: string) => void): void
|
||||
@ -91,12 +91,12 @@ window.WA = {
|
||||
} as ChatEvent
|
||||
}, '*');
|
||||
},
|
||||
disablePlayerControl(): void {
|
||||
window.parent.postMessage({ 'type': 'disablePlayerControl' }, '*');
|
||||
disablePlayerControls(): void {
|
||||
window.parent.postMessage({ 'type': 'disablePlayerControls' }, '*');
|
||||
},
|
||||
|
||||
restorePlayerControl(): void {
|
||||
window.parent.postMessage({ 'type': 'restorePlayerControl' }, '*');
|
||||
restorePlayerControls(): void {
|
||||
window.parent.postMessage({ 'type': 'restorePlayerControls' }, '*');
|
||||
},
|
||||
|
||||
displayBubble(): void {
|
||||
|
@ -127,19 +127,12 @@ const config: GameConfig = {
|
||||
//const game = new Phaser.Game(config);
|
||||
const game = new Game(config);
|
||||
|
||||
waScaleManager.setScaleManager(game.scale);
|
||||
waScaleManager.setGame(game);
|
||||
|
||||
window.addEventListener('resize', function (event) {
|
||||
coWebsiteManager.resetStyle();
|
||||
|
||||
waScaleManager.applyNewSize();
|
||||
|
||||
// Let's trigger the onResize method of any active scene that is a ResizableScene
|
||||
for (const scene of game.scene.getScenes(true)) {
|
||||
if (scene instanceof ResizableScene) {
|
||||
scene.onResize(event);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
coWebsiteManager.onResize.subscribe(() => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import Phaser from "phaser";
|
||||
import type Phaser from "phaser";
|
||||
|
||||
export type CursorKey = {
|
||||
isDown: boolean
|
||||
|
@ -50,6 +50,6 @@ describe("Test HdpiManager", () => {
|
||||
const result = hdpiManager.getOptimalGameSize({ width: 1280, height: 768 });
|
||||
expect(result.game.width).toEqual(1280);
|
||||
expect(result.game.height).toEqual(768);
|
||||
expect(hdpiManager.zoomModifier).toEqual(1);
|
||||
expect(hdpiManager.zoomModifier).toEqual(2 / 3);
|
||||
});
|
||||
});
|
||||
|
@ -4,13 +4,15 @@
|
||||
"sourceMap": true,
|
||||
"moduleResolution": "node",
|
||||
"module": "CommonJS",
|
||||
"target": "ES2015",
|
||||
"target": "ES2017",
|
||||
"declaration": false,
|
||||
"downlevelIteration": true,
|
||||
"jsx": "react",
|
||||
"allowJs": true,
|
||||
"esModuleInterop": true,
|
||||
|
||||
"importsNotUsedAsValues": "error",
|
||||
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
"strictNullChecks": true, /* Enable strict null checks. */
|
||||
|
@ -1,14 +1,23 @@
|
||||
import type {Configuration} from "webpack";
|
||||
import type WebpackDevServer from "webpack-dev-server";
|
||||
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const NodePolyfillPlugin = require("node-polyfill-webpack-plugin")
|
||||
|
||||
const mode = process.env.NODE_ENV ?? 'development';
|
||||
const isProduction = mode === 'production';
|
||||
const isDevelopment = !isProduction;
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
'main': './src/index.ts',
|
||||
'iframe_api': './src/iframe_api.ts'
|
||||
},
|
||||
devtool: 'inline-source-map',
|
||||
mode: mode,
|
||||
devtool: isDevelopment ? 'inline-source-map' : 'source-map',
|
||||
devServer: {
|
||||
contentBase: './dist',
|
||||
host: '0.0.0.0',
|
||||
@ -41,14 +50,14 @@ module.exports = {
|
||||
filename: (pathData) => {
|
||||
// Add a content hash only for the main bundle.
|
||||
// We want the iframe_api.js file to keep its name as it will be referenced from outside iframes.
|
||||
return pathData.chunk.name === 'main' ? 'js/[name].[contenthash].js': '[name].js';
|
||||
return pathData.chunk?.name === 'main' ? 'js/[name].[contenthash].js': '[name].js';
|
||||
},
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
publicPath: '/'
|
||||
},
|
||||
externals:[
|
||||
/*externals:[
|
||||
require('webpack-require-http')
|
||||
],
|
||||
],*/
|
||||
plugins: [
|
||||
new MiniCssExtractPlugin({filename: 'style.[contenthash].css'}),
|
||||
new HtmlWebpackPlugin(
|
||||
@ -69,8 +78,11 @@ module.exports = {
|
||||
new webpack.ProvidePlugin({
|
||||
Phaser: 'phaser'
|
||||
}),
|
||||
new NodePolyfillPlugin(),
|
||||
new webpack.EnvironmentPlugin({
|
||||
'API_URL': null,
|
||||
'SKIP_RENDER_OPTIMIZATIONS': false,
|
||||
'DISABLE_NOTIFICATIONS': false,
|
||||
'PUSHER_URL': undefined,
|
||||
'UPLOADER_URL': null,
|
||||
'ADMIN_URL': null,
|
||||
@ -81,8 +93,10 @@ module.exports = {
|
||||
'TURN_PASSWORD': null,
|
||||
'JITSI_URL': null,
|
||||
'JITSI_PRIVATE_MODE': null,
|
||||
'START_ROOM_URL': null
|
||||
'START_ROOM_URL': null,
|
||||
'MAX_USERNAME_LENGTH': 8,
|
||||
'MAX_PER_GROUP': 4
|
||||
})
|
||||
],
|
||||
|
||||
};
|
||||
} as Configuration & WebpackDevServer.Configuration;
|
@ -1,7 +0,0 @@
|
||||
const merge = require('webpack-merge');
|
||||
const common = require('./webpack.config.js');
|
||||
|
||||
module.exports = merge(common, {
|
||||
mode: 'production',
|
||||
devtool: 'source-map'
|
||||
});
|
2469
front/yarn.lock
2469
front/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -25,7 +25,7 @@ function launchTuto (){
|
||||
label: "Got it!",
|
||||
className : "success",callback:(popup2 => {
|
||||
popup2.close();
|
||||
WA.restorePlayerControl();
|
||||
WA.restorePlayerControls();
|
||||
})
|
||||
}
|
||||
])
|
||||
@ -36,7 +36,7 @@ function launchTuto (){
|
||||
}
|
||||
}
|
||||
]);
|
||||
WA.disablePlayerControl();
|
||||
WA.disablePlayerControls();
|
||||
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ function launchTuto (){
|
||||
callback: (popup1) => {
|
||||
WA.sendChatMessage("Hey you can talk here too ! ", 'WA Guide');
|
||||
popup1.close();
|
||||
WA.restorePlayerControl();
|
||||
WA.restorePlayerControls();
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ function launchTuto (){
|
||||
}
|
||||
}
|
||||
]);
|
||||
WA.disablePlayerControl();
|
||||
WA.disablePlayerControls();
|
||||
|
||||
}
|
||||
WA.onChatMessage((message => {
|
||||
|
@ -881,9 +881,9 @@ loader-utils@^2.0.0:
|
||||
json5 "^2.1.2"
|
||||
|
||||
lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19:
|
||||
version "4.17.20"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
|
||||
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
||||
loud-rejection@^1.0.0:
|
||||
version "1.6.0"
|
||||
|
@ -2606,9 +2606,9 @@ lodash.templatesettings@^4.0.0:
|
||||
lodash._reinterpolate "^3.0.0"
|
||||
|
||||
lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19:
|
||||
version "4.17.20"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
|
||||
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
||||
log-ok@^0.1.1:
|
||||
version "0.1.1"
|
||||
|
@ -1105,9 +1105,9 @@ lodash.once@^4.0.0:
|
||||
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
|
||||
|
||||
lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19:
|
||||
version "4.17.20"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
|
||||
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
||||
loud-rejection@^1.0.0:
|
||||
version "1.6.0"
|
||||
|
Loading…
Reference in New Issue
Block a user