Merge branch 'develop' into patch-1
3
.gitignore
vendored
|
@ -4,3 +4,6 @@
|
||||||
Vagrantfile
|
Vagrantfile
|
||||||
docker-compose.override.yaml
|
docker-compose.override.yaml
|
||||||
*.DS_Store
|
*.DS_Store
|
||||||
|
maps/yarn.lock
|
||||||
|
maps/dist/computer.js
|
||||||
|
maps/dist/computer.js.map
|
|
@ -49,7 +49,7 @@
|
||||||
"multer": "^1.4.2",
|
"multer": "^1.4.2",
|
||||||
"prom-client": "^12.0.0",
|
"prom-client": "^12.0.0",
|
||||||
"query-string": "^6.13.3",
|
"query-string": "^6.13.3",
|
||||||
"systeminformation": "^4.27.11",
|
"systeminformation": "^4.30.5",
|
||||||
"ts-node-dev": "^1.0.0-pre.44",
|
"ts-node-dev": "^1.0.0-pre.44",
|
||||||
"typescript": "^3.8.3",
|
"typescript": "^3.8.3",
|
||||||
"uWebSockets.js": "uNetworking/uWebSockets.js#v18.5.0",
|
"uWebSockets.js": "uNetworking/uWebSockets.js#v18.5.0",
|
||||||
|
|
|
@ -2345,10 +2345,10 @@ supports-color@^7.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
has-flag "^4.0.0"
|
has-flag "^4.0.0"
|
||||||
|
|
||||||
systeminformation@^4.27.11:
|
systeminformation@^4.30.5:
|
||||||
version "4.27.11"
|
version "4.30.5"
|
||||||
resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-4.27.11.tgz#6dbe96e48091444f80dab6c05ee1901286826b60"
|
resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-4.30.5.tgz#2219c305c8be56a2cfa527a5519c45bc81d4916c"
|
||||||
integrity sha512-U7bigXbOnsB8k1vNHS0Y13RCsRz5/UohiUmND+3mMUL6vfzrpbe/h4ZqewowB+B+tJNnmGFDj08Z8xGfYo45dQ==
|
integrity sha512-aYWs8yttl8ePpr6VOQ/Ak8cznuc9L/NQODVhbOKhInX73ZMLvV2BS86Mzr7LLfmUteVFR36CTDNQgiJgRqq+SQ==
|
||||||
|
|
||||||
table@^5.2.3:
|
table@^5.2.3:
|
||||||
version "5.4.6"
|
version "5.4.6"
|
||||||
|
|
1
front/.gitignore
vendored
|
@ -1,5 +1,6 @@
|
||||||
/node_modules/
|
/node_modules/
|
||||||
/dist/bundle.js
|
/dist/bundle.js
|
||||||
|
/dist/tests/
|
||||||
/yarn-error.log
|
/yarn-error.log
|
||||||
/dist/webpack.config.js
|
/dist/webpack.config.js
|
||||||
/dist/webpack.config.js.map
|
/dist/webpack.config.js.map
|
||||||
|
|
6
front/dist/index.html
vendored
|
@ -72,7 +72,11 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="audio-playing">
|
||||||
<img src="/resources/logos/megaphone.svg"/>
|
<img src="/resources/logos/megaphone.svg"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
BIN
front/dist/resources/customisation/character_accessories/mask.png
vendored
Normal file
After Width: | Height: | Size: 514 B |
BIN
front/dist/resources/customisation/character_accessories/mate_bottle1.png
vendored
Normal file
After Width: | Height: | Size: 632 B |
BIN
front/dist/resources/customisation/character_clothes/black_hoodie.png
vendored
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
front/dist/resources/customisation/character_clothes/engelbert.png
vendored
Normal file
After Width: | Height: | Size: 4.2 KiB |
BIN
front/dist/resources/customisation/character_clothes/pride_shirt.png
vendored
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
front/dist/resources/customisation/character_clothes/white_hoodie.png
vendored
Normal file
After Width: | Height: | Size: 4 KiB |
BIN
front/dist/resources/customisation/character_hats/tinfoil_hat1.png
vendored
Normal file
After Width: | Height: | Size: 548 B |
45
front/dist/resources/style/style.css
vendored
|
@ -284,6 +284,23 @@ body {
|
||||||
#cowebsite.hidden {
|
#cowebsite.hidden {
|
||||||
transform: translateX(100%);
|
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) {
|
@media (max-aspect-ratio: 1/1) {
|
||||||
.game-overlay {
|
.game-overlay {
|
||||||
|
@ -901,3 +918,31 @@ div.modal-report-user{
|
||||||
background-color: #ffffff;
|
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;}
|
||||||
|
}
|
|
@ -12,12 +12,14 @@ const URL_ROOM_STARTED = '/Floor0/floor0.json';
|
||||||
class ConnectionManager {
|
class ConnectionManager {
|
||||||
private localUser!:LocalUser;
|
private localUser!:LocalUser;
|
||||||
|
|
||||||
|
private connexionType?: GameConnexionTypes
|
||||||
/**
|
/**
|
||||||
* Tries to login to the node server and return the starting map url to be loaded
|
* Tries to login to the node server and return the starting map url to be loaded
|
||||||
*/
|
*/
|
||||||
public async initGameConnexion(): Promise<Room> {
|
public async initGameConnexion(): Promise<Room> {
|
||||||
|
|
||||||
const connexionType = urlManager.getGameConnexionType();
|
const connexionType = urlManager.getGameConnexionType();
|
||||||
|
this.connexionType = connexionType;
|
||||||
if(connexionType === GameConnexionTypes.register) {
|
if(connexionType === GameConnexionTypes.register) {
|
||||||
const organizationMemberToken = urlManager.getOrganizationToken();
|
const organizationMemberToken = urlManager.getOrganizationToken();
|
||||||
const data = await Axios.post(`${API_URL}/register`, {organizationMemberToken}).then(res => res.data);
|
const data = await Axios.post(`${API_URL}/register`, {organizationMemberToken}).then(res => res.data);
|
||||||
|
@ -108,6 +110,10 @@ class ConnectionManager {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get getConnexionType(){
|
||||||
|
return this.connexionType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const connectionManager = new ConnectionManager();
|
export const connectionManager = new ConnectionManager();
|
||||||
|
|
|
@ -6,10 +6,8 @@ export class Room {
|
||||||
public readonly isPublic: boolean;
|
public readonly isPublic: boolean;
|
||||||
private mapUrl: string|undefined;
|
private mapUrl: string|undefined;
|
||||||
private instance: string|undefined;
|
private instance: string|undefined;
|
||||||
public readonly hash: string;
|
|
||||||
|
|
||||||
constructor(id: string) {
|
constructor(id: string) {
|
||||||
this.hash = '';
|
|
||||||
if (id.startsWith('/')) {
|
if (id.startsWith('/')) {
|
||||||
id = id.substr(1);
|
id = id.substr(1);
|
||||||
}
|
}
|
||||||
|
@ -24,12 +22,29 @@ export class Room {
|
||||||
|
|
||||||
const indexOfHash = this.id.indexOf('#');
|
const indexOfHash = this.id.indexOf('#');
|
||||||
if (indexOfHash !== -1) {
|
if (indexOfHash !== -1) {
|
||||||
const idWithHash = this.id;
|
|
||||||
this.id = this.id.substr(0, indexOfHash);
|
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> {
|
public async getMapUrl(): Promise<string> {
|
||||||
return new Promise<string>((resolve, reject) => {
|
return new Promise<string>((resolve, reject) => {
|
||||||
if (this.mapUrl !== undefined) {
|
if (this.mapUrl !== undefined) {
|
||||||
|
|
|
@ -52,6 +52,7 @@ export class RoomConnection implements RoomConnection {
|
||||||
private static websocketFactory: null|((url: string)=>any) = null; // eslint-disable-line @typescript-eslint/no-explicit-any
|
private static websocketFactory: null|((url: string)=>any) = null; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
private closed: boolean = false;
|
private closed: boolean = false;
|
||||||
private tags: string[] = [];
|
private tags: string[] = [];
|
||||||
|
private intervalId!: NodeJS.Timeout;
|
||||||
|
|
||||||
public static setWebsocketFactory(websocketFactory: (url: string)=>any): void { // eslint-disable-line @typescript-eslint/no-explicit-any
|
public static setWebsocketFactory(websocketFactory: (url: string)=>any): void { // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
RoomConnection.websocketFactory = websocketFactory;
|
RoomConnection.websocketFactory = websocketFactory;
|
||||||
|
@ -89,9 +90,13 @@ export class RoomConnection implements RoomConnection {
|
||||||
this.socket.onopen = (ev) => {
|
this.socket.onopen = (ev) => {
|
||||||
//we manually ping every 20s to not be logged out by the server, even when the game is in background.
|
//we manually ping every 20s to not be logged out by the server, even when the game is in background.
|
||||||
const pingMessage = new PingMessage();
|
const pingMessage = new PingMessage();
|
||||||
setInterval(() => this.socket.send(pingMessage.serializeBinary().buffer), manualPingDelay);
|
this.intervalId = setInterval(() => this.socket.send(pingMessage.serializeBinary().buffer), manualPingDelay);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.socket.onclose = () => {
|
||||||
|
clearTimeout(this.intervalId);
|
||||||
|
}
|
||||||
|
|
||||||
this.socket.onmessage = (messageEvent) => {
|
this.socket.onmessage = (messageEvent) => {
|
||||||
const arrayBuffer: ArrayBuffer = messageEvent.data;
|
const arrayBuffer: ArrayBuffer = messageEvent.data;
|
||||||
const message = ServerToClientMessage.deserializeBinary(new Uint8Array(arrayBuffer));
|
const message = ServerToClientMessage.deserializeBinary(new Uint8Array(arrayBuffer));
|
||||||
|
|
|
@ -226,6 +226,10 @@ export const CLOTHES_RESOURCES: Array<BodyResourceDescriptionInterface> = [
|
||||||
{name:"clothes_68",img: "resources/customisation/character_clothes/character_clothes67.png"},
|
{name:"clothes_68",img: "resources/customisation/character_clothes/character_clothes67.png"},
|
||||||
{name:"clothes_69",img: "resources/customisation/character_clothes/character_clothes68.png"},
|
{name:"clothes_69",img: "resources/customisation/character_clothes/character_clothes68.png"},
|
||||||
{name:"clothes_70",img: "resources/customisation/character_clothes/character_clothes69.png"},
|
{name:"clothes_70",img: "resources/customisation/character_clothes/character_clothes69.png"},
|
||||||
|
{name:"clothes_pride_shirt",img: "resources/customisation/character_clothes/pride_shirt.png"},
|
||||||
|
{name:"clothes_black_hoodie",img: "resources/customisation/character_clothes/black_hoodie.png"},
|
||||||
|
{name:"clothes_white_hoodie",img: "resources/customisation/character_clothes/white_hoodie.png"},
|
||||||
|
{name:"clothes_engelbert",img: "resources/customisation/character_clothes/engelbert.png"}
|
||||||
];
|
];
|
||||||
|
|
||||||
export const HATS_RESOURCES: Array<BodyResourceDescriptionInterface> = [
|
export const HATS_RESOURCES: Array<BodyResourceDescriptionInterface> = [
|
||||||
|
@ -254,7 +258,8 @@ export const HATS_RESOURCES: Array<BodyResourceDescriptionInterface> = [
|
||||||
{name: "hats_23", img: "resources/customisation/character_hats/character_hats23.png"},
|
{name: "hats_23", img: "resources/customisation/character_hats/character_hats23.png"},
|
||||||
{name: "hats_24", img: "resources/customisation/character_hats/character_hats24.png"},
|
{name: "hats_24", img: "resources/customisation/character_hats/character_hats24.png"},
|
||||||
{name: "hats_25", img: "resources/customisation/character_hats/character_hats25.png"},
|
{name: "hats_25", img: "resources/customisation/character_hats/character_hats25.png"},
|
||||||
{name: "hats_26", img: "resources/customisation/character_hats/character_hats26.png"}
|
{name: "hats_26", img: "resources/customisation/character_hats/character_hats26.png"},
|
||||||
|
{name: "tinfoil_hat1", img: "resources/customisation/character_hats/tinfoil_hat1.png"}
|
||||||
];
|
];
|
||||||
|
|
||||||
export const ACCESSORIES_RESOURCES: Array<BodyResourceDescriptionInterface> = [
|
export const ACCESSORIES_RESOURCES: Array<BodyResourceDescriptionInterface> = [
|
||||||
|
@ -289,7 +294,9 @@ export const ACCESSORIES_RESOURCES: Array<BodyResourceDescriptionInterface> = [
|
||||||
{name: "accessory_29", img: "resources/customisation/character_accessories/character_accessories29.png"},
|
{name: "accessory_29", img: "resources/customisation/character_accessories/character_accessories29.png"},
|
||||||
{name: "accessory_30", img: "resources/customisation/character_accessories/character_accessories30.png"},
|
{name: "accessory_30", img: "resources/customisation/character_accessories/character_accessories30.png"},
|
||||||
{name: "accessory_31", img: "resources/customisation/character_accessories/character_accessories31.png"},
|
{name: "accessory_31", img: "resources/customisation/character_accessories/character_accessories31.png"},
|
||||||
{name: "accessory_32", img: "resources/customisation/character_accessories/character_accessories32.png"}
|
{name: "accessory_32", img: "resources/customisation/character_accessories/character_accessories32.png"},
|
||||||
|
{name: "accessory_mate_bottle", img: "resources/customisation/character_accessories/mate_bottle1.png"},
|
||||||
|
{name: "accessory_mask", img: "resources/customisation/character_accessories/mask.png"}
|
||||||
];
|
];
|
||||||
|
|
||||||
export const LAYERS: Array<Array<BodyResourceDescriptionInterface>> = [
|
export const LAYERS: Array<Array<BodyResourceDescriptionInterface>> = [
|
||||||
|
|
|
@ -39,20 +39,16 @@ export class GameManager {
|
||||||
public async loadMap(room: Room, scenePlugin: Phaser.Scenes.ScenePlugin): Promise<void> {
|
public async loadMap(room: Room, scenePlugin: Phaser.Scenes.ScenePlugin): Promise<void> {
|
||||||
const roomID = room.id;
|
const roomID = room.id;
|
||||||
const mapUrl = await room.getMapUrl();
|
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){
|
if(gameIndex === -1){
|
||||||
const game : Phaser.Scene = GameScene.createFromUrl(room, mapUrl);
|
const game : Phaser.Scene = new GameScene(room, mapUrl);
|
||||||
console.log('Adding scene '+mapUrl);
|
scenePlugin.add(roomID, game, false);
|
||||||
scenePlugin.add(mapUrl, game, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async goToStartingMap(scenePlugin: Phaser.Scenes.ScenePlugin) {
|
public goToStartingMap(scenePlugin: Phaser.Scenes.ScenePlugin): void {
|
||||||
const url = await this.startRoom.getMapUrl();
|
scenePlugin.start(this.startRoom.id);
|
||||||
console.log('Starting scene '+url);
|
|
||||||
scenePlugin.start(url);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,12 @@ import {Queue} from 'queue-typescript';
|
||||||
import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer";
|
import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer";
|
||||||
import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene";
|
import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene";
|
||||||
import {loadAllLayers, loadObject, loadPlayerCharacters} from "../Entity/body_character";
|
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 Texture = Phaser.Textures.Texture;
|
||||||
import Sprite = Phaser.GameObjects.Sprite;
|
import Sprite = Phaser.GameObjects.Sprite;
|
||||||
import CanvasTexture = Phaser.Textures.CanvasTexture;
|
import CanvasTexture = Phaser.Textures.CanvasTexture;
|
||||||
|
@ -54,6 +59,7 @@ import {ConsoleGlobalMessageManager} from "../../Administration/ConsoleGlobalMes
|
||||||
import {ResizableScene} from "../Login/ResizableScene";
|
import {ResizableScene} from "../Login/ResizableScene";
|
||||||
import {Room} from "../../Connexion/Room";
|
import {Room} from "../../Connexion/Room";
|
||||||
import {jitsiFactory} from "../../WebRtc/JitsiFactory";
|
import {jitsiFactory} from "../../WebRtc/JitsiFactory";
|
||||||
|
import {urlManager} from "../../Url/UrlManager";
|
||||||
|
|
||||||
export interface GameSceneInitInterface {
|
export interface GameSceneInitInterface {
|
||||||
initPosition: PointInterface|null
|
initPosition: PointInterface|null
|
||||||
|
@ -89,6 +95,8 @@ interface DeleteGroupEventInterface {
|
||||||
groupId: number
|
groupId: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultStartLayerName = 'start';
|
||||||
|
|
||||||
export class GameScene extends ResizableScene implements CenterListener {
|
export class GameScene extends ResizableScene implements CenterListener {
|
||||||
GameManager : GameManager;
|
GameManager : GameManager;
|
||||||
Terrains : Array<Phaser.Tilemaps.Tileset>;
|
Terrains : Array<Phaser.Tilemaps.Tileset>;
|
||||||
|
@ -118,7 +126,6 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||||
private createPromise: Promise<void>;
|
private createPromise: Promise<void>;
|
||||||
private createPromiseResolve!: (value?: void | PromiseLike<void>) => void;
|
private createPromiseResolve!: (value?: void | PromiseLike<void>) => void;
|
||||||
|
|
||||||
MapKey: string;
|
|
||||||
MapUrlFile: string;
|
MapUrlFile: string;
|
||||||
RoomId: string;
|
RoomId: string;
|
||||||
instance: string;
|
instance: string;
|
||||||
|
@ -132,7 +139,6 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||||
y: -1000
|
y: -1000
|
||||||
}
|
}
|
||||||
|
|
||||||
private PositionNextScene: Array<Array<{ key: string, hash: string }>> = new Array<Array<{ key: string, hash: string }>>();
|
|
||||||
private presentationModeSprite!: Sprite;
|
private presentationModeSprite!: Sprite;
|
||||||
private chatModeSprite!: Sprite;
|
private chatModeSprite!: Sprite;
|
||||||
private gameMap!: GameMap;
|
private gameMap!: GameMap;
|
||||||
|
@ -140,18 +146,11 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||||
// The item that can be selected by pressing the space key.
|
// The item that can be selected by pressing the space key.
|
||||||
private outlinedItem: ActionableItem|null = null;
|
private outlinedItem: ActionableItem|null = null;
|
||||||
private userInputManager!: UserInputManager;
|
private userInputManager!: UserInputManager;
|
||||||
|
private startLayerName!: string | null;
|
||||||
|
|
||||||
static createFromUrl(room: Room, mapUrlFile: string, gameSceneKey: string|null = null): GameScene {
|
constructor(private room: Room, MapUrlFile: string) {
|
||||||
// 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) {
|
|
||||||
super({
|
super({
|
||||||
key: gameSceneKey
|
key: room.id
|
||||||
});
|
});
|
||||||
|
|
||||||
this.GameManager = gameManager;
|
this.GameManager = gameManager;
|
||||||
|
@ -159,7 +158,6 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||||
this.groups = new Map<number, Sprite>();
|
this.groups = new Map<number, Sprite>();
|
||||||
this.instance = room.getInstance();
|
this.instance = room.getInstance();
|
||||||
|
|
||||||
this.MapKey = MapUrlFile;
|
|
||||||
this.MapUrlFile = MapUrlFile;
|
this.MapUrlFile = MapUrlFile;
|
||||||
this.RoomId = room.id;
|
this.RoomId = room.id;
|
||||||
|
|
||||||
|
@ -179,15 +177,15 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this.load.scenePlugin('AnimatedTiles', 'resources/plugins/AnimatedTiles.js', 'animatedTiles', 'animatedTiles');
|
this.load.scenePlugin('AnimatedTiles', 'resources/plugins/AnimatedTiles.js', 'animatedTiles', 'animatedTiles');
|
||||||
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);
|
this.onMapLoad(data);
|
||||||
});
|
});
|
||||||
//TODO strategy to add access token
|
//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.
|
// If the map has already been loaded as part of another GameScene, the "on load" event will not be triggered.
|
||||||
// In this case, we check in the cache to see if the map is here and trigger the event manually.
|
// In this case, we check in the cache to see if the map is here and trigger the event manually.
|
||||||
if (this.cache.tilemap.exists(this.MapKey)) {
|
if (this.cache.tilemap.exists(this.MapUrlFile)) {
|
||||||
const data = this.cache.tilemap.get(this.MapKey);
|
const data = this.cache.tilemap.get(this.MapUrlFile);
|
||||||
this.onMapLoad(data);
|
this.onMapLoad(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -298,14 +296,17 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||||
//hook initialisation
|
//hook initialisation
|
||||||
init(initData : GameSceneInitInterface) {
|
init(initData : GameSceneInitInterface) {
|
||||||
if (initData.initPosition !== undefined) {
|
if (initData.initPosition !== undefined) {
|
||||||
this.initPosition = initData.initPosition;
|
this.initPosition = initData.initPosition; //todo: still used?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//hook create scene
|
//hook create scene
|
||||||
create(): void {
|
create(): void {
|
||||||
|
urlManager.pushRoomIdToUrl(this.room);
|
||||||
|
this.startLayerName = urlManager.getStartLayerNameFromUrl();
|
||||||
|
|
||||||
//initalise map
|
//initalise map
|
||||||
this.Map = this.add.tilemap(this.MapKey);
|
this.Map = this.add.tilemap(this.MapUrlFile);
|
||||||
this.gameMap = new GameMap(this.mapFile);
|
this.gameMap = new GameMap(this.mapFile);
|
||||||
const mapDirUrl = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf('/'));
|
const mapDirUrl = this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf('/'));
|
||||||
this.mapFile.tilesets.forEach((tileset: ITiledTileSet) => {
|
this.mapFile.tilesets.forEach((tileset: ITiledTileSet) => {
|
||||||
|
@ -315,25 +316,21 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||||
//permit to set bound collision
|
//permit to set bound collision
|
||||||
this.physics.world.setBounds(0, 0, this.Map.widthInPixels, this.Map.heightInPixels);
|
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
|
//add layer on map
|
||||||
this.Layers = new Array<Phaser.Tilemaps.DynamicTilemapLayer>();
|
this.Layers = new Array<Phaser.Tilemaps.DynamicTilemapLayer>();
|
||||||
let depth = -2;
|
let depth = -2;
|
||||||
for (const layer of this.mapFile.layers) {
|
for (const layer of this.mapFile.layers) {
|
||||||
if (layer.type === 'tilelayer') {
|
if (layer.type === 'tilelayer') {
|
||||||
this.addLayer(this.Map.createDynamicLayer(layer.name, this.Terrains, 0, 0).setDepth(depth));
|
this.addLayer(this.Map.createDynamicLayer(layer.name, this.Terrains, 0, 0).setDepth(depth));
|
||||||
|
|
||||||
|
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 === '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);
|
|
||||||
}
|
}
|
||||||
if (layer.type === 'objectgroup' && layer.name === 'floorLayer') {
|
if (layer.type === 'objectgroup' && layer.name === 'floorLayer') {
|
||||||
depth = 10000;
|
depth = 10000;
|
||||||
|
@ -480,7 +477,6 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||||
if (position === undefined) {
|
if (position === undefined) {
|
||||||
throw new Error('Position missing from UserMovedMessage');
|
throw new Error('Position missing from UserMovedMessage');
|
||||||
}
|
}
|
||||||
//console.log('Received position ', position.getX(), position.getY(), "from user", message.getUserid());
|
|
||||||
|
|
||||||
const messageUserMoved: MessageUserMovedInterface = {
|
const messageUserMoved: MessageUserMovedInterface = {
|
||||||
userId: message.getUserid(),
|
userId: message.getUserid(),
|
||||||
|
@ -513,7 +509,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||||
this.simplePeer.unregister();
|
this.simplePeer.unregister();
|
||||||
|
|
||||||
const gameSceneKey = 'somekey' + Math.round(Math.random() * 10000);
|
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,
|
this.scene.add(gameSceneKey, game, true,
|
||||||
{
|
{
|
||||||
initPosition: {
|
initPosition: {
|
||||||
|
@ -579,17 +575,38 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
private triggerOnMapLayerPropertyChange(){
|
private triggerOnMapLayerPropertyChange(){
|
||||||
this.gameMap.onPropertyChange('openWebsite', (newValue, oldValue) => {
|
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) {
|
if (newValue === undefined) {
|
||||||
|
layoutManager.removeActionButton('openWebsite', this.userInputManager);
|
||||||
coWebsiteManager.closeCoWebsite();
|
coWebsiteManager.closeCoWebsite();
|
||||||
} else {
|
}else{
|
||||||
|
const openWebsiteFunction = () => {
|
||||||
coWebsiteManager.loadCoWebsite(newValue as string);
|
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) => {
|
this.gameMap.onPropertyChange('jitsiRoom', (newValue, oldValue, allProps) => {
|
||||||
if (newValue === undefined) {
|
if (newValue === undefined) {
|
||||||
|
layoutManager.removeActionButton('jitsiRoom', this.userInputManager);
|
||||||
this.stopJitsi();
|
this.stopJitsi();
|
||||||
} else {
|
}else{
|
||||||
|
const openJitsiRoomFunction = () => {
|
||||||
if (JITSI_PRIVATE_MODE) {
|
if (JITSI_PRIVATE_MODE) {
|
||||||
const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined;
|
const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined;
|
||||||
|
|
||||||
|
@ -597,6 +614,17 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||||
} else {
|
} else {
|
||||||
this.startJitsi(newValue as string);
|
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) => {
|
this.gameMap.onPropertyChange('silent', (newValue, oldValue) => {
|
||||||
|
@ -608,6 +636,25 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
private switchLayoutMode(): void {
|
||||||
//if discussion is activated, this layout cannot be activated
|
//if discussion is activated, this layout cannot be activated
|
||||||
if(mediaManager.activatedDiscussion){
|
if(mediaManager.activatedDiscussion){
|
||||||
|
@ -632,12 +679,12 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||||
this.startY = this.initPosition.y;
|
this.startY = this.initPosition.y;
|
||||||
} else {
|
} else {
|
||||||
// Now, let's find the start layer
|
// Now, let's find the start layer
|
||||||
if (this.room.hash) {
|
if (this.startLayerName) {
|
||||||
this.initPositionFromLayerName(this.room.hash);
|
this.initPositionFromLayerName(this.startLayerName);
|
||||||
}
|
}
|
||||||
if (this.startX === undefined) {
|
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.
|
// 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("start");
|
this.initPositionFromLayerName(defaultStartLayerName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Still no start position? Something is wrong with the map, we need a "start" layer.
|
// Still no start position? Something is wrong with the map, we need a "start" layer.
|
||||||
|
@ -651,7 +698,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||||
|
|
||||||
private initPositionFromLayerName(layerName: string) {
|
private initPositionFromLayerName(layerName: string) {
|
||||||
for (const layer of this.mapFile.layers) {
|
for (const layer of this.mapFile.layers) {
|
||||||
if (layerName === layer.name && layer.type === 'tilelayer' && (layerName === "start" || this.isStartLayer(layer))) {
|
if (layerName === layer.name && layer.type === 'tilelayer' && (layerName === defaultStartLayerName || this.isStartLayer(layer))) {
|
||||||
const startPosition = this.startUser(layer);
|
const startPosition = this.startUser(layer);
|
||||||
this.startX = startPosition.x;
|
this.startX = startPosition.x;
|
||||||
this.startY = startPosition.y;
|
this.startY = startPosition.y;
|
||||||
|
@ -663,14 +710,13 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||||
return this.getProperty(layer, "exitUrl") as string|undefined;
|
return this.getProperty(layer, "exitUrl") as string|undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated the map property exitSceneUrl is deprecated
|
||||||
|
*/
|
||||||
private getExitSceneUrl(layer: ITiledMapLayer): string|undefined {
|
private getExitSceneUrl(layer: ITiledMapLayer): string|undefined {
|
||||||
return this.getProperty(layer, "exitSceneUrl") as string|undefined;
|
return this.getProperty(layer, "exitSceneUrl") as string|undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getExitSceneInstance(layer: ITiledMapLayer): string|undefined {
|
|
||||||
return this.getProperty(layer, "exitInstance") as string|undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
private isStartLayer(layer: ITiledMapLayer): boolean {
|
private isStartLayer(layer: ITiledMapLayer): boolean {
|
||||||
return this.getProperty(layer, "startLayer") == true;
|
return this.getProperty(layer, "startLayer") == true;
|
||||||
}
|
}
|
||||||
|
@ -680,73 +726,18 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||||
if (!properties) {
|
if (!properties) {
|
||||||
return undefined;
|
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) {
|
if (obj === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
return obj.value;
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
//todo: push that into the gameManager
|
//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);
|
const room = new Room(roomId);
|
||||||
gameManager.loadMap(room, this.scene);
|
await 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
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private startUser(layer: ITiledMapLayer): PositionInterface {
|
private startUser(layer: ITiledMapLayer): PositionInterface {
|
||||||
|
@ -909,6 +900,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||||
* @param delta The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate.
|
* @param delta The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate.
|
||||||
*/
|
*/
|
||||||
update(time: number, delta: number) : void {
|
update(time: number, delta: number) : void {
|
||||||
|
mediaManager.setLastUpdateScene();
|
||||||
this.currentTick = time;
|
this.currentTick = time;
|
||||||
this.CurrentPlayer.moveUser(delta);
|
this.CurrentPlayer.moveUser(delta);
|
||||||
|
|
||||||
|
@ -946,33 +938,6 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||||
}
|
}
|
||||||
player.updatePosition(moveEvent);
|
player.updatePosition(moveEvent);
|
||||||
});
|
});
|
||||||
|
|
||||||
const nextSceneKey = this.checkToExit();
|
|
||||||
if (!nextSceneKey) return;
|
|
||||||
if (nextSceneKey.key !== 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(nextSceneKey.key);
|
|
||||||
} else {
|
|
||||||
//if the exit points to the current map, we simply teleport the user back to the startLayer
|
|
||||||
this.initPositionFromLayerName(this.room.hash || 'start');
|
|
||||||
this.CurrentPlayer.x = this.startX;
|
|
||||||
this.CurrentPlayer.y = this.startY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1204,12 +1169,19 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||||
jitsiFactory.start(roomName, gameManager.getPlayerName(), jwt);
|
jitsiFactory.start(roomName, gameManager.getPlayerName(), jwt);
|
||||||
this.connection.setSilent(true);
|
this.connection.setSilent(true);
|
||||||
mediaManager.hideGameOverlay();
|
mediaManager.hideGameOverlay();
|
||||||
|
|
||||||
|
//permit to stop jitsi when user close iframe
|
||||||
|
mediaManager.addTriggerCloseJitsiFrameButton('close-jisi',() => {
|
||||||
|
this.stopJitsi();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public stopJitsi(): void {
|
public stopJitsi(): void {
|
||||||
this.connection.setSilent(false);
|
this.connection.setSilent(false);
|
||||||
jitsiFactory.stop();
|
jitsiFactory.stop();
|
||||||
mediaManager.showGameOverlay();
|
mediaManager.showGameOverlay();
|
||||||
|
|
||||||
|
mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi');
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadSpritesheet(name: string, url: string): Promise<void> {
|
private loadSpritesheet(name: string, url: string): Promise<void> {
|
||||||
|
|
|
@ -79,4 +79,11 @@ export class UserInputManager {
|
||||||
return event;
|
return event;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addSpaceEventListner(callback : Function){
|
||||||
|
this.Scene.input.keyboard.addListener('keyup-SPACE', callback);
|
||||||
|
}
|
||||||
|
removeSpaceEventListner(callback : Function){
|
||||||
|
this.Scene.input.keyboard.removeListener('keyup-SPACE', callback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import {Room} from "../Connexion/Room";
|
||||||
|
|
||||||
export enum GameConnexionTypes {
|
export enum GameConnexionTypes {
|
||||||
anonymous=1,
|
anonymous=1,
|
||||||
|
@ -45,6 +46,20 @@ class UrlManager {
|
||||||
return newUrl;
|
return newUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public pushRoomIdToUrl(room:Room): void {
|
||||||
|
if (window.location.pathname === room.id) return;
|
||||||
|
const hash = window.location.hash;
|
||||||
|
history.pushState({}, 'WorkAdventure', room.id+hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getStartLayerNameFromUrl(): string|null {
|
||||||
|
const hash = window.location.hash;
|
||||||
|
return hash.length > 1 ? hash.substring(1) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pushStartLayerNameToUrl(startLayerName: string): void {
|
||||||
|
window.location.hash = startLayerName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const urlManager = new UrlManager();
|
export const urlManager = new UrlManager();
|
||||||
|
|
|
@ -44,7 +44,9 @@ class CoWebsiteManager {
|
||||||
|
|
||||||
public loadCoWebsite(url: string): void {
|
public loadCoWebsite(url: string): void {
|
||||||
this.load();
|
this.load();
|
||||||
this.cowebsiteDiv.innerHTML = '';
|
this.cowebsiteDiv.innerHTML = `<button class="close-btn" id="cowebsite-close">
|
||||||
|
<img src="resources/logos/close.svg">
|
||||||
|
</button>`;
|
||||||
|
|
||||||
const iframe = document.createElement('iframe');
|
const iframe = document.createElement('iframe');
|
||||||
iframe.id = 'cowebsite-iframe';
|
iframe.id = 'cowebsite-iframe';
|
||||||
|
@ -83,7 +85,9 @@ class CoWebsiteManager {
|
||||||
this.close();
|
this.close();
|
||||||
this.fire();
|
this.fire();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.cowebsiteDiv.innerHTML = '';
|
this.cowebsiteDiv.innerHTML = `<button class="close-btn" id="cowebsite-close">
|
||||||
|
<img src="resources/logos/close.svg">
|
||||||
|
</button>`;
|
||||||
resolve();
|
resolve();
|
||||||
}, animationTime)
|
}, animationTime)
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import {HtmlUtils} from "./HtmlUtils";
|
import {HtmlUtils} from "./HtmlUtils";
|
||||||
import {MediaManager, ReportCallback, UpdatedLocalStreamCallback} from "./MediaManager";
|
import {MediaManager, ReportCallback, UpdatedLocalStreamCallback} from "./MediaManager";
|
||||||
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||||
|
import {connectionManager} from "../Connexion/ConnectionManager";
|
||||||
|
import {GameConnexionTypes} from "../Url/UrlManager";
|
||||||
|
|
||||||
export type SendMessageCallback = (message:string) => void;
|
export type SendMessageCallback = (message:string) => void;
|
||||||
|
|
||||||
export class DiscussionManager {
|
export class DiscussionManager {
|
||||||
|
@ -115,7 +118,11 @@ export class DiscussionManager {
|
||||||
divParticipant.appendChild(divImgParticipant);
|
divParticipant.appendChild(divImgParticipant);
|
||||||
divParticipant.appendChild(divPParticipant);
|
divParticipant.appendChild(divPParticipant);
|
||||||
|
|
||||||
if(!isMe) {
|
if(
|
||||||
|
!isMe
|
||||||
|
&& connectionManager.getConnexionType
|
||||||
|
&& connectionManager.getConnexionType !== GameConnexionTypes.anonymous
|
||||||
|
) {
|
||||||
const reportBanUserAction: HTMLButtonElement = document.createElement('button');
|
const reportBanUserAction: HTMLButtonElement = document.createElement('button');
|
||||||
reportBanUserAction.classList.add('report-btn')
|
reportBanUserAction.classList.add('report-btn')
|
||||||
reportBanUserAction.innerText = 'Report';
|
reportBanUserAction.innerText = 'Report';
|
||||||
|
|
|
@ -63,6 +63,9 @@ class JitsiFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async stop(): Promise<void> {
|
public async stop(): Promise<void> {
|
||||||
|
if(!this.jitsiApi){
|
||||||
|
return;
|
||||||
|
}
|
||||||
await coWebsiteManager.closeCoWebsite();
|
await coWebsiteManager.closeCoWebsite();
|
||||||
this.jitsiApi.removeListener('audioMuteStatusChanged', this.audioCallback);
|
this.jitsiApi.removeListener('audioMuteStatusChanged', this.audioCallback);
|
||||||
this.jitsiApi.removeListener('videoMuteStatusChanged', this.videoCallback);
|
this.jitsiApi.removeListener('videoMuteStatusChanged', this.videoCallback);
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { UserInputManager } from "../Phaser/UserInput/UserInputManager";
|
||||||
import {HtmlUtils} from "./HtmlUtils";
|
import {HtmlUtils} from "./HtmlUtils";
|
||||||
|
|
||||||
export enum LayoutMode {
|
export enum LayoutMode {
|
||||||
|
@ -22,6 +23,10 @@ export interface CenterListener {
|
||||||
onCenterChange(): void;
|
onCenterChange(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ON_ACTION_TRIGGER_BUTTON = 'onaction';
|
||||||
|
export const TRIGGER_WEBSITE_PROPERTIES = 'openWebsiteTrigger';
|
||||||
|
export const TRIGGER_JITSI_PROPERTIES = 'jitsiTrigger';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is in charge of the video-conference layout.
|
* 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.
|
* It receives positioning requests for videos and does its best to place them on the screen depending on the active layout mode.
|
||||||
|
@ -33,6 +38,9 @@ class LayoutManager {
|
||||||
private normalDivs: Map<string, HTMLDivElement> = new Map<string, HTMLDivElement>();
|
private normalDivs: Map<string, HTMLDivElement> = new Map<string, HTMLDivElement>();
|
||||||
private listener: CenterListener|null = null;
|
private listener: CenterListener|null = null;
|
||||||
|
|
||||||
|
private actionButtonTrigger: Map<string, Function> = new Map<string, Function>();
|
||||||
|
private actionButtonInformation: Map<string, HTMLDivElement> = new Map<string, HTMLDivElement>();
|
||||||
|
|
||||||
public setListener(centerListener: CenterListener|null) {
|
public setListener(centerListener: CenterListener|null) {
|
||||||
this.listener = centerListener;
|
this.listener = centerListener;
|
||||||
}
|
}
|
||||||
|
@ -305,6 +313,48 @@ class LayoutManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public addActionButton(id: string, text: string, callBack: Function, userInputManager: UserInputManager){
|
||||||
|
//delete previous element
|
||||||
|
this.removeActionButton(id, userInputManager);
|
||||||
|
|
||||||
|
//create div and text html component
|
||||||
|
const p = document.createElement('p');
|
||||||
|
p.classList.add('action-body');
|
||||||
|
p.innerText = text;
|
||||||
|
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.classList.add('action');
|
||||||
|
div.id = id;
|
||||||
|
div.appendChild(p);
|
||||||
|
|
||||||
|
this.actionButtonInformation.set(id, div);
|
||||||
|
|
||||||
|
const mainContainer = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
|
||||||
|
mainContainer.appendChild(div);
|
||||||
|
|
||||||
|
const callBackFunctionTrigger = (() => {
|
||||||
|
console.log('user click on space => ', id);
|
||||||
|
callBack();
|
||||||
|
});
|
||||||
|
|
||||||
|
//add trigger action
|
||||||
|
this.actionButtonTrigger.set(id, callBackFunctionTrigger);
|
||||||
|
userInputManager.addSpaceEventListner(callBackFunctionTrigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeActionButton(id: string, userInputManager: UserInputManager){
|
||||||
|
//delete previous element
|
||||||
|
const previousDiv = this.actionButtonInformation.get(id);
|
||||||
|
if(previousDiv){
|
||||||
|
previousDiv.remove();
|
||||||
|
this.actionButtonInformation.delete(id);
|
||||||
|
}
|
||||||
|
const previousEventCallback = this.actionButtonTrigger.get(id);
|
||||||
|
if(previousEventCallback){
|
||||||
|
userInputManager.removeSpaceEventListner(previousEventCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const layoutManager = new LayoutManager();
|
const layoutManager = new LayoutManager();
|
||||||
|
|
|
@ -40,12 +40,20 @@ export class MediaManager {
|
||||||
private cinemaBtn: HTMLDivElement;
|
private cinemaBtn: HTMLDivElement;
|
||||||
private monitorBtn: HTMLDivElement;
|
private monitorBtn: HTMLDivElement;
|
||||||
|
|
||||||
|
private previousConstraint : MediaStreamConstraints;
|
||||||
|
private focused : boolean = true;
|
||||||
|
|
||||||
|
private lastUpdateScene : Date = new Date();
|
||||||
|
private setTimeOutlastUpdateScene? : NodeJS.Timeout;
|
||||||
|
|
||||||
private discussionManager: DiscussionManager;
|
private discussionManager: DiscussionManager;
|
||||||
|
|
||||||
private userInputManager?: UserInputManager;
|
private userInputManager?: UserInputManager;
|
||||||
|
|
||||||
private hasCamera = true;
|
private hasCamera = true;
|
||||||
|
|
||||||
|
private triggerCloseJistiFrame : Map<String, Function> = new Map<String, Function>();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
||||||
this.myCamVideo = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideo');
|
this.myCamVideo = HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('myCamVideo');
|
||||||
|
@ -98,9 +106,35 @@ export class MediaManager {
|
||||||
//update tracking
|
//update tracking
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.previousConstraint = JSON.parse(JSON.stringify(this.constraintsMedia));
|
||||||
|
this.pingCameraStatus();
|
||||||
|
|
||||||
|
this.checkActiveUser(); //todo: desactivated in case of bug
|
||||||
|
|
||||||
this.discussionManager = new DiscussionManager(this,'');
|
this.discussionManager = new DiscussionManager(this,'');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setLastUpdateScene(){
|
||||||
|
this.lastUpdateScene = new Date();
|
||||||
|
}
|
||||||
|
|
||||||
|
public blurCamera() {
|
||||||
|
if(!this.focused){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.focused = false;
|
||||||
|
this.previousConstraint = JSON.parse(JSON.stringify(this.constraintsMedia));
|
||||||
|
this.disableCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
public focusCamera() {
|
||||||
|
if(this.focused){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.focused = true;
|
||||||
|
this.applyPreviousConfig();
|
||||||
|
}
|
||||||
|
|
||||||
public onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void {
|
public onUpdateLocalStream(callback: UpdatedLocalStreamCallback): void {
|
||||||
this.updatedLocalStreamCallBacks.add(callback);
|
this.updatedLocalStreamCallBacks.add(callback);
|
||||||
}
|
}
|
||||||
|
@ -138,20 +172,27 @@ export class MediaManager {
|
||||||
public showGameOverlay(){
|
public showGameOverlay(){
|
||||||
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
|
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
|
||||||
gameOverlay.classList.add('active');
|
gameOverlay.classList.add('active');
|
||||||
|
|
||||||
|
const buttonCloseFrame = HtmlUtils.getElementByIdOrFail('cowebsite-close');
|
||||||
|
const functionTrigger = () => {
|
||||||
|
this.triggerCloseJitsiFrameButton();
|
||||||
|
}
|
||||||
|
buttonCloseFrame.removeEventListener('click', functionTrigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
public hideGameOverlay(){
|
public hideGameOverlay(){
|
||||||
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
|
const gameOverlay = HtmlUtils.getElementByIdOrFail('game-overlay');
|
||||||
gameOverlay.classList.remove('active');
|
gameOverlay.classList.remove('active');
|
||||||
|
|
||||||
|
const buttonCloseFrame = HtmlUtils.getElementByIdOrFail('cowebsite-close');
|
||||||
|
const functionTrigger = () => {
|
||||||
|
this.triggerCloseJitsiFrameButton();
|
||||||
|
}
|
||||||
|
buttonCloseFrame.addEventListener('click', functionTrigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
public enableCamera() {
|
public enableCamera() {
|
||||||
if(!this.hasCamera){
|
this.enableCameraStyle();
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.cinemaClose.style.display = "none";
|
|
||||||
this.cinemaBtn.classList.remove("disabled");
|
|
||||||
this.cinema.style.display = "block";
|
|
||||||
this.constraintsMedia.video = videoConstraint;
|
this.constraintsMedia.video = videoConstraint;
|
||||||
this.getCamera().then((stream: MediaStream) => {
|
this.getCamera().then((stream: MediaStream) => {
|
||||||
this.triggerUpdatedLocalStreamCallbacks(stream);
|
this.triggerUpdatedLocalStreamCallbacks(stream);
|
||||||
|
@ -159,7 +200,8 @@ export class MediaManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async disableCamera() {
|
public async disableCamera() {
|
||||||
this.disabledCameraView();
|
this.disableCameraStyle();
|
||||||
|
|
||||||
if (this.constraintsMedia.audio !== false) {
|
if (this.constraintsMedia.audio !== false) {
|
||||||
const stream = await this.getCamera();
|
const stream = await this.getCamera();
|
||||||
this.triggerUpdatedLocalStreamCallbacks(stream);
|
this.triggerUpdatedLocalStreamCallbacks(stream);
|
||||||
|
@ -168,19 +210,8 @@ export class MediaManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private disabledCameraView(){
|
|
||||||
this.cinemaClose.style.display = "block";
|
|
||||||
this.cinema.style.display = "none";
|
|
||||||
this.cinemaBtn.classList.add("disabled");
|
|
||||||
this.constraintsMedia.video = false;
|
|
||||||
this.myCamVideo.srcObject = null;
|
|
||||||
this.stopCamera();
|
|
||||||
}
|
|
||||||
|
|
||||||
public enableMicrophone() {
|
public enableMicrophone() {
|
||||||
this.microphoneClose.style.display = "none";
|
this.enableMicrophoneStyle();
|
||||||
this.microphone.style.display = "block";
|
|
||||||
this.microphoneBtn.classList.remove("disabled");
|
|
||||||
this.constraintsMedia.audio = true;
|
this.constraintsMedia.audio = true;
|
||||||
|
|
||||||
this.getCamera().then((stream) => {
|
this.getCamera().then((stream) => {
|
||||||
|
@ -189,10 +220,7 @@ export class MediaManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async disableMicrophone() {
|
public async disableMicrophone() {
|
||||||
this.microphoneClose.style.display = "block";
|
this.disableMicrophoneStyle();
|
||||||
this.microphone.style.display = "none";
|
|
||||||
this.microphoneBtn.classList.add("disabled");
|
|
||||||
this.constraintsMedia.audio = false;
|
|
||||||
this.stopMicrophone();
|
this.stopMicrophone();
|
||||||
|
|
||||||
if (this.constraintsMedia.video !== false) {
|
if (this.constraintsMedia.video !== false) {
|
||||||
|
@ -203,6 +231,52 @@ export class MediaManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private applyPreviousConfig() {
|
||||||
|
this.constraintsMedia = this.previousConstraint;
|
||||||
|
if(!this.constraintsMedia.video){
|
||||||
|
this.disableCameraStyle();
|
||||||
|
}else{
|
||||||
|
this.enableCameraStyle();
|
||||||
|
}
|
||||||
|
if(!this.constraintsMedia.audio){
|
||||||
|
this.disableMicrophoneStyle()
|
||||||
|
}else{
|
||||||
|
this.enableMicrophoneStyle()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getCamera().then((stream: MediaStream) => {
|
||||||
|
this.triggerUpdatedLocalStreamCallbacks(stream);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private enableCameraStyle(){
|
||||||
|
this.cinemaClose.style.display = "none";
|
||||||
|
this.cinemaBtn.classList.remove("disabled");
|
||||||
|
this.cinema.style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
private disableCameraStyle(){
|
||||||
|
this.cinemaClose.style.display = "block";
|
||||||
|
this.cinema.style.display = "none";
|
||||||
|
this.cinemaBtn.classList.add("disabled");
|
||||||
|
this.constraintsMedia.video = false;
|
||||||
|
this.myCamVideo.srcObject = null;
|
||||||
|
this.stopCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
private enableMicrophoneStyle(){
|
||||||
|
this.microphoneClose.style.display = "none";
|
||||||
|
this.microphone.style.display = "block";
|
||||||
|
this.microphoneBtn.classList.remove("disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
private disableMicrophoneStyle(){
|
||||||
|
this.microphoneClose.style.display = "block";
|
||||||
|
this.microphone.style.display = "none";
|
||||||
|
this.microphoneBtn.classList.add("disabled");
|
||||||
|
this.constraintsMedia.audio = false;
|
||||||
|
}
|
||||||
|
|
||||||
private enableScreenSharing() {
|
private enableScreenSharing() {
|
||||||
this.monitorClose.style.display = "none";
|
this.monitorClose.style.display = "none";
|
||||||
this.monitor.style.display = "block";
|
this.monitor.style.display = "block";
|
||||||
|
@ -285,7 +359,7 @@ export class MediaManager {
|
||||||
|
|
||||||
return this.getLocalStream().catch(() => {
|
return this.getLocalStream().catch(() => {
|
||||||
console.info('Error get camera, trying with video option at null');
|
console.info('Error get camera, trying with video option at null');
|
||||||
this.disabledCameraView();
|
this.disableCameraStyle();
|
||||||
return this.getLocalStream().then((stream : MediaStream) => {
|
return this.getLocalStream().then((stream : MediaStream) => {
|
||||||
this.hasCamera = false;
|
this.hasCamera = false;
|
||||||
return stream;
|
return stream;
|
||||||
|
@ -412,7 +486,7 @@ export class MediaManager {
|
||||||
if(!element){
|
if(!element){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
element.classList.add('active')
|
element.classList.add('active') //todo: why does a method 'disable' add a class 'active'?
|
||||||
}
|
}
|
||||||
|
|
||||||
enabledMicrophoneByUserId(userId: number){
|
enabledMicrophoneByUserId(userId: number){
|
||||||
|
@ -420,7 +494,7 @@ export class MediaManager {
|
||||||
if(!element){
|
if(!element){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
element.classList.remove('active')
|
element.classList.remove('active') //todo: why does a method 'enable' remove a class 'active'?
|
||||||
}
|
}
|
||||||
|
|
||||||
disabledVideoByUserId(userId: number) {
|
disabledVideoByUserId(userId: number) {
|
||||||
|
@ -445,7 +519,7 @@ export class MediaManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addStreamRemoteVideo(userId: string, stream : MediaStream){
|
addStreamRemoteVideo(userId: string, stream : MediaStream): void {
|
||||||
const remoteVideo = this.remoteVideo.get(userId);
|
const remoteVideo = this.remoteVideo.get(userId);
|
||||||
if (remoteVideo === undefined) {
|
if (remoteVideo === undefined) {
|
||||||
throw `Unable to find video for ${userId}`;
|
throw `Unable to find video for ${userId}`;
|
||||||
|
@ -594,6 +668,29 @@ export class MediaManager {
|
||||||
public removeParticipant(userId: number|string){
|
public removeParticipant(userId: number|string){
|
||||||
this.discussionManager.removeParticipant(userId);
|
this.discussionManager.removeParticipant(userId);
|
||||||
}
|
}
|
||||||
|
public addTriggerCloseJitsiFrameButton(id: String, Function: Function){
|
||||||
|
this.triggerCloseJistiFrame.set(id, Function);
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeTriggerCloseJitsiFrameButton(id: String){
|
||||||
|
this.triggerCloseJistiFrame.delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private triggerCloseJitsiFrameButton(): void {
|
||||||
|
for (const callback of this.triggerCloseJistiFrame.values()) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* For some reasons, the microphone muted icon or the stream is not always up to date.
|
||||||
|
* Here, every 30 seconds, we are "reseting" the streams and sending again the constraints to the other peers via the data channel again (see SimplePeer::pushVideoToRemoteUser)
|
||||||
|
**/
|
||||||
|
private pingCameraStatus(){
|
||||||
|
/*setInterval(() => {
|
||||||
|
console.log('ping camera status');
|
||||||
|
this.triggerUpdatedLocalStreamCallbacks(this.localStream);
|
||||||
|
}, 30000);*/
|
||||||
|
}
|
||||||
|
|
||||||
public addNewMessage(name: string, message: string, isMe: boolean = false){
|
public addNewMessage(name: string, message: string, isMe: boolean = false){
|
||||||
this.discussionManager.addMessage(name, message, isMe);
|
this.discussionManager.addMessage(name, message, isMe);
|
||||||
|
@ -615,6 +712,22 @@ export class MediaManager {
|
||||||
public setUserInputManager(userInputManager : UserInputManager){
|
public setUserInputManager(userInputManager : UserInputManager){
|
||||||
this.discussionManager.setUserInputManager(userInputManager);
|
this.discussionManager.setUserInputManager(userInputManager);
|
||||||
}
|
}
|
||||||
|
//check if user is active
|
||||||
|
private checkActiveUser(){
|
||||||
|
if(this.setTimeOutlastUpdateScene){
|
||||||
|
clearTimeout(this.setTimeOutlastUpdateScene);
|
||||||
|
}
|
||||||
|
this.setTimeOutlastUpdateScene = setTimeout(() => {
|
||||||
|
const now = new Date();
|
||||||
|
//if last update is more of 10 sec
|
||||||
|
if( (now.getTime() - this.lastUpdateScene.getTime()) > 10000) {
|
||||||
|
this.blurCamera();
|
||||||
|
}else{
|
||||||
|
this.focusCamera();
|
||||||
|
}
|
||||||
|
this.checkActiveUser();
|
||||||
|
}, this.focused ? 10000 : 1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mediaManager = new MediaManager();
|
export const mediaManager = new MediaManager();
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import {
|
import {
|
||||||
WebRtcDisconnectMessageInterface,
|
WebRtcDisconnectMessageInterface,
|
||||||
WebRtcSignalReceivedMessageInterface,
|
WebRtcSignalReceivedMessageInterface,
|
||||||
WebRtcStartMessageInterface
|
|
||||||
} from "../Connexion/ConnexionModels";
|
} from "../Connexion/ConnexionModels";
|
||||||
import {
|
import {
|
||||||
mediaManager,
|
mediaManager,
|
||||||
|
@ -29,7 +28,7 @@ export interface PeerConnectionListener {
|
||||||
* This class manages connections to all the peers in the same group as me.
|
* This class manages connections to all the peers in the same group as me.
|
||||||
*/
|
*/
|
||||||
export class SimplePeer {
|
export class SimplePeer {
|
||||||
private Users: Array<UserSimplePeerInterface> = new Array<UserSimplePeerInterface>();
|
private Users: Array<UserSimplePeerInterface> = new Array<UserSimplePeerInterface>(); //todo: this array should be fusionned with PeerConnectionArray
|
||||||
|
|
||||||
private PeerScreenSharingConnectionArray: Map<number, ScreenSharingPeer> = new Map<number, ScreenSharingPeer>();
|
private PeerScreenSharingConnectionArray: Map<number, ScreenSharingPeer> = new Map<number, ScreenSharingPeer>();
|
||||||
private PeerConnectionArray: Map<number, VideoPeer> = new Map<number, VideoPeer>();
|
private PeerConnectionArray: Map<number, VideoPeer> = new Map<number, VideoPeer>();
|
||||||
|
@ -95,12 +94,9 @@ export class SimplePeer {
|
||||||
this.Users.push(user);
|
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)
|
// 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)
|
// So we can receive a request we already had before. (which will abort at the first line of createPeerConnection)
|
||||||
// TODO: refactor this to only send a message to connect to one user (rather than several users). => DONE
|
|
||||||
// This would be symmetrical to the way we handle disconnection.
|
// This would be symmetrical to the way we handle disconnection.
|
||||||
//console.log('Start message', data);
|
|
||||||
|
|
||||||
//start connection
|
//start connection
|
||||||
//this.startWebRtc();
|
|
||||||
console.log('receiveWebrtcStart. Initiator: ', user.initiator)
|
console.log('receiveWebrtcStart. Initiator: ', user.initiator)
|
||||||
if(!user.initiator){
|
if(!user.initiator){
|
||||||
return;
|
return;
|
||||||
|
@ -204,8 +200,6 @@ export class SimplePeer {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is triggered twice. Once by the server, and once by a remote client disconnecting
|
* This is triggered twice. Once by the server, and once by a remote client disconnecting
|
||||||
*
|
|
||||||
* @param userId
|
|
||||||
*/
|
*/
|
||||||
private closeConnection(userId : number) {
|
private closeConnection(userId : number) {
|
||||||
try {
|
try {
|
||||||
|
@ -226,6 +220,12 @@ export class SimplePeer {
|
||||||
for (const peerConnectionListener of this.peerConnectionListeners) {
|
for (const peerConnectionListener of this.peerConnectionListeners) {
|
||||||
peerConnectionListener.onDisconnect(userId);
|
peerConnectionListener.onDisconnect(userId);
|
||||||
}
|
}
|
||||||
|
const userIndex = this.Users.findIndex(user => user.userId === userId);
|
||||||
|
if(userIndex < 0){
|
||||||
|
throw 'Couln\'t delete user';
|
||||||
|
} else {
|
||||||
|
this.Users.splice(userIndex, 1);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("closeConnection", err)
|
console.error("closeConnection", err)
|
||||||
}
|
}
|
||||||
|
@ -233,8 +233,6 @@ export class SimplePeer {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is triggered twice. Once by the server, and once by a remote client disconnecting
|
* This is triggered twice. Once by the server, and once by a remote client disconnecting
|
||||||
*
|
|
||||||
* @param userId
|
|
||||||
*/
|
*/
|
||||||
private closeScreenSharingConnection(userId : number) {
|
private closeScreenSharingConnection(userId : number) {
|
||||||
try {
|
try {
|
||||||
|
@ -246,7 +244,6 @@ export class SimplePeer {
|
||||||
}
|
}
|
||||||
// FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray"
|
// 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.
|
// I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel.
|
||||||
//console.log('Closing connection with '+userId);
|
|
||||||
peer.destroy();
|
peer.destroy();
|
||||||
if(!this.PeerScreenSharingConnectionArray.delete(userId)){
|
if(!this.PeerScreenSharingConnectionArray.delete(userId)){
|
||||||
throw 'Couln\'t delete peer screen sharing connexion';
|
throw 'Couln\'t delete peer screen sharing connexion';
|
||||||
|
@ -313,10 +310,6 @@ export class SimplePeer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param userId
|
|
||||||
*/
|
|
||||||
private pushVideoToRemoteUser(userId : number) {
|
private pushVideoToRemoteUser(userId : number) {
|
||||||
try {
|
try {
|
||||||
const PeerConnection = this.PeerConnectionArray.get(userId);
|
const PeerConnection = this.PeerConnectionArray.get(userId);
|
||||||
|
@ -331,6 +324,9 @@ export class SimplePeer {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const track of localStream.getTracks()) {
|
for (const track of localStream.getTracks()) {
|
||||||
|
//todo: this is a ugly hack to reduce the amount of error in console. Find a better way.
|
||||||
|
if ((track as any).added !== undefined) continue; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
(track as any).added = true; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
PeerConnection.addTrack(track, localStream);
|
PeerConnection.addTrack(track, localStream);
|
||||||
}
|
}
|
||||||
}catch (e) {
|
}catch (e) {
|
||||||
|
|
|
@ -84,22 +84,18 @@ export class VideoPeer extends Peer {
|
||||||
console.log("data", message);
|
console.log("data", message);
|
||||||
|
|
||||||
if(message.type === MESSAGE_TYPE_CONSTRAINT) {
|
if(message.type === MESSAGE_TYPE_CONSTRAINT) {
|
||||||
const constraint = message;
|
if (message.audio) {
|
||||||
if (constraint.audio) {
|
|
||||||
mediaManager.enabledMicrophoneByUserId(this.userId);
|
mediaManager.enabledMicrophoneByUserId(this.userId);
|
||||||
} else {
|
} else {
|
||||||
mediaManager.disabledMicrophoneByUserId(this.userId);
|
mediaManager.disabledMicrophoneByUserId(this.userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (constraint.video || constraint.screen) {
|
if (message.video || message.screen) {
|
||||||
mediaManager.enabledVideoByUserId(this.userId);
|
mediaManager.enabledVideoByUserId(this.userId);
|
||||||
} else {
|
} else {
|
||||||
this.stream(undefined);
|
|
||||||
mediaManager.disabledVideoByUserId(this.userId);
|
mediaManager.disabledVideoByUserId(this.userId);
|
||||||
}
|
}
|
||||||
}
|
} else if(message.type === 'message') {
|
||||||
|
|
||||||
if(message.type === 'message') {
|
|
||||||
mediaManager.addNewMessage(message.name, message.message);
|
mediaManager.addNewMessage(message.name, message.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -122,21 +118,15 @@ export class VideoPeer extends Peer {
|
||||||
/**
|
/**
|
||||||
* Sends received stream to screen.
|
* Sends received stream to screen.
|
||||||
*/
|
*/
|
||||||
private stream(stream?: MediaStream) {
|
private stream(stream: MediaStream) {
|
||||||
//console.log(`VideoPeer::stream => ${this.userId}`, stream);
|
|
||||||
if(!stream){
|
|
||||||
mediaManager.disabledVideoByUserId(this.userId);
|
|
||||||
mediaManager.disabledMicrophoneByUserId(this.userId);
|
|
||||||
} else {
|
|
||||||
try {
|
try {
|
||||||
mediaManager.addStreamRemoteVideo("" + this.userId, stream);
|
mediaManager.addStreamRemoteVideo("" + this.userId, stream);
|
||||||
}catch (err){
|
}catch (err){
|
||||||
console.error(err);
|
console.error(err);
|
||||||
//Force add streem video
|
//Force add streem video
|
||||||
setTimeout(() => {
|
/*setTimeout(() => {
|
||||||
this.stream(stream);
|
this.stream(stream);
|
||||||
}, 500);
|
}, 500);*/ //todo: find a way to prevent infinite regression.
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
42
front/tests/Phaser/Game/RoomTest.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import "jasmine";
|
||||||
|
import {Room} from "../../../src/Connexion/Room";
|
||||||
|
|
||||||
|
describe("Room getIdFromIdentifier()", () => {
|
||||||
|
it("should work with an absolute room id and no hash as parameter", () => {
|
||||||
|
const {roomId, hash} = Room.getIdFromIdentifier('/_/global/maps.workadventu.re/test2.json', '', '');
|
||||||
|
expect(roomId).toEqual('_/global/maps.workadventu.re/test2.json');
|
||||||
|
expect(hash).toEqual('');
|
||||||
|
});
|
||||||
|
it("should work with an absolute room id and a hash as parameters", () => {
|
||||||
|
const {roomId, hash} = Room.getIdFromIdentifier('/_/global/maps.workadventu.re/test2.json#start', '', '');
|
||||||
|
expect(roomId).toEqual('_/global/maps.workadventu.re/test2.json');
|
||||||
|
expect(hash).toEqual("start");
|
||||||
|
});
|
||||||
|
it("should work with an absolute room id, regardless of baseUrl or instance", () => {
|
||||||
|
const {roomId, hash} = Room.getIdFromIdentifier('/_/global/maps.workadventu.re/test2.json', 'https://another.domain/_/global/test.json', 'lol');
|
||||||
|
expect(roomId).toEqual('_/global/maps.workadventu.re/test2.json');
|
||||||
|
expect(hash).toEqual('');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("should work with a relative file link and no hash as parameters", () => {
|
||||||
|
const {roomId, hash} = Room.getIdFromIdentifier('./test2.json', 'https://maps.workadventu.re/test.json', 'global');
|
||||||
|
expect(roomId).toEqual('_/global/maps.workadventu.re/test2.json');
|
||||||
|
expect(hash).toEqual('');
|
||||||
|
});
|
||||||
|
it("should work with a relative file link with no dot", () => {
|
||||||
|
const {roomId, hash} = Room.getIdFromIdentifier('test2.json', 'https://maps.workadventu.re/test.json', 'global');
|
||||||
|
expect(roomId).toEqual('_/global/maps.workadventu.re/test2.json');
|
||||||
|
expect(hash).toEqual('');
|
||||||
|
});
|
||||||
|
it("should work with a relative file link two levels deep", () => {
|
||||||
|
const {roomId, hash} = Room.getIdFromIdentifier('../floor1/Floor1.json', 'https://maps.workadventu.re/floor0/Floor0.json', 'global');
|
||||||
|
expect(roomId).toEqual('_/global/maps.workadventu.re/floor1/Floor1.json');
|
||||||
|
expect(hash).toEqual('');
|
||||||
|
});
|
||||||
|
it("should work with a relative file link and a hash as parameters", () => {
|
||||||
|
const {roomId, hash} = Room.getIdFromIdentifier('./test2.json#start', 'https://maps.workadventu.re/test.json', 'global');
|
||||||
|
expect(roomId).toEqual('_/global/maps.workadventu.re/test2.json');
|
||||||
|
expect(hash).toEqual("start");
|
||||||
|
});
|
||||||
|
});
|
1
maps/.gitignore
vendored
|
@ -1 +1,2 @@
|
||||||
/node_modules/
|
/node_modules/
|
||||||
|
dist/
|
|
@ -44,6 +44,11 @@
|
||||||
"name":"jitsiRoom",
|
"name":"jitsiRoom",
|
||||||
"type":"string",
|
"type":"string",
|
||||||
"value":"tcm-chillzone-2"
|
"value":"tcm-chillzone-2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"jitsiTrigger",
|
||||||
|
"type":"string",
|
||||||
|
"value":"onaction"
|
||||||
}],
|
}],
|
||||||
"type":"tilelayer",
|
"type":"tilelayer",
|
||||||
"visible":true,
|
"visible":true,
|
||||||
|
@ -61,7 +66,12 @@
|
||||||
{
|
{
|
||||||
"name":"jitsiRoom",
|
"name":"jitsiRoom",
|
||||||
"type":"string",
|
"type":"string",
|
||||||
"value":"tcm-chillzone-1"
|
"value":"tcm-chillzone-11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"jitsiTrigger",
|
||||||
|
"type":"string",
|
||||||
|
"value":"onaction"
|
||||||
}],
|
}],
|
||||||
"type":"tilelayer",
|
"type":"tilelayer",
|
||||||
"visible":true,
|
"visible":true,
|
||||||
|
@ -128,6 +138,11 @@
|
||||||
"name":"openWebsite",
|
"name":"openWebsite",
|
||||||
"type":"string",
|
"type":"string",
|
||||||
"value":"https:\/\/app.swile.co\/"
|
"value":"https:\/\/app.swile.co\/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"openWebsiteTrigger",
|
||||||
|
"type":"string",
|
||||||
|
"value":"onaction"
|
||||||
}],
|
}],
|
||||||
"type":"tilelayer",
|
"type":"tilelayer",
|
||||||
"visible":true,
|
"visible":true,
|
||||||
|
|
1577
maps/yarn.lock
Normal file
6
website/package-lock.json
generated
|
@ -2251,9 +2251,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dot-prop": {
|
"dot-prop": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.1.tgz",
|
||||||
"integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==",
|
"integrity": "sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"is-obj": "^1.0.0"
|
"is-obj": "^1.0.0"
|
||||||
|
|