Merge branch 'master' of github.com:thecodingmachine/workadventure into scaling

# Conflicts:
#	back/src/Services/SocketManager.ts
#	deeployer.libsonnet
#	docker-compose.yaml
#	front/src/Connexion/RoomConnection.ts
#	front/src/Enum/EnvironmentVariable.ts
#	front/src/Phaser/Game/GameScene.ts
#	front/webpack.config.js
#	pusher/src/Controller/IoSocketController.ts
This commit is contained in:
David Négrier 2020-12-11 13:00:11 +01:00
commit 6f2c319785
59 changed files with 7667 additions and 569 deletions

View file

@ -20,7 +20,7 @@ jobs:
# Create a slugified value of the branch
- uses: rlespinasse/github-slug-action@1.1.1
- uses: rlespinasse/github-slug-action@3.1.0
- name: "Build and push front image"
uses: docker/build-push-action@v1

View file

@ -6,8 +6,13 @@ class GaugeManager {
private nbClientsPerRoomGauge: Gauge<string>;
private nbGroupsPerRoomGauge: Gauge<string>;
private nbGroupsPerRoomCounter: Counter<string>;
private nbRoomsGauge: Gauge<string>;
constructor() {
this.nbRoomsGauge = new Gauge({
name: 'workadventure_nb_rooms',
help: 'Number of active rooms'
});
this.nbClientsGauge = new Gauge({
name: 'workadventure_nb_sockets',
help: 'Number of connected sockets',
@ -31,6 +36,13 @@ class GaugeManager {
});
}
incNbRoomGauge(): void {
this.nbRoomsGauge.inc();
}
decNbRoomGauge(): void {
this.nbRoomsGauge.dec();
}
incNbClientPerRoomGauge(roomId: string): void {
this.nbClientsGauge.inc();
this.nbClientsPerRoomGauge.inc({ room: roomId });

View file

@ -320,6 +320,7 @@ export class SocketManager {
room.leave(user);
if (room.isEmpty()) {
this.rooms.delete(room.roomId);
gaugeManager.decNbRoomGauge();
debug('Room is empty. Deleting room "%s"', room.roomId);
}
} finally {
@ -349,6 +350,7 @@ export class SocketManager {
world.tags = data.tags
world.policyType = Number(data.policy_type)
}
gaugeManager.incNbRoomGauge();
this.rooms.set(roomId, world);
}
return Promise.resolve(world)
@ -718,6 +720,7 @@ export class SocketManager {
room.adminLeave(admin);
if (room.isEmpty()) {
this.rooms.delete(room.roomId);
gaugeManager.decNbRoomGauge();
debug('Room is empty. Deleting room "%s"', room.roomId);
}
}

View file

@ -54,6 +54,7 @@
"env": {
"API_URL": "pusher."+url,
"UPLOADER_URL": "uploader."+url,
"ADMIN_URL": "admin."+url,
"JITSI_URL": env.JITSI_URL,
"SECRET_JITSI_KEY": env.SECRET_JITSI_KEY,
"TURN_SERVER": "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443",

View file

@ -28,6 +28,7 @@ services:
NODE_ENV: development
API_URL: pusher.workadventure.localhost
UPLOADER_URL: uploader.workadventure.localhost
ADMIN_URL: admin.workadventure.localhost
STARTUP_COMMAND_1: yarn install
TURN_SERVER: "turn:coturn.workadventu.re:443,turns:coturn.workadventu.re:443"
TURN_USER: workadventure

1
front/.gitignore vendored
View file

@ -1,5 +1,6 @@
/node_modules/
/dist/bundle.js
/dist/tests/
/yarn-error.log
/dist/webpack.config.js
/dist/webpack.config.js.map

View file

@ -72,7 +72,11 @@
</div>
</div>
<div id="cowebsite" class="cowebsite hidden"></div>
<div id="cowebsite" class="cowebsite hidden">
<button class="close-btn" id="cowebsite-close">
<img src="resources/logos/close.svg"/>
</button>
</div>
<div class="audio-playing">
<img src="/resources/logos/megaphone.svg"/>
</div>

77
front/dist/resources/logos/boy.svg vendored Normal file
View file

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 188.149 188.149" style="enable-background:new 0 0 188.149 188.149;" xml:space="preserve">
<g>
<g>
<defs>
<circle id="SVGID_1_" cx="94.075" cy="94.075" r="94.074"/>
</defs>
<use xlink:href="#SVGID_1_" style="overflow:visible;fill:#4AC8EB;"/>
<clipPath id="SVGID_2_">
<use xlink:href="#SVGID_1_" style="overflow:visible;"/>
</clipPath>
<g style="clip-path:url(#SVGID_2_);">
<path style="fill:#A57561;" d="M148.572,197.629v0.01H39.507v-0.01c0-15.124,6.147-28.809,16.09-38.679
c0.463-0.463,0.926-0.905,1.408-1.347c1.43-1.326,2.931-2.57,4.493-3.732c1.028-0.771,2.098-1.491,3.177-2.189
c1.08-0.699,2.19-1.357,3.331-1.975c0.021-0.021,0.042-0.021,0.042-0.021c0.441-0.247,0.873-0.493,1.306-0.761
c1.295-0.761,2.519-1.624,3.69-2.56c4.966-3.948,8.812-9.254,11.001-15.34v-0.011c1.306-3.629,2.016-7.546,2.016-11.639
l16.121,0.072c0,4.04,0.688,7.927,1.964,11.525c2.169,6.117,5.994,11.433,10.96,15.401c0.339,0.277,0.688,0.545,1.038,0.802
c0.483,0.36,0.977,0.71,1.47,1.039c0.37,0.246,0.751,0.493,1.132,0.72c0.421,0.257,0.853,0.514,1.285,0.75
c0.041,0.011,0.071,0.021,0.103,0.041c0.03,0.02,0.062,0.041,0.093,0.061c1.265,0.699,2.498,1.439,3.69,2.221
c0.401,0.258,0.802,0.524,1.192,0.803c0.515,0.37,1.039,0.74,1.543,1.131h0.021C139.966,163.875,148.572,179.729,148.572,197.629
z"/>
<path style="fill:#EB6D4A;" d="M148.572,197.629H39.507c0-15.124,6.147-28.809,16.09-38.679c0.463-0.463,0.926-0.905,1.408-1.347
c1.43-1.326,2.931-2.581,4.493-3.742c1.028-0.762,2.098-1.491,3.177-2.18c1.08-0.699,2.19-1.357,3.331-1.975
c0.021-0.021,0.042-0.021,0.042-0.021c0.441-0.247,0.873-0.493,1.306-0.761c1.295-0.761,2.519-1.624,3.69-2.56
c5.347,5.469,12.79,8.852,21.046,8.852c8.226,0,15.669-3.393,21.016-8.842c0.339,0.277,0.688,0.545,1.038,0.802
c0.483,0.36,0.977,0.71,1.47,1.039c0.37,0.246,0.751,0.493,1.132,0.72c0.421,0.267,0.853,0.514,1.285,0.75
c0.041,0.011,0.071,0.021,0.103,0.041c0.03,0.011,0.062,0.031,0.093,0.052c1.265,0.699,2.498,1.439,3.69,2.23
c0.401,0.258,0.802,0.524,1.192,0.803c0.515,0.37,1.039,0.74,1.543,1.131h0.021C139.966,163.875,148.572,179.729,148.572,197.629
z"/>
<path style="fill:#A57561;" d="M52.183,46.81v34.117c0,28.977,25.437,52.466,41.857,52.466c16.421,0,41.858-23.489,41.858-52.466
V46.81H52.183z"/>
<path style="fill:#141720;" d="M52.183,76.823L52.183,76.823c2.063,0,3.734-1.671,3.734-3.733V49.356h-3.734V76.823z"/>
<path style="fill:#141720;" d="M135.899,76.823L135.899,76.823V49.356h-3.733V73.09
C132.165,75.152,133.836,76.823,135.899,76.823z"/>
<path style="fill:#141720;" d="M135.893,48.33c0,4.884-0.328,6.061-3.734,5.801c-0.137-2.367-17.141-4.296-38.111-4.296
c-20.985,0-37.989,1.929-38.126,4.296c-3.406,0.26-3.734-0.917-3.734-5.801c0-0.479,0.014-0.984,0.027-1.519
c0.52-12.052,7.318-34.582,41.833-34.582c34.5,0,41.299,22.53,41.818,34.582C135.879,47.346,135.893,47.852,135.893,48.33z"/>
</g>
<path style="clip-path:url(#SVGID_2_);fill:#FFFFFF;" d="M115.106,146.119c-3.517,10.826-10.601,21.539-21.036,30.299
c-10.436-8.76-17.509-19.473-21.025-30.299c0.052,0.02,0.113,0.041,0.165,0.061c5.531,4.955,12.852,7.979,20.86,7.979
c8.009,0,15.319-3.013,20.851-7.968C114.982,146.17,115.044,146.14,115.106,146.119z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View file

@ -0,0 +1 @@
<svg height="682pt" viewBox="-21 -47 682.66669 682" width="682pt" xmlns="http://www.w3.org/2000/svg"><path d="m640 86.65625v283.972656c0 48.511719-39.472656 87.988282-87.988281 87.988282h-279.152344l-185.183594 128.863281v-128.863281c-48.375-.164063-87.675781-39.574219-87.675781-87.988282v-283.972656c0-48.515625 39.472656-87.988281 87.988281-87.988281h464.023438c48.515625 0 87.988281 39.472656 87.988281 87.988281zm0 0" fill="#ffdb2d"/><path d="m640 86.65625v283.972656c0 48.511719-39.472656 87.988282-87.988281 87.988282h-232.109375v-459.949219h232.109375c48.515625 0 87.988281 39.472656 87.988281 87.988281zm0 0" fill="#ffaa20"/><g fill="#fff"><path d="m171.296875 131.167969h297.40625v37.5h-297.40625zm0 0"/><path d="m171.296875 211.167969h297.40625v37.5h-297.40625zm0 0"/><path d="m171.296875 291.167969h297.40625v37.5h-297.40625zm0 0"/></g><path d="m319.902344 131.167969h148.800781v37.5h-148.800781zm0 0" fill="#e1e1e3"/><path d="m319.902344 211.167969h148.800781v37.5h-148.800781zm0 0" fill="#e1e1e3"/><path d="m319.902344 291.167969h148.800781v37.5h-148.800781zm0 0" fill="#e1e1e3"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -1,6 +1,14 @@
*{
font-family: 'Open Sans', sans-serif;
}
body{
overflow: hidden;
}
body button:focus,
body img:focus,
body input:focus {
outline: -webkit-focus-ring-color auto 0;
}
body .message-info{
width: 20%;
height: auto;
@ -72,14 +80,16 @@ body .message-info.warning{
#div-myCamVideo {
position: absolute;
right: 0;
bottom: 0;
right: 15px;
bottom: 15px;
border-radius: 15px 15px 15px 15px;
}
video#myCamVideo{
width: 15vw;
-webkit-transform: scaleX(-1);
transform: scaleX(-1);
border-radius: 15px 15px 15px 15px;
/*width: 200px;*/
/*height: 113px;*/
}
@ -274,6 +284,23 @@ body {
#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 {
@ -372,6 +399,7 @@ body {
margin: 2%;
transition: margin-left 0.2s, margin-right 0.2s, margin-bottom 0.2s, margin-top 0.2s, max-height 0.2s, max-width 0.2s;
cursor: pointer;
border-radius: 15px 15px 15px 15px;
}
.sidebar > div:hover {
@ -384,6 +412,7 @@ body {
justify-content: center;
flex-direction: column;
overflow: hidden;
border-radius: 15px;
}
.chat-mode {
@ -435,7 +464,7 @@ body {
max-height: 80%;
top: -80%;
left: 10%;
background: #000000a6;
background: #333333;
z-index: 200;
transition: all 0.1s ease-out;
}
@ -532,7 +561,7 @@ body {
border: 1px solid black;
background-color: #00000000;
color: #ffda01;
border-radius: 10px;
border-radius: 15px;
padding: 10px 30px;
transition: all .2s ease;
}
@ -627,6 +656,7 @@ div.modal-report-user{
left: calc(50% - 400px);
top: 100px;
background-color: #000000ad;
border-radius: 15px;
}
.modal-report-user textarea{
@ -638,6 +668,7 @@ div.modal-report-user{
color: white;
width: calc(100% - 60px);
margin: 30px;
border-radius: 15px;
}
.modal-report-user img{
@ -669,7 +700,7 @@ div.modal-report-user{
border: 1px solid black;
background-color: #00000000;
color: #ffda01;
border-radius: 10px;
border-radius: 15px;
padding: 10px 30px;
transition: all .2s ease;
}
@ -701,3 +732,217 @@ div.modal-report-user{
max-width: calc(800px - 60px); /* size of modal - padding*/
}
/*MESSAGE*/
.discussion{
position: fixed;
left: -300px;
top: 0px;
width: 220px;
height: 100%;
background-color: #333333;
padding: 20px;
transition: all 0.5s ease;
}
.discussion.active{
left: 0;
}
.discussion .active-btn{
display: none;
cursor: pointer;
height: 50px;
width: 50px;
background-color: #2d2d2dba;
position: absolute;
top: calc(50% - 25px);
margin-left: 315px;
border-radius: 50%;
border: none;
transition: all 0.5s ease;
}
.discussion .active-btn.active{
display: block;
}
.discussion .active-btn:hover {
transform: scale(1.1) rotateY(3.142rad);
}
.discussion .active-btn img{
width: 26px;
height: 26px;
margin: 13px 5px;
}
.discussion .close-btn{
position: absolute;
top: 0;
right: 10px;
background: none;
border: none;
cursor: pointer;
}
.discussion .close-btn img{
height: 15px;
right: 15px;
}
.discussion p{
color: white;
font-size: 22px;
padding-left: 10px;
margin: 0;
}
.discussion .participants{
height: 200px;
margin: 10px 0;
}
.discussion .participants .participant{
display: flex;
margin: 5px 10px;
background-color: #ffffff69;
padding: 5px;
border-radius: 15px;
cursor: pointer;
}
.discussion .participants .participant:hover{
background-color: #ffffff;
}
.discussion .participants .participant:hover p{
color: black;
}
.discussion .participants .participant:before {
content: '';
height: 10px;
width: 10px;
background-color: #1e7e34;
position: absolute;
margin-left: 18px;
border-radius: 50%;
margin-top: 18px;
}
.discussion .participants .participant img{
width: 26px;
height: 26px;
}
.discussion .participants .participant p{
font-size: 16px;
margin-left: 10px;
margin-top: 2px;
}
.discussion .participants .participant button.report-btn{
cursor: pointer;
position: absolute;
background-color: #2d2d2dba;
right: 34px;
margin: 0px;
padding: 6px 0px;
border-radius: 15px;
border: none;
color: white;
width: 0px;
overflow: hidden;
transition: all .5s ease;
}
.discussion .participants .participant:hover button.report-btn{
width: 70px;
}
.discussion .messages{
position: absolute;
height: calc(100% - 360px);
overflow-x: hidden;
overflow-y: auto;
max-width: calc(100% - 40px);
width: calc(100% - 40px);
}
.discussion .messages h2{
color: white;
}
.discussion .messages .message{
margin: 5px;
float: right;
text-align: right;
width: 100%;
}
.discussion .messages .message.me{
float: left;
text-align: left;
}
.discussion .messages .message p{
font-size: 12px;
}
.discussion .messages .message p.body{
font-size: 16px;
overflow: hidden;
white-space: pre-wrap;
word-wrap: break-word;
}
.discussion .send-message{
position: absolute;
bottom: 45px;
width: 220px;
height: 26px;
}
.discussion .send-message input{
position: absolute;
width: calc(100% - 10px);
height: 20px;
background-color: #171717;
color: white;
border-radius: 15px;
border: none;
padding: 6px;
}
.discussion .send-message img{
position: absolute;
margin-right: 10px;
width: 20px;
height: 20px;
background-color: #ffffff69;
}
.discussion .send-message img:hover{
background-color: #ffffff;
}
/** Action button **/
div.action{
position: absolute;
width: 100%;
height: auto;
text-align: center;
bottom: 40px;
transition: all .5s ease;
animation: mymove .5s;
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;
}
div.action p.action-body{
padding: 10px;
background-color: #2d2d2dba;
color: #fff;
font-size: 12px;
text-align: center;
max-width: 150px;
margin-left: calc(50% - 75px);
border-radius: 15px;
}
@keyframes mymove {
0% {bottom: 40px;}
50% {bottom: 30px;}
100% {bottom: 40px;}
}

View file

@ -12,12 +12,14 @@ const URL_ROOM_STARTED = '/Floor0/floor0.json';
class ConnectionManager {
private localUser!:LocalUser;
private connexionType?: GameConnexionTypes
/**
* Tries to login to the node server and return the starting map url to be loaded
*/
public async initGameConnexion(): Promise<Room> {
const connexionType = urlManager.getGameConnexionType();
this.connexionType = connexionType;
if(connexionType === GameConnexionTypes.register) {
const organizationMemberToken = urlManager.getOrganizationToken();
const data = await Axios.post(`${API_URL}/register`, {organizationMemberToken}).then(res => res.data);
@ -31,7 +33,7 @@ class ConnectionManager {
const room = new Room(window.location.pathname + window.location.hash);
return Promise.resolve(room);
} else if (connexionType === GameConnexionTypes.anonymous || connexionType === GameConnexionTypes.empty) {
} else if (connexionType === GameConnexionTypes.organization || connexionType === GameConnexionTypes.anonymous || connexionType === GameConnexionTypes.empty) {
const localUser = localUserStore.getLocalUser();
if (localUser && localUser.jwtToken && localUser.uuid && localUser.textures) {
@ -55,18 +57,6 @@ class ConnectionManager {
}
const room = new Room(roomId);
return Promise.resolve(room);
} else if (connexionType == GameConnexionTypes.organization) {
const localUser = localUserStore.getLocalUser();
if (localUser) {
this.localUser = localUser;
await this.verifyToken(localUser.jwtToken);
const room = new Room(window.location.pathname + window.location.hash);
return Promise.resolve(room);
} else {
//todo: find some kind of fallback?
return Promise.reject('Could not find a user in localstorage');
}
}
return Promise.reject('Invalid URL');
@ -116,6 +106,10 @@ class ConnectionManager {
});
});
}
get getConnexionType(){
return this.connexionType;
}
}
export const connectionManager = new ConnectionManager();

View file

@ -6,10 +6,8 @@ export class Room {
public readonly isPublic: boolean;
private mapUrl: string|undefined;
private instance: string|undefined;
public readonly hash: string;
constructor(id: string) {
this.hash = '';
if (id.startsWith('/')) {
id = id.substr(1);
}
@ -24,11 +22,28 @@ export class Room {
const indexOfHash = this.id.indexOf('#');
if (indexOfHash !== -1) {
const idWithHash = this.id;
this.id = this.id.substr(0, indexOfHash);
this.hash = idWithHash.substr(indexOfHash + 1);
}
}
public static getIdFromIdentifier(identifier: string, baseUrl: string, currentInstance: string): {roomId: string, hash: string} {
let roomId = '';
let hash = '';
if (!identifier.startsWith('/_/') && !identifier.startsWith('/@/')) { //relative file link
const absoluteExitSceneUrl = new URL(identifier, baseUrl);
roomId = '_/'+currentInstance+'/'+absoluteExitSceneUrl.hostname + absoluteExitSceneUrl.pathname; //in case of a relative url, we need to create a public roomId
hash = absoluteExitSceneUrl.hash;
hash = hash.substring(1); //remove the leading diese
} else { //absolute room Id
const parts = identifier.split('#');
roomId = parts[0];
roomId = roomId.substring(1); //remove the leading slash
if (parts.length > 1) {
hash = parts[1]
}
}
return {roomId, hash}
}
public async getMapUrl(): Promise<string> {
return new Promise<string>((resolve, reject) => {

View file

@ -1,13 +1,13 @@
const DEBUG_MODE: boolean = process.env.DEBUG_MODE == "true";
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 = API_URL.replace('api', 'admin');
const ADMIN_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.ADMIN_URL || "admin.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 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 = 3;
const RESOLUTION = 2;
const ZOOM_LEVEL = 1/*3/4*/;
const POSITION_DELAY = 200; // Wait 200ms between sending position events
const MAX_EXTRAPOLATION_TIME = 100; // Extrapolate a maximum of 250ms if no new movement is sent by the player

View file

@ -39,20 +39,16 @@ export class GameManager {
public async loadMap(room: Room, scenePlugin: Phaser.Scenes.ScenePlugin): Promise<void> {
const roomID = room.id;
const mapUrl = await room.getMapUrl();
console.log('Loading map '+roomID+' at url '+mapUrl);
const gameIndex = scenePlugin.getIndex(mapUrl);
const gameIndex = scenePlugin.getIndex(roomID);
if(gameIndex === -1){
const game : Phaser.Scene = GameScene.createFromUrl(room, mapUrl);
console.log('Adding scene '+mapUrl);
scenePlugin.add(mapUrl, game, false);
const game : Phaser.Scene = new GameScene(room, mapUrl);
scenePlugin.add(roomID, game, false);
}
}
public async goToStartingMap(scenePlugin: Phaser.Scenes.ScenePlugin) {
const url = await this.startRoom.getMapUrl();
console.log('Starting scene '+url);
scenePlugin.start(url);
public goToStartingMap(scenePlugin: Phaser.Scenes.ScenePlugin): void {
scenePlugin.start(this.startRoom.id);
}
}

View file

@ -31,7 +31,12 @@ import {Queue} from 'queue-typescript';
import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer";
import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene";
import {loadAllLayers, loadObject, loadPlayerCharacters} from "../Entity/body_character";
import {CenterListener, layoutManager, LayoutMode} from "../../WebRtc/LayoutManager";
import {
CenterListener,
layoutManager,
LayoutMode,
ON_ACTION_TRIGGER_BUTTON, TRIGGER_JITSI_PROPERTIES, TRIGGER_WEBSITE_PROPERTIES
} from "../../WebRtc/LayoutManager";
import Texture = Phaser.Textures.Texture;
import Sprite = Phaser.GameObjects.Sprite;
import CanvasTexture = Phaser.Textures.CanvasTexture;
@ -54,6 +59,7 @@ import {ConsoleGlobalMessageManager} from "../../Administration/ConsoleGlobalMes
import {ResizableScene} from "../Login/ResizableScene";
import {Room} from "../../Connexion/Room";
import {jitsiFactory} from "../../WebRtc/JitsiFactory";
import {urlManager} from "../../Url/UrlManager";
export interface GameSceneInitInterface {
initPosition: PointInterface|null,
@ -90,6 +96,8 @@ interface DeleteGroupEventInterface {
groupId: number
}
const defaultStartLayerName = 'start';
export class GameScene extends ResizableScene implements CenterListener {
GameManager : GameManager;
Terrains : Array<Phaser.Tilemaps.Tileset>;
@ -119,7 +127,6 @@ export class GameScene extends ResizableScene implements CenterListener {
private createPromise: Promise<void>;
private createPromiseResolve!: (value?: void | PromiseLike<void>) => void;
MapKey: string;
MapUrlFile: string;
RoomId: string;
instance: string;
@ -133,7 +140,6 @@ export class GameScene extends ResizableScene implements CenterListener {
y: -1000
}
private PositionNextScene: Array<Array<{ key: string, hash: string }>> = new Array<Array<{ key: string, hash: string }>>();
private presentationModeSprite!: Sprite;
private chatModeSprite!: Sprite;
private gameMap!: GameMap;
@ -142,18 +148,11 @@ export class GameScene extends ResizableScene implements CenterListener {
private outlinedItem: ActionableItem|null = null;
private userInputManager!: UserInputManager;
private isReconnecting: boolean = false;
private startLayerName!: string | null;
static createFromUrl(room: Room, mapUrlFile: string, gameSceneKey: string|null = null): GameScene {
// We use the map URL as a key
if (gameSceneKey === null) {
gameSceneKey = mapUrlFile;
}
return new GameScene(room, mapUrlFile, gameSceneKey);
}
constructor(private room: Room, MapUrlFile: string, gameSceneKey: string) {
constructor(private room: Room, MapUrlFile: string) {
super({
key: gameSceneKey
key: room.id
});
this.GameManager = gameManager;
@ -161,7 +160,6 @@ export class GameScene extends ResizableScene implements CenterListener {
this.groups = new Map<number, Sprite>();
this.instance = room.getInstance();
this.MapKey = MapUrlFile;
this.MapUrlFile = MapUrlFile;
this.RoomId = room.id;
@ -180,15 +178,15 @@ export class GameScene extends ResizableScene implements CenterListener {
file: file.src
});
});
this.load.on('filecomplete-tilemapJSON-'+this.MapKey, (key: string, type: string, data: unknown) => {
this.load.on('filecomplete-tilemapJSON-'+this.MapUrlFile, (key: string, type: string, data: unknown) => {
this.onMapLoad(data);
});
//TODO strategy to add access token
this.load.tilemapTiledJSON(this.MapKey, this.MapUrlFile);
this.load.tilemapTiledJSON(this.MapUrlFile, this.MapUrlFile);
// If the map has already been loaded as part of another GameScene, the "on load" event will not be triggered.
// In this case, we check in the cache to see if the map is here and trigger the event manually.
if (this.cache.tilemap.exists(this.MapKey)) {
const data = this.cache.tilemap.get(this.MapKey);
if (this.cache.tilemap.exists(this.MapUrlFile)) {
const data = this.cache.tilemap.get(this.MapUrlFile);
this.onMapLoad(data);
}
@ -299,7 +297,7 @@ export class GameScene extends ResizableScene implements CenterListener {
//hook initialisation
init(initData : GameSceneInitInterface) {
if (initData.initPosition !== undefined) {
this.initPosition = initData.initPosition;
this.initPosition = initData.initPosition; //todo: still used?
}
if (initData.initPosition !== undefined) {
this.isReconnecting = initData.reconnecting;
@ -308,8 +306,11 @@ export class GameScene extends ResizableScene implements CenterListener {
//hook create scene
create(): void {
urlManager.pushRoomIdToUrl(this.room);
this.startLayerName = urlManager.getStartLayerNameFromUrl();
//initalise map
this.Map = this.add.tilemap(this.MapKey);
this.Map = this.add.tilemap(this.MapUrlFile);
this.gameMap = new GameMap(this.mapFile);
const mapDirUrl = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf('/'));
this.mapFile.tilesets.forEach((tileset: ITiledTileSet) => {
@ -319,25 +320,21 @@ export class GameScene extends ResizableScene implements CenterListener {
//permit to set bound collision
this.physics.world.setBounds(0, 0, this.Map.widthInPixels, this.Map.heightInPixels);
// Let's alter browser history
let path = this.room.id;
if (this.room.hash) {
path += '#'+this.room.hash;
}
window.history.pushState({}, 'WorkAdventure', path);
//add layer on map
this.Layers = new Array<Phaser.Tilemaps.StaticTilemapLayer>();
let depth = -2;
for (const layer of this.mapFile.layers) {
if (layer.type === 'tilelayer') {
this.addLayer(this.Map.createStaticLayer(layer.name, this.Terrains, 0, 0).setDepth(depth));
}
if (layer.type === 'tilelayer' && this.getExitSceneUrl(layer) !== undefined) {
this.loadNextGameFromExitSceneUrl(layer, this.mapFile.width);
} else if (layer.type === 'tilelayer' && this.getExitUrl(layer) !== undefined) {
console.log('Loading exitUrl ', this.getExitUrl(layer))
this.loadNextGameFromExitUrl(layer, this.mapFile.width);
const exitSceneUrl = this.getExitSceneUrl(layer);
if (exitSceneUrl !== undefined) {
this.loadNextGame(exitSceneUrl);
}
const exitUrl = this.getExitUrl(layer);
if (exitUrl !== undefined) {
this.loadNextGame(exitUrl);
}
}
if (layer.type === 'objectgroup' && layer.name === 'floorLayer') {
depth = 10000;
@ -347,51 +344,17 @@ export class GameScene extends ResizableScene implements CenterListener {
throw new Error('Your map MUST contain a layer of type "objectgroup" whose name is "floorLayer" that represents the layer characters are drawn at.');
}
// If there is an init position passed
if (this.initPosition !== null) {
this.startX = this.initPosition.x;
this.startY = this.initPosition.y;
} else {
// Now, let's find the start layer
if (this.room.hash) {
for (const layer of this.mapFile.layers) {
if (this.room.hash === layer.name && layer.type === 'tilelayer' && this.isStartLayer(layer)) {
const startPosition = this.startUser(layer);
this.startX = startPosition.x;
this.startY = startPosition.y;
}
}
}
if (this.startX === undefined) {
// If we have no start layer specified or if the hash passed does not exist, let's go with the default start position.
for (const layer of this.mapFile.layers) {
if (layer.type === 'tilelayer' && layer.name === "start") {
const startPosition = this.startUser(layer);
this.startX = startPosition.x;
this.startY = startPosition.y;
}
}
}
}
// Still no start position? Something is wrong with the map, we need a "start" layer.
if (this.startX === undefined) {
console.warn('This map is missing a layer named "start" that contains the available default start positions.');
// Let's start in the middle of the map
this.startX = this.mapFile.width * 16;
this.startY = this.mapFile.height * 16;
}
this.initStartXAndStartY();
//add entities
this.Objects = new Array<Phaser.Physics.Arcade.Sprite>();
//init event click
this.EventToClickOnTile();
//initialise list of other player
this.MapPlayers = this.physics.add.group({immovable: true});
//create input to move
this.userInputManager = new UserInputManager(this);
mediaManager.setUserInputManager(this.userInputManager);
//notify game manager can to create currentUser in map
this.createCurrentPlayer();
@ -476,35 +439,7 @@ export class GameScene extends ResizableScene implements CenterListener {
// From now, this game scene will be notified of reposition events
layoutManager.setListener(this);
this.gameMap.onPropertyChange('openWebsite', (newValue, oldValue) => {
if (newValue === undefined) {
coWebsiteManager.closeCoWebsite();
} else {
coWebsiteManager.loadCoWebsite(newValue as string);
}
});
this.gameMap.onPropertyChange('jitsiRoom', (newValue, oldValue, allProps) => {
if (newValue === undefined) {
this.stopJitsi();
} else {
if (JITSI_PRIVATE_MODE) {
const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined;
this.connection.emitQueryJitsiJwtMessage(this.instance.replace('/', '-') + "-" + newValue, adminTag);
} else {
this.startJitsi(newValue as string);
}
}
})
this.gameMap.onPropertyChange('silent', (newValue, oldValue) => {
if (newValue === undefined || newValue === false || newValue === '') {
this.connection.setSilent(false);
} else {
this.connection.setSilent(true);
}
});
this.triggerOnMapLayerPropertyChange();
const camera = this.cameras.main;
@ -543,7 +478,6 @@ export class GameScene extends ResizableScene implements CenterListener {
if (position === undefined) {
throw new Error('Position missing from UserMovedMessage');
}
//console.log('Received position ', position.getX(), position.getY(), "from user", message.getUserid());
const messageUserMoved: MessageUserMovedInterface = {
userId: message.getUserid(),
@ -576,7 +510,7 @@ export class GameScene extends ResizableScene implements CenterListener {
this.simplePeer.unregister();
const gameSceneKey = 'somekey' + Math.round(Math.random() * 10000);
const game: Phaser.Scene = GameScene.createFromUrl(this.room, this.MapUrlFile, gameSceneKey);
const game: Phaser.Scene = new GameScene(this.room, this.MapUrlFile);
this.scene.add(gameSceneKey, game, true,
{
initPosition: {
@ -607,7 +541,7 @@ 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.simplePeer = new SimplePeer(this.connection, !this.room.isPublic, this.GameManager.getPlayerName());
this.GlobalMessageManager = new GlobalMessageManager(this.connection);
this.UserMessageManager = new UserMessageManager(this.connection);
@ -638,15 +572,103 @@ export class GameScene extends ResizableScene implements CenterListener {
if (this.connection.hasTag('admin')) {
this.ConsoleGlobalMessageManager = new ConsoleGlobalMessageManager(this.connection, this.userInputManager);
}
console.log('wakingup');
this.scene.wake();
this.scene.sleep(ReconnectingSceneName);
//init user position and play trigger to check layers properties
this.gameMap.setPosition(this.CurrentPlayer.x, this.CurrentPlayer.y);
return this.connection;
});
}
private triggerOnMapLayerPropertyChange(){
this.gameMap.onPropertyChange('exitSceneUrl', (newValue, oldValue) => {
if (newValue) this.onMapExit(newValue as string);
});
this.gameMap.onPropertyChange('exitUrl', (newValue, oldValue) => {
if (newValue) this.onMapExit(newValue as string);
});
this.gameMap.onPropertyChange('openWebsite', (newValue, oldValue, allProps) => {
if (newValue === undefined) {
layoutManager.removeActionButton('openWebsite', this.userInputManager);
coWebsiteManager.closeCoWebsite();
}else{
const openWebsiteFunction = () => {
coWebsiteManager.loadCoWebsite(newValue as string);
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', () => {
openWebsiteFunction();
}, this.userInputManager);
}else{
openWebsiteFunction();
}
}
});
this.gameMap.onPropertyChange('jitsiRoom', (newValue, oldValue, allProps) => {
if (newValue === undefined) {
layoutManager.removeActionButton('jitsiRoom', this.userInputManager);
this.stopJitsi();
}else{
const openJitsiRoomFunction = () => {
if (JITSI_PRIVATE_MODE) {
const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined;
this.connection.emitQueryJitsiJwtMessage(this.instance.replace('/', '-') + "-" + newValue, adminTag);
} else {
this.startJitsi(newValue as string);
}
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', () => {
openJitsiRoomFunction();
}, this.userInputManager);
}else{
openJitsiRoomFunction();
}
}
})
this.gameMap.onPropertyChange('silent', (newValue, oldValue) => {
if (newValue === undefined || newValue === false || newValue === '') {
this.connection.setSilent(false);
} else {
this.connection.setSilent(true);
}
});
}
private onMapExit(exitKey: string) {
const {roomId, hash} = Room.getIdFromIdentifier(exitKey, this.MapUrlFile, this.instance);
if (!roomId) throw new Error('Could not find the room from its exit key: '+exitKey);
urlManager.pushStartLayerNameToUrl(hash);
if (roomId !== this.scene.key) {
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
this.connection.closeConnection();
this.simplePeer.unregister();
this.scene.stop();
this.scene.remove(this.scene.key);
this.scene.start(roomId);
} else {
//if the exit points to the current map, we simply teleport the user back to the startLayer
this.initPositionFromLayerName(hash || defaultStartLayerName);
this.CurrentPlayer.x = this.startX;
this.CurrentPlayer.y = this.startY;
}
}
private switchLayoutMode(): void {
//if discussion is activated, this layout cannot be activated
if(mediaManager.activatedDiscussion){
return;
}
const mode = layoutManager.getLayoutMode();
if (mode === LayoutMode.Presentation) {
layoutManager.switchLayoutMode(LayoutMode.VideoChat);
@ -659,18 +681,51 @@ export class GameScene extends ResizableScene implements CenterListener {
}
}
private initStartXAndStartY() {
// If there is an init position passed
if (this.initPosition !== null) {
this.startX = this.initPosition.x;
this.startY = this.initPosition.y;
} else {
// Now, let's find the start layer
if (this.startLayerName) {
this.initPositionFromLayerName(this.startLayerName);
}
if (this.startX === undefined) {
// If we have no start layer specified or if the hash passed does not exist, let's go with the default start position.
this.initPositionFromLayerName(defaultStartLayerName);
}
}
// Still no start position? Something is wrong with the map, we need a "start" layer.
if (this.startX === undefined) {
console.warn('This map is missing a layer named "start" that contains the available default start positions.');
// Let's start in the middle of the map
this.startX = this.mapFile.width * 16;
this.startY = this.mapFile.height * 16;
}
}
private initPositionFromLayerName(layerName: string) {
for (const layer of this.mapFile.layers) {
if (layerName === layer.name && layer.type === 'tilelayer' && (layerName === defaultStartLayerName || this.isStartLayer(layer))) {
const startPosition = this.startUser(layer);
this.startX = startPosition.x;
this.startY = startPosition.y;
}
}
}
private getExitUrl(layer: ITiledMapLayer): string|undefined {
return this.getProperty(layer, "exitUrl") as string|undefined;
}
/**
* @deprecated the map property exitSceneUrl is deprecated
*/
private getExitSceneUrl(layer: ITiledMapLayer): string|undefined {
return this.getProperty(layer, "exitSceneUrl") as string|undefined;
}
private getExitSceneInstance(layer: ITiledMapLayer): string|undefined {
return this.getProperty(layer, "exitInstance") as string|undefined;
}
private isStartLayer(layer: ITiledMapLayer): boolean {
return this.getProperty(layer, "startLayer") == true;
}
@ -680,85 +735,20 @@ export class GameScene extends ResizableScene implements CenterListener {
if (!properties) {
return undefined;
}
const obj = properties.find((property: ITiledMapLayerProperty) => property.name === name);
const obj = properties.find((property: ITiledMapLayerProperty) => property.name.toLowerCase() === name.toLowerCase());
if (obj === undefined) {
return undefined;
}
return obj.value;
}
private loadNextGameFromExitSceneUrl(layer: ITiledMapLayer, mapWidth: number) {
const exitSceneUrl = this.getExitSceneUrl(layer);
if (exitSceneUrl === undefined) {
throw new Error('Layer is not an exit scene layer.');
}
let instance = this.getExitSceneInstance(layer);
if (instance === undefined) {
instance = this.instance;
}
//console.log('existSceneUrl', exitSceneUrl);
//console.log('existSceneInstance', instance);
const absoluteExitSceneUrl = new URL(exitSceneUrl, this.MapUrlFile).href;
const absoluteExitSceneUrlWithoutProtocol = absoluteExitSceneUrl.toString().substr(absoluteExitSceneUrl.toString().indexOf('://')+3);
const roomId = '_/'+instance+'/'+absoluteExitSceneUrlWithoutProtocol;
this.loadNextGame(layer, mapWidth, roomId);
}
private loadNextGameFromExitUrl(layer: ITiledMapLayer, mapWidth: number) {
const exitUrl = this.getExitUrl(layer);
if (exitUrl === undefined) {
throw new Error('Layer is not an exit layer.');
}
const fullPath = new URL(exitUrl, window.location.toString()).pathname;
this.loadNextGame(layer, mapWidth, fullPath);
}
/**
*
* @param layer
* @param mapWidth
*/
//todo: push that into the gameManager
private loadNextGame(layer: ITiledMapLayer, mapWidth: number, roomId: string){
private async loadNextGame(exitSceneIdentifier: string){
const {roomId, hash} = Room.getIdFromIdentifier(exitSceneIdentifier, this.MapUrlFile, this.instance);
const room = new Room(roomId);
gameManager.loadMap(room, this.scene);
const exitSceneKey = roomId;
const tiles : number[] = layer.data as number[];
for (let key=0; key < tiles.length; key++) {
const objectKey = tiles[key];
if(objectKey === 0){
continue;
}
//key + 1 because the start x = 0;
const y : number = parseInt(((key + 1) / mapWidth).toString());
const x : number = key - (y * mapWidth);
let hash = new URL(roomId, this.MapUrlFile).hash;
if (hash) {
hash = hash.substr(1);
}
//push and save switching case
if (this.PositionNextScene[y] === undefined) {
this.PositionNextScene[y] = new Array<{key: string, hash: string}>();
}
room.getMapUrl().then((url: string) => {
this.PositionNextScene[y][x] = {
key: url,
hash
}
})
}
await gameManager.loadMap(room, this.scene);
}
/**
* @param layer
*/
private startUser(layer: ITiledMapLayer): PositionInterface {
const tiles = layer.data;
if (typeof(tiles) === 'string') {
@ -914,22 +904,12 @@ export class GameScene extends ResizableScene implements CenterListener {
});
}
EventToClickOnTile(){
// debug code to get a tile properties by clicking on it
/*this.input.on("pointerdown", (pointer: Phaser.Input.Pointer)=>{
//pixel position toz tile position
const tile = this.Map.getTileAt(this.Map.worldToTileX(pointer.worldX), this.Map.worldToTileY(pointer.worldY));
if(tile){
this.CurrentPlayer.say("Your touch " + tile.layer.name);
}
});*/
}
/**
* @param time
* @param delta The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate.
*/
update(time: number, delta: number) : void {
mediaManager.setLastUpdateScene();
this.currentTick = time;
this.CurrentPlayer.moveUser(delta);
@ -967,27 +947,6 @@ export class GameScene extends ResizableScene implements CenterListener {
}
player.updatePosition(moveEvent);
});
const nextSceneKey = this.checkToExit();
if (nextSceneKey) {
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
this.connection.closeConnection();
this.simplePeer.unregister();
this.scene.stop();
this.scene.remove(this.scene.key);
this.scene.start(nextSceneKey.key);
}
}
private checkToExit(): {key: string, hash: string} | null {
const x = Math.floor(this.CurrentPlayer.x / 32);
const y = Math.floor(this.CurrentPlayer.y / 32);
if (this.PositionNextScene[y] !== undefined && this.PositionNextScene[y][x] !== undefined) {
return this.PositionNextScene[y][x];
} else {
return null;
}
}
/**
@ -1077,11 +1036,6 @@ export class GameScene extends ResizableScene implements CenterListener {
await Promise.all(loadPromises);
player.addTextures(characterLayerList, 1);
//init collision
/*this.physics.add.collider(this.CurrentPlayer, player, (CurrentPlayer: CurrentGamerInterface, MapPlayer: GamerInterface) => {
CurrentPlayer.say("Hello, how are you ? ");
});*/
}
/**
@ -1124,7 +1078,6 @@ export class GameScene extends ResizableScene implements CenterListener {
// We do not update the player position directly (because it is sent only every 200ms).
// Instead we use the PlayersPositionInterpolator that will do a smooth animation over the next 200ms.
const playerMovement = new PlayerMovement({ x: player.x, y: player.y }, this.currentTick, message.position, this.currentTick + POSITION_DELAY);
//console.log('Target position: ', player.x, player.y);
this.playersPositionInterpolator.updatePlayerPosition(player.userId, playerMovement);
}
@ -1173,11 +1126,6 @@ export class GameScene extends ResizableScene implements CenterListener {
/**
* Sends to the server an event emitted by one of the ActionableItems.
*
* @param itemId
* @param eventName
* @param state
* @param parameters
*/
emitActionableEvent(itemId: number, eventName: string, state: unknown, parameters: unknown) {
this.connection.emitActionableEvent(itemId, eventName, state, parameters)</