Merge branch 'gamestate-api-read' of github.com:jonnytest1/workadventure into metadataScriptingApi

This commit is contained in:
GRL 2021-05-18 15:18:35 +02:00
commit 201fcf6afa
16 changed files with 9165 additions and 319 deletions

8393
front/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,16 @@
import * as tg from "generic-type-guard";
export const isHasDataLayerChangedEvent =
new tg.IsInterface().withProperties({
data: tg.isObject
}).get();
/**
* A message sent from the game to the iFrame when the data of the layers change after the iFrame send a message to the game that it want to listen to the data of the layers
*/
export type DataLayerEvent = tg.GuardedType<typeof isHasDataLayerChangedEvent>;
export type HasDataLayerChangedEventCallback = (event: DataLayerEvent) => void

View File

@ -0,0 +1,27 @@
import * as tg from "generic-type-guard";
/*export const isPositionState = new tg.IsInterface().withProperties({
x: tg.isNumber,
y: tg.isNumber
}).get()
export const isPlayerState = new tg.IsInterface()
.withStringIndexSignature(
new tg.IsInterface().withProperties({
position: isPositionState,
pusherId: tg.isUnion(tg.isNumber, tg.isUndefined)
}).get()
).get()
export type PlayerStateObject = tg.GuardedType<typeof isPlayerState>;*/
export const isGameStateEvent =
new tg.IsInterface().withProperties({
roomId: tg.isString,
mapUrl: tg.isString,
uuid: tg.isUnion(tg.isString, tg.isUndefined),
startLayerName: tg.isUnion(tg.isString, tg.isNull)
}).get();
/**
* A message sent from the game to the iFrame when the gameState is got by the script
*/
export type GameStateEvent = tg.GuardedType<typeof isGameStateEvent>;

View File

@ -0,0 +1,19 @@
import * as tg from "generic-type-guard";
export const isHasPlayerMovedEvent =
new tg.IsInterface().withProperties({
direction: tg.isString,
moving: tg.isBoolean,
x: tg.isNumber,
y: tg.isNumber
}).get();
/**
* A message sent from the game to the iFrame when the player move after the iFrame send a message to the game that it want to listen to the position of the player
*/
export type HasPlayerMovedEvent = tg.GuardedType<typeof isHasPlayerMovedEvent>;
export type HasPlayerMovedEventCallback = (event: HasPlayerMovedEvent) => void

View File

@ -1,24 +1,25 @@
import type { GameStateEvent } from './GameStateEvent';
import type { ButtonClickedEvent } from './ButtonClickedEvent'; import type { ButtonClickedEvent } from './ButtonClickedEvent';
import type { ChatEvent } from './ChatEvent'; import type { ChatEvent } from './ChatEvent';
import type { ClosePopupEvent } from './ClosePopupEvent'; import type { ClosePopupEvent } from './ClosePopupEvent';
import type { EnterLeaveEvent } from './EnterLeaveEvent'; import type { EnterLeaveEvent } from './EnterLeaveEvent';
import type { GoToPageEvent } from './GoToPageEvent'; import type { GoToPageEvent } from './GoToPageEvent';
import type { HasPlayerMovedEvent } from './HasPlayerMovedEvent';
import type { OpenCoWebSiteEvent } from './OpenCoWebSiteEvent'; import type { OpenCoWebSiteEvent } from './OpenCoWebSiteEvent';
import type { OpenPopupEvent } from './OpenPopupEvent'; import type { OpenPopupEvent } from './OpenPopupEvent';
import type { OpenTabEvent } from './OpenTabEvent'; import type { OpenTabEvent } from './OpenTabEvent';
import type { UserInputChatEvent } from './UserInputChatEvent'; import type { UserInputChatEvent } from './UserInputChatEvent';
import type { HasDataLayerChangedEvent } from "./HasDataLayerChangedEvent";
import type { LayerEvent } from './LayerEvent'; import type { LayerEvent } from './LayerEvent';
import type { SetPropertyEvent } from "./setPropertyEvent"; import type { SetPropertyEvent } from "./setPropertyEvent";
export interface TypedMessageEvent<T> extends MessageEvent { export interface TypedMessageEvent<T> extends MessageEvent {
data: T data: T
} }
export type IframeEventMap = { export type IframeEventMap = {
//getState: GameStateEvent, getState: GameStateEvent,
// updateTile: UpdateTileEvent // updateTile: UpdateTileEvent
chat: ChatEvent, chat: ChatEvent,
openPopup: OpenPopupEvent openPopup: OpenPopupEvent
@ -31,6 +32,8 @@ export type IframeEventMap = {
restorePlayerControls: null restorePlayerControls: null
displayBubble: null displayBubble: null
removeBubble: null removeBubble: null
onPlayerMove: undefined
onDataLayerChange: undefined
showLayer: LayerEvent showLayer: LayerEvent
hideLayer: LayerEvent hideLayer: LayerEvent
setProperty: SetPropertyEvent setProperty: SetPropertyEvent
@ -49,7 +52,9 @@ export interface IframeResponseEventMap {
enterEvent: EnterLeaveEvent enterEvent: EnterLeaveEvent
leaveEvent: EnterLeaveEvent leaveEvent: EnterLeaveEvent
buttonClickedEvent: ButtonClickedEvent buttonClickedEvent: ButtonClickedEvent
// gameState: GameStateEvent gameState: GameStateEvent
hasPlayerMoved: HasPlayerMovedEvent
hasDataLayerChanged: HasDataLayerChangedEvent
} }
export interface IframeResponseEvent<T extends keyof IframeResponseEventMap> { export interface IframeResponseEvent<T extends keyof IframeResponseEventMap> {
type: T; type: T;

View File

@ -13,6 +13,12 @@ import { IframeEventMap, IframeEvent, IframeResponseEvent, IframeResponseEventMa
import type { UserInputChatEvent } from "./Events/UserInputChatEvent"; import type { UserInputChatEvent } from "./Events/UserInputChatEvent";
import { isLayerEvent, LayerEvent } from "./Events/LayerEvent"; import { isLayerEvent, LayerEvent } from "./Events/LayerEvent";
import { isSetPropertyEvent, SetPropertyEvent} from "./Events/setPropertyEvent"; import { isSetPropertyEvent, SetPropertyEvent} from "./Events/setPropertyEvent";
import { GameStateEvent } from './Events/GameStateEvent';
import { deepFreezeClone as deepFreezeClone } from '../utility';
import { HasPlayerMovedEvent } from './Events/HasPlayerMovedEvent';
import { Math } from 'phaser';
import { HasDataLayerChangedEvent } from "./Events/HasDataLayerChangedEvent";
/** /**
@ -20,6 +26,7 @@ import { isSetPropertyEvent, SetPropertyEvent} from "./Events/setPropertyEvent";
* Also allows to send messages to those iframes. * Also allows to send messages to those iframes.
*/ */
class IframeListener { class IframeListener {
private readonly _chatStream: Subject<ChatEvent> = new Subject(); private readonly _chatStream: Subject<ChatEvent> = new Subject();
public readonly chatStream = this._chatStream.asObservable(); public readonly chatStream = this._chatStream.asObservable();
@ -62,8 +69,14 @@ class IframeListener {
private readonly _setPropertyStream: Subject<SetPropertyEvent> = new Subject(); private readonly _setPropertyStream: Subject<SetPropertyEvent> = new Subject();
public readonly setPropertyStream = this._setPropertyStream.asObservable(); public readonly setPropertyStream = this._setPropertyStream.asObservable();
private readonly _gameStateStream: Subject<void> = new Subject();
public readonly gameStateStream = this._gameStateStream.asObservable();
private readonly iframes = new Set<HTMLIFrameElement>(); private readonly iframes = new Set<HTMLIFrameElement>();
private readonly scripts = new Map<string, HTMLIFrameElement>(); private readonly scripts = new Map<string, HTMLIFrameElement>();
private sendPlayerMove: boolean = false;
private sendDataLayerChange: boolean = false;
init() { init() {
window.addEventListener("message", (message: TypedMessageEvent<IframeEvent<keyof IframeEventMap>>) => { window.addEventListener("message", (message: TypedMessageEvent<IframeEvent<keyof IframeEventMap>>) => {
@ -117,12 +130,16 @@ class IframeListener {
} }
else if (payload.type === 'restorePlayerControls') { else if (payload.type === 'restorePlayerControls') {
this._enablePlayerControlStream.next(); this._enablePlayerControlStream.next();
} } else if (payload.type === 'displayBubble') {
else if (payload.type === 'displayBubble') {
this._displayBubbleStream.next(); this._displayBubbleStream.next();
} } else if (payload.type === 'removeBubble') {
else if (payload.type === 'removeBubble') {
this._removeBubbleStream.next(); this._removeBubbleStream.next();
} else if (payload.type == "getState") {
this._gameStateStream.next();
} else if (payload.type == "onPlayerMove") {
this.sendPlayerMove = true
} else if (payload.type == "onDataLayerChange") {
this.sendDataLayerChange = true
} }
} }
@ -131,6 +148,14 @@ class IframeListener {
} }
sendFrozenGameStateEvent(gameStateEvent: GameStateEvent) {
this.postMessage({
'type': 'gameState',
'data': gameStateEvent //deepFreezeClone(gameStateEvent)
});
}
/** /**
* Allows the passed iFrame to send/receive messages via the API. * Allows the passed iFrame to send/receive messages via the API.
*/ */
@ -234,6 +259,24 @@ class IframeListener {
}); });
} }
hasPlayerMoved(event: HasPlayerMovedEvent) {
if (this.sendPlayerMove) {
this.postMessage({
'type': 'hasPlayerMoved',
'data': event
});
}
}
hasDataLayerChanged(event: HasDataLayerChangedEvent) {
if (this.sendDataLayerChange) {
this.postMessage({
'type' : 'hasDataLayerChanged',
'data' : event
});
}
}
sendButtonClickedEvent(popupId: number, buttonId: number): void { sendButtonClickedEvent(popupId: number, buttonId: number): void {
this.postMessage({ this.postMessage({
'type': 'buttonClickedEvent', 'type': 'buttonClickedEvent',

View File

@ -1,4 +1,4 @@
import {PUSHER_URL, UPLOADER_URL} from "../Enum/EnvironmentVariable"; import { PUSHER_URL, UPLOADER_URL } from "../Enum/EnvironmentVariable";
import Axios from "axios"; import Axios from "axios";
import { import {
BatchMessage, BatchMessage,
@ -31,9 +31,9 @@ import {
BanUserMessage BanUserMessage
} from "../Messages/generated/messages_pb" } from "../Messages/generated/messages_pb"
import type {UserSimplePeerInterface} from "../WebRtc/SimplePeer"; import type { UserSimplePeerInterface } from "../WebRtc/SimplePeer";
import Direction = PositionMessage.Direction; import Direction = PositionMessage.Direction;
import {ProtobufClientUtils} from "../Network/ProtobufClientUtils"; import { ProtobufClientUtils } from "../Network/ProtobufClientUtils";
import { import {
EventMessage, EventMessage,
GroupCreatedUpdatedMessageInterface, ItemEventMessageInterface, GroupCreatedUpdatedMessageInterface, ItemEventMessageInterface,
@ -42,23 +42,23 @@ import {
ViewportInterface, WebRtcDisconnectMessageInterface, ViewportInterface, WebRtcDisconnectMessageInterface,
WebRtcSignalReceivedMessageInterface, WebRtcSignalReceivedMessageInterface,
} from "./ConnexionModels"; } from "./ConnexionModels";
import type {BodyResourceDescriptionInterface} from "../Phaser/Entity/PlayerTextures"; import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures";
import {adminMessagesService} from "./AdminMessagesService"; import { adminMessagesService } from "./AdminMessagesService";
import {worldFullMessageStream} from "./WorldFullMessageStream"; import { worldFullMessageStream } from "./WorldFullMessageStream";
import {worldFullWarningStream} from "./WorldFullWarningStream"; import { worldFullWarningStream } from "./WorldFullWarningStream";
import {connectionManager} from "./ConnectionManager"; import { connectionManager } from "./ConnectionManager";
const manualPingDelay = 20000; const manualPingDelay = 20000;
export class RoomConnection implements RoomConnection { export class RoomConnection implements RoomConnection {
private readonly socket: WebSocket; private readonly socket: WebSocket;
private userId: number|null = null; private userId: number | null = null;
private listeners: Map<string, Function[]> = new Map<string, Function[]>(); private listeners: Map<string, Function[]> = new Map<string, Function[]>();
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[] = [];
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;
} }
@ -67,28 +67,27 @@ export class RoomConnection implements RoomConnection {
* @param token A JWT token containing the UUID of the user * @param token A JWT token containing the UUID of the user
* @param roomId The ID of the room in the form "_/[instance]/[map_url]" or "@/[org]/[event]/[map]" * @param roomId The ID of the room in the form "_/[instance]/[map_url]" or "@/[org]/[event]/[map]"
*/ */
public constructor(token: string|null, roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface, companion: string|null) { public constructor(token: string | null, roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface, companion: string | null) {
let url = new URL(PUSHER_URL, window.location.toString()).toString(); let url = new URL(PUSHER_URL, window.location.toString()).toString();
url = url.replace('http://', 'ws://').replace('https://', 'wss://'); url = url.replace('http://', 'ws://').replace('https://', 'wss://');
if (!url.endsWith('/')) { if (!url.endsWith('/')) {
url += '/'; url += '/';
} }
url += 'room'; url += 'room';
url += '?roomId='+(roomId ?encodeURIComponent(roomId):''); url += '?roomId=' + (roomId ? encodeURIComponent(roomId) : '');
url += '&token='+(token ?encodeURIComponent(token):''); url += '&token=' + (token ? encodeURIComponent(token) : '');
url += '&name='+encodeURIComponent(name); url += '&name=' + encodeURIComponent(name);
for (const layer of characterLayers) { for (const layer of characterLayers) {
url += '&characterLayers='+encodeURIComponent(layer); url += '&characterLayers=' + encodeURIComponent(layer);
} }
url += '&x='+Math.floor(position.x); url += '&x=' + Math.floor(position.x);
url += '&y='+Math.floor(position.y); url += '&y=' + Math.floor(position.y);
url += '&top='+Math.floor(viewport.top); url += '&top=' + Math.floor(viewport.top);
url += '&bottom='+Math.floor(viewport.bottom); url += '&bottom=' + Math.floor(viewport.bottom);
url += '&left='+Math.floor(viewport.left); url += '&left=' + Math.floor(viewport.left);
url += '&right='+Math.floor(viewport.right); url += '&right=' + Math.floor(viewport.right);
if (typeof companion === 'string') { if (typeof companion === 'string') {
url += '&companion='+encodeURIComponent(companion); url += '&companion=' + encodeURIComponent(companion);
} }
if (RoomConnection.websocketFactory) { if (RoomConnection.websocketFactory) {
@ -99,7 +98,7 @@ export class RoomConnection implements RoomConnection {
this.socket.binaryType = 'arraybuffer'; this.socket.binaryType = 'arraybuffer';
let interval: ReturnType<typeof setInterval>|undefined = undefined; let interval: ReturnType<typeof setInterval> | undefined = undefined;
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.
@ -153,7 +152,7 @@ export class RoomConnection implements RoomConnection {
} else if (message.hasRoomjoinedmessage()) { } else if (message.hasRoomjoinedmessage()) {
const roomJoinedMessage = message.getRoomjoinedmessage() as RoomJoinedMessage; const roomJoinedMessage = message.getRoomjoinedmessage() as RoomJoinedMessage;
const items: { [itemId: number] : unknown } = {}; const items: { [itemId: number]: unknown } = {};
for (const item of roomJoinedMessage.getItemList()) { for (const item of roomJoinedMessage.getItemList()) {
items[item.getItemid()] = JSON.parse(item.getStatejson()); items[item.getItemid()] = JSON.parse(item.getStatejson());
} }
@ -170,10 +169,10 @@ export class RoomConnection implements RoomConnection {
} else if (message.hasWorldfullmessage()) { } else if (message.hasWorldfullmessage()) {
worldFullMessageStream.onMessage(); worldFullMessageStream.onMessage();
this.closed = true; this.closed = true;
} else if (message.hasWorldconnexionmessage()) { // // } else if (message.hasWorldconnexionmessage()) {
worldFullMessageStream.onMessage(message.getWorldconnexionmessage()?.getMessage()); // worldFullMessageStream.onMessage(message.getWorldconnexionmessage()?.getMessage());
this.closed = true; // this.closed = true;
}else if (message.hasWebrtcsignaltoclientmessage()) { } else if (message.hasWebrtcsignaltoclientmessage()) {
this.dispatch(EventMessage.WEBRTC_SIGNAL, message.getWebrtcsignaltoclientmessage()); this.dispatch(EventMessage.WEBRTC_SIGNAL, message.getWebrtcsignaltoclientmessage());
} else if (message.hasWebrtcscreensharingsignaltoclientmessage()) { } else if (message.hasWebrtcscreensharingsignaltoclientmessage()) {
this.dispatch(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, message.getWebrtcscreensharingsignaltoclientmessage()); this.dispatch(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, message.getWebrtcscreensharingsignaltoclientmessage());
@ -230,7 +229,7 @@ export class RoomConnection implements RoomConnection {
this.closed = true; this.closed = true;
} }
private toPositionMessage(x : number, y : number, direction : string, moving: boolean): PositionMessage { private toPositionMessage(x: number, y: number, direction: string, moving: boolean): PositionMessage {
const positionMessage = new PositionMessage(); const positionMessage = new PositionMessage();
positionMessage.setX(Math.floor(x)); positionMessage.setX(Math.floor(x));
positionMessage.setY(Math.floor(y)); positionMessage.setY(Math.floor(y));
@ -267,8 +266,8 @@ export class RoomConnection implements RoomConnection {
return viewportMessage; return viewportMessage;
} }
public sharePosition(x : number, y : number, direction : string, moving: boolean, viewport: ViewportInterface) : void{ public sharePosition(x: number, y: number, direction: string, moving: boolean, viewport: ViewportInterface): void {
if(!this.socket){ if (!this.socket) {
return; return;
} }
@ -472,7 +471,7 @@ export class RoomConnection implements RoomConnection {
if (this.closed === true || connectionManager.unloading) { if (this.closed === true || connectionManager.unloading) {
return; return;
} }
console.log('Socket closed with code '+event.code+". Reason: "+event.reason); console.log('Socket closed with code ' + event.code + ". Reason: " + event.reason);
if (event.code === 1000) { if (event.code === 1000) {
// Normal closure case // Normal closure case
return; return;
@ -518,8 +517,8 @@ export class RoomConnection implements RoomConnection {
}); });
} }
public uploadAudio(file : FormData){ public uploadAudio(file: FormData) {
return Axios.post(`${UPLOADER_URL}/upload-audio-message`, file).then((res: {data:{}}) => { return Axios.post(`${UPLOADER_URL}/upload-audio-message`, file).then((res: { data: {} }) => {
return res.data; return res.data;
}).catch((err) => { }).catch((err) => {
console.error(err); console.error(err);
@ -550,7 +549,7 @@ export class RoomConnection implements RoomConnection {
}); });
} }
public emitGlobalMessage(message: PlayGlobalMessageInterface){ public emitGlobalMessage(message: PlayGlobalMessageInterface) {
const playGlobalMessage = new PlayGlobalMessage(); const playGlobalMessage = new PlayGlobalMessage();
playGlobalMessage.setId(message.id); playGlobalMessage.setId(message.id);
playGlobalMessage.setType(message.type); playGlobalMessage.setType(message.type);
@ -562,7 +561,7 @@ export class RoomConnection implements RoomConnection {
this.socket.send(clientToServerMessage.serializeBinary().buffer); this.socket.send(clientToServerMessage.serializeBinary().buffer);
} }
public emitReportPlayerMessage(reportedUserId: number, reportComment: string ): void { public emitReportPlayerMessage(reportedUserId: number, reportComment: string): void {
const reportPlayerMessage = new ReportPlayerMessage(); const reportPlayerMessage = new ReportPlayerMessage();
reportPlayerMessage.setReporteduserid(reportedUserId); reportPlayerMessage.setReporteduserid(reportedUserId);
reportPlayerMessage.setReportcomment(reportComment); reportPlayerMessage.setReportcomment(reportComment);
@ -573,7 +572,7 @@ export class RoomConnection implements RoomConnection {
this.socket.send(clientToServerMessage.serializeBinary().buffer); this.socket.send(clientToServerMessage.serializeBinary().buffer);
} }
public emitQueryJitsiJwtMessage(jitsiRoom: string, tag: string|undefined ): void { public emitQueryJitsiJwtMessage(jitsiRoom: string, tag: string | undefined): void {
const queryJitsiJwtMessage = new QueryJitsiJwtMessage(); const queryJitsiJwtMessage = new QueryJitsiJwtMessage();
queryJitsiJwtMessage.setJitsiroom(jitsiRoom); queryJitsiJwtMessage.setJitsiroom(jitsiRoom);
if (tag !== undefined) { if (tag !== undefined) {

View File

@ -8,12 +8,7 @@ import {SelectCharacterSceneName} from "../Login/SelectCharacterScene";
import {EnableCameraSceneName} from "../Login/EnableCameraScene"; import {EnableCameraSceneName} from "../Login/EnableCameraScene";
import {localUserStore} from "../../Connexion/LocalUserStore"; import {localUserStore} from "../../Connexion/LocalUserStore";
export interface HasMovedEvent {
direction: string;
moving: boolean;
x: number;
y: number;
}
/** /**
* This class should be responsible for any scene starting/stopping * This class should be responsible for any scene starting/stopping

View File

@ -9,7 +9,7 @@ import type {
PositionInterface, PositionInterface,
RoomJoinedMessageInterface RoomJoinedMessageInterface
} from "../../Connexion/ConnexionModels"; } from "../../Connexion/ConnexionModels";
import {CurrentGamerInterface, hasMovedEventName, Player} from "../Player/Player"; import { CurrentGamerInterface, hasMovedEventName, Player } from "../Player/Player";
import { import {
DEBUG_MODE, DEBUG_MODE,
JITSI_PRIVATE_MODE, JITSI_PRIVATE_MODE,
@ -25,15 +25,15 @@ import type {
ITiledMapTileLayer, ITiledMapTileLayer,
ITiledTileSet ITiledTileSet
} from "../Map/ITiledMap"; } from "../Map/ITiledMap";
import type {AddPlayerInterface} from "./AddPlayerInterface"; import type { AddPlayerInterface } from "./AddPlayerInterface";
import {PlayerAnimationDirections} from "../Player/Animation"; import { PlayerAnimationDirections } from "../Player/Animation";
import {PlayerMovement} from "./PlayerMovement"; import { PlayerMovement } from "./PlayerMovement";
import {PlayersPositionInterpolator} from "./PlayersPositionInterpolator"; import { PlayersPositionInterpolator } from "./PlayersPositionInterpolator";
import {RemotePlayer} from "../Entity/RemotePlayer"; import { RemotePlayer } from "../Entity/RemotePlayer";
import {Queue} from 'queue-typescript'; 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 {lazyLoadPlayerCharacterTextures, loadCustomTexture} from "../Entity/PlayerTexturesLoadingManager"; import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager";
import { import {
CenterListener, CenterListener,
JITSI_MESSAGE_PROPERTIES, JITSI_MESSAGE_PROPERTIES,
@ -46,56 +46,57 @@ import {
AUDIO_VOLUME_PROPERTY, AUDIO_VOLUME_PROPERTY,
AUDIO_LOOP_PROPERTY AUDIO_LOOP_PROPERTY
} from "../../WebRtc/LayoutManager"; } from "../../WebRtc/LayoutManager";
import {GameMap} from "./GameMap"; import { GameMap } from "./GameMap";
import {coWebsiteManager} from "../../WebRtc/CoWebsiteManager"; import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
import {mediaManager} from "../../WebRtc/MediaManager"; import { mediaManager } from "../../WebRtc/MediaManager";
import type {ItemFactoryInterface} from "../Items/ItemFactoryInterface"; import type { ItemFactoryInterface } from "../Items/ItemFactoryInterface";
import type {ActionableItem} from "../Items/ActionableItem"; import type { ActionableItem } from "../Items/ActionableItem";
import {UserInputManager} from "../UserInput/UserInputManager"; import { UserInputManager } from "../UserInput/UserInputManager";
import type {UserMovedMessage} from "../../Messages/generated/messages_pb"; import type { UserMovedMessage } from "../../Messages/generated/messages_pb";
import {ProtobufClientUtils} from "../../Network/ProtobufClientUtils"; import { ProtobufClientUtils } from "../../Network/ProtobufClientUtils";
import {connectionManager} from "../../Connexion/ConnectionManager"; import { connectionManager } from "../../Connexion/ConnectionManager";
import type {RoomConnection} from "../../Connexion/RoomConnection"; import type { RoomConnection } from "../../Connexion/RoomConnection";
import {GlobalMessageManager} from "../../Administration/GlobalMessageManager"; import { GlobalMessageManager } from "../../Administration/GlobalMessageManager";
import {userMessageManager} from "../../Administration/UserMessageManager"; import { userMessageManager } from "../../Administration/UserMessageManager";
import {ConsoleGlobalMessageManager} from "../../Administration/ConsoleGlobalMessageManager"; import { ConsoleGlobalMessageManager } from "../../Administration/ConsoleGlobalMessageManager";
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"; import { urlManager } from "../../Url/UrlManager";
import {audioManager} from "../../WebRtc/AudioManager"; import { audioManager } from "../../WebRtc/AudioManager";
import {PresentationModeIcon} from "../Components/PresentationModeIcon"; import { PresentationModeIcon } from "../Components/PresentationModeIcon";
import {ChatModeIcon} from "../Components/ChatModeIcon"; import { ChatModeIcon } from "../Components/ChatModeIcon";
import {OpenChatIcon, openChatIconName} from "../Components/OpenChatIcon"; import { OpenChatIcon, openChatIconName } from "../Components/OpenChatIcon";
import {SelectCharacterScene, SelectCharacterSceneName} from "../Login/SelectCharacterScene"; import { SelectCharacterScene, SelectCharacterSceneName } from "../Login/SelectCharacterScene";
import {TextureError} from "../../Exception/TextureError"; import { TextureError } from "../../Exception/TextureError";
import {addLoader} from "../Components/Loader"; import { addLoader } from "../Components/Loader";
import {ErrorSceneName} from "../Reconnecting/ErrorScene"; import { ErrorSceneName } from "../Reconnecting/ErrorScene";
import {localUserStore} from "../../Connexion/LocalUserStore"; import { localUserStore } from "../../Connexion/LocalUserStore";
import {iframeListener} from "../../Api/IframeListener"; import { iframeListener } from "../../Api/IframeListener";
import {HtmlUtils} from "../../WebRtc/HtmlUtils"; import { HtmlUtils } from "../../WebRtc/HtmlUtils";
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;
import GameObject = Phaser.GameObjects.GameObject; import GameObject = Phaser.GameObjects.GameObject;
import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR; import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR;
import DOMElement = Phaser.GameObjects.DOMElement; import DOMElement = Phaser.GameObjects.DOMElement;
import type {Subscription} from "rxjs"; import type { Subscription } from "rxjs";
import {worldFullMessageStream} from "../../Connexion/WorldFullMessageStream"; import { worldFullMessageStream } from "../../Connexion/WorldFullMessageStream";
import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager"; import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager";
import RenderTexture = Phaser.GameObjects.RenderTexture; import RenderTexture = Phaser.GameObjects.RenderTexture;
import Tilemap = Phaser.Tilemaps.Tilemap; import Tilemap = Phaser.Tilemaps.Tilemap;
import {DirtyScene} from "./DirtyScene"; import { DirtyScene } from "./DirtyScene";
import {TextUtils} from "../Components/TextUtils"; import { TextUtils } from "../Components/TextUtils";
import {touchScreenManager} from "../../Touch/TouchScreenManager"; import { touchScreenManager } from "../../Touch/TouchScreenManager";
import {PinchManager} from "../UserInput/PinchManager"; import { PinchManager } from "../UserInput/PinchManager";
import {joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey} from "../Components/MobileJoystick"; import { joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey } from "../Components/MobileJoystick";
import {waScaleManager} from "../Services/WaScaleManager"; import { waScaleManager } from "../Services/WaScaleManager";
import { HasPlayerMovedEvent } from '../../Api/Events/HasPlayerMovedEvent';
import {LayerEvent} from "../../Api/Events/LayerEvent"; import {LayerEvent} from "../../Api/Events/LayerEvent";
import {SetPropertyEvent} from "../../Api/Events/setPropertyEvent"; import {SetPropertyEvent} from "../../Api/Events/setPropertyEve
export interface GameSceneInitInterface { export interface GameSceneInitInterface {
initPosition: PointInterface|null, initPosition: PointInterface | null,
reconnecting: boolean reconnecting: boolean
} }
@ -132,10 +133,10 @@ interface DeleteGroupEventInterface {
const defaultStartLayerName = 'start'; const defaultStartLayerName = 'start';
export class GameScene extends DirtyScene implements CenterListener { export class GameScene extends DirtyScene implements CenterListener {
Terrains : Array<Phaser.Tilemaps.Tileset>; Terrains: Array<Phaser.Tilemaps.Tileset>;
CurrentPlayer!: CurrentGamerInterface; CurrentPlayer!: CurrentGamerInterface;
MapPlayers!: Phaser.Physics.Arcade.Group; MapPlayers!: Phaser.Physics.Arcade.Group;
MapPlayersByKey : Map<number, RemotePlayer> = new Map<number, RemotePlayer>(); MapPlayersByKey: Map<number, RemotePlayer> = new Map<number, RemotePlayer>();
Map!: Phaser.Tilemaps.Tilemap; Map!: Phaser.Tilemaps.Tilemap;
Objects!: Array<Phaser.Physics.Arcade.Sprite>; Objects!: Array<Phaser.Physics.Arcade.Sprite>;
mapFile!: ITiledMap; mapFile!: ITiledMap;
@ -144,10 +145,10 @@ export class GameScene extends DirtyScene implements CenterListener {
startY!: number; startY!: number;
circleTexture!: CanvasTexture; circleTexture!: CanvasTexture;
circleRedTexture!: CanvasTexture; circleRedTexture!: CanvasTexture;
pendingEvents: Queue<InitUserPositionEventInterface|AddPlayerEventInterface|RemovePlayerEventInterface|UserMovedEventInterface|GroupCreatedUpdatedEventInterface|DeleteGroupEventInterface> = new Queue<InitUserPositionEventInterface|AddPlayerEventInterface|RemovePlayerEventInterface|UserMovedEventInterface|GroupCreatedUpdatedEventInterface|DeleteGroupEventInterface>(); pendingEvents: Queue<InitUserPositionEventInterface | AddPlayerEventInterface | RemovePlayerEventInterface | UserMovedEventInterface | GroupCreatedUpdatedEventInterface | DeleteGroupEventInterface> = new Queue<InitUserPositionEventInterface | AddPlayerEventInterface | RemovePlayerEventInterface | UserMovedEventInterface | GroupCreatedUpdatedEventInterface | DeleteGroupEventInterface>();
private initPosition: PositionInterface|null = null; private initPosition: PositionInterface | null = null;
private playersPositionInterpolator = new PlayersPositionInterpolator(); private playersPositionInterpolator = new PlayersPositionInterpolator();
public connection: RoomConnection|undefined; public connection: RoomConnection | undefined;
private simplePeer!: SimplePeer; private simplePeer!: SimplePeer;
private GlobalMessageManager!: GlobalMessageManager; private GlobalMessageManager!: GlobalMessageManager;
public ConsoleGlobalMessageManager!: ConsoleGlobalMessageManager; public ConsoleGlobalMessageManager!: ConsoleGlobalMessageManager;
@ -156,14 +157,14 @@ export class GameScene extends DirtyScene implements CenterListener {
// A promise that will resolve when the "create" method is called (signaling loading is ended) // A promise that will resolve when the "create" method is called (signaling loading is ended)
private createPromise: Promise<void>; private createPromise: Promise<void>;
private createPromiseResolve!: (value?: void | PromiseLike<void>) => void; private createPromiseResolve!: (value?: void | PromiseLike<void>) => void;
private iframeSubscriptionList! : Array<Subscription>; private iframeSubscriptionList!: Array<Subscription>;
MapUrlFile: string; MapUrlFile: string;
RoomId: string; RoomId: string;
instance: string; instance: string;
currentTick!: number; currentTick!: number;
lastSentTick!: number; // The last tick at which a position was sent. lastSentTick!: number; // The last tick at which a position was sent.
lastMoveEventSent: HasMovedEvent = { lastMoveEventSent: HasPlayerMovedEvent = {
direction: '', direction: '',
moving: false, moving: false,
x: -1000, x: -1000,
@ -175,23 +176,23 @@ export class GameScene extends DirtyScene implements CenterListener {
private gameMap!: GameMap; private gameMap!: GameMap;
private actionableItems: Map<number, ActionableItem> = new Map<number, ActionableItem>(); private actionableItems: Map<number, ActionableItem> = new Map<number, ActionableItem>();
// 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;
public userInputManager!: UserInputManager; public userInputManager!: UserInputManager;
private isReconnecting: boolean|undefined = undefined; private isReconnecting: boolean | undefined = undefined;
private startLayerName!: string | null; private startLayerName!: string | null;
private openChatIcon!: OpenChatIcon; private openChatIcon!: OpenChatIcon;
private playerName!: string; private playerName!: string;
private characterLayers!: string[]; private characterLayers!: string[];
private companion!: string|null; private companion!: string | null;
private messageSubscription: Subscription|null = null; private messageSubscription: Subscription | null = null;
private popUpElements : Map<number, DOMElement> = new Map<number, Phaser.GameObjects.DOMElement>(); private popUpElements : Map<number, DOMElement> = new Map<number, Phaser.GameObjects.DOMElement>();
private originalMapUrl: string|undefined; private originalMapUrl: string | undefined;
private pinchManager: PinchManager|undefined; private pinchManager: PinchManager | undefined;
private physicsEnabled: boolean = true; private physicsEnabled: boolean = true;
private mapTransitioning: boolean = false; //used to prevent transitions happenning at the same time. private mapTransitioning: boolean = false; //used to prevent transitions happenning at the same time.
private onVisibilityChangeCallback: () => void; private onVisibilityChangeCallback: () => void;
constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) { constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) {
super({ super({
key: customKey ?? room.id key: customKey ?? room.id
}); });
@ -227,13 +228,13 @@ export class GameScene extends DirtyScene implements CenterListener {
this.load.image(joystickBaseKey, joystickBaseImg); this.load.image(joystickBaseKey, joystickBaseImg);
this.load.image(joystickThumbKey, joystickThumbImg); this.load.image(joystickThumbKey, joystickThumbImg);
} }
this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => { this.load.on(FILE_LOAD_ERROR, (file: { src: string }) => {
// If we happen to be in HTTP and we are trying to load a URL in HTTPS only... (this happens only in dev environments) // If we happen to be in HTTP and we are trying to load a URL in HTTPS only... (this happens only in dev environments)
if (window.location.protocol === 'http:' && file.src === this.MapUrlFile && file.src.startsWith('http:') && this.originalMapUrl === undefined) { if (window.location.protocol === 'http:' && file.src === this.MapUrlFile && file.src.startsWith('http:') && this.originalMapUrl === undefined) {
this.originalMapUrl = this.MapUrlFile; this.originalMapUrl = this.MapUrlFile;
this.MapUrlFile = this.MapUrlFile.replace('http://', 'https://'); this.MapUrlFile = this.MapUrlFile.replace('http://', 'https://');
this.load.tilemapTiledJSON(this.MapUrlFile, this.MapUrlFile); this.load.tilemapTiledJSON(this.MapUrlFile, this.MapUrlFile);
this.load.on('filecomplete-tilemapJSON-'+this.MapUrlFile, (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);
}); });
return; return;
@ -247,7 +248,7 @@ export class GameScene extends DirtyScene implements CenterListener {
this.originalMapUrl = this.MapUrlFile; this.originalMapUrl = this.MapUrlFile;
this.MapUrlFile = this.MapUrlFile.replace('https://', 'http://'); this.MapUrlFile = this.MapUrlFile.replace('https://', 'http://');
this.load.tilemapTiledJSON(this.MapUrlFile, this.MapUrlFile); this.load.tilemapTiledJSON(this.MapUrlFile, this.MapUrlFile);
this.load.on('filecomplete-tilemapJSON-'+this.MapUrlFile, (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);
}); });
return; return;
@ -259,7 +260,7 @@ export class GameScene extends DirtyScene implements CenterListener {
message: this.originalMapUrl ?? file.src message: this.originalMapUrl ?? file.src
}); });
}); });
this.load.on('filecomplete-tilemapJSON-'+this.MapUrlFile, (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
@ -271,7 +272,7 @@ export class GameScene extends DirtyScene implements CenterListener {
this.onMapLoad(data); this.onMapLoad(data);
} }
this.load.spritesheet('layout_modes', 'resources/objects/layout_modes.png', {frameWidth: 32, frameHeight: 32}); 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'); this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
//this function must stay at the end of preload function //this function must stay at the end of preload function
@ -300,7 +301,7 @@ export class GameScene extends DirtyScene implements CenterListener {
for (const layer of this.mapFile.layers) { for (const layer of this.mapFile.layers) {
if (layer.type === 'objectgroup') { if (layer.type === 'objectgroup') {
for (const object of layer.objects) { for (const object of layer.objects) {
let objectsOfType: ITiledMapObject[]|undefined; let objectsOfType: ITiledMapObject[] | undefined;
if (!objects.has(object.type)) { if (!objects.has(object.type)) {
objectsOfType = new Array<ITiledMapObject>(); objectsOfType = new Array<ITiledMapObject>();
} else { } else {
@ -363,7 +364,7 @@ export class GameScene extends DirtyScene implements CenterListener {
} }
//hook initialisation //hook initialisation
init(initData : GameSceneInitInterface) { init(initData: GameSceneInitInterface) {
if (initData.initPosition !== undefined) { if (initData.initPosition !== undefined) {
this.initPosition = initData.initPosition; //todo: still used? this.initPosition = initData.initPosition; //todo: still used?
} }
@ -433,7 +434,7 @@ export class GameScene extends DirtyScene implements CenterListener {
this.Objects = new Array<Phaser.Physics.Arcade.Sprite>(); this.Objects = new Array<Phaser.Physics.Arcade.Sprite>();
//initialise list of other player //initialise list of other player
this.MapPlayers = this.physics.add.group({immovable: true}); this.MapPlayers = this.physics.add.group({ immovable: true });
//create input to move //create input to move
@ -630,7 +631,7 @@ export class GameScene extends DirtyScene implements CenterListener {
//listen event to share position of user //listen event to share position of user
this.CurrentPlayer.on(hasMovedEventName, this.pushPlayerPosition.bind(this)) this.CurrentPlayer.on(hasMovedEventName, this.pushPlayerPosition.bind(this))
this.CurrentPlayer.on(hasMovedEventName, this.outlineItem.bind(this)) this.CurrentPlayer.on(hasMovedEventName, this.outlineItem.bind(this))
this.CurrentPlayer.on(hasMovedEventName, (event: HasMovedEvent) => { this.CurrentPlayer.on(hasMovedEventName, (event: HasPlayerMovedEvent) => {
this.gameMap.setPosition(event.x, event.y); this.gameMap.setPosition(event.x, event.y);
}) })
@ -683,16 +684,16 @@ export class GameScene extends DirtyScene implements CenterListener {
} }
private safeParseJSONstring(jsonString: string|undefined, propertyName: string) { private safeParseJSONstring(jsonString: string | undefined, propertyName: string) {
try { try {
return jsonString ? JSON.parse(jsonString) : {}; return jsonString ? JSON.parse(jsonString) : {};
} catch(e) { } catch (e) {
console.warn('Invalid JSON found in property "' + propertyName + '" of the map:' + jsonString, e); console.warn('Invalid JSON found in property "' + propertyName + '" of the map:' + jsonString, e);
return {} return {}
} }
} }
private triggerOnMapLayerPropertyChange(){ private triggerOnMapLayerPropertyChange() {
this.gameMap.onPropertyChange('exitSceneUrl', (newValue, oldValue) => { this.gameMap.onPropertyChange('exitSceneUrl', (newValue, oldValue) => {
if (newValue) this.onMapExit(newValue as string); if (newValue) this.onMapExit(newValue as string);
}); });
@ -703,22 +704,22 @@ export class GameScene extends DirtyScene implements CenterListener {
if (newValue === undefined) { if (newValue === undefined) {
layoutManager.removeActionButton('openWebsite', this.userInputManager); layoutManager.removeActionButton('openWebsite', this.userInputManager);
coWebsiteManager.closeCoWebsite(); coWebsiteManager.closeCoWebsite();
}else{ } else {
const openWebsiteFunction = () => { const openWebsiteFunction = () => {
coWebsiteManager.loadCoWebsite(newValue as string, this.MapUrlFile, allProps.get('openWebsiteAllowApi') as boolean | undefined, allProps.get('openWebsitePolicy') as string | undefined); coWebsiteManager.loadCoWebsite(newValue as string, this.MapUrlFile, allProps.get('openWebsiteAllowApi') as boolean | undefined, allProps.get('openWebsitePolicy') as string | undefined);
layoutManager.removeActionButton('openWebsite', this.userInputManager); layoutManager.removeActionButton('openWebsite', this.userInputManager);
}; };
const openWebsiteTriggerValue = allProps.get(TRIGGER_WEBSITE_PROPERTIES); const openWebsiteTriggerValue = allProps.get(TRIGGER_WEBSITE_PROPERTIES);
if(openWebsiteTriggerValue && openWebsiteTriggerValue === ON_ACTION_TRIGGER_BUTTON) { if (openWebsiteTriggerValue && openWebsiteTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
let message = allProps.get(WEBSITE_MESSAGE_PROPERTIES); let message = allProps.get(WEBSITE_MESSAGE_PROPERTIES);
if(message === undefined){ if (message === undefined) {
message = 'Press SPACE or touch here to open web site'; message = 'Press SPACE or touch here to open web site';
} }
layoutManager.addActionButton('openWebsite', message.toString(), () => { layoutManager.addActionButton('openWebsite', message.toString(), () => {
openWebsiteFunction(); openWebsiteFunction();
}, this.userInputManager); }, this.userInputManager);
}else{ } else {
openWebsiteFunction(); openWebsiteFunction();
} }
} }
@ -727,12 +728,12 @@ export class GameScene extends DirtyScene implements CenterListener {
if (newValue === undefined) { if (newValue === undefined) {
layoutManager.removeActionButton('jitsiRoom', this.userInputManager); layoutManager.removeActionButton('jitsiRoom', this.userInputManager);
this.stopJitsi(); this.stopJitsi();
}else{ } else {
const openJitsiRoomFunction = () => { const openJitsiRoomFunction = () => {
const roomName = jitsiFactory.getRoomName(newValue.toString(), this.instance); const roomName = jitsiFactory.getRoomName(newValue.toString(), this.instance);
const jitsiUrl = allProps.get("jitsiUrl") as string|undefined; const jitsiUrl = allProps.get("jitsiUrl") as string | undefined;
if (JITSI_PRIVATE_MODE && !jitsiUrl) { if (JITSI_PRIVATE_MODE && !jitsiUrl) {
const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined; const adminTag = allProps.get("jitsiRoomAdminTag") as string | undefined;
this.connection?.emitQueryJitsiJwtMessage(roomName, adminTag); this.connection?.emitQueryJitsiJwtMessage(roomName, adminTag);
} else { } else {
@ -742,7 +743,7 @@ export class GameScene extends DirtyScene implements CenterListener {
} }
const jitsiTriggerValue = allProps.get(TRIGGER_JITSI_PROPERTIES); const jitsiTriggerValue = allProps.get(TRIGGER_JITSI_PROPERTIES);
if(jitsiTriggerValue && jitsiTriggerValue === ON_ACTION_TRIGGER_BUTTON) { if (jitsiTriggerValue && jitsiTriggerValue === ON_ACTION_TRIGGER_BUTTON) {
let message = allProps.get(JITSI_MESSAGE_PROPERTIES); let message = allProps.get(JITSI_MESSAGE_PROPERTIES);
if (message === undefined) { if (message === undefined) {
message = 'Press SPACE or touch here to enter Jitsi Meet room'; message = 'Press SPACE or touch here to enter Jitsi Meet room';
@ -750,7 +751,7 @@ export class GameScene extends DirtyScene implements CenterListener {
layoutManager.addActionButton('jitsiRoom', message.toString(), () => { layoutManager.addActionButton('jitsiRoom', message.toString(), () => {
openJitsiRoomFunction(); openJitsiRoomFunction();
}, this.userInputManager); }, this.userInputManager);
}else{ } else {
openJitsiRoomFunction(); openJitsiRoomFunction();
} }
} }
@ -763,8 +764,8 @@ export class GameScene extends DirtyScene implements CenterListener {
} }
}); });
this.gameMap.onPropertyChange('playAudio', (newValue, oldValue, allProps) => { this.gameMap.onPropertyChange('playAudio', (newValue, oldValue, allProps) => {
const volume = allProps.get(AUDIO_VOLUME_PROPERTY) as number|undefined; const volume = allProps.get(AUDIO_VOLUME_PROPERTY) as number | undefined;
const loop = allProps.get(AUDIO_LOOP_PROPERTY) as boolean|undefined; const loop = allProps.get(AUDIO_LOOP_PROPERTY) as boolean | undefined;
newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl(), volume, loop); newValue === undefined ? audioManager.unloadAudio() : audioManager.playAudio(newValue, this.getMapDirUrl(), volume, loop);
}); });
// TODO: This legacy property should be removed at some point // TODO: This legacy property should be removed at some point
@ -786,9 +787,9 @@ export class GameScene extends DirtyScene implements CenterListener {
this.iframeSubscriptionList = []; this.iframeSubscriptionList = [];
this.iframeSubscriptionList.push(iframeListener.openPopupStream.subscribe((openPopupEvent) => { this.iframeSubscriptionList.push(iframeListener.openPopupStream.subscribe((openPopupEvent) => {
let objectLayerSquare : ITiledMapObject; let objectLayerSquare: ITiledMapObject;
const targetObjectData = this.getObjectLayerData(openPopupEvent.targetObject); const targetObjectData = this.getObjectLayerData(openPopupEvent.targetObject);
if (targetObjectData !== undefined){ if (targetObjectData !== undefined) {
objectLayerSquare = targetObjectData; objectLayerSquare = targetObjectData;
} else { } else {
console.error("Error while opening a popup. Cannot find an object on the map with name '" + openPopupEvent.targetObject + "'. The first parameter of WA.openPopup() must be the name of a rectangle object in your map."); console.error("Error while opening a popup. Cannot find an object on the map with name '" + openPopupEvent.targetObject + "'. The first parameter of WA.openPopup() must be the name of a rectangle object in your map.");
@ -806,10 +807,10 @@ ${escapedMessage}
id++; id++;
} }
html += '</div>'; html += '</div>';
const domElement = this.add.dom(objectLayerSquare.x , const domElement = this.add.dom(objectLayerSquare.x,
objectLayerSquare.y).createFromHTML(html); objectLayerSquare.y).createFromHTML(html);
const container : HTMLDivElement = domElement.getChildByID("container") as HTMLDivElement; const container: HTMLDivElement = domElement.getChildByID("container") as HTMLDivElement;
container.style.width = objectLayerSquare.width + "px"; container.style.width = objectLayerSquare.width + "px";
domElement.scale = 0; domElement.scale = 0;
domElement.setClassName('popUpElement'); domElement.setClassName('popUpElement');
@ -829,10 +830,10 @@ ${escapedMessage}
id++; id++;
} }
this.tweens.add({ this.tweens.add({
targets : domElement , targets: domElement,
scale : 1, scale: 1,
ease : "EaseOut", ease: "EaseOut",
duration : 400, duration: 400,
}); });
this.popUpElements.set(openPopupEvent.popupId, domElement); this.popUpElements.set(openPopupEvent.popupId, domElement);
@ -841,36 +842,45 @@ ${escapedMessage}
this.iframeSubscriptionList.push(iframeListener.closePopupStream.subscribe((closePopupEvent) => { this.iframeSubscriptionList.push(iframeListener.closePopupStream.subscribe((closePopupEvent) => {
const popUpElement = this.popUpElements.get(closePopupEvent.popupId); const popUpElement = this.popUpElements.get(closePopupEvent.popupId);
if (popUpElement === undefined) { if (popUpElement === undefined) {
console.error('Could not close popup with ID ', closePopupEvent.popupId,'. Maybe it has already been closed?'); console.error('Could not close popup with ID ', closePopupEvent.popupId, '. Maybe it has already been closed?');
} }
this.tweens.add({ this.tweens.add({
targets : popUpElement , targets: popUpElement,
scale : 0, scale: 0,
ease : "EaseOut", ease: "EaseOut",
duration : 400, duration: 400,
onComplete : () => { onComplete: () => {
popUpElement?.destroy(); popUpElement?.destroy();
this.popUpElements.delete(closePopupEvent.popupId); this.popUpElements.delete(closePopupEvent.popupId);
}, },
}); });
})); }));
this.iframeSubscriptionList.push(iframeListener.disablePlayerControlStream.subscribe(()=>{ this.iframeSubscriptionList.push(iframeListener.disablePlayerControlStream.subscribe(() => {
this.userInputManager.disableControls(); this.userInputManager.disableControls();
})); }));
this.iframeSubscriptionList.push(iframeListener.enablePlayerControlStream.subscribe(()=>{ this.iframeSubscriptionList.push(iframeListener.enablePlayerControlStream.subscribe(() => {
this.userInputManager.restoreControls(); this.userInputManager.restoreControls();
})); }));
let scriptedBubbleSprite : Sprite; this.iframeSubscriptionList.push(iframeListener.gameStateStream.subscribe(() => {
this.iframeSubscriptionList.push(iframeListener.displayBubbleStream.subscribe(()=>{ iframeListener.sendFrozenGameStateEvent({
scriptedBubbleSprite = new Sprite(this,this.CurrentPlayer.x + 25,this.CurrentPlayer.y,'circleSprite-white'); mapUrl: this.MapUrlFile,
startLayerName: this.startLayerName,
uuid: localUserStore.getLocalUser()?.uuid,
roomId: this.RoomId,
})
}));
let scriptedBubbleSprite: Sprite;
this.iframeSubscriptionList.push(iframeListener.displayBubbleStream.subscribe(() => {
scriptedBubbleSprite = new Sprite(this, this.CurrentPlayer.x + 25, this.CurrentPlayer.y, 'circleSprite-white');
scriptedBubbleSprite.setDisplayOrigin(48, 48); scriptedBubbleSprite.setDisplayOrigin(48, 48);
this.add.existing(scriptedBubbleSprite); this.add.existing(scriptedBubbleSprite);
})); }));
this.iframeSubscriptionList.push(iframeListener.removeBubbleStream.subscribe(()=>{ this.iframeSubscriptionList.push(iframeListener.removeBubbleStream.subscribe(() => {
scriptedBubbleSprite.destroy(); scriptedBubbleSprite.destroy();
})); }));
@ -965,7 +975,7 @@ ${escapedMessage}
this.userInputManager.destroy(); this.userInputManager.destroy();
this.pinchManager?.destroy(); this.pinchManager?.destroy();
for(const iframeEvents of this.iframeSubscriptionList){ for (const iframeEvents of this.iframeSubscriptionList) {
iframeEvents.unsubscribe(); iframeEvents.unsubscribe();
} }
@ -987,7 +997,7 @@ ${escapedMessage}
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) {
return; return;
} }
const mode = layoutManager.getLayoutMode(); const mode = layoutManager.getLayoutMode();
@ -1028,24 +1038,24 @@ ${escapedMessage}
private initPositionFromLayerName(layerName: string) { private initPositionFromLayerName(layerName: string) {
for (const layer of this.gameMap.flatLayers) { for (const layer of this.gameMap.flatLayers) {
if ((layerName === layer.name || layer.name.endsWith('/'+layerName)) && layer.type === 'tilelayer' && (layerName === defaultStartLayerName || this.isStartLayer(layer))) { if ((layerName === layer.name || layer.name.endsWith('/' + layerName)) && layer.type === 'tilelayer' && (layerName === defaultStartLayerName || this.isStartLayer(layer))) {
const startPosition = this.startUser(layer); const startPosition = this.startUser(layer);
this.startX = startPosition.x + this.mapFile.tilewidth/2; this.startX = startPosition.x + this.mapFile.tilewidth / 2;
this.startY = startPosition.y + this.mapFile.tileheight/2; this.startY = startPosition.y + this.mapFile.tileheight / 2;
} }
} }
} }
private getExitUrl(layer: ITiledMapLayer): string|undefined { private getExitUrl(layer: ITiledMapLayer): string | undefined {
return this.getProperty(layer, "exitUrl") as string|undefined; return this.getProperty(layer, "exitUrl") as string | undefined;
} }
/** /**
* @deprecated the map property exitSceneUrl is deprecated * @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 isStartLayer(layer: ITiledMapLayer): boolean { private isStartLayer(layer: ITiledMapLayer): boolean {
@ -1056,8 +1066,8 @@ ${escapedMessage}
return (this.getProperties(map, "script") as string[]).map((script) => (new URL(script, this.MapUrlFile)).toString()); return (this.getProperties(map, "script") as string[]).map((script) => (new URL(script, this.MapUrlFile)).toString());
} }
private getProperty(layer: ITiledMapLayer|ITiledMap, name: string): string|boolean|number|undefined { private getProperty(layer: ITiledMapLayer | ITiledMap, name: string): string | boolean | number | undefined {
const properties: ITiledMapLayerProperty[]|undefined = layer.properties; const properties: ITiledMapLayerProperty[] | undefined = layer.properties;
if (!properties) { if (!properties) {
return undefined; return undefined;
} }
@ -1068,8 +1078,8 @@ ${escapedMessage}
return obj.value; return obj.value;
} }
private getProperties(layer: ITiledMapLayer|ITiledMap, name: string): (string|number|boolean|undefined)[] { private getProperties(layer: ITiledMapLayer | ITiledMap, name: string): (string | number | boolean | undefined)[] {
const properties: ITiledMapLayerProperty[]|undefined = layer.properties; const properties: ITiledMapLayerProperty[] | undefined = layer.properties;
if (!properties) { if (!properties) {
return []; return [];
} }
@ -1077,30 +1087,30 @@ ${escapedMessage}
} }
//todo: push that into the gameManager //todo: push that into the gameManager
private async loadNextGame(exitSceneIdentifier: string){ private async loadNextGame(exitSceneIdentifier: string) {
const {roomId, hash} = Room.getIdFromIdentifier(exitSceneIdentifier, this.MapUrlFile, this.instance); const { roomId, hash } = Room.getIdFromIdentifier(exitSceneIdentifier, this.MapUrlFile, this.instance);
const room = new Room(roomId); const room = new Room(roomId);
await gameManager.loadMap(room, this.scene); await gameManager.loadMap(room, this.scene);
} }
private startUser(layer: ITiledMapTileLayer): PositionInterface { private startUser(layer: ITiledMapTileLayer): PositionInterface {
const tiles = layer.data; const tiles = layer.data;
if (typeof(tiles) === 'string') { if (typeof (tiles) === 'string') {
throw new Error('The content of a JSON map must be filled as a JSON array, not as a string'); throw new Error('The content of a JSON map must be filled as a JSON array, not as a string');
} }
const possibleStartPositions : PositionInterface[] = []; const possibleStartPositions: PositionInterface[] = [];
tiles.forEach((objectKey : number, key: number) => { tiles.forEach((objectKey: number, key: number) => {
if(objectKey === 0){ if (objectKey === 0) {
return; return;
} }
const y = Math.floor(key / layer.width); const y = Math.floor(key / layer.width);
const x = key % layer.width; const x = key % layer.width;
possibleStartPositions.push({x: x * this.mapFile.tilewidth, y: y * this.mapFile.tilewidth}); possibleStartPositions.push({ x: x * this.mapFile.tilewidth, y: y * this.mapFile.tilewidth });
}); });
// Get a value at random amongst allowed values // Get a value at random amongst allowed values
if (possibleStartPositions.length === 0) { if (possibleStartPositions.length === 0) {
console.warn('The start layer "'+layer.name+'" for this map is empty.'); console.warn('The start layer "' + layer.name + '" for this map is empty.');
return { return {
x: 0, x: 0,
y: 0 y: 0
@ -1112,7 +1122,7 @@ ${escapedMessage}
//todo: in a dedicated class/function? //todo: in a dedicated class/function?
initCamera() { initCamera() {
this.cameras.main.setBounds(0,0, this.Map.widthInPixels, this.Map.heightInPixels); this.cameras.main.setBounds(0, 0, this.Map.widthInPixels, this.Map.heightInPixels);
this.cameras.main.startFollow(this.CurrentPlayer, true); this.cameras.main.startFollow(this.CurrentPlayer, true);
this.updateCameraOffset(); this.updateCameraOffset();
} }
@ -1143,7 +1153,7 @@ ${escapedMessage}
} }
} }
createCurrentPlayer(){ createCurrentPlayer() {
//TODO create animation moving between exit and start //TODO create animation moving between exit and start
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.characterLayers); const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.characterLayers);
try { try {
@ -1159,8 +1169,8 @@ ${escapedMessage}
this.companion, this.companion,
this.companion !== null ? lazyLoadCompanionResource(this.load, this.companion) : undefined this.companion !== null ? lazyLoadCompanionResource(this.load, this.companion) : undefined
); );
}catch (err){ } catch (err) {
if(err instanceof TextureError) { if (err instanceof TextureError) {
gameManager.leaveGame(this, SelectCharacterSceneName, new SelectCharacterScene()); gameManager.leaveGame(this, SelectCharacterSceneName, new SelectCharacterScene());
} }
throw err; throw err;
@ -1170,7 +1180,7 @@ ${escapedMessage}
this.createCollisionWithPlayer(); this.createCollisionWithPlayer();
} }
pushPlayerPosition(event: HasMovedEvent) { pushPlayerPosition(event: HasPlayerMovedEvent) {
if (this.lastMoveEventSent === event) { if (this.lastMoveEventSent === event) {
return; return;
} }
@ -1200,7 +1210,7 @@ ${escapedMessage}
* Finds the correct item to outline and outline it (if there is an item to be outlined) * Finds the correct item to outline and outline it (if there is an item to be outlined)
* @param event * @param event
*/ */
private outlineItem(event: HasMovedEvent): void { private outlineItem(event: HasPlayerMovedEvent): void {
let x = event.x; let x = event.x;
let y = event.y; let y = event.y;
switch (event.direction) { switch (event.direction) {
@ -1221,7 +1231,7 @@ ${escapedMessage}
} }
let shortestDistance: number = Infinity; let shortestDistance: number = Infinity;
let selectedItem: ActionableItem|null = null; let selectedItem: ActionableItem | null = null;
for (const item of this.actionableItems.values()) { for (const item of this.actionableItems.values()) {
const distance = item.actionableDistance(x, y); const distance = item.actionableDistance(x, y);
if (distance !== null && distance < shortestDistance) { if (distance !== null && distance < shortestDistance) {
@ -1239,7 +1249,7 @@ ${escapedMessage}
this.outlinedItem?.selectable(); this.outlinedItem?.selectable();
} }
private doPushPlayerPosition(event: HasMovedEvent): void { private doPushPlayerPosition(event: HasPlayerMovedEvent): void {
this.lastMoveEventSent = event; this.lastMoveEventSent = event;
this.lastSentTick = this.currentTick; this.lastSentTick = this.currentTick;
const camera = this.cameras.main; const camera = this.cameras.main;
@ -1249,13 +1259,14 @@ ${escapedMessage}
right: camera.scrollX + camera.width, right: camera.scrollX + camera.width,
bottom: camera.scrollY + camera.height, bottom: camera.scrollY + camera.height,
}); });
iframeListener.hasPlayerMoved(event);
} }
/** /**
* @param time * @param time
* @param delta The delta time in ms since the last frame. This is a smoothed and capped value based on the FPS rate. * @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.updateScene(); mediaManager.updateScene();
this.currentTick = time; this.currentTick = time;
if (this.CurrentPlayer.isMoving()) { if (this.CurrentPlayer.isMoving()) {
@ -1300,7 +1311,7 @@ ${escapedMessage}
} }
// Let's move all users // Let's move all users
const updatedPlayersPositions = this.playersPositionInterpolator.getUpdatedPositions(time); const updatedPlayersPositions = this.playersPositionInterpolator.getUpdatedPositions(time);
updatedPlayersPositions.forEach((moveEvent: HasMovedEvent, userId: number) => { updatedPlayersPositions.forEach((moveEvent: HasPlayerMovedEvent, userId: number) => {
this.dirty = true; this.dirty = true;
const player: RemotePlayer | undefined = this.MapPlayersByKey.get(userId); const player: RemotePlayer | undefined = this.MapPlayersByKey.get(userId);
if (player === undefined) { if (player === undefined) {
@ -1327,8 +1338,8 @@ ${escapedMessage}
const currentPlayerId = this.connection?.getUserId(); const currentPlayerId = this.connection?.getUserId();
this.removeAllRemotePlayers(); this.removeAllRemotePlayers();
// load map // load map
usersPosition.forEach((userPosition : MessageUserPositionInterface) => { usersPosition.forEach((userPosition: MessageUserPositionInterface) => {
if(userPosition.userId === currentPlayerId){ if (userPosition.userId === currentPlayerId) {
return; return;
} }
this.addPlayer(userPosition); this.addPlayer(userPosition);
@ -1338,16 +1349,16 @@ ${escapedMessage}
/** /**
* Called by the connexion when a new player arrives on a map * Called by the connexion when a new player arrives on a map
*/ */
public addPlayer(addPlayerData : AddPlayerInterface) : void { public addPlayer(addPlayerData: AddPlayerInterface): void {
this.pendingEvents.enqueue({ this.pendingEvents.enqueue({
type: "AddPlayerEvent", type: "AddPlayerEvent",
event: addPlayerData event: addPlayerData
}); });
} }
private doAddPlayer(addPlayerData : AddPlayerInterface): void { private doAddPlayer(addPlayerData: AddPlayerInterface): void {
//check if exist player, if exist, move position //check if exist player, if exist, move position
if(this.MapPlayersByKey.has(addPlayerData.userId)){ if (this.MapPlayersByKey.has(addPlayerData.userId)) {
this.updatePlayerPosition({ this.updatePlayerPosition({
userId: addPlayerData.userId, userId: addPlayerData.userId,
position: addPlayerData.position position: addPlayerData.position
@ -1408,10 +1419,10 @@ ${escapedMessage}
} }
private doUpdatePlayerPosition(message: MessageUserMovedInterface): void { private doUpdatePlayerPosition(message: MessageUserMovedInterface): void {
const player : RemotePlayer | undefined = this.MapPlayersByKey.get(message.userId); const player: RemotePlayer | undefined = this.MapPlayersByKey.get(message.userId);
if (player === undefined) { if (player === undefined) {
//throw new Error('Cannot find player with ID "' + message.userId +'"'); //throw new Error('Cannot find player with ID "' + message.userId +'"');
console.error('Cannot update position of player with ID "' + message.userId +'": player not found'); console.error('Cannot update position of player with ID "' + message.userId + '": player not found');
return; return;
} }
@ -1455,7 +1466,7 @@ ${escapedMessage}
doDeleteGroup(groupId: number): void { doDeleteGroup(groupId: number): void {
const group = this.groups.get(groupId); const group = this.groups.get(groupId);
if(!group){ if (!group) {
return; return;
} }
group.destroy(); group.destroy();
@ -1484,7 +1495,7 @@ ${escapedMessage}
bottom: camera.scrollY + camera.height, bottom: camera.scrollY + camera.height,
}); });
} }
private getObjectLayerData(objectName : string) : ITiledMapObject| undefined{ private getObjectLayerData(objectName: string): ITiledMapObject | undefined {
for (const layer of this.mapFile.layers) { for (const layer of this.mapFile.layers) {
if (layer.type === 'objectgroup' && layer.name === 'floorLayer') { if (layer.type === 'objectgroup' && layer.name === 'floorLayer') {
for (const object of layer.objects) { for (const object of layer.objects) {
@ -1518,7 +1529,7 @@ ${escapedMessage}
const game = HtmlUtils.querySelectorOrFail<HTMLCanvasElement>('#game canvas'); const game = HtmlUtils.querySelectorOrFail<HTMLCanvasElement>('#game canvas');
// Let's put this in Game coordinates by applying the zoom level: // Let's put this in Game coordinates by applying the zoom level:
this.cameras.main.setFollowOffset((xCenter - game.offsetWidth/2) * window.devicePixelRatio / this.scale.zoom , (yCenter - game.offsetHeight/2) * window.devicePixelRatio / this.scale.zoom); this.cameras.main.setFollowOffset((xCenter - game.offsetWidth / 2) * window.devicePixelRatio / this.scale.zoom, (yCenter - game.offsetHeight / 2) * window.devicePixelRatio / this.scale.zoom);
} }
public onCenterChange(): void { public onCenterChange(): void {
@ -1527,16 +1538,16 @@ ${escapedMessage}
public startJitsi(roomName: string, jwt?: string): void { public startJitsi(roomName: string, jwt?: string): void {
const allProps = this.gameMap.getCurrentProperties(); const allProps = this.gameMap.getCurrentProperties();
const jitsiConfig = this.safeParseJSONstring(allProps.get("jitsiConfig") as string|undefined, 'jitsiConfig'); const jitsiConfig = this.safeParseJSONstring(allProps.get("jitsiConfig") as string | undefined, 'jitsiConfig');
const jitsiInterfaceConfig = this.safeParseJSONstring(allProps.get("jitsiInterfaceConfig") as string|undefined, 'jitsiInterfaceConfig'); const jitsiInterfaceConfig = this.safeParseJSONstring(allProps.get("jitsiInterfaceConfig") as string | undefined, 'jitsiInterfaceConfig');
const jitsiUrl = allProps.get("jitsiUrl") as string|undefined; const jitsiUrl = allProps.get("jitsiUrl") as string | undefined;
jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl); jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl);
this.connection?.setSilent(true); this.connection?.setSilent(true);
mediaManager.hideGameOverlay(); mediaManager.hideGameOverlay();
//permit to stop jitsi when user close iframe //permit to stop jitsi when user close iframe
mediaManager.addTriggerCloseJitsiFrameButton('close-jisi',() => { mediaManager.addTriggerCloseJitsiFrameButton('close-jisi', () => {
this.stopJitsi(); this.stopJitsi();
}); });
@ -1553,7 +1564,7 @@ ${escapedMessage}
} }
//todo: put this into an 'orchestrator' scene (EntryScene?) //todo: put this into an 'orchestrator' scene (EntryScene?)
private bannedUser(){ private bannedUser() {
this.cleanupClosingScene(); this.cleanupClosingScene();
this.userInputManager.disableControls(); this.userInputManager.disableControls();
this.scene.start(ErrorSceneName, { this.scene.start(ErrorSceneName, {
@ -1564,22 +1575,22 @@ ${escapedMessage}
} }
//todo: put this into an 'orchestrator' scene (EntryScene?) //todo: put this into an 'orchestrator' scene (EntryScene?)
private showWorldFullError(message: string|null): void { private showWorldFullError(message: string | null): void {
this.cleanupClosingScene(); this.cleanupClosingScene();
this.scene.stop(ReconnectingSceneName); this.scene.stop(ReconnectingSceneName);
this.scene.remove(ReconnectingSceneName); this.scene.remove(ReconnectingSceneName);
this.userInputManager.disableControls(); this.userInputManager.disableControls();
//FIX ME to use status code //FIX ME to use status code
if(message == undefined){ if (message == undefined) {
this.scene.start(ErrorSceneName, { this.scene.start(ErrorSceneName, {
title: 'Connection rejected', title: 'Connection rejected',
subTitle: 'The world you are trying to join is full. Try again later.', subTitle: 'The world you are trying to join is full. Try again later.',
message: 'If you want more information, you may contact us at: workadventure@thecodingmachine.com' message: 'If you want more information, you may contact us at: workadventure@thecodingmachine.com'
}); });
}else{ } else {
this.scene.start(ErrorSceneName, { this.scene.start(ErrorSceneName, {
title: 'Connection rejected', title: 'Connection rejected',
subTitle: 'You cannot join the World. Try again later. \n\r \n\r Error: '+message+'.', subTitle: 'You cannot join the World. Try again later. \n\r \n\r Error: ' + message + '.',
message: 'If you want more information, you may contact administrator or contact us at: workadventure@thecodingmachine.com' message: 'If you want more information, you may contact administrator or contact us at: workadventure@thecodingmachine.com'
}); });
} }

View File

@ -1,9 +1,10 @@
import type {HasMovedEvent} from "./GameManager"; import type {HasMovedEvent} from "./GameManager";
import {MAX_EXTRAPOLATION_TIME} from "../../Enum/EnvironmentVariable"; import { MAX_EXTRAPOLATION_TIME } from "../../Enum/EnvironmentVariable";
import type {PositionInterface} from "../../Connexion/ConnexionModels"; import type { PositionInterface } from "../../Connexion/ConnexionModels";
import type { HasPlayerMovedEvent } from '../../Api/Events/HasPlayerMovedEvent';
export class PlayerMovement { export class PlayerMovement {
public constructor(private startPosition: PositionInterface, private startTick: number, private endPosition: HasMovedEvent, private endTick: number) { public constructor(private startPosition: PositionInterface, private startTick: number, private endPosition: HasPlayerMovedEvent, private endTick: number) {
} }
public isOutdated(tick: number): boolean { public isOutdated(tick: number): boolean {
@ -17,7 +18,7 @@ export class PlayerMovement {
return tick > this.endTick + MAX_EXTRAPOLATION_TIME; return tick > this.endTick + MAX_EXTRAPOLATION_TIME;
} }
public getPosition(tick: number): HasMovedEvent { public getPosition(tick: number): HasPlayerMovedEvent {
// Special case: end position reached and end position is not moving // Special case: end position reached and end position is not moving
if (tick >= this.endTick && this.endPosition.moving === false) { if (tick >= this.endTick && this.endPosition.moving === false) {
//console.log('Movement finished ', this.endPosition) //console.log('Movement finished ', this.endPosition)

View File

@ -2,13 +2,13 @@
* This class is in charge of computing the position of all players. * This class is in charge of computing the position of all players.
* Player movement is delayed by 200ms so position depends on ticks. * Player movement is delayed by 200ms so position depends on ticks.
*/ */
import type {PlayerMovement} from "./PlayerMovement"; import type { HasPlayerMovedEvent } from '../../Api/Events/HasPlayerMovedEvent';
import type {HasMovedEvent} from "./GameManager"; import type { PlayerMovement } from "./PlayerMovement";
export class PlayersPositionInterpolator { export class PlayersPositionInterpolator {
playerMovements: Map<number, PlayerMovement> = new Map<number, PlayerMovement>(); playerMovements: Map<number, PlayerMovement> = new Map<number, PlayerMovement>();
updatePlayerPosition(userId: number, playerMovement: PlayerMovement) : void { updatePlayerPosition(userId: number, playerMovement: PlayerMovement): void {
this.playerMovements.set(userId, playerMovement); this.playerMovements.set(userId, playerMovement);
} }
@ -16,8 +16,8 @@ export class PlayersPositionInterpolator {
this.playerMovements.delete(userId); this.playerMovements.delete(userId);
} }
getUpdatedPositions(tick: number) : Map<number, HasMovedEvent> { getUpdatedPositions(tick: number): Map<number, HasPlayerMovedEvent> {
const positions = new Map<number, HasMovedEvent>(); const positions = new Map<number, HasPlayerMovedEvent>();
this.playerMovements.forEach((playerMovement: PlayerMovement, userId: number) => { this.playerMovements.forEach((playerMovement: PlayerMovement, userId: number) => {
if (playerMovement.isOutdated(tick)) { if (playerMovement.isOutdated(tick)) {
//console.log("outdated") //console.log("outdated")

View File

@ -1,15 +1,15 @@
import {DivImportance, layoutManager} from "./LayoutManager"; import { DivImportance, layoutManager } from "./LayoutManager";
import {HtmlUtils} from "./HtmlUtils"; import { HtmlUtils } from "./HtmlUtils";
import {discussionManager, SendMessageCallback} from "./DiscussionManager"; import { discussionManager, SendMessageCallback } from "./DiscussionManager";
import type {UserInputManager} from "../Phaser/UserInput/UserInputManager"; import type { UserInputManager } from "../Phaser/UserInput/UserInputManager";
import {localUserStore} from "../Connexion/LocalUserStore"; import { localUserStore } from "../Connexion/LocalUserStore";
import type {UserSimplePeerInterface} from "./SimplePeer"; import type { UserSimplePeerInterface } from "./SimplePeer";
import {SoundMeter} from "../Phaser/Components/SoundMeter"; import { SoundMeter } from "../Phaser/Components/SoundMeter";
import {DISABLE_NOTIFICATIONS} from "../Enum/EnvironmentVariable"; import { DISABLE_NOTIFICATIONS } from "../Enum/EnvironmentVariable";
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any declare const navigator: any; // eslint-disable-line @typescript-eslint/no-explicit-any
let videoConstraint: boolean|MediaTrackConstraints = { let videoConstraint: boolean | MediaTrackConstraints = {
width: { min: 640, ideal: 1280, max: 1920 }, width: { min: 640, ideal: 1280, max: 1920 },
height: { min: 400, ideal: 720 }, height: { min: 400, ideal: 720 },
frameRate: { ideal: localUserStore.getVideoQualityValue() }, frameRate: { ideal: localUserStore.getVideoQualityValue() },
@ -17,24 +17,24 @@ let videoConstraint: boolean|MediaTrackConstraints = {
resizeMode: 'crop-and-scale', resizeMode: 'crop-and-scale',
aspectRatio: 1.777777778 aspectRatio: 1.777777778
}; };
const audioConstraint: boolean|MediaTrackConstraints = { const audioConstraint: boolean | MediaTrackConstraints = {
//TODO: make these values configurable in the game settings menu and store them in localstorage //TODO: make these values configurable in the game settings menu and store them in localstorage
autoGainControl: false, autoGainControl: false,
echoCancellation: true, echoCancellation: true,
noiseSuppression: true noiseSuppression: true
}; };
export type UpdatedLocalStreamCallback = (media: MediaStream|null) => void; export type UpdatedLocalStreamCallback = (media: MediaStream | null) => void;
export type StartScreenSharingCallback = (media: MediaStream) => void; export type StartScreenSharingCallback = (media: MediaStream) => void;
export type StopScreenSharingCallback = (media: MediaStream) => void; export type StopScreenSharingCallback = (media: MediaStream) => void;
export type ReportCallback = (message: string) => void; export type ReportCallback = (message: string) => void;
export type ShowReportCallBack = (userId: string, userName: string|undefined) => void; export type ShowReportCallBack = (userId: string, userName: string | undefined) => void;
export type HelpCameraSettingsCallBack = () => void; export type HelpCameraSettingsCallBack = () => void;
// TODO: Split MediaManager in 2 classes: MediaManagerUI (in charge of HTML) and MediaManager (singleton in charge of the camera only) // TODO: Split MediaManager in 2 classes: MediaManagerUI (in charge of HTML) and MediaManager (singleton in charge of the camera only)
export class MediaManager { export class MediaManager {
localStream: MediaStream|null = null; localStream: MediaStream | null = null;
localScreenCapture: MediaStream|null = null; localScreenCapture: MediaStream | null = null;
private remoteVideo: Map<string, HTMLVideoElement> = new Map<string, HTMLVideoElement>(); private remoteVideo: Map<string, HTMLVideoElement> = new Map<string, HTMLVideoElement>();
myCamVideo: HTMLVideoElement; myCamVideo: HTMLVideoElement;
cinemaClose: HTMLImageElement; cinemaClose: HTMLImageElement;
@ -47,26 +47,26 @@ export class MediaManager {
//FIX ME SOUNDMETER: check stalability of sound meter calculation //FIX ME SOUNDMETER: check stalability of sound meter calculation
//mySoundMeterElement: HTMLDivElement; //mySoundMeterElement: HTMLDivElement;
private webrtcOutAudio: HTMLAudioElement; private webrtcOutAudio: HTMLAudioElement;
constraintsMedia : MediaStreamConstraints = { constraintsMedia: MediaStreamConstraints = {
audio: audioConstraint, audio: audioConstraint,
video: videoConstraint video: videoConstraint
}; };
updatedLocalStreamCallBacks : Set<UpdatedLocalStreamCallback> = new Set<UpdatedLocalStreamCallback>(); updatedLocalStreamCallBacks: Set<UpdatedLocalStreamCallback> = new Set<UpdatedLocalStreamCallback>();
startScreenSharingCallBacks : Set<StartScreenSharingCallback> = new Set<StartScreenSharingCallback>(); startScreenSharingCallBacks: Set<StartScreenSharingCallback> = new Set<StartScreenSharingCallback>();
stopScreenSharingCallBacks : Set<StopScreenSharingCallback> = new Set<StopScreenSharingCallback>(); stopScreenSharingCallBacks: Set<StopScreenSharingCallback> = new Set<StopScreenSharingCallback>();
showReportModalCallBacks : Set<ShowReportCallBack> = new Set<ShowReportCallBack>(); showReportModalCallBacks: Set<ShowReportCallBack> = new Set<ShowReportCallBack>();
helpCameraSettingsCallBacks : Set<HelpCameraSettingsCallBack> = new Set<HelpCameraSettingsCallBack>(); helpCameraSettingsCallBacks: Set<HelpCameraSettingsCallBack> = new Set<HelpCameraSettingsCallBack>();
private microphoneBtn: HTMLDivElement; private microphoneBtn: HTMLDivElement;
private cinemaBtn: HTMLDivElement; private cinemaBtn: HTMLDivElement;
private monitorBtn: HTMLDivElement; private monitorBtn: HTMLDivElement;
private previousConstraint : MediaStreamConstraints; private previousConstraint: MediaStreamConstraints;
private focused : boolean = true; private focused: boolean = true;
private hasCamera = true; private hasCamera = true;
private triggerCloseJistiFrame : Map<String, Function> = new Map<String, Function>(); private triggerCloseJistiFrame: Map<String, Function> = new Map<String, Function>();
private userInputManager?: UserInputManager; private userInputManager?: UserInputManager;
@ -148,7 +148,7 @@ export class MediaManager {
} }
public blurCamera() { public blurCamera() {
if(!this.focused){ if (!this.focused) {
return; return;
} }
this.focused = false; this.focused = false;
@ -164,7 +164,7 @@ export class MediaManager {
} }
public focusCamera() { public focusCamera() {
if(this.focused){ if (this.focused) {
return; return;
} }
this.focused = true; this.focused = true;
@ -187,7 +187,7 @@ export class MediaManager {
this.updatedLocalStreamCallBacks.delete(callback); this.updatedLocalStreamCallBacks.delete(callback);
} }
private triggerUpdatedLocalStreamCallbacks(stream: MediaStream|null): void { private triggerUpdatedLocalStreamCallbacks(stream: MediaStream | null): void {
for (const callback of this.updatedLocalStreamCallBacks) { for (const callback of this.updatedLocalStreamCallBacks) {
callback(stream); callback(stream);
} }
@ -235,7 +235,7 @@ export class MediaManager {
public updateCameraQuality(value: number) { public updateCameraQuality(value: number) {
this.enableCameraStyle(); this.enableCameraStyle();
const newVideoConstraint = JSON.parse(JSON.stringify(videoConstraint)); const newVideoConstraint = JSON.parse(JSON.stringify(videoConstraint));
newVideoConstraint.frameRate = {exact: value, ideal: value}; newVideoConstraint.frameRate = { exact: value, ideal: value };
videoConstraint = newVideoConstraint; videoConstraint = newVideoConstraint;
this.constraintsMedia.video = videoConstraint; this.constraintsMedia.video = videoConstraint;
this.getCamera().then((stream: MediaStream) => { this.getCamera().then((stream: MediaStream) => {
@ -250,7 +250,7 @@ export class MediaManager {
const stream = await this.getCamera() const stream = await this.getCamera()
//TODO show error message tooltip upper of camera button //TODO show error message tooltip upper of camera button
//TODO message : please check camera permission of your navigator //TODO message : please check camera permission of your navigator
if(stream.getVideoTracks().length === 0) { if (stream.getVideoTracks().length === 0) {
throw new Error('Video track is empty, please check camera permission of your navigator') throw new Error('Video track is empty, please check camera permission of your navigator')
} }
this.enableCameraStyle(); this.enableCameraStyle();
@ -315,14 +315,14 @@ export class MediaManager {
private applyPreviousConfig() { private applyPreviousConfig() {
this.constraintsMedia = this.previousConstraint; this.constraintsMedia = this.previousConstraint;
if(!this.constraintsMedia.video){ if (!this.constraintsMedia.video) {
this.disableCameraStyle(); this.disableCameraStyle();
}else{ } else {
this.enableCameraStyle(); this.enableCameraStyle();
} }
if(!this.constraintsMedia.audio){ if (!this.constraintsMedia.audio) {
this.disableMicrophoneStyle() this.disableMicrophoneStyle()
}else{ } else {
this.enableMicrophoneStyle() this.enableMicrophoneStyle()
} }
@ -331,13 +331,13 @@ export class MediaManager {
}); });
} }
private enableCameraStyle(){ private enableCameraStyle() {
this.cinemaClose.style.display = "none"; this.cinemaClose.style.display = "none";
this.cinemaBtn.classList.remove("disabled"); this.cinemaBtn.classList.remove("disabled");
this.cinema.style.display = "block"; this.cinema.style.display = "block";
} }
private disableCameraStyle(){ private disableCameraStyle() {
this.cinemaClose.style.display = "block"; this.cinemaClose.style.display = "block";
this.cinema.style.display = "none"; this.cinema.style.display = "none";
this.cinemaBtn.classList.add("disabled"); this.cinemaBtn.classList.add("disabled");
@ -345,13 +345,13 @@ export class MediaManager {
this.myCamVideo.srcObject = null; this.myCamVideo.srcObject = null;
} }
private enableMicrophoneStyle(){ private enableMicrophoneStyle() {
this.microphoneClose.style.display = "none"; this.microphoneClose.style.display = "none";
this.microphone.style.display = "block"; this.microphone.style.display = "block";
this.microphoneBtn.classList.remove("disabled"); this.microphoneBtn.classList.remove("disabled");
} }
private disableMicrophoneStyle(){ private disableMicrophoneStyle() {
this.microphoneClose.style.display = "block"; this.microphoneClose.style.display = "block";
this.microphone.style.display = "none"; this.microphone.style.display = "none";
this.microphoneBtn.classList.add("disabled"); this.microphoneBtn.classList.add("disabled");
@ -399,7 +399,7 @@ export class MediaManager {
} }
//get screen //get screen
getScreenMedia() : Promise<MediaStream>{ getScreenMedia(): Promise<MediaStream> {
try { try {
return this._startScreenCapture() return this._startScreenCapture()
.then((stream: MediaStream) => { .then((stream: MediaStream) => {
@ -421,7 +421,7 @@ export class MediaManager {
console.error("Error => getScreenMedia => ", err); console.error("Error => getScreenMedia => ", err);
throw err; throw err;
}); });
}catch (err) { } catch (err) {
return new Promise((resolve, reject) => { // eslint-disable-line no-unused-vars return new Promise((resolve, reject) => { // eslint-disable-line no-unused-vars
reject(err); reject(err);
}); });
@ -430,9 +430,9 @@ export class MediaManager {
private _startScreenCapture() { private _startScreenCapture() {
if (navigator.getDisplayMedia) { if (navigator.getDisplayMedia) {
return navigator.getDisplayMedia({video: true}); return navigator.getDisplayMedia({ video: true });
} else if (navigator.mediaDevices.getDisplayMedia) { } else if (navigator.mediaDevices.getDisplayMedia) {
return navigator.mediaDevices.getDisplayMedia({video: true}); return navigator.mediaDevices.getDisplayMedia({ video: true });
} else { } else {
return new Promise((resolve, reject) => { // eslint-disable-line no-unused-vars return new Promise((resolve, reject) => { // eslint-disable-line no-unused-vars
reject("error sharing screen"); reject("error sharing screen");
@ -455,7 +455,7 @@ export class MediaManager {
this.disableCameraStyle(); this.disableCameraStyle();
this.stopCamera(); this.stopCamera();
return this.getLocalStream().then((stream : MediaStream) => { return this.getLocalStream().then((stream: MediaStream) => {
this.hasCamera = false; this.hasCamera = false;
return stream; return stream;
}).catch((err) => { }).catch((err) => {
@ -472,8 +472,8 @@ export class MediaManager {
console.info(`${width}x${height}`); // 6*/ console.info(`${width}x${height}`); // 6*/
} }
private getLocalStream() : Promise<MediaStream> { private getLocalStream(): Promise<MediaStream> {
return navigator.mediaDevices.getUserMedia(this.constraintsMedia).then((stream : MediaStream) => { return navigator.mediaDevices.getUserMedia(this.constraintsMedia).then((stream: MediaStream) => {
this.localStream = stream; this.localStream = stream;
this.myCamVideo.srcObject = this.localStream; this.myCamVideo.srcObject = this.localStream;
@ -514,7 +514,7 @@ export class MediaManager {
setCamera(id: string): Promise<MediaStream> { setCamera(id: string): Promise<MediaStream> {
let video = this.constraintsMedia.video; let video = this.constraintsMedia.video;
if (typeof(video) === 'boolean' || video === undefined) { if (typeof (video) === 'boolean' || video === undefined) {
video = {} video = {}
} }
video.deviceId = { video.deviceId = {
@ -526,7 +526,7 @@ export class MediaManager {
setMicrophone(id: string): Promise<MediaStream> { setMicrophone(id: string): Promise<MediaStream> {
let audio = this.constraintsMedia.audio; let audio = this.constraintsMedia.audio;
if (typeof(audio) === 'boolean' || audio === undefined) { if (typeof (audio) === 'boolean' || audio === undefined) {
audio = {} audio = {}
} }
audio.deviceId = { audio.deviceId = {
@ -536,9 +536,9 @@ export class MediaManager {
return this.getCamera(); return this.getCamera();
} }
addActiveVideo(user: UserSimplePeerInterface, userName: string = ""){ addActiveVideo(user: UserSimplePeerInterface, userName: string = "") {
this.webrtcInAudio.play(); this.webrtcInAudio.play();
const userId = ''+user.userId const userId = '' + user.userId
userName = userName.toUpperCase(); userName = userName.toUpperCase();
const color = this.getColorByString(userName); const color = this.getColorByString(userName);
@ -571,7 +571,7 @@ export class MediaManager {
//permit to create participant in discussion part //permit to create participant in discussion part
const showReportUser = () => { const showReportUser = () => {
for(const callBack of this.showReportModalCallBacks){ for (const callBack of this.showReportModalCallBacks) {
callBack(userId, userName); callBack(userId, userName);
} }
}; };
@ -603,17 +603,17 @@ export class MediaManager {
return `screen-sharing-${userId}`; return `screen-sharing-${userId}`;
} }
disabledMicrophoneByUserId(userId: number){ disabledMicrophoneByUserId(userId: number) {
const element = document.getElementById(`microphone-${userId}`); const element = document.getElementById(`microphone-${userId}`);
if(!element){ if (!element) {
return; return;
} }
element.classList.add('active') //todo: why does a method 'disable' add a class 'active'? element.classList.add('active') //todo: why does a method 'disable' add a class 'active'?
} }
enabledMicrophoneByUserId(userId: number){ enabledMicrophoneByUserId(userId: number) {
const element = document.getElementById(`microphone-${userId}`); const element = document.getElementById(`microphone-${userId}`);
if(!element){ if (!element) {
return; return;
} }
element.classList.remove('active') //todo: why does a method 'enable' remove a class 'active'? element.classList.remove('active') //todo: why does a method 'enable' remove a class 'active'?
@ -630,22 +630,22 @@ export class MediaManager {
} }
} }
enabledVideoByUserId(userId: number){ enabledVideoByUserId(userId: number) {
let element = document.getElementById(`${userId}`); let element = document.getElementById(`${userId}`);
if(element){ if (element) {
element.style.opacity = "1"; element.style.opacity = "1";
} }
element = document.getElementById(`name-${userId}`); element = document.getElementById(`name-${userId}`);
if(element){ if (element) {
element.style.display = "none"; element.style.display = "none";
} }
} }
toggleBlockLogo(userId: number, show: boolean): void { toggleBlockLogo(userId: number, show: boolean): void {
const blockLogoElement = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('blocking-'+userId); const blockLogoElement = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('blocking-' + userId);
show ? blockLogoElement.classList.add('active') : blockLogoElement.classList.remove('active'); show ? blockLogoElement.classList.add('active') : blockLogoElement.classList.remove('active');
} }
addStreamRemoteVideo(userId: string, stream : MediaStream): void { 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}`;
@ -659,7 +659,7 @@ export class MediaManager {
this.soundMeters.set(userId, soundMeter); this.soundMeters.set(userId, soundMeter);
this.soundMeterElements.set(userId, HtmlUtils.getElementByIdOrFail<HTMLImageElement>('soundMeter-'+userId));*/ this.soundMeterElements.set(userId, HtmlUtils.getElementByIdOrFail<HTMLImageElement>('soundMeter-'+userId));*/
} }
addStreamRemoteScreenSharing(userId: string, stream : MediaStream){ 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 // 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(this.getScreenSharingId(userId)); const remoteVideo = this.remoteVideo.get(this.getScreenSharingId(userId));
if (remoteVideo === undefined) { if (remoteVideo === undefined) {
@ -669,7 +669,7 @@ export class MediaManager {
this.addStreamRemoteVideo(this.getScreenSharingId(userId), stream); this.addStreamRemoteVideo(this.getScreenSharingId(userId), stream);
} }
removeActiveVideo(userId: string){ removeActiveVideo(userId: string) {
layoutManager.remove(userId); layoutManager.remove(userId);
this.remoteVideo.delete(userId); this.remoteVideo.delete(userId);
@ -708,10 +708,10 @@ export class MediaManager {
isError(userId: string): void { isError(userId: string): void {
console.info("isError", `div-${userId}`); console.info("isError", `div-${userId}`);
const element = document.getElementById(`div-${userId}`); const element = document.getElementById(`div-${userId}`);
if(!element){ if (!element) {
return; return;
} }
const errorDiv = element.getElementsByClassName('rtc-error').item(0) as HTMLDivElement|null; const errorDiv = element.getElementsByClassName('rtc-error').item(0) as HTMLDivElement | null;
if (errorDiv === null) { if (errorDiv === null) {
return; return;
} }
@ -722,16 +722,16 @@ export class MediaManager {
} }
private getSpinner(userId: string): HTMLDivElement|null { private getSpinner(userId: string): HTMLDivElement | null {
const element = document.getElementById(`div-${userId}`); const element = document.getElementById(`div-${userId}`);
if(!element){ if (!element) {
return null; return null;
} }
const connnectingSpinnerDiv = element.getElementsByClassName('connecting-spinner').item(0) as HTMLDivElement|null; const connnectingSpinnerDiv = element.getElementsByClassName('connecting-spinner').item(0) as HTMLDivElement | null;
return connnectingSpinnerDiv; return connnectingSpinnerDiv;
} }
private getColorByString(str: String) : String|null { private getColorByString(str: String): String | null {
let hash = 0; let hash = 0;
if (str.length === 0) return null; if (str.length === 0) return null;
for (let i = 0; i < str.length; i++) { for (let i = 0; i < str.length; i++) {
@ -746,18 +746,18 @@ export class MediaManager {
return color; return color;
} }
public addNewParticipant(userId: number|string, name: string|undefined, img?: string, showReportUserCallBack?: ShowReportCallBack){ public addNewParticipant(userId: number | string, name: string | undefined, img?: string, showReportUserCallBack?: ShowReportCallBack) {
discussionManager.addParticipant(userId, name, img, false, showReportUserCallBack); discussionManager.addParticipant(userId, name, img, false, showReportUserCallBack);
} }
public removeParticipant(userId: number|string){ public removeParticipant(userId: number | string) {
discussionManager.removeParticipant(userId); discussionManager.removeParticipant(userId);
} }
public addTriggerCloseJitsiFrameButton(id: String, Function: Function){ public addTriggerCloseJitsiFrameButton(id: String, Function: Function) {
this.triggerCloseJistiFrame.set(id, Function); this.triggerCloseJistiFrame.set(id, Function);
} }
public removeTriggerCloseJitsiFrameButton(id: String){ public removeTriggerCloseJitsiFrameButton(id: String) {
this.triggerCloseJistiFrame.delete(id); this.triggerCloseJistiFrame.delete(id);
} }
@ -770,45 +770,45 @@ export class MediaManager {
* For some reasons, the microphone muted icon or the stream is not always up to date. * 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) * 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(){ private pingCameraStatus() {
/*setInterval(() => { /*setInterval(() => {
console.log('ping camera status'); console.log('ping camera status');
this.triggerUpdatedLocalStreamCallbacks(this.localStream); this.triggerUpdatedLocalStreamCallbacks(this.localStream);
}, 30000);*/ }, 30000);*/
} }
public addNewMessage(name: string, message: string, isMe: boolean = false){ public addNewMessage(name: string, message: string, isMe: boolean = false) {
discussionManager.addMessage(name, message, isMe); discussionManager.addMessage(name, message, isMe);
//when there are new message, show discussion //when there are new message, show discussion
if(!discussionManager.activatedDiscussion) { if (!discussionManager.activatedDiscussion) {
discussionManager.showDiscussionPart(); discussionManager.showDiscussionPart();
} }
} }
public addSendMessageCallback(userId: string|number, callback: SendMessageCallback){ public addSendMessageCallback(userId: string | number, callback: SendMessageCallback) {
discussionManager.onSendMessageCallback(userId, callback); discussionManager.onSendMessageCallback(userId, callback);
} }
get activatedDiscussion(){ get activatedDiscussion() {
return discussionManager.activatedDiscussion; return discussionManager.activatedDiscussion;
} }
public setUserInputManager(userInputManager : UserInputManager){ public setUserInputManager(userInputManager: UserInputManager) {
this.userInputManager = userInputManager; this.userInputManager = userInputManager;
discussionManager.setUserInputManager(userInputManager); discussionManager.setUserInputManager(userInputManager);
} }
public setShowReportModalCallBacks(callback: ShowReportCallBack){ public setShowReportModalCallBacks(callback: ShowReportCallBack) {
this.showReportModalCallBacks.add(callback); this.showReportModalCallBacks.add(callback);
} }
public setHelpCameraSettingsCallBack(callback: HelpCameraSettingsCallBack){ public setHelpCameraSettingsCallBack(callback: HelpCameraSettingsCallBack) {
this.helpCameraSettingsCallBacks.add(callback); this.helpCameraSettingsCallBacks.add(callback);
} }
private showHelpCameraSettingsCallBack(){ private showHelpCameraSettingsCallBack() {
for(const callBack of this.helpCameraSettingsCallBacks){ for (const callBack of this.helpCameraSettingsCallBacks) {
callBack(); callBack();
} }
} }
@ -822,33 +822,33 @@ export class MediaManager {
for(const indexUserId of this.soundMeters.keys()){ for(const indexUserId of this.soundMeters.keys()){
const soundMeter = this.soundMeters.get(indexUserId); const soundMeter = this.soundMeters.get(indexUserId);
const soundMeterElement = this.soundMeterElements.get(indexUserId); const soundMeterElement = this.soundMeterElements.get(indexUserId);
if(!soundMeter || !soundMeterElement){ if (!soundMeter || !soundMeterElement) {
return; return;
} }
const volumeByUser = parseInt((soundMeter.getVolume() / 10).toFixed(0)); const volumeByUser = parseInt((soundMeter.getVolume() / 10).toFixed(0));
this.setVolumeSoundMeter(volumeByUser, soundMeterElement); this.setVolumeSoundMeter(volumeByUser, soundMeterElement);
} }
}catch(err){ } catch (err) {
//console.error(err); //console.error(err);
} }
}*/ }*/
private setVolumeSoundMeter(volume: number, element: HTMLDivElement){ private setVolumeSoundMeter(volume: number, element: HTMLDivElement) {
if(volume <= 0 && !element.classList.contains('active')){ if (volume <= 0 && !element.classList.contains('active')) {
return; return;
} }
element.classList.remove('active'); element.classList.remove('active');
if(volume <= 0){ if (volume <= 0) {
return; return;
} }
element.classList.add('active'); element.classList.add('active');
element.childNodes.forEach((value: ChildNode, index) => { element.childNodes.forEach((value: ChildNode, index) => {
const elementChildre = element.children.item(index); const elementChildre = element.children.item(index);
if(!elementChildre){ if (!elementChildre) {
return; return;
} }
elementChildre.classList.remove('active'); elementChildre.classList.remove('active');
if((index +1) > volume){ if ((index + 1) > volume) {
return; return;
} }
elementChildre.classList.add('active'); elementChildre.classList.add('active');

View File

@ -1,5 +1,5 @@
import type { ChatEvent } from "./Api/Events/ChatEvent"; import type { ChatEvent } from "./Api/Events/ChatEvent";
import { isIframeResponseEventWrapper } from "./Api/Events/IframeEvent"; import { IframeEvent, IframeEventMap, isIframeResponseEventWrapper } from "./Api/Events/IframeEvent";
import { isUserInputChatEvent, UserInputChatEvent } from "./Api/Events/UserInputChatEvent"; import { isUserInputChatEvent, UserInputChatEvent } from "./Api/Events/UserInputChatEvent";
import { Subject } from "rxjs"; import { Subject } from "rxjs";
import { EnterLeaveEvent, isEnterLeaveEvent } from "./Api/Events/EnterLeaveEvent"; import { EnterLeaveEvent, isEnterLeaveEvent } from "./Api/Events/EnterLeaveEvent";
@ -11,6 +11,9 @@ import type { GoToPageEvent } from "./Api/Events/GoToPageEvent";
import type { OpenCoWebSiteEvent } from "./Api/Events/OpenCoWebSiteEvent"; import type { OpenCoWebSiteEvent } from "./Api/Events/OpenCoWebSiteEvent";
import type { LayerEvent } from "./Api/Events/LayerEvent"; import type { LayerEvent } from "./Api/Events/LayerEvent";
import type { SetPropertyEvent } from "./Api/Events/setPropertyEvent"; import type { SetPropertyEvent } from "./Api/Events/setPropertyEvent";
import { GameStateEvent, isGameStateEvent } from './Api/Events/GameStateEvent';
import { HasPlayerMovedEvent, HasPlayerMovedEventCallback, isHasPlayerMovedEvent } from './Api/Events/HasPlayerMovedEvent';
import { HasDataLayerChangedEvent, HasDataLayerChangedEventCallback, isHasDataLayerChangedEvent} from "./Api/Events/HasDataLayerChangedEvent";
interface WorkAdventureApi { interface WorkAdventureApi {
sendChatMessage(message: string, author: string): void; sendChatMessage(message: string, author: string): void;
@ -18,9 +21,9 @@ interface WorkAdventureApi {
onEnterZone(name: string, callback: () => void): void; onEnterZone(name: string, callback: () => void): void;
onLeaveZone(name: string, callback: () => void): void; onLeaveZone(name: string, callback: () => void): void;
openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup; openPopup(targetObject: string, message: string, buttons: ButtonDescriptor[]): Popup;
openTab(url : string): void; openTab(url: string): void;
goToPage(url : string): void; goToPage(url: string): void;
openCoWebSite(url : string): void; openCoWebSite(url: string): void;
closeCoWebSite(): void; closeCoWebSite(): void;
disablePlayerControls() : void; disablePlayerControls() : void;
restorePlayerControls() : void; restorePlayerControls() : void;
@ -29,6 +32,18 @@ interface WorkAdventureApi {
showLayer(layer: string) : void; showLayer(layer: string) : void;
hideLayer(layer: string) : void; hideLayer(layer: string) : void;
setProperty(layerName: string, propertyName: string, propertyValue: string | number | boolean | undefined): void; setProperty(layerName: string, propertyName: string, propertyValue: string | number | boolean | undefined): void;
disablePlayerControls(): void;
restorePlayerControls(): void;
displayBubble(): void;
removeBubble(): void;
getMapUrl(): Promise<string>;
getUuid(): Promise<string | undefined>;
getRoomId(): Promise<string>;
getStartLayerName(): Promise<string | null>;
onPlayerMove(callback: (playerMovedEvent: HasPlayerMovedEvent) => void): void
onDataLayerChange(callback: (dataLayerChangedEvent: HasDataLayerChangedEvent) => void): void
} }
declare global { declare global {
@ -78,8 +93,82 @@ class Popup {
}, '*'); }, '*');
} }
} }
function uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
function getGameState(): Promise<GameStateEvent> {
if (immutableData) {
return Promise.resolve(immutableData);
}
else {
return new Promise<GameStateEvent>((resolver, thrower) => {
stateResolvers.push(resolver);
window.parent.postMessage({
type: "getState"
}, "*")
})
}
}
const stateResolvers: Array<(event: GameStateEvent) => void> = []
let immutableData: GameStateEvent;
const callbackPlayerMoved: { [type: string]: HasPlayerMovedEventCallback | ((arg?: HasPlayerMovedEvent | never) => void) } = {}
const callbackDataLayerChanged: { [type: string]: HasDataLayerChangedEventCallback | ((arg?: HasDataLayerChangedEvent | never) => void) } = {}
function postToParent(content: IframeEvent<keyof IframeEventMap>) {
window.parent.postMessage(content, "*")
}
let playerUuid: string | undefined;
window.WA = { window.WA = {
onPlayerMove(callback: HasPlayerMovedEventCallback): void {
playerUuid = uuidv4();
callbackPlayerMoved[playerUuid] = callback;
postToParent({
type: "onPlayerMove",
data: undefined
})
},
onDataLayerChange(callback: HasDataLayerChangedEventCallback): void {
callbackDataLayerChanged['test'] = callback;
postToParent({
type : "onDataLayerChange",
data: undefined
})
},
getMapUrl() {
return getGameState().then((res) => {
return res.mapUrl;
})
},
getUuid() {
return getGameState().then((res) => {
return res.uuid;
})
},
getRoomId() {
return getGameState().then((res) => {
return res.roomId;
})
},
getStartLayerName() {
return getGameState().then((res) => {
return res.startLayerName;
})
},
/** /**
* Send a message in the chat. * Send a message in the chat.
* Only the local user will receive this message. * Only the local user will receive this message.
@ -153,10 +242,10 @@ window.WA = {
}, '*'); }, '*');
}, },
openCoWebSite(url : string) : void{ openCoWebSite(url: string): void {
window.parent.postMessage({ window.parent.postMessage({
"type" : 'openCoWebSite', "type": 'openCoWebSite',
"data" : { "data": {
url url
} as OpenCoWebSiteEvent } as OpenCoWebSiteEvent
}, '*'); }, '*');
@ -255,6 +344,15 @@ window.addEventListener('message', message => {
if (callback) { if (callback) {
callback(popup); callback(popup);
} }
} else if (payload.type == "gameState" && isGameStateEvent(payloadData)) {
stateResolvers.forEach(resolver => {
resolver(payloadData);
})
immutableData = payloadData;
} else if (payload.type == "hasPlayerMoved" && isHasPlayerMovedEvent(payloadData) && playerUuid) {
callbackPlayerMoved[playerUuid](payloadData)
} else if (payload.type == "hasDataLayerChanged" && isHasDataLayerChangedEvent(payloadData)) {
callbackDataLayerChanged['test'](payloadData)
} }
} }

View File

@ -0,0 +1,230 @@
{ "compressionlevel":-1,
"height":10,
"infinite":false,
"layers":[
{
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":10,
"id":1,
"name":"start",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"data":[46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 33, 34, 34, 34, 34, 34, 34, 35, 46, 46, 41, 42, 42, 42, 42, 42, 42, 43, 46, 46, 41, 42, 42, 42, 42, 42, 42, 43, 46, 46, 41, 42, 42, 42, 42, 42, 42, 43, 46, 46, 41, 42, 42, 42, 42, 42, 42, 43, 46, 46, 41, 42, 42, 42, 42, 42, 42, 43, 46, 46, 41, 42, 42, 42, 42, 42, 42, 43, 46, 46, 49, 50, 50, 50, 50, 50, 50, 51, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46],
"height":10,
"id":2,
"name":"bottom",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 52, 52, 0, 0, 0, 0, 0, 0, 0, 52, 52, 52, 0, 0, 0, 0, 0, 0, 0, 52, 52, 52, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
"height":10,
"id":4,
"name":"metadata",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
},
{
"draworder":"topdown",
"id":5,
"name":"floorLayer",
"objects":[],
"opacity":1,
"type":"objectgroup",
"visible":true,
"x":0,
"y":0
},
{
"data":[1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 9, 0, 0, 0, 0, 0, 0, 0, 0, 11, 9, 0, 0, 0, 0, 0, 0, 0, 0, 11, 9, 0, 0, 0, 0, 0, 0, 0, 0, 11, 9, 0, 0, 0, 0, 0, 0, 0, 0, 11, 9, 0, 0, 0, 0, 0, 0, 0, 0, 11, 9, 0, 0, 0, 0, 0, 0, 0, 0, 11, 9, 0, 0, 0, 0, 0, 0, 0, 0, 11, 9, 0, 0, 0, 0, 0, 0, 0, 0, 11, 17, 18, 18, 18, 18, 18, 18, 18, 18, 19],
"height":10,
"id":3,
"name":"wall",
"opacity":1,
"type":"tilelayer",
"visible":true,
"width":10,
"x":0,
"y":0
}],
"nextlayerid":6,
"nextobjectid":1,
"orientation":"orthogonal",
"properties":[
{
"name":"script",
"type":"string",
"value":"script.js"
}],
"renderorder":"right-down",
"tiledversion":"1.4.3",
"tileheight":32,
"tilesets":[
{
"columns":8,
"firstgid":1,
"image":"tileset_dungeon.png",
"imageheight":256,
"imagewidth":256,
"margin":0,
"name":"TDungeon",
"spacing":0,
"tilecount":64,
"tileheight":32,
"tiles":[
{
"id":0,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":1,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":2,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":3,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":4,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":8,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":9,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":10,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":11,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":12,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":16,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":17,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":18,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":19,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
},
{
"id":20,
"properties":[
{
"name":"collides",
"type":"bool",
"value":true
}]
}],
"tilewidth":32
}],
"tilewidth":32,
"type":"map",
"version":1.4,
"width":10
}

View File

@ -0,0 +1,9 @@
WA.getMapUrl().then((map) => {console.log('mapUrl : ', map)});
WA.getUuid().then((uuid) => {console.log('Uuid : ',uuid)});
WA.getRoomId().then((roomId) => console.log('roomID : ',roomId));
WA.listenPositionPlayer(console.log);

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB