Merge branch 'develop' into exitTriggerAction
# Conflicts: # front/src/Phaser/Game/GameScene.ts
This commit is contained in:
commit
83fc7d0cc0
88 changed files with 1382 additions and 1996 deletions
|
@ -332,7 +332,7 @@ export class ConsoleGlobalMessageManager {
|
|||
}
|
||||
|
||||
active(){
|
||||
this.userInputManager.clearAllInputKeyboard();
|
||||
this.userInputManager.clearAllKeys();
|
||||
this.divMainConsole.style.top = '0';
|
||||
this.activeConsole = true;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import * as TypeMessages from "./TypeMessage";
|
||||
import List = Phaser.Structs.List;
|
||||
import {UpdatedLocalStreamCallback} from "../WebRtc/MediaManager";
|
||||
import {Banned} from "./TypeMessage";
|
||||
|
||||
export interface TypeMessageInterface {
|
||||
showMessage(message: string): void;
|
||||
|
@ -8,6 +11,7 @@ export interface TypeMessageInterface {
|
|||
export class UserMessageManager {
|
||||
|
||||
typeMessages: Map<string, TypeMessageInterface> = new Map<string, TypeMessageInterface>();
|
||||
receiveBannedMessageListener: Set<Function> = new Set<UpdatedLocalStreamCallback>();
|
||||
|
||||
constructor(private Connection: RoomConnection) {
|
||||
const valueTypeMessageTab = Object.values(TypeMessages);
|
||||
|
@ -21,7 +25,14 @@ export class UserMessageManager {
|
|||
initialise() {
|
||||
//receive signal to show message
|
||||
this.Connection.receiveUserMessage((type: string, message: string) => {
|
||||
this.showMessage(type, message);
|
||||
const typeMessage = this.showMessage(type, message);
|
||||
|
||||
//listener on banned receive message
|
||||
if(typeMessage instanceof Banned) {
|
||||
for (const callback of this.receiveBannedMessageListener) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -32,5 +43,10 @@ export class UserMessageManager {
|
|||
return;
|
||||
}
|
||||
classTypeMessage.showMessage(message);
|
||||
return classTypeMessage;
|
||||
}
|
||||
|
||||
setReceiveBanListener(callback: Function){
|
||||
this.receiveBannedMessageListener.add(callback);
|
||||
}
|
||||
}
|
|
@ -96,7 +96,9 @@ export interface WebRtcSignalSentMessageInterface {
|
|||
|
||||
export interface WebRtcSignalReceivedMessageInterface {
|
||||
userId: number,
|
||||
signal: SignalData
|
||||
signal: SignalData,
|
||||
webRtcUser: string | undefined,
|
||||
webRtcPassword: string | undefined
|
||||
}
|
||||
|
||||
export interface StartMapInterface {
|
||||
|
|
|
@ -427,7 +427,9 @@ export class RoomConnection implements RoomConnection {
|
|||
callback({
|
||||
userId: message.getUserid(),
|
||||
name: message.getName(),
|
||||
initiator: message.getInitiator()
|
||||
initiator: message.getInitiator(),
|
||||
webRtcUser: message.getWebrtcusername() ?? undefined,
|
||||
webRtcPassword: message.getWebrtcpassword() ?? undefined,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -436,7 +438,9 @@ export class RoomConnection implements RoomConnection {
|
|||
this.onMessage(EventMessage.WEBRTC_SIGNAL, (message: WebRtcSignalToClientMessage) => {
|
||||
callback({
|
||||
userId: message.getUserid(),
|
||||
signal: JSON.parse(message.getSignal())
|
||||
signal: JSON.parse(message.getSignal()),
|
||||
webRtcUser: message.getWebrtcusername() ?? undefined,
|
||||
webRtcPassword: message.getWebrtcpassword() ?? undefined,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -445,7 +449,9 @@ export class RoomConnection implements RoomConnection {
|
|||
this.onMessage(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, (message: WebRtcSignalToClientMessage) => {
|
||||
callback({
|
||||
userId: message.getUserid(),
|
||||
signal: JSON.parse(message.getSignal())
|
||||
signal: JSON.parse(message.getSignal()),
|
||||
webRtcUser: message.getWebrtcusername() ?? undefined,
|
||||
webRtcPassword: message.getWebrtcpassword() ?? undefined,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -464,7 +470,8 @@ export class RoomConnection implements RoomConnection {
|
|||
});
|
||||
}
|
||||
|
||||
public getUserId(): number|null {
|
||||
public getUserId(): number {
|
||||
if (this.userId === null) throw 'UserId cannot be null!'
|
||||
return this.userId;
|
||||
}
|
||||
|
||||
|
@ -583,7 +590,7 @@ export class RoomConnection implements RoomConnection {
|
|||
public hasTag(tag: string): boolean {
|
||||
return this.tags.includes(tag);
|
||||
}
|
||||
|
||||
|
||||
public isAdmin(): boolean {
|
||||
return this.hasTag('admin');
|
||||
}
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
declare let window:WindowWithCypressAsserter;
|
||||
|
||||
interface WindowWithCypressAsserter extends Window {
|
||||
cypressAsserter: CypressAsserter;
|
||||
}
|
||||
|
||||
//this class is used to communicate with cypress, our e2e testing client
|
||||
//Since cypress cannot manipulate canvas, we notified it with console logs
|
||||
class CypressAsserter {
|
||||
|
||||
constructor() {
|
||||
window.cypressAsserter = this
|
||||
}
|
||||
|
||||
gameStarted() {
|
||||
console.log('Started the game')
|
||||
}
|
||||
|
||||
preloadStarted() {
|
||||
console.log('Preloading')
|
||||
}
|
||||
|
||||
preloadFinished() {
|
||||
console.log('Preloading done')
|
||||
}
|
||||
|
||||
initStarted() {
|
||||
console.log('startInit')
|
||||
}
|
||||
|
||||
initFinished() {
|
||||
console.log('startInit done')
|
||||
}
|
||||
}
|
||||
|
||||
export const cypressAsserter = new CypressAsserter()
|
|
@ -3,9 +3,10 @@ const START_ROOM_URL : string = process.env.START_ROOM_URL || '/_/global/maps.wo
|
|||
const API_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.API_URL || "pusher.workadventure.localhost");
|
||||
const UPLOADER_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.UPLOADER_URL || 'uploader.workadventure.localhost');
|
||||
const ADMIN_URL = (process.env.API_PROTOCOL || (typeof(window) !== 'undefined' ? window.location.protocol : 'http:')) + '//' + (process.env.ADMIN_URL || "workadventure.localhost");
|
||||
const TURN_SERVER: string = process.env.TURN_SERVER || "turn:numb.viagenie.ca";
|
||||
const TURN_USER: string = process.env.TURN_USER || 'g.parant@thecodingmachine.com';
|
||||
const TURN_PASSWORD: string = process.env.TURN_PASSWORD || 'itcugcOHxle9Acqi$';
|
||||
const STUN_SERVER: string = process.env.STUN_SERVER || "stun:stun.l.google.com:19302";
|
||||
const TURN_SERVER: string = process.env.TURN_SERVER || "";
|
||||
const TURN_USER: string = process.env.TURN_USER || '';
|
||||
const TURN_PASSWORD: string = process.env.TURN_PASSWORD || '';
|
||||
const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL;
|
||||
const JITSI_PRIVATE_MODE : boolean = process.env.JITSI_PRIVATE_MODE == "true";
|
||||
const RESOLUTION = 2;
|
||||
|
@ -23,6 +24,7 @@ export {
|
|||
ZOOM_LEVEL,
|
||||
POSITION_DELAY,
|
||||
MAX_EXTRAPOLATION_TIME,
|
||||
STUN_SERVER,
|
||||
TURN_SERVER,
|
||||
TURN_USER,
|
||||
TURN_PASSWORD,
|
||||
|
|
|
@ -1,14 +1,54 @@
|
|||
import ImageFrameConfig = Phaser.Types.Loader.FileTypes.ImageFrameConfig;
|
||||
|
||||
export const addLoader = (scene:Phaser.Scene): void => {
|
||||
const loadingText = scene.add.text(scene.game.renderer.width / 2, 200, 'Loading');
|
||||
const LogoNameIndex: string = 'logoLoading';
|
||||
const TextName: string = 'Loading...';
|
||||
const LogoResource: string = 'resources/logos/logo.png';
|
||||
const LogoFrame: ImageFrameConfig = {frameWidth: 307, frameHeight: 59};
|
||||
|
||||
export const addLoader = (scene: Phaser.Scene): void => {
|
||||
// If there is nothing to load, do not display the loader.
|
||||
if (scene.load.list.entries.length === 0) {
|
||||
return;
|
||||
}
|
||||
let loadingText: Phaser.GameObjects.Text|null = null;
|
||||
const loadingBarWidth: number = Math.floor(scene.game.renderer.width / 3);
|
||||
const loadingBarHeight: number = 16;
|
||||
const padding: number = 5;
|
||||
|
||||
const promiseLoadLogoTexture = new Promise<Phaser.GameObjects.Image>((res) => {
|
||||
if(scene.load.textureManager.exists(LogoNameIndex)){
|
||||
return res(scene.add.image(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 150, LogoNameIndex));
|
||||
}else{
|
||||
//add loading if logo image is not ready
|
||||
loadingText = scene.add.text(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 50, TextName);
|
||||
}
|
||||
scene.load.spritesheet(LogoNameIndex, LogoResource, LogoFrame);
|
||||
scene.load.once(`filecomplete-spritesheet-${LogoNameIndex}`, () => {
|
||||
if(loadingText){
|
||||
loadingText.destroy();
|
||||
}
|
||||
return res(scene.add.image(scene.game.renderer.width / 2, scene.game.renderer.height / 2 - 150, LogoNameIndex));
|
||||
});
|
||||
});
|
||||
|
||||
const progressContainer = scene.add.graphics();
|
||||
const progress = scene.add.graphics();
|
||||
progressContainer.fillStyle(0x444444, 0.8);
|
||||
progressContainer.fillRect((scene.game.renderer.width - loadingBarWidth) / 2 - padding, scene.game.renderer.height / 2 + 50 - padding, loadingBarWidth + padding * 2, loadingBarHeight + padding * 2);
|
||||
|
||||
scene.load.on('progress', (value: number) => {
|
||||
progress.clear();
|
||||
progress.fillStyle(0xffffff, 1);
|
||||
progress.fillRect(0, 270, 800 * value, 60);
|
||||
progress.fillStyle(0xBBBBBB, 1);
|
||||
progress.fillRect((scene.game.renderer.width - loadingBarWidth) / 2, scene.game.renderer.height / 2 + 50, loadingBarWidth * value, loadingBarHeight);
|
||||
});
|
||||
scene.load.on('complete', () => {
|
||||
loadingText.destroy();
|
||||
if(loadingText){
|
||||
loadingText.destroy();
|
||||
}
|
||||
promiseLoadLogoTexture.then((resLoadingImage: Phaser.GameObjects.Image) => {
|
||||
resLoadingImage.destroy();
|
||||
});
|
||||
progress.destroy();
|
||||
progressContainer.destroy();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,14 +3,12 @@ import {discussionManager} from "../../WebRtc/DiscussionManager";
|
|||
export const openChatIconName = 'openChatIcon';
|
||||
export class OpenChatIcon extends Phaser.GameObjects.Image {
|
||||
constructor(scene: Phaser.Scene, x: number, y: number) {
|
||||
super(scene, x, y, openChatIconName);
|
||||
super(scene, x, y, openChatIconName, 3);
|
||||
scene.add.existing(this);
|
||||
this.setScrollFactor(0, 0);
|
||||
this.setOrigin(0, 1);
|
||||
this.displayWidth = 30;
|
||||
this.displayHeight = 30;
|
||||
this.setInteractive();
|
||||
this.setVisible(false)
|
||||
this.setVisible(false);
|
||||
this.setDepth(99999);
|
||||
|
||||
this.on("pointerup", () => discussionManager.showDiscussionPart());
|
||||
|
|
|
@ -6,7 +6,8 @@ export interface BodyResourceDescriptionListInterface {
|
|||
|
||||
export interface BodyResourceDescriptionInterface {
|
||||
name: string,
|
||||
img: string
|
||||
img: string,
|
||||
level?: number
|
||||
}
|
||||
|
||||
export const PLAYER_RESOURCES: BodyResourceDescriptionListInterface = {
|
||||
|
|
|
@ -23,21 +23,26 @@ export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptio
|
|||
});
|
||||
return returnArray;
|
||||
}
|
||||
export const loadCustomTexture = (load: LoaderPlugin, texture: CharacterTexture) => {
|
||||
|
||||
export const loadCustomTexture = (loaderPlugin: LoaderPlugin, texture: CharacterTexture) : Promise<BodyResourceDescriptionInterface> => {
|
||||
const name = 'customCharacterTexture'+texture.id;
|
||||
load.spritesheet(name,texture.url,{frameWidth: 32, frameHeight: 32});
|
||||
return name;
|
||||
const playerResourceDescriptor: BodyResourceDescriptionInterface = {name, img: texture.url, level: texture.level}
|
||||
return createLoadingPromise(loaderPlugin, playerResourceDescriptor);
|
||||
}
|
||||
|
||||
export const lazyLoadPlayerCharacterTextures = (loadPlugin: LoaderPlugin, texturePlugin: TextureManager, texturekeys:Array<string|BodyResourceDescriptionInterface>): Promise<string[]> => {
|
||||
const promisesList:Promise<void>[] = [];
|
||||
export const lazyLoadPlayerCharacterTextures = (loadPlugin: LoaderPlugin, texturekeys:Array<string|BodyResourceDescriptionInterface>): Promise<string[]> => {
|
||||
const promisesList:Promise<unknown>[] = [];
|
||||
texturekeys.forEach((textureKey: string|BodyResourceDescriptionInterface) => {
|
||||
const playerResourceDescriptor = getRessourceDescriptor(textureKey);
|
||||
if(!texturePlugin.exists(playerResourceDescriptor.name)) {
|
||||
console.log('Loading '+playerResourceDescriptor.name)
|
||||
promisesList.push(createLoadingPromise(loadPlugin, playerResourceDescriptor));
|
||||
try {
|
||||
//TODO refactor
|
||||
const playerResourceDescriptor = getRessourceDescriptor(textureKey);
|
||||
if (playerResourceDescriptor && !loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
|
||||
promisesList.push(createLoadingPromise(loadPlugin, playerResourceDescriptor));
|
||||
}
|
||||
}catch (err){
|
||||
console.error(err);
|
||||
}
|
||||
})
|
||||
});
|
||||
let returnPromise:Promise<Array<string|BodyResourceDescriptionInterface>>;
|
||||
if (promisesList.length > 0) {
|
||||
loadPlugin.start();
|
||||
|
@ -66,8 +71,14 @@ export const getRessourceDescriptor = (textureKey: string|BodyResourceDescriptio
|
|||
}
|
||||
|
||||
const createLoadingPromise = (loadPlugin: LoaderPlugin, playerResourceDescriptor: BodyResourceDescriptionInterface) => {
|
||||
return new Promise<void>((res, rej) => {
|
||||
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, {frameWidth: 32, frameHeight: 32});
|
||||
loadPlugin.once('filecomplete-spritesheet-'+playerResourceDescriptor.name, () => res());
|
||||
return new Promise<BodyResourceDescriptionInterface>((res) => {
|
||||
if (loadPlugin.textureManager.exists(playerResourceDescriptor.name)) {
|
||||
return res(playerResourceDescriptor);
|
||||
}
|
||||
loadPlugin.spritesheet(playerResourceDescriptor.name, playerResourceDescriptor.img, {
|
||||
frameWidth: 32,
|
||||
frameHeight: 32
|
||||
});
|
||||
loadPlugin.once('filecomplete-spritesheet-' + playerResourceDescriptor.name, () => res(playerResourceDescriptor));
|
||||
});
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
import {GameScene} from "../Game/GameScene";
|
||||
import {PointInterface} from "../../Connexion/ConnexionModels";
|
||||
import {Character} from "../Entity/Character";
|
||||
import {Sprite} from "./Sprite";
|
||||
|
||||
/**
|
||||
* Class representing the sprite of a remote player (a player that plays on another computer)
|
||||
|
@ -23,6 +22,11 @@ export class RemotePlayer extends Character {
|
|||
|
||||
//set data
|
||||
this.userId = userId;
|
||||
|
||||
//todo: implement on click action
|
||||
/*this.playerName.setInteractive();
|
||||
this.playerName.on('pointerup', () => {
|
||||
});*/
|
||||
}
|
||||
|
||||
updatePosition(position: PointInterface): void {
|
||||
|
|
|
@ -14,7 +14,7 @@ export class SpeechBubble {
|
|||
const bubbleWidth = bubblePadding * 2 + text.length * 10;
|
||||
const arrowHeight = bubbleHeight / 4;
|
||||
|
||||
this.bubble = scene.add.graphics({ x: player.x + 16, y: player.y - 80 });
|
||||
this.bubble = scene.add.graphics({ x: 16, y: -80 });
|
||||
player.add(this.bubble);
|
||||
|
||||
// Bubble shadow
|
||||
|
|
|
@ -49,6 +49,10 @@ export class GameMap {
|
|||
this.lastProperties = newProps;
|
||||
}
|
||||
|
||||
public getCurrentProperties(): Map<string, string|boolean|number> {
|
||||
return this.lastProperties;
|
||||
}
|
||||
|
||||
private getProperties(key: number): Map<string, string|boolean|number> {
|
||||
const properties = new Map<string, string|boolean|number>();
|
||||
|
||||
|
|
|
@ -30,10 +30,11 @@ import {RemotePlayer} from "../Entity/RemotePlayer";
|
|||
import {Queue} from 'queue-typescript';
|
||||
import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer";
|
||||
import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene";
|
||||
import {lazyLoadPlayerCharacterTextures} from "../Entity/PlayerTexturesLoadingManager";
|
||||
import {lazyLoadPlayerCharacterTextures, loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager";
|
||||
import {
|
||||
CenterListener,
|
||||
EXIT_MESSAGE_PROPERTIES, JITSI_MESSAGE_PROPERTIES,
|
||||
EXIT_MESSAGE_PROPERTIES,
|
||||
JITSI_MESSAGE_PROPERTIES,
|
||||
layoutManager,
|
||||
LayoutMode,
|
||||
ON_ACTION_TRIGGER_BUTTON,
|
||||
|
@ -72,6 +73,8 @@ import {SelectCharacterScene, SelectCharacterSceneName} from "../Login/SelectCha
|
|||
import {TextureError} from "../../Exception/TextureError";
|
||||
import {addLoader} from "../Components/Loader";
|
||||
import {ErrorSceneName} from "../Reconnecting/ErrorScene";
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||
|
||||
export interface GameSceneInitInterface {
|
||||
initPosition: PointInterface|null,
|
||||
|
@ -116,7 +119,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
MapPlayers!: Phaser.Physics.Arcade.Group;
|
||||
MapPlayersByKey : Map<number, RemotePlayer> = new Map<number, RemotePlayer>();
|
||||
Map!: Phaser.Tilemaps.Tilemap;
|
||||
Layers!: Array<Phaser.Tilemaps.TilemapLayer>;
|
||||
Layers!: Array<Phaser.Tilemaps.StaticTilemapLayer>;
|
||||
Objects!: Array<Phaser.Physics.Arcade.Sprite>;
|
||||
mapFile!: ITiledMap;
|
||||
groups: Map<number, Sprite>;
|
||||
|
@ -157,7 +160,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
private actionableItems: Map<number, ActionableItem> = new Map<number, ActionableItem>();
|
||||
// The item that can be selected by pressing the space key.
|
||||
private outlinedItem: ActionableItem|null = null;
|
||||
private userInputManager!: UserInputManager;
|
||||
public userInputManager!: UserInputManager;
|
||||
private isReconnecting: boolean = false;
|
||||
private startLayerName!: string | null;
|
||||
private openChatIcon!: OpenChatIcon;
|
||||
|
@ -186,7 +189,13 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
|
||||
//hook preload scene
|
||||
preload(): void {
|
||||
addLoader(this);
|
||||
const localUser = localUserStore.getLocalUser();
|
||||
const textures = localUser?.textures;
|
||||
if (textures) {
|
||||
for (const texture of textures) {
|
||||
loadCustomTexture(this.load, texture);
|
||||
}
|
||||
}
|
||||
|
||||
this.load.image(openChatIconName, 'resources/objects/talk.png');
|
||||
this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => {
|
||||
|
@ -210,6 +219,8 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
|
||||
this.load.spritesheet('layout_modes', 'resources/objects/layout_modes.png', {frameWidth: 32, frameHeight: 32});
|
||||
this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
|
||||
|
||||
addLoader(this);
|
||||
}
|
||||
|
||||
// FIXME: we need to put a "unknown" instead of a "any" and validate the structure of the JSON we are receiving.
|
||||
|
@ -344,11 +355,11 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
this.physics.world.setBounds(0, 0, this.Map.widthInPixels, this.Map.heightInPixels);
|
||||
|
||||
//add layer on map
|
||||
this.Layers = new Array<Phaser.Tilemaps.TilemapLayer>();
|
||||
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.createLayer(layer.name, this.Terrains, 0, 0).setDepth(depth));
|
||||
this.addLayer(this.Map.createStaticLayer(layer.name, this.Terrains, 0, 0).setDepth(depth));
|
||||
|
||||
const exitSceneUrl = this.getExitSceneUrl(layer);
|
||||
if (exitSceneUrl !== undefined) {
|
||||
|
@ -529,6 +540,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic, this.playerName);
|
||||
this.GlobalMessageManager = new GlobalMessageManager(this.connection);
|
||||
this.UserMessageManager = new UserMessageManager(this.connection);
|
||||
this.UserMessageManager.setReceiveBanListener(this.bannedUser.bind(this));
|
||||
|
||||
const self = this;
|
||||
this.simplePeer.registerPeerConnectionListener({
|
||||
|
@ -625,6 +637,15 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
}
|
||||
}
|
||||
|
||||
private safeParseJSONstring(jsonString: string|undefined, propertyName: string) {
|
||||
try {
|
||||
return jsonString ? JSON.parse(jsonString) : {};
|
||||
} catch(e) {
|
||||
console.warn('Invalid JSON found in property "' + propertyName + '" of the map:' + jsonString, e);
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
private triggerOnMapLayerPropertyChange(){
|
||||
/* @deprecated
|
||||
this.gameMap.onPropertyChange('exitSceneUrl', (newValue, oldValue) => {
|
||||
|
@ -656,7 +677,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
coWebsiteManager.closeCoWebsite();
|
||||
}else{
|
||||
const openWebsiteFunction = () => {
|
||||
coWebsiteManager.loadCoWebsite(newValue as string);
|
||||
coWebsiteManager.loadCoWebsite(newValue as string, allProps.get('openWebsitePolicy') as string | undefined);
|
||||
layoutManager.removeActionButton('openWebsite', this.userInputManager);
|
||||
};
|
||||
|
||||
|
@ -680,11 +701,13 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
this.stopJitsi();
|
||||
}else{
|
||||
const openJitsiRoomFunction = () => {
|
||||
const roomName = jitsiFactory.getRoomName(newValue.toString(), this.instance);
|
||||
if (JITSI_PRIVATE_MODE) {
|
||||
const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined;
|
||||
this.connection.emitQueryJitsiJwtMessage(this.instance.replace('/', '-') + "-" + newValue, adminTag);
|
||||
|
||||
this.connection.emitQueryJitsiJwtMessage(roomName, adminTag);
|
||||
} else {
|
||||
this.startJitsi(newValue as string);
|
||||
this.startJitsi(roomName, undefined);
|
||||
}
|
||||
layoutManager.removeActionButton('jitsiRoom', this.userInputManager);
|
||||
}
|
||||
|
@ -725,6 +748,10 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
if (!roomId) throw new Error('Could not find the room from its exit key: '+exitKey);
|
||||
urlManager.pushStartLayerNameToUrl(hash);
|
||||
if (roomId !== this.scene.key) {
|
||||
if (this.scene.get(roomId) === null) {
|
||||
console.error("next room not loaded", exitKey);
|
||||
return;
|
||||
}
|
||||
this.cleanupClosingScene();
|
||||
this.scene.stop();
|
||||
this.scene.remove(this.scene.key);
|
||||
|
@ -878,13 +905,13 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
this.cameras.main.setZoom(ZOOM_LEVEL);
|
||||
}
|
||||
|
||||
addLayer(Layer : Phaser.Tilemaps.TilemapLayer){
|
||||
addLayer(Layer : Phaser.Tilemaps.StaticTilemapLayer){
|
||||
this.Layers.push(Layer);
|
||||
}
|
||||
|
||||
createCollisionWithPlayer() {
|
||||
//add collision layer
|
||||
this.Layers.forEach((Layer: Phaser.Tilemaps.TilemapLayer) => {
|
||||
this.Layers.forEach((Layer: Phaser.Tilemaps.StaticTilemapLayer) => {
|
||||
this.physics.add.collider(this.CurrentPlayer, Layer, (object1: GameObject, object2: GameObject) => {
|
||||
//this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name)
|
||||
});
|
||||
|
@ -902,7 +929,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
|
||||
createCurrentPlayer(){
|
||||
//TODO create animation moving between exit and start
|
||||
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.textures, this.characterLayers);
|
||||
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.characterLayers);
|
||||
try {
|
||||
this.CurrentPlayer = new Player(
|
||||
this,
|
||||
|
@ -1096,7 +1123,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
return;
|
||||
}
|
||||
|
||||
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.textures, addPlayerData.characterLayers);
|
||||
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, addPlayerData.characterLayers);
|
||||
const player = new RemotePlayer(
|
||||
addPlayerData.userId,
|
||||
this,
|
||||
|
@ -1221,6 +1248,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
private reposition(): void {
|
||||
this.presentationModeSprite.setY(this.game.renderer.height - 2);
|
||||
this.chatModeSprite.setY(this.game.renderer.height - 2);
|
||||
this.openChatIcon.setY(this.game.renderer.height - 2);
|
||||
|
||||
// Recompute camera offset if needed
|
||||
this.updateCameraOffset();
|
||||
|
@ -1247,7 +1275,11 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
}
|
||||
|
||||
public startJitsi(roomName: string, jwt?: string): void {
|
||||
jitsiFactory.start(roomName, this.playerName, jwt);
|
||||
const allProps = this.gameMap.getCurrentProperties();
|
||||
const jitsiConfig = this.safeParseJSONstring(allProps.get("jitsiConfig") as string|undefined, 'jitsiConfig');
|
||||
const jitsiInterfaceConfig = this.safeParseJSONstring(allProps.get("jitsiInterfaceConfig") as string|undefined, 'jitsiInterfaceConfig');
|
||||
|
||||
jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig);
|
||||
this.connection.setSilent(true);
|
||||
mediaManager.hideGameOverlay();
|
||||
|
||||
|
@ -1265,5 +1297,14 @@ export class GameScene extends ResizableScene implements CenterListener {
|
|||
mediaManager.removeTriggerCloseJitsiFrameButton('close-jisi');
|
||||
}
|
||||
|
||||
private bannedUser(){
|
||||
this.cleanupClosingScene();
|
||||
this.userInputManager.clearAllKeys();
|
||||
this.scene.start(ErrorSceneName, {
|
||||
title: 'Banned',
|
||||
subTitle: 'You was banned of WorkAdventure',
|
||||
message: 'If you want more information, you can contact us: workadventure@thecodingmachine.com'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -43,8 +43,9 @@ export class ActionableItem {
|
|||
}
|
||||
this.isSelectable = true;
|
||||
if (this.sprite.pipeline) {
|
||||
this.sprite.setPipeline(OutlinePipeline.KEY);
|
||||
this.sprite.pipeline.set2f('uTextureSize', this.sprite.texture.getSourceImage().width, this.sprite.texture.getSourceImage().height);
|
||||
// Commented out to try to fix MacOS issue
|
||||
/*this.sprite.setPipeline(OutlinePipeline.KEY);
|
||||
this.sprite.pipeline.set2f('uTextureSize', this.sprite.texture.getSourceImage().width, this.sprite.texture.getSourceImage().height);*/
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,7 +57,8 @@ export class ActionableItem {
|
|||
return;
|
||||
}
|
||||
this.isSelectable = false;
|
||||
this.sprite.resetPipeline();
|
||||
// Commented out to try to fix MacOS issue
|
||||
//this.sprite.resetPipeline();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
41
front/src/Phaser/Login/AbstractCharacterScene.ts
Normal file
41
front/src/Phaser/Login/AbstractCharacterScene.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import {ResizableScene} from "./ResizableScene";
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||
import {loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager";
|
||||
import {CharacterTexture} from "../../Connexion/LocalUser";
|
||||
|
||||
export abstract class AbstractCharacterScene extends ResizableScene {
|
||||
|
||||
loadCustomSceneSelectCharacters() : Promise<BodyResourceDescriptionInterface[]> {
|
||||
const textures = this.getTextures();
|
||||
const promises : Promise<BodyResourceDescriptionInterface>[] = [];
|
||||
if (textures) {
|
||||
for (const texture of textures) {
|
||||
if (texture.level === -1) {
|
||||
continue;
|
||||
}
|
||||
promises.push(loadCustomTexture(this.load, texture));
|
||||
}
|
||||
}
|
||||
return Promise.all(promises)
|
||||
}
|
||||
|
||||
loadSelectSceneCharacters() : Promise<BodyResourceDescriptionInterface[]> {
|
||||
const textures = this.getTextures();
|
||||
const promises: Promise<BodyResourceDescriptionInterface>[] = [];
|
||||
if (textures) {
|
||||
for (const texture of textures) {
|
||||
if (texture.level !== -1) {
|
||||
continue;
|
||||
}
|
||||
promises.push(loadCustomTexture(this.load, texture));
|
||||
}
|
||||
}
|
||||
return Promise.all(promises)
|
||||
}
|
||||
|
||||
private getTextures() : CharacterTexture[]|undefined{
|
||||
const localUser = localUserStore.getLocalUser();
|
||||
return localUser?.textures;
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ import {ResizableScene} from "./ResizableScene";
|
|||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
import {addLoader} from "../Components/Loader";
|
||||
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||
import {AbstractCharacterScene} from "./AbstractCharacterScene";
|
||||
|
||||
export const CustomizeSceneName = "CustomizeScene";
|
||||
|
||||
|
@ -20,7 +21,7 @@ enum CustomizeTextures{
|
|||
arrowUp = "arrow_up",
|
||||
}
|
||||
|
||||
export class CustomizeScene extends ResizableScene {
|
||||
export class CustomizeScene extends AbstractCharacterScene {
|
||||
|
||||
private textField!: TextField;
|
||||
private enterField!: TextField;
|
||||
|
@ -48,29 +49,21 @@ export class CustomizeScene extends ResizableScene {
|
|||
|
||||
preload() {
|
||||
addLoader(this);
|
||||
|
||||
this.layers = loadAllLayers(this.load);
|
||||
this.loadCustomSceneSelectCharacters().then((bodyResourceDescriptions) => {
|
||||
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
|
||||
if(!bodyResourceDescription.level){
|
||||
throw 'Texture level is null';
|
||||
}
|
||||
this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription);
|
||||
});
|
||||
});
|
||||
|
||||
this.load.image(CustomizeTextures.arrowRight, "resources/objects/arrow_right.png");
|
||||
this.load.image(CustomizeTextures.icon, "resources/logos/tcm_full.png");
|
||||
this.load.image(CustomizeTextures.arrowUp, "resources/objects/arrow_up.png");
|
||||
this.load.bitmapFont(CustomizeTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
|
||||
|
||||
this.layers = loadAllLayers(this.load);
|
||||
|
||||
const localUser = localUserStore.getLocalUser();
|
||||
|
||||
const textures = localUser?.textures;
|
||||
if (textures) {
|
||||
for (const texture of textures) {
|
||||
if(texture.level === -1){
|
||||
continue;
|
||||
}
|
||||
loadCustomTexture(this.load, texture);
|
||||
const name = 'customCharacterTexture'+texture.id;
|
||||
this.layers[texture.level].unshift({
|
||||
name,
|
||||
img: texture.url
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
create() {
|
||||
|
|
|
@ -2,7 +2,6 @@ import {gameManager} from "../Game/GameManager";
|
|||
import {TextField} from "../Components/TextField";
|
||||
import {TextInput} from "../Components/TextInput";
|
||||
import Image = Phaser.GameObjects.Image;
|
||||
import {cypressAsserter} from "../../Cypress/CypressAsserter";
|
||||
import {SelectCharacterSceneName} from "./SelectCharacterScene";
|
||||
import {ResizableScene} from "./ResizableScene";
|
||||
|
||||
|
@ -29,16 +28,13 @@ export class LoginScene extends ResizableScene {
|
|||
}
|
||||
|
||||
preload() {
|
||||
cypressAsserter.preloadStarted();
|
||||
//this.load.image(LoginTextures.playButton, "resources/objects/play_button.png");
|
||||
this.load.image(LoginTextures.icon, "resources/logos/tcm_full.png");
|
||||
// Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap
|
||||
this.load.bitmapFont(LoginTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
|
||||
cypressAsserter.preloadFinished();
|
||||
}
|
||||
|
||||
create() {
|
||||
cypressAsserter.initStarted();
|
||||
|
||||
this.textField = new TextField(this, this.game.renderer.width / 2, 50, 'Enter your name:');
|
||||
this.nameInput = new TextInput(this, this.game.renderer.width / 2, 70, 8, this.name,(text: string) => {
|
||||
|
@ -59,8 +55,6 @@ export class LoginScene extends ResizableScene {
|
|||
}
|
||||
this.login(this.name);
|
||||
});
|
||||
|
||||
cypressAsserter.initFinished();
|
||||
}
|
||||
|
||||
update(time: number, delta: number): void {
|
||||
|
|
|
@ -9,6 +9,7 @@ import {localUserStore} from "../../Connexion/LocalUserStore";
|
|||
import {loadAllDefaultModels, loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager";
|
||||
import {addLoader} from "../Components/Loader";
|
||||
import {BodyResourceDescriptionInterface} from "../Entity/PlayerTextures";
|
||||
import {AbstractCharacterScene} from "./AbstractCharacterScene";
|
||||
|
||||
|
||||
//todo: put this constants in a dedicated file
|
||||
|
@ -21,7 +22,7 @@ enum LoginTextures {
|
|||
customizeButtonSelected = "customize_button_selected"
|
||||
}
|
||||
|
||||
export class SelectCharacterScene extends ResizableScene {
|
||||
export class SelectCharacterScene extends AbstractCharacterScene {
|
||||
private readonly nbCharactersPerRow = 6;
|
||||
private textField!: TextField;
|
||||
private pressReturnField!: TextField;
|
||||
|
@ -44,6 +45,13 @@ export class SelectCharacterScene extends ResizableScene {
|
|||
|
||||
preload() {
|
||||
addLoader(this);
|
||||
|
||||
this.loadSelectSceneCharacters().then((bodyResourceDescriptions) => {
|
||||
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
|
||||
this.playerModels.push(bodyResourceDescription);
|
||||
});
|
||||
})
|
||||
|
||||
this.load.image(LoginTextures.playButton, "resources/objects/play_button.png");
|
||||
this.load.image(LoginTextures.icon, "resources/logos/tcm_full.png");
|
||||
// Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap
|
||||
|
@ -52,17 +60,7 @@ export class SelectCharacterScene extends ResizableScene {
|
|||
this.load.image(LoginTextures.customizeButton, 'resources/objects/customize.png');
|
||||
this.load.image(LoginTextures.customizeButtonSelected, 'resources/objects/customize_selected.png');
|
||||
|
||||
const localUser = localUserStore.getLocalUser();
|
||||
const textures = localUser?.textures;
|
||||
if (textures) {
|
||||
for (const texture of textures) {
|
||||
if(texture.level !== -1){
|
||||
continue;
|
||||
}
|
||||
const name = loadCustomTexture(this.load, texture);
|
||||
this.playerModels.push({name: name, img: texture.url});
|
||||
}
|
||||
}
|
||||
addLoader(this);
|
||||
}
|
||||
|
||||
create() {
|
||||
|
@ -127,7 +125,7 @@ export class SelectCharacterScene extends ResizableScene {
|
|||
|
||||
/*create user*/
|
||||
this.createCurrentPlayer();
|
||||
|
||||
|
||||
const playerNumber = localUserStore.getPlayerCharacterIndex();
|
||||
if (playerNumber && playerNumber !== -1) {
|
||||
this.selectedRectangleXPos = playerNumber % this.nbCharactersPerRow;
|
||||
|
|
|
@ -3,7 +3,9 @@ import {SelectCharacterScene, SelectCharacterSceneName} from "../Login/SelectCha
|
|||
import {gameManager} from "../Game/GameManager";
|
||||
import {localUserStore} from "../../Connexion/LocalUserStore";
|
||||
import {mediaManager} from "../../WebRtc/MediaManager";
|
||||
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager";
|
||||
import {gameReportKey, gameReportRessource, ReportMenu} from "./ReportMenu";
|
||||
import {connectionManager} from "../../Connexion/ConnectionManager";
|
||||
import {GameConnexionTypes} from "../../Url/UrlManager";
|
||||
|
||||
export const MenuSceneName = 'MenuScene';
|
||||
const gameMenuKey = 'gameMenu';
|
||||
|
@ -21,6 +23,7 @@ export class MenuScene extends Phaser.Scene {
|
|||
private menuElement!: Phaser.GameObjects.DOMElement;
|
||||
private gameQualityMenuElement!: Phaser.GameObjects.DOMElement;
|
||||
private gameShareElement!: Phaser.GameObjects.DOMElement;
|
||||
private gameReportElement!: ReportMenu;
|
||||
private sideMenuOpened = false;
|
||||
private settingsMenuOpened = false;
|
||||
private gameShareOpened = false;
|
||||
|
@ -40,20 +43,21 @@ export class MenuScene extends Phaser.Scene {
|
|||
this.load.html(gameMenuIconKey, 'resources/html/gameMenuIcon.html');
|
||||
this.load.html(gameSettingsMenuKey, 'resources/html/gameQualityMenu.html');
|
||||
this.load.html(gameShare, 'resources/html/gameShare.html');
|
||||
this.load.html(gameReportKey, gameReportRessource);
|
||||
}
|
||||
|
||||
create() {
|
||||
this.menuElement = this.add.dom(closedSideMenuX, 30).createFromCache(gameMenuKey);
|
||||
this.menuElement.setOrigin(0);
|
||||
this.revealMenusAfterInit(this.menuElement, 'gameMenu');
|
||||
MenuScene.revealMenusAfterInit(this.menuElement, 'gameMenu');
|
||||
|
||||
const middleX = (window.innerWidth / 3) - 298;
|
||||
this.gameQualityMenuElement = this.add.dom(middleX, -400).createFromCache(gameSettingsMenuKey);
|
||||
this.revealMenusAfterInit(this.gameQualityMenuElement, 'gameQuality');
|
||||
MenuScene.revealMenusAfterInit(this.gameQualityMenuElement, 'gameQuality');
|
||||
|
||||
|
||||
this.gameShareElement = this.add.dom(middleX, -400).createFromCache(gameShare);
|
||||
this.revealMenusAfterInit(this.gameShareElement, gameShare);
|
||||
MenuScene.revealMenusAfterInit(this.gameShareElement, gameShare);
|
||||
this.gameShareElement.addListener('click');
|
||||
this.gameShareElement.on('click', (event:MouseEvent) => {
|
||||
event.preventDefault();
|
||||
|
@ -64,6 +68,12 @@ export class MenuScene extends Phaser.Scene {
|
|||
}
|
||||
});
|
||||
|
||||
this.gameReportElement = new ReportMenu(this, connectionManager.getConnexionType === GameConnexionTypes.anonymous);
|
||||
mediaManager.setShowReportModalCallBacks((userId, userName) => {
|
||||
this.closeAll();
|
||||
this.gameReportElement.open(parseInt(userId), userName);
|
||||
});
|
||||
|
||||
this.input.keyboard.on('keyup-TAB', () => {
|
||||
this.sideMenuOpened ? this.closeSideMenu() : this.openSideMenu();
|
||||
});
|
||||
|
@ -77,7 +87,8 @@ export class MenuScene extends Phaser.Scene {
|
|||
this.menuElement.on('click', this.onMenuClick.bind(this));
|
||||
}
|
||||
|
||||
private revealMenusAfterInit(menuElement: Phaser.GameObjects.DOMElement, rootDomId: string) {
|
||||
//todo put this method in a parent menuElement class
|
||||
static revealMenusAfterInit(menuElement: Phaser.GameObjects.DOMElement, rootDomId: string) {
|
||||
//Dom elements will appear inside the viewer screen when creating before being moved out of it, which create a flicker effect.
|
||||
//To prevent this, we put a 'hidden' attribute on the root element, we remove it only after the init is done.
|
||||
setTimeout(() => {
|
||||
|
@ -98,6 +109,11 @@ export class MenuScene extends Phaser.Scene {
|
|||
const adminSection = this.menuElement.getChildByID('adminConsoleSection') as HTMLElement;
|
||||
adminSection.hidden = false;
|
||||
}
|
||||
//TODO bind with future metadata of card
|
||||
//if (connectionManager.getConnexionType === GameConnexionTypes.anonymous){
|
||||
const adminSection = this.menuElement.getChildByID('socialLinks') as HTMLElement;
|
||||
adminSection.hidden = false;
|
||||
//}
|
||||
this.tweens.add({
|
||||
targets: this.menuElement,
|
||||
x: openedSideMenuX,
|
||||
|
@ -222,6 +238,9 @@ export class MenuScene extends Phaser.Scene {
|
|||
}
|
||||
|
||||
private onMenuClick(event:MouseEvent) {
|
||||
if((event?.target as HTMLInputElement).classList.contains('not-button')){
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
|
||||
switch ((event?.target as HTMLInputElement).id) {
|
||||
|
@ -280,5 +299,6 @@ export class MenuScene extends Phaser.Scene {
|
|||
private closeAll(){
|
||||
this.closeGameQualityMenu();
|
||||
this.closeGameShare();
|
||||
this.gameReportElement.close();
|
||||
}
|
||||
}
|
||||
|
|
119
front/src/Phaser/Menu/ReportMenu.ts
Normal file
119
front/src/Phaser/Menu/ReportMenu.ts
Normal file
|
@ -0,0 +1,119 @@
|
|||
import {MenuScene} from "./MenuScene";
|
||||
import {gameManager} from "../Game/GameManager";
|
||||
import {blackListManager} from "../../WebRtc/BlackListManager";
|
||||
|
||||
export const gameReportKey = 'gameReport';
|
||||
export const gameReportRessource = 'resources/html/gameReport.html';
|
||||
|
||||
export class ReportMenu extends Phaser.GameObjects.DOMElement {
|
||||
private opened: boolean = false;
|
||||
|
||||
private userId!: number;
|
||||
private userName!: string|undefined;
|
||||
private anonymous: boolean;
|
||||
|
||||
constructor(scene: Phaser.Scene, anonymous: boolean) {
|
||||
super(scene, -2000, -2000);
|
||||
this.anonymous = anonymous;
|
||||
this.createFromCache(gameReportKey);
|
||||
|
||||
if (this.anonymous) {
|
||||
const divToHide = this.getChildByID('reportSection') as HTMLElement;
|
||||
divToHide.hidden = true;
|
||||
const textToHide = this.getChildByID('askActionP') as HTMLElement;
|
||||
textToHide.hidden = true;
|
||||
}
|
||||
|
||||
scene.add.existing(this);
|
||||
MenuScene.revealMenusAfterInit(this, gameReportKey);
|
||||
|
||||
this.addListener('click');
|
||||
this.on('click', (event:MouseEvent) => {
|
||||
event.preventDefault();
|
||||
if ((event?.target as HTMLInputElement).id === 'gameReportFormSubmit') {
|
||||
this.submitReport();
|
||||
} else if((event?.target as HTMLInputElement).id === 'gameReportFormCancel') {
|
||||
this.close();
|
||||
} else if((event?.target as HTMLInputElement).id === 'toggleBlockButton') {
|
||||
this.toggleBlock();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public open(userId: number, userName: string|undefined): void {
|
||||
if (this.opened) {
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
this.userId = userId;
|
||||
this.userName = userName;
|
||||
|
||||
const mainEl = this.getChildByID('gameReport') as HTMLElement;
|
||||
this.x = this.getCenteredX(mainEl);
|
||||
this.y = this.getHiddenY(mainEl);
|
||||
|
||||
const gameTitleReport = this.getChildByID('nameReported') as HTMLElement;
|
||||
gameTitleReport.innerText = userName || '';
|
||||
|
||||
const blockButton = this.getChildByID('toggleBlockButton') as HTMLElement;
|
||||
blockButton.innerText = blackListManager.isBlackListed(this.userId) ? 'Unblock this user' : 'Block this user';
|
||||
|
||||
this.opened = true;
|
||||
|
||||
gameManager.getCurrentGameScene(this.scene).userInputManager.clearAllKeys();
|
||||
|
||||
this.scene.tweens.add({
|
||||
targets: this,
|
||||
y: this.getCenteredY(mainEl),
|
||||
duration: 1000,
|
||||
ease: 'Power3'
|
||||
});
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this.opened = false;
|
||||
gameManager.getCurrentGameScene(this.scene).userInputManager.initKeyBoardEvent();
|
||||
const mainEl = this.getChildByID('gameReport') as HTMLElement;
|
||||
this.scene.tweens.add({
|
||||
targets: this,
|
||||
y: this.getHiddenY(mainEl),
|
||||
duration: 1000,
|
||||
ease: 'Power3'
|
||||
});
|
||||
}
|
||||
|
||||
//todo: into a parent class?
|
||||
private getCenteredX(mainEl: HTMLElement): number {
|
||||
return window.innerWidth / 4 - mainEl.clientWidth / 2;
|
||||
}
|
||||
private getHiddenY(mainEl: HTMLElement): number {
|
||||
return - mainEl.clientHeight - 50;
|
||||
}
|
||||
private getCenteredY(mainEl: HTMLElement): number {
|
||||
return window.innerHeight / 4 - mainEl.clientHeight / 2;
|
||||
}
|
||||
|
||||
private toggleBlock(): void {
|
||||
!blackListManager.isBlackListed(this.userId) ? blackListManager.blackList(this.userId) : blackListManager.cancelBlackList(this.userId);
|
||||
this.close();
|
||||
}
|
||||
|
||||
private submitReport(): void{
|
||||
const gamePError = this.getChildByID('gameReportErr') as HTMLParagraphElement;
|
||||
gamePError.innerText = '';
|
||||
gamePError.style.display = 'none';
|
||||
const gameTextArea = this.getChildByID('gameReportInput') as HTMLInputElement;
|
||||
const gameIdUserReported = this.getChildByID('idUserReported') as HTMLInputElement;
|
||||
if(!gameTextArea || !gameTextArea.value || !gameIdUserReported || !gameIdUserReported.value){
|
||||
gamePError.innerText = 'Report message cannot to be empty.';
|
||||
gamePError.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
gameManager.getCurrentGameScene(this.scene).connection.emitReportPlayerMessage(
|
||||
parseInt(gameIdUserReported.value),
|
||||
gameTextArea.value
|
||||
);
|
||||
this.close();
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
export class OutlinePipeline extends Phaser.Renderer.WebGL.Pipelines.MultiPipeline {
|
||||
export class OutlinePipeline extends Phaser.Renderer.WebGL.Pipelines.TextureTintPipeline {
|
||||
|
||||
// the unique id of this pipeline
|
||||
public static readonly KEY = 'Outline';
|
||||
|
|
|
@ -59,7 +59,11 @@ export class UserInputManager {
|
|||
];
|
||||
}
|
||||
|
||||
clearAllInputKeyboard(){
|
||||
clearAllListeners(){
|
||||
this.Scene.input.keyboard.removeAllListeners();
|
||||
}
|
||||
|
||||
clearAllKeys(){
|
||||
this.Scene.input.keyboard.removeAllKeys();
|
||||
}
|
||||
|
||||
|
|
24
front/src/WebRtc/BlackListManager.ts
Normal file
24
front/src/WebRtc/BlackListManager.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import {Subject} from 'rxjs';
|
||||
|
||||
class BlackListManager {
|
||||
private list: number[] = [];
|
||||
public onBlockStream: Subject<number> = new Subject();
|
||||
public onUnBlockStream: Subject<number> = new Subject();
|
||||
|
||||
isBlackListed(userId: number): boolean {
|
||||
return this.list.find((data) => data === userId) !== undefined;
|
||||
}
|
||||
|
||||
blackList(userId: number): void {
|
||||
if (this.isBlackListed(userId)) return;
|
||||
this.list.push(userId);
|
||||
this.onBlockStream.next(userId);
|
||||
}
|
||||
|
||||
cancelBlackList(userId: number): void {
|
||||
this.list.splice(this.list.findIndex(data => data === userId), 1);
|
||||
this.onUnBlockStream.next(userId);
|
||||
}
|
||||
}
|
||||
|
||||
export const blackListManager = new BlackListManager();
|
|
@ -42,7 +42,7 @@ class CoWebsiteManager {
|
|||
this.opened = iframeStates.opened;
|
||||
}
|
||||
|
||||
public loadCoWebsite(url: string): void {
|
||||
public loadCoWebsite(url: string, allowPolicy?: string): void {
|
||||
this.load();
|
||||
this.cowebsiteDiv.innerHTML = `<button class="close-btn" id="cowebsite-close">
|
||||
<img src="resources/logos/close.svg">
|
||||
|
@ -56,6 +56,9 @@ class CoWebsiteManager {
|
|||
const iframe = document.createElement('iframe');
|
||||
iframe.id = 'cowebsite-iframe';
|
||||
iframe.src = url;
|
||||
if (allowPolicy) {
|
||||
iframe.allow = allowPolicy;
|
||||
}
|
||||
const onloadPromise = new Promise((resolve) => {
|
||||
iframe.onload = () => resolve();
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {HtmlUtils} from "./HtmlUtils";
|
||||
import {mediaManager, ReportCallback} from "./MediaManager";
|
||||
import {mediaManager, ReportCallback, ShowReportCallBack} from "./MediaManager";
|
||||
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||
import {connectionManager} from "../Connexion/ConnectionManager";
|
||||
import {GameConnexionTypes} from "../Url/UrlManager";
|
||||
|
@ -61,7 +61,7 @@ export class DiscussionManager {
|
|||
const inputMessage: HTMLInputElement = document.createElement('input');
|
||||
inputMessage.onfocus = () => {
|
||||
if(this.userInputManager) {
|
||||
this.userInputManager.clearAllInputKeyboard();
|
||||
this.userInputManager.clearAllKeys();
|
||||
}
|
||||
}
|
||||
inputMessage.onblur = () => {
|
||||
|
@ -99,7 +99,7 @@ export class DiscussionManager {
|
|||
name: string|undefined,
|
||||
img?: string|undefined,
|
||||
isMe: boolean = false,
|
||||
reportCallback?: ReportCallback
|
||||
showReportCallBack?: ShowReportCallBack
|
||||
) {
|
||||
const divParticipant: HTMLDivElement = document.createElement('div');
|
||||
divParticipant.classList.add('participant');
|
||||
|
@ -128,8 +128,8 @@ export class DiscussionManager {
|
|||
reportBanUserAction.classList.add('report-btn')
|
||||
reportBanUserAction.innerText = 'Report';
|
||||
reportBanUserAction.addEventListener('click', () => {
|
||||
if(reportCallback) {
|
||||
mediaManager.showReportModal(`${userId}`, name ?? '', reportCallback);
|
||||
if(showReportCallBack) {
|
||||
showReportCallBack(`${userId}`, name);
|
||||
}else{
|
||||
console.info('report feature is not activated!');
|
||||
}
|
||||
|
|
|
@ -1,27 +1,45 @@
|
|||
export class HtmlUtils {
|
||||
public static getElementByIdOrFail<T extends HTMLElement>(id: string): T {
|
||||
const elem = document.getElementById(id);
|
||||
if (elem === null) {
|
||||
throw new Error("Cannot find HTML element with id '"+id+"'");
|
||||
if (HtmlUtils.isHtmlElement<T>(elem)) {
|
||||
return elem;
|
||||
}
|
||||
// FIXME: does not check the type of the returned type
|
||||
return elem as T;
|
||||
throw new Error("Cannot find HTML element with id '"+id+"'");
|
||||
}
|
||||
|
||||
public static querySelectorOrFail<T extends HTMLElement>(selector: string): T {
|
||||
const elem = document.querySelector<T>(selector);
|
||||
if (HtmlUtils.isHtmlElement<T>(elem)) {
|
||||
return elem;
|
||||
}
|
||||
throw new Error("Cannot find HTML element with selector '"+selector+"'");
|
||||
}
|
||||
|
||||
public static removeElementByIdOrFail<T extends HTMLElement>(id: string): T {
|
||||
const elem = document.getElementById(id);
|
||||
if (elem === null) {
|
||||
throw new Error("Cannot find HTML element with id '"+id+"'");
|
||||
if (HtmlUtils.isHtmlElement<T>(elem)) {
|
||||
elem.remove();
|
||||
return elem;
|
||||
}
|
||||
// FIXME: does not check the type of the returned type
|
||||
elem.remove();
|
||||
return elem as T;
|
||||
throw new Error("Cannot find HTML element with id '"+id+"'");
|
||||
}
|
||||
|
||||
private static escapeHtml(html: string): string {
|
||||
const text = document.createTextNode(html);
|
||||
const p = document.createElement('p');
|
||||
p.appendChild(text);
|
||||
return p.innerHTML;
|
||||
}
|
||||
|
||||
public static urlify(text: string): string {
|
||||
const urlRegex = /(https?:\/\/[^\s]+)/g;
|
||||
text = HtmlUtils.escapeHtml(text);
|
||||
return text.replace(urlRegex, (url: string) => {
|
||||
return '<a href="' + url + '" target="_blank" style=":visited {color: white}">' + url + '</a>';
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
private static isHtmlElement<T extends HTMLElement>(elem: HTMLElement | null): elem is T {
|
||||
return elem !== null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,11 +3,21 @@ import {mediaManager} from "./MediaManager";
|
|||
import {coWebsiteManager} from "./CoWebsiteManager";
|
||||
declare const window:any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
|
||||
const interfaceConfig = {
|
||||
const getDefaultConfig = () => {
|
||||
return {
|
||||
startWithAudioMuted: !mediaManager.constraintsMedia.audio,
|
||||
startWithVideoMuted: mediaManager.constraintsMedia.video === false,
|
||||
prejoinPageEnabled: false
|
||||
}
|
||||
}
|
||||
|
||||
const defaultInterfaceConfig = {
|
||||
SHOW_CHROME_EXTENSION_BANNER: false,
|
||||
MOBILE_APP_PROMO: false,
|
||||
|
||||
HIDE_INVITE_MORE_HEADER: true,
|
||||
DISABLE_JOIN_LEAVE_NOTIFICATIONS: true,
|
||||
DISABLE_VIDEO_BACKGROUND: true,
|
||||
|
||||
// Note: hiding brand does not seem to work, we probably need to put this on the server side.
|
||||
SHOW_BRAND_WATERMARK: false,
|
||||
|
@ -25,13 +35,40 @@ const interfaceConfig = {
|
|||
],
|
||||
};
|
||||
|
||||
const slugify = (...args: (string | number)[]): string => {
|
||||
const value = args.join(' ')
|
||||
|
||||
return value
|
||||
.normalize('NFD') // split an accented letter in the base letter and the accent
|
||||
.replace(/[\u0300-\u036f]/g, '') // remove all previously split accents
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/[^a-z0-9 ]/g, '') // remove all chars not letters, numbers and spaces (to be replaced)
|
||||
.replace(/\s+/g, '-') // separator
|
||||
}
|
||||
|
||||
class JitsiFactory {
|
||||
private jitsiApi: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
private audioCallback = this.onAudioChange.bind(this);
|
||||
private videoCallback = this.onVideoChange.bind(this);
|
||||
|
||||
public start(roomName: string, playerName:string, jwt?: string): void {
|
||||
|
||||
/**
|
||||
* Slugifies the room name and prepends the room name with the instance
|
||||
*/
|
||||
public getRoomName(roomName: string, instance: string): string {
|
||||
return slugify(instance.replace('/', '-') + "-" + roomName);
|
||||
}
|
||||
|
||||
public start(roomName: string, playerName:string, jwt?: string, config?: object, interfaceConfig?: object): void {
|
||||
coWebsiteManager.insertCoWebsite((cowebsiteDiv => {
|
||||
// Jitsi meet external API maintains some data in local storage
|
||||
// which is sent via the appData URL parameter when joining a
|
||||
// conference. Problem is that this data grows indefinitely. Thus
|
||||
// after some time the URLs get so huge that loading the iframe
|
||||
// becomes slow and eventually breaks completely. Thus lets just
|
||||
// clear jitsi local storage before starting a new conference.
|
||||
window.localStorage.removeItem("jitsiLocalStorage");
|
||||
|
||||
const domain = JITSI_URL;
|
||||
const options: any = { // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
roomName: roomName,
|
||||
|
@ -39,17 +76,13 @@ class JitsiFactory {
|
|||
width: "100%",
|
||||
height: "100%",
|
||||
parentNode: cowebsiteDiv,
|
||||
configOverwrite: {
|
||||
startWithAudioMuted: !mediaManager.constraintsMedia.audio,
|
||||
startWithVideoMuted: mediaManager.constraintsMedia.video === false,
|
||||
prejoinPageEnabled: false
|
||||
},
|
||||
interfaceConfigOverwrite: interfaceConfig,
|
||||
configOverwrite: {...config, ...getDefaultConfig()},
|
||||
interfaceConfigOverwrite: {...defaultInterfaceConfig, ...interfaceConfig}
|
||||
};
|
||||
if (!options.jwt) {
|
||||
delete options.jwt;
|
||||
}
|
||||
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
options.onload = () => resolve(); //we want for the iframe to be loaded before triggering animations.
|
||||
setTimeout(() => resolve(), 2000); //failsafe in case the iframe is deleted before loading or too long to load
|
||||
|
@ -87,7 +120,6 @@ class JitsiFactory {
|
|||
mediaManager.enableCamera();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const jitsiFactory = new JitsiFactory();
|
||||
export const jitsiFactory = new JitsiFactory();
|
||||
|
|
|
@ -218,7 +218,7 @@ class LayoutManager {
|
|||
* Tries to find the biggest available box of remaining space (this is a space where we can center the character)
|
||||
*/
|
||||
public findBiggestAvailableArray(): {xStart: number, yStart: number, xEnd: number, yEnd: number} {
|
||||
const game = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('game');
|
||||
const game = HtmlUtils.querySelectorOrFail<HTMLCanvasElement>('#game canvas');
|
||||
if (this.mode === LayoutMode.VideoChat) {
|
||||
const children = document.querySelectorAll<HTMLDivElement>('div.chat-mode > div');
|
||||
const htmlChildren = Array.from(children.values());
|
||||
|
|
|
@ -3,6 +3,7 @@ import {HtmlUtils} from "./HtmlUtils";
|
|||
import {discussionManager, SendMessageCallback} from "./DiscussionManager";
|
||||
import {UserInputManager} from "../Phaser/UserInput/UserInputManager";
|
||||
import {VIDEO_QUALITY_SELECT} from "../Administration/ConsoleGlobalMessageManager";
|
||||
import {UserSimplePeerInterface} from "./SimplePeer";
|
||||
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
|
||||
const localValueVideo = localStorage.getItem(VIDEO_QUALITY_SELECT);
|
||||
|
@ -23,9 +24,9 @@ export type UpdatedLocalStreamCallback = (media: MediaStream|null) => void;
|
|||
export type StartScreenSharingCallback = (media: MediaStream) => void;
|
||||
export type StopScreenSharingCallback = (media: MediaStream) => void;
|
||||
export type ReportCallback = (message: string) => void;
|
||||
export type ShowReportCallBack = (userId: string, userName: string|undefined) => void;
|
||||
|
||||
// TODO: Split MediaManager in 2 classes: MediaManagerUI (in charge of HTML) and MediaManager (singleton in charge of the camera only)
|
||||
// TODO: verify that microphone event listeners are not triggered plenty of time NOW (since MediaManager is created many times!!!!)
|
||||
export class MediaManager {
|
||||
localStream: MediaStream|null = null;
|
||||
localScreenCapture: MediaStream|null = null;
|
||||
|
@ -46,6 +47,7 @@ export class MediaManager {
|
|||
updatedLocalStreamCallBacks : Set<UpdatedLocalStreamCallback> = new Set<UpdatedLocalStreamCallback>();
|
||||
startScreenSharingCallBacks : Set<StartScreenSharingCallback> = new Set<StartScreenSharingCallback>();
|
||||
stopScreenSharingCallBacks : Set<StopScreenSharingCallback> = new Set<StopScreenSharingCallback>();
|
||||
showReportModalCallBacks : Set<ShowReportCallBack> = new Set<ShowReportCallBack>();
|
||||
private microphoneBtn: HTMLDivElement;
|
||||
private cinemaBtn: HTMLDivElement;
|
||||
private monitorBtn: HTMLDivElement;
|
||||
|
@ -469,8 +471,9 @@ export class MediaManager {
|
|||
return this.getCamera();
|
||||
}
|
||||
|
||||
addActiveVideo(userId: string, reportCallBack: ReportCallback|undefined, userName: string = ""){
|
||||
addActiveVideo(user: UserSimplePeerInterface, userName: string = ""){
|
||||
this.webrtcInAudio.play();
|
||||
const userId = ''+user.userId
|
||||
|
||||
userName = userName.toUpperCase();
|
||||
const color = this.getColorByString(userName);
|
||||
|
@ -480,33 +483,39 @@ export class MediaManager {
|
|||
<div class="connecting-spinner"></div>
|
||||
<div class="rtc-error" style="display: none"></div>
|
||||
<i id="name-${userId}" style="background-color: ${color};">${userName}</i>
|
||||
<img id="microphone-${userId}" src="resources/logos/microphone-close.svg">
|
||||
` +
|
||||
((reportCallBack!==undefined)?`<img id="report-${userId}" class="report active" src="resources/logos/report.svg">`:'')
|
||||
+
|
||||
`<video id="${userId}" autoplay></video>
|
||||
<img id="microphone-${userId}" title="mute" src="resources/logos/microphone-close.svg">
|
||||
<button id="report-${userId}" class="report">
|
||||
<img title="report this user" src="resources/logos/report.svg">
|
||||
<span>Report/Block</span>
|
||||
</button>
|
||||
<video id="${userId}" autoplay></video>
|
||||
<img src="resources/logos/blockSign.svg" id="blocking-${userId}" class="block-logo">
|
||||
</div>
|
||||
`;
|
||||
|
||||
layoutManager.add(DivImportance.Normal, userId, html);
|
||||
|
||||
if (reportCallBack) {
|
||||
const reportBtn = HtmlUtils.getElementByIdOrFail<HTMLDivElement>(`report-${userId}`);
|
||||
reportBtn.addEventListener('click', (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
this.showReportModal(userId, userName, reportCallBack);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail<HTMLVideoElement>(userId));
|
||||
|
||||
//permit to create participant in discussion part
|
||||
this.addNewParticipant(userId, userName, undefined, reportCallBack);
|
||||
const showReportUser = () => {
|
||||
for(const callBack of this.showReportModalCallBacks){
|
||||
callBack(userId, userName);
|
||||
}
|
||||
};
|
||||
this.addNewParticipant(userId, userName, undefined, showReportUser);
|
||||
|
||||
const reportBanUserActionEl: HTMLImageElement = HtmlUtils.getElementByIdOrFail<HTMLImageElement>(`report-${userId}`);
|
||||
reportBanUserActionEl.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
showReportUser();
|
||||
});
|
||||
}
|
||||
|
||||
addScreenSharingActiveVideo(userId: string, divImportance: DivImportance = DivImportance.Important){
|
||||
|
||||
userId = `screen-sharing-${userId}`;
|
||||
userId = this.getScreenSharingId(userId);
|
||||
const html = `
|
||||
<div id="div-${userId}" class="video-container">
|
||||
<video id="${userId}" autoplay></video>
|
||||
|
@ -517,7 +526,11 @@ export class MediaManager {
|
|||
|
||||
this.remoteVideo.set(userId, HtmlUtils.getElementByIdOrFail<HTMLVideoElement>(userId));
|
||||
}
|
||||
|
||||
|
||||
private getScreenSharingId(userId: string): string {
|
||||
return `screen-sharing-${userId}`;
|
||||
}
|
||||
|
||||
disabledMicrophoneByUserId(userId: number){
|
||||
const element = document.getElementById(`microphone-${userId}`);
|
||||
if(!element){
|
||||
|
@ -556,6 +569,10 @@ export class MediaManager {
|
|||
}
|
||||
}
|
||||
|
||||
toggleBlockLogo(userId: number, show: boolean): void {
|
||||
const blockLogoElement = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('blocking-'+userId);
|
||||
show ? blockLogoElement.classList.add('active') : blockLogoElement.classList.remove('active');
|
||||
}
|
||||
addStreamRemoteVideo(userId: string, stream : MediaStream): void {
|
||||
const remoteVideo = this.remoteVideo.get(userId);
|
||||
if (remoteVideo === undefined) {
|
||||
|
@ -565,12 +582,12 @@ export class MediaManager {
|
|||
}
|
||||
addStreamRemoteScreenSharing(userId: string, stream : MediaStream){
|
||||
// In the case of screen sharing (going both ways), we may need to create the HTML element if it does not exist yet
|
||||
const remoteVideo = this.remoteVideo.get(`screen-sharing-${userId}`);
|
||||
const remoteVideo = this.remoteVideo.get(this.getScreenSharingId(userId));
|
||||
if (remoteVideo === undefined) {
|
||||
this.addScreenSharingActiveVideo(userId);
|
||||
}
|
||||
|
||||
this.addStreamRemoteVideo(`screen-sharing-${userId}`, stream);
|
||||
this.addStreamRemoteVideo(this.getScreenSharingId(userId), stream);
|
||||
}
|
||||
|
||||
removeActiveVideo(userId: string){
|
||||
|
@ -581,7 +598,7 @@ export class MediaManager {
|
|||
this.removeParticipant(userId);
|
||||
}
|
||||
removeActiveScreenSharingVideo(userId: string) {
|
||||
this.removeActiveVideo(`screen-sharing-${userId}`)
|
||||
this.removeActiveVideo(this.getScreenSharingId(userId))
|
||||
}
|
||||
|
||||
playWebrtcOutSound(): void {
|
||||
|
@ -617,7 +634,7 @@ export class MediaManager {
|
|||
errorDiv.style.display = 'block';
|
||||
}
|
||||
isErrorScreenSharing(userId: string): void {
|
||||
this.isError(`screen-sharing-${userId}`);
|
||||
this.isError(this.getScreenSharingId(userId));
|
||||
}
|
||||
|
||||
|
||||
|
@ -645,65 +662,8 @@ export class MediaManager {
|
|||
return color;
|
||||
}
|
||||
|
||||
public showReportModal(userId: string, userName: string, reportCallBack: ReportCallback){
|
||||
//create report text area
|
||||
const mainContainer = HtmlUtils.getElementByIdOrFail<HTMLDivElement>('main-container');
|
||||
|
||||
const divReport = document.createElement('div');
|
||||
divReport.classList.add('modal-report-user');
|
||||
|
||||
const inputHidden = document.createElement('input');
|
||||
inputHidden.id = 'input-report-user';
|
||||
inputHidden.type = 'hidden';
|
||||
inputHidden.value = userId;
|
||||
divReport.appendChild(inputHidden);
|
||||
|
||||
const titleMessage = document.createElement('p');
|
||||
titleMessage.id = 'title-report-user';
|
||||
titleMessage.innerText = 'Open a report';
|
||||
divReport.appendChild(titleMessage);
|
||||
|
||||
const bodyMessage = document.createElement('p');
|
||||
bodyMessage.id = 'body-report-user';
|
||||
bodyMessage.innerText = `You are about to open a report regarding an offensive conduct from user ${userName.toUpperCase()}. Please explain to us how you think ${userName.toUpperCase()} breached the code of conduct.`;
|
||||
divReport.appendChild(bodyMessage);
|
||||
|
||||
const imgReportUser = document.createElement('img');
|
||||
imgReportUser.id = 'img-report-user';
|
||||
imgReportUser.src = 'resources/logos/report.svg';
|
||||
divReport.appendChild(imgReportUser);
|
||||
|
||||
const textareaUser = document.createElement('textarea');
|
||||
textareaUser.id = 'textarea-report-user';
|
||||
textareaUser.placeholder = 'Write ...';
|
||||
divReport.appendChild(textareaUser);
|
||||
|
||||
const buttonReport = document.createElement('button');
|
||||
buttonReport.id = 'button-save-report-user';
|
||||
buttonReport.innerText = 'Report';
|
||||
buttonReport.addEventListener('click', () => {
|
||||
if(!textareaUser.value){
|
||||
textareaUser.style.border = '1px solid red'
|
||||
return;
|
||||
}
|
||||
reportCallBack(textareaUser.value);
|
||||
divReport.remove();
|
||||
});
|
||||
divReport.appendChild(buttonReport);
|
||||
|
||||
const buttonCancel = document.createElement('img');
|
||||
buttonCancel.id = 'cancel-report-user';
|
||||
buttonCancel.src = 'resources/logos/close.svg';
|
||||
buttonCancel.addEventListener('click', () => {
|
||||
divReport.remove();
|
||||
});
|
||||
divReport.appendChild(buttonCancel);
|
||||
|
||||
mainContainer.appendChild(divReport);
|
||||
}
|
||||
|
||||
public addNewParticipant(userId: number|string, name: string|undefined, img?: string, reportCallBack?: ReportCallback){
|
||||
discussionManager.addParticipant(userId, name, img, false, reportCallBack);
|
||||
public addNewParticipant(userId: number|string, name: string|undefined, img?: string, showReportUserCallBack?: ShowReportCallBack){
|
||||
discussionManager.addParticipant(userId, name, img, false, showReportUserCallBack);
|
||||
}
|
||||
|
||||
public removeParticipant(userId: number|string){
|
||||
|
@ -769,6 +729,10 @@ export class MediaManager {
|
|||
this.checkActiveUser();
|
||||
}, this.focused ? 10000 : 1000);
|
||||
}
|
||||
|
||||
public setShowReportModalCallBacks(callback: ShowReportCallBack){
|
||||
this.showReportModalCallBacks.add(callback);
|
||||
}
|
||||
}
|
||||
|
||||
export const mediaManager = new MediaManager();
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import * as SimplePeerNamespace from "simple-peer";
|
||||
import {mediaManager} from "./MediaManager";
|
||||
import {TURN_SERVER, TURN_USER, TURN_PASSWORD} from "../Enum/EnvironmentVariable";
|
||||
import {STUN_SERVER, TURN_SERVER, TURN_USER, TURN_PASSWORD} from "../Enum/EnvironmentVariable";
|
||||
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import {MESSAGE_TYPE_CONSTRAINT} from "./VideoPeer";
|
||||
import {UserSimplePeerInterface} from "./SimplePeer";
|
||||
|
||||
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
|
||||
|
||||
|
@ -16,25 +17,28 @@ export class ScreenSharingPeer extends Peer {
|
|||
private isReceivingStream:boolean = false;
|
||||
public toClose: boolean = false;
|
||||
public _connected: boolean = false;
|
||||
private userId: number;
|
||||
|
||||
constructor(private userId: number, initiator: boolean, private connection: RoomConnection) {
|
||||
constructor(user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection) {
|
||||
super({
|
||||
initiator: initiator ? initiator : false,
|
||||
reconnectTimer: 10000,
|
||||
config: {
|
||||
iceServers: [
|
||||
{
|
||||
urls: 'stun:stun.l.google.com:19302'
|
||||
urls: STUN_SERVER.split(',')
|
||||
},
|
||||
{
|
||||
TURN_SERVER !== '' ? {
|
||||
urls: TURN_SERVER.split(','),
|
||||
username: TURN_USER,
|
||||
credential: TURN_PASSWORD
|
||||
},
|
||||
]
|
||||
username: user.webRtcUser || TURN_USER,
|
||||
credential: user.webRtcPassword || TURN_PASSWORD
|
||||
} : undefined,
|
||||
].filter((value) => value !== undefined)
|
||||
}
|
||||
});
|
||||
|
||||
this.userId = user.userId;
|
||||
|
||||
//start listen signal for the peer connection
|
||||
this.on('signal', (data: unknown) => {
|
||||
this.sendWebrtcScreenSharingSignal(data);
|
||||
|
|
|
@ -9,13 +9,18 @@ import {
|
|||
UpdatedLocalStreamCallback
|
||||
} from "./MediaManager";
|
||||
import {ScreenSharingPeer} from "./ScreenSharingPeer";
|
||||
import {MESSAGE_TYPE_CONSTRAINT, MESSAGE_TYPE_MESSAGE, VideoPeer} from "./VideoPeer";
|
||||
import {MESSAGE_TYPE_BLOCKED, MESSAGE_TYPE_CONSTRAINT, MESSAGE_TYPE_MESSAGE, VideoPeer} from "./VideoPeer";
|
||||
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import {connectionManager} from "../Connexion/ConnectionManager";
|
||||
import {GameConnexionTypes} from "../Url/UrlManager";
|
||||
import {blackListManager} from "./BlackListManager";
|
||||
|
||||
export interface UserSimplePeerInterface{
|
||||
userId: number;
|
||||
name?: string;
|
||||
initiator?: boolean;
|
||||
webRtcUser?: string|undefined;
|
||||
webRtcPassword?: string|undefined;
|
||||
}
|
||||
|
||||
export interface PeerConnectionListener {
|
||||
|
@ -36,6 +41,7 @@ export class SimplePeer {
|
|||
private readonly sendLocalScreenSharingStreamCallback: StartScreenSharingCallback;
|
||||
private readonly stopLocalScreenSharingStreamCallback: StopScreenSharingCallback;
|
||||
private readonly peerConnectionListeners: Array<PeerConnectionListener> = new Array<PeerConnectionListener>();
|
||||
private readonly userId: number;
|
||||
|
||||
constructor(private Connection: RoomConnection, private enableReporting: boolean, private myName: string) {
|
||||
// We need to go through this weird bound function pointer in order to be able to "free" this reference later.
|
||||
|
@ -46,6 +52,7 @@ export class SimplePeer {
|
|||
mediaManager.onUpdateLocalStream(this.sendLocalVideoStreamCallback);
|
||||
mediaManager.onStartScreenSharing(this.sendLocalScreenSharingStreamCallback);
|
||||
mediaManager.onStopScreenSharing(this.stopLocalScreenSharingStreamCallback);
|
||||
this.userId = Connection.getUserId();
|
||||
this.initialise();
|
||||
}
|
||||
|
||||
|
@ -89,15 +96,14 @@ export class SimplePeer {
|
|||
});
|
||||
}
|
||||
|
||||
private receiveWebrtcStart(user: UserSimplePeerInterface) {
|
||||
//this.WebRtcRoomId = data.roomId;
|
||||
private receiveWebrtcStart(user: UserSimplePeerInterface): void {
|
||||
this.Users.push(user);
|
||||
// Note: the clients array contain the list of all clients (even the ones we are already connected to in case a user joints a group)
|
||||
// So we can receive a request we already had before. (which will abort at the first line of createPeerConnection)
|
||||
// This would be symmetrical to the way we handle disconnection.
|
||||
|
||||
|
||||
//start connection
|
||||
console.log('receiveWebrtcStart. Initiator: ', user.initiator)
|
||||
//console.log('receiveWebrtcStart. Initiator: ', user.initiator)
|
||||
if(!user.initiator){
|
||||
return;
|
||||
}
|
||||
|
@ -134,17 +140,13 @@ export class SimplePeer {
|
|||
|
||||
mediaManager.removeActiveVideo("" + user.userId);
|
||||
|
||||
const reportCallback = this.enableReporting ? (comment: string) => {
|
||||
this.reportUser(user.userId, comment);
|
||||
} : undefined;
|
||||
mediaManager.addActiveVideo(user, name);
|
||||
|
||||
mediaManager.addActiveVideo("" + user.userId, reportCallback, name);
|
||||
|
||||
const peer = new VideoPeer(user.userId, user.initiator ? user.initiator : false, this.Connection);
|
||||
const peer = new VideoPeer(user, user.initiator ? user.initiator : false, this.Connection);
|
||||
|
||||
//permit to send message
|
||||
mediaManager.addSendMessageCallback(user.userId,(message: string) => {
|
||||
peer.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_MESSAGE, name: this.myName.toUpperCase(), message: message})));
|
||||
peer.write(new Buffer(JSON.stringify({type: MESSAGE_TYPE_MESSAGE, name: this.myName.toUpperCase(), userId: this.userId, message: message})));
|
||||
});
|
||||
|
||||
peer.toClose = false;
|
||||
|
@ -189,7 +191,7 @@ export class SimplePeer {
|
|||
mediaManager.addScreenSharingActiveVideo("" + user.userId);
|
||||
}
|
||||
|
||||
const peer = new ScreenSharingPeer(user.userId, user.initiator ? user.initiator : false, this.Connection);
|
||||
const peer = new ScreenSharingPeer(user, user.initiator ? user.initiator : false, this.Connection);
|
||||
this.PeerScreenSharingConnectionArray.set(user.userId, peer);
|
||||
|
||||
for (const peerConnectionListener of this.peerConnectionListeners) {
|
||||
|
@ -300,6 +302,7 @@ export class SimplePeer {
|
|||
}
|
||||
|
||||
private receiveWebrtcScreenSharingSignal(data: WebRtcSignalReceivedMessageInterface) {
|
||||
if (blackListManager.isBlackListed(data.userId)) return;
|
||||
console.log("receiveWebrtcScreenSharingSignal", data);
|
||||
try {
|
||||
//if offer type, create peer connection
|
||||
|
@ -391,14 +394,8 @@ export class SimplePeer {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered locally when clicking on the report button
|
||||
*/
|
||||
public reportUser(userId: number, message: string) {
|
||||
this.Connection.emitReportPlayerMessage(userId, message)
|
||||
}
|
||||
|
||||
private sendLocalScreenSharingStreamToUser(userId: number): void {
|
||||
if (blackListManager.isBlackListed(userId)) return;
|
||||
// If a connection already exists with user (because it is already sharing a screen with us... let's use this connection)
|
||||
if (this.PeerScreenSharingConnectionArray.has(userId)) {
|
||||
this.pushScreenSharingToRemoteUser(userId);
|
||||
|
|
|
@ -1,65 +1,57 @@
|
|||
import * as SimplePeerNamespace from "simple-peer";
|
||||
import {mediaManager} from "./MediaManager";
|
||||
import {TURN_PASSWORD, TURN_SERVER, TURN_USER} from "../Enum/EnvironmentVariable";
|
||||
import {STUN_SERVER, TURN_PASSWORD, TURN_SERVER, TURN_USER} from "../Enum/EnvironmentVariable";
|
||||
import {RoomConnection} from "../Connexion/RoomConnection";
|
||||
import {blackListManager} from "./BlackListManager";
|
||||
import {Subscription} from "rxjs";
|
||||
import {UserSimplePeerInterface} from "./SimplePeer";
|
||||
|
||||
const Peer: SimplePeerNamespace.SimplePeer = require('simple-peer');
|
||||
|
||||
export const MESSAGE_TYPE_CONSTRAINT = 'constraint';
|
||||
export const MESSAGE_TYPE_MESSAGE = 'message';
|
||||
export const MESSAGE_TYPE_BLOCKED = 'blocked';
|
||||
export const MESSAGE_TYPE_UNBLOCKED = 'unblocked';
|
||||
/**
|
||||
* A peer connection used to transmit video / audio signals between 2 peers.
|
||||
*/
|
||||
export class VideoPeer extends Peer {
|
||||
public toClose: boolean = false;
|
||||
public _connected: boolean = false;
|
||||
private remoteStream!: MediaStream;
|
||||
private blocked: boolean = false;
|
||||
private userId: number;
|
||||
private userName: string;
|
||||
private onBlockSubscribe: Subscription;
|
||||
private onUnBlockSubscribe: Subscription;
|
||||
|
||||
constructor(public userId: number, initiator: boolean, private connection: RoomConnection) {
|
||||
constructor(public user: UserSimplePeerInterface, initiator: boolean, private connection: RoomConnection) {
|
||||
super({
|
||||
initiator: initiator ? initiator : false,
|
||||
reconnectTimer: 10000,
|
||||
config: {
|
||||
iceServers: [
|
||||
{
|
||||
urls: 'stun:stun.l.google.com:19302'
|
||||
urls: STUN_SERVER.split(',')
|
||||
},
|
||||
{
|
||||
TURN_SERVER !== '' ? {
|
||||
urls: TURN_SERVER.split(','),
|
||||
username: TURN_USER,
|
||||
credential: TURN_PASSWORD
|
||||
},
|
||||
]
|
||||
username: user.webRtcUser || TURN_USER,
|
||||
credential: user.webRtcPassword || TURN_PASSWORD
|
||||
} : undefined,
|
||||
].filter((value) => value !== undefined)
|
||||
}
|
||||
});
|
||||
|
||||
console.log('PEER SETUP ', {
|
||||
initiator: initiator ? initiator : false,
|
||||
reconnectTimer: 10000,
|
||||
config: {
|
||||
iceServers: [
|
||||
{
|
||||
urls: 'stun:stun.l.google.com:19302'
|
||||
},
|
||||
{
|
||||
urls: TURN_SERVER.split(','),
|
||||
username: TURN_USER,
|
||||
credential: TURN_PASSWORD
|
||||
},
|
||||
]
|
||||
}
|
||||
});
|
||||
this.userId = user.userId;
|
||||
this.userName = user.name || '';
|
||||
|
||||
//start listen signal for the peer connection
|
||||
this.on('signal', (data: unknown) => {
|
||||
this.sendWebrtcSignal(data);
|
||||
});
|
||||
|
||||
this.on('stream', (stream: MediaStream) => {
|
||||
this.stream(stream);
|
||||
});
|
||||
|
||||
/*peer.on('track', (track: MediaStreamTrack, stream: MediaStream) => {
|
||||
});*/
|
||||
this.on('stream', (stream: MediaStream) => this.stream(stream));
|
||||
|
||||
this.on('close', () => {
|
||||
this._connected = false;
|
||||
|
@ -70,7 +62,7 @@ export class VideoPeer extends Peer {
|
|||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
this.on('error', (err: any) => {
|
||||
console.error(`error => ${this.userId} => ${err.code}`, err);
|
||||
mediaManager.isError("" + userId);
|
||||
mediaManager.isError("" + this.userId);
|
||||
});
|
||||
|
||||
this.on('connect', () => {
|
||||
|
@ -81,8 +73,6 @@ export class VideoPeer extends Peer {
|
|||
|
||||
this.on('data', (chunk: Buffer) => {
|
||||
const message = JSON.parse(chunk.toString('utf8'));
|
||||
console.log("data", message);
|
||||
|
||||
if(message.type === MESSAGE_TYPE_CONSTRAINT) {
|
||||
if (message.audio) {
|
||||
mediaManager.enabledMicrophoneByUserId(this.userId);
|
||||
|
@ -95,8 +85,19 @@ export class VideoPeer extends Peer {
|
|||
} else {
|
||||
mediaManager.disabledVideoByUserId(this.userId);
|
||||
}
|
||||
} else if(message.type === 'message') {
|
||||
mediaManager.addNewMessage(message.name, message.message);
|
||||
} else if(message.type === MESSAGE_TYPE_MESSAGE) {
|
||||
if (!blackListManager.isBlackListed(message.userId)) {
|
||||
mediaManager.addNewMessage(message.name, message.message);
|
||||
}
|
||||
} else if(message.type === MESSAGE_TYPE_BLOCKED) {
|
||||
//FIXME when A blacklists B, the output stream from A is muted in B's js client. This is insecure since B can manipulate the code to unmute A stream.
|
||||
// Find a way to block A's output stream in A's js client
|
||||
//However, the output stream stream B is correctly blocked in A client
|
||||
this.blocked = true;
|
||||
this.toggleRemoteStream(false);
|
||||
} else if(message.type === MESSAGE_TYPE_UNBLOCKED) {
|
||||
this.blocked = false;
|
||||
this.toggleRemoteStream(true);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -105,6 +106,31 @@ export class VideoPeer extends Peer {
|
|||
});
|
||||
|
||||
this.pushVideoToRemoteUser();
|
||||
this.onBlockSubscribe = blackListManager.onBlockStream.subscribe((userId) => {
|
||||
if (userId === this.userId) {
|
||||
this.toggleRemoteStream(false);
|
||||
this.sendBlockMessage(true);
|
||||
}
|
||||
});
|
||||
this.onUnBlockSubscribe = blackListManager.onUnBlockStream.subscribe((userId) => {
|
||||
if (userId === this.userId) {
|
||||
this.toggleRemoteStream(true);
|
||||
this.sendBlockMessage(false);
|
||||
}
|
||||
});
|
||||
|
||||
if (blackListManager.isBlackListed(this.userId)) {
|
||||
this.sendBlockMessage(true)
|
||||
}
|
||||
}
|
||||
|
||||
private sendBlockMessage(blocking: boolean) {
|
||||
this.write(new Buffer(JSON.stringify({type: blocking ? MESSAGE_TYPE_BLOCKED : MESSAGE_TYPE_UNBLOCKED, name: this.userName.toUpperCase(), userId: this.userId, message: ''})));
|
||||
}
|
||||
|
||||
private toggleRemoteStream(enable: boolean) {
|
||||
this.remoteStream.getTracks().forEach(track => track.enabled = enable);
|
||||
mediaManager.toggleBlockLogo(this.userId, !enable);
|
||||
}
|
||||
|
||||
private sendWebrtcSignal(data: unknown) {
|
||||
|
@ -120,13 +146,13 @@ export class VideoPeer extends Peer {
|
|||
*/
|
||||
private stream(stream: MediaStream) {
|
||||
try {
|
||||
this.remoteStream = stream;
|
||||
if (blackListManager.isBlackListed(this.userId) || this.blocked) {
|
||||
this.toggleRemoteStream(false);
|
||||
}
|
||||
mediaManager.addStreamRemoteVideo("" + this.userId, stream);
|
||||
}catch (err){
|
||||
console.error(err);
|
||||
//Force add streem video
|
||||
/*setTimeout(() => {
|
||||
this.stream(stream);
|
||||
}, 500);*/ //todo: find a way to prevent infinite regression.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -139,6 +165,8 @@ export class VideoPeer extends Peer {
|
|||
if(!this.toClose){
|
||||
return;
|
||||
}
|
||||
this.onBlockSubscribe.unsubscribe();
|
||||
this.onUnBlockSubscribe.unsubscribe();
|
||||
mediaManager.removeActiveVideo("" + this.userId);
|
||||
// FIXME: I don't understand why "Closing connection with" message is displayed TWICE before "Nb users in peerConnectionArray"
|
||||
// I do understand the method closeConnection is called twice, but I don't understand how they manage to run in parallel.
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import 'phaser';
|
||||
import GameConfig = Phaser.Types.Core.GameConfig;
|
||||
import {DEBUG_MODE, JITSI_URL, RESOLUTION} from "./Enum/EnvironmentVariable";
|
||||
import {cypressAsserter} from "./Cypress/CypressAsserter";
|
||||
import {LoginScene} from "./Phaser/Login/LoginScene";
|
||||
import {ReconnectingScene} from "./Phaser/Reconnecting/ReconnectingScene";
|
||||
import {SelectCharacterScene} from "./Phaser/Login/SelectCharacterScene";
|
||||
|
@ -53,8 +52,28 @@ const fps : Phaser.Types.Core.FPSConfig = {
|
|||
smoothStep: false
|
||||
}
|
||||
|
||||
// the ?phaserMode=canvas parameter can be used to force Canvas usage
|
||||
const params = new URLSearchParams(document.location.search.substring(1));
|
||||
const phaserMode = params.get("phaserMode");
|
||||
let mode: number;
|
||||
switch (phaserMode) {
|
||||
case 'auto':
|
||||
case null:
|
||||
mode = Phaser.AUTO;
|
||||
break;
|
||||
case 'canvas':
|
||||
mode = Phaser.CANVAS;
|
||||
break;
|
||||
case 'webgl':
|
||||
mode = Phaser.WEBGL;
|
||||
break;
|
||||
default:
|
||||
throw new Error('phaserMode parameter must be one of "auto", "canvas" or "webgl"');
|
||||
}
|
||||
|
||||
|
||||
const config: GameConfig = {
|
||||
type: Phaser.AUTO,
|
||||
type: mode,
|
||||
title: "WorkAdventure",
|
||||
width: width / RESOLUTION,
|
||||
height: height / RESOLUTION,
|
||||
|
@ -65,6 +84,11 @@ const config: GameConfig = {
|
|||
dom: {
|
||||
createContainer: true
|
||||
},
|
||||
render: {
|
||||
pixelArt: true,
|
||||
roundPixels: true,
|
||||
antialias: false
|
||||
},
|
||||
physics: {
|
||||
default: "arcade",
|
||||
arcade: {
|
||||
|
@ -73,16 +97,15 @@ const config: GameConfig = {
|
|||
},
|
||||
callbacks: {
|
||||
postBoot: game => {
|
||||
const renderer = game.renderer;
|
||||
// Commented out to try to fix MacOS bug
|
||||
/*const renderer = game.renderer;
|
||||
if (renderer instanceof WebGLRenderer) {
|
||||
renderer.pipelines.add(OutlinePipeline.KEY, new OutlinePipeline(game));
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
cypressAsserter.gameStarted();
|
||||
|
||||
const game = new Phaser.Game(config);
|
||||
|
||||
window.addEventListener('resize', function (event) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue