Compare commits
213 Commits
bace2c6281
...
15ad163da3
Author | SHA1 | Date |
---|---|---|
Thomas Basler | 15ad163da3 | |
Thomas Basler | fa71e2b296 | |
Kharhamel | 4b8a2aca34 | |
kharhamel | 3b6fe8da8d | |
Kharhamel | 77c40985c4 | |
David Négrier | d269596702 | |
kharhamel | 886daebfea | |
David Négrier | 30e12702eb | |
kharhamel | 99e6743c01 | |
Kharhamel | dc1a5da175 | |
Kharhamel | 6f038001de | |
kharhamel | 0b352fc5dc | |
kharhamel | 43921e3fcc | |
Kharhamel | 1d69af0664 | |
kharhamel | f90046459e | |
Kharhamel | ec2fa9501c | |
kharhamel | 8d6c1a50bf | |
Kharhamel | 7765775df5 | |
Kharhamel | 210daf46e3 | |
David Négrier | 979e43bb05 | |
David Négrier | 23c33b2971 | |
PizZaKatZe | 821f91e996 | |
PizZaKatZe | 599fdd6ceb | |
TabascoEye | e6accd711d | |
PizZaKatZe | 5bd6f49846 | |
PizZaKatZe | fdbcd98a9a | |
PizZaKatZe | 132c6c9ad6 | |
PizZaKatZe | 5a7e67f5df | |
PizZaKatZe | 20d3236e73 | |
David Négrier | f9a2097bc9 | |
David Négrier | 4966789b3b | |
David Négrier | ce0ede7613 | |
David Négrier | 6bb757e8b5 | |
Kharhamel | 04bd60ca26 | |
David Négrier | 9da0acb0d7 | |
David Négrier | 96492e8594 | |
PizZaKatZe | 318c63ab90 | |
PizZaKatZe | 13fcda7bcf | |
PizZaKatZe | c0bf5755ed | |
PizZaKatZe | 97ec721c2a | |
Kharhamel | 519018f6c5 | |
David Négrier | 4660b450c5 | |
GRL | e50c8bc354 | |
GRL | 0eb16bce6f | |
GRL | 1cfb22f695 | |
GRL | 0b00055eda | |
GRL | c654f722e3 | |
Gregoire Parant | d68406d5e1 | |
Kharhamel | 1f85e548ce | |
kharhamel | 32fdfaab35 | |
githubIsNotOpenSourceLOL | ec691a75fe | |
kharhamel | 0701e607fa | |
kharhamel | f9c8b4131c | |
kharhamel | 272be3eba0 | |
kharhamel | 3f6c61633a | |
Kharhamel | 19856c0ee9 | |
David Négrier | 4a50729db3 | |
David Négrier | ffb5823b2a | |
Wikinaut | d8c74fa838 | |
Kharhamel | 18fa1e6153 | |
kharhamel | fe8c75610d | |
Gregoire Parant | 59c310d0a8 | |
Gregoire Parant | 2fcb8a76b1 | |
grégoire parant | ce6976ec34 | |
Gregoire Parant | 2cf99df2da | |
Gregoire Parant | 46cb9333e6 | |
dependabot[bot] | d95c71281f | |
David Négrier | a28fcbb9e6 | |
Kharhamel | 21cc842c33 | |
kharhamel | 8abc34c631 | |
David Négrier | f3c67a94ee | |
David Négrier | 1a4c7ad928 | |
David Négrier | 80df1ffb5f | |
David Négrier | c384a73edf | |
David Négrier | 99ab99d893 | |
David Négrier | 15405633d1 | |
David Négrier | bf8e8bf777 | |
Kharhamel | 5833629469 | |
kharhamel | ad7e16c33b | |
grégoire parant | df610aa01b | |
Gregoire Parant | 915d945dd5 | |
Gregoire Parant | 30f4793342 | |
David Négrier | e0540790e1 | |
Moini | 95da470a89 | |
David Négrier | 48c13119bd | |
Lukas Bachschwell | 2772d72eba | |
githubIsNotOpenSourceLOL | 6e6b607226 | |
David Négrier | 6e9c715980 | |
David Négrier | 3eec41a1d0 | |
David Négrier | 6f06b92395 | |
David Négrier | 13a20a0ef0 | |
grégoire parant | b5514e6e48 | |
Gregoire Parant | 772c3174be | |
grégoire parant | 3a1b2490ef | |
Gregoire Parant | 80449e9a96 | |
Gregoire Parant | 8a2954eab9 | |
grégoire parant | 13078489c6 | |
Gregoire Parant | 5ddf909fe6 | |
grégoire parant | c3286e61cf | |
Gregoire Parant | fff0cbc3f6 | |
Gregoire Parant | 9b7b3eb140 | |
Gregoire Parant | ce423c9fec | |
Gregoire Parant | 83fc7d0cc0 | |
Gregoire Parant | baae1e8125 | |
Gregoire Parant | e8b1b286a3 | |
David Négrier | 36a713ec91 | |
David Négrier | ae1d00c828 | |
David Négrier | 43b48ce555 | |
David Négrier | ddcb5ede59 | |
David Négrier | 9affa36608 | |
Kharhamel | f00d07289c | |
kharhamel | e21868ed3b | |
David Négrier | 6b00355ea8 | |
David Négrier | 7fb923f823 | |
David Négrier | 12ef2820bf | |
David Négrier | cdb3cfdc81 | |
znerol | 9670f92a08 | |
David Négrier | e07efbdf28 | |
David Négrier | f5610261c0 | |
David Négrier | b9c839a87d | |
znerol | 4fcdad1551 | |
Tim Schlüter | 05b829da70 | |
MisterErwin | aff4c8d145 | |
MisterErwin | 511cd99e22 | |
Kharhamel | cfcad07487 | |
kharhamel | 0c892e0243 | |
grégoire parant | f917420ca7 | |
Gregoire Parant | b92b7304b0 | |
David Négrier | 0e8be106e5 | |
David Négrier | 64cd07cc39 | |
David Négrier | bc35ade8df | |
David Négrier | 3eb4d7bb1e | |
David Négrier | c8fc8a2834 | |
David Négrier | a4bddbf4fc | |
David Négrier | 42aee9f5c8 | |
David Négrier | 35fdd3bf29 | |
Gregoire Parant | c5916e7b6d | |
Gregoire Parant | 0d104cb307 | |
David Négrier | 91fddd6893 | |
David Négrier | f6e93da48a | |
David Négrier | 8389fad877 | |
David Négrier | e7ddeedb3e | |
David Négrier | f8b5ca04e5 | |
David Négrier | a5bdf68246 | |
PizZaKatZe | d7b0cb83de | |
Mickael Chimansky | 5cb9624b0b | |
David Négrier | f98b107bb7 | |
Mickael Chimansky | 9c4d0aa32f | |
Sebastian Wiesendahl | b6807d274b | |
David Négrier | 969c9fd544 | |
David Négrier | 10e4f515b3 | |
David Négrier | adb535d1b6 | |
Gregoire Parant | 03f1f5bf75 | |
grégoire parant | b0c49478cd | |
Gregoire Parant | 59b4b168e9 | |
Gregoire Parant | 4de9b8ba40 | |
grégoire parant | c41c058fb0 | |
David Négrier | 9c9f046b16 | |
David Négrier | 246174c365 | |
David Négrier | 4efa308473 | |
grégoire parant | f2b9f6c92a | |
David Négrier | 8572959cb0 | |
David Négrier | 3f274df813 | |
David Négrier | 6665317f63 | |
David Négrier | e757d63924 | |
David Négrier | cc53023586 | |
David Négrier | 274f5eba4c | |
Manfred Stock | 0afbb5412e | |
Gregoire Parant | 788e22e8b0 | |
David Négrier | 686427f6fe | |
David Négrier | cc1e3bbe0e | |
Gregoire Parant | f99db4856f | |
Gregoire Parant | ac2bc76239 | |
Gregoire Parant | 7eb38fae83 | |
David Négrier | 3a046cb450 | |
David Négrier | 2f01677849 | |
kharhamel | f2c3d6f158 | |
David Négrier | 8d8d0cb1b7 | |
David Négrier | ce56a5cba1 | |
David Négrier | 755fc44dd9 | |
Paul Bottein | a8b5e8599f | |
Paul Bottein | 608a9ad347 | |
Paul Bottein | b52c546a8c | |
kharhamel | e7120317c1 | |
David Négrier | b7b2934106 | |
Tony Amirault | 72799d4f0e | |
Tony Amirault | 3a05ef6640 | |
Kharhamel | 9849e70613 | |
kharhamel | ca231eee4a | |
Kharhamel | a6a66c7789 | |
Tony Amirault | cdbae5fa43 | |
David Négrier | 8eebed1829 | |
Thomas Alberola | 5cd982f114 | |
grégoire parant | 86f1099247 | |
Gregoire Parant | 45b355f780 | |
Kharhamel | 556d5a4695 | |
Gregoire Parant | 1dd2858424 | |
Gregoire Parant | e2695c58af | |
Gregoire Parant | 803be36def | |
Gregoire Parant | c6903cc4c5 | |
David Négrier | 6432d2459b | |
David Négrier | ab0fe63cb2 | |
Gregoire Parant | 8de05c39c1 | |
Gregoire Parant | beb53df364 | |
Gregoire Parant | 5f88a1a0b9 | |
Gregoire Parant | 232ac6d5d1 | |
Gregoire Parant | 0133609770 | |
Gregoire Parant | 75d8d46c62 | |
Gregoire Parant | d93a8da828 | |
David Négrier | 2452c96bf1 | |
David Négrier | 92246df8b6 | |
Piotr Dobrowolski | 33b9bd773f | |
Oliver Lorenz | 7c89cf0e47 |
|
@ -0,0 +1,2 @@
|
|||
**/node_modules/**
|
||||
**/Dockerfile
|
|
@ -6,3 +6,10 @@ JITSI_ISS=
|
|||
SECRET_JITSI_KEY=
|
||||
ADMIN_API_TOKEN=123
|
||||
START_ROOM_URL=/_/global/maps.workadventure.localhost/Floor0/floor0.json
|
||||
# If your Turn server is configured to use the Turn REST API, you should put the shared auth secret here.
|
||||
# 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=
|
||||
|
||||
# The email address used by Let's encrypt to send renewal warnings (compulsory)
|
||||
ACME_EMAIL=
|
||||
|
|
|
@ -150,6 +150,7 @@ jobs:
|
|||
JITSI_ISS: ${{ secrets.JITSI_ISS }}
|
||||
JITSI_URL: ${{ secrets.JITSI_URL }}
|
||||
SECRET_JITSI_KEY: ${{ secrets.SECRET_JITSI_KEY }}
|
||||
TURN_STATIC_AUTH_SECRET: ${{ secrets.TURN_STATIC_AUTH_SECRET }}
|
||||
with:
|
||||
namespace: workadventure-${{ env.GITHUB_REF_SLUG }}
|
||||
|
||||
|
@ -159,34 +160,5 @@ jobs:
|
|||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
msg: Environment deployed at https://${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com
|
||||
msg: Environment deployed at https://play.${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com
|
||||
check_for_duplicate_msg: true
|
||||
|
||||
- name: Run Cypress tests
|
||||
uses: cypress-io/github-action@v2
|
||||
if: ${{ env.GITHUB_REF_SLUG != 'master' }}
|
||||
env:
|
||||
CYPRESS_BASE_URL: https://play.${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com
|
||||
with:
|
||||
env: host=play.${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com,port=80
|
||||
spec: cypress/integration/spec.js
|
||||
wait-on: https://play.${{ env.GITHUB_REF_SLUG }}.workadventure.test.thecodingmachine.com
|
||||
working-directory: e2e
|
||||
|
||||
- name: Run Cypress tests in prod
|
||||
uses: cypress-io/github-action@v2
|
||||
if: ${{ env.GITHUB_REF_SLUG == 'master' }}
|
||||
env:
|
||||
CYPRESS_BASE_URL: https://play.workadventu.re
|
||||
with:
|
||||
env: host=play.workadventu.re
|
||||
spec: cypress/integration/spec.js
|
||||
wait-on: https://workadventu.re
|
||||
working-directory: e2e
|
||||
|
||||
- name: "Upload the screenshot on test failure"
|
||||
uses: actions/upload-artifact@v1
|
||||
if: failure()
|
||||
with:
|
||||
name: "screenshot"
|
||||
path: "./e2e/cypress/screenshots/spec.js/WorkAdventureGame -- loads (failed).png"
|
||||
|
|
17
README.md
|
@ -1,4 +1,4 @@
|
|||
![](https://github.com/thecodingmachine/workadventure/workflows/Continuous%20Integration/badge.svg)
|
||||
![](https://github.com/thecodingmachine/workadventure/workflows/Continuous%20Integration/badge.svg) [![Discord](https://img.shields.io/discord/821338762134290432?label=Discord)](https://discord.gg/YGtngdh9gt)
|
||||
|
||||
![WorkAdventure landscape image](README-INTRO.jpg)
|
||||
|
||||
|
@ -6,8 +6,6 @@ Demo here : [https://workadventu.re/](https://workadventu.re/).
|
|||
|
||||
# Work Adventure
|
||||
|
||||
## Work in progress
|
||||
|
||||
Work Adventure is a web-based collaborative workspace for small to medium teams (2-100 people) presented in the form of a
|
||||
16-bit video game.
|
||||
|
||||
|
@ -15,7 +13,7 @@ In Work Adventure, you can move around your office and talk to your colleagues (
|
|||
triggered when you move next to a colleague).
|
||||
|
||||
|
||||
## Getting started
|
||||
## Setting up a development environment
|
||||
|
||||
Install Docker.
|
||||
|
||||
|
@ -27,13 +25,14 @@ docker-compose up
|
|||
|
||||
The environment will start.
|
||||
|
||||
You should now be able to browse to http://workadventure.localhost/ and see the application.
|
||||
You should now be able to browse to http://play.workadventure.localhost/ and see the application.
|
||||
You can view the dashboard at http://workadventure.localhost:8080/
|
||||
|
||||
Note: on some OSes, you will need to add this line to your `/etc/hosts` file:
|
||||
|
||||
**/etc/hosts**
|
||||
```
|
||||
workadventure.localhost 127.0.0.1
|
||||
127.0.0.1 workadventure.localhost
|
||||
```
|
||||
|
||||
### MacOS developers, your environment with Vagrant
|
||||
|
@ -101,5 +100,7 @@ Vagrant destroy
|
|||
* `Vagrant halt`: stop your VM Vagrant.
|
||||
* `Vagrant destroy`: delete your VM Vagrant.
|
||||
|
||||
## Features developed
|
||||
You have more details of features developed in back [README.md](./back/README.md).
|
||||
## Setting up a production environment
|
||||
|
||||
The way you set up your production environment will highly depend on your servers.
|
||||
We provide a production ready `docker-compose` file that you can use as a good starting point in the [contrib/docker](https://github.com/thecodingmachine/workadventure/tree/master/contrib/docker) directory.
|
||||
|
|
|
@ -1,16 +1,26 @@
|
|||
FROM thecodingmachine/workadventure-back-base:latest as builder
|
||||
WORKDIR /var/www/messages
|
||||
COPY --chown=docker:docker messages .
|
||||
# protobuf build
|
||||
FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder
|
||||
WORKDIR /usr/src
|
||||
COPY messages .
|
||||
RUN yarn install && yarn proto
|
||||
|
||||
FROM thecodingmachine/nodejs:12
|
||||
|
||||
COPY --chown=docker:docker back .
|
||||
COPY --from=builder --chown=docker:docker /var/www/messages/generated /usr/src/app/src/Messages/generated
|
||||
# typescript build
|
||||
FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder2
|
||||
WORKDIR /usr/src
|
||||
COPY back/yarn.lock back/package.json ./
|
||||
RUN yarn install
|
||||
|
||||
COPY back .
|
||||
COPY --from=builder /usr/src/generated src/Messages/generated
|
||||
ENV NODE_ENV=production
|
||||
RUN yarn run tsc
|
||||
|
||||
CMD ["yarn", "run", "runprod"]
|
||||
# final production image
|
||||
FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76
|
||||
WORKDIR /usr/src
|
||||
COPY back/yarn.lock back/package.json ./
|
||||
COPY --from=builder2 /usr/src/dist /usr/src/dist
|
||||
ENV NODE_ENV=production
|
||||
RUN yarn install --production
|
||||
|
||||
USER node
|
||||
CMD ["yarn", "run", "runprod"]
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
# Back Features
|
||||
|
||||
## Login
|
||||
To start your game, you must authenticate on the server back.
|
||||
When you are authenticated, the back server return token and room starting.
|
||||
```
|
||||
POST => /login
|
||||
Params :
|
||||
email: email of user.
|
||||
```
|
||||
|
||||
## Join a room
|
||||
When a user is connected, the user can join a room.
|
||||
So you must send emit `join-room` with information user:
|
||||
```
|
||||
Socket.io => 'join-room'
|
||||
|
||||
userId: user id of gamer
|
||||
roomId: room id when user enter in game
|
||||
position: {
|
||||
x: position x on map
|
||||
y: position y on map
|
||||
}
|
||||
```
|
||||
All data users are stocked on socket client.
|
||||
|
||||
## Send position user
|
||||
When user move on the map, you can share new position on back with event `user-position`.
|
||||
The information sent:
|
||||
```
|
||||
Socket.io => 'user-position'
|
||||
|
||||
userId: user id of gamer
|
||||
roomId: room id when user enter in game
|
||||
position: {
|
||||
x: position x on map
|
||||
y: position y on map
|
||||
}
|
||||
```
|
||||
All data users are updated on socket client.
|
||||
|
||||
## Receive positions of all users
|
||||
The application sends position of all users in each room in every few 10 milliseconds.
|
||||
The data will pushed on event `user-position`:
|
||||
```
|
||||
Socket.io => 'user-position'
|
||||
|
||||
[
|
||||
{
|
||||
userId: user id of gamer
|
||||
roomId: room id when user enter in game
|
||||
position: {
|
||||
x: position x on map
|
||||
y: position y on map
|
||||
}
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
[<<< back](../README.md)
|
|
@ -1,5 +1,4 @@
|
|||
import {HttpRequest, HttpResponse} from "uWebSockets.js";
|
||||
import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable";
|
||||
import {HttpResponse} from "uWebSockets.js";
|
||||
|
||||
|
||||
export class BaseController {
|
||||
|
|
|
@ -4,7 +4,6 @@ import {HttpRequest, HttpResponse} from "uWebSockets.js";
|
|||
import { parse } from 'query-string';
|
||||
import {App} from "../Server/sifrr.server";
|
||||
import {socketManager} from "../Services/SocketManager";
|
||||
import {ServerWritableStream} from "grpc";
|
||||
|
||||
export class DebugController {
|
||||
constructor(private App : App) {
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
const SECRET_KEY = process.env.SECRET_KEY || "THECODINGMACHINE_SECRET_KEY";
|
||||
const MINIMUM_DISTANCE = process.env.MINIMUM_DISTANCE ? Number(process.env.MINIMUM_DISTANCE) : 64;
|
||||
const GROUP_RADIUS = process.env.GROUP_RADIUS ? Number(process.env.GROUP_RADIUS) : 48;
|
||||
const ALLOW_ARTILLERY = process.env.ALLOW_ARTILLERY ? process.env.ALLOW_ARTILLERY == 'true' : false;
|
||||
const ADMIN_API_URL = process.env.ADMIN_API_URL || '';
|
||||
const ADMIN_API_TOKEN = process.env.ADMIN_API_TOKEN || 'myapitoken';
|
||||
const MAX_USERS_PER_ROOM = parseInt(process.env.MAX_USERS_PER_ROOM || '') || 600;
|
||||
const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80;
|
||||
const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL;
|
||||
const JITSI_ISS = process.env.JITSI_ISS || '';
|
||||
|
@ -12,15 +10,14 @@ const SECRET_JITSI_KEY = process.env.SECRET_JITSI_KEY || '';
|
|||
const HTTP_PORT = parseInt(process.env.HTTP_PORT || '8080') || 8080;
|
||||
const GRPC_PORT = parseInt(process.env.GRPC_PORT || '50051') || 50051;
|
||||
export const SOCKET_IDLE_TIMER = parseInt(process.env.SOCKET_IDLE_TIMER as string) || 30; // maximum time (in second) without activity before a socket is closed
|
||||
export const TURN_STATIC_AUTH_SECRET = process.env.TURN_STATIC_AUTH_SECRET || '';
|
||||
|
||||
export {
|
||||
SECRET_KEY,
|
||||
MINIMUM_DISTANCE,
|
||||
ADMIN_API_URL,
|
||||
ADMIN_API_TOKEN,
|
||||
HTTP_PORT,
|
||||
GRPC_PORT,
|
||||
MAX_USERS_PER_ROOM,
|
||||
GROUP_RADIUS,
|
||||
ALLOW_ARTILLERY,
|
||||
CPU_OVERHEAT_THRESHOLD,
|
||||
|
|
|
@ -7,7 +7,6 @@ import {PositionNotifier} from "./PositionNotifier";
|
|||
import {Movable} from "_Model/Movable";
|
||||
import {extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous} from "./RoomIdentifier";
|
||||
import {arrayIntersect} from "../Services/ArrayHelper";
|
||||
import {MAX_USERS_PER_ROOM} from "../Enum/EnvironmentVariable";
|
||||
import {JoinRoomMessage} from "../Messages/generated/messages_pb";
|
||||
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
||||
import {ZoneSocket} from "src/RoomManager";
|
||||
|
@ -116,8 +115,6 @@ export class GameRoom {
|
|||
this.nextUserId++;
|
||||
this.users.set(user.id, user);
|
||||
this.usersByUuid.set(user.uuid, user);
|
||||
// Let's call update position to trigger the join / leave room
|
||||
//this.updatePosition(socket, userPosition);
|
||||
this.updateUserGroup(user);
|
||||
|
||||
// Notify admins
|
||||
|
@ -149,10 +146,6 @@ export class GameRoom {
|
|||
}
|
||||
}
|
||||
|
||||
get isFull(): boolean {
|
||||
return this.users.size >= MAX_USERS_PER_ROOM;
|
||||
}
|
||||
|
||||
public isEmpty(): boolean {
|
||||
return this.users.size === 0 && this.admins.size === 0;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import {
|
|||
AdminGlobalMessage,
|
||||
AdminMessage,
|
||||
AdminPusherToBackMessage,
|
||||
AdminRoomMessage,
|
||||
BanMessage,
|
||||
EmptyMessage,
|
||||
ItemEventMessage,
|
||||
|
@ -14,7 +15,7 @@ import {
|
|||
ServerToClientMessage,
|
||||
SilentMessage,
|
||||
UserMovesMessage,
|
||||
WebRtcSignalToServerMessage,
|
||||
WebRtcSignalToServerMessage, WorldFullWarningToRoomMessage,
|
||||
ZoneMessage
|
||||
} from "./Messages/generated/messages_pb";
|
||||
import {sendUnaryData, ServerDuplexStream, ServerUnaryCall, ServerWritableStream} from "grpc";
|
||||
|
@ -42,8 +43,13 @@ const roomManager: IRoomManagerServer = {
|
|||
if (room === null || user === null) {
|
||||
if (message.hasJoinroommessage()) {
|
||||
socketManager.handleJoinRoom(call, message.getJoinroommessage() as JoinRoomMessage).then(({room: gameRoom, user: myUser}) => {
|
||||
room = gameRoom;
|
||||
user = myUser;
|
||||
if (call.writable) {
|
||||
room = gameRoom;
|
||||
user = myUser;
|
||||
} else {
|
||||
//Connexion may have been closed before the init was finished, so we have to manually disconnect the user.
|
||||
socketManager.leaveRoom(gameRoom, myUser);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
throw new Error('The first message sent MUST be of type JoinRoomMessage');
|
||||
|
@ -51,12 +57,8 @@ const roomManager: IRoomManagerServer = {
|
|||
} else {
|
||||
if (message.hasJoinroommessage()) {
|
||||
throw new Error('Cannot call JoinRoomMessage twice!');
|
||||
/*} else if (message.hasViewportmessage()) {
|
||||
socketManager.handleViewport(client, message.getViewportmessage() as ViewportMessage);*/
|
||||
} else if (message.hasUsermovesmessage()) {
|
||||
socketManager.handleUserMovesMessage(room, user, message.getUsermovesmessage() as UserMovesMessage);
|
||||
/*} else if (message.hasSetplayerdetailsmessage()) {
|
||||
socketManager.handleSetPlayerDetails(client, message.getSetplayerdetailsmessage() as SetPlayerDetailsMessage);*/
|
||||
} else if (message.hasSilentmessage()) {
|
||||
socketManager.handleSilentMessage(room, user, message.getSilentmessage() as SilentMessage);
|
||||
} else if (message.hasItemeventmessage()) {
|
||||
|
@ -67,8 +69,6 @@ const roomManager: IRoomManagerServer = {
|
|||
socketManager.emitScreenSharing(room, user, message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage);
|
||||
} else if (message.hasPlayglobalmessage()) {
|
||||
socketManager.emitPlayGlobalMessage(room, message.getPlayglobalmessage() as PlayGlobalMessage);
|
||||
/*} else if (message.hasReportplayermessage()){
|
||||
socketManager.handleReportMessage(client, message.getReportplayermessage() as ReportPlayerMessage);*/
|
||||
} else if (message.hasQueryjitsijwtmessage()){
|
||||
socketManager.handleQueryJitsiJwtMessage(user, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
|
||||
}else if (message.hasSendusermessage()) {
|
||||
|
@ -119,10 +119,7 @@ const roomManager: IRoomManagerServer = {
|
|||
socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY());
|
||||
call.end();
|
||||
})
|
||||
|
||||
/*call.on('finish', () => {
|
||||
debug('listenZone finish');
|
||||
})*/
|
||||
|
||||
call.on('close', () => {
|
||||
debug('listenZone connection closed');
|
||||
socketManager.removeZoneListener(call, zoneMessage.getRoomid(), zoneMessage.getX(), zoneMessage.getY());
|
||||
|
@ -150,26 +147,6 @@ const roomManager: IRoomManagerServer = {
|
|||
} else {
|
||||
throw new Error('The first message sent MUST be of type JoinRoomMessage');
|
||||
}
|
||||
} else {
|
||||
/*if (message.hasJoinroommessage()) {
|
||||
throw new Error('Cannot call JoinRoomMessage twice!');
|
||||
} else if (message.hasUsermovesmessage()) {
|
||||
socketManager.handleUserMovesMessage(room, user, message.getUsermovesmessage() as UserMovesMessage);
|
||||
} else if (message.hasSilentmessage()) {
|
||||
socketManager.handleSilentMessage(room, user, message.getSilentmessage() as SilentMessage);
|
||||
} else if (message.hasItemeventmessage()) {
|
||||
socketManager.handleItemEvent(room, user, message.getItemeventmessage() as ItemEventMessage);
|
||||
} else if (message.hasWebrtcsignaltoservermessage()) {
|
||||
socketManager.emitVideo(room, user, message.getWebrtcsignaltoservermessage() as WebRtcSignalToServerMessage);
|
||||
} else if (message.hasWebrtcscreensharingsignaltoservermessage()) {
|
||||
socketManager.emitScreenSharing(room, user, message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage);
|
||||
} else if (message.hasPlayglobalmessage()) {
|
||||
socketManager.emitPlayGlobalMessage(room, message.getPlayglobalmessage() as PlayGlobalMessage);
|
||||
} else if (message.hasQueryjitsijwtmessage()){
|
||||
socketManager.handleQueryJitsiJwtMessage(user, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
|
||||
} else {
|
||||
throw new Error('Unhandled message type');
|
||||
}*/
|
||||
}
|
||||
} catch (e) {
|
||||
emitError(call, e);
|
||||
|
@ -204,10 +181,18 @@ const roomManager: IRoomManagerServer = {
|
|||
},
|
||||
ban(call: ServerUnaryCall<BanMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
||||
// FIXME Work in progress
|
||||
socketManager.banUser(call.request.getRoomid(), call.request.getRecipientuuid(), 'foo bar TODO change this');
|
||||
socketManager.banUser(call.request.getRoomid(), call.request.getRecipientuuid(), call.request.getMessage());
|
||||
|
||||
callback(null, new EmptyMessage());
|
||||
},
|
||||
sendAdminMessageToRoom(call: ServerUnaryCall<AdminRoomMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
||||
socketManager.sendAdminRoomMessage(call.request.getRoomid(), call.request.getMessage());
|
||||
callback(null, new EmptyMessage());
|
||||
},
|
||||
sendWorldFullWarningToRoom(call: ServerUnaryCall<WorldFullWarningToRoomMessage>, callback: sendUnaryData<EmptyMessage>): void {
|
||||
socketManager.dispatchWorlFullWarning(call.request.getRoomid());
|
||||
callback(null, new EmptyMessage());
|
||||
},
|
||||
};
|
||||
|
||||
export {roomManager};
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable";
|
||||
import Axios from "axios";
|
||||
import {v4} from "uuid";
|
||||
|
||||
export interface AdminApiData {
|
||||
organizationSlug: string
|
||||
|
@ -21,13 +20,6 @@ export interface CharacterTexture {
|
|||
rights: string
|
||||
}
|
||||
|
||||
export interface FetchMemberDataByUuidResponse {
|
||||
uuid: string;
|
||||
tags: string[];
|
||||
textures: CharacterTexture[];
|
||||
messages: unknown[];
|
||||
}
|
||||
|
||||
class AdminApi {
|
||||
|
||||
async fetchMapDetails(organizationSlug: string, worldSlug: string, roomSlug: string|undefined): Promise<AdminApiData> {
|
||||
|
@ -52,64 +44,6 @@ class AdminApi {
|
|||
)
|
||||
return res.data;
|
||||
}
|
||||
|
||||
async fetchMemberDataByUuid(uuid: string): Promise<FetchMemberDataByUuidResponse> {
|
||||
if (!ADMIN_API_URL) {
|
||||
return Promise.reject('No admin backoffice set!');
|
||||
}
|
||||
try {
|
||||
const res = await Axios.get(ADMIN_API_URL+'/api/membership/'+uuid,
|
||||
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
|
||||
)
|
||||
return res.data;
|
||||
} catch (e) {
|
||||
if (e?.response?.status == 404) {
|
||||
// If we get an HTTP 404, the token is invalid. Let's perform an anonymous login!
|
||||
console.warn('Cannot find user with uuid "'+uuid+'". Performing an anonymous login instead.');
|
||||
return {
|
||||
uuid: v4(),
|
||||
tags: [],
|
||||
textures: [],
|
||||
messages: [],
|
||||
}
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fetchMemberDataByToken(organizationMemberToken: string): Promise<AdminApiData> {
|
||||
if (!ADMIN_API_URL) {
|
||||
return Promise.reject('No admin backoffice set!');
|
||||
}
|
||||
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
|
||||
const res = await Axios.get(ADMIN_API_URL+'/api/login-url/'+organizationMemberToken,
|
||||
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
|
||||
)
|
||||
return res.data;
|
||||
}
|
||||
|
||||
async fetchCheckUserByToken(organizationMemberToken: string): Promise<AdminApiData> {
|
||||
if (!ADMIN_API_URL) {
|
||||
return Promise.reject('No admin backoffice set!');
|
||||
}
|
||||
//todo: this call can fail if the corresponding world is not activated or if the token is invalid. Handle that case.
|
||||
const res = await Axios.get(ADMIN_API_URL+'/api/check-user/'+organizationMemberToken,
|
||||
{ headers: {"Authorization" : `${ADMIN_API_TOKEN}`} }
|
||||
)
|
||||
return res.data;
|
||||
}
|
||||
|
||||
reportPlayer(reportedUserUuid: string, reportedUserComment: string, reporterUserUuid: string) {
|
||||
return Axios.post(`${ADMIN_API_URL}/api/report`, {
|
||||
reportedUserUuid,
|
||||
reportedUserComment,
|
||||
reporterUserUuid,
|
||||
},
|
||||
{
|
||||
headers: {"Authorization": `${ADMIN_API_TOKEN}`}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const adminApi = new AdminApi();
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import {GameRoom} from "../Model/GameRoom";
|
||||
import {CharacterLayer} from "_Model/Websocket/CharacterLayer";
|
||||
import {
|
||||
ItemEventMessage,
|
||||
ItemStateMessage,
|
||||
|
@ -22,13 +21,24 @@ import {
|
|||
Zone as ProtoZone,
|
||||
BatchToPusherMessage,
|
||||
SubToPusherMessage,
|
||||
UserJoinedZoneMessage, GroupUpdateZoneMessage, GroupLeftZoneMessage, UserLeftZoneMessage, BanUserMessage
|
||||
UserJoinedZoneMessage,
|
||||
GroupUpdateZoneMessage,
|
||||
GroupLeftZoneMessage,
|
||||
WorldFullWarningMessage,
|
||||
UserLeftZoneMessage,
|
||||
BanUserMessage,
|
||||
} from "../Messages/generated/messages_pb";
|
||||
import {User, UserSocket} from "../Model/User";
|
||||
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
|
||||
import {Group} from "../Model/Group";
|
||||
import {cpuTracker} from "./CpuTracker";
|
||||
import {GROUP_RADIUS, JITSI_ISS, MINIMUM_DISTANCE, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable";
|
||||
import {
|
||||
GROUP_RADIUS,
|
||||
JITSI_ISS,
|
||||
MINIMUM_DISTANCE,
|
||||
SECRET_JITSI_KEY,
|
||||
TURN_STATIC_AUTH_SECRET
|
||||
} from "../Enum/EnvironmentVariable";
|
||||
import {Movable} from "../Model/Movable";
|
||||
import {PositionInterface} from "../Model/PositionInterface";
|
||||
import {adminApi, CharacterTexture} from "./AdminApi";
|
||||
|
@ -40,25 +50,16 @@ import {ZoneSocket} from "../RoomManager";
|
|||
import {Zone} from "_Model/Zone";
|
||||
import Debug from "debug";
|
||||
import {Admin} from "_Model/Admin";
|
||||
import crypto from "crypto";
|
||||
|
||||
|
||||
const debug = Debug('sockermanager');
|
||||
|
||||
interface AdminSocketRoomsList {
|
||||
[index: string]: number;
|
||||
}
|
||||
interface AdminSocketUsersList {
|
||||
[index: string]: boolean;
|
||||
}
|
||||
|
||||
export interface AdminSocketData {
|
||||
rooms: AdminSocketRoomsList,
|
||||
users: AdminSocketUsersList,
|
||||
}
|
||||
|
||||
function emitZoneMessage(subMessage: SubToPusherMessage, socket: ZoneSocket): void {
|
||||
// TODO: should we batch those every 100ms?
|
||||
const batchMessage = new BatchToPusherMessage();
|
||||
batchMessage.addPayload(subMessage);
|
||||
|
||||
|
||||
socket.write(batchMessage);
|
||||
}
|
||||
|
@ -75,68 +76,20 @@ export class SocketManager {
|
|||
});
|
||||
}
|
||||
|
||||
/*getAdminSocketDataFor(roomId:string): AdminSocketData {
|
||||
const data:AdminSocketData = {
|
||||
rooms: {},
|
||||
users: {},
|
||||
}
|
||||
const room = this.rooms.get(roomId);
|
||||
if (room === undefined) {
|
||||
return data;
|
||||
}
|
||||
const users = room.getUsers();
|
||||
data.rooms[roomId] = users.size;
|
||||
users.forEach(user => {
|
||||
data.users[user.uuid] = true
|
||||
})
|
||||
return data;
|
||||
}*/
|
||||
|
||||
public async handleJoinRoom(socket: UserSocket, joinRoomMessage: JoinRoomMessage): Promise<{ room: GameRoom; user: User }> {
|
||||
/*const positionMessage = joinRoomMessage.getPositionmessage();
|
||||
if (positionMessage === undefined) {
|
||||
// TODO: send error message?
|
||||
throw new Error('Empty pointMessage found in JoinRoomMessage');
|
||||
}*/
|
||||
|
||||
//const position = ProtobufUtils.toPointInterface(positionMessage);
|
||||
//const viewport = client.viewport;
|
||||
|
||||
//this.sockets.set(client.userId, client); //todo: should this be at the end of the function?
|
||||
|
||||
|
||||
//join new previous room
|
||||
const {room, user} = await this.joinRoom(socket, joinRoomMessage);
|
||||
|
||||
//const things = room.setViewport(client, viewport);
|
||||
|
||||
|
||||
if (!socket.writable) {
|
||||
console.warn('Socket was aborted');
|
||||
return {
|
||||
room,
|
||||
user
|
||||
};
|
||||
}
|
||||
const roomJoinedMessage = new RoomJoinedMessage();
|
||||
roomJoinedMessage.setTagList(joinRoomMessage.getTagList());
|
||||
/*for (const thing of things) {
|
||||
if (thing instanceof User) {
|
||||
const player: ExSocketInterface|undefined = this.sockets.get(thing.id);
|
||||
if (player === undefined) {
|
||||
console.warn('Something went wrong. The World contains a user "'+thing.id+"' but this user does not exist in the sockets list!");
|
||||
continue;
|
||||
}
|
||||
|
||||
const userJoinedMessage = new UserJoinedMessage();
|
||||
userJoinedMessage.setUserid(thing.id);
|
||||
userJoinedMessage.setName(player.name);
|
||||
userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(player.characterLayers));
|
||||
userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(player.position));
|
||||
|
||||
roomJoinedMessage.addUser(userJoinedMessage);
|
||||
roomJoinedMessage.setTagList(joinRoomMessage.getTagList());
|
||||
} else if (thing instanceof Group) {
|
||||
const groupUpdateMessage = new GroupUpdateMessage();
|
||||
groupUpdateMessage.setGroupid(thing.getId());
|
||||
groupUpdateMessage.setPosition(ProtobufUtils.toPointMessage(thing.getPosition()));
|
||||
|
||||
roomJoinedMessage.addGroup(groupUpdateMessage);
|
||||
} else {
|
||||
console.error("Unexpected type for Movable returned by setViewport");
|
||||
}
|
||||
}*/
|
||||
|
||||
for (const [itemId, item] of room.getItemsState().entries()) {
|
||||
const itemStateMessage = new ItemStateMessage();
|
||||
|
@ -150,9 +103,6 @@ export class SocketManager {
|
|||
|
||||
const serverToClientMessage = new ServerToClientMessage();
|
||||
serverToClientMessage.setRoomjoinedmessage(roomJoinedMessage);
|
||||
|
||||
//user.socket.write(serverToClientMessage);
|
||||
console.log('SENDING MESSAGE roomJoinedMessage');
|
||||
socket.write(serverToClientMessage);
|
||||
|
||||
return {
|
||||
|
@ -160,13 +110,6 @@ export class SocketManager {
|
|||
user
|
||||
};
|
||||
|
||||
/*const serverToClientMessage = new ServerToClientMessage();
|
||||
serverToClientMessage.setRoomjoinedmessage(roomJoinedMessage);
|
||||
|
||||
if (!client.disconnecting) {
|
||||
client.send(serverToClientMessage.serializeBinary().buffer, true);
|
||||
}*/
|
||||
|
||||
}
|
||||
|
||||
handleUserMovesMessage(room: GameRoom, user: User, userMovesMessage: UserMovesMessage) {
|
||||
|
@ -275,6 +218,12 @@ export class SocketManager {
|
|||
const webrtcSignalToClient = new WebRtcSignalToClientMessage();
|
||||
webrtcSignalToClient.setUserid(user.id);
|
||||
webrtcSignalToClient.setSignal(data.getSignal());
|
||||
// TODO: only compute credentials if data.signal.type === "offer"
|
||||
if (TURN_STATIC_AUTH_SECRET !== '') {
|
||||
const {username, password} = this.getTURNCredentials(''+user.id, TURN_STATIC_AUTH_SECRET);
|
||||
webrtcSignalToClient.setWebrtcusername(username);
|
||||
webrtcSignalToClient.setWebrtcpassword(password);
|
||||
}
|
||||
|
||||
const serverToClientMessage = new ServerToClientMessage();
|
||||
serverToClientMessage.setWebrtcsignaltoclientmessage(webrtcSignalToClient);
|
||||
|
@ -295,6 +244,12 @@ export class SocketManager {
|
|||
const webrtcSignalToClient = new WebRtcSignalToClientMessage();
|
||||
webrtcSignalToClient.setUserid(user.id);
|
||||
webrtcSignalToClient.setSignal(data.getSignal());
|
||||
// TODO: only compute credentials if data.signal.type === "offer"
|
||||
if (TURN_STATIC_AUTH_SECRET !== '') {
|
||||
const {username, password} = this.getTURNCredentials(''+user.id, TURN_STATIC_AUTH_SECRET);
|
||||
webrtcSignalToClient.setWebrtcusername(username);
|
||||
webrtcSignalToClient.setWebrtcpassword(password);
|
||||
}
|
||||
|
||||
const serverToClientMessage = new ServerToClientMessage();
|
||||
serverToClientMessage.setWebrtcscreensharingsignaltoclientmessage(webrtcSignalToClient);
|
||||
|
@ -315,8 +270,6 @@ export class SocketManager {
|
|||
debug('Room is empty. Deleting room "%s"', room.roomId);
|
||||
}
|
||||
} finally {
|
||||
//delete Client.roomId;
|
||||
//this.sockets.delete(Client.userId);
|
||||
clientEventsEmitter.emitClientLeave(user.uuid, room.roomId);
|
||||
console.log('A user left');
|
||||
}
|
||||
|
@ -351,20 +304,14 @@ export class SocketManager {
|
|||
|
||||
const roomId = joinRoomMessage.getRoomid();
|
||||
|
||||
const world = await socketManager.getOrCreateRoom(roomId);
|
||||
|
||||
// Dispatch groups position to newly connected user
|
||||
/*world.getGroups().forEach((group: Group) => {
|
||||
this.emitCreateUpdateGroupEvent(socket, group);
|
||||
});*/
|
||||
const room = await socketManager.getOrCreateRoom(roomId);
|
||||
|
||||
//join world
|
||||
const user = world.join(socket, joinRoomMessage);
|
||||
const user = room.join(socket, joinRoomMessage);
|
||||
|
||||
clientEventsEmitter.emitClientJoin(user.uuid, roomId);
|
||||
//console.log(new Date().toISOString() + ' A user joined (', this.sockets.size, ' connected users)');
|
||||
console.log(new Date().toISOString() + ' A user joined');
|
||||
return {room: world, user};
|
||||
return {room, user};
|
||||
}
|
||||
|
||||
private onZoneEnter(thing: Movable, fromZone: Zone|null, listener: ZoneSocket) {
|
||||
|
@ -472,10 +419,6 @@ export class SocketManager {
|
|||
}
|
||||
|
||||
private joinWebRtcRoom(user: User, group: Group) {
|
||||
/*const roomId: string = "webrtcroom"+group.getId();
|
||||
if (user.socket.webRtcRoomId === roomId) {
|
||||
return;
|
||||
}*/
|
||||
|
||||
for (const otherUser of group.getUsers()) {
|
||||
if (user === otherUser) {
|
||||
|
@ -487,6 +430,11 @@ export class SocketManager {
|
|||
webrtcStartMessage1.setUserid(otherUser.id);
|
||||
webrtcStartMessage1.setName(otherUser.name);
|
||||
webrtcStartMessage1.setInitiator(true);
|
||||
if (TURN_STATIC_AUTH_SECRET !== '') {
|
||||
const {username, password} = this.getTURNCredentials(''+otherUser.id, TURN_STATIC_AUTH_SECRET);
|
||||
webrtcStartMessage1.setWebrtcusername(username);
|
||||
webrtcStartMessage1.setWebrtcpassword(password);
|
||||
}
|
||||
|
||||
const serverToClientMessage1 = new ServerToClientMessage();
|
||||
serverToClientMessage1.setWebrtcstartmessage(webrtcStartMessage1);
|
||||
|
@ -500,6 +448,11 @@ export class SocketManager {
|
|||
webrtcStartMessage2.setUserid(user.id);
|
||||
webrtcStartMessage2.setName(user.name);
|
||||
webrtcStartMessage2.setInitiator(false);
|
||||
if (TURN_STATIC_AUTH_SECRET !== '') {
|
||||
const {username, password} = this.getTURNCredentials(''+user.id, TURN_STATIC_AUTH_SECRET);
|
||||
webrtcStartMessage2.setWebrtcusername(username);
|
||||
webrtcStartMessage2.setWebrtcpassword(password);
|
||||
}
|
||||
|
||||
const serverToClientMessage2 = new ServerToClientMessage();
|
||||
serverToClientMessage2.setWebrtcstartmessage(webrtcStartMessage2);
|
||||
|
@ -512,6 +465,25 @@ export class SocketManager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a unique user/password for the TURN server, using a shared secret between the WorkAdventure API server
|
||||
* and the Coturn server.
|
||||
* The Coturn server should be initialized with parameters: `--use-auth-secret --static-auth-secret=MySecretKey`
|
||||
*/
|
||||
private getTURNCredentials(name: string, secret: string): {username: string, password: string} {
|
||||
const unixTimeStamp = Math.floor(Date.now()/1000) + 4*3600; // this credential would be valid for the next 4 hours
|
||||
const username = [unixTimeStamp, name].join(':');
|
||||
const hmac = crypto.createHmac('sha1', secret);
|
||||
hmac.setEncoding('base64');
|
||||
hmac.write(username);
|
||||
hmac.end();
|
||||
const password = hmac.read();
|
||||
return {
|
||||
username: username,
|
||||
password: password
|
||||
};
|
||||
}
|
||||
|
||||
//disconnect user
|
||||
private disConnectedUser(user: User, group: Group) {
|
||||
// Most of the time, sending a disconnect event to one of the players is enough (the player will close the connection
|
||||
|
@ -644,33 +616,6 @@ export class SocketManager {
|
|||
}, 10000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the characterLayers received from the front (as an array of string) with the custom textures from the back.
|
||||
*/
|
||||
static mergeCharacterLayersAndCustomTextures(characterLayers: string[], memberTextures: CharacterTexture[]): CharacterLayer[] {
|
||||
const characterLayerObjs: CharacterLayer[] = [];
|
||||
for (const characterLayer of characterLayers) {
|
||||
if (characterLayer.startsWith('customCharacterTexture')) {
|
||||
const customCharacterLayerId: number = +characterLayer.substr(22);
|
||||
for (const memberTexture of memberTextures) {
|
||||
if (memberTexture.id == customCharacterLayerId) {
|
||||
characterLayerObjs.push({
|
||||
name: characterLayer,
|
||||
url: memberTexture.url
|
||||
})
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
characterLayerObjs.push({
|
||||
name: characterLayer,
|
||||
url: undefined
|
||||
})
|
||||
}
|
||||
}
|
||||
return characterLayerObjs;
|
||||
}
|
||||
|
||||
public addZoneListener(call: ZoneSocket, roomId: string, x: number, y: number): void {
|
||||
const room = this.rooms.get(roomId);
|
||||
if (!room) {
|
||||
|
@ -724,11 +669,6 @@ export class SocketManager {
|
|||
public async handleJoinAdminRoom(admin: Admin, roomId: string): Promise<GameRoom> {
|
||||
const room = await socketManager.getOrCreateRoom(roomId);
|
||||
|
||||
// Dispatch groups position to newly connected user
|
||||
/*world.getGroups().forEach((group: Group) => {
|
||||
this.emitCreateUpdateGroupEvent(socket, group);
|
||||
});*/
|
||||
|
||||
room.adminJoin(admin);
|
||||
|
||||
return room;
|
||||
|
@ -746,7 +686,7 @@ export class SocketManager {
|
|||
public sendAdminMessage(roomId: string, recipientUuid: string, message: string): void {
|
||||
const room = this.rooms.get(roomId);
|
||||
if (!room) {
|
||||
console.error("In sendAdminMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?");
|
||||
console.error("In sendAdminMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -758,12 +698,12 @@ export class SocketManager {
|
|||
|
||||
const sendUserMessage = new SendUserMessage();
|
||||
sendUserMessage.setMessage(message);
|
||||
sendUserMessage.setType('ban');
|
||||
sendUserMessage.setType('ban'); //todo: is the type correct?
|
||||
|
||||
const subToPusherMessage = new SubToPusherMessage();
|
||||
subToPusherMessage.setSendusermessage(sendUserMessage);
|
||||
const serverToClientMessage = new ServerToClientMessage();
|
||||
serverToClientMessage.setSendusermessage(sendUserMessage);
|
||||
|
||||
recipient.socket.write(subToPusherMessage);
|
||||
recipient.socket.write(serverToClientMessage);
|
||||
}
|
||||
|
||||
public banUser(roomId: string, recipientUuid: string, message: string): void {
|
||||
|
@ -782,18 +722,56 @@ export class SocketManager {
|
|||
// Let's leave the room now.
|
||||
room.leave(recipient);
|
||||
|
||||
const sendUserMessage = new SendUserMessage();
|
||||
sendUserMessage.setMessage(message);
|
||||
sendUserMessage.setType('banned');
|
||||
const banUserMessage = new BanUserMessage();
|
||||
banUserMessage.setMessage(message);
|
||||
banUserMessage.setType('banned');
|
||||
|
||||
const subToPusherMessage = new SubToPusherMessage();
|
||||
subToPusherMessage.setSendusermessage(sendUserMessage);
|
||||
|
||||
recipient.socket.write(subToPusherMessage);
|
||||
const serverToClientMessage = new ServerToClientMessage();
|
||||
serverToClientMessage.setBanusermessage(banUserMessage);
|
||||
|
||||
// Let's close the connection when the user is banned.
|
||||
recipient.socket.write(serverToClientMessage);
|
||||
recipient.socket.end();
|
||||
}
|
||||
|
||||
|
||||
sendAdminRoomMessage(roomId: string, message: string) {
|
||||
const room = this.rooms.get(roomId);
|
||||
if (!room) {
|
||||
//todo: this should cause the http call to return a 500
|
||||
console.error("In sendAdminRoomMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?");
|
||||
return;
|
||||
}
|
||||
|
||||
room.getUsers().forEach((recipient) => {
|
||||
const sendUserMessage = new SendUserMessage();
|
||||
sendUserMessage.setMessage(message);
|
||||
sendUserMessage.setType('message');
|
||||
|
||||
const clientMessage = new ServerToClientMessage();
|
||||
clientMessage.setSendusermessage(sendUserMessage);
|
||||
|
||||
recipient.socket.write(clientMessage);
|
||||
});
|
||||
}
|
||||
|
||||
dispatchWorlFullWarning(roomId: string,): void {
|
||||
const room = this.rooms.get(roomId);
|
||||
if (!room) {
|
||||
//todo: this should cause the http call to return a 500
|
||||
console.error("In sendAdminRoomMessage, could not find room with id '" + roomId + "'. Maybe the room was closed a few milliseconds ago and there was a race condition?");
|
||||
return;
|
||||
}
|
||||
|
||||
room.getUsers().forEach((recipient) => {
|
||||
const worldFullMessage = new WorldFullWarningMessage();
|
||||
|
||||
const clientMessage = new ServerToClientMessage();
|
||||
clientMessage.setWorldfullwarningmessage(worldFullMessage);
|
||||
|
||||
recipient.socket.write(clientMessage);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const socketManager = new SocketManager();
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# The base domain
|
||||
DOMAIN=workadventure.localhost
|
||||
|
||||
DEBUG_MODE=false
|
||||
JITSI_URL=meet.jit.si
|
||||
# If your Jitsi environment has authentication set up, you MUST set JITSI_PRIVATE_MODE to "true" and you MUST pass a SECRET_JITSI_KEY to generate the JWT secret
|
||||
JITSI_PRIVATE_MODE=false
|
||||
JITSI_ISS=
|
||||
SECRET_JITSI_KEY=
|
||||
|
||||
# URL of the TURN server (needed to "punch a hole" through some networks for P2P connections)
|
||||
TURN_SERVER=
|
||||
TURN_USER=
|
||||
TURN_PASSWORD=
|
||||
|
||||
# The URL used by default, in the form: "/_/global/map/url.json"
|
||||
START_ROOM_URL=/_/global/maps.workadventu.re/Floor0/floor0.json
|
||||
|
||||
# The email address used by Let's encrypt to send renewal warnings (compulsory)
|
||||
ACME_EMAIL=
|
|
@ -0,0 +1,100 @@
|
|||
version: "3.3"
|
||||
services:
|
||||
reverse-proxy:
|
||||
image: traefik:v2.3
|
||||
command:
|
||||
- --log.level=WARN
|
||||
#- --api.insecure=true
|
||||
- --providers.docker
|
||||
- --entryPoints.web.address=:80
|
||||
- --entrypoints.web.http.redirections.entryPoint.to=websecure
|
||||
- --entrypoints.web.http.redirections.entryPoint.scheme=https
|
||||
- --entryPoints.websecure.address=:443
|
||||
- --certificatesresolvers.myresolver.acme.email=${ACME_EMAIL}
|
||||
- --certificatesresolvers.myresolver.acme.storage=/acme.json
|
||||
# used during the challenge
|
||||
- --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
# The Web UI (enabled by --api.insecure=true)
|
||||
#- "8080:8080"
|
||||
depends_on:
|
||||
- pusher
|
||||
- front
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./acme.json:/acme.json
|
||||
restart: unless-stopped
|
||||
|
||||
|
||||
front:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: front/Dockerfile
|
||||
#image: thecodingmachine/workadventure-front:master
|
||||
environment:
|
||||
DEBUG_MODE: "$DEBUG_MODE"
|
||||
JITSI_URL: $JITSI_URL
|
||||
JITSI_PRIVATE_MODE: "$JITSI_PRIVATE_MODE"
|
||||
API_URL: pusher.${DOMAIN}
|
||||
TURN_SERVER: "${TURN_SERVER}"
|
||||
TURN_USER: "${TURN_USER}"
|
||||
TURN_PASSWORD: "${TURN_PASSWORD}"
|
||||
START_ROOM_URL: "${START_ROOM_URL}"
|
||||
labels:
|
||||
- "traefik.http.routers.front.rule=Host(`play.${DOMAIN}`)"
|
||||
- "traefik.http.routers.front.entryPoints=web,traefik"
|
||||
- "traefik.http.services.front.loadbalancer.server.port=80"
|
||||
- "traefik.http.routers.front-ssl.rule=Host(`play.${DOMAIN}`)"
|
||||
- "traefik.http.routers.front-ssl.entryPoints=websecure"
|
||||
- "traefik.http.routers.front-ssl.tls=true"
|
||||
- "traefik.http.routers.front-ssl.service=front"
|
||||
- "traefik.http.routers.front-ssl.tls.certresolver=myresolver"
|
||||
restart: unless-stopped
|
||||
|
||||
pusher:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: pusher/Dockerfile
|
||||
#image: thecodingmachine/workadventure-pusher:master
|
||||
command: yarn run runprod
|
||||
environment:
|
||||
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||
SECRET_KEY: yourSecretKey
|
||||
API_URL: back:50051
|
||||
JITSI_URL: $JITSI_URL
|
||||
JITSI_ISS: $JITSI_ISS
|
||||
labels:
|
||||
- "traefik.http.routers.pusher.rule=Host(`pusher.${DOMAIN}`)"
|
||||
- "traefik.http.routers.pusher.entryPoints=web,traefik"
|
||||
- "traefik.http.services.pusher.loadbalancer.server.port=8080"
|
||||
- "traefik.http.routers.pusher-ssl.rule=Host(`pusher.${DOMAIN}`)"
|
||||
- "traefik.http.routers.pusher-ssl.entryPoints=websecure"
|
||||
- "traefik.http.routers.pusher-ssl.tls=true"
|
||||
- "traefik.http.routers.pusher-ssl.service=pusher"
|
||||
- "traefik.http.routers.pusher-ssl.tls.certresolver=myresolver"
|
||||
restart: unless-stopped
|
||||
|
||||
back:
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: back/Dockerfile
|
||||
#image: thecodingmachine/workadventure-back:master
|
||||
command: yarn run runprod
|
||||
environment:
|
||||
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
||||
ADMIN_API_URL: "$ADMIN_API_URL"
|
||||
JITSI_URL: $JITSI_URL
|
||||
JITSI_ISS: $JITSI_ISS
|
||||
labels:
|
||||
- "traefik.http.routers.back.rule=Host(`api.${DOMAIN}`)"
|
||||
- "traefik.http.routers.back.entryPoints=web"
|
||||
- "traefik.http.services.back.loadbalancer.server.port=8080"
|
||||
- "traefik.http.routers.back-ssl.rule=Host(`api.${DOMAIN}`)"
|
||||
- "traefik.http.routers.back-ssl.entryPoints=websecure"
|
||||
- "traefik.http.routers.back-ssl.tls=true"
|
||||
- "traefik.http.routers.back-ssl.service=back"
|
||||
- "traefik.http.routers.back-ssl.tls.certresolver=myresolver"
|
||||
restart: unless-stopped
|
|
@ -3,6 +3,7 @@
|
|||
local namespace = env.GITHUB_REF_SLUG,
|
||||
local tag = namespace,
|
||||
local url = if namespace == "master" then "workadventu.re" else namespace+".workadventure.test.thecodingmachine.com",
|
||||
// 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",
|
||||
"version": "1.0",
|
||||
|
@ -21,9 +22,13 @@
|
|||
"JITSI_ISS": env.JITSI_ISS,
|
||||
"JITSI_URL": env.JITSI_URL,
|
||||
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
|
||||
} + if adminUrl != null then {
|
||||
"TURN_STATIC_AUTH_SECRET": env.TURN_STATIC_AUTH_SECRET,
|
||||
} + (if adminUrl != null then {
|
||||
"ADMIN_API_URL": adminUrl,
|
||||
} else {}
|
||||
} 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"
|
||||
}
|
||||
},
|
||||
"back2": {
|
||||
"image": "thecodingmachine/workadventure-back:"+tag,
|
||||
|
@ -39,9 +44,13 @@
|
|||
"JITSI_ISS": env.JITSI_ISS,
|
||||
"JITSI_URL": env.JITSI_URL,
|
||||
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
|
||||
} + if adminUrl != null then {
|
||||
"TURN_STATIC_AUTH_SECRET": env.TURN_STATIC_AUTH_SECRET,
|
||||
} + (if adminUrl != null then {
|
||||
"ADMIN_API_URL": adminUrl,
|
||||
} else {}
|
||||
} 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"
|
||||
}
|
||||
},
|
||||
"pusher": {
|
||||
"replicas": 2,
|
||||
|
@ -58,9 +67,12 @@
|
|||
"JITSI_URL": env.JITSI_URL,
|
||||
"API_URL": "back1:50051,back2:50051",
|
||||
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
|
||||
} + if adminUrl != null then {
|
||||
} + (if adminUrl != null then {
|
||||
"ADMIN_API_URL": adminUrl,
|
||||
} else {}
|
||||
} 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"
|
||||
}
|
||||
},
|
||||
"front": {
|
||||
"image": "thecodingmachine/workadventure-front:"+tag,
|
||||
|
@ -79,6 +91,7 @@
|
|||
"TURN_USER": "workadventure",
|
||||
"TURN_PASSWORD": "WorkAdventure123",
|
||||
"JITSI_PRIVATE_MODE": if env.SECRET_JITSI_KEY != '' then "true" else "false",
|
||||
"START_ROOM_URL": "/_/global/maps."+url+"/Floor0/floor0.json"
|
||||
//"GA_TRACKING_ID": "UA-10196481-11"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
|
||||
wait_app:
|
||||
image: dadarek/wait-for-dependencies
|
||||
depends_on:
|
||||
- reverse-proxy
|
||||
command: front:8080
|
||||
cypress:
|
||||
# the Docker image to use from https://github.com/cypress-io/cypress-docker-images
|
||||
image: "cypress/included:3.8.3"
|
||||
depends_on:
|
||||
- reverse-proxy
|
||||
environment:
|
||||
# pass base url to test pointing at the web application
|
||||
- CYPRESS_baseUrl=http://front:8080
|
||||
working_dir: /e2e
|
||||
volumes:
|
||||
- ./e2e/:/e2e
|
|
@ -31,9 +31,12 @@ services:
|
|||
ADMIN_URL: workadventure.localhost
|
||||
STARTUP_COMMAND_1: ./templater.sh
|
||||
STARTUP_COMMAND_2: yarn install
|
||||
TURN_SERVER: "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443"
|
||||
TURN_USER: workadventure
|
||||
TURN_PASSWORD: WorkAdventure123
|
||||
STUN_SERVER: "stun:stun.l.google.com:19302"
|
||||
TURN_SERVER: "turn:coturn.workadventure.localhost:3478,turns:coturn.workadventure.localhost:5349"
|
||||
# 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: ""
|
||||
TURN_PASSWORD: ""
|
||||
START_ROOM_URL: "$START_ROOM_URL"
|
||||
command: yarn run start
|
||||
volumes:
|
||||
|
@ -50,10 +53,8 @@ services:
|
|||
pusher:
|
||||
image: thecodingmachine/nodejs:12
|
||||
command: yarn dev
|
||||
#command: yarn run prod
|
||||
#command: yarn run profile
|
||||
environment:
|
||||
DEBUG: "*"
|
||||
DEBUG: "socket:*"
|
||||
STARTUP_COMMAND_1: yarn install
|
||||
SECRET_JITSI_KEY: "$SECRET_JITSI_KEY"
|
||||
SECRET_KEY: yourSecretKey
|
||||
|
@ -108,6 +109,7 @@ services:
|
|||
ADMIN_API_TOKEN: "$ADMIN_API_TOKEN"
|
||||
JITSI_URL: $JITSI_URL
|
||||
JITSI_ISS: $JITSI_ISS
|
||||
TURN_STATIC_AUTH_SECRET: SomeStaticAuthSecret
|
||||
volumes:
|
||||
- ./back:/usr/src/app
|
||||
labels:
|
||||
|
@ -149,3 +151,28 @@ services:
|
|||
- ./back:/usr/src/back
|
||||
- ./front:/usr/src/front
|
||||
- ./pusher:/usr/src/pusher
|
||||
|
||||
# coturn:
|
||||
# image: coturn/coturn:4.5.2
|
||||
# command:
|
||||
# - turnserver
|
||||
# #- -c=/etc/coturn/turnserver.conf
|
||||
# - --log-file=stdout
|
||||
# - --external-ip=$$(detect-external-ip)
|
||||
# - --listening-port=3478
|
||||
# - --min-port=10000
|
||||
# - --max-port=10010
|
||||
# - --tls-listening-port=5349
|
||||
# - --listening-ip=0.0.0.0
|
||||
# - --realm=coturn.workadventure.localhost
|
||||
# - --server-name=coturn.workadventure.localhost
|
||||
# - --lt-cred-mech
|
||||
# # Enable Coturn "REST API" to validate temporary passwords.
|
||||
# #- --use-auth-secret
|
||||
# #- --static-auth-secret=SomeStaticAuthSecret
|
||||
# #- --userdb=/var/lib/turn/turndb
|
||||
# - --user=workadventure:WorkAdventure123
|
||||
# # use real-valid certificate/privatekey files
|
||||
# #- --cert=/root/letsencrypt/fullchain.pem
|
||||
# #- --pkey=/root/letsencrypt/privkey.pem
|
||||
# network_mode: host
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
screenshots/
|
||||
videos/
|
||||
node_modules/
|
|
@ -1,36 +0,0 @@
|
|||
# Testing with cypress
|
||||
|
||||
This project use [cypress](https://www.cypress.io/) to do functional testing of the website.
|
||||
Unfortunately we cannot integrate it with docker-compose for the moment, so you will need to install some packages locally on your pc.
|
||||
|
||||
## Getting Started
|
||||
|
||||
You will need to install theses dependancies on linux (don't know about mac):
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install libgtk2.0-0 libgtk-3-0 libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb
|
||||
```
|
||||
|
||||
Cypress can be installed locally in the e2e directory
|
||||
```bash
|
||||
cd e2e
|
||||
npm install
|
||||
```
|
||||
|
||||
|
||||
How to use:
|
||||
```bash
|
||||
npm run cy:run
|
||||
npm run cy:open
|
||||
```
|
||||
|
||||
The first command will run all tests in the terminal, while the second will open the interactive task runner which allow you to easily manage the test workflow
|
||||
|
||||
[More details here](https://docs.cypress.io/guides/getting-started/testing-your-app.html#Step-1-Start-your-server)
|
||||
|
||||
## How to test a game
|
||||
|
||||
Cypress cannot "see" and so cannot directly manipulate the canva created by Phaser.
|
||||
|
||||
This means we have to do workarounds such as exposing core objects in the window so that cypress can manipulate them or doing console that cypress can catch.
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"baseUrl": "http://workadventure.localhost",
|
||||
"video": false,
|
||||
"defaultCommandTimeout": 20000,
|
||||
"pluginsFile": false,
|
||||
"supportFile": false
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
Cypress.on('window:before:load', (win) => {
|
||||
// because this is called before any scripts
|
||||
// have loaded - the ga function is undefined
|
||||
// so we need to create it.
|
||||
win.cypressAsserter = cy.stub().as('ca')
|
||||
})
|
||||
|
||||
describe('WorkAdventureGame', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/', {
|
||||
onBeforeLoad (win) {
|
||||
cy.spy(win.console, 'log').as('console.log')
|
||||
},
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
it('loads', () => {
|
||||
cy.get('@console.log').should('be.calledWith', 'Started the game')
|
||||
cy.get('@console.log').should('be.calledWith', 'Preloading')
|
||||
cy.get('@console.log').should('be.calledWith', 'Preloading done')
|
||||
cy.get('@console.log').should('be.calledWith', 'startInit')
|
||||
cy.get('@console.log').should('be.calledWith', 'startInit done')
|
||||
});
|
||||
});
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"cypress": "^3.8.3"
|
||||
},
|
||||
"scripts": {
|
||||
"cy:run": "cypress run",
|
||||
"cy:open": "cypress open"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
index.html
|
||||
index.tmpl.html.tmp
|
||||
style.*.css
|
|
@ -29,7 +29,6 @@
|
|||
|
||||
|
||||
<base href="/">
|
||||
<link rel="stylesheet" href="/resources/style/style.css">
|
||||
<title>Binary Kitchen World</title>
|
||||
</head>
|
||||
<body id="body" style="margin: 0; background-color: #000">
|
||||
|
@ -72,98 +71,54 @@
|
|||
</div>
|
||||
</div>
|
||||
<div id="cowebsite" class="cowebsite hidden">
|
||||
<button class="close-btn" id="cowebsite-close">
|
||||
<aside id="cowebsite-aside">
|
||||
<img src="/static/images/menu.svg" alt="hold to resize"/>
|
||||
</aside>
|
||||
<main id="cowebsite-main">
|
||||
</main>
|
||||
<button class="top-right-btn" id="cowebsite-fullscreen">
|
||||
<img id="cowebsite-fullscreen-open" src="resources/logos/monitor.svg"/>
|
||||
<img id="cowebsite-fullscreen-close" style="display: none;" src="resources/logos/monitor-close.svg"/>
|
||||
</button>
|
||||
<button class="top-right-btn" id="cowebsite-close">
|
||||
<img src="resources/logos/close.svg"/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="audioplayerctrl" class="hidden">
|
||||
<div class="audioplayer">
|
||||
<button type="button" id="audioplayer_mute" class="fa fa-volump-up">
|
||||
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-volume-up" fill="white" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" d="M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06zM6 5.04L4.312 6.39A.5.5 0 0 1 4 6.5H2v3h2a.5.5 0 0 1 .312.11L6 10.96V5.04z" />
|
||||
<g id="audioplayer_volume_icon_playing">
|
||||
<path d="M11.536 14.01A8.473 8.473 0 0 0 14.026 8a8.473 8.473 0 0 0-2.49-6.01l-.708.707A7.476 7.476 0 0 1 13.025 8c0 2.071-.84 3.946-2.197 5.303l.708.707z" />
|
||||
<path d="M10.121 12.596A6.48 6.48 0 0 0 12.025 8a6.48 6.48 0 0 0-1.904-4.596l-.707.707A5.483 5.483 0 0 1 11.025 8a5.483 5.483 0 0 1-1.61 3.89l.706.706z" />
|
||||
<path d="M8.707 11.182A4.486 4.486 0 0 0 10.025 8a4.486 4.486 0 0 0-1.318-3.182L8 5.525A3.489 3.489 0 0 1 9.025 8 3.49 3.49 0 0 1 8 10.475l.707.707z" />
|
||||
</g>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="audioplayer">
|
||||
<input type="range" id="audioplayer_volume" min="0" max="1" step="0.025" value="1" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="audioplayer">
|
||||
<label id="label-audioplayer_decrease_while_talking" for="audiooplayer_decrease_while_talking" title="decrease background volume by 50% when entering conversations">
|
||||
reduce in conversations
|
||||
<input type="checkbox" id="audioplayer_decrease_while_talking" checked />
|
||||
</label>
|
||||
<div id="audioplayer" style="visibility: hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="audio-playing">
|
||||
<img src="/resources/logos/megaphone.svg"/>
|
||||
<img src="/resources/logos/megaphone.svg" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="audioplayerctrl" class="hidden">
|
||||
<div class="audioplayer">
|
||||
<button type="button" id="audioplayer_mute" class="fa fa-volump-up">
|
||||
<svg
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 16 16"
|
||||
class="bi bi-volume-up"
|
||||
fill="white"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06zM6 5.04L4.312 6.39A.5.5 0 0 1 4 6.5H2v3h2a.5.5 0 0 1 .312.11L6 10.96V5.04z"
|
||||
/>
|
||||
<g id="audioplayer_volume_icon_playing">
|
||||
<path
|
||||
d="M11.536 14.01A8.473 8.473 0 0 0 14.026 8a8.473 8.473 0 0 0-2.49-6.01l-.708.707A7.476 7.476 0 0 1 13.025 8c0 2.071-.84 3.946-2.197 5.303l.708.707z"
|
||||
/>
|
||||
<path
|
||||
d="M10.121 12.596A6.48 6.48 0 0 0 12.025 8a6.48 6.48 0 0 0-1.904-4.596l-.707.707A5.483 5.483 0 0 1 11.025 8a5.483 5.483 0 0 1-1.61 3.89l.706.706z"
|
||||
/>
|
||||
<path
|
||||
d="M8.707 11.182A4.486 4.486 0 0 0 10.025 8a4.486 4.486 0 0 0-1.318-3.182L8 5.525A3.489 3.489 0 0 1 9.025 8 3.49 3.49 0 0 1 8 10.475l.707.707z"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="audioplayer">
|
||||
<input
|
||||
type="range"
|
||||
id="audioplayer_volume"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.05"
|
||||
value="1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="audioplayer">
|
||||
<label
|
||||
id="label-audioplayer_decrease_while_talking"
|
||||
for="audiooplayer_decrease_while_talking"
|
||||
title="decrease background volume by 50% when entering conversations"
|
||||
>
|
||||
autoreduce
|
||||
<input type="checkbox" id="audioplayer_decrease_while_talking" checked />
|
||||
</label>
|
||||
<div id="audioplayer" style="visibility: hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="audio-playing">
|
||||
<img src="/resources/logos/megaphone.svg" />
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
<div id="webRtc" class="webrtc">
|
||||
<div id="activeCam" class="activeCam">
|
||||
<div id="div-myCamVideo" class="video-container">
|
||||
<video id="myCamVideo" autoplay muted></video>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-cam-action">
|
||||
<div class="btn-micro">
|
||||
<img id="microphone" src="resources/logos/microphone.svg">
|
||||
<img id="microphone-close" src="resources/logos/microphone-close.svg">
|
||||
</div>
|
||||
<div class="btn-video">
|
||||
<img id="cinema" src="resources/logos/cinema.svg">
|
||||
<img id="cinema-close" src="resources/logos/cinema-close.svg">
|
||||
</div>
|
||||
<div class="btn-monitor">
|
||||
<img id="monitor" src="resources/logos/monitor.svg">
|
||||
<img id="monitor-close" src="resources/logos/monitor-close.svg">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
<div id="activeScreenSharing" class="active-screen-sharing active">
|
||||
</div>
|
||||
<div id="webRtcSetup" class="webrtcsetup">
|
||||
<img id="webRtcSetupNoVideo" class="background-img" src="resources/logos/cinema-close.svg">
|
||||
<video id="myCamVideoSetup" autoplay muted></video>
|
||||
<img id="webRtcSetupNoVideo" class="background-img" src="resources/logos/cinema-close.svg">
|
||||
<video id="myCamVideoSetup" autoplay muted></video>
|
||||
</div>
|
||||
<audio id="audio-webrtc-in">
|
||||
<source src="/resources/objects/webrtc-in.mp3" type="audio/mp3">
|
|
@ -1,11 +1,4 @@
|
|||
<style>
|
||||
*{
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
cursor: auto;
|
||||
}
|
||||
* a, button, select{
|
||||
cursor: pointer;
|
||||
}
|
||||
#gameMenu button {
|
||||
background-color: black;
|
||||
color: white;
|
||||
|
@ -15,6 +8,14 @@
|
|||
#gameMenu section {
|
||||
margin: 10px;
|
||||
}
|
||||
section#socialLinks{
|
||||
position: absolute;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
section#socialLinks img{
|
||||
width: 32px;
|
||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="gameMenu" hidden>
|
||||
|
@ -38,5 +39,9 @@
|
|||
<section id="adminConsoleSection" hidden>
|
||||
<button id="adminConsoleButton">Admin console</button>
|
||||
</section>
|
||||
<section id="socialLinks" hidden>
|
||||
<a class="not-button" href="https://www.facebook.com/workadventurebytcm" target="_blank"><img class="not-button" src="/resources/objects/facebook-icon.png"/></a>
|
||||
<a class="not-button" href="https://twitter.com/Workadventure_" target="_blank"><img class="not-button" src="/resources/objects/twitter-icon.png"/></a>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
|
|
@ -1,11 +1,4 @@
|
|||
<style>
|
||||
*{
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
cursor: auto;
|
||||
}
|
||||
* a, button, select{
|
||||
cursor: pointer;
|
||||
}
|
||||
#menuIcon button {
|
||||
background-color: black;
|
||||
color: white;
|
||||
|
|
|
@ -1,11 +1,4 @@
|
|||
<style>
|
||||
*{
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
cursor: auto;
|
||||
}
|
||||
* a, button, select{
|
||||
cursor: pointer;
|
||||
}
|
||||
#gameQuality {
|
||||
background: #eceeee;
|
||||
border: 1px solid #42464b;
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
<style>
|
||||
#gameReport {
|
||||
background: #eceeee;
|
||||
border: 1px solid #42464b;
|
||||
border-radius: 6px;
|
||||
margin: 2px auto 0;
|
||||
width: 298px;
|
||||
}
|
||||
#gameReport h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #727678;
|
||||
display: block;
|
||||
height: 43px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#gameReport h3 {
|
||||
margin: 0;
|
||||
}
|
||||
#gameReport textarea {
|
||||
font-size: 70%;
|
||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
||||
border: 1px solid #a1a3a3;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px #fff;
|
||||
box-sizing: border-box;
|
||||
color: #696969;
|
||||
height: 100px;
|
||||
transition: box-shadow 0.3s;
|
||||
width: 100%;
|
||||
}
|
||||
#gameReport section {
|
||||
margin: 10px;
|
||||
}
|
||||
#gameReport section.action{
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
#gameReport button {
|
||||
margin-top: 10px;
|
||||
font-size: 60%;
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
border-radius: 7px;
|
||||
padding: 3px 10px 3px 10px;
|
||||
}
|
||||
#gameReport button#gameReportFormCancel {
|
||||
background-color: #c7c7c700;
|
||||
color: #292929;
|
||||
display: block;
|
||||
float: right;
|
||||
}
|
||||
#gameReport section a{
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
margin: 0 6px;
|
||||
color: black;
|
||||
}
|
||||
#gameReport section h6,
|
||||
#gameReport section h5{
|
||||
margin: 1px;
|
||||
}
|
||||
#gameReport section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
#gameReport p{
|
||||
font-size: 8px;
|
||||
margin: 3px 0 0 0;
|
||||
}
|
||||
#gameReport form p{
|
||||
margin: 0px 70px;
|
||||
}
|
||||
#gameReport section p.err{
|
||||
color: red;
|
||||
display: none;
|
||||
}
|
||||
#gameReport section p.info{
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
<main id="gameReport" hidden>
|
||||
<section>
|
||||
<button id="gameReportFormCancel">X</button>
|
||||
<h1>Moderate <span id="nameReported"></span></h1>
|
||||
<p id="askActionP">What action do you want to take?</p>
|
||||
</section>
|
||||
<section>
|
||||
<h3>Block: </h3>
|
||||
<p>Block any communication from and to this user. This can be reverted.</p>
|
||||
<section class="action">
|
||||
<button id="toggleBlockButton">Block this user</button>
|
||||
</section>
|
||||
</section>
|
||||
<section id="reportSection">
|
||||
<h3>Report: </h3>
|
||||
<p>Send a report message to the administrators of this room. They may later ban this user.</p>
|
||||
<form>
|
||||
<section>
|
||||
<h6>Your message: </h6>
|
||||
<textarea type="text" name="report" id="gameReportInput"></textarea>
|
||||
<p class="err" id="gameReportErr"></p>
|
||||
</section>
|
||||
<section class="action">
|
||||
<button type="submit" id="gameReportFormSubmit">Report this user</button>
|
||||
</section>
|
||||
</form>
|
||||
</section>
|
||||
</main>
|
||||
|
|
@ -1,11 +1,4 @@
|
|||
<style>
|
||||
*{
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
cursor: auto;
|
||||
}
|
||||
* a, button, input{
|
||||
cursor: pointer;
|
||||
}
|
||||
#gameShare {
|
||||
background: #eceeee;
|
||||
border: 1px solid #42464b;
|
||||
|
@ -14,9 +7,6 @@
|
|||
width: 298px;
|
||||
height: 150px;
|
||||
}
|
||||
#gameShare .cautiousText {
|
||||
font-size: 50%;
|
||||
}
|
||||
#gameShare h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
<style>
|
||||
#helpCameraSettings {
|
||||
background: #eceeee;
|
||||
border: 1px solid #42464b;
|
||||
border-radius: 6px;
|
||||
margin: 10px auto 0;
|
||||
width: 400px;
|
||||
height: 370px;
|
||||
}
|
||||
#helpCameraSettings h1 {
|
||||
background-image: linear-gradient(top, #f1f3f3, #d4dae0);
|
||||
border-bottom: 1px solid #a6abaf;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-sizing: border-box;
|
||||
color: #727678;
|
||||
display: block;
|
||||
height: 43px;
|
||||
padding-top: 10px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,0.2), 0 1px 0 #fff;
|
||||
}
|
||||
#helpCameraSettings input {
|
||||
font-size: 70%;
|
||||
background: linear-gradient(top, #d6d7d7, #dee0e0);
|
||||
border: 1px solid #a1a3a3;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px #fff;
|
||||
box-sizing: border-box;
|
||||
color: #696969;
|
||||
height: 30px;
|
||||
transition: box-shadow 0.3s;
|
||||
width: 100%;
|
||||
}
|
||||
#helpCameraSettings section {
|
||||
margin: 10px;
|
||||
}
|
||||
#helpCameraSettings section.action{
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
}
|
||||
#helpCameraSettings button {
|
||||
margin-top: 10px;
|
||||
background-color: black;
|
||||
color: white;
|
||||
border-radius: 7px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
#helpCameraSettings button#helpCameraSettingsFormCancel {
|
||||
background-color: #c7c7c700;
|
||||
color: #292929;
|
||||
}
|
||||
#helpCameraSettings section a{
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
margin: 0 6px;
|
||||
color: black;
|
||||
}
|
||||
#helpCameraSettings section h6,
|
||||
#helpCameraSettings section h5{
|
||||
margin: 1px;
|
||||
}
|
||||
#helpCameraSettings section.text-center{
|
||||
text-align: center;
|
||||
}
|
||||
#helpCameraSettings section p{
|
||||
font-size: 8px;
|
||||
margin: 0px 20px;
|
||||
}
|
||||
#helpCameraSettings section p.err{
|
||||
color: #ff0000;
|
||||
}
|
||||
#helpCameraSettings section ul{
|
||||
margin: 6px;
|
||||
}
|
||||
#helpCameraSettings section li{
|
||||
text-align: left;
|
||||
font-size: 8px;
|
||||
}
|
||||
#helpCameraSettings section img {
|
||||
width: 200px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<form id="helpCameraSettings" hidden>
|
||||
<section class="text-center">
|
||||
<h5>Camera/Microphone access needed</h5>
|
||||
<p class="err" id="permissionError">Permission denied</p>
|
||||
<p class="info">You must allow camera and microphone access in your browser.</p>
|
||||
<ul>
|
||||
<li>Please click on the lock or camera symbol on the side of the URL in the address bar. Here you can grant "always allow" access to your input devices.</li>
|
||||
<li>Please ensure that you have a camera AND microphone plugged into your computer.</li>
|
||||
</ul>
|
||||
<p class="info">Once you've followed these steps, please refresh this page.</p>
|
||||
<p>If you prefer to continue without allowing camera and microphone access, click on Continue</p>
|
||||
<p id='browserHelpSetting'></p>
|
||||
</section>
|
||||
<section class="action">
|
||||
<button type="submit" id="helpCameraSettingsFormRefresh">Refresh</button>
|
||||
<button type="submit" id="helpCameraSettingsFormContinue">Continue</button>
|
||||
</section>
|
||||
</form>
|
|
@ -0,0 +1,18 @@
|
|||
<style>
|
||||
#warningMain {
|
||||
border-radius: 5px;
|
||||
height: 100px;
|
||||
width: 300px;
|
||||
background-color: red;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#warningMain h2 {
|
||||
padding: 5px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<main id="warningMain">
|
||||
<h2>Warning!</h2>
|
||||
<p>This world is close to its limit!</p>
|
||||
</main>
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" id="svg2985" version="1.1" inkscape:version="0.48.4 r9939" width="485.33627" height="485.33627" sodipodi:docname="600px-France_road_sign_B1j.svg[1].png">
|
||||
<metadata id="metadata2991">
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||
<dc:title/>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs id="defs2989"/>
|
||||
<sodipodi:namedview pagecolor="#ffffff" bordercolor="#666666" borderopacity="1" objecttolerance="10" gridtolerance="10" guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1272" inkscape:window-height="745" id="namedview2987" showgrid="false" inkscape:snap-global="true" inkscape:snap-grids="true" inkscape:snap-bbox="true" inkscape:bbox-paths="true" inkscape:bbox-nodes="true" inkscape:snap-bbox-edge-midpoints="true" inkscape:snap-bbox-midpoints="true" inkscape:object-paths="true" inkscape:snap-intersection-paths="true" inkscape:object-nodes="true" inkscape:snap-smooth-nodes="true" inkscape:snap-midpoints="true" inkscape:snap-object-midpoints="true" inkscape:snap-center="false" fit-margin-top="0" fit-margin-left="0" fit-margin-right="0" fit-margin-bottom="0" inkscape:zoom="0.59970176" inkscape:cx="390.56499" inkscape:cy="244.34365" inkscape:window-x="86" inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="layer1">
|
||||
<inkscape:grid type="xygrid" id="grid2995" empspacing="5" visible="true" enabled="true" snapvisiblegridlinesonly="true" originx="-57.33186px" originy="-57.33186px"/>
|
||||
</sodipodi:namedview>
|
||||
<g inkscape:groupmode="layer" id="layer1" inkscape:label="1" style="display:inline" transform="translate(-57.33186,-57.33186)">
|
||||
<path sodipodi:type="arc" style="color:#000000;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:2.5;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" id="path2997" sodipodi:cx="300" sodipodi:cy="300" sodipodi:rx="240" sodipodi:ry="240" d="M 540,300 C 540,432.54834 432.54834,540 300,540 167.45166,540 60,432.54834 60,300 60,167.45166 167.45166,60 300,60 432.54834,60 540,167.45166 540,300 z" transform="matrix(1.0058783,0,0,1.0058783,-1.76349,-1.76349)"/>
|
||||
<path sodipodi:type="arc" style="color:#000000;fill:#ff0000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.5;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" id="path4005" sodipodi:cx="304.75" sodipodi:cy="214.75" sodipodi:rx="44.75" sodipodi:ry="44.75" d="m 349.5,214.75 c 0,24.71474 -20.03526,44.75 -44.75,44.75 -24.71474,0 -44.75,-20.03526 -44.75,-44.75 0,-24.71474 20.03526,-44.75 44.75,-44.75 24.71474,0 44.75,20.03526 44.75,44.75 z" transform="matrix(5.1364411,0,0,5.1364411,-1265.3304,-803.05073)"/>
|
||||
<rect style="color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.5;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" id="rect4001" width="345" height="80.599998" x="127.5" y="259.70001"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 111 KiB |
After Width: | Height: | Size: 80 KiB |
After Width: | Height: | Size: 16 KiB |
|
@ -0,0 +1 @@
|
|||
<svg id="Calque_1" data-name="Calque 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 56.48 56.48"><defs><style>.cls-1{fill:#e76e54;}.cls-2{fill:#fff;}</style></defs><path class="cls-1" d="M39.94,512H16.54L0,495.46v-23.4l16.54-16.54h23.4l16.54,16.54v23.4Z" transform="translate(0 -455.52)"/><path class="cls-2" d="M33.54,485.52H23l-1.77-21.18H35.3Z" transform="translate(0 -455.52)"/><path class="cls-2" d="M23,492.58H33.54v10.59H23Z" transform="translate(0 -455.52)"/></svg>
|
After Width: | Height: | Size: 477 B |
Before Width: | Height: | Size: 477 B After Width: | Height: | Size: 6.1 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 516 B |
After Width: | Height: | Size: 3.0 KiB |
|
@ -0,0 +1,170 @@
|
|||
/* A potentially shared website could appear in an iframe in the cowebsite space. */
|
||||
|
||||
#cowebsite {
|
||||
position: fixed;
|
||||
transition: transform 0.5s;
|
||||
background-color: white;
|
||||
|
||||
&.loading {
|
||||
background-color: gray;
|
||||
}
|
||||
|
||||
main {
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
aside {
|
||||
background: gray;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
img {
|
||||
margin: 3px;
|
||||
pointer-events: none;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.top-right-btn{
|
||||
position: absolute;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
||||
|
||||
img {
|
||||
height: 20px;
|
||||
background-color: rgba(0,0.0,0,0.3);
|
||||
padding: 5px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
img:hover {
|
||||
background-color: rgba(0,0,0,0.4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-aspect-ratio: 1/1) {
|
||||
#cowebsite {
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 50%;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
|
||||
&.loading {
|
||||
transform: translateX(90%);
|
||||
}
|
||||
&.hidden {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
main {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
aside {
|
||||
width: 30px;
|
||||
cursor: ew-resize;
|
||||
|
||||
img {
|
||||
cursor: ew-resize;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
|
||||
.top-right-btn{
|
||||
top: 10px;
|
||||
right: -100px;
|
||||
animation: right .2s ease;
|
||||
|
||||
img {
|
||||
right: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
#cowebsite-close {
|
||||
right: -140px;
|
||||
}
|
||||
#cowebsite-fullscreen {
|
||||
right: -100px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#cowebsite:hover {
|
||||
#cowebsite-close{
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
#cowebsite-fullscreen{
|
||||
right: 45px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (max-aspect-ratio: 1/1) {
|
||||
|
||||
|
||||
#cowebsite {
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&.loading {
|
||||
transform: translateY(90%);
|
||||
}
|
||||
&.hidden {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
|
||||
main {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
aside {
|
||||
height: 30px;
|
||||
cursor: ns-resize;
|
||||
flex-direction: column;
|
||||
|
||||
img {
|
||||
cursor: ns-resize;
|
||||
}
|
||||
}
|
||||
|
||||
.top-right-btn{
|
||||
top: 10px;
|
||||
right: -100px;
|
||||
animation: right .2s ease;
|
||||
|
||||
img {
|
||||
right: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
#cowebsite-close {
|
||||
right: -140px;
|
||||
}
|
||||
#cowebsite-fullscreen {
|
||||
right: -100px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#cowebsite:hover {
|
||||
#cowebsite-close{
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
#cowebsite-fullscreen{
|
||||
right: 45px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
@import "cowebsite.scss";
|
||||
@import "style.css";
|
|
@ -39,6 +39,7 @@ body .message-info.warning{
|
|||
position: relative;
|
||||
transition: all 0.2s ease;
|
||||
background-color: #00000099;
|
||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
||||
}
|
||||
.video-container i{
|
||||
position: absolute;
|
||||
|
@ -53,25 +54,71 @@ body .message-info.warning{
|
|||
font-size: 28px;
|
||||
color: white;
|
||||
}
|
||||
.video-container img.active{
|
||||
display: block;
|
||||
}
|
||||
|
||||
.video-container img{
|
||||
position: absolute;
|
||||
display: none;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
background: #d93025;
|
||||
border-radius: 48px;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
left: 5px;
|
||||
bottom: 5px;
|
||||
padding: 10px;
|
||||
z-index: 2;
|
||||
}
|
||||
.video-container img.block-logo {
|
||||
left: 30%;
|
||||
bottom: 15%;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.video-container img.report{
|
||||
.video-container button.report{
|
||||
display: block;
|
||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
||||
background: none;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
border: none;
|
||||
background-color: black;
|
||||
border-radius: 15px;
|
||||
position: absolute;
|
||||
width: 0px;
|
||||
height: 35px;
|
||||
right: 5px;
|
||||
left: auto;
|
||||
bottom: 5px;
|
||||
padding: 0px;
|
||||
overflow: hidden;
|
||||
z-index: 2;
|
||||
transition: all .5s ease;
|
||||
}
|
||||
|
||||
.video-container:hover button.report{
|
||||
width: 35px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.video-container button.report:hover {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.video-container button.report img{
|
||||
position: absolute;
|
||||
display: block;
|
||||
bottom: 5px;
|
||||
left: 5px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
||||
}
|
||||
.video-container button.report span{
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
left: 36px;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
cursor: url('/resources/logos/cursor_pointer.png'), pointer;
|
||||
}
|
||||
.video-container img.active {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.video-container video{
|
||||
|
@ -171,10 +218,6 @@ video#myCamVideo{
|
|||
right: 44px;
|
||||
opacity: 1;
|
||||
}
|
||||
/*.btn-call{
|
||||
transition: all .1s;
|
||||
left: 0px;
|
||||
}*/
|
||||
.btn-cam-action div img{
|
||||
height: 22px;
|
||||
width: 30px;
|
||||
|
@ -302,35 +345,7 @@ body {
|
|||
max-height: 25%;
|
||||
}
|
||||
|
||||
#cowebsite {
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 50%;
|
||||
height: 100vh;
|
||||
}
|
||||
#cowebsite.loading {
|
||||
transform: translateX(90%);
|
||||
}
|
||||
#cowebsite.hidden {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
#cowebsite .close-btn{
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: -100px;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
animation: right .2s ease;
|
||||
}
|
||||
#cowebsite .close-btn img{
|
||||
height: 15px;
|
||||
right: 15px;
|
||||
}
|
||||
#cowebsite:hover .close-btn{
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
}
|
||||
@media (max-aspect-ratio: 1/1) {
|
||||
.game-overlay {
|
||||
|
@ -349,19 +364,6 @@ body {
|
|||
.sidebar > div:hover {
|
||||
max-width: 25%;
|
||||
}
|
||||
|
||||
#cowebsite {
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 50%;
|
||||
}
|
||||
#cowebsite.loading {
|
||||
transform: translateY(90%);
|
||||
}
|
||||
#cowebsite.hidden {
|
||||
transform: translateY(100%);
|
||||
}
|
||||
}
|
||||
|
||||
#game {
|
||||
|
@ -369,20 +371,6 @@ body {
|
|||
position: relative; /* Position relative is needed for the game-overlay. */
|
||||
}
|
||||
|
||||
/* A potentially shared website could appear in an iframe in the cowebsite space. */
|
||||
#cowebsite {
|
||||
position: fixed;
|
||||
transition: transform 0.5s;
|
||||
}
|
||||
#cowebsite.loading {
|
||||
background-color: gray;
|
||||
}
|
||||
|
||||
#cowebsite > iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.audioplayer:first-child {
|
||||
display: grid;
|
||||
grid: 2rem / 4rem 10rem;
|
||||
|
@ -395,10 +383,14 @@ body {
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
.audioplayer > div {
|
||||
padding-right: 1.2rem;
|
||||
}
|
||||
|
||||
#audioplayerctrl {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 50%;
|
||||
right: calc(50% - 120px);
|
||||
padding: 0.3rem 0.5rem;
|
||||
color: white;
|
||||
transition: transform 0.5s;
|
||||
|
@ -588,10 +580,9 @@ input[type=range]:focus::-ms-fill-upper {
|
|||
}
|
||||
|
||||
.chat-mode {
|
||||
display: flex;
|
||||
display: grid;
|
||||
width: 100%;
|
||||
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
|
||||
padding: 1%;
|
||||
|
@ -607,24 +598,20 @@ input[type=range]:focus::-ms-fill-upper {
|
|||
.chat-mode > div:hover {
|
||||
margin: 0%;
|
||||
}
|
||||
.chat-mode.one-col > div {
|
||||
flex-basis: 98%;
|
||||
.chat-mode.one-col {
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
|
||||
.chat-mode.two-col > div {
|
||||
flex-basis: 48%;
|
||||
.chat-mode.two-col {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.chat-mode.three-col > div {
|
||||
flex-basis: 31.333333%;
|
||||
.chat-mode.three-col {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.chat-mode.four-col > div {
|
||||
flex-basis: 23%;
|
||||
}
|
||||
|
||||
.chat-mode > div:last-child {
|
||||
flex-grow: 5;
|
||||
.chat-mode.four-col {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
/*CONSOLE*/
|
||||
|
@ -1099,7 +1086,7 @@ div.modal-report-user{
|
|||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.discussion .messages .message p.a{
|
||||
.discussion .messages .message p a{
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,9 +9,13 @@
|
|||
"@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",
|
||||
"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",
|
||||
|
@ -26,9 +30,10 @@
|
|||
"axios": "^0.21.1",
|
||||
"generic-type-guard": "^3.2.0",
|
||||
"google-protobuf": "^3.13.0",
|
||||
"phaser": "^3.52.0",
|
||||
"phaser": "^3.53.1",
|
||||
"queue-typescript": "^1.0.1",
|
||||
"quill": "^1.3.7",
|
||||
"rxjs": "^6.6.3",
|
||||
"simple-peer": "^9.6.2",
|
||||
"socket.io-client": "^2.3.0",
|
||||
"webpack-require-http": "^0.4.3"
|
||||
|
|
|
@ -3,6 +3,7 @@ import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
|||
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import {PlayGlobalMessageInterface} from "../Connexion/ConnexionModels";
|
||||
import {ADMIN_URL} from "../Enum/EnvironmentVariable";
|
||||
import {AdminMessageEventTypes} from "../Connexion/AdminMessagesService";
|
||||
|
||||
export const CLASS_CONSOLE_MESSAGE = 'main-console';
|
||||
export const INPUT_CONSOLE_MESSAGE = 'input-send-text';
|
||||
|
@ -10,13 +11,16 @@ export const UPLOAD_CONSOLE_MESSAGE = 'input-upload-music';
|
|||
export const INPUT_TYPE_CONSOLE = 'input-type';
|
||||
export const VIDEO_QUALITY_SELECT = 'select-video-quality';
|
||||
|
||||
export const AUDIO_TYPE = 'audio';
|
||||
export const MESSAGE_TYPE = 'message';
|
||||
export const AUDIO_TYPE = AdminMessageEventTypes.audio;
|
||||
export const MESSAGE_TYPE = AdminMessageEventTypes.admin;
|
||||
|
||||
interface EventTargetFiles extends EventTarget {
|
||||
files: Array<File>;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export class ConsoleGlobalMessageManager {
|
||||
|
||||
private readonly divMainConsole: HTMLDivElement;
|
||||
|
@ -47,7 +51,7 @@ export class ConsoleGlobalMessageManager {
|
|||
//this.buttonSettingsMainConsole = document.createElement('img');
|
||||
//this.buttonAdminMainConsole = document.createElement('img');
|
||||
this.userInputManager = userInputManager;
|
||||
// TBA: Disable because of exception: this.initialise();
|
||||
// TBA: Disable because of exception: this.initialise();
|
||||
|
||||
}
|
||||
|
||||
|
@ -140,7 +144,7 @@ export class ConsoleGlobalMessageManager {
|
|||
const div = document.createElement('div');
|
||||
div.id = INPUT_CONSOLE_MESSAGE
|
||||
const buttonSend = document.createElement('button');
|
||||
buttonSend.innerText = 'Envoyer';
|
||||
buttonSend.innerText = 'Send';
|
||||
buttonSend.classList.add('btn');
|
||||
buttonSend.addEventListener('click', (event: MouseEvent) => {
|
||||
this.sendMessage();
|
||||
|
@ -242,7 +246,7 @@ export class ConsoleGlobalMessageManager {
|
|||
div.appendChild(input);
|
||||
|
||||
const buttonSend = document.createElement('button');
|
||||
buttonSend.innerText = 'Envoyer';
|
||||
buttonSend.innerText = 'Send';
|
||||
buttonSend.classList.add('btn');
|
||||
buttonSend.addEventListener('click', (event: MouseEvent) => {
|
||||
this.sendMessage();
|
||||
|
@ -332,7 +336,7 @@ export class ConsoleGlobalMessageManager {
|
|||
}
|
||||
|
||||
active(){
|
||||
this.userInputManager.clearAllInputKeyboard();
|
||||
this.userInputManager.clearAllKeys();
|
||||
this.divMainConsole.style.top = '0';
|
||||
this.activeConsole = true;
|
||||
}
|
||||
|
@ -372,23 +376,6 @@ export class ConsoleGlobalMessageManager {
|
|||
this.buttonSendMainConsole.classList.remove('active');
|
||||
}
|
||||
|
||||
/*activeSettingConsole(){
|
||||
this.activeSetting = true;
|
||||
if(this.activeMessage){
|
||||
this.disabledSettingConsole();
|
||||
}
|
||||
this.active();
|
||||
this.divSettingConsole.classList.add('active');
|
||||
//this.buttonSettingsMainConsole.classList.add('active');
|
||||
}
|
||||
|
||||
disabledSettingConsole(){
|
||||
this.activeSetting = false;
|
||||
this.disabled();
|
||||
this.divSettingConsole.classList.remove('active');
|
||||
//this.buttonSettingsMainConsole.classList.remove('active');
|
||||
}*/
|
||||
|
||||
private getSectionId(id: string) : string {
|
||||
return `section-${id}`;
|
||||
}
|
||||
|
|
|
@ -77,8 +77,10 @@ export class TypeMessageExt implements TypeMessageInterface{
|
|||
}
|
||||
}
|
||||
}
|
||||
export class Ban extends TypeMessageExt {
|
||||
}
|
||||
|
||||
export class Message extends TypeMessageExt {}
|
||||
|
||||
export class Ban extends TypeMessageExt {}
|
||||
|
||||
export class Banned extends TypeMessageExt {
|
||||
showMessage(message: string){
|
||||
|
|
|
@ -1,28 +1,29 @@
|
|||
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import * as TypeMessages from "./TypeMessage";
|
||||
import {Banned} from "./TypeMessage";
|
||||
import {adminMessagesService} from "../Connexion/AdminMessagesService";
|
||||
|
||||
export interface TypeMessageInterface {
|
||||
showMessage(message: string): void;
|
||||
}
|
||||
|
||||
export class UserMessageManager {
|
||||
class UserMessageManager {
|
||||
|
||||
typeMessages: Map<string, TypeMessageInterface> = new Map<string, TypeMessageInterface>();
|
||||
receiveBannedMessageListener!: Function;
|
||||
|
||||
constructor(private Connection: RoomConnection) {
|
||||
constructor() {
|
||||
const valueTypeMessageTab = Object.values(TypeMessages);
|
||||
Object.keys(TypeMessages).forEach((value: string, index: number) => {
|
||||
const typeMessageInstance: TypeMessageInterface = (new valueTypeMessageTab[index]() as TypeMessageInterface);
|
||||
this.typeMessages.set(value.toLowerCase(), typeMessageInstance);
|
||||
});
|
||||
this.initialise();
|
||||
}
|
||||
|
||||
initialise() {
|
||||
//receive signal to show message
|
||||
this.Connection.receiveUserMessage((type: string, message: string) => {
|
||||
this.showMessage(type, message);
|
||||
});
|
||||
adminMessagesService.messageStream.subscribe((event) => {
|
||||
const typeMessage = this.showMessage(event.type, event.text);
|
||||
if(typeMessage instanceof Banned) {
|
||||
this.receiveBannedMessageListener();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
showMessage(type: string, message: string) {
|
||||
|
@ -32,5 +33,11 @@ export class UserMessageManager {
|
|||
return;
|
||||
}
|
||||
classTypeMessage.showMessage(message);
|
||||
return classTypeMessage;
|
||||
}
|
||||
}
|
||||
|
||||
setReceiveBanListener(callback: Function){
|
||||
this.receiveBannedMessageListener = callback;
|
||||
}
|
||||
}
|
||||
export const userMessageManager = new UserMessageManager()
|
|
@ -0,0 +1,34 @@
|
|||
import {Subject} from "rxjs";
|
||||
import {BanUserMessage, SendUserMessage} from "../Messages/generated/messages_pb";
|
||||
|
||||
export enum AdminMessageEventTypes {
|
||||
admin = 'message',
|
||||
audio = 'audio',
|
||||
ban = 'ban',
|
||||
}
|
||||
|
||||
interface AdminMessageEvent {
|
||||
type: AdminMessageEventTypes,
|
||||
text: string;
|
||||
//todo add optional properties for other event types
|
||||
}
|
||||
|
||||
//this class is designed to easily allow communication between the RoomConnection objects (that receive the message)
|
||||
//and the various objects that may render the message on screen
|
||||
class AdminMessagesService {
|
||||
private _messageStream: Subject<AdminMessageEvent> = new Subject();
|
||||
public messageStream = this._messageStream.asObservable();
|
||||
|
||||
constructor() {
|
||||
this.messageStream.subscribe((event) => console.log('message', event))
|
||||
}
|
||||
|
||||
onSendusermessage(message: SendUserMessage|BanUserMessage) {
|
||||
this._messageStream.next({
|
||||
type: message.getType() as unknown as AdminMessageEventTypes,
|
||||
text: message.getMessage(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const adminMessagesService = new AdminMessagesService();
|
|
@ -7,10 +7,24 @@ import {localUserStore} from "./LocalUserStore";
|
|||
import {LocalUser} from "./LocalUser";
|
||||
import {Room} from "./Room";
|
||||
|
||||
|
||||
class ConnectionManager {
|
||||
private localUser!:LocalUser;
|
||||
|
||||
private connexionType?: GameConnexionTypes
|
||||
private reconnectingTimeout: NodeJS.Timeout|null = null;
|
||||
private _unloading:boolean = false;
|
||||
|
||||
get unloading () {
|
||||
return this._unloading;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
window.addEventListener('beforeunload', () => {
|
||||
this._unloading = true;
|
||||
if (this.reconnectingTimeout) clearTimeout(this.reconnectingTimeout)
|
||||
})
|
||||
}
|
||||
/**
|
||||
* Tries to login to the node server and return the starting map url to be loaded
|
||||
*/
|
||||
|
@ -94,7 +108,7 @@ class ConnectionManager {
|
|||
}).catch((err) => {
|
||||
// Let's retry in 4-6 seconds
|
||||
return new Promise<OnConnectInterface>((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
this.reconnectingTimeout = setTimeout(() => {
|
||||
//todo: allow a way to break recursion?
|
||||
//todo: find a way to avoid recursive function. Otherwise, the call stack will grow indefinitely.
|
||||
this.connectToRoomSocket(roomId, name, characterLayers, position, viewport).then((connection) => resolve(connection));
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {PlayerAnimationNames} from "../Phaser/Player/Animation";
|
||||
import {PlayerAnimationDirections} from "../Phaser/Player/Animation";
|
||||
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
|
||||
import {SignalData} from "simple-peer";
|
||||
import {RoomConnection} from "./RoomConnection";
|
||||
|
@ -42,14 +42,6 @@ export interface PointInterface {
|
|||
moving: boolean;
|
||||
}
|
||||
|
||||
export class Point implements PointInterface{
|
||||
constructor(public x : number, public y : number, public direction : string = PlayerAnimationNames.WalkDown, public moving : boolean = false) {
|
||||
if(x === null || y === null){
|
||||
throw Error("position x and y cannot be null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface MessageUserPositionInterface {
|
||||
userId: number;
|
||||
name: string;
|
||||
|
@ -80,23 +72,15 @@ export interface GroupCreatedUpdatedMessageInterface {
|
|||
groupSize: number
|
||||
}
|
||||
|
||||
export interface WebRtcStartMessageInterface {
|
||||
roomId: string,
|
||||
clients: UserSimplePeerInterface[]
|
||||
}
|
||||
|
||||
export interface WebRtcDisconnectMessageInterface {
|
||||
userId: number
|
||||
}
|
||||
|
||||
export interface WebRtcSignalSentMessageInterface {
|
||||
receiverId: number,
|
||||
signal: SignalData
|
||||
}
|
||||
|
||||
export interface WebRtcSignalReceivedMessageInterface {
|
||||
userId: number,
|
||||
signal: SignalData
|
||||
signal: SignalData,
|
||||
webRtcUser: string | undefined,
|
||||
webRtcPassword: string | undefined
|
||||
}
|
||||
|
||||
export interface StartMapInterface {
|
||||
|
@ -111,11 +95,6 @@ export interface ViewportInterface {
|
|||
bottom: number,
|
||||
}
|
||||
|
||||
export interface BatchedMessageInterface {
|
||||
event: string,
|
||||
payload: unknown
|
||||
}
|
||||
|
||||
export interface ItemEventMessageInterface {
|
||||
itemId: number,
|
||||
event: string,
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
import {LocalUser} from "./LocalUser";
|
||||
|
||||
const characterLayersKey = 'characterLayers';
|
||||
const gameQualityKey = 'gameQuality';
|
||||
const videoQualityKey = 'videoQuality';
|
||||
const playerNameKey = 'playerName';
|
||||
const selectedPlayerKey = 'selectedPlayer';
|
||||
const customCursorPositionKey = 'customCursorPosition';
|
||||
const characterLayersKey = 'characterLayers';
|
||||
const gameQualityKey = 'gameQuality';
|
||||
const videoQualityKey = 'videoQuality';
|
||||
const audioPlayerVolumeKey = 'audioVolume';
|
||||
const audioPlayerMuteKey = 'audioMute';
|
||||
const helpCameraSettingsShown = 'helpCameraSettingsShown';
|
||||
|
||||
//todo: add localstorage fallback
|
||||
class LocalUserStore {
|
||||
|
||||
saveUser(localUser: LocalUser) {
|
||||
localStorage.setItem('localUser', JSON.stringify(localUser));
|
||||
}
|
||||
|
@ -14,48 +18,69 @@ class LocalUserStore {
|
|||
const data = localStorage.getItem('localUser');
|
||||
return data ? JSON.parse(data) : null;
|
||||
}
|
||||
|
||||
|
||||
setName(name:string): void {
|
||||
window.localStorage.setItem('playerName', name);
|
||||
localStorage.setItem(playerNameKey, name);
|
||||
}
|
||||
getName(): string {
|
||||
return window.localStorage.getItem('playerName') ?? '';
|
||||
return localStorage.getItem(playerNameKey) || '';
|
||||
}
|
||||
|
||||
setPlayerCharacterIndex(playerCharacterIndex: number): void {
|
||||
window.localStorage.setItem('selectedPlayer', ''+playerCharacterIndex);
|
||||
localStorage.setItem(selectedPlayerKey, ''+playerCharacterIndex);
|
||||
}
|
||||
getPlayerCharacterIndex(): number {
|
||||
return parseInt(window.localStorage.getItem('selectedPlayer') || '');
|
||||
return parseInt(localStorage.getItem(selectedPlayerKey) || '');
|
||||
}
|
||||
|
||||
setCustomCursorPosition(activeRow:number, selectedLayers: number[]): void {
|
||||
window.localStorage.setItem('customCursorPosition', JSON.stringify({activeRow, selectedLayers}));
|
||||
localStorage.setItem(customCursorPositionKey, JSON.stringify({activeRow, selectedLayers}));
|
||||
}
|
||||
getCustomCursorPosition(): {activeRow:number, selectedLayers:number[]}|null {
|
||||
return JSON.parse(window.localStorage.getItem('customCursorPosition') || "null");
|
||||
return JSON.parse(localStorage.getItem(customCursorPositionKey) || "null");
|
||||
}
|
||||
|
||||
setCharacterLayers(layers: string[]): void {
|
||||
window.localStorage.setItem(characterLayersKey, JSON.stringify(layers));
|
||||
localStorage.setItem(characterLayersKey, JSON.stringify(layers));
|
||||
}
|
||||
getCharacterLayers(): string[]|null {
|
||||
return JSON.parse(window.localStorage.getItem(characterLayersKey) || "null");
|
||||
}
|
||||
|
||||
getGameQualityValue(): number {
|
||||
return parseInt(window.localStorage.getItem(gameQualityKey) || '') || 60;
|
||||
return JSON.parse(localStorage.getItem(characterLayersKey) || "null");
|
||||
}
|
||||
|
||||
setGameQualityValue(value: number): void {
|
||||
localStorage.setItem(gameQualityKey, '' + value);
|
||||
}
|
||||
|
||||
getVideoQualityValue(): number {
|
||||
return parseInt(window.localStorage.getItem(videoQualityKey) || '') || 20;
|
||||
getGameQualityValue(): number {
|
||||
return parseInt(localStorage.getItem(gameQualityKey) || '60');
|
||||
}
|
||||
|
||||
setVideoQualityValue(value: number): void {
|
||||
localStorage.setItem(videoQualityKey, '' + value);
|
||||
}
|
||||
getVideoQualityValue(): number {
|
||||
return parseInt(localStorage.getItem(videoQualityKey) || '20');
|
||||
}
|
||||
|
||||
setAudioPlayerVolume(value: number): void {
|
||||
localStorage.setItem(audioPlayerVolumeKey, '' + value);
|
||||
}
|
||||
getAudioPlayerVolume(): number {
|
||||
return parseFloat(localStorage.getItem(audioPlayerVolumeKey) || '1');
|
||||
}
|
||||
|
||||
setAudioPlayerMuted(value: boolean): void {
|
||||
localStorage.setItem(audioPlayerMuteKey, value.toString());
|
||||
}
|
||||
getAudioPlayerMuted(): boolean {
|
||||
return localStorage.getItem(audioPlayerMuteKey) === 'true';
|
||||
}
|
||||
|
||||
setHelpCameraSettingsShown(): void {
|
||||
localStorage.setItem(helpCameraSettingsShown, '1');
|
||||
}
|
||||
getHelpCameraSettingsShown(): boolean {
|
||||
return localStorage.getItem(helpCameraSettingsShown) === '1';
|
||||
}
|
||||
}
|
||||
|
||||
export const localUserStore = new LocalUserStore();
|
||||
export const localUserStore = new LocalUserStore();
|
||||
|
|
|
@ -27,7 +27,7 @@ import {
|
|||
SendJitsiJwtMessage,
|
||||
CharacterLayerMessage,
|
||||
PingMessage,
|
||||
SendUserMessage
|
||||
SendUserMessage, BanUserMessage
|
||||
} from "../Messages/generated/messages_pb"
|
||||
|
||||
import {UserSimplePeerInterface} from "../WebRtc/SimplePeer";
|
||||
|
@ -42,6 +42,10 @@ import {
|
|||
WebRtcSignalReceivedMessageInterface,
|
||||
} from "./ConnexionModels";
|
||||
import {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures";
|
||||
import {adminMessagesService} from "./AdminMessagesService";
|
||||
import {worldFullMessageStream} from "./WorldFullMessageStream";
|
||||
import {worldFullWarningStream} from "./WorldFullWarningStream";
|
||||
import {connectionManager} from "./ConnectionManager";
|
||||
|
||||
const manualPingDelay = 20000;
|
||||
|
||||
|
@ -100,7 +104,7 @@ export class RoomConnection implements RoomConnection {
|
|||
}
|
||||
|
||||
// If we are not connected yet (if a JoinRoomMessage was not sent), we need to retry.
|
||||
if (this.userId === null) {
|
||||
if (this.userId === null && !this.closed) {
|
||||
this.dispatch(EventMessage.CONNECTING_ERROR, event);
|
||||
}
|
||||
});
|
||||
|
@ -140,8 +144,6 @@ export class RoomConnection implements RoomConnection {
|
|||
} else if (message.hasRoomjoinedmessage()) {
|
||||
const roomJoinedMessage = message.getRoomjoinedmessage() as RoomJoinedMessage;
|
||||
|
||||
//const users: Array<MessageUserJoined> = roomJoinedMessage.getUserList().map(this.toMessageUserJoined.bind(this));
|
||||
//const groups: Array<GroupCreatedUpdatedMessageInterface> = roomJoinedMessage.getGroupList().map(this.toGroupCreatedUpdatedMessage.bind(this));
|
||||
const items: { [itemId: number] : unknown } = {};
|
||||
for (const item of roomJoinedMessage.getItemList()) {
|
||||
items[item.getItemid()] = JSON.parse(item.getStatejson());
|
||||
|
@ -150,24 +152,15 @@ export class RoomConnection implements RoomConnection {
|
|||
this.userId = roomJoinedMessage.getCurrentuserid();
|
||||
this.tags = roomJoinedMessage.getTagList();
|
||||
|
||||
//console.log('Dispatching CONNECT')
|
||||
this.dispatch(EventMessage.CONNECT, {
|
||||
connection: this,
|
||||
room: {
|
||||
//users,
|
||||
//groups,
|
||||
items
|
||||
} as RoomJoinedMessageInterface
|
||||
});
|
||||
|
||||
/*console.log('Dispatching START_ROOM')
|
||||
this.dispatch(EventMessage.START_ROOM, {
|
||||
//users,
|
||||
//groups,
|
||||
items
|
||||
});*/
|
||||
} else if (message.hasErrormessage()) {
|
||||
console.error(EventMessage.MESSAGE_ERROR, message.getErrormessage()?.getMessage());
|
||||
} else if (message.hasWorldfullmessage()) {
|
||||
worldFullMessageStream.onMessage();
|
||||
this.closed = true;
|
||||
} else if (message.hasWebrtcsignaltoclientmessage()) {
|
||||
this.dispatch(EventMessage.WEBRTC_SIGNAL, message.getWebrtcsignaltoclientmessage());
|
||||
} else if (message.hasWebrtcscreensharingsignaltoclientmessage()) {
|
||||
|
@ -185,7 +178,11 @@ export class RoomConnection implements RoomConnection {
|
|||
} else if (message.hasSendjitsijwtmessage()) {
|
||||
this.dispatch(EventMessage.START_JITSI_ROOM, message.getSendjitsijwtmessage());
|
||||
} else if (message.hasSendusermessage()) {
|
||||
this.dispatch(EventMessage.USER_MESSAGE, message.getSendusermessage());
|
||||
adminMessagesService.onSendusermessage(message.getSendusermessage() as SendUserMessage);
|
||||
} else if (message.hasBanusermessage()) {
|
||||
adminMessagesService.onSendusermessage(message.getSendusermessage() as BanUserMessage);
|
||||
} else if (message.hasWorldfullwarningmessage()) {
|
||||
worldFullWarningStream.onMessage();
|
||||
} else {
|
||||
throw new Error('Unknown message received');
|
||||
}
|
||||
|
@ -384,10 +381,7 @@ export class RoomConnection implements RoomConnection {
|
|||
public onConnectError(callback: (error: Event) => void): void {
|
||||
this.socket.addEventListener('error', callback)
|
||||
}
|
||||
|
||||
/*public onConnect(callback: (e: Event) => void): void {
|
||||
this.socket.addEventListener('open', callback)
|
||||
}*/
|
||||
|
||||
public onConnect(callback: (roomConnection: OnConnectInterface) => void): void {
|
||||
//this.socket.addEventListener('open', callback)
|
||||
this.onMessage(EventMessage.CONNECT, callback);
|
||||
|
@ -427,7 +421,9 @@ export class RoomConnection implements RoomConnection {
|
|||
callback({
|
||||
userId: message.getUserid(),
|
||||
name: message.getName(),
|
||||
initiator: message.getInitiator()
|
||||
initiator: message.getInitiator(),
|
||||
webRtcUser: message.getWebrtcusername() ?? undefined,
|
||||
webRtcPassword: message.getWebrtcpassword() ?? undefined,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -436,7 +432,9 @@ export class RoomConnection implements RoomConnection {
|
|||
this.onMessage(EventMessage.WEBRTC_SIGNAL, (message: WebRtcSignalToClientMessage) => {
|
||||
callback({
|
||||
userId: message.getUserid(),
|
||||
signal: JSON.parse(message.getSignal())
|
||||
signal: JSON.parse(message.getSignal()),
|
||||
webRtcUser: message.getWebrtcusername() ?? undefined,
|
||||
webRtcPassword: message.getWebrtcpassword() ?? undefined,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -445,14 +443,16 @@ export class RoomConnection implements RoomConnection {
|
|||
this.onMessage(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, (message: WebRtcSignalToClientMessage) => {
|
||||
callback({
|
||||
userId: message.getUserid(),
|
||||
signal: JSON.parse(message.getSignal())
|
||||
signal: JSON.parse(message.getSignal()),
|
||||
webRtcUser: message.getWebrtcusername() ?? undefined,
|
||||
webRtcPassword: message.getWebrtcpassword() ?? undefined,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public onServerDisconnected(callback: (event: CloseEvent) => void): void {
|
||||
public onServerDisconnected(callback: () => void): void {
|
||||
this.socket.addEventListener('close', (event) => {
|
||||
if (this.closed === true) {
|
||||
if (this.closed === true || connectionManager.unloading) {
|
||||
return;
|
||||
}
|
||||
console.log('Socket closed with code '+event.code+". Reason: "+event.reason);
|
||||
|
@ -460,11 +460,12 @@ export class RoomConnection implements RoomConnection {
|
|||
// Normal closure case
|
||||
return;
|
||||
}
|
||||
callback(event);
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
public getUserId(): number|null {
|
||||
public getUserId(): number {
|
||||
if (this.userId === null) throw 'UserId cannot be null!'
|
||||
return this.userId;
|
||||
}
|
||||
|
||||
|
@ -532,12 +533,6 @@ export class RoomConnection implements RoomConnection {
|
|||
});
|
||||
}
|
||||
|
||||
public receiveUserMessage(callback: (type: string, message: string) => void) {
|
||||
return this.onMessage(EventMessage.USER_MESSAGE, (message: SendUserMessage) => {
|
||||
callback(message.getType(), message.getMessage());
|
||||
});
|
||||
}
|
||||
|
||||
public emitGlobalMessage(message: PlayGlobalMessageInterface){
|
||||
const playGlobalMessage = new PlayGlobalMessage();
|
||||
playGlobalMessage.setId(message.id);
|
||||
|
@ -583,7 +578,7 @@ export class RoomConnection implements RoomConnection {
|
|||
public hasTag(tag: string): boolean {
|
||||
return this.tags.includes(tag);
|
||||
}
|
||||
|
||||
|
||||
public isAdmin(): boolean {
|
||||
return this.hasTag('admin');
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import {Subject} from "rxjs";
|
||||
|
||||
class WorldFullMessageStream {
|
||||
|
||||
private _stream:Subject<void> = new Subject();
|
||||
public stream = this._stream.asObservable();
|
||||
|
||||
|
||||
onMessage() {
|
||||
this._stream.next();
|
||||
}
|
||||
}
|
||||
|
||||
export const worldFullMessageStream = new WorldFullMessageStream();
|
|
@ -0,0 +1,14 @@
|
|||
import {Subject} from "rxjs";
|
||||
|
||||
class WorldFullWarningStream {
|
||||
|
||||
private _stream:Subject<void> = new Subject();
|
||||
public stream = this._stream.asObservable();
|
||||
|
||||
|
||||
onMessage() {
|
||||
this._stream.next();
|
||||
}
|
||||
}
|
||||
|
||||
export const worldFullWarningStream = new WorldFullWarningStream();
|
|
@ -1,36 +0,0 @@
|
|||
declare let window:WindowWithCypressAsserter;
|
||||
|
||||
interface WindowWithCypressAsserter extends Window {
|
||||
cypressAsserter: CypressAsserter;
|
||||
}
|
||||
|
||||
//this class is used to communicate with cypress, our e2e testing client
|
||||
//Since cypress cannot manipulate canvas, we notified it with console logs
|
||||
class CypressAsserter {
|
||||
|
||||
constructor() {
|
||||
window.cypressAsserter = this
|
||||
}
|
||||
|
||||
gameStarted() {
|
||||
console.log('Started the game')
|
||||
}
|
||||
|
||||
preloadStarted() {
|
||||
console.log('Preloading')
|
||||
}
|
||||
|
||||
preloadFinished() {
|
||||
console.log('Preloading done')
|
||||
}
|
||||
|
||||
initStarted() {
|
||||
console.log('startInit')
|
||||
}
|
||||
|
||||
initFinished() {
|
||||
console.log('startInit done')
|
||||
}
|
||||
}
|
||||
|
||||
export const cypressAsserter = new CypressAsserter()
|
|
@ -3,9 +3,10 @@ const START_ROOM_URL : string = process.env.START_ROOM_URL || '/_/global/maps.wo
|
|||
const API_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.API_URL || "pusher.workadventure.localhost");
|
||||
const UPLOADER_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.UPLOADER_URL || 'uploader.workadventure.localhost');
|
||||
const ADMIN_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.ADMIN_URL || "workadventure.localhost");
|
||||
const TURN_SERVER: string = process.env.TURN_SERVER || "turn:numb.viagenie.ca";
|
||||
const TURN_USER: string = process.env.TURN_USER || 'g.parant@thecodingmachine.com';
|
||||
const TURN_PASSWORD: string = process.env.TURN_PASSWORD || 'itcugcOHxle9Acqi$';
|
||||
const STUN_SERVER: string = process.env.STUN_SERVER || "stun:stun.l.google.com:19302";
|
||||
const TURN_SERVER: string = process.env.TURN_SERVER || "";
|
||||
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;
|
||||
const JITSI_PRIVATE_MODE : boolean = process.env.JITSI_PRIVATE_MODE == "true";
|
||||
const RESOLUTION = 2;
|
||||
|
@ -23,6 +24,7 @@ export {
|
|||
ZOOM_LEVEL,
|
||||
POSITION_DELAY,
|
||||
MAX_EXTRAPOLATION_TIME,
|
||||
STUN_SERVER,
|
||||
TURN_SERVER,
|
||||
TURN_USER,
|
||||
TURN_PASSWORD,
|
||||
|
|
|
@ -1,14 +1,54 @@
|
|||
import ImageFrameConfig = Phaser.Types.Loader.FileTypes.ImageFrameConfig;
|
||||
|
||||
export const addLoader = (scene:Phaser.Scene): void => {
|
||||
const loadingText = scene.add.text(scene.game.renderer.width / 2, 200, 'Loading');
|
||||
const LogoNameIndex: string = 'logoLoading';
|
||||
const TextName: string = 'Loading...';
|
||||
const LogoResource: string = 'resources/logos/logo.png';
|
||||
const LogoFrame: ImageFrameConfig = {frameWidth: 307, frameHeight: 59};
|
||||
|
||||
export const addLoader = (scene: Phaser.Scene): void => {
|
||||
// If there is nothing to load, do not display the loader.
|
||||
if (scene.load.list.entries.length === 0) {
|
||||
return;
|
||||
}
|
||||
let loadingText: Phaser.GameObjects.Text|null = null;
|
||||
const loadingBarWidth: number = Math.floor(scene.game.renderer.width / 3);
|
||||
const loadingBarHeight: number = 16;
|
||||
const padding: number = 5;
|
||||
|
||||
const promiseLoadLogoTexture = new Promise<Phaser.GameObjects.Image>((res) => {
|
||||
if(scene.load.textureManager.exists(LogoNameIndex)){
|
||||
return res(scene.add.image(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 150, LogoNameIndex));
|
||||
}else{
|
||||
//add loading if logo image is not ready
|
||||
loadingText = scene.add.text(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 50, TextName);
|
||||
}
|
||||
scene.load.spritesheet(LogoNameIndex, LogoResource, LogoFrame);
|
||||
scene.load.once(`filecomplete-spritesheet-${LogoNameIndex}`, () => {
|
||||
if(loadingText){
|
||||
loadingText.destroy();
|
||||
}
|
||||
return res(scene.add.image(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 150, LogoNameIndex));
|
||||
});
|
||||
});
|
||||
|
||||
const progressContainer = scene.add.graphics();
|
||||
const progress = scene.add.graphics();
|
||||
progressContainer.fillStyle(0x444444, 0.8);
|
||||
progressContainer.fillRect((scene.game.renderer.width - loadingBarWidth) / 2 - padding, scene.game.renderer.height / 2 + 50 - padding, loadingBarWidth + padding * 2, loadingBarHeight + padding * 2);
|
||||
|
||||
scene.load.on('progress', (value: number) => {
|
||||
progress.clear();
|
||||
progress.fillStyle(0xffffff, 1);
|
||||
progress.fillRect(0, 270, 800 * value, 60);
|
||||
progress.fillStyle(0xBBBBBB, 1);
|
||||
progress.fillRect((scene.game.renderer.width - loadingBarWidth) / 2, scene.game.renderer.height / 2 + 50, loadingBarWidth * value, loadingBarHeight);
|
||||
});
|
||||
scene.load.on('complete', () => {
|
||||
loadingText.destroy();
|
||||
if(loadingText){
|
||||
loadingText.destroy();
|
||||
}
|
||||
promiseLoadLogoTexture.then((resLoadingImage: Phaser.GameObjects.Image) => {
|
||||
resLoadingImage.destroy();
|
||||
});
|
||||
progress.destroy();
|
||||
progressContainer.destroy();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,14 +3,12 @@ import {discussionManager} from "../../WebRtc/DiscussionManager";
|
|||
export const openChatIconName = 'openChatIcon';
|
||||
export class OpenChatIcon extends Phaser.GameObjects.Image {
|
||||
constructor(scene: Phaser.Scene, x: number, y: number) {
|
||||
super(scene, x, y, openChatIconName);
|
||||
super(scene, x, y, openChatIconName, 3);
|
||||
scene.add.existing(this);
|
||||
this.setScrollFactor(0, 0);
|
||||
this.setOrigin(0, 1);
|
||||
this.displayWidth = 30;
|
||||
this.displayHeight = 30;
|
||||
this.setInteractive();
|
||||
this.setVisible(false)
|
||||
this.setVisible(false);
|
||||
this.setDepth(99999);
|
||||
|
||||
this.on("pointerup", () => discussionManager.showDiscussionPart());
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
export const warningContainerKey = 'warningContainer';
|
||||
export const warningContainerHtml = 'resources/html/warningContainer.html';
|
||||
|
||||
export class WarningContainer extends Phaser.GameObjects.DOMElement {
|
||||
|
||||
constructor(scene: Phaser.Scene) {
|
||||
super(scene, 100, 0);
|
||||
this.setOrigin(0, 0);
|
||||
this.createFromCache(warningContainerKey);
|
||||
this.scene.add.existing(this);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import {PlayerAnimationNames} from "../Player/Animation";
|
||||
import {PlayerAnimationDirections, PlayerAnimationTypes} from "../Player/Animation";
|
||||
import {SpeechBubble} from "./SpeechBubble";
|
||||
import BitmapText = Phaser.GameObjects.BitmapText;
|
||||
import Container = Phaser.GameObjects.Container;
|
||||
|
@ -10,8 +10,7 @@ interface AnimationData {
|
|||
frameRate: number;
|
||||
repeat: number;
|
||||
frameModel: string; //todo use an enum
|
||||
frameStart: number;
|
||||
frameEnd: number;
|
||||
frames : number[]
|
||||
}
|
||||
|
||||
export abstract class Character extends Container {
|
||||
|
@ -19,7 +18,7 @@ export abstract class Character extends Container {
|
|||
private readonly playerName: BitmapText;
|
||||
public PlayerValue: string;
|
||||
public sprites: Map<string, Sprite>;
|
||||
private lastDirection: string = PlayerAnimationNames.WalkDown;
|
||||
private lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down;
|
||||
//private teleportation: Sprite;
|
||||
private invisible: boolean;
|
||||
|
||||
|
@ -28,7 +27,7 @@ export abstract class Character extends Container {
|
|||
y: number,
|
||||
texturesPromise: Promise<string[]>,
|
||||
name: string,
|
||||
direction: string,
|
||||
direction: PlayerAnimationDirections,
|
||||
moving: boolean,
|
||||
frame?: string | number
|
||||
) {
|
||||
|
@ -81,7 +80,7 @@ export abstract class Character extends Container {
|
|||
this.getPlayerAnimations(texture).forEach(d => {
|
||||
this.scene.anims.create({
|
||||
key: d.key,
|
||||
frames: this.scene.anims.generateFrameNumbers(d.frameModel, {start: d.frameStart, end: d.frameEnd}),
|
||||
frames: this.scene.anims.generateFrameNumbers(d.frameModel, {frames: d.frames}),
|
||||
frameRate: d.frameRate,
|
||||
repeat: d.repeat
|
||||
});
|
||||
|
@ -96,37 +95,57 @@ export abstract class Character extends Container {
|
|||
|
||||
private getPlayerAnimations(name: string): AnimationData[] {
|
||||
return [{
|
||||
key: `${name}-${PlayerAnimationNames.WalkDown}`,
|
||||
key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frameStart: 0,
|
||||
frameEnd: 2,
|
||||
frames: [0, 1, 2, 1],
|
||||
frameRate: 10,
|
||||
repeat: -1
|
||||
}, {
|
||||
key: `${name}-${PlayerAnimationNames.WalkLeft}`,
|
||||
key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frameStart: 3,
|
||||
frameEnd: 5,
|
||||
frames: [3, 4, 5, 4],
|
||||
frameRate: 10,
|
||||
repeat: -1
|
||||
}, {
|
||||
key: `${name}-${PlayerAnimationNames.WalkRight}`,
|
||||
key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frameStart: 6,
|
||||
frameEnd: 8,
|
||||
frames: [6, 7, 8, 7],
|
||||
frameRate: 10,
|
||||
repeat: -1
|
||||
}, {
|
||||
key: `${name}-${PlayerAnimationNames.WalkUp}`,
|
||||
key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Walk}`,
|
||||
frameModel: name,
|
||||
frameStart: 9,
|
||||
frameEnd: 11,
|
||||
frames: [9, 10, 11, 10],
|
||||
frameRate: 10,
|
||||
repeat: -1
|
||||
},{
|
||||
key: `${name}-${PlayerAnimationDirections.Down}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [1],
|
||||
frameRate: 10,
|
||||
repeat: 1
|
||||
}, {
|
||||
key: `${name}-${PlayerAnimationDirections.Left}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [4],
|
||||
frameRate: 10,
|
||||
repeat: 1
|
||||
}, {
|
||||
key: `${name}-${PlayerAnimationDirections.Right}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [7],
|
||||
frameRate: 10,
|
||||
repeat: 1
|
||||
}, {
|
||||
key: `${name}-${PlayerAnimationDirections.Up}-${PlayerAnimationTypes.Idle}`,
|
||||
frameModel: name,
|
||||
frames: [10],
|
||||
frameRate: 10,
|
||||
repeat: 1
|
||||
}];
|
||||
}
|
||||
|
||||
protected playAnimation(direction : string, moving: boolean): void {
|
||||
protected playAnimation(direction : PlayerAnimationDirections, moving: boolean): void {
|
||||
if (this.invisible) return;
|
||||
for (const [texture, sprite] of this.sprites.entries()) {
|
||||
if (!sprite.anims) {
|
||||
|
@ -134,10 +153,9 @@ export abstract class Character extends Container {
|
|||
return;
|
||||
}
|
||||
if (moving && (!sprite.anims.currentAnim || sprite.anims.currentAnim.key !== direction)) {
|
||||
sprite.play(texture+'-'+direction, true);
|
||||
sprite.play(texture+'-'+direction+'-'+PlayerAnimationTypes.Walk, true);
|
||||
} else if (!moving) {
|
||||
sprite.anims.play(texture + '-' + direction, true);
|
||||
sprite.anims.stop();
|
||||
sprite.anims.play(texture + '-' + direction + '-'+PlayerAnimationTypes.Idle, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -157,17 +175,17 @@ export abstract class Character extends Container {
|
|||
|
||||
// up or down animations are prioritized over left and right
|
||||
if (body.velocity.y < 0) { //moving up
|
||||
this.lastDirection = PlayerAnimationNames.WalkUp;
|
||||
this.playAnimation(PlayerAnimationNames.WalkUp, true);
|
||||
this.lastDirection = PlayerAnimationDirections.Up;
|
||||
this.playAnimation(PlayerAnimationDirections.Up, true);
|
||||
} else if (body.velocity.y > 0) { //moving down
|
||||
this.lastDirection = PlayerAnimationNames.WalkDown;
|
||||
this.playAnimation(PlayerAnimationNames.WalkDown, true);
|
||||
this.lastDirection = PlayerAnimationDirections.Down;
|
||||
this.playAnimation(PlayerAnimationDirections.Down, true);
|
||||
} else if (body.velocity.x > 0) { //moving right
|
||||
this.lastDirection = PlayerAnimationNames.WalkRight;
|
||||
this.playAnimation(PlayerAnimationNames.WalkRight, true);
|
||||
this.lastDirection = PlayerAnimationDirections.Right;
|
||||
this.playAnimation(PlayerAnimationDirections.Right, true);
|
||||
} else if (body.velocity.x < 0) { //moving left
|
||||
this.lastDirection = PlayerAnimationNames.WalkLeft;
|
||||
this.playAnimation(PlayerAnimationNames.WalkLeft, true);
|
||||
this.lastDirection = PlayerAnimationDirections.Left;
|
||||
this.playAnimation(PlayerAnimationDirections.Left, true);
|
||||
}
|
||||
|
||||
this.setDepth(this.y);
|
||||
|
|
|
@ -6,7 +6,8 @@ export interface BodyResourceDescriptionListInterface {
|
|||
|
||||
export interface BodyResourceDescriptionInterface {
|
||||
name: string,
|
||||
img: string
|
||||
img: string,
|
||||
level?: number
|
||||
}
|
||||
|
||||
export const PLAYER_RESOURCES: BodyResourceDescriptionListInterface = {
|
||||
|
|
|
@ -23,21 +23,26 @@ export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptio
|
|||
});
|
||||
return returnArray;
|
||||
}
|
||||
export const loadCustomTexture = (load: LoaderPlugin, texture: CharacterTexture) => {
|
||||
|
||||
export const loadCustomTexture = (loaderPlugin: LoaderPlugin, texture: CharacterTexture) : Promise<BodyResourceDescriptionInterface> => {
|
||||
const name = 'customCharacterTexture'+texture.id;
|
||||
load.spritesheet(name,texture.url,{frameWidth: 32, frameHeight: 32});
|
||||
return name;
|
||||
const playerResourceDescriptor: BodyResourceDescriptionInterface = {name, img: texture.url, level: texture.level}
|
||||
return createLoadingPromise(loaderPlugin, playerResourceDescriptor);
|
||||
}
|
||||
|
||||
export const lazyLoadPlayerCharacterTextures = (loadPlugin: LoaderPlugin, texturePlugin: TextureManager, texturekeys:Array<string|BodyResourceDescriptionInterface>): Promise<string[]> => {
|
||||
const promisesList:Promise<void>[] = [];
|
||||
export const lazyLoadPlayerCharacterTextures = (loadPlugin: LoaderPlugin, texturekeys:Array<string|BodyResourceDescriptionInterface>): Promise<string[]> => {
|
||||
const promisesList:Promise<unknown>[] = [];
|
||||
texturekeys.forEach((textureKey: string|BodyResourceDescriptionInterface) => {
|
||||
const playerResourceDescriptor = getRessourceDescriptor(textureKey);
|
||||
if(!texturePlugin.exists(playerResourceDescriptor.name)) {
|
||||
console.log('Loading '+playerResourceDescriptor.name)
|
||||
promisesList.push(createLoadingPromise(loadPlugin, playerResourceDescriptor));
|
||||
try {
|
||||
//TODO refactor
|
||||
const playerResourceDescriptor = getRessourceDescriptor(textureKey);
|
||||
if (playerResourceDescriptor && !loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
|
||||
promisesList.push(createLoadingPromise(loadPlugin, playerResourceDescriptor));
|
||||
}
|
||||
}catch (err){
|
||||
console.error(err);
|
||||
}
|
||||
})
|
||||
});
|
||||
let returnPromise:Promise<Array<string|BodyResourceDescriptionInterface>>;
|
||||
if (promisesList.length > 0) {
|
||||
loadPlugin.start();
|
||||
|
@ -66,8 +71,14 @@ export const getRessourceDescriptor = (textureKey: string|BodyResourceDescriptio
|
|||
}
|
||||
|
||||
const createLoadingPromise = (loadPlugin: LoaderPlugin, playerResourceDescriptor: BodyResourceDescriptionInterface) => {
|
||||
return new Promise<void>((res, rej) => {
|
||||
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, {frameWidth: 32, frameHeight: 32});
|
||||
loadPlugin.once('filecomplete-spritesheet-'+playerResourceDescriptor.name, () => res());
|
||||
return new Promise<BodyResourceDescriptionInterface>((res) => {
|
||||
if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
|
||||
return res(playerResourceDescriptor);
|
||||
}
|
||||
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, {
|
||||
frameWidth: 32,
|
||||
frameHeight: 32
|
||||
});
|
||||
loadPlugin.once('filecomplete-spritesheet-' + playerResourceDescriptor.name, () => res(playerResourceDescriptor));
|
||||
});
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import {GameScene} from "../Game/GameScene";
|
||||
import {PointInterface} from "../../Connexion/ConnexionModels";
|
||||
import {Character} from "../Entity/Character";
|
||||
import {Sprite} from "./Sprite";
|
||||
import {PlayerAnimationDirections} from "../Player/Animation";
|
||||
|
||||
/**
|
||||
* Class representing the sprite of a remote player (a player that plays on another computer)
|
||||
|
@ -16,7 +16,7 @@ export class RemotePlayer extends Character {
|
|||
y: number,
|
||||
name: string,
|
||||
texturesPromise: Promise<string[]>,
|
||||
direction: string,
|
||||
direction: PlayerAnimationDirections,
|
||||
moving: boolean
|
||||
) {
|
||||
super(Scene, x, y, texturesPromise, name, direction, moving, 1);
|
||||
|
@ -26,7 +26,7 @@ export class RemotePlayer extends Character {
|
|||
}
|
||||
|
||||
updatePosition(position: PointInterface): void {
|
||||
this.playAnimation(position.direction, position.moving);
|
||||
this.playAnimation(position.direction as PlayerAnimationDirections, position.moving);
|
||||
this.setX(position.x);
|
||||
this.setY(position.y);
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ export class SpeechBubble {
|
|||
const bubbleWidth = bubblePadding * 2 + text.length * 10;
|
||||
const arrowHeight = bubbleHeight / 4;
|
||||
|
||||
this.bubble = scene.add.graphics({ x: player.x + 16, y: player.y - 80 });
|
||||
this.bubble = scene.add.graphics({ x: 16, y: -80 });
|
||||
player.add(this.bubble);
|
||||
|
||||
// Bubble shadow
|
||||
|
|
|
@ -2,6 +2,7 @@ import {GameScene} from "./GameScene";
|
|||
import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||
import {Room} from "../../Connexion/Room";
|
||||
import {MenuScene, MenuSceneName} from "../Menu/MenuScene";
|
||||
import {HelpCameraSettingsScene, HelpCameraSettingsSceneName} from "../Menu/HelpCameraSettingsScene";
|
||||
import {LoginSceneName} from "../Login/LoginScene";
|
||||
import {SelectCharacterSceneName} from "../Login/SelectCharacterScene";
|
||||
import {EnableCameraSceneName} from "../Login/EnableCameraScene";
|
||||
|
@ -78,6 +79,10 @@ export class GameManager {
|
|||
console.log('starting '+ (this.currentGameSceneName || this.startRoom.id))
|
||||
scenePlugin.start(this.currentGameSceneName || this.startRoom.id);
|
||||
scenePlugin.launch(MenuSceneName);
|
||||
|
||||
if (!localUserStore.getHelpCameraSettingsShown()) {
|
||||
scenePlugin.launch(HelpCameraSettingsSceneName);//700
|
||||
}
|
||||
}
|
||||
|
||||
public gameSceneIsCreated(scene: GameScene) {
|
||||
|
|
|
@ -29,6 +29,7 @@ export class GameMap {
|
|||
|
||||
const newProps = this.getProperties(key);
|
||||
const oldProps = this.lastProperties;
|
||||
this.lastProperties = newProps;
|
||||
|
||||
// Let's compare the 2 maps:
|
||||
// First new properties vs oldProperties
|
||||
|
@ -45,8 +46,10 @@ export class GameMap {
|
|||
this.trigger(oldPropName, oldPropValue, undefined, newProps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.lastProperties = newProps;
|
||||
public getCurrentProperties(): Map<string, string|boolean|number> {
|
||||
return this.lastProperties;
|
||||
}
|
||||
|
||||
private getProperties(key: number): Map<string, string|boolean|number> {
|
||||
|
|
|
@ -3,45 +3,36 @@ import {
|
|||
GroupCreatedUpdatedMessageInterface,
|
||||
MessageUserJoined,
|
||||
MessageUserMovedInterface,
|
||||
MessageUserPositionInterface, OnConnectInterface,
|
||||
MessageUserPositionInterface,
|
||||
OnConnectInterface,
|
||||
PointInterface,
|
||||
PositionInterface,
|
||||
RoomJoinedMessageInterface
|
||||
} from "../../Connexion/ConnexionModels";
|
||||
import {CurrentGamerInterface, hasMovedEventName, Player} from "../Player/Player";
|
||||
import {
|
||||
DEBUG_MODE,
|
||||
JITSI_PRIVATE_MODE,
|
||||
POSITION_DELAY,
|
||||
RESOLUTION,
|
||||
ZOOM_LEVEL
|
||||
} from "../../Enum/EnvironmentVariable";
|
||||
import {
|
||||
ITiledMap,
|
||||
ITiledMapLayer,
|
||||
ITiledMapLayerProperty, ITiledMapObject,
|
||||
ITiledTileSet
|
||||
} from "../Map/ITiledMap";
|
||||
import {DEBUG_MODE, JITSI_PRIVATE_MODE, POSITION_DELAY, RESOLUTION, ZOOM_LEVEL} from "../../Enum/EnvironmentVariable";
|
||||
import {ITiledMap, ITiledMapLayer, ITiledMapLayerProperty, ITiledMapObject, ITiledTileSet} from "../Map/ITiledMap";
|
||||
import {AddPlayerInterface} from "./AddPlayerInterface";
|
||||
import {PlayerAnimationNames} from "../Player/Animation";
|
||||
import {PlayerAnimationDirections} from "../Player/Animation";
|
||||
import {PlayerMovement} from "./PlayerMovement";
|
||||
import {PlayersPositionInterpolator} from "./PlayersPositionInterpolator";
|
||||
import {RemotePlayer} from "../Entity/RemotePlayer";
|
||||
import {Queue} from 'queue-typescript';
|
||||
import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer";
|
||||
import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene";
|
||||
import {lazyLoadPlayerCharacterTextures} from "../Entity/PlayerTexturesLoadingManager";
|
||||
import {lazyLoadPlayerCharacterTextures, loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager";
|
||||
import {
|
||||
CenterListener,
|
||||
JITSI_MESSAGE_PROPERTIES,
|
||||
layoutManager,
|
||||
LayoutMode,
|
||||
ON_ACTION_TRIGGER_BUTTON, TRIGGER_JITSI_PROPERTIES, TRIGGER_WEBSITE_PROPERTIES
|
||||
ON_ACTION_TRIGGER_BUTTON,
|
||||
TRIGGER_JITSI_PROPERTIES,
|
||||
TRIGGER_WEBSITE_PROPERTIES,
|
||||
WEBSITE_MESSAGE_PROPERTIES,
|
||||
AUDIO_VOLUME_PROPERTY,
|
||||
AUDIO_LOOP_PROPERTY
|
||||
} from "../../WebRtc/LayoutManager";
|
||||
import Texture = Phaser.Textures.Texture;
|
||||
import Sprite = Phaser.GameObjects.Sprite;
|
||||
import CanvasTexture = Phaser.Textures.CanvasTexture;
|
||||
import GameObject = Phaser.GameObjects.GameObject;
|
||||
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
|
||||
import {GameMap} from "./GameMap";
|
||||
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
|
||||
import {mediaManager} from "../../WebRtc/MediaManager";
|
||||
|
@ -53,7 +44,7 @@ import {ProtobufClientUtils} from "../../Network/ProtobufClientUtils";
|
|||
import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||
import {RoomConnection} from "../../Connexion/RoomConnection";
|
||||
import {GlobalMessageManager} from "../../Administration/GlobalMessageManager";
|
||||
import {UserMessageManager} from "../../Administration/UserMessageManager";
|
||||
import {userMessageManager} from "../../Administration/UserMessageManager";
|
||||
import {ConsoleGlobalMessageManager} from "../../Administration/ConsoleGlobalMessageManager";
|
||||
import {ResizableScene} from "../Login/ResizableScene";
|
||||
import {Room} from "../../Connexion/Room";
|
||||
|
@ -68,6 +59,14 @@ import {SelectCharacterScene, SelectCharacterSceneName} from "../Login/SelectCha
|
|||
import {TextureError} from "../../Exception/TextureError";
|
||||
import {addLoader} from "../Components/Loader";
|
||||
import {ErrorSceneName} from "../Reconnecting/ErrorScene";
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
import Texture = Phaser.Textures.Texture;
|
||||
import Sprite = Phaser.GameObjects.Sprite;
|
||||
import CanvasTexture = Phaser.Textures.CanvasTexture;
|
||||
import GameObject = Phaser.GameObjects.GameObject;
|
||||
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
|
||||
import {Subscription} from "rxjs";
|
||||
import {worldFullMessageStream} from "../../Connexion/WorldFullMessageStream";
|
||||
|
||||
export interface GameSceneInitInterface {
|
||||
initPosition: PointInterface|null,
|
||||
|
@ -126,7 +125,6 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
public connection!: RoomConnection;
|
||||
private simplePeer!: SimplePeer;
|
||||
private GlobalMessageManager!: GlobalMessageManager;
|
||||
private UserMessageManager!: UserMessageManager;
|
||||
public ConsoleGlobalMessageManager!: ConsoleGlobalMessageManager;
|
||||
private connectionAnswerPromise: Promise<RoomJoinedMessageInterface>;
|
||||
private connectionAnswerPromiseResolve!: (value?: RoomJoinedMessageInterface | PromiseLike<RoomJoinedMessageInterface>) => void;
|
||||
|
@ -153,12 +151,13 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
private actionableItems: Map<number, ActionableItem> = new Map<number, ActionableItem>();
|
||||
// The item that can be selected by pressing the space key.
|
||||
private outlinedItem: ActionableItem|null = null;
|
||||
private userInputManager!: UserInputManager;
|
||||
private isReconnecting: boolean = false;
|
||||
public userInputManager!: UserInputManager;
|
||||
private isReconnecting: boolean|undefined = undefined;
|
||||
private startLayerName!: string | null;
|
||||
private openChatIcon!: OpenChatIcon;
|
||||
private playerName!: string;
|
||||
private characterLayers!: string[];
|
||||
private messageSubscription: Subscription|null = null;
|
||||
|
||||
constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) {
|
||||
super({
|
||||
|
@ -182,7 +181,13 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
|
||||
//hook preload scene
|
||||
preload(): void {
|
||||
addLoader(this);
|
||||
const localUser = localUserStore.getLocalUser();
|
||||
const textures = localUser?.textures;
|
||||
if (textures) {
|
||||
for (const texture of textures) {
|
||||
loadCustomTexture(this.load, texture);
|
||||
}
|
||||
}
|
||||
|
||||
this.load.image(openChatIconName, 'resources/objects/talk.png');
|
||||
this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => {
|
||||
|
@ -207,6 +212,8 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
|
||||
this.load.spritesheet('layout_modes', 'resources/objects/layout_modes.png', {frameWidth: 32, frameHeight: 32});
|
||||
this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
|
||||
|
||||
addLoader(this);
|
||||
}
|
||||
|
||||
// FIXME: we need to put a "unknown" instead of a "any" and validate the structure of the JSON we are receiving.
|
||||
|
@ -283,25 +290,6 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
// import(/* webpackIgnore: true */ scriptUrl).then(result => {
|
||||
//
|
||||
// result.default.preload(this.load);
|
||||
//
|
||||
// this.load.start(); // Let's manually start the loader because the import might be over AFTER the loading ends.
|
||||
// this.load.on('complete', () => {
|
||||
// // FIXME: the factory might fail because the resources might not be loaded yet...
|
||||
// // We would need to add a loader ended event in addition to the createPromise
|
||||
// this.createPromise.then(() => {
|
||||
// result.default.create(this);
|
||||
//
|
||||
// for (let object of objectsOfType) {
|
||||
// // TODO: we should pass here a factory to create sprites (maybe?)
|
||||
// let objectSprite = result.default.factory(this, object);
|
||||
// }
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -320,6 +308,8 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
gameManager.gameSceneIsCreated(this);
|
||||
urlManager.pushRoomIdToUrl(this.room);
|
||||
this.startLayerName = urlManager.getStartLayerNameFromUrl();
|
||||
|
||||
this.messageSubscription = worldFullMessageStream.stream.subscribe((message) => this.showWorldFullError())
|
||||
|
||||
const playerName = gameManager.getPlayerName();
|
||||
if (!playerName) {
|
||||
|
@ -393,13 +383,13 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
this.scene.launch(ReconnectingSceneName);
|
||||
}, 0);
|
||||
} else if (this.connection === undefined) {
|
||||
// Let's wait 0.5 seconds before printing the "connecting" screen to avoid blinking
|
||||
// Let's wait 1 second before printing the "connecting" screen to avoid blinking
|
||||
setTimeout(() => {
|
||||
if (this.connection === undefined) {
|
||||
this.scene.sleep();
|
||||
this.scene.launch(ReconnectingSceneName);
|
||||
}
|
||||
}, 500);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
copyrightInfo.initCopyrightInfo(mapDirUrl);
|
||||
|
@ -474,16 +464,12 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
});
|
||||
|
||||
this.connection.onGroupUpdatedOrCreated((groupPositionMessage: GroupCreatedUpdatedMessageInterface) => {
|
||||
audioManager.decreaseVolume();
|
||||
this.shareGroupPosition(groupPositionMessage);
|
||||
this.openChatIcon.setVisible(true);
|
||||
})
|
||||
|
||||
this.connection.onGroupDeleted((groupId: number) => {
|
||||
audioManager.restoreVolume();
|
||||
try {
|
||||
this.deleteGroup(groupId);
|
||||
this.openChatIcon.setVisible(false);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
@ -491,9 +477,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
|
||||
this.connection.onServerDisconnected(() => {
|
||||
console.log('Player disconnected from server. Reloading scene.');
|
||||
|
||||
this.simplePeer.closeAllConnections();
|
||||
this.simplePeer.unregister();
|
||||
this.cleanupClosingScene();
|
||||
|
||||
const gameSceneKey = 'somekey' + Math.round(Math.random() * 10000);
|
||||
const game: Phaser.Scene = new GameScene(this.room, this.MapUrlFile, gameSceneKey);
|
||||
|
@ -529,18 +513,22 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
// When connection is performed, let's connect SimplePeer
|
||||
this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic, this.playerName);
|
||||
this.GlobalMessageManager = new GlobalMessageManager(this.connection);
|
||||
this.UserMessageManager = new UserMessageManager(this.connection);
|
||||
userMessageManager.setReceiveBanListener(this.bannedUser.bind(this));
|
||||
|
||||
const self = this;
|
||||
this.simplePeer.registerPeerConnectionListener({
|
||||
onConnect(user: UserSimplePeerInterface) {
|
||||
self.presentationModeSprite.setVisible(true);
|
||||
self.chatModeSprite.setVisible(true);
|
||||
self.openChatIcon.setVisible(true);
|
||||
audioManager.decreaseVolume();
|
||||
},
|
||||
onDisconnect(userId: number) {
|
||||
if (self.simplePeer.getNbConnections() === 0) {
|
||||
self.presentationModeSprite.setVisible(false);
|
||||
self.chatModeSprite.setVisible(false);
|
||||
self.openChatIcon.setVisible(false);
|
||||
audioManager.restoreVolume();
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -563,11 +551,10 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
|
||||
//init user position and play trigger to check layers properties
|
||||
this.gameMap.setPosition(this.CurrentPlayer.x, this.CurrentPlayer.y);
|
||||
|
||||
return this.connection;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
//todo: into dedicated classes
|
||||
private initCirclesCanvas(): void {
|
||||
// Let's generate the circle for the group delimiter
|
||||
|
@ -601,28 +588,14 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
contextRed.stroke();
|
||||
this.circleRedTexture.refresh();
|
||||
}
|
||||
|
||||
|
||||
private playAudio(url: string|number|boolean|undefined, loop=false): void {
|
||||
if (url === undefined) {
|
||||
audioManager.unloadAudio();
|
||||
} else {
|
||||
const audioPath = url as string;
|
||||
let realAudioPath = '';
|
||||
|
||||
if (audioPath.indexOf('://') > 0) {
|
||||
// remote file or stream
|
||||
realAudioPath = audioPath;
|
||||
} else {
|
||||
// local file, include it relative to map directory
|
||||
const mapDirUrl = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf('/'));
|
||||
realAudioPath = mapDirUrl + '/' + url;
|
||||
}
|
||||
|
||||
audioManager.loadAudio(realAudioPath);
|
||||
|
||||
if (loop) {
|
||||
audioManager.loop();
|
||||
}
|
||||
private safeParseJSONstring(jsonString: string|undefined, propertyName: string) {
|
||||
try {
|
||||
return jsonString ? JSON.parse(jsonString) : {};
|
||||
} catch(e) {
|
||||
console.warn('Invalid JSON found in property "' + propertyName + '" of the map:' + jsonString, e);
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -639,13 +612,17 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
coWebsiteManager.closeCoWebsite();
|
||||
}else{
|
||||
const openWebsiteFunction = () => {
|
||||
coWebsiteManager.loadCoWebsite(newValue as string);
|
||||
coWebsiteManager.loadCoWebsite(newValue as string, this.MapUrlFile, allProps.get('openWebsitePolicy') as string | undefined);
|
||||
layoutManager.removeActionButton('openWebsite', this.userInputManager);
|
||||
};
|
||||
|
||||
const openWebsiteTriggerValue = allProps.get(TRIGGER_WEBSITE_PROPERTIES);
|
||||
if(openWebsiteTriggerValue && openWebsiteTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
|
||||
layoutManager.addActionButton('openWebsite', 'Click on SPACE to open the web site', () => {
|
||||
let message = allProps.get(WEBSITE_MESSAGE_PROPERTIES);
|
||||
if(message === undefined){
|
||||
message = 'Press on SPACE to open the web site';
|
||||
}
|
||||
layoutManager.addActionButton('openWebsite', message.toString(), () => {
|
||||
openWebsiteFunction();
|
||||
}, this.userInputManager);
|
||||
}else{
|
||||
|
@ -659,26 +636,32 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
this.stopJitsi();
|
||||
}else{
|
||||
const openJitsiRoomFunction = () => {
|
||||
if (JITSI_PRIVATE_MODE) {
|
||||
const roomName = jitsiFactory.getRoomName(newValue.toString(), this.instance);
|
||||
const jitsiUrl = allProps.get("jitsiUrl") as string|undefined;
|
||||
if (JITSI_PRIVATE_MODE && !jitsiUrl) {
|
||||
const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined;
|
||||
|
||||
this.connection.emitQueryJitsiJwtMessage(this.instance.replace('/', '-') + "-" + newValue, adminTag);
|
||||
this.connection.emitQueryJitsiJwtMessage(roomName, adminTag);
|
||||
} else {
|
||||
this.startJitsi(newValue as string);
|
||||
this.startJitsi(roomName, undefined);
|
||||
}
|
||||
layoutManager.removeActionButton('jitsiRoom', this.userInputManager);
|
||||
}
|
||||
|
||||
const jitsiTriggerValue = allProps.get(TRIGGER_JITSI_PROPERTIES);
|
||||
if(jitsiTriggerValue && jitsiTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
|
||||
layoutManager.addActionButton('jitsiRoom', 'Click on SPACE to enter in jitsi meet room', () => {
|
||||
let message = allProps.get(JITSI_MESSAGE_PROPERTIES);
|
||||
if (message === undefined) {
|
||||
message = 'Press on SPACE to enter in jitsi meet room';
|
||||
}
|
||||
layoutManager.addActionButton('jitsiRoom', message.toString(), () => {
|
||||
openJitsiRoomFunction();
|
||||
}, this.userInputManager);
|
||||
}else{
|
||||
openJitsiRoomFunction();
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
this.gameMap.onPropertyChange('silent', (newValue, oldValue) => {
|
||||
if (newValue === undefined || newValue === false || newValue === '') {
|
||||
this.connection.setSilent(false);
|
||||
|
@ -686,14 +669,19 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
this.connection.setSilent(true);
|
||||
}
|
||||
});
|
||||
this.gameMap.onPropertyChange('playAudio', (newValue, oldValue) => {
|
||||
this.playAudio(newValue);
|
||||
this.gameMap.onPropertyChange('playAudio', (newValue, oldValue, allProps) => {
|
||||
const volume = allProps.get(AUDIO_VOLUME_PROPERTY) as number|undefined;
|
||||
const loop = allProps.get(AUDIO_LOOP_PROPERTY) as boolean|undefined;
|
||||
newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl(), volume, loop);
|
||||
});
|
||||
|
||||
// TODO: This legacy property should be removed at some point
|
||||
this.gameMap.onPropertyChange('playAudioLoop', (newValue, oldValue) => {
|
||||
this.playAudio(newValue, true);
|
||||
newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl(), undefined, true);
|
||||
});
|
||||
}
|
||||
|
||||
private getMapDirUrl(): string {
|
||||
return this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf('/'));
|
||||
}
|
||||
|
||||
private onMapExit(exitKey: string) {
|
||||
|
@ -701,6 +689,10 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
if (!roomId) throw new Error('Could not find the room from its exit key: '+exitKey);
|
||||
urlManager.pushStartLayerNameToUrl(hash);
|
||||
if (roomId !== this.scene.key) {
|
||||
if (this.scene.get(roomId) === null) {
|
||||
console.error("next room not loaded", exitKey);
|
||||
return;
|
||||
}
|
||||
this.cleanupClosingScene();
|
||||
this.scene.stop();
|
||||
this.scene.remove(this.scene.key);
|
||||
|
@ -717,14 +709,12 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
// stop playing audio, close any open website, stop any open Jitsi
|
||||
coWebsiteManager.closeCoWebsite();
|
||||
this.stopJitsi();
|
||||
this.playAudio(undefined);
|
||||
audioManager.unloadAudio();
|
||||
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
|
||||
if(this.connection) {
|
||||
this.connection.closeConnection();
|
||||
}
|
||||
if(this.simplePeer) {
|
||||
this.simplePeer.unregister();
|
||||
}
|
||||
this.connection?.closeConnection();
|
||||
this.simplePeer.closeAllConnections();
|
||||
this.simplePeer?.unregister();
|
||||
this.messageSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
private removeAllRemotePlayers(): void {
|
||||
|
@ -878,7 +868,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
|
||||
createCurrentPlayer(){
|
||||
//TODO create animation moving between exit and start
|
||||
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.textures, this.characterLayers);
|
||||
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.characterLayers);
|
||||
try {
|
||||
this.CurrentPlayer = new Player(
|
||||
this,
|
||||
|
@ -886,7 +876,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
this.startY,
|
||||
this.playerName,
|
||||
texturesPromise,
|
||||
PlayerAnimationNames.WalkDown,
|
||||
PlayerAnimationDirections.Down,
|
||||
false,
|
||||
this.userInputManager
|
||||
);
|
||||
|
@ -935,16 +925,16 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
let x = event.x;
|
||||
let y = event.y;
|
||||
switch (event.direction) {
|
||||
case PlayerAnimationNames.WalkUp:
|
||||
case PlayerAnimationDirections.Up:
|
||||
y -= 32;
|
||||
break;
|
||||
case PlayerAnimationNames.WalkDown:
|
||||
case PlayerAnimationDirections.Down:
|
||||
y += 32;
|
||||
break;
|
||||
case PlayerAnimationNames.WalkLeft:
|
||||
case PlayerAnimationDirections.Left:
|
||||
x -= 32;
|
||||
break;
|
||||
case PlayerAnimationNames.WalkRight:
|
||||
case PlayerAnimationDirections.Right:
|
||||
x += 32;
|
||||
break;
|
||||
default:
|
||||
|
@ -1015,13 +1005,12 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Let's move all users
|
||||
const updatedPlayersPositions = this.playersPositionInterpolator.getUpdatedPositions(time);
|
||||
updatedPlayersPositions.forEach((moveEvent: HasMovedEvent, userId: number) => {
|
||||
const player : RemotePlayer | undefined = this.MapPlayersByKey.get(userId);
|
||||
const player: RemotePlayer | undefined = this.MapPlayersByKey.get(userId);
|
||||
if (player === undefined) {
|
||||
throw new Error('Cannot find player with ID "' + userId +'"');
|
||||
throw new Error('Cannot find player with ID "' + userId + '"');
|
||||
}
|
||||
player.updatePosition(moveEvent);
|
||||
});
|
||||
|
@ -1072,7 +1061,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
return;
|
||||
}
|
||||
|
||||
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.textures, addPlayerData.characterLayers);
|
||||
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, addPlayerData.characterLayers);
|
||||
const player = new RemotePlayer(
|
||||
addPlayerData.userId,
|
||||
this,
|
||||
|
@ -1080,7 +1069,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
addPlayerData.position.y,
|
||||
addPlayerData.name,
|
||||
texturesPromise,
|
||||
addPlayerData.position.direction,
|
||||
addPlayerData.position.direction as PlayerAnimationDirections,
|
||||
addPlayerData.position.moving
|
||||
);
|
||||
this.MapPlayers.add(player);
|
||||
|
@ -1197,6 +1186,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
private reposition(): void {
|
||||
this.presentationModeSprite.setY(this.game.renderer.height - 2);
|
||||
this.chatModeSprite.setY(this.game.renderer.height - 2);
|
||||
this.openChatIcon.setY(this.game.renderer.height - 2);
|
||||
|
||||
// Recompute camera offset if needed
|
||||
this.updateCameraOffset();
|
||||
|
@ -1223,7 +1213,12 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
}
|
||||
|
||||
public startJitsi(roomName: string, jwt?: string): void {
|
||||
jitsiFactory.start(roomName, this.playerName, jwt);
|
||||
const allProps = this.gameMap.getCurrentProperties();
|
||||
const jitsiConfig = this.safeParseJSONstring(allProps.get("jitsiConfig") as string|undefined, 'jitsiConfig');
|
||||
const jitsiInterfaceConfig = this.safeParseJSONstring(allProps.get("jitsiInterfaceConfig") as string|undefined, 'jitsiInterfaceConfig');
|
||||
const jitsiUrl = allProps.get("jitsiUrl") as string|undefined;
|
||||
|
||||
jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl);
|
||||
this.connection.setSilent(true);
|
||||
mediaManager.hideGameOverlay();
|
||||
|
||||
|
@ -1234,12 +1229,33 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
}
|
||||
|
||||
public stopJitsi(): void {
|
||||
this.connection.setSilent(false);
|
||||
this.connection?.setSilent(false);
|
||||
jitsiFactory.stop();
|
||||
mediaManager.showGameOverlay();
|
||||
|
||||
mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi');
|
||||
}
|
||||
|
||||
//todo: put this into an 'orchestrator' scene (EntryScene?)
|
||||
private bannedUser(){
|
||||
this.cleanupClosingScene();
|
||||
this.userInputManager.clearAllKeys();
|
||||
this.scene.start(ErrorSceneName, {
|
||||
title: 'Banned',
|
||||
subTitle: 'You were banned from WorkAdventure',
|
||||
message: 'If you want more information, you may contact us at: workadventure@thecodingmachine.com'
|
||||
});
|
||||
}
|
||||
|
||||
//todo: put this into an 'orchestrator' scene (EntryScene?)
|
||||
private showWorldFullError(): void {
|
||||
this.cleanupClosingScene();
|
||||
this.scene.stop(ReconnectingSceneName);
|
||||
this.userInputManager.clearAllKeys();
|
||||
this.scene.start(ErrorSceneName, {
|
||||
title: 'Connection rejected',
|
||||
subTitle: 'The world you are trying to join is full. Try again later.',
|
||||
message: 'If you want more information, you may contact us at: workadventure@thecodingmachine.com'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,8 +43,9 @@ export class ActionableItem {
|
|||
}
|
||||
this.isSelectable = true;
|
||||
if (this.sprite.pipeline) {
|
||||
this.sprite.setPipeline(OutlinePipeline.KEY);
|
||||
this.sprite.pipeline.set2f('uTextureSize', this.sprite.texture.getSourceImage().width, this.sprite.texture.getSourceImage().height);
|
||||
// Commented out to try to fix MacOS issue
|
||||
/*this.sprite.setPipeline(OutlinePipeline.KEY);
|
||||
this.sprite.pipeline.set2f('uTextureSize', this.sprite.texture.getSourceImage().width, this.sprite.texture.getSourceImage().height);*/
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,7 +57,8 @@ export class ActionableItem {
|
|||
return;
|
||||
}
|
||||
this.isSelectable = false;
|
||||
this.sprite.resetPipeline();
|
||||
// Commented out to try to fix MacOS issue
|
||||
//this.sprite.resetPipeline();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
import {ResizableScene} from "./ResizableScene";
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||
import {loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager";
|
||||
import {CharacterTexture} from "../../Connexion/LocalUser";
|
||||
|
||||
export abstract class AbstractCharacterScene extends ResizableScene {
|
||||
|
||||
loadCustomSceneSelectCharacters() : Promise<BodyResourceDescriptionInterface[]> {
|
||||
const textures = this.getTextures();
|
||||
const promises : Promise<BodyResourceDescriptionInterface>[] = [];
|
||||
if (textures) {
|
||||
for (const texture of textures) {
|
||||
if (texture.level === -1) {
|
||||
continue;
|
||||
}
|
||||
promises.push(loadCustomTexture(this.load, texture));
|
||||
}
|
||||
}
|
||||
return Promise.all(promises)
|
||||
}
|
||||
|
||||
loadSelectSceneCharacters() : Promise<BodyResourceDescriptionInterface[]> {
|
||||
const textures = this.getTextures();
|
||||
const promises: Promise<BodyResourceDescriptionInterface>[] = [];
|
||||
if (textures) {
|
||||
for (const texture of textures) {
|
||||
if (texture.level !== -1) {
|
||||
continue;
|
||||
}
|
||||
promises.push(loadCustomTexture(this.load, texture));
|
||||
}
|
||||
}
|
||||
return Promise.all(promises)
|
||||
}
|
||||
|
||||
private getTextures() : CharacterTexture[]|undefined{
|
||||
const localUser = localUserStore.getLocalUser();
|
||||
return localUser?.textures;
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ import {ResizableScene} from "./ResizableScene";
|
|||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
import {addLoader} from "../Components/Loader";
|
||||
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||
import {AbstractCharacterScene} from "./AbstractCharacterScene";
|
||||
|
||||
export const CustomizeSceneName = "CustomizeScene";
|
||||
|
||||
|
@ -20,7 +21,7 @@ enum CustomizeTextures{
|
|||
arrowUp = "arrow_up",
|
||||
}
|
||||
|
||||
export class CustomizeScene extends ResizableScene {
|
||||
export class CustomizeScene extends AbstractCharacterScene {
|
||||
|
||||
private textField!: TextField;
|
||||
private enterField!: TextField;
|
||||
|
@ -48,29 +49,21 @@ export class CustomizeScene extends ResizableScene {
|
|||
|
||||
preload() {
|
||||
addLoader(this);
|
||||
|
||||
this.layers = loadAllLayers(this.load);
|
||||
this.loadCustomSceneSelectCharacters().then((bodyResourceDescriptions) => {
|
||||
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
|
||||
if(!bodyResourceDescription.level){
|
||||
throw 'Texture level is null';
|
||||
}
|
||||
this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription);
|
||||
});
|
||||
});
|
||||
|
||||
this.load.image(CustomizeTextures.arrowRight, "resources/objects/arrow_right.png");
|
||||
this.load.image(CustomizeTextures.icon, "resources/logos/tcm_full.png");
|
||||
this.load.image(CustomizeTextures.arrowUp, "resources/objects/arrow_up.png");
|
||||
this.load.bitmapFont(CustomizeTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
|
||||
|
||||
this.layers = loadAllLayers(this.load);
|
||||
|
||||
const localUser = localUserStore.getLocalUser();
|
||||
|
||||
const textures = localUser?.textures;
|
||||
if (textures) {
|
||||
for (const texture of textures) {
|
||||
if(texture.level === -1){
|
||||
continue;
|
||||
}
|
||||
loadCustomTexture(this.load, texture);
|
||||
const name = 'customCharacterTexture'+texture.id;
|
||||
this.layers[texture.level].unshift({
|
||||
name,
|
||||
img: texture.url
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
create() {
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import {gameManager} from "../Game/GameManager";
|
||||
import {TextField} from "../Components/TextField";
|
||||
import Image = Phaser.GameObjects.Image;
|
||||
import {GameSceneInitInterface} from "../Game/GameScene";
|
||||
import {StartMapInterface} from "../../Connexion/ConnexionModels";
|
||||
import {mediaManager} from "../../WebRtc/MediaManager";
|
||||
import {RESOLUTION} from "../../Enum/EnvironmentVariable";
|
||||
import {SoundMeter} from "../Components/SoundMeter";
|
||||
|
@ -18,6 +16,7 @@ enum LoginTextures {
|
|||
arrowUp = "arrow_up"
|
||||
}
|
||||
|
||||
|
||||
export class EnableCameraScene extends Phaser.Scene {
|
||||
private textField!: TextField;
|
||||
private pressReturnField!: TextField;
|
||||
|
@ -62,26 +61,22 @@ export class EnableCameraScene extends Phaser.Scene {
|
|||
this.microphoneNameField = new TextField(this, this.game.renderer.width / 2, this.game.renderer.height - 40, '');
|
||||
|
||||
this.arrowRight = new Image(this, 0, 0, LoginTextures.arrowRight);
|
||||
this.arrowRight.setOrigin(0.5, 0.5);
|
||||
this.arrowRight.setVisible(false);
|
||||
this.arrowRight.setInteractive().on('pointerdown', this.nextCam.bind(this));
|
||||
this.add.existing(this.arrowRight);
|
||||
|
||||
this.arrowLeft = new Image(this, 0, 0, LoginTextures.arrowRight);
|
||||
this.arrowLeft.setOrigin(0.5, 0.5);
|
||||
this.arrowLeft.setVisible(false);
|
||||
this.arrowLeft.flipX = true;
|
||||
this.arrowLeft.setInteractive().on('pointerdown', this.previousCam.bind(this));
|
||||
this.add.existing(this.arrowLeft);
|
||||
|
||||
this.arrowUp = new Image(this, 0, 0, LoginTextures.arrowUp);
|
||||
this.arrowUp.setOrigin(0.5, 0.5);
|
||||
this.arrowUp.setVisible(false);
|
||||
this.arrowUp.setInteractive().on('pointerdown', this.previousMic.bind(this));
|
||||
this.add.existing(this.arrowUp);
|
||||
|
||||
this.arrowDown = new Image(this, 0, 0, LoginTextures.arrowUp);
|
||||
this.arrowDown.setOrigin(0.5, 0.5);
|
||||
this.arrowDown.setVisible(false);
|
||||
this.arrowDown.flipY = true;
|
||||
this.arrowDown.setInteractive().on('pointerdown', this.nextMic.bind(this));
|
||||
|
@ -165,8 +160,6 @@ export class EnableCameraScene extends Phaser.Scene {
|
|||
|
||||
private updateWebCamName(): void {
|
||||
if (this.camerasList.length > 1) {
|
||||
const div = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideoSetup');
|
||||
|
||||
let label = this.camerasList[this.cameraSelected].label;
|
||||
// remove text in parenthesis
|
||||
label = label.replace(/\([^()]*\)/g, '').trim();
|
||||
|
@ -174,17 +167,8 @@ export class EnableCameraScene extends Phaser.Scene {
|
|||
label = label.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
||||
this.cameraNameField.text = label;
|
||||
|
||||
if (this.cameraSelected < this.camerasList.length - 1) {
|
||||
this.arrowRight.setVisible(true);
|
||||
} else {
|
||||
this.arrowRight.setVisible(false);
|
||||
}
|
||||
|
||||
if (this.cameraSelected > 0) {
|
||||
this.arrowLeft.setVisible(true);
|
||||
} else {
|
||||
this.arrowLeft.setVisible(false);
|
||||
}
|
||||
this.arrowRight.setVisible(this.cameraSelected < this.camerasList.length - 1);
|
||||
this.arrowLeft.setVisible(this.cameraSelected > 0);
|
||||
}
|
||||
if (this.microphonesList.length > 1) {
|
||||
let label = this.microphonesList[this.microphoneSelected].label;
|
||||
|
@ -195,17 +179,8 @@ export class EnableCameraScene extends Phaser.Scene {
|
|||
|
||||
this.microphoneNameField.text = label;
|
||||
|
||||
if (this.microphoneSelected < this.microphonesList.length - 1) {
|
||||
this.arrowDown.setVisible(true);
|
||||
} else {
|
||||
this.arrowDown.setVisible(false);
|
||||
}
|
||||
|
||||
if (this.microphoneSelected > 0) {
|
||||
this.arrowUp.setVisible(true);
|
||||
} else {
|
||||
this.arrowUp.setVisible(false);
|
||||
}
|
||||
this.arrowDown.setVisible(this.microphoneSelected < this.microphonesList.length - 1);
|
||||
this.arrowUp.setVisible(this.microphoneSelected > 0);
|
||||
|
||||
}
|
||||
this.reposition();
|
||||
|
|
|
@ -2,7 +2,6 @@ import {gameManager} from "../Game/GameManager";
|
|||
import {TextField} from "../Components/TextField";
|
||||
import {TextInput} from "../Components/TextInput";
|
||||
import Image = Phaser.GameObjects.Image;
|
||||
import {cypressAsserter} from "../../Cypress/CypressAsserter";
|
||||
import {SelectCharacterSceneName} from "./SelectCharacterScene";
|
||||
import {ResizableScene} from "./ResizableScene";
|
||||
|
||||
|
@ -29,16 +28,13 @@ export class LoginScene extends ResizableScene {
|
|||
}
|
||||
|
||||
preload() {
|
||||
cypressAsserter.preloadStarted();
|
||||
//this.load.image(LoginTextures.playButton, "resources/objects/play_button.png");
|
||||
this.load.image(LoginTextures.icon, "resources/logos/tcm_full.png");
|
||||
// Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap
|
||||
this.load.bitmapFont(LoginTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
|
||||
cypressAsserter.preloadFinished();
|
||||
}
|
||||
|
||||
create() {
|
||||
cypressAsserter.initStarted();
|
||||
|
||||
this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Enter your name:');
|
||||
this.nameInput = new TextInput(this, this.game.renderer.width / 2, 70, 8, this.name,(text: string) => {
|
||||
|
@ -59,8 +55,6 @@ export class LoginScene extends ResizableScene {
|
|||
}
|
||||
this.login(this.name);
|
||||
});
|
||||
|
||||
cypressAsserter.initFinished();
|
||||
}
|
||||
|
||||
update(time: number, delta: number): void {
|
||||
|
|
|
@ -9,6 +9,7 @@ import {localUserStore} from "../../Connexion/LocalUserStore";
|
|||
import {loadAllDefaultModels, loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager";
|
||||
import {addLoader} from "../Components/Loader";
|
||||
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||
import {AbstractCharacterScene} from "./AbstractCharacterScene";
|
||||
|
||||
|
||||
//todo: put this constants in a dedicated file
|
||||
|
@ -21,7 +22,7 @@ enum LoginTextures {
|
|||
customizeButtonSelected = "customize_button_selected"
|
||||
}
|
||||
|
||||
export class SelectCharacterScene extends ResizableScene {
|
||||
export class SelectCharacterScene extends AbstractCharacterScene {
|
||||
private readonly nbCharactersPerRow = 6;
|
||||
private textField!: TextField;
|
||||
private pressReturnField!: TextField;
|
||||
|
@ -44,6 +45,13 @@ export class SelectCharacterScene extends ResizableScene {
|
|||
|
||||
preload() {
|
||||
addLoader(this);
|
||||
|
||||
this.loadSelectSceneCharacters().then((bodyResourceDescriptions) => {
|
||||
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
|
||||
this.playerModels.push(bodyResourceDescription);
|
||||
});
|
||||
})
|
||||
|
||||
this.load.image(LoginTextures.playButton, "resources/objects/play_button.png");
|
||||
this.load.image(LoginTextures.icon, "resources/logos/tcm_full.png");
|
||||
// Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap
|
||||
|
@ -52,17 +60,7 @@ export class SelectCharacterScene extends ResizableScene {
|
|||
this.load.image(LoginTextures.customizeButton, 'resources/objects/customize.png');
|
||||
this.load.image(LoginTextures.customizeButtonSelected, 'resources/objects/customize_selected.png');
|
||||
|
||||
const localUser = localUserStore.getLocalUser();
|
||||
const textures = localUser?.textures;
|
||||
if (textures) {
|
||||
for (const texture of textures) {
|
||||
if(texture.level !== -1){
|
||||
continue;
|
||||
}
|
||||
const name = loadCustomTexture(this.load, texture);
|
||||
this.playerModels.push({name: name, img: texture.url});
|
||||
}
|
||||
}
|
||||
addLoader(this);
|
||||
}
|
||||
|
||||
create() {
|
||||
|
@ -127,7 +125,7 @@ export class SelectCharacterScene extends ResizableScene {
|
|||
|
||||
/*create user*/
|
||||
this.createCurrentPlayer();
|
||||
|
||||
|
||||
const playerNumber = localUserStore.getPlayerCharacterIndex();
|
||||
if (playerNumber && playerNumber !== -1) {
|
||||
this.selectedRectangleXPos = playerNumber % this.nbCharactersPerRow;
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
import {mediaManager} from "../../WebRtc/MediaManager";
|
||||
import {HtmlUtils} from "../../WebRtc/HtmlUtils";
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
|
||||
export const HelpCameraSettingsSceneName = 'HelpCameraSettingsScene';
|
||||
const helpCameraSettings = 'helpCameraSettings';
|
||||
/**
|
||||
* The scene that show how to permit Camera and Microphone access if there are not already allowed
|
||||
*/
|
||||
export class HelpCameraSettingsScene extends Phaser.Scene {
|
||||
private helpCameraSettingsElement!: Phaser.GameObjects.DOMElement;
|
||||
private helpCameraSettingsOpened: boolean = false;
|
||||
|
||||
constructor() {
|
||||
super({key: HelpCameraSettingsSceneName});
|
||||
}
|
||||
|
||||
preload() {
|
||||
this.load.html(helpCameraSettings, 'resources/html/helpCameraSettings.html');
|
||||
}
|
||||
|
||||
create(){
|
||||
localUserStore.setHelpCameraSettingsShown();
|
||||
this.createHelpCameraSettings();
|
||||
}
|
||||
|
||||
private createHelpCameraSettings() : void {
|
||||
const middleX = (window.innerWidth / 3) - (370*0.85);
|
||||
this.helpCameraSettingsElement = this.add.dom(middleX, -800, undefined, {overflow: 'scroll'}).createFromCache(helpCameraSettings);
|
||||
this.revealMenusAfterInit(this.helpCameraSettingsElement, helpCameraSettings);
|
||||
this.helpCameraSettingsElement.addListener('click');
|
||||
this.helpCameraSettingsElement.on('click', (event:MouseEvent) => {
|
||||
event.preventDefault();
|
||||
if((event?.target as HTMLInputElement).id === 'helpCameraSettingsFormRefresh') {
|
||||
window.location.reload();
|
||||
}else if((event?.target as HTMLInputElement).id === 'helpCameraSettingsFormContinue') {
|
||||
this.closeHelpCameraSettingsOpened();
|
||||
}
|
||||
});
|
||||
|
||||
if(!mediaManager.constraintsMedia.audio || !mediaManager.constraintsMedia.video){
|
||||
this.openHelpCameraSettingsOpened();
|
||||
}
|
||||
}
|
||||
|
||||
private openHelpCameraSettingsOpened(): void{
|
||||
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('webRtcSetup').style.display = 'none';
|
||||
this.helpCameraSettingsOpened = true;
|
||||
let middleY = (window.innerHeight / 3) - (495);
|
||||
if(middleY < 0){
|
||||
middleY = 0;
|
||||
}
|
||||
let middleX = (window.innerWidth / 3) - (370*0.85);
|
||||
if(middleX < 0){
|
||||
middleX = 0;
|
||||
}
|
||||
if(window.navigator.userAgent.includes('Firefox')){
|
||||
HtmlUtils.getElementByIdOrFail<HTMLParagraphElement>('browserHelpSetting').innerHTML ='<img src="/resources/objects/help-setting-camera-permission-firefox.png"/>';
|
||||
}else if(window.navigator.userAgent.includes('Chrome')){
|
||||
HtmlUtils.getElementByIdOrFail<HTMLParagraphElement>('browserHelpSetting').innerHTML ='<img src="/resources/objects/help-setting-camera-permission-chrome.png"/>';
|
||||
}
|
||||
this.tweens.add({
|
||||
targets: this.helpCameraSettingsElement,
|
||||
y: middleY,
|
||||
x: middleX,
|
||||
duration: 1000,
|
||||
ease: 'Power3',
|
||||
overflow: 'scroll'
|
||||
});
|
||||
}
|
||||
|
||||
private closeHelpCameraSettingsOpened(): void{
|
||||
const helpCameraSettingsInfo = this.helpCameraSettingsElement.getChildByID('helpCameraSettings') as HTMLParagraphElement;
|
||||
helpCameraSettingsInfo.innerText = '';
|
||||
helpCameraSettingsInfo.style.display = 'none';
|
||||
this.helpCameraSettingsOpened = false;
|
||||
this.tweens.add({
|
||||
targets: this.helpCameraSettingsElement,
|
||||
y: -400,
|
||||
duration: 1000,
|
||||
ease: 'Power3',
|
||||
overflow: 'scroll'
|
||||
});
|
||||
}
|
||||
|
||||
private revealMenusAfterInit(menuElement: Phaser.GameObjects.DOMElement, rootDomId: string) {
|
||||
//Dom elements will appear inside the viewer screen when creating before being moved out of it, which create a flicker effect.
|
||||
//To prevent this, we put a 'hidden' attribute on the root element, we remove it only after the init is done.
|
||||
setTimeout(() => {
|
||||
(menuElement.getChildByID(rootDomId) as HTMLElement).hidden = false;
|
||||
}, 250);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -3,7 +3,11 @@ import {SelectCharacterScene, SelectCharacterSceneName} from "../Login/SelectCha
|
|||
import {gameManager} from "../Game/GameManager";
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
import {mediaManager} from "../../WebRtc/MediaManager";
|
||||
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
|
||||
import {gameReportKey, gameReportRessource, ReportMenu} from "./ReportMenu";
|
||||
import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||
import {GameConnexionTypes} from "../../Url/UrlManager";
|
||||
import {WarningContainer, warningContainerHtml, warningContainerKey} from "../Components/WarningContainer";
|
||||
import {worldFullWarningStream} from "../../Connexion/WorldFullWarningStream";
|
||||
|
||||
export const MenuSceneName = 'MenuScene';
|
||||
const gameMenuKey = 'gameMenu';
|
||||
|
@ -21,12 +25,15 @@ export class MenuScene extends Phaser.Scene {
|
|||
private menuElement!: Phaser.GameObjects.DOMElement;
|
||||
private gameQualityMenuElement!: Phaser.GameObjects.DOMElement;
|
||||
private gameShareElement!: Phaser.GameObjects.DOMElement;
|
||||
private gameReportElement!: ReportMenu;
|
||||
private sideMenuOpened = false;
|
||||
private settingsMenuOpened = false;
|
||||
private gameShareOpened = false;
|
||||
private gameQualityValue: number;
|
||||
private videoQualityValue: number;
|
||||
private menuButton!: Phaser.GameObjects.DOMElement;
|
||||
private warningContainer: WarningContainer | null = null;
|
||||
private warningContainerTimeout: NodeJS.Timeout | null = null;
|
||||
|
||||
constructor() {
|
||||
super({key: MenuSceneName});
|
||||
|
@ -40,20 +47,22 @@ export class MenuScene extends Phaser.Scene {
|
|||
this.load.html(gameMenuIconKey, 'resources/html/gameMenuIcon.html');
|
||||
this.load.html(gameSettingsMenuKey, 'resources/html/gameQualityMenu.html');
|
||||
this.load.html(gameShare, 'resources/html/gameShare.html');
|
||||
this.load.html(gameReportKey, gameReportRessource);
|
||||
this.load.html(warningContainerKey, warningContainerHtml);
|
||||
}
|
||||
|
||||
create() {
|
||||
this.menuElement = this.add.dom(closedSideMenuX, 30).createFromCache(gameMenuKey);
|
||||
this.menuElement.setOrigin(0);
|
||||
this.revealMenusAfterInit(this.menuElement, 'gameMenu');
|
||||
MenuScene.revealMenusAfterInit(this.menuElement, 'gameMenu');
|
||||
|
||||
const middleX = (window.innerWidth / 3) - 298;
|
||||
this.gameQualityMenuElement = this.add.dom(middleX, -400).createFromCache(gameSettingsMenuKey);
|
||||
this.revealMenusAfterInit(this.gameQualityMenuElement, 'gameQuality');
|
||||
MenuScene.revealMenusAfterInit(this.gameQualityMenuElement, 'gameQuality');
|
||||
|
||||
|
||||
this.gameShareElement = this.add.dom(middleX, -400).createFromCache(gameShare);
|
||||
this.revealMenusAfterInit(this.gameShareElement, gameShare);
|
||||
MenuScene.revealMenusAfterInit(this.gameShareElement, gameShare);
|
||||
this.gameShareElement.addListener('click');
|
||||
this.gameShareElement.on('click', (event:MouseEvent) => {
|
||||
event.preventDefault();
|
||||
|
@ -64,6 +73,12 @@ export class MenuScene extends Phaser.Scene {
|
|||
}
|
||||
});
|
||||
|
||||
this.gameReportElement = new ReportMenu(this, connectionManager.getConnexionType === GameConnexionTypes.anonymous);
|
||||
mediaManager.setShowReportModalCallBacks((userId, userName) => {
|
||||
this.closeAll();
|
||||
this.gameReportElement.open(parseInt(userId), userName);
|
||||
});
|
||||
|
||||
this.input.keyboard.on('keyup-TAB', () => {
|
||||
this.sideMenuOpened ? this.closeSideMenu() : this.openSideMenu();
|
||||
});
|
||||
|
@ -75,9 +90,12 @@ export class MenuScene extends Phaser.Scene {
|
|||
|
||||
this.menuElement.addListener('click');
|
||||
this.menuElement.on('click', this.onMenuClick.bind(this));
|
||||
|
||||
worldFullWarningStream.stream.subscribe(() => this.showWorldCapacityWarning());
|
||||
}
|
||||
|
||||
private revealMenusAfterInit(menuElement: Phaser.GameObjects.DOMElement, rootDomId: string) {
|
||||
//todo put this method in a parent menuElement class
|
||||
static revealMenusAfterInit(menuElement: Phaser.GameObjects.DOMElement, rootDomId: string) {
|
||||
//Dom elements will appear inside the viewer screen when creating before being moved out of it, which create a flicker effect.
|
||||
//To prevent this, we put a 'hidden' attribute on the root element, we remove it only after the init is done.
|
||||
setTimeout(() => {
|
||||
|
@ -98,6 +116,11 @@ export class MenuScene extends Phaser.Scene {
|
|||
const adminSection = this.menuElement.getChildByID('adminConsoleSection') as HTMLElement;
|
||||
adminSection.hidden = false;
|
||||
}
|
||||
//TODO bind with future metadata of card
|
||||
//if (connectionManager.getConnexionType === GameConnexionTypes.anonymous){
|
||||
const adminSection = this.menuElement.getChildByID('socialLinks') as HTMLElement;
|
||||
adminSection.hidden = false;
|
||||
//}
|
||||
this.tweens.add({
|
||||
targets: this.menuElement,
|
||||
x: openedSideMenuX,
|
||||
|
@ -105,6 +128,21 @@ export class MenuScene extends Phaser.Scene {
|
|||
ease: 'Power3'
|
||||
});
|
||||
}
|
||||
|
||||
private showWorldCapacityWarning() {
|
||||
if (!this.warningContainer) {
|
||||
this.warningContainer = new WarningContainer(this);
|
||||
}
|
||||
if (this.warningContainerTimeout) {
|
||||
clearTimeout(this.warningContainerTimeout);
|
||||
}
|
||||
this.warningContainerTimeout = setTimeout(() => {
|
||||
this.warningContainer?.destroy();
|
||||
this.warningContainer = null
|
||||
this.warningContainerTimeout = null
|
||||
}, 120000);
|
||||
|
||||
}
|
||||
|
||||
private closeSideMenu(): void {
|
||||
if (!this.sideMenuOpened) return;
|
||||
|
@ -222,6 +260,9 @@ export class MenuScene extends Phaser.Scene {
|
|||
}
|
||||
|
||||
private onMenuClick(event:MouseEvent) {
|
||||
if((event?.target as HTMLInputElement).classList.contains('not-button')){
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
|
||||
switch ((event?.target as HTMLInputElement).id) {
|
||||
|
@ -280,5 +321,6 @@ export class MenuScene extends Phaser.Scene {
|
|||
private closeAll(){
|
||||
this.closeGameQualityMenu();
|
||||
this.closeGameShare();
|
||||
this.gameReportElement.close();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
import {MenuScene} from "./MenuScene";
|
||||
import {gameManager} from "../Game/GameManager";
|
||||
import {blackListManager} from "../../WebRtc/BlackListManager";
|
||||
|
||||
export const gameReportKey = 'gameReport';
|
||||
export const gameReportRessource = 'resources/html/gameReport.html';
|
||||
|
||||
export class ReportMenu extends Phaser.GameObjects.DOMElement {
|
||||
private opened: boolean = false;
|
||||
|
||||
private userId!: number;
|
||||
private userName!: string|undefined;
|
||||
private anonymous: boolean;
|
||||
|
||||
constructor(scene: Phaser.Scene, anonymous: boolean) {
|
||||
super(scene, -2000, -2000);
|
||||
this.anonymous = anonymous;
|
||||
this.createFromCache(gameReportKey);
|
||||
|
||||
if (this.anonymous) {
|
||||
const divToHide = this.getChildByID('reportSection') as HTMLElement;
|
||||
divToHide.hidden = true;
|
||||
const textToHide = this.getChildByID('askActionP') as HTMLElement;
|
||||
textToHide.hidden = true;
|
||||
}
|
||||
|
||||
scene.add.existing(this);
|
||||
MenuScene.revealMenusAfterInit(this, gameReportKey);
|
||||
|
||||
this.addListener('click');
|
||||
this.on('click', (event:MouseEvent) => {
|
||||
event.preventDefault();
|
||||
if ((event?.target as HTMLInputElement).id === 'gameReportFormSubmit') {
|
||||
this.submitReport();
|
||||
} else if((event?.target as HTMLInputElement).id === 'gameReportFormCancel') {
|
||||
this.close();
|
||||
} else if((event?.target as HTMLInputElement).id === 'toggleBlockButton') {
|
||||
this.toggleBlock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public open(userId: number, userName: string|undefined): void {
|
||||
if (this.opened) {
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
this.userId = userId;
|
||||
this.userName = userName;
|
||||
|
||||
const mainEl = this.getChildByID('gameReport') as HTMLElement;
|
||||
this.x = this.getCenteredX(mainEl);
|
||||
this.y = this.getHiddenY(mainEl);
|
||||
|
||||
const gameTitleReport = this.getChildByID('nameReported') as HTMLElement;
|
||||
gameTitleReport.innerText = userName || '';
|
||||
|
||||
const blockButton = this.getChildByID('toggleBlockButton') as HTMLElement;
|
||||
blockButton.innerText = blackListManager.isBlackListed(this.userId) ? 'Unblock this user' : 'Block this user';
|
||||
|
||||
this.opened = true;
|
||||
|
||||
gameManager.getCurrentGameScene(this.scene).userInputManager.clearAllKeys();
|
||||
|
||||
this.scene.tweens.add({
|
||||
targets: this,
|
||||
y: this.getCenteredY(mainEl),
|
||||
duration: 1000,
|
||||
ease: 'Power3'
|
||||
});
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this.opened = false;
|
||||
gameManager.getCurrentGameScene(this.scene).userInputManager.initKeyBoardEvent();
|
||||
const mainEl = this.getChildByID('gameReport') as HTMLElement;
|
||||
this.scene.tweens.add({
|
||||
targets: this,
|
||||
y: this.getHiddenY(mainEl),
|
||||
duration: 1000,
|
||||
ease: 'Power3'
|
||||
});
|
||||
}
|
||||
|
||||
//todo: into a parent class?
|
||||
private getCenteredX(mainEl: HTMLElement): number {
|
||||
return window.innerWidth / 4 - mainEl.clientWidth / 2;
|
||||
}
|
||||
private getHiddenY(mainEl: HTMLElement): number {
|
||||
return - mainEl.clientHeight - 50;
|
||||
}
|
||||
private getCenteredY(mainEl: HTMLElement): number {
|
||||
return window.innerHeight / 4 - mainEl.clientHeight / 2;
|
||||
}
|
||||
|
||||
private toggleBlock(): void {
|
||||
!blackListManager.isBlackListed(this.userId) ? blackListManager.blackList(this.userId) : blackListManager.cancelBlackList(this.userId);
|
||||
this.close();
|
||||
}
|
||||
|
||||
private submitReport(): void{
|
||||
const gamePError = this.getChildByID('gameReportErr') as HTMLParagraphElement;
|
||||
gamePError.innerText = '';
|
||||
gamePError.style.display = 'none';
|
||||
const gameTextArea = this.getChildByID('gameReportInput') as HTMLInputElement;
|
||||
if(!gameTextArea || !gameTextArea.value){
|
||||
gamePError.innerText = 'Report message cannot to be empty.';
|
||||
gamePError.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
gameManager.getCurrentGameScene(this.scene).connection.emitReportPlayerMessage(
|
||||
this.userId,
|
||||
gameTextArea.value
|
||||
);
|
||||
this.close();
|
||||
}
|
||||
}
|
|
@ -1,9 +1,13 @@
|
|||
|
||||
export enum PlayerAnimationNames {
|
||||
WalkDown = 'down',
|
||||
WalkLeft = 'left',
|
||||
WalkUp = 'up',
|
||||
WalkRight = 'right',
|
||||
export enum PlayerAnimationDirections {
|
||||
Down = 'down',
|
||||
Left = 'left',
|
||||
Up = 'up',
|
||||
Right = 'right',
|
||||
}
|
||||
export enum PlayerAnimationTypes {
|
||||
Walk = 'walk',
|
||||
Idle = 'idle',
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {PlayerAnimationNames} from "./Animation";
|
||||
import {PlayerAnimationDirections} from "./Animation";
|
||||
import {GameScene} from "../Game/GameScene";
|
||||
import {UserInputEvent, UserInputManager} from "../UserInput/UserInputManager";
|
||||
import {Character} from "../Entity/Character";
|
||||
|
@ -11,7 +11,7 @@ export interface CurrentGamerInterface extends Character{
|
|||
}
|
||||
|
||||
export class Player extends Character implements CurrentGamerInterface {
|
||||
private previousDirection: string = PlayerAnimationNames.WalkDown;
|
||||
private previousDirection: string = PlayerAnimationDirections.Down;
|
||||
private wasMoving: boolean = false;
|
||||
|
||||
constructor(
|
||||
|
@ -20,7 +20,7 @@ export class Player extends Character implements CurrentGamerInterface {
|
|||
y: number,
|
||||
name: string,
|
||||
texturesPromise: Promise<string[]>,
|
||||
direction: string,
|
||||
direction: PlayerAnimationDirections,
|
||||
moving: boolean,
|
||||
private userInputManager: UserInputManager
|
||||
) {
|
||||
|
@ -43,20 +43,20 @@ export class Player extends Character implements CurrentGamerInterface {
|
|||
let y = 0;
|
||||
if (activeEvents.get(UserInputEvent.MoveUp)) {
|
||||
y = - moveAmount;
|
||||
direction = PlayerAnimationNames.WalkUp;
|
||||
direction = PlayerAnimationDirections.Up;
|
||||
moving = true;
|
||||
} else if (activeEvents.get(UserInputEvent.MoveDown)) {
|
||||
y = moveAmount;
|
||||
direction = PlayerAnimationNames.WalkDown;
|
||||
direction = PlayerAnimationDirections.Down;
|
||||
moving = true;
|
||||
}
|
||||
if (activeEvents.get(UserInputEvent.MoveLeft)) {
|
||||
x = -moveAmount;
|
||||
direction = PlayerAnimationNames.WalkLeft;
|
||||
direction = PlayerAnimationDirections.Left;
|
||||
moving = true;
|
||||
} else if (activeEvents.get(UserInputEvent.MoveRight)) {
|
||||
x = moveAmount;
|
||||
direction = PlayerAnimationNames.WalkRight;
|
||||
direction = PlayerAnimationDirections.Right;
|
||||
moving = true;
|
||||
}
|
||||
if (x !== 0 || y !== 0) {
|
||||
|
|
|
@ -59,7 +59,11 @@ export class UserInputManager {
|
|||
];
|
||||
}
|
||||
|
||||
clearAllInputKeyboard(){
|
||||
clearAllListeners(){
|
||||
this.Scene.input.keyboard.removeAllListeners();
|
||||
}
|
||||
|
||||
clearAllKeys(){
|
||||
this.Scene.input.keyboard.removeAllKeys();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {HtmlUtils} from "./HtmlUtils";
|
||||
import {isUndefined} from "generic-type-guard";
|
||||
import {localUserStore} from "../Connexion/LocalUserStore";
|
||||
|
||||
enum audioStates {
|
||||
closed = 0,
|
||||
|
@ -9,6 +10,8 @@ enum audioStates {
|
|||
|
||||
const audioPlayerDivId = "audioplayer";
|
||||
const audioPlayerCtrlId = "audioplayerctrl";
|
||||
const audioPlayerVolId = "audioplayer_volume";
|
||||
const audioPlayerMuteId = "audioplayer_volume_icon_playing";
|
||||
const animationTime = 500;
|
||||
|
||||
class AudioManager {
|
||||
|
@ -17,6 +20,8 @@ class AudioManager {
|
|||
private audioPlayerDiv: HTMLDivElement;
|
||||
private audioPlayerCtrl: HTMLDivElement;
|
||||
private audioPlayerElem: HTMLAudioElement | undefined;
|
||||
private audioPlayerVol: HTMLInputElement;
|
||||
private audioPlayerMute: HTMLInputElement;
|
||||
|
||||
private volume = 1;
|
||||
private muted = false;
|
||||
|
@ -26,16 +31,35 @@ class AudioManager {
|
|||
constructor() {
|
||||
this.audioPlayerDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(audioPlayerDivId);
|
||||
this.audioPlayerCtrl = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(audioPlayerCtrlId);
|
||||
this.audioPlayerVol = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(audioPlayerVolId);
|
||||
this.audioPlayerMute = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(audioPlayerMuteId);
|
||||
|
||||
const storedVolume = localStorage.getItem('volume')
|
||||
if (storedVolume === null) {
|
||||
this.setVolume(1);
|
||||
this.volume = localUserStore.getAudioPlayerVolume();
|
||||
this.audioPlayerVol.value = '' + this.volume;
|
||||
|
||||
this.muted = localUserStore.getAudioPlayerMuted();
|
||||
if (this.muted) {
|
||||
this.audioPlayerMute.classList.add('muted');
|
||||
}
|
||||
}
|
||||
|
||||
public playAudio(url: string|number|boolean, mapDirUrl: string, volume: number|undefined, loop=false): void {
|
||||
const audioPath = url as string;
|
||||
let realAudioPath = '';
|
||||
|
||||
if (audioPath.indexOf('://') > 0) {
|
||||
// remote file or stream
|
||||
realAudioPath = audioPath;
|
||||
} else {
|
||||
this.volume = parseFloat(storedVolume);
|
||||
HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume').value = storedVolume;
|
||||
// local file, include it relative to map directory
|
||||
realAudioPath = mapDirUrl + '/' + url;
|
||||
}
|
||||
|
||||
HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume').value = '' + this.volume;
|
||||
this.loadAudio(realAudioPath, volume);
|
||||
|
||||
if (loop) {
|
||||
this.loop();
|
||||
}
|
||||
}
|
||||
|
||||
private close(): void {
|
||||
|
@ -56,26 +80,29 @@ class AudioManager {
|
|||
}
|
||||
|
||||
private changeVolume(talking = false): void {
|
||||
if (!isUndefined(this.audioPlayerElem)) {
|
||||
this.audioPlayerElem.volume = this.naturalVolume(talking && this.decreaseWhileTalking);
|
||||
this.audioPlayerElem.muted = this.muted;
|
||||
if (isUndefined(this.audioPlayerElem)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private naturalVolume(makeSofter: boolean = false): number {
|
||||
const volume = this.volume
|
||||
const retVol = makeSofter && !this.volumeReduced ? Math.pow(volume * 0.5, 3) : volume
|
||||
this.volumeReduced = makeSofter
|
||||
return retVol;
|
||||
const reduceVolume = talking && this.decreaseWhileTalking;
|
||||
if (reduceVolume && !this.volumeReduced) {
|
||||
this.volume *= 0.5;
|
||||
} else if (!reduceVolume && this.volumeReduced) {
|
||||
this.volume *= 2.0;
|
||||
}
|
||||
this.volumeReduced = reduceVolume;
|
||||
|
||||
this.audioPlayerElem.volume = this.volume;
|
||||
this.audioPlayerVol.value = '' + this.volume;
|
||||
this.audioPlayerElem.muted = this.muted;
|
||||
}
|
||||
|
||||
private setVolume(volume: number): void {
|
||||
this.volume = volume;
|
||||
localStorage.setItem('volume', '' + volume);
|
||||
localUserStore.setAudioPlayerVolume(volume);
|
||||
}
|
||||
|
||||
|
||||
public loadAudio(url: string): void {
|
||||
private loadAudio(url: string, volume: number|undefined): void {
|
||||
this.load();
|
||||
|
||||
/* Solution 1, remove whole audio player */
|
||||
|
@ -93,23 +120,24 @@ class AudioManager {
|
|||
this.audioPlayerElem.append(srcElem);
|
||||
|
||||
this.audioPlayerDiv.append(this.audioPlayerElem);
|
||||
this.volume = volume ? Math.min(volume, this.volume) : this.volume;
|
||||
this.changeVolume();
|
||||
this.audioPlayerElem.play();
|
||||
|
||||
const muteElem = HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_mute');
|
||||
muteElem.onclick = (ev: Event)=> {
|
||||
muteElem.onclick = (ev: Event) => {
|
||||
this.muted = !this.muted;
|
||||
this.changeVolume();
|
||||
localUserStore.setAudioPlayerMuted(this.muted);
|
||||
|
||||
if (this.muted) {
|
||||
HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume_icon_playing').classList.add('muted');
|
||||
this.audioPlayerMute.classList.add('muted');
|
||||
} else {
|
||||
HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume_icon_playing').classList.remove('muted');
|
||||
this.audioPlayerMute.classList.remove('muted');
|
||||
}
|
||||
}
|
||||
|
||||
const volumeElem = HtmlUtils.getElementByIdOrFail<HTMLInputElement>('audioplayer_volume');
|
||||
volumeElem.oninput = (ev: Event)=> {
|
||||
this.audioPlayerVol.oninput = (ev: Event)=> {
|
||||
this.setVolume(parseFloat((<HTMLInputElement>ev.currentTarget).value));
|
||||
this.changeVolume();
|
||||
|
||||
|
@ -125,7 +153,7 @@ class AudioManager {
|
|||
this.open();
|
||||
}
|
||||
|
||||
public loop(): void {
|
||||
private loop(): void {
|
||||
if (this.audioPlayerElem !== undefined) {
|
||||
this.audioPlayerElem.loop = true;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import {Subject} from 'rxjs';
|
||||
|
||||
class BlackListManager {
|
||||
private list: number[] = [];
|
||||
public onBlockStream: Subject<number> = new Subject();
|
||||
public onUnBlockStream: Subject<number> = new Subject();
|
||||
|
||||
isBlackListed(userId: number): boolean {
|
||||
return this.list.find((data) => data === userId) !== undefined;
|
||||
}
|
||||
|
||||
blackList(userId: number): void {
|
||||
if (this.isBlackListed(userId)) return;
|
||||
this.list.push(userId);
|
||||
this.onBlockStream.next(userId);
|
||||
}
|
||||
|
||||
cancelBlackList(userId: number): void {
|
||||
this.list.splice(this.list.findIndex(data => data === userId), 1);
|
||||
this.onUnBlockStream.next(userId);
|
||||
}
|
||||
}
|
||||
|
||||
export const blackListManager = new BlackListManager();
|
|
@ -1,6 +1,5 @@
|
|||
import {HtmlUtils} from "./HtmlUtils";
|
||||
|
||||
export type CoWebsiteStateChangedCallback = () => void;
|
||||
import {Subject} from "rxjs";
|
||||
|
||||
enum iframeStates {
|
||||
closed = 1,
|
||||
|
@ -8,29 +7,102 @@ enum iframeStates {
|
|||
opened,
|
||||
}
|
||||
|
||||
const cowebsiteDivId = "cowebsite"; // the id of the parent div of the iframe.
|
||||
const cowebsiteDivId = 'cowebsite'; // the id of the whole container.
|
||||
const cowebsiteMainDomId = 'cowebsite-main'; // the id of the parent div of the iframe.
|
||||
const cowebsiteAsideDomId = 'cowebsite-aside'; // the id of the parent div of the iframe.
|
||||
const cowebsiteCloseButtonId = 'cowebsite-close';
|
||||
const cowebsiteFullScreenButtonId = 'cowebsite-fullscreen';
|
||||
const cowebsiteOpenFullScreenImageId = 'cowebsite-fullscreen-open';
|
||||
const cowebsiteCloseFullScreenImageId = 'cowebsite-fullscreen-close';
|
||||
const animationTime = 500; //time used by the css transitions, in ms.
|
||||
|
||||
class CoWebsiteManager {
|
||||
|
||||
private opened: iframeStates = iframeStates.closed;
|
||||
|
||||
private observers = new Array<CoWebsiteStateChangedCallback>();
|
||||
private _onResize: Subject<void> = new Subject();
|
||||
public onResize = this._onResize.asObservable();
|
||||
/**
|
||||
* Quickly going in and out of an iframe trigger can create conflicts between the iframe states.
|
||||
* So we use this promise to queue up every cowebsite state transition
|
||||
*/
|
||||
private currentOperationPromise: Promise<void> = Promise.resolve();
|
||||
private cowebsiteDiv: HTMLDivElement;
|
||||
private resizing: boolean = false;
|
||||
private cowebsiteMainDom: HTMLDivElement;
|
||||
private cowebsiteAsideDom: HTMLDivElement;
|
||||
|
||||
get width(): number {
|
||||
return this.cowebsiteDiv.clientWidth;
|
||||
}
|
||||
|
||||
set width(width: number) {
|
||||
this.cowebsiteDiv.style.width = width+'px';
|
||||
}
|
||||
|
||||
get height(): number {
|
||||
return this.cowebsiteDiv.clientHeight;
|
||||
}
|
||||
|
||||
set height(height: number) {
|
||||
this.cowebsiteDiv.style.height = height+'px';
|
||||
}
|
||||
|
||||
get verticalMode(): boolean {
|
||||
return window.innerWidth < window.innerHeight;
|
||||
}
|
||||
|
||||
get isFullScreen(): boolean {
|
||||
return this.verticalMode ? this.height === window.innerHeight : this.width === window.innerWidth;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.cowebsiteDiv = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteDivId);
|
||||
this.cowebsiteMainDom = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteMainDomId);
|
||||
this.cowebsiteAsideDom = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteAsideDomId);
|
||||
|
||||
this.initResizeListeners();
|
||||
|
||||
HtmlUtils.getElementByIdOrFail(cowebsiteCloseButtonId).addEventListener('click', () => {
|
||||
this.closeCoWebsite();
|
||||
});
|
||||
HtmlUtils.getElementByIdOrFail(cowebsiteFullScreenButtonId).addEventListener('click', () => {
|
||||
this.fullscreen();
|
||||
});
|
||||
}
|
||||
|
||||
private initResizeListeners() {
|
||||
const movecallback = (event:MouseEvent) => {
|
||||
this.verticalMode ? this.height -= event.movementY / this.getDevicePixelRatio() : this.width -= event.movementX / this.getDevicePixelRatio();
|
||||
this.fire();
|
||||
}
|
||||
|
||||
this.cowebsiteAsideDom.addEventListener('mousedown', (event) => {
|
||||
this.resizing = true;
|
||||
this.getIframeDom().style.display = 'none';
|
||||
|
||||
document.addEventListener('mousemove', movecallback);
|
||||
});
|
||||
|
||||
document.addEventListener('mouseup', (event) => {
|
||||
if (!this.resizing) return;
|
||||
document.removeEventListener('mousemove', movecallback);
|
||||
this.getIframeDom().style.display = 'block';
|
||||
this.resizing = false;
|
||||
});
|
||||
}
|
||||
|
||||
private getDevicePixelRatio(): number {
|
||||
//on chrome engines, movementX and movementY return global screens coordinates while other browser return pixels
|
||||
//so on chrome-based browser we need to adjust using 'devicePixelRatio'
|
||||
return window.navigator.userAgent.includes('Firefox') ? 1 : window.devicePixelRatio;
|
||||
}
|
||||
|
||||
private close(): void {
|
||||
this.cowebsiteDiv.classList.remove('loaded'); //edit the css class to trigger the transition
|
||||
this.cowebsiteDiv.classList.add('hidden');
|
||||
this.opened = iframeStates.closed;
|
||||
this.resetStyle();
|
||||
}
|
||||
private load(): void {
|
||||
this.cowebsiteDiv.classList.remove('hidden'); //edit the css class to trigger the transition
|
||||
|
@ -40,26 +112,34 @@ class CoWebsiteManager {
|
|||
private open(): void {
|
||||
this.cowebsiteDiv.classList.remove('loading', 'hidden'); //edit the css class to trigger the transition
|
||||
this.opened = iframeStates.opened;
|
||||
this.resetStyle();
|
||||
}
|
||||
|
||||
public loadCoWebsite(url: string): void {
|
||||
public resetStyle() {
|
||||
this.cowebsiteDiv.style.width = '';
|
||||
this.cowebsiteDiv.style.height = '';
|
||||
}
|
||||
|
||||
private getIframeDom(): HTMLIFrameElement {
|
||||
const iframe = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(cowebsiteDivId).querySelector('iframe');
|
||||
if (!iframe) throw new Error('Could not find iframe!');
|
||||
return iframe;
|
||||
}
|
||||
|
||||
public loadCoWebsite(url: string, base: string, allowPolicy?: string): void {
|
||||
this.load();
|
||||
this.cowebsiteDiv.innerHTML = `<button class="close-btn" id="cowebsite-close">
|
||||
<img src="resources/logos/close.svg">
|
||||
</button>`;
|
||||
setTimeout(() => {
|
||||
HtmlUtils.getElementByIdOrFail('cowebsite-close').addEventListener('click', () => {
|
||||
this.closeCoWebsite();
|
||||
});
|
||||
}, 100);
|
||||
this.cowebsiteMainDom.innerHTML = ``;
|
||||
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.id = 'cowebsite-iframe';
|
||||
iframe.src = url;
|
||||
iframe.src = (new URL(url, base)).toString();
|
||||
if (allowPolicy) {
|
||||
iframe.allow = allowPolicy;
|
||||
}
|
||||
const onloadPromise = new Promise((resolve) => {
|
||||
iframe.onload = () => resolve();
|
||||
});
|
||||
this.cowebsiteDiv.appendChild(iframe);
|
||||
this.cowebsiteMainDom.appendChild(iframe);
|
||||
const onTimeoutPromise = new Promise((resolve) => {
|
||||
setTimeout(() => resolve(), 2000);
|
||||
});
|
||||
|
@ -76,7 +156,8 @@ class CoWebsiteManager {
|
|||
*/
|
||||
public insertCoWebsite(callback: (cowebsite: HTMLDivElement) => Promise<void>): void {
|
||||
this.load();
|
||||
this.currentOperationPromise = this.currentOperationPromise.then(() => callback(this.cowebsiteDiv)).then(() => {
|
||||
this.cowebsiteMainDom.innerHTML = ``;
|
||||
this.currentOperationPromise = this.currentOperationPromise.then(() => callback(this.cowebsiteMainDom)).then(() => {
|
||||
this.open();
|
||||
setTimeout(() => {
|
||||
this.fire();
|
||||
|
@ -90,9 +171,7 @@ class CoWebsiteManager {
|
|||
this.close();
|
||||
this.fire();
|
||||
setTimeout(() => {
|
||||
this.cowebsiteDiv.innerHTML = `<button class="close-btn" id="cowebsite-close">
|
||||
<img src="resources/logos/close.svg">
|
||||
</button>`;
|
||||
this.cowebsiteMainDom.innerHTML = ``;
|
||||
resolve();
|
||||
}, animationTime)
|
||||
}));
|
||||
|
@ -106,29 +185,37 @@ class CoWebsiteManager {
|
|||
height: window.innerHeight
|
||||
}
|
||||
}
|
||||
if (window.innerWidth >= window.innerHeight) {
|
||||
if (!this.verticalMode) {
|
||||
return {
|
||||
width: window.innerWidth / 2,
|
||||
width: window.innerWidth - this.width,
|
||||
height: window.innerHeight
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight / 2
|
||||
height: window.innerHeight - this.height,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//todo: is it still useful to allow any kind of observers?
|
||||
public onStateChange(observer: CoWebsiteStateChangedCallback) {
|
||||
this.observers.push(observer);
|
||||
}
|
||||
|
||||
|
||||
private fire(): void {
|
||||
for (const callback of this.observers) {
|
||||
callback();
|
||||
this._onResize.next();
|
||||
}
|
||||
|
||||
private fullscreen(): void {
|
||||
if (this.isFullScreen) {
|
||||
this.resetStyle();
|
||||
this.fire();
|
||||
//we don't trigger a resize of the phaser game since it won't be visible anyway.
|
||||
HtmlUtils.getElementByIdOrFail(cowebsiteOpenFullScreenImageId).style.display = 'inline';
|
||||
HtmlUtils.getElementByIdOrFail(cowebsiteCloseFullScreenImageId).style.display = 'none';
|
||||
} else {
|
||||
this.verticalMode ? this.height = window.innerHeight : this.width = window.innerWidth;
|
||||
//we don't trigger a resize of the phaser game since it won't be visible anyway.
|
||||
HtmlUtils.getElementByIdOrFail(cowebsiteOpenFullScreenImageId).style.display = 'none';
|
||||
HtmlUtils.getElementByIdOrFail(cowebsiteCloseFullScreenImageId).style.display = 'inline';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const coWebsiteManager = new CoWebsiteManager();
|
||||
export const coWebsiteManager = new CoWebsiteManager();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {HtmlUtils} from "./HtmlUtils";
|
||||
import {mediaManager, ReportCallback} from "./MediaManager";
|
||||
import {mediaManager, ReportCallback, ShowReportCallBack} from "./MediaManager";
|
||||
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||
import {connectionManager} from "../Connexion/ConnectionManager";
|
||||
import {GameConnexionTypes} from "../Url/UrlManager";
|
||||
|
@ -61,7 +61,7 @@ export class DiscussionManager {
|
|||
const inputMessage: HTMLInputElement = document.createElement('input');
|
||||
inputMessage.onfocus = () => {
|
||||
if(this.userInputManager) {
|
||||
this.userInputManager.clearAllInputKeyboard();
|
||||
this.userInputManager.clearAllKeys();
|
||||
}
|
||||
}
|
||||
inputMessage.onblur = () => {
|
||||
|
@ -99,7 +99,7 @@ export class DiscussionManager {
|
|||
name: string|undefined,
|
||||
img?: string|undefined,
|
||||
isMe: boolean = false,
|
||||
reportCallback?: ReportCallback
|
||||
showReportCallBack?: ShowReportCallBack
|
||||
) {
|
||||
const divParticipant: HTMLDivElement = document.createElement('div');
|
||||
divParticipant.classList.add('participant');
|
||||
|
@ -128,8 +128,8 @@ export class DiscussionManager {
|
|||
reportBanUserAction.classList.add('report-btn')
|
||||
reportBanUserAction.innerText = 'Report';
|
||||
reportBanUserAction.addEventListener('click', () => {
|
||||
if(reportCallback) {
|
||||
mediaManager.showReportModal(`${userId}`, name ?? '', reportCallback);
|
||||
if(showReportCallBack) {
|
||||
showReportCallBack(`${userId}`, name);
|
||||
}else{
|
||||
console.info('report feature is not activated!');
|
||||
}
|
||||
|
|
|
@ -1,27 +1,45 @@
|
|||
export class HtmlUtils {
|
||||
public static getElementByIdOrFail<T extends HTMLElement>(id: string): T {
|
||||
const elem = document.getElementById(id);
|
||||
if (elem === null) {
|
||||
throw new Error("Cannot find HTML element with id '"+id+"'");
|
||||
if (HtmlUtils.isHtmlElement<T>(elem)) {
|
||||
return elem;
|
||||
}
|
||||
// FIXME: does not check the type of the returned type
|
||||
return elem as T;
|
||||
throw new Error("Cannot find HTML element with id '"+id+"'");
|
||||
}
|
||||
|
||||
public static querySelectorOrFail<T extends HTMLElement>(selector: string): T {
|
||||
const elem = document.querySelector<T>(selector);
|
||||
if (HtmlUtils.isHtmlElement<T>(elem)) {
|
||||
return elem;
|
||||
}
|
||||
throw new Error("Cannot find HTML element with selector '"+selector+"'");
|
||||
}
|
||||
|
||||
public static removeElementByIdOrFail<T extends HTMLElement>(id: string): T {
|
||||
const elem = document.getElementById(id);
|
||||
if (elem === null) {
|
||||
throw new Error("Cannot find HTML element with id '"+id+"'");
|
||||
if (HtmlUtils.isHtmlElement<T>(elem)) {
|
||||
elem.remove();
|
||||
return elem;
|
||||
}
|
||||
// FIXME: does not check the type of the returned type
|
||||
elem.remove();
|
||||
return elem as T;
|
||||
throw new Error("Cannot find HTML element with id '"+id+"'");
|
||||
}
|
||||
|
||||
private static escapeHtml(html: string): string {
|
||||
const text = document.createTextNode(html);
|
||||
const p = document.createElement('p');
|
||||
p.appendChild(text);
|
||||
return p.innerHTML;
|
||||
}
|
||||
|
||||
public static urlify(text: string): string {
|
||||
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
||||
text = HtmlUtils.escapeHtml(text);
|
||||
return text.replace(urlRegex, (url: string) => {
|
||||
return '<a href="' + url + '" target="_blank" style=":visited {color: white}">' + url + '</a>';
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
private static isHtmlElement<T extends HTMLElement>(elem: HTMLElement | null): elem is T {
|
||||
return elem !== null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,11 +3,41 @@ import {mediaManager} from "./MediaManager";
|
|||
import {coWebsiteManager} from "./CoWebsiteManager";
|
||||
declare const window:any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
|
||||
const interfaceConfig = {
|
||||
interface jitsiConfigInterface {
|
||||
startWithAudioMuted: boolean
|
||||
startWithVideoMuted: boolean
|
||||
prejoinPageEnabled: boolean
|
||||
}
|
||||
|
||||
const getDefaultConfig = () : jitsiConfigInterface => {
|
||||
return {
|
||||
startWithAudioMuted: !mediaManager.constraintsMedia.audio,
|
||||
startWithVideoMuted: mediaManager.constraintsMedia.video === false,
|
||||
prejoinPageEnabled: false
|
||||
}
|
||||
}
|
||||
|
||||
const mergeConfig = (config?: object) => {
|
||||
const currentDefaultConfig = getDefaultConfig();
|
||||
if(!config){
|
||||
return currentDefaultConfig;
|
||||
}
|
||||
return {
|
||||
...currentDefaultConfig,
|
||||
...config,
|
||||
startWithAudioMuted: (config as jitsiConfigInterface).startWithAudioMuted ? true : currentDefaultConfig.startWithAudioMuted,
|
||||
startWithVideoMuted: (config as jitsiConfigInterface).startWithVideoMuted ? true : currentDefaultConfig.startWithVideoMuted,
|
||||
prejoinPageEnabled: (config as jitsiConfigInterface).prejoinPageEnabled ? true : currentDefaultConfig.prejoinPageEnabled
|
||||
}
|
||||
}
|
||||
|
||||
const defaultInterfaceConfig = {
|
||||
SHOW_CHROME_EXTENSION_BANNER: false,
|
||||
MOBILE_APP_PROMO: false,
|
||||
|
||||
HIDE_INVITE_MORE_HEADER: true,
|
||||
DISABLE_JOIN_LEAVE_NOTIFICATIONS: true,
|
||||
DISABLE_VIDEO_BACKGROUND: true,
|
||||
|
||||
// Note: hiding brand does not seem to work, we probably need to put this on the server side.
|
||||
SHOW_BRAND_WATERMARK: false,
|
||||
|
@ -25,31 +55,64 @@ const interfaceConfig = {
|
|||
],
|
||||
};
|
||||
|
||||
const slugify = (...args: (string | number)[]): string => {
|
||||
const value = args.join(' ')
|
||||
|
||||
return value
|
||||
.normalize('NFD') // split an accented letter in the base letter and the accent
|
||||
.replace(/[\u0300-\u036f]/g, '') // remove all previously split accents
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/[^a-z0-9 ]/g, '') // remove all chars not letters, numbers and spaces (to be replaced)
|
||||
.replace(/\s+/g, '-') // separator
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
public start(roomName: string, playerName:string, jwt?: string): void {
|
||||
coWebsiteManager.insertCoWebsite((cowebsiteDiv => {
|
||||
const domain = JITSI_URL;
|
||||
private previousConfigMeet? : jitsiConfigInterface;
|
||||
private jitsiScriptLoaded: boolean = false;
|
||||
|
||||
/**
|
||||
* Slugifies the room name and prepends the room name with the instance
|
||||
*/
|
||||
public getRoomName(roomName: string, instance: string): string {
|
||||
return slugify(instance.replace('/', '-') + "-" + roomName);
|
||||
}
|
||||
|
||||
public start(roomName: string, playerName:string, jwt?: string, config?: object, interfaceConfig?: object, jitsiUrl?: string): void {
|
||||
//save previous config
|
||||
this.previousConfigMeet = getDefaultConfig();
|
||||
|
||||
coWebsiteManager.insertCoWebsite((async cowebsiteDiv => {
|
||||
// Jitsi meet external API maintains some data in local storage
|
||||
// which is sent via the appData URL parameter when joining a
|
||||
// conference. Problem is that this data grows indefinitely. Thus
|
||||
// after some time the URLs get so huge that loading the iframe
|
||||
// becomes slow and eventually breaks completely. Thus lets just
|
||||
// clear jitsi local storage before starting a new conference.
|
||||
window.localStorage.removeItem("jitsiLocalStorage");
|
||||
|
||||
const domain = jitsiUrl || JITSI_URL;
|
||||
if (domain === undefined) {
|
||||
throw new Error('Missing JITSI_URL environment variable or jitsiUrl parameter in the map.')
|
||||
}
|
||||
await this.loadJitsiScript(domain);
|
||||
|
||||
const options: any = { // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
roomName: roomName,
|
||||
jwt: jwt,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
parentNode: cowebsiteDiv,
|
||||
configOverwrite: {
|
||||
startWithAudioMuted: !mediaManager.constraintsMedia.audio,
|
||||
startWithVideoMuted: mediaManager.constraintsMedia.video === false,
|
||||
prejoinPageEnabled: false
|
||||
},
|
||||
interfaceConfigOverwrite: interfaceConfig,
|
||||
configOverwrite: mergeConfig(config),
|
||||
interfaceConfigOverwrite: {...defaultInterfaceConfig, ...interfaceConfig}
|
||||
};
|
||||
if (!options.jwt) {
|
||||
delete options.jwt;
|
||||
}
|
||||
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
options.onload = () => resolve(); //we want for the iframe to be loaded before triggering animations.
|
||||
setTimeout(() => resolve(), 2000); //failsafe in case the iframe is deleted before loading or too long to load
|
||||
|
@ -70,6 +133,19 @@ class JitsiFactory {
|
|||
this.jitsiApi.removeListener('audioMuteStatusChanged', this.audioCallback);
|
||||
this.jitsiApi.removeListener('videoMuteStatusChanged', this.videoCallback);
|
||||
this.jitsiApi?.dispose();
|
||||
|
||||
//restore previous config
|
||||
if(this.previousConfigMeet?.startWithAudioMuted){
|
||||
mediaManager.disableMicrophone();
|
||||
}else{
|
||||
mediaManager.enableMicrophone();
|
||||
}
|
||||
|
||||
if(this.previousConfigMeet?.startWithVideoMuted){
|
||||
mediaManager.disableCamera();
|
||||
}else{
|
||||
mediaManager.enableCamera();
|
||||
}
|
||||
}
|
||||
|
||||
private onAudioChange({muted}: {muted: boolean}): void {
|
||||
|
@ -88,6 +164,31 @@ class JitsiFactory {
|
|||
}
|
||||
}
|
||||
|
||||
private async loadJitsiScript(domain: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
if (this.jitsiScriptLoaded) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
this.jitsiScriptLoaded = true;
|
||||
|
||||
// Load Jitsi if the environment variable is set.
|
||||
const jitsiScript = document.createElement('script');
|
||||
jitsiScript.src = 'https://' + domain + '/external_api.js';
|
||||
jitsiScript.onload = () => {
|
||||
resolve();
|
||||
}
|
||||
jitsiScript.onerror = () => {
|
||||
reject();
|
||||
}
|
||||
|
||||
document.head.appendChild(jitsiScript);
|
||||
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export const jitsiFactory = new JitsiFactory();
|
||||
export const jitsiFactory = new JitsiFactory();
|
||||
|
|
|
@ -24,9 +24,16 @@ export interface CenterListener {
|
|||
}
|
||||
|
||||
export const ON_ACTION_TRIGGER_BUTTON = 'onaction';
|
||||
|
||||
export const TRIGGER_WEBSITE_PROPERTIES = 'openWebsiteTrigger';
|
||||
export const TRIGGER_JITSI_PROPERTIES = 'jitsiTrigger';
|
||||
|
||||
export const WEBSITE_MESSAGE_PROPERTIES = 'openWebsiteTriggerMessage';
|
||||
export const JITSI_MESSAGE_PROPERTIES = 'jitsiTriggerMessage';
|
||||
|
||||
export const AUDIO_VOLUME_PROPERTY = 'audioVolume';
|
||||
export const AUDIO_LOOP_PROPERTY = 'audioLoop';
|
||||
|
||||
/**
|
||||
* This class is in charge of the video-conference layout.
|
||||
* It receives positioning requests for videos and does its best to place them on the screen depending on the active layout mode.
|
||||
|
@ -188,7 +195,7 @@ class LayoutManager {
|
|||
} else {
|
||||
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('sidebar').style.display = 'none';
|
||||
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-section').style.display = 'none';
|
||||
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('chat-mode').style.display = 'flex';
|
||||
HtmlUtils.getElementByIdOrFail<HTMLDivElement>('chat-mode').style.display = 'grid';
|
||||
}
|
||||
|
||||
for (const div of this.importantDivs.values()) {
|
||||
|
@ -212,7 +219,7 @@ class LayoutManager {
|
|||
* Tries to find the biggest available box of remaining space (this is a space where we can center the character)
|
||||
*/
|
||||
public findBiggestAvailableArray(): {xStart: number, yStart: number, xEnd: number, yEnd: number} {
|
||||
const game = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('game');
|
||||
const game = HtmlUtils.querySelectorOrFail<HTMLCanvasElement>('#game canvas');
|
||||
if (this.mode === LayoutMode.VideoChat) {
|
||||
const children = document.querySelectorAll<HTMLDivElement>('div.chat-mode > div');
|
||||
const htmlChildren = Array.from(children.values());
|
||||
|
|
|
@ -3,6 +3,7 @@ import {HtmlUtils} from "./HtmlUtils";
|
|||
import {discussionManager, SendMessageCallback} from "./DiscussionManager";
|
||||
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||
import {VIDEO_QUALITY_SELECT} from "../Administration/ConsoleGlobalMessageManager";
|
||||
import {UserSimplePeerInterface} from "./SimplePeer";
|
||||
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
|
||||
const localValueVideo = localStorage.getItem(VIDEO_QUALITY_SELECT);
|
||||
|
@ -23,9 +24,9 @@ export type UpdatedLocalStreamCallback = (media: MediaStream|null) => void;
|
|||
export type StartScreenSharingCallback = (media: MediaStream) => void;
|
||||
export type StopScreenSharingCallback = (media: MediaStream) => void;
|
||||
export type ReportCallback = (message: string) => void;
|
||||
export type ShowReportCallBack = (userId: string, userName: string|undefined) => void;
|
||||
|
||||
// TODO: Split MediaManager in 2 classes: MediaManagerUI (in charge of HTML) and MediaManager (singleton in charge of the camera only)
|
||||
// TODO: verify that microphone event listeners are not triggered plenty of time NOW (since MediaManager is created many times!!!!)
|
||||
export class MediaManager {
|
||||
localStream: MediaStream|null = null;
|
||||
localScreenCapture: MediaStream|null = null;
|
||||
|
@ -46,6 +47,7 @@ export class MediaManager {
|
|||
updatedLocalStreamCallBacks : Set<UpdatedLocalStreamCallback> = new Set<UpdatedLocalStreamCallback>();
|
||||
startScreenSharingCallBacks : Set<StartScreenSharingCallback> = new Set<StartScreenSharingCallback>();
|
||||
stopScreenSharingCallBacks : Set<StopScreenSharingCallback> = new Set<StopScreenSharingCallback>();
|
||||
showReportModalCallBacks : Set<ShowReportCallBack> = new Set<ShowReportCallBack>();
|
||||
private microphoneBtn: HTMLDivElement;
|
||||
private cinemaBtn: HTMLDivElement;
|
||||
private monitorBtn: HTMLDivElement;
|
||||
|
@ -469,8 +471,9 @@ export class MediaManager {
|
|||
return this.getCamera();
|
||||
}
|
||||
|
||||
addActiveVideo(userId: string, reportCallBack: ReportCallback|undefined, userName: string = ""){
|
||||
addActiveVideo(user: UserSimplePeerInterface, userName: string = ""){
|
||||
this.webrtcInAudio.play();
|
||||
const userId = ''+user.userId
|
||||
|
||||
userName = userName.toUpperCase();
|
||||
const color = this.getColorByString(userName);
|
||||
|
@ -480,33 +483,39 @@ export class MediaManager {
|
|||
<div class="connecting-spinner"></div>
|
||||
<div class="rtc-error" style="display: none"></div>
|
||||
<i id="name-${userId}" style="background-color: ${color};">${userName}</i>
|
||||
<img id="microphone-${userId}" src="resources/logos/microphone-close.svg">
|
||||
` +
|
||||
((reportCallBack!==undefined)?`<img id="report-${userId}" class="report active" src="resources/logos/report.svg">`:'')
|
||||
+
|
||||
`<video id="${userId}" autoplay></video>
|
||||
<img id="microphone-${userId}" title="mute" src="resources/logos/microphone-close.svg">
|
||||
<button id="report-${userId}" class="report">
|
||||
<img title="report this user" src="resources/logos/report.svg">
|
||||
<span>Report/Block</span>
|
||||
</button>
|
||||
<video id="${userId}" autoplay></video>
|
||||
<img src="resources/logos/blockSign.svg" id="blocking-${userId}" class="block-logo">
|
||||
</div>
|
||||
`;
|
||||
|
||||
layoutManager.add(DivImportance.Normal, userId, html);
|
||||
|
||||
if (reportCallBack) {
|
||||
const reportBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(`report-${userId}`);
|
||||
reportBtn.addEventListener('click', (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
this.showReportModal(userId, userName, reportCallBack);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail<HTMLVideoElement>(userId));
|
||||
|
||||
//permit to create participant in discussion part
|
||||
this.addNewParticipant(userId, userName, undefined, reportCallBack);
|
||||
const showReportUser = () => {
|
||||
for(const callBack of this.showReportModalCallBacks){
|
||||
callBack(userId, userName);
|
||||
}
|
||||
};
|
||||
this.addNewParticipant(userId, userName, undefined, showReportUser);
|
||||
|
||||
const reportBanUserActionEl: HTMLImageElement = HtmlUtils.getElementByIdOrFail<HTMLImageElement>(`report-${userId}`);
|
||||
reportBanUserActionEl.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
showReportUser();
|
||||
});
|
||||
}
|
||||
|
||||
addScreenSharingActiveVideo(userId: string, divImportance: DivImportance = DivImportance.Important){
|
||||
|
||||
userId = `screen-sharing-${userId}`;
|
||||
userId = this.getScreenSharingId(userId);
|
||||
const html = `
|
||||
<div id="div-${userId}" class="video-container">
|
||||
<video id="${userId}" autoplay></video>
|
||||
|
@ -517,7 +526,11 @@ export class MediaManager {
|
|||
|
||||
this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail<HTMLVideoElement>(userId));
|
||||
}
|
||||
|
||||
|
||||
private getScreenSharingId(userId: string): string {
|
||||
return `screen-sharing-${userId}`;
|
||||
}
|
||||
|
||||
disabledMicrophoneByUserId(userId: number){
|
||||
const element = document.getElementById(`microphone-${userId}`);
|
||||
if(!element){
|
||||
|
@ -556,6 +569,10 @@ export class MediaManager {
|
|||
}
|
||||
}
|
||||
|
||||
toggleBlockLogo(userId: number, show: boolean): void {
|
||||
const blockLogoElement = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('blocking-'+userId);
|
||||
show ? blockLogoElement.classList.add('active') : blockLogoElement.classList.remove('active');
|
||||
}
|
||||
addStreamRemoteVideo(userId: string, stream : MediaStream): void {
|
||||
const remoteVideo = this.remoteVideo.get(userId);
|
||||
if (remoteVideo === undefined) {
|
||||
|
@ -565,12 +582,12 @@ export class MediaManager {
|
|||
}
|
||||
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
|
||||
const remoteVideo = this.remoteVideo.get(`screen-sharing-${userId}`);
|
||||
const remoteVideo = this.remoteVideo.get(this.getScreenSharingId(userId));
|
||||
if (remoteVideo === undefined) {
|
||||
this.addScreenSharingActiveVideo(userId);
|
||||
}
|
||||
|
||||
this.addStreamRemoteVideo(`screen-sharing-${userId}`, stream);
|
||||
this.addStreamRemoteVideo(this.getScreenSharingId(userId), stream);
|
||||
}
|
||||
|
||||
removeActiveVideo(userId: string){
|
||||
|
@ -581,7 +598,7 @@ export class MediaManager {
|
|||
this.removeParticipant(userId);
|
||||
}
|
||||
removeActiveScreenSharingVideo(userId: string) {
|
||||
this.removeActiveVideo(`screen-sharing-${userId}`)
|
||||
this.removeActiveVideo(this.getScreenSharingId(userId))
|
||||
}
|
||||
|
||||
playWebrtcOutSound(): void {
|
||||
|
@ -617,7 +634,7 @@ export class MediaManager {
|
|||
errorDiv.style.display = 'block';
|
||||
}
|
||||
isErrorScreenSharing(userId: string): void {
|
||||
this.isError(`screen-sharing-${userId}`);
|
||||
this.isError(this.getScreenSharingId(userId));
|
||||
}
|
||||
|
||||
|
||||
|
@ -645,65 +662,8 @@ export class MediaManager {
|
|||
return color;
|
||||
}
|
||||
|
||||
public showReportModal(userId: string, userName: string, reportCallBack: ReportCallback){
|
||||
//create report text area
|
||||
const mainContainer = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
|
||||
|
||||
const divReport = document.createElement('div');
|
||||
divReport.classList.add('modal-report-user');
|
||||
|
||||
const inputHidden = document.createElement('input');
|
||||
inputHidden.id = 'input-report-user';
|
||||
inputHidden.type = 'hidden';
|
||||
inputHidden.value = userId;
|
||||
divReport.appendChild(inputHidden);
|
||||
|
||||
const titleMessage = document.createElement('p');
|
||||
titleMessage.id = 'title-report-user';
|
||||
titleMessage.innerText = 'Open a report';
|
||||
divReport.appendChild(titleMessage);
|
||||
|
||||
const bodyMessage = document.createElement('p');
|
||||
bodyMessage.id = 'body-report-user';
|
||||
bodyMessage.innerText = `You are about to open a report regarding an offensive conduct from user ${userName.toUpperCase()}. Please explain to us how you think ${userName.toUpperCase()} breached the code of conduct.`;
|
||||
divReport.appendChild(bodyMessage);
|
||||
|
||||
const imgReportUser = document.createElement('img');
|
||||
imgReportUser.id = 'img-report-user';
|
||||
imgReportUser.src = 'resources/logos/report.svg';
|
||||
divReport.appendChild(imgReportUser);
|
||||
|
||||
const textareaUser = document.createElement('textarea');
|
||||
textareaUser.id = 'textarea-report-user';
|
||||
textareaUser.placeholder = 'Write ...';
|
||||
divReport.appendChild(textareaUser);
|
||||
|
||||
const buttonReport = document.createElement('button');
|
||||
buttonReport.id = 'button-save-report-user';
|
||||
buttonReport.innerText = 'Report';
|
||||
buttonReport.addEventListener('click', () => {
|
||||
if(!textareaUser.value){
|
||||
textareaUser.style.border = '1px solid red'
|
||||
return;
|
||||
}
|
||||
reportCallBack(textareaUser.value);
|
||||
divReport.remove();
|
||||
});
|
||||
divReport.appendChild(buttonReport);
|
||||
|
||||
const buttonCancel = document.createElement('img');
|
||||
buttonCancel.id = 'cancel-report-user';
|
||||
buttonCancel.src = 'resources/logos/close.svg';
|
||||
buttonCancel.addEventListener('click', () => {
|
||||
divReport.remove();
|
||||
});
|
||||
divReport.appendChild(buttonCancel);
|
||||
|
||||
mainContainer.appendChild(divReport);
|
||||
}
|
||||
|
||||
public addNewParticipant(userId: number|string, name: string|undefined, img?: string, reportCallBack?: ReportCallback){
|
||||
discussionManager.addParticipant(userId, name, img, false, reportCallBack);
|
||||
public addNewParticipant(userId: number|string, name: string|undefined, img?: string, showReportUserCallBack?: ShowReportCallBack){
|
||||
discussionManager.addParticipant(userId, name, img, false, showReportUserCallBack);
|
||||
}
|
||||
|
||||
public removeParticipant(userId: number|string){
|
||||
|
@ -769,6 +729,10 @@ export class MediaManager {
|
|||
this.checkActiveUser();
|
||||
}, this.focused ? 10000 : 1000);
|
||||
}
|
||||
|
||||
public setShowReportModalCallBacks(callback: ShowReportCallBack){
|
||||
this.showReportModalCallBacks.add(callback);
|
||||
}
|
||||
}
|
||||
|
||||
export const mediaManager = new MediaManager();
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import * as SimplePeerNamespace from "simple-peer";
|
||||
import {mediaManager} from "./MediaManager";
|
||||
import {TURN_SERVER, TURN_USER, TURN_PASSWORD} from "../Enum/EnvironmentVariable";
|
||||
import {STUN_SERVER, TURN_SERVER, TURN_USER, TURN_PASSWORD} from "../Enum/EnvironmentVariable";
|
||||
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import {MESSAGE_TYPE_CONSTRAINT} from "./VideoPeer";
|
||||
import {UserSimplePeerInterface} from "./SimplePeer";
|
||||
|
||||
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
|
||||
|
||||
|
@ -16,25 +17,28 @@ export class ScreenSharingPeer extends Peer {
|
|||
private isReceivingStream:boolean = false;
|
||||
public toClose: boolean = false;
|
||||
public _connected: boolean = false;
|
||||
private userId: number;
|
||||
|
||||
constructor(private userId: number, initiator: boolean, private connection: RoomConnection) {
|
||||
constructor(user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection) {
|
||||
super({
|
||||
initiator: initiator ? initiator : false,
|
||||
reconnectTimer: 10000,
|
||||
config: {
|
||||
iceServers: [
|
||||
{
|
||||
urls: 'stun:stun.l.google.com:19302'
|
||||
urls: STUN_SERVER.split(',')
|
||||
},
|
||||
{
|
||||
TURN_SERVER !== '' ? {
|
||||
urls: TURN_SERVER.split(','),
|
||||
username: TURN_USER,
|
||||
credential: TURN_PASSWORD
|
||||
},
|
||||
]
|
||||
username: user.webRtcUser || TURN_USER,
|
||||
credential: user.webRtcPassword || TURN_PASSWORD
|
||||
} : undefined,
|
||||
].filter((value) => value !== undefined)
|
||||
}
|
||||
});
|
||||
|
||||
this.userId = user.userId;
|
||||
|
||||
//start listen signal for the peer connection
|
||||
this.on('signal', (data: unknown) => {
|
||||
this.sendWebrtcScreenSharingSignal(data);
|
||||
|
|
|
@ -9,13 +9,18 @@ import {
|
|||
UpdatedLocalStreamCallback
|
||||
} from "./MediaManager";
|
||||
import {ScreenSharingPeer} from "./ScreenSharingPeer";
|
||||
import {MESSAGE_TYPE_CONSTRAINT, MESSAGE_TYPE_MESSAGE, VideoPeer} from "./VideoPeer";
|
||||
import {MESSAGE_TYPE_BLOCKED, MESSAGE_TYPE_CONSTRAINT, MESSAGE_TYPE_MESSAGE, VideoPeer} from "./VideoPeer";
|
||||
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import {connectionManager} from "../Connexion/ConnectionManager";
|
||||
import {GameConnexionTypes} from "../Url/UrlManager";
|
||||
import {blackListManager} from "./BlackListManager";
|
||||
|
||||
export interface UserSimplePeerInterface{
|
||||
userId: number;
|
||||
name?: string;
|
||||
initiator?: boolean;
|
||||
webRtcUser?: string|undefined;
|
||||
webRtcPassword?: string|undefined;
|
||||
}
|
||||
|
||||
export interface PeerConnectionListener {
|
||||
|
@ -36,6 +41,9 @@ export class SimplePeer {
|
|||
private readonly sendLocalScreenSharingStreamCallback: StartScreenSharingCallback;
|
||||
private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback;
|
||||
private readonly peerConnectionListeners: Array<PeerConnectionListener> = new Array<PeerConnectionListener>();
|
||||
private readonly userId: number;
|
||||
private lastWebrtcUserName: string|undefined;
|
||||
private lastWebrtcPassword: string|undefined;
|
||||
|
||||
constructor(private Connection: RoomConnection, private enableReporting: boolean, private myName: string) {
|
||||
// We need to go through this weird bound function pointer in order to be able to "free" this reference later.
|
||||
|
@ -46,6 +54,7 @@ export class SimplePeer {
|
|||
mediaManager.onUpdateLocalStream(this.sendLocalVideoStreamCallback);
|
||||
mediaManager.onStartScreenSharing(this.sendLocalScreenSharingStreamCallback);
|
||||
mediaManager.onStopScreenSharing(this.stopLocalScreenSharingStreamCallback);
|
||||
this.userId = Connection.getUserId();
|
||||
this.initialise();
|
||||
}
|
||||
|
||||
|
@ -54,7 +63,7 @@ export class SimplePeer {
|
|||
}
|
||||
|
||||
public getNbConnections(): number {
|
||||
return this.PeerConnectionArray.size;
|
||||
return this.Users.length;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -89,15 +98,14 @@ export class SimplePeer {
|
|||
});
|
||||
}
|
||||
|
||||
private receiveWebrtcStart(user: UserSimplePeerInterface) {
|
||||
//this.WebRtcRoomId = data.roomId;
|
||||
private receiveWebrtcStart(user: UserSimplePeerInterface): void {
|
||||
this.Users.push(user);
|
||||
// Note: the clients array contain the list of all clients (even the ones we are already connected to in case a user joints a group)
|
||||
// So we can receive a request we already had before. (which will abort at the first line of createPeerConnection)
|
||||
// This would be symmetrical to the way we handle disconnection.
|
||||
|
||||
|
||||
//start connection
|
||||
console.log('receiveWebrtcStart. Initiator: ', user.initiator)
|
||||
//console.log('receiveWebrtcStart. Initiator: ', user.initiator)
|
||||
if(!user.initiator){
|
||||
return;
|
||||
}
|
||||
|
@ -134,17 +142,16 @@ export class SimplePeer {
|
|||
|
||||
mediaManager.removeActiveVideo("" + user.userId);
|
||||
|
||||
const reportCallback = this.enableReporting ? (comment: string) => {
|
||||
this.reportUser(user.userId, comment);
|
||||
} : undefined;
|
||||
mediaManager.addActiveVideo(user, name);
|
||||
|
||||
mediaManager.addActiveVideo("" + user.userId, reportCallback, name);
|
||||
this.lastWebrtcUserName = user.webRtcUser;
|
||||
this.lastWebrtcPassword = user.webRtcPassword;
|
||||
|
||||
const peer = new VideoPeer(user.userId, user.initiator ? user.initiator : false, this.Connection);
|
||||
const peer = new VideoPeer(user, user.initiator ? user.initiator : false, this.Connection);
|
||||
|
||||
//permit to send message
|
||||
mediaManager.addSendMessageCallback(user.userId,(message: string) => {
|
||||
peer.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_MESSAGE, name: this.myName.toUpperCase(), message: message})));
|
||||
peer.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_MESSAGE, name: this.myName.toUpperCase(), userId: this.userId, message: message})));
|
||||
});
|
||||
|
||||
peer.toClose = false;
|
||||
|
@ -189,7 +196,13 @@ export class SimplePeer {
|
|||
mediaManager.addScreenSharingActiveVideo("" + user.userId);
|
||||
}
|
||||
|
||||
const peer = new ScreenSharingPeer(user.userId, user.initiator ? user.initiator : false, this.Connection);
|
||||
// Enrich the user with last known credentials (if they are not set in the user object, which happens when a user triggers the screen sharing)
|
||||
if (user.webRtcUser === undefined) {
|
||||
user.webRtcUser = this.lastWebrtcUserName;
|
||||
user.webRtcPassword = this.lastWebrtcPassword;
|
||||
}
|
||||
|
||||
const peer = new ScreenSharingPeer(user, user.initiator ? user.initiator : false, this.Connection);
|
||||
this.PeerScreenSharingConnectionArray.set(user.userId, peer);
|
||||
|
||||
for (const peerConnectionListener of this.peerConnectionListeners) {
|
||||
|
@ -217,9 +230,6 @@ export class SimplePeer {
|
|||
|
||||
this.closeScreenSharingConnection(userId);
|
||||
|
||||
for (const peerConnectionListener of this.peerConnectionListeners) {
|
||||
peerConnectionListener.onDisconnect(userId);
|
||||
}
|
||||
const userIndex = this.Users.findIndex(user => user.userId === userId);
|
||||
if(userIndex < 0){
|
||||
throw 'Couln\'t delete user';
|
||||
|
@ -237,6 +247,10 @@ export class SimplePeer {
|
|||
this.PeerScreenSharingConnectionArray.delete(userId);
|
||||
}
|
||||
}
|
||||
|
||||
for (const peerConnectionListener of this.peerConnectionListeners) {
|
||||
peerConnectionListener.onDisconnect(userId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -300,6 +314,7 @@ export class SimplePeer {
|
|||
}
|
||||
|
||||
private receiveWebrtcScreenSharingSignal(data: WebRtcSignalReceivedMessageInterface) {
|
||||
if (blackListManager.isBlackListed(data.userId)) return;
|
||||
console.log("receiveWebrtcScreenSharingSignal", data);
|
||||
try {
|
||||
//if offer type, create peer connection
|
||||
|
@ -391,14 +406,8 @@ export class SimplePeer {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered locally when clicking on the report button
|
||||
*/
|
||||
public reportUser(userId: number, message: string) {
|
||||
this.Connection.emitReportPlayerMessage(userId, message)
|
||||
}
|
||||
|
||||
private sendLocalScreenSharingStreamToUser(userId: number): void {
|
||||
if (blackListManager.isBlackListed(userId)) return;
|
||||
// If a connection already exists with user (because it is already sharing a screen with us... let's use this connection)
|
||||
if (this.PeerScreenSharingConnectionArray.has(userId)) {
|
||||
this.pushScreenSharingToRemoteUser(userId);
|
||||
|
|
|
@ -1,65 +1,57 @@
|
|||
import * as SimplePeerNamespace from "simple-peer";
|
||||
import {mediaManager} from "./MediaManager";
|
||||
import {TURN_PASSWORD, TURN_SERVER, TURN_USER} from "../Enum/EnvironmentVariable";
|
||||
import {STUN_SERVER, TURN_PASSWORD, TURN_SERVER, TURN_USER} from "../Enum/EnvironmentVariable";
|
||||
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import {blackListManager} from "./BlackListManager";
|
||||
import {Subscription} from "rxjs";
|
||||
import {UserSimplePeerInterface} from "./SimplePeer";
|
||||
|
||||
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
|
||||
|
||||
export const MESSAGE_TYPE_CONSTRAINT = 'constraint';
|
||||
export const MESSAGE_TYPE_MESSAGE = 'message';
|
||||
export const MESSAGE_TYPE_BLOCKED = 'blocked';
|
||||
export const MESSAGE_TYPE_UNBLOCKED = 'unblocked';
|
||||
/**
|
||||
* A peer connection used to transmit video / audio signals between 2 peers.
|
||||
*/
|
||||
export class VideoPeer extends Peer {
|
||||
public toClose: boolean = false;
|
||||
public _connected: boolean = false;
|
||||
private remoteStream!: MediaStream;
|
||||
private blocked: boolean = false;
|
||||
private userId: number;
|
||||
private userName: string;
|
||||
private onBlockSubscribe: Subscription;
|
||||
private onUnBlockSubscribe: Subscription;
|
||||
|
||||
constructor(public userId: number, initiator: boolean, private connection: RoomConnection) {
|
||||
constructor(public user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection) {
|
||||
super({
|
||||
initiator: initiator ? initiator : false,
|
||||
reconnectTimer: 10000,
|
||||
config: {
|
||||
iceServers: [
|
||||
{
|
||||
urls: 'stun:stun.l.google.com:19302'
|
||||
urls: STUN_SERVER.split(',')
|
||||
},
|
||||
{
|
||||
TURN_SERVER !== '' ? {
|
||||
urls: TURN_SERVER.split(','),
|
||||
username: TURN_USER,
|
||||
credential: TURN_PASSWORD
|
||||
},
|
||||
]
|
||||
username: user.webRtcUser || TURN_USER,
|
||||
credential: user.webRtcPassword || TURN_PASSWORD
|
||||
} : undefined,
|
||||
].filter((value) => value !== undefined)
|
||||
}
|
||||
});
|
||||
|
||||
console.log('PEER SETUP ', {
|
||||
initiator: initiator ? initiator : false,
|
||||
reconnectTimer: 10000,
|
||||
config: {
|
||||
iceServers: [
|
||||
{
|
||||
urls: 'stun:stun.l.google.com:19302'
|
||||
},
|
||||
{
|
||||
urls: TURN_SERVER.split(','),
|
||||
username: TURN_USER,
|
||||
credential: TURN_PASSWORD
|
||||
},
|
||||
]
|
||||
}
|
||||
});
|
||||
this.userId = user.userId;
|
||||
this.userName = user.name || '';
|
||||
|
||||
//start listen signal for the peer connection
|
||||
this.on('signal', (data: unknown) => {
|
||||
this.sendWebrtcSignal(data);
|
||||
});
|
||||
|
||||
this.on('stream', (stream: MediaStream) => {
|
||||
this.stream(stream);
|
||||
});
|
||||
|
||||
/*peer.on('track', (track: MediaStreamTrack, stream: MediaStream) => {
|
||||
});*/
|
||||
this.on('stream', (stream: MediaStream) => this.stream(stream));
|
||||
|
||||
this.on('close', () => {
|
||||
this._connected = false;
|
||||
|
@ -70,7 +62,7 @@ export class VideoPeer extends Peer {
|
|||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
this.on('error', (err: any) => {
|
||||
console.error(`error => ${this.userId} => ${err.code}`, err);
|
||||
mediaManager.isError("" + userId);
|
||||
mediaManager.isError("" + this.userId);
|
||||
});
|
||||
|
||||
this.on('connect', () => {
|
||||
|
@ -81,8 +73,6 @@ export class VideoPeer extends Peer {
|
|||
|
||||
this.on('data', (chunk: Buffer) => {
|
||||
const message = JSON.parse(chunk.toString('utf8'));
|
||||
console.log("data", message);
|
||||
|
||||
if(message.type === MESSAGE_TYPE_CONSTRAINT) {
|
||||
if (message.audio) {
|
||||
mediaManager.enabledMicrophoneByUserId(this.userId);
|
||||
|
@ -95,8 +85,19 @@ export class VideoPeer extends Peer {
|
|||
} else {
|
||||
mediaManager.disabledVideoByUserId(this.userId);
|
||||
}
|
||||
} else if(message.type === 'message') {
|
||||
mediaManager.addNewMessage(message.name, message.message);
|
||||
} else if(message.type === MESSAGE_TYPE_MESSAGE) {
|
||||
if (!blackListManager.isBlackListed(message.userId)) {
|
||||
mediaManager.addNewMessage(message.name, message.message);
|
||||
}
|
||||
} else if(message.type === MESSAGE_TYPE_BLOCKED) {
|
||||
//FIXME when A blacklists B, the output stream from A is muted in B's js client. This is insecure since B can manipulate the code to unmute A stream.
|
||||
// Find a way to block A's output stream in A's js client
|
||||
//However, the output stream stream B is correctly blocked in A client
|
||||
this.blocked = true;
|
||||
this.toggleRemoteStream(false);
|
||||
} else if(message.type === MESSAGE_TYPE_UNBLOCKED) {
|
||||
this.blocked = false;
|
||||
this.toggleRemoteStream(true);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -105,6 +106,31 @@ export class VideoPeer extends Peer {
|
|||
});
|
||||
|
||||
this.pushVideoToRemoteUser();
|
||||
this.onBlockSubscribe = blackListManager.onBlockStream.subscribe((userId) => {
|
||||
if (userId === this.userId) {
|
||||
this.toggleRemoteStream(false);
|
||||
this.sendBlockMessage(true);
|
||||
}
|
||||
});
|
||||
this.onUnBlockSubscribe = blackListManager.onUnBlockStream.subscribe((userId) => {
|
||||
if (userId === this.userId) {
|
||||
this.toggleRemoteStream(true);
|
||||
this.sendBlockMessage(false);
|
||||
}
|
||||
});
|
||||
|
||||
if (blackListManager.isBlackListed(this.userId)) {
|
||||
this.sendBlockMessage(true)
|
||||
}
|
||||
}
|
||||
|
||||
private sendBlockMessage(blocking: boolean) {
|
||||
this.write(new Buffer(JSON.stringify({type: blocking ? MESSAGE_TYPE_BLOCKED : MESSAGE_TYPE_UNBLOCKED, name: this.userName.toUpperCase(), userId: this.userId, message: ''})));
|
||||
}
|
||||
|
||||
private toggleRemoteStream(enable: boolean) {
|
||||
this.remoteStream.getTracks().forEach(track => track.enabled = enable);
|
||||
mediaManager.toggleBlockLogo(this.userId, !enable);
|
||||
}
|
||||
|
||||
private sendWebrtcSignal(data: unknown) {
|
||||
|
@ -120,13 +146,13 @@ export class VideoPeer extends Peer {
|
|||
*/
|
||||
private stream(stream: MediaStream) {
|
||||
try {
|
||||
this.remoteStream = stream;
|
||||
if (blackListManager.isBlackListed(this.userId) || this.blocked) {
|
||||
this.toggleRemoteStream(false);
|
||||
}
|
||||
mediaManager.addStreamRemoteVideo("" + this.userId, stream);
|
||||
}catch (err){
|
||||
console.error(err);
|
||||
//Force add streem video
|
||||
/*setTimeout(() => {
|
||||
this.stream(stream);
|
||||
}, 500);*/ //todo: find a way to prevent infinite regression.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,6 +165,8 @@ export class VideoPeer extends Peer {
|
|||
if(!this.toClose){
|
||||
return;
|
||||
}
|
||||
this.onBlockSubscribe.unsubscribe();
|
||||
this.onUnBlockSubscribe.unsubscribe();
|
||||
mediaManager.removeActiveVideo("" + this.userId);
|
||||
// FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray"
|
||||
// I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel.
|
||||
|
|
|
@ -1,28 +1,21 @@
|
|||
import 'phaser';
|
||||
import GameConfig = Phaser.Types.Core.GameConfig;
|
||||
import "../dist/resources/style/index.scss";
|
||||
|
||||
import {DEBUG_MODE, JITSI_URL, RESOLUTION} from "./Enum/EnvironmentVariable";
|
||||
import {cypressAsserter} from "./Cypress/CypressAsserter";
|
||||
import {LoginScene} from "./Phaser/Login/LoginScene";
|
||||
import {ReconnectingScene} from "./Phaser/Reconnecting/ReconnectingScene";
|
||||
import {SelectCharacterScene} from "./Phaser/Login/SelectCharacterScene";
|
||||
import {EnableCameraScene} from "./Phaser/Login/EnableCameraScene";
|
||||
import WebGLRenderer = Phaser.Renderer.WebGL.WebGLRenderer;
|
||||
import {OutlinePipeline} from "./Phaser/Shaders/OutlinePipeline";
|
||||
import {CustomizeScene} from "./Phaser/Login/CustomizeScene";
|
||||
import {ResizableScene} from "./Phaser/Login/ResizableScene";
|
||||
import {EntryScene} from "./Phaser/Login/EntryScene";
|
||||
import {coWebsiteManager} from "./WebRtc/CoWebsiteManager";
|
||||
import {MenuScene} from "./Phaser/Menu/MenuScene";
|
||||
import {HelpCameraSettingsScene} from "./Phaser/Menu/HelpCameraSettingsScene";
|
||||
import {localUserStore} from "./Connexion/LocalUserStore";
|
||||
import {ErrorScene} from "./Phaser/Reconnecting/ErrorScene";
|
||||
|
||||
// Load Jitsi if the environment variable is set.
|
||||
if (JITSI_URL) {
|
||||
const jitsiScript = document.createElement('script');
|
||||
jitsiScript.src = 'https://' + JITSI_URL + '/external_api.js';
|
||||
document.head.appendChild(jitsiScript);
|
||||
}
|
||||
|
||||
const {width, height} = coWebsiteManager.getGameSize();
|
||||
|
||||
const valueGameQuality = localUserStore.getGameQualityValue();
|
||||
|
@ -79,12 +72,17 @@ const config: GameConfig = {
|
|||
width: width / RESOLUTION,
|
||||
height: height / RESOLUTION,
|
||||
parent: "game",
|
||||
scene: [EntryScene, LoginScene, SelectCharacterScene, EnableCameraScene, ReconnectingScene, ErrorScene, CustomizeScene, MenuScene],
|
||||
scene: [EntryScene, LoginScene, SelectCharacterScene, EnableCameraScene, ReconnectingScene, ErrorScene, CustomizeScene, MenuScene, HelpCameraSettingsScene],
|
||||
zoom: RESOLUTION,
|
||||
fps: fps,
|
||||
dom: {
|
||||
createContainer: true
|
||||
},
|
||||
render: {
|
||||
pixelArt: true,
|
||||
roundPixels: true,
|
||||
antialias: false
|
||||
},
|
||||
physics: {
|
||||
default: "arcade",
|
||||
arcade: {
|
||||
|
@ -93,19 +91,19 @@ const config: GameConfig = {
|
|||
},
|
||||
callbacks: {
|
||||
postBoot: game => {
|
||||
const renderer = game.renderer;
|
||||
// Commented out to try to fix MacOS bug
|
||||
/*const renderer = game.renderer;
|
||||
if (renderer instanceof WebGLRenderer) {
|
||||
renderer.pipelines.add(OutlinePipeline.KEY, new OutlinePipeline(game));
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
cypressAsserter.gameStarted();
|
||||
|
||||
const game = new Phaser.Game(config);
|
||||
|
||||
window.addEventListener('resize', function (event) {
|
||||
coWebsiteManager.resetStyle();
|
||||
const {width, height} = coWebsiteManager.getGameSize();
|
||||
game.scale.resize(width / RESOLUTION, height / RESOLUTION);
|
||||
|
||||
|
@ -117,7 +115,7 @@ window.addEventListener('resize', function (event) {
|
|||
}
|
||||
});
|
||||
|
||||
coWebsiteManager.onStateChange(() => {
|
||||
coWebsiteManager.onResize.subscribe(() => {
|
||||
const {width, height} = coWebsiteManager.getGameSize();
|
||||
game.scale.resize(width / RESOLUTION, height / RESOLUTION);
|
||||
});
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#!/usr/bin/env bash
|
||||
set -x
|
||||
set -o nounset errexit
|
||||
template_file_index=dist/index.html.tmpl
|
||||
generated_file_index=dist/index.html
|
||||
template_file_index=dist/index.tmpl.html
|
||||
generated_file_index=dist/index.tmpl.html.tmp
|
||||
tmp_trackcodefile=/tmp/trackcode
|
||||
|
||||
# To inject tracking code, you have two choices:
|
||||
|
|
|
@ -2,13 +2,19 @@ import "jasmine";
|
|||
import {HtmlUtils} from "../../../src/WebRtc/HtmlUtils";
|
||||
|
||||
describe("urlify()", () => {
|
||||
it("should transform an url into a link", () => {
|
||||
const text = HtmlUtils.urlify('https://google.com');
|
||||
expect(text).toEqual('<a href="https://google.com" target="_blank" style=":visited {color: white}">https://google.com</a>');
|
||||
// FIXME: we need to add PhantomJS to have a good mock for "document".
|
||||
/*it("should transform an url into a link", () => {
|
||||
const text = HtmlUtils.urlify('foo https://google.com bar');
|
||||
expect(text).toEqual('foo <a href="https://google.com" target="_blank" style=":visited {color: white}">https://google.com</a> bar');
|
||||
});
|
||||
|
||||
it("should not transform a normal text into a link", () => {
|
||||
const text = HtmlUtils.urlify('hello');
|
||||
expect(text).toEqual('hello');
|
||||
});
|
||||
});
|
||||
|
||||
it("should escape HTML", () => {
|
||||
const text = HtmlUtils.urlify('<h1>boo</h1>');
|
||||
expect(text).toEqual('<h1>boo</h1>');
|
||||
});*/
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
|
||||
module.exports = {
|
||||
entry: './src/index.ts',
|
||||
|
@ -23,6 +24,10 @@ module.exports = {
|
|||
use: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: [MiniCssExtractPlugin.loader, 'css-loader?url=false', 'sass-loader'],
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
|
@ -37,15 +42,37 @@ module.exports = {
|
|||
require('webpack-require-http')
|
||||
],
|
||||
plugins: [
|
||||
new MiniCssExtractPlugin({filename: 'style.[contenthash].css'}),
|
||||
new HtmlWebpackPlugin(
|
||||
{
|
||||
template: './dist/index.html'
|
||||
template: './dist/index.tmpl.html.tmp',
|
||||
minify: {
|
||||
collapseWhitespace: true,
|
||||
keepClosingSlash: true,
|
||||
removeComments: false,
|
||||
removeRedundantAttributes: true,
|
||||
removeScriptTypeAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
useShortDoctype: true
|
||||
}
|
||||
}
|
||||
),
|
||||
new webpack.ProvidePlugin({
|
||||
Phaser: 'phaser'
|
||||
}),
|
||||
new webpack.EnvironmentPlugin(['API_URL', 'UPLOADER_URL', 'ADMIN_URL', 'DEBUG_MODE', 'TURN_SERVER', 'TURN_USER', 'TURN_PASSWORD', 'JITSI_URL', 'JITSI_PRIVATE_MODE', 'START_ROOM_URL'])
|
||||
new webpack.EnvironmentPlugin([
|
||||
'API_URL',
|
||||
'UPLOADER_URL',
|
||||
'ADMIN_URL',
|
||||
'DEBUG_MODE',
|
||||
'STUN_SERVER',
|
||||
'TURN_SERVER',
|
||||
'TURN_USER',
|
||||
'TURN_PASSWORD',
|
||||
'JITSI_URL',
|
||||
'JITSI_PRIVATE_MODE',
|
||||
'START_ROOM_URL'
|
||||
])
|
||||
],
|
||||
|
||||
};
|
||||
|
|
257
front/yarn.lock
|
@ -640,10 +640,10 @@ bluebird@^3.5.5:
|
|||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
|
||||
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
|
||||
|
||||
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.4.0:
|
||||
version "4.11.9"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828"
|
||||
integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==
|
||||
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9:
|
||||
version "4.12.0"
|
||||
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
|
||||
integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
|
||||
|
||||
bn.js@^5.1.1:
|
||||
version "5.1.3"
|
||||
|
@ -714,7 +714,7 @@ braces@^3.0.1, braces@~3.0.2:
|
|||
dependencies:
|
||||
fill-range "^7.0.1"
|
||||
|
||||
brorand@^1.0.1:
|
||||
brorand@^1.0.1, brorand@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
|
||||
integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
|
||||
|
@ -878,6 +878,11 @@ camelcase@^5.0.0:
|
|||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
|
||||
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
|
||||
|
||||
camelcase@^6.2.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809"
|
||||
integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
|
||||
|
||||
chalk@^2.0.0, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.2:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
|
||||
|
@ -905,6 +910,21 @@ charenc@0.0.2:
|
|||
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
|
||||
integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=
|
||||
|
||||
"chokidar@>=2.0.0 <4.0.0":
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a"
|
||||
integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==
|
||||
dependencies:
|
||||
anymatch "~3.1.1"
|
||||
braces "~3.0.2"
|
||||
glob-parent "~5.1.0"
|
||||
is-binary-path "~2.1.0"
|
||||
is-glob "~4.0.1"
|
||||
normalize-path "~3.0.0"
|
||||
readdirp "~3.5.0"
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.1"
|
||||
|
||||
chokidar@^2.1.8:
|
||||
version "2.1.8"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
|
||||
|
@ -1034,6 +1054,11 @@ color-name@~1.1.4:
|
|||
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
|
||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||
|
||||
colorette@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
|
||||
integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
|
||||
|
||||
commander@^2.20.0:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
|
@ -1222,6 +1247,24 @@ crypto-browserify@^3.11.0:
|
|||
randombytes "^2.0.0"
|
||||
randomfill "^1.0.3"
|
||||
|
||||
css-loader@^5.1.3:
|
||||
version "5.1.3"
|
||||
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.1.3.tgz#87f6fc96816b20debe3cf682f85c7e56a963d0d1"
|
||||
integrity sha512-CoPZvyh8sLiGARK3gqczpfdedbM74klGWurF2CsNZ2lhNaXdLIUks+3Mfax3WBeRuHoglU+m7KG/+7gY6G4aag==
|
||||
dependencies:
|
||||
camelcase "^6.2.0"
|
||||
cssesc "^3.0.0"
|
||||
icss-utils "^5.1.0"
|
||||
loader-utils "^2.0.0"
|
||||
postcss "^8.2.8"
|
||||
postcss-modules-extract-imports "^3.0.0"
|
||||
postcss-modules-local-by-default "^4.0.0"
|
||||
postcss-modules-scope "^3.0.0"
|
||||
postcss-modules-values "^4.0.0"
|
||||
postcss-value-parser "^4.1.0"
|
||||
schema-utils "^3.0.0"
|
||||
semver "^7.3.4"
|
||||
|
||||
css-select@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858"
|
||||
|
@ -1237,6 +1280,11 @@ css-what@2.1:
|
|||
resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2"
|
||||
integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==
|
||||
|
||||
cssesc@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
|
||||
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
|
||||
|
||||
cyclist@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
|
||||
|
@ -1493,17 +1541,17 @@ ee-first@1.1.1:
|
|||
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
|
||||
|
||||
elliptic@^6.5.3:
|
||||
version "6.5.3"
|
||||
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6"
|
||||
integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==
|
||||
version "6.5.4"
|
||||
resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
|
||||
integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==
|
||||
dependencies:
|
||||
bn.js "^4.4.0"
|
||||
brorand "^1.0.1"
|
||||
bn.js "^4.11.9"
|
||||
brorand "^1.1.0"
|
||||
hash.js "^1.0.0"
|
||||
hmac-drbg "^1.0.0"
|
||||
inherits "^2.0.1"
|
||||
minimalistic-assert "^1.0.0"
|
||||
minimalistic-crypto-utils "^1.0.0"
|
||||
hmac-drbg "^1.0.1"
|
||||
inherits "^2.0.4"
|
||||
minimalistic-assert "^1.0.1"
|
||||
minimalistic-crypto-utils "^1.0.1"
|
||||
|
||||
emoji-regex@^7.0.1:
|
||||
version "7.0.3"
|
||||
|
@ -1829,15 +1877,6 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2:
|
|||
dependencies:
|
||||
homedir-polyfill "^1.0.1"
|
||||
|
||||
exports-loader@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/exports-loader/-/exports-loader-1.1.1.tgz#88c9a6877ee6a5519d7c41a016bdd99148421e69"
|
||||
integrity sha512-CmyhIR2sJ3KOfVsHjsR0Yvo+0lhRhRMAevCbB8dhTVLHsZPs0lCQTvRmR9YNvBXDBxUuhmCE2f54KqEjZUaFrg==
|
||||
dependencies:
|
||||
loader-utils "^2.0.0"
|
||||
schema-utils "^3.0.0"
|
||||
source-map "^0.6.1"
|
||||
|
||||
express@^4.17.1:
|
||||
version "4.17.1"
|
||||
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
|
||||
|
@ -2128,6 +2167,11 @@ fsevents@~2.1.2:
|
|||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e"
|
||||
integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==
|
||||
|
||||
fsevents@~2.3.1:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
|
||||
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
|
||||
|
||||
function-bind@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||
|
@ -2353,7 +2397,7 @@ he@^1.2.0:
|
|||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||
|
||||
hmac-drbg@^1.0.0:
|
||||
hmac-drbg@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
|
||||
integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=
|
||||
|
@ -2497,6 +2541,11 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24:
|
|||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3"
|
||||
|
||||
icss-utils@^5.0.0, icss-utils@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
|
||||
integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
|
||||
|
||||
ieee754@^1.1.4:
|
||||
version "1.1.13"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
|
||||
|
@ -2528,21 +2577,16 @@ import-local@^2.0.0:
|
|||
pkg-dir "^3.0.0"
|
||||
resolve-cwd "^2.0.0"
|
||||
|
||||
imports-loader@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/imports-loader/-/imports-loader-1.2.0.tgz#b06823d0bb42e6f5ff89bc893829000eda46693f"
|
||||
integrity sha512-zPvangKEgrrPeqeUqH0Uhc59YqK07JqZBi9a9cQ3v/EKUIqrbJHY4CvUrDus2lgQa5AmPyXuGrWP8JJTqzE5RQ==
|
||||
dependencies:
|
||||
loader-utils "^2.0.0"
|
||||
schema-utils "^3.0.0"
|
||||
source-map "^0.6.1"
|
||||
strip-comments "^2.0.1"
|
||||
|
||||
imurmurhash@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
|
||||
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
|
||||
|
||||
indexes-of@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607"
|
||||
integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc=
|
||||
|
||||
indexof@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
|
||||
|
@ -2942,6 +2986,11 @@ kind-of@^6.0.0, kind-of@^6.0.2:
|
|||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
|
||||
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
|
||||
|
||||
klona@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0"
|
||||
integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==
|
||||
|
||||
levn@^0.3.0, levn@~0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
|
||||
|
@ -3010,6 +3059,13 @@ lru-cache@^5.1.1:
|
|||
dependencies:
|
||||
yallist "^3.0.2"
|
||||
|
||||
lru-cache@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
|
||||
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
|
||||
dependencies:
|
||||
yallist "^4.0.0"
|
||||
|
||||
make-dir@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
|
||||
|
@ -3156,12 +3212,21 @@ mimic-fn@^2.1.0:
|
|||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
||||
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
|
||||
|
||||
mini-css-extract-plugin@^1.3.9:
|
||||
version "1.3.9"
|
||||
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.3.9.tgz#47a32132b0fd97a119acd530e8421e8f6ab16d5e"
|
||||
integrity sha512-Ac4s+xhVbqlyhXS5J/Vh/QXUz3ycXlCqoCPpg0vdfhsIBH9eg/It/9L1r1XhSCH737M1lqcWnMuWL13zcygn5A==
|
||||
dependencies:
|
||||
loader-utils "^2.0.0"
|
||||
schema-utils "^3.0.0"
|
||||
webpack-sources "^1.1.0"
|
||||
|
||||
minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
|
||||
integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
|
||||
|
||||
minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
|
||||
minimalistic-crypto-utils@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
|
||||
integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
|
||||
|
@ -3259,6 +3324,11 @@ nan@^2.12.1:
|
|||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01"
|
||||
integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==
|
||||
|
||||
nanoid@^3.1.20:
|
||||
version "3.1.22"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.22.tgz#b35f8fb7d151990a8aebd5aa5015c03cf726f844"
|
||||
integrity sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ==
|
||||
|
||||
nanomatch@^1.2.9:
|
||||
version "1.2.13"
|
||||
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
|
||||
|
@ -3286,7 +3356,7 @@ negotiator@0.6.2:
|
|||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
|
||||
integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
|
||||
|
||||
neo-async@^2.5.0, neo-async@^2.6.1:
|
||||
neo-async@^2.5.0, neo-async@^2.6.1, neo-async@^2.6.2:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
|
||||
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
|
||||
|
@ -3663,14 +3733,12 @@ pbkdf2@^3.0.3:
|
|||
safe-buffer "^5.0.1"
|
||||
sha.js "^2.4.8"
|
||||
|
||||
phaser@^3.52.0:
|
||||
version "3.52.0"
|
||||
resolved "https://registry.yarnpkg.com/phaser/-/phaser-3.52.0.tgz#834dd7a7717308c2576d2450d0806317cf3d1ea8"
|
||||
integrity sha512-oH39AUXR+cletB3GIvLnVO2J7/M6xs0D0LamVdCrbCDO3jckQOVBcqR6SJ2wam+4D5iJTCXv8K+FmxFzcTUWuA==
|
||||
phaser@^3.53.1:
|
||||
version "3.54.0"
|
||||
resolved "https://registry.yarnpkg.com/phaser/-/phaser-3.54.0.tgz#46b191e46059aab2a9a57f78525c60b595767eee"
|
||||
integrity sha512-/1XVI6J2siS0OGwJez7vLbRjars1zb//EvJdYMVyd3wNTUf5DHrvYUj1f6TsEISr4vjnbrNtS66QIuPbGU8x6A==
|
||||
dependencies:
|
||||
eventemitter3 "^4.0.7"
|
||||
exports-loader "^1.1.1"
|
||||
imports-loader "^1.2.0"
|
||||
path "^0.12.7"
|
||||
|
||||
picomatch@^2.0.4, picomatch@^2.0.5, picomatch@^2.2.1:
|
||||
|
@ -3721,6 +3789,58 @@ posix-character-classes@^0.1.0:
|
|||
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
|
||||
integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
|
||||
|
||||
postcss-modules-extract-imports@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d"
|
||||
integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==
|
||||
|
||||
postcss-modules-local-by-default@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c"
|
||||
integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==
|
||||
dependencies:
|
||||
icss-utils "^5.0.0"
|
||||
postcss-selector-parser "^6.0.2"
|
||||
postcss-value-parser "^4.1.0"
|
||||
|
||||
postcss-modules-scope@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06"
|
||||
integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==
|
||||
dependencies:
|
||||
postcss-selector-parser "^6.0.4"
|
||||
|
||||
postcss-modules-values@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c"
|
||||
integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==
|
||||
dependencies:
|
||||
icss-utils "^5.0.0"
|
||||
|
||||
postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4:
|
||||
version "6.0.4"
|
||||
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz#56075a1380a04604c38b063ea7767a129af5c2b3"
|
||||
integrity sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==
|
||||
dependencies:
|
||||
cssesc "^3.0.0"
|
||||
indexes-of "^1.0.1"
|
||||
uniq "^1.0.1"
|
||||
util-deprecate "^1.0.2"
|
||||
|
||||
postcss-value-parser@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb"
|
||||
integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
|
||||
|
||||
postcss@^8.2.8:
|
||||
version "8.2.8"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.8.tgz#0b90f9382efda424c4f0f69a2ead6f6830d08ece"
|
||||
integrity sha512-1F0Xb2T21xET7oQV9eKuctbM9S7BC0fetoHCc4H13z0PT6haiRLP4T0ZY4XWh7iLP0usgqykT6p9B2RtOf4FPw==
|
||||
dependencies:
|
||||
colorette "^1.2.2"
|
||||
nanoid "^3.1.20"
|
||||
source-map "^0.6.1"
|
||||
|
||||
prelude-ls@~1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
||||
|
@ -3940,6 +4060,13 @@ readdirp@~3.4.0:
|
|||
dependencies:
|
||||
picomatch "^2.2.1"
|
||||
|
||||
readdirp@~3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e"
|
||||
integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==
|
||||
dependencies:
|
||||
picomatch "^2.2.1"
|
||||
|
||||
regex-not@^1.0.0, regex-not@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"
|
||||
|
@ -4094,7 +4221,7 @@ run-queue@^1.0.0, run-queue@^1.0.3:
|
|||
dependencies:
|
||||
aproba "^1.1.1"
|
||||
|
||||
rxjs@^6.6.0:
|
||||
rxjs@^6.6.0, rxjs@^6.6.3:
|
||||
version "6.6.3"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.3.tgz#8ca84635c4daa900c0d3967a6ee7ac60271ee552"
|
||||
integrity sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==
|
||||
|
@ -4123,6 +4250,24 @@ safe-regex@^1.1.0:
|
|||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||
|
||||
sass-loader@10.1.1:
|
||||
version "10.1.1"
|
||||
resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.1.1.tgz#4ddd5a3d7638e7949065dd6e9c7c04037f7e663d"
|
||||
integrity sha512-W6gVDXAd5hR/WHsPicvZdjAWHBcEJ44UahgxcIE196fW2ong0ZHMPO1kZuI5q0VlvMQZh32gpv69PLWQm70qrw==
|
||||
dependencies:
|
||||
klona "^2.0.4"
|
||||
loader-utils "^2.0.0"
|
||||
neo-async "^2.6.2"
|
||||
schema-utils "^3.0.0"
|
||||
semver "^7.3.2"
|
||||
|
||||
sass@^1.32.8:
|
||||
version "1.32.8"
|
||||
resolved "https://registry.yarnpkg.com/sass/-/sass-1.32.8.tgz#f16a9abd8dc530add8834e506878a2808c037bdc"
|
||||
integrity sha512-Sl6mIeGpzjIUZqvKnKETfMf0iDAswD9TNlv13A7aAF3XZlRPMq4VvJWBC2N2DXbp94MQVdNSFG6LfF/iOXrPHQ==
|
||||
dependencies:
|
||||
chokidar ">=2.0.0 <4.0.0"
|
||||
|
||||
schema-utils@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
|
||||
|
@ -4168,6 +4313,13 @@ semver@^7.3.2:
|
|||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
|
||||
integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
|
||||
|
||||
semver@^7.3.4:
|
||||
version "7.3.4"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97"
|
||||
integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
send@0.17.1:
|
||||
version "0.17.1"
|
||||
resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
|
||||
|
@ -4569,11 +4721,6 @@ strip-ansi@^6.0.0:
|
|||
dependencies:
|
||||
ansi-regex "^5.0.0"
|
||||
|
||||
strip-comments@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-comments/-/strip-comments-2.0.1.tgz#4ad11c3fbcac177a67a40ac224ca339ca1c1ba9b"
|
||||
integrity sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==
|
||||
|
||||
strip-eof@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
|
||||
|
@ -4812,6 +4959,11 @@ union-value@^1.0.0:
|
|||
is-extendable "^0.1.1"
|
||||
set-value "^2.0.1"
|
||||
|
||||
uniq@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
|
||||
integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=
|
||||
|
||||
unique-filename@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230"
|
||||
|
@ -4877,7 +5029,7 @@ use@^3.1.0:
|
|||
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
|
||||
integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
|
||||
|
||||
util-deprecate@^1.0.1, util-deprecate@~1.0.1:
|
||||
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
||||
|
@ -5056,7 +5208,7 @@ webpack-require-http@^0.4.3:
|
|||
md5 "^2.0.0"
|
||||
url "^0.11.0"
|
||||
|
||||
webpack-sources@^1.4.0, webpack-sources@^1.4.1:
|
||||
webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933"
|
||||
integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==
|
||||
|
@ -5193,6 +5345,11 @@ yallist@^3.0.2:
|
|||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
|
||||
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
|
||||
|
||||
yallist@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||
|
||||
yargs-parser@^13.1.2:
|
||||
version "13.1.2"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
/node_modules/
|
||||
/dist/bundle.js
|
||||
/yarn-error.log
|
||||
/Dockerfile
|