2021-06-25 16:32:48 +02:00
import { Queue } from 'queue-typescript' ;
import type { Subscription } from "rxjs" ;
import { GlobalMessageManager } from "../../Administration/GlobalMessageManager" ;
import { userMessageManager } from "../../Administration/UserMessageManager" ;
import { iframeListener } from "../../Api/IframeListener" ;
import { connectionManager } from "../../Connexion/ConnectionManager" ;
2021-05-12 09:13:25 +02:00
import type {
2020-08-16 23:19:04 +02:00
GroupCreatedUpdatedMessageInterface ,
MessageUserJoined ,
2020-05-19 19:11:12 +02:00
MessageUserMovedInterface ,
2021-03-05 18:25:27 +01:00
MessageUserPositionInterface ,
OnConnectInterface ,
2020-08-16 23:19:04 +02:00
PointInterface ,
2020-08-17 22:51:37 +02:00
PositionInterface ,
RoomJoinedMessageInterface
2020-09-25 18:29:22 +02:00
} from "../../Connexion/ConnexionModels" ;
2021-06-25 16:32:48 +02:00
import { localUserStore } from "../../Connexion/LocalUserStore" ;
import { Room } from "../../Connexion/Room" ;
import type { RoomConnection } from "../../Connexion/RoomConnection" ;
import { worldFullMessageStream } from "../../Connexion/WorldFullMessageStream" ;
2021-04-14 15:36:25 +02:00
import {
DEBUG_MODE ,
JITSI_PRIVATE_MODE ,
MAX_PER_GROUP ,
2021-06-21 18:22:31 +02:00
POSITION_DELAY
2021-04-14 15:36:25 +02:00
} from "../../Enum/EnvironmentVariable" ;
2021-06-25 16:32:48 +02:00
import { TextureError } from "../../Exception/TextureError" ;
import type { UserMovedMessage } from "../../Messages/generated/messages_pb" ;
import { ProtobufClientUtils } from "../../Network/ProtobufClientUtils" ;
import { touchScreenManager } from "../../Touch/TouchScreenManager" ;
import { urlManager } from "../../Url/UrlManager" ;
import { audioManager } from "../../WebRtc/AudioManager" ;
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager" ;
import { HtmlUtils } from "../../WebRtc/HtmlUtils" ;
import { jitsiFactory } from "../../WebRtc/JitsiFactory" ;
2020-11-23 20:34:05 +01:00
import {
2021-06-24 10:49:55 +02:00
AUDIO_LOOP_PROPERTY , AUDIO_VOLUME_PROPERTY ,
Box ,
2021-02-17 19:28:41 +01:00
JITSI_MESSAGE_PROPERTIES ,
2020-11-23 20:34:05 +01:00
layoutManager ,
2021-01-25 12:21:40 +01:00
ON_ACTION_TRIGGER_BUTTON ,
TRIGGER_JITSI_PROPERTIES ,
TRIGGER_WEBSITE_PROPERTIES ,
2021-06-21 18:22:31 +02:00
WEBSITE_MESSAGE_PROPERTIES
2020-11-23 20:34:05 +01:00
} from "../../WebRtc/LayoutManager" ;
2021-06-25 16:32:48 +02:00
import { mediaManager } from "../../WebRtc/MediaManager" ;
import { SimplePeer , UserSimplePeerInterface } from "../../WebRtc/SimplePeer" ;
import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager" ;
import { ChatModeIcon } from "../Components/ChatModeIcon" ;
import { addLoader } from "../Components/Loader" ;
import { joystickBaseImg , joystickBaseKey , joystickThumbImg , joystickThumbKey } from "../Components/MobileJoystick" ;
import { OpenChatIcon , openChatIconName } from "../Components/OpenChatIcon" ;
import { PresentationModeIcon } from "../Components/PresentationModeIcon" ;
import { TextUtils } from "../Components/TextUtils" ;
import { lazyLoadPlayerCharacterTextures , loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager" ;
import { RemotePlayer } from "../Entity/RemotePlayer" ;
import type { ActionableItem } from "../Items/ActionableItem" ;
import type { ItemFactoryInterface } from "../Items/ItemFactoryInterface" ;
import { SelectCharacterScene , SelectCharacterSceneName } from "../Login/SelectCharacterScene" ;
2021-06-21 18:22:31 +02:00
import type {
ITiledMap ,
ITiledMapLayer ,
ITiledMapLayerProperty ,
ITiledMapObject ,
ITiledMapTileLayer ,
2021-06-25 16:32:48 +02:00
ITiledTileSet } from "../Map/ITiledMap" ;
import { MenuScene , MenuSceneName } from '../Menu/MenuScene' ;
import { PlayerAnimationDirections } from "../Player/Animation" ;
import { hasMovedEventName , Player , requestEmoteEventName } from "../Player/Player" ;
import { ErrorSceneName } from "../Reconnecting/ErrorScene" ;
import { ReconnectingSceneName } from "../Reconnecting/ReconnectingScene" ;
import { waScaleManager } from "../Services/WaScaleManager" ;
import { PinchManager } from "../UserInput/PinchManager" ;
import { UserInputManager } from "../UserInput/UserInputManager" ;
import type { AddPlayerInterface } from "./AddPlayerInterface" ;
import { DEPTH_OVERLAY_INDEX } from "./DepthIndexes" ;
import { DirtyScene } from "./DirtyScene" ;
import { EmoteManager } from "./EmoteManager" ;
import { gameManager } from "./GameManager" ;
import { GameMap } from "./GameMap" ;
import { PlayerMovement } from "./PlayerMovement" ;
import { PlayersPositionInterpolator } from "./PlayersPositionInterpolator" ;
2021-03-05 18:25:27 +01:00
import Texture = Phaser . Textures . Texture ;
import Sprite = Phaser . GameObjects . Sprite ;
import CanvasTexture = Phaser . Textures . CanvasTexture ;
import GameObject = Phaser . GameObjects . GameObject ;
import FILE_LOAD_ERROR = Phaser . Loader . Events . FILE_LOAD_ERROR ;
2021-03-08 09:28:15 +01:00
import DOMElement = Phaser . GameObjects . DOMElement ;
2021-06-24 08:56:29 +02:00
import EVENT_TYPE = Phaser . Scenes . Events
2021-04-23 18:30:41 +02:00
import RenderTexture = Phaser . GameObjects . RenderTexture ;
import Tilemap = Phaser . Tilemaps . Tilemap ;
2021-06-25 16:32:48 +02:00
import type { HasPlayerMovedEvent } from '../../Api/Events/HasPlayerMovedEvent' ;
2020-04-11 18:17:36 +02:00
2021-05-13 15:24:35 +02:00
import AnimatedTiles from "phaser-animated-tiles" ;
2021-06-23 09:25:13 +02:00
import { soundManager } from "./SoundManager" ;
2021-06-24 11:54:09 +02:00
import { peerStore , screenSharingPeerStore } from "../../Stores/PeerStore" ;
2021-06-15 14:45:01 +02:00
import { videoFocusStore } from "../../Stores/VideoFocusStore" ;
2021-06-21 14:07:03 +02:00
import { biggestAvailableAreaStore } from "../../Stores/BiggestAvailableAreaStore" ;
2020-04-11 18:17:36 +02:00
2020-06-07 13:23:32 +02:00
export interface GameSceneInitInterface {
2021-06-24 08:56:29 +02:00
initPosition : PointInterface | null ,
2020-11-13 18:00:22 +01:00
reconnecting : boolean
2020-05-23 15:43:26 +02:00
}
2020-06-19 18:18:43 +02:00
interface InitUserPositionEventInterface {
type : 'InitUserPositionEvent'
event : MessageUserPositionInterface [ ]
}
interface AddPlayerEventInterface {
type : 'AddPlayerEvent'
event : AddPlayerInterface
}
interface RemovePlayerEventInterface {
type : 'RemovePlayerEvent'
2020-09-18 13:57:38 +02:00
userId : number
2020-06-19 18:18:43 +02:00
}
interface UserMovedEventInterface {
type : 'UserMovedEvent'
event : MessageUserMovedInterface
}
interface GroupCreatedUpdatedEventInterface {
type : 'GroupCreatedUpdatedEvent'
event : GroupCreatedUpdatedMessageInterface
}
interface DeleteGroupEventInterface {
type : 'DeleteGroupEvent'
2020-09-21 11:24:03 +02:00
groupId : number
2020-06-19 18:18:43 +02:00
}
2020-11-19 14:32:18 +01:00
const defaultStartLayerName = 'start' ;
2021-06-17 18:48:56 +02:00
export class GameScene extends DirtyScene {
2021-06-24 08:56:29 +02:00
Terrains : Array < Phaser.Tilemaps.Tileset > ;
2021-05-10 17:10:41 +02:00
CurrentPlayer ! : Player ;
2020-08-07 23:39:06 +02:00
MapPlayers ! : Phaser . Physics . Arcade . Group ;
2021-06-24 08:56:29 +02:00
MapPlayersByKey : Map < number , RemotePlayer > = new Map < number , RemotePlayer > ( ) ;
2020-08-07 23:39:06 +02:00
Map ! : Phaser . Tilemaps . Tilemap ;
Objects ! : Array < Phaser.Physics.Arcade.Sprite > ;
mapFile ! : ITiledMap ;
2021-05-13 15:24:35 +02:00
animatedTiles ! : AnimatedTiles ;
2020-09-21 11:24:03 +02:00
groups : Map < number , Sprite > ;
2020-08-07 23:39:06 +02:00
startX ! : number ;
startY ! : number ;
circleTexture ! : CanvasTexture ;
2020-10-21 16:07:42 +02:00
circleRedTexture ! : CanvasTexture ;
2021-06-24 08:56:29 +02:00
pendingEvents : Queue < InitUserPositionEventInterface | AddPlayerEventInterface | RemovePlayerEventInterface | UserMovedEventInterface | GroupCreatedUpdatedEventInterface | DeleteGroupEventInterface > = new Queue < InitUserPositionEventInterface | AddPlayerEventInterface | RemovePlayerEventInterface | UserMovedEventInterface | GroupCreatedUpdatedEventInterface | DeleteGroupEventInterface > ( ) ;
private initPosition : PositionInterface | null = null ;
2020-06-02 13:44:42 +02:00
private playersPositionInterpolator = new PlayersPositionInterpolator ( ) ;
2021-06-24 08:56:29 +02:00
public connection : RoomConnection | undefined ;
2020-08-07 23:39:06 +02:00
private simplePeer ! : SimplePeer ;
2020-09-16 18:38:50 +02:00
private GlobalMessageManager ! : GlobalMessageManager ;
2020-07-27 22:36:07 +02:00
private connectionAnswerPromise : Promise < RoomJoinedMessageInterface > ;
2021-05-12 10:35:14 +02:00
private connectionAnswerPromiseResolve ! : ( value : RoomJoinedMessageInterface | PromiseLike < RoomJoinedMessageInterface > ) = > void ;
2020-07-23 18:09:24 +02:00
// A promise that will resolve when the "create" method is called (signaling loading is ended)
private createPromise : Promise < void > ;
2020-08-17 22:51:37 +02:00
private createPromiseResolve ! : ( value? : void | PromiseLike < void > ) = > void ;
2021-06-24 08:56:29 +02:00
private iframeSubscriptionList ! : Array < Subscription > ;
2021-06-03 15:40:44 +02:00
private peerStoreUnsubscribe ! : ( ) = > void ;
2021-06-24 17:02:57 +02:00
private biggestAvailableAreaStoreUnsubscribe ! : ( ) = > void ;
2020-05-09 19:41:21 +02:00
MapUrlFile : string ;
2020-05-23 17:27:49 +02:00
RoomId : string ;
instance : string ;
2020-05-09 19:41:21 +02:00
2020-08-07 23:39:06 +02:00
currentTick ! : number ;
lastSentTick ! : number ; // The last tick at which a position was sent.
2021-05-18 11:33:16 +02:00
lastMoveEventSent : HasPlayerMovedEvent = {
2020-06-01 22:42:18 +02:00
direction : '' ,
moving : false ,
x : - 1000 ,
y : - 1000
}
2020-08-30 15:44:22 +02:00
private gameMap ! : GameMap ;
2020-07-27 22:36:07 +02:00
private actionableItems : Map < number , ActionableItem > = new Map < number , ActionableItem > ( ) ;
2020-07-23 18:09:24 +02:00
// The item that can be selected by pressing the space key.
2021-06-24 08:56:29 +02:00
private outlinedItem : ActionableItem | null = null ;
2021-01-29 21:09:10 +01:00
public userInputManager ! : UserInputManager ;
2021-06-24 08:56:29 +02:00
private isReconnecting : boolean | undefined = undefined ;
2020-11-19 14:32:18 +01:00
private startLayerName ! : string | null ;
2020-12-04 11:30:35 +01:00
private openChatIcon ! : OpenChatIcon ;
private playerName ! : string ;
private characterLayers ! : string [ ] ;
2021-06-24 08:56:29 +02:00
private companion ! : string | null ;
private messageSubscription : Subscription | null = null ;
private popUpElements : Map < number , DOMElement > = new Map < number , Phaser.GameObjects.DOMElement > ( ) ;
private originalMapUrl : string | undefined ;
private pinchManager : PinchManager | undefined ;
2021-05-10 19:13:53 +02:00
private mapTransitioning : boolean = false ; //used to prevent transitions happenning at the same time.
2021-03-31 11:21:06 +02:00
private emoteManager ! : EmoteManager ;
2020-05-09 21:28:50 +02:00
2021-06-24 08:56:29 +02:00
constructor ( private room : Room , MapUrlFile : string , customKey? : string | undefined ) {
2020-04-07 19:23:21 +02:00
super ( {
2020-12-18 15:58:49 +01:00
key : customKey ? ? room . id
2020-04-07 19:23:21 +02:00
} ) ;
2020-04-15 19:39:26 +02:00
this . Terrains = [ ] ;
2020-09-21 11:24:03 +02:00
this . groups = new Map < number , Sprite > ( ) ;
2020-10-13 18:44:50 +02:00
this . instance = room . getInstance ( ) ;
2020-12-18 15:58:49 +01:00
2020-05-09 19:41:21 +02:00
this . MapUrlFile = MapUrlFile ;
2020-10-13 18:44:50 +02:00
this . RoomId = room . id ;
2020-07-23 18:09:24 +02:00
this . createPromise = new Promise < void > ( ( resolve , reject ) : void = > {
this . createPromiseResolve = resolve ;
2021-05-11 16:43:28 +02:00
} ) ;
2020-07-27 22:36:07 +02:00
this . connectionAnswerPromise = new Promise < RoomJoinedMessageInterface > ( ( resolve , reject ) : void = > {
this . connectionAnswerPromiseResolve = resolve ;
2021-04-21 18:01:26 +02:00
} ) ;
2020-04-07 19:23:21 +02:00
}
//hook preload scene
preload ( ) : void {
2021-01-24 15:57:47 +01:00
const localUser = localUserStore . getLocalUser ( ) ;
const textures = localUser ? . textures ;
2021-06-25 16:32:48 +02:00
if ( textures ) {
for ( const texture of textures ) {
2021-01-24 15:57:47 +01:00
loadCustomTexture ( this . load , texture ) ;
}
}
2021-01-06 17:08:48 +01:00
2020-12-04 11:30:35 +01:00
this . load . image ( openChatIconName , 'resources/objects/talk.png' ) ;
2021-06-25 16:32:48 +02:00
if ( touchScreenManager . supportTouchScreen ) {
2021-04-20 11:40:39 +02:00
this . load . image ( joystickBaseKey , joystickBaseImg ) ;
this . load . image ( joystickThumbKey , joystickThumbImg ) ;
}
2021-06-03 15:40:44 +02:00
this . load . audio ( 'audio-webrtc-in' , '/resources/objects/webrtc-in.mp3' ) ;
this . load . audio ( 'audio-webrtc-out' , '/resources/objects/webrtc-out.mp3' ) ;
//this.load.audio('audio-report-message', '/resources/objects/report-message.mp3');
this . sound . pauseOnBlur = false ;
2021-06-25 16:32:48 +02:00
this . load . on ( FILE_LOAD_ERROR , ( file : { src : string } ) = > {
2021-03-28 18:35:01 +02:00
// If we happen to be in HTTP and we are trying to load a URL in HTTPS only... (this happens only in dev environments)
2021-06-25 16:32:48 +02:00
if ( window . location . protocol === 'http:' && file . src === this . MapUrlFile && file . src . startsWith ( 'http:' ) && this . originalMapUrl === undefined ) {
Allowing loading HTTP local resources from a HTTPS endpoint.
By default, maps are loaded in HTTPS if WorkAdventure is running in HTTPS, and in HTTP is WorkAdventure is running in HTTP.
Also, if WorkAdventure is running in HTTP and map loading fails, we try map loading in HTTPS (useful when we are working on WorkAdventure locally and want to load a map on a secure domain).
This commit adds the last combination: If WorkAdventure is running in HTTPS, and map loading fails in HTTPS **AND** if the map URL is targetting "localhost", "*.localhost" or "127.0.0.1", then we attempt to load the resource in HTTP.
Why?
"localhost" is considered secure context by modern browsers. So even if a page is loaded in HTTPS, it can load resources from any secure context (including localhost in HTTP).
This means that from "https://play.workadventu.re", I can now test a map running locally on my machine (served by a classic webserver without any certificate).
This change should make map testing easier, since map developers will not have to install the whole WorkAdventure project to test their map locally.
2021-04-11 14:56:03 +02:00
this . originalMapUrl = this . MapUrlFile ;
2021-03-28 18:35:01 +02:00
this . MapUrlFile = this . MapUrlFile . replace ( 'http://' , 'https://' ) ;
this . load . tilemapTiledJSON ( this . MapUrlFile , this . MapUrlFile ) ;
2021-06-24 08:56:29 +02:00
this . load . on ( 'filecomplete-tilemapJSON-' + this . MapUrlFile , ( key : string , type : string , data : unknown ) = > {
2021-03-28 18:35:01 +02:00
this . onMapLoad ( data ) ;
} ) ;
return ;
}
Allowing loading HTTP local resources from a HTTPS endpoint.
By default, maps are loaded in HTTPS if WorkAdventure is running in HTTPS, and in HTTP is WorkAdventure is running in HTTP.
Also, if WorkAdventure is running in HTTP and map loading fails, we try map loading in HTTPS (useful when we are working on WorkAdventure locally and want to load a map on a secure domain).
This commit adds the last combination: If WorkAdventure is running in HTTPS, and map loading fails in HTTPS **AND** if the map URL is targetting "localhost", "*.localhost" or "127.0.0.1", then we attempt to load the resource in HTTP.
Why?
"localhost" is considered secure context by modern browsers. So even if a page is loaded in HTTPS, it can load resources from any secure context (including localhost in HTTP).
This means that from "https://play.workadventu.re", I can now test a map running locally on my machine (served by a classic webserver without any certificate).
This change should make map testing easier, since map developers will not have to install the whole WorkAdventure project to test their map locally.
2021-04-11 14:56:03 +02:00
// 127.0.0.1, localhost and *.localhost are considered secure, even on HTTP.
// So if we are in https, we can still try to load a HTTP local resource (can be useful for testing purposes)
// See https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure
const url = new URL ( file . src ) ;
const host = url . host . split ( ':' ) [ 0 ] ;
2021-06-25 16:32:48 +02:00
if ( window . location . protocol === 'https:' && file . src === this . MapUrlFile && ( host === '127.0.0.1' || host === 'localhost' || host . endsWith ( '.localhost' ) ) && this . originalMapUrl === undefined ) {
Allowing loading HTTP local resources from a HTTPS endpoint.
By default, maps are loaded in HTTPS if WorkAdventure is running in HTTPS, and in HTTP is WorkAdventure is running in HTTP.
Also, if WorkAdventure is running in HTTP and map loading fails, we try map loading in HTTPS (useful when we are working on WorkAdventure locally and want to load a map on a secure domain).
This commit adds the last combination: If WorkAdventure is running in HTTPS, and map loading fails in HTTPS **AND** if the map URL is targetting "localhost", "*.localhost" or "127.0.0.1", then we attempt to load the resource in HTTP.
Why?
"localhost" is considered secure context by modern browsers. So even if a page is loaded in HTTPS, it can load resources from any secure context (including localhost in HTTP).
This means that from "https://play.workadventu.re", I can now test a map running locally on my machine (served by a classic webserver without any certificate).
This change should make map testing easier, since map developers will not have to install the whole WorkAdventure project to test their map locally.
2021-04-11 14:56:03 +02:00
this . originalMapUrl = this . MapUrlFile ;
this . MapUrlFile = this . MapUrlFile . replace ( 'https://' , 'http://' ) ;
this . load . tilemapTiledJSON ( this . MapUrlFile , this . MapUrlFile ) ;
2021-06-24 08:56:29 +02:00
this . load . on ( 'filecomplete-tilemapJSON-' + this . MapUrlFile , ( key : string , type : string , data : unknown ) = > {
Allowing loading HTTP local resources from a HTTPS endpoint.
By default, maps are loaded in HTTPS if WorkAdventure is running in HTTPS, and in HTTP is WorkAdventure is running in HTTP.
Also, if WorkAdventure is running in HTTP and map loading fails, we try map loading in HTTPS (useful when we are working on WorkAdventure locally and want to load a map on a secure domain).
This commit adds the last combination: If WorkAdventure is running in HTTPS, and map loading fails in HTTPS **AND** if the map URL is targetting "localhost", "*.localhost" or "127.0.0.1", then we attempt to load the resource in HTTP.
Why?
"localhost" is considered secure context by modern browsers. So even if a page is loaded in HTTPS, it can load resources from any secure context (including localhost in HTTP).
This means that from "https://play.workadventu.re", I can now test a map running locally on my machine (served by a classic webserver without any certificate).
This change should make map testing easier, since map developers will not have to install the whole WorkAdventure project to test their map locally.
2021-04-11 14:56:03 +02:00
this . onMapLoad ( data ) ;
} ) ;
return ;
}
2021-01-17 20:34:35 +01:00
this . scene . start ( ErrorSceneName , {
title : 'Network error' ,
subTitle : 'An error occurred while loading resource:' ,
Allowing loading HTTP local resources from a HTTPS endpoint.
By default, maps are loaded in HTTPS if WorkAdventure is running in HTTPS, and in HTTP is WorkAdventure is running in HTTP.
Also, if WorkAdventure is running in HTTP and map loading fails, we try map loading in HTTPS (useful when we are working on WorkAdventure locally and want to load a map on a secure domain).
This commit adds the last combination: If WorkAdventure is running in HTTPS, and map loading fails in HTTPS **AND** if the map URL is targetting "localhost", "*.localhost" or "127.0.0.1", then we attempt to load the resource in HTTP.
Why?
"localhost" is considered secure context by modern browsers. So even if a page is loaded in HTTPS, it can load resources from any secure context (including localhost in HTTP).
This means that from "https://play.workadventu.re", I can now test a map running locally on my machine (served by a classic webserver without any certificate).
This change should make map testing easier, since map developers will not have to install the whole WorkAdventure project to test their map locally.
2021-04-11 14:56:03 +02:00
message : this.originalMapUrl ? ? file . src
2020-07-07 22:52:22 +02:00
} ) ;
} ) ;
2021-05-13 15:24:35 +02:00
this . load . scenePlugin ( 'AnimatedTiles' , AnimatedTiles , 'animatedTiles' , 'animatedTiles' ) ;
2021-06-24 08:56:29 +02:00
this . load . on ( 'filecomplete-tilemapJSON-' + this . MapUrlFile , ( key : string , type : string , data : unknown ) = > {
2020-06-03 10:45:25 +02:00
this . onMapLoad ( data ) ;
2020-04-15 19:23:06 +02:00
} ) ;
2020-05-10 17:55:30 +02:00
//TODO strategy to add access token
2020-11-18 18:15:57 +01:00
this . load . tilemapTiledJSON ( this . MapUrlFile , this . MapUrlFile ) ;
2020-06-03 10:45:25 +02:00
// If the map has already been loaded as part of another GameScene, the "on load" event will not be triggered.
// In this case, we check in the cache to see if the map is here and trigger the event manually.
2021-06-25 16:32:48 +02:00
if ( this . cache . tilemap . exists ( this . MapUrlFile ) ) {
2020-11-18 18:15:57 +01:00
const data = this . cache . tilemap . get ( this . MapUrlFile ) ;
2020-06-03 10:45:25 +02:00
this . onMapLoad ( data ) ;
}
2020-05-06 01:50:01 +02:00
2020-05-01 23:38:09 +02:00
this . load . bitmapFont ( 'main_font' , 'resources/fonts/arcade.png' , 'resources/fonts/arcade.xml' ) ;
2021-05-26 20:31:33 +02:00
//eslint-disable-next-line @typescript-eslint/no-explicit-any
2021-06-02 16:46:28 +02:00
( this . load as any ) . rexWebFont ( {
2021-05-26 20:31:33 +02:00
custom : {
families : [ 'Press Start 2P' ] ,
urls : [ '/resources/fonts/fonts.css' ] ,
testString : 'abcdefg'
} ,
} ) ;
2021-02-09 12:41:35 +01:00
2021-05-04 15:47:45 +02:00
//this function must stay at the end of preload function
2021-02-09 12:41:35 +01:00
addLoader ( this ) ;
2020-04-07 19:23:21 +02:00
}
2020-06-19 16:36:40 +02:00
// FIXME: we need to put a "unknown" instead of a "any" and validate the structure of the JSON we are receiving.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2020-07-23 18:09:24 +02:00
private async onMapLoad ( data : any ) : Promise < void > {
2020-06-03 10:45:25 +02:00
// Triggered when the map is loaded
// Load tiles attached to the map recursively
2020-06-03 23:17:52 +02:00
this . mapFile = data . data ;
2020-06-09 23:13:26 +02:00
const url = this . MapUrlFile . substr ( 0 , this . MapUrlFile . lastIndexOf ( '/' ) ) ;
2020-06-03 23:17:52 +02:00
this . mapFile . tilesets . forEach ( ( tileset ) = > {
2021-06-25 16:32:48 +02:00
if ( typeof tileset . name === 'undefined' || typeof tileset . image === 'undefined' ) {
2020-06-03 10:45:25 +02:00
console . warn ( "Don't know how to handle tileset " , tileset )
return ;
}
//TODO strategy to add access token
2020-07-07 18:22:10 +02:00
this . load . image ( ` ${ url } / ${ tileset . image } ` , ` ${ url } / ${ tileset . image } ` ) ;
2020-06-03 10:45:25 +02:00
} )
2020-07-23 18:09:24 +02:00
// Scan the object layers for objects to load and load them.
2020-07-23 18:47:28 +02:00
const objects = new Map < string , ITiledMapObject [ ] > ( ) ;
2020-07-23 18:09:24 +02:00
2021-06-25 16:32:48 +02:00
for ( const layer of this . mapFile . layers ) {
if ( layer . type === 'objectgroup' ) {
for ( const object of layer . objects ) {
2021-06-24 08:56:29 +02:00
let objectsOfType : ITiledMapObject [ ] | undefined ;
2021-06-25 16:32:48 +02:00
if ( ! objects . has ( object . type ) ) {
2020-07-23 18:09:24 +02:00
objectsOfType = new Array < ITiledMapObject > ( ) ;
} else {
objectsOfType = objects . get ( object . type ) ;
2021-06-25 16:32:48 +02:00
if ( objectsOfType === undefined ) {
2020-07-23 18:09:24 +02:00
throw new Error ( 'Unexpected object type not found' ) ;
}
}
objectsOfType . push ( object ) ;
objects . set ( object . type , objectsOfType ) ;
}
}
}
2021-06-25 16:32:48 +02:00
for ( const [ itemType , objectsOfType ] of objects ) {
2020-07-23 18:09:24 +02:00
// FIXME: we would ideally need for the loader to WAIT for the import to be performed, which means writing our own loader plugin.
let itemFactory : ItemFactoryInterface ;
2021-06-25 16:32:48 +02:00
switch ( itemType ) {
2020-07-23 18:47:28 +02:00
case 'computer' : {
const module = await import ( '../Items/Computer/computer' ) ;
itemFactory = module .default ;
2020-07-23 18:09:24 +02:00
break ;
2020-07-23 18:47:28 +02:00
}
2020-07-23 18:09:24 +02:00
default :
2021-03-09 18:05:07 +01:00
continue ;
2021-06-24 08:56:29 +02:00
//throw new Error('Unsupported object type: "'+ itemType +'"');
2020-07-23 18:09:24 +02:00
}
itemFactory . preload ( this . load ) ;
this . load . start ( ) ; // Let's manually start the loader because the import might be over AFTER the loading ends.
this . load . on ( 'complete' , ( ) = > {
// FIXME: the factory might fail because the resources might not be loaded yet...
// We would need to add a loader ended event in addition to the createPromise
2020-07-27 22:36:07 +02:00
this . createPromise . then ( async ( ) = > {
2020-07-23 18:09:24 +02:00
itemFactory . create ( this ) ;
2020-07-27 22:36:07 +02:00
const roomJoinedAnswer = await this . connectionAnswerPromise ;
2021-06-25 16:32:48 +02:00
for ( const object of objectsOfType ) {
2020-07-23 18:09:24 +02:00
// TODO: we should pass here a factory to create sprites (maybe?)
2020-07-27 22:36:07 +02:00
// Do we have a state for this object?
const state = roomJoinedAnswer . items [ object . id ] ;
const actionableItem = itemFactory . factory ( this , object , state ) ;
this . actionableItems . set ( actionableItem . getId ( ) , actionableItem ) ;
2020-07-23 18:09:24 +02:00
}
} ) ;
} ) ;
}
2021-03-07 21:02:38 +01:00
// Now, let's load the script, if any
const scripts = this . getScriptUrls ( this . mapFile ) ;
2021-06-25 16:32:48 +02:00
for ( const script of scripts ) {
2021-03-07 21:02:38 +01:00
iframeListener . registerScript ( script ) ;
}
2020-06-03 10:45:25 +02:00
}
2020-04-07 19:23:21 +02:00
//hook initialisation
2021-06-24 08:56:29 +02:00
init ( initData : GameSceneInitInterface ) {
2021-06-25 16:32:48 +02:00
if ( initData . initPosition !== undefined ) {
2020-11-18 18:15:57 +01:00
this . initPosition = initData . initPosition ; //todo: still used?
2020-06-04 18:11:07 +02:00
}
2021-06-25 16:32:48 +02:00
if ( initData . initPosition !== undefined ) {
2020-11-13 18:00:22 +01:00
this . isReconnecting = initData . reconnecting ;
}
2020-05-23 15:43:26 +02:00
}
2020-04-07 19:23:21 +02:00
//hook create scene
create ( ) : void {
2021-04-23 18:30:41 +02:00
this . trackDirtyAnims ( ) ;
2020-12-16 15:09:58 +01:00
gameManager . gameSceneIsCreated ( this ) ;
2020-11-25 17:17:48 +01:00
urlManager . pushRoomIdToUrl ( this . room ) ;
2020-11-19 14:32:18 +01:00
this . startLayerName = urlManager . getStartLayerNameFromUrl ( ) ;
2020-12-11 13:00:11 +01:00
2021-06-25 16:32:48 +02:00
if ( touchScreenManager . supportTouchScreen ) {
2021-05-04 11:29:37 +02:00
this . pinchManager = new PinchManager ( this ) ;
2021-04-14 17:47:26 +02:00
}
2021-04-29 23:47:30 +02:00
this . messageSubscription = worldFullMessageStream . stream . subscribe ( ( message ) = > this . showWorldFullError ( message ) )
2020-12-11 13:00:11 +01:00
2020-12-04 11:30:35 +01:00
const playerName = gameManager . getPlayerName ( ) ;
2021-06-25 16:32:48 +02:00
if ( ! playerName ) {
2020-12-04 11:30:35 +01:00
throw 'playerName is not set' ;
}
this . playerName = playerName ;
2021-01-07 17:11:22 +01:00
this . characterLayers = gameManager . getCharacterLayers ( ) ;
2021-04-02 21:21:11 +02:00
this . companion = gameManager . getCompanion ( ) ;
2020-12-04 11:30:35 +01:00
2020-04-13 15:34:09 +02:00
//initalise map
2020-11-18 18:15:57 +01:00
this . Map = this . add . tilemap ( this . MapUrlFile ) ;
2020-07-07 18:22:10 +02:00
const mapDirUrl = this . MapUrlFile . substr ( 0 , this . MapUrlFile . lastIndexOf ( '/' ) ) ;
2020-06-03 23:17:52 +02:00
this . mapFile . tilesets . forEach ( ( tileset : ITiledTileSet ) = > {
2020-07-07 18:22:10 +02:00
this . Terrains . push ( this . Map . addTilesetImage ( tileset . name , ` ${ mapDirUrl } / ${ tileset . image } ` , tileset . tilewidth , tileset . tileheight , tileset . margin , tileset . spacing /*, tileset.firstgid*/ ) ) ;
2020-04-15 19:39:26 +02:00
} ) ;
2020-04-13 15:34:09 +02:00
//permit to set bound collision
2020-10-13 20:39:29 +02:00
this . physics . world . setBounds ( 0 , 0 , this . Map . widthInPixels , this . Map . heightInPixels ) ;
2020-04-13 15:34:09 +02:00
//add layer on map
2021-05-12 14:30:12 +02:00
this . gameMap = new GameMap ( this . mapFile , this . Map , this . Terrains ) ;
2021-06-25 16:32:48 +02:00
for ( const layer of this . gameMap . flatLayers ) {
if ( layer . type === 'tilelayer' ) {
2020-12-11 13:00:11 +01:00
2020-11-18 18:15:57 +01:00
const exitSceneUrl = this . getExitSceneUrl ( layer ) ;
2021-06-25 16:32:48 +02:00
if ( exitSceneUrl !== undefined ) {
2020-11-18 18:15:57 +01:00
this . loadNextGame ( exitSceneUrl ) ;
}
const exitUrl = this . getExitUrl ( layer ) ;
2021-06-25 16:32:48 +02:00
if ( exitUrl !== undefined ) {
2020-11-18 18:15:57 +01:00
this . loadNextGame ( exitUrl ) ;
}
2020-05-09 21:28:50 +02:00
}
2021-06-25 16:32:48 +02:00
if ( layer . type === 'objectgroup' ) {
for ( const object of layer . objects ) {
if ( object . text ) {
2021-04-16 21:29:05 +02:00
TextUtils . createTextFromITiledMapObject ( this , object ) ;
}
}
}
2020-06-07 23:00:05 +02:00
}
2020-04-13 15:34:09 +02:00
2021-06-19 15:17:28 +02:00
this . gameMap . exitUrls . forEach ( exitUrl = > {
this . loadNextGame ( exitUrl )
} )
2020-04-13 15:34:09 +02:00
2020-11-17 15:02:21 +01:00
this . initStartXAndStartY ( ) ;
2020-06-07 13:23:32 +02:00
2020-04-13 15:34:09 +02:00
//add entities
this . Objects = new Array < Phaser.Physics.Arcade.Sprite > ( ) ;
//initialise list of other player
2021-06-25 16:32:48 +02:00
this . MapPlayers = this . physics . add . group ( { immovable : true } ) ;
2020-04-13 15:34:09 +02:00
2021-04-23 13:44:04 +02:00
2020-07-27 22:36:07 +02:00
//create input to move
2021-04-16 18:49:04 +02:00
this . userInputManager = new UserInputManager ( this ) ;
2021-05-14 11:59:38 +02:00
mediaManager . setUserInputManager ( this . userInputManager ) ;
2021-04-07 12:42:56 +02:00
2021-06-25 16:32:48 +02:00
if ( localUserStore . getFullscreen ( ) ) {
2021-04-07 12:42:56 +02:00
document . querySelector ( 'body' ) ? . requestFullscreen ( ) ;
}
2020-04-10 12:54:05 +02:00
//notify game manager can to create currentUser in map
2020-05-24 23:33:56 +02:00
this . createCurrentPlayer ( ) ;
2021-01-07 12:43:21 +01:00
this . removeAllRemotePlayers ( ) ; //cleanup the list of remote players in case the scene was rebooted
2021-01-17 20:34:35 +01:00
2020-04-13 19:56:41 +02:00
this . initCamera ( ) ;
2020-05-08 00:35:36 +02:00
2021-05-13 15:24:35 +02:00
this . animatedTiles . init ( this . Map ) ;
2021-06-22 14:21:15 +02:00
this . events . on ( 'tileanimationupdate' , ( ) = > this . dirty = true ) ;
2021-01-07 12:43:21 +01:00
this . initCirclesCanvas ( ) ;
2020-10-21 16:07:42 +02:00
2020-06-22 11:58:07 +02:00
// Let's pause the scene if the connection is not established yet
2021-06-25 16:32:48 +02:00
if ( ! this . room . isDisconnected ( ) ) {
if ( this . isReconnecting ) {
2021-03-30 16:08:49 +02:00
setTimeout ( ( ) = > {
2020-06-22 15:00:23 +02:00
this . scene . sleep ( ) ;
this . scene . launch ( ReconnectingSceneName ) ;
2021-03-30 16:08:49 +02:00
} , 0 ) ;
2021-06-25 16:32:48 +02:00
} else if ( this . connection === undefined ) {
2021-03-30 16:08:49 +02:00
// Let's wait 1 second before printing the "connecting" screen to avoid blinking
setTimeout ( ( ) = > {
2021-06-25 16:32:48 +02:00
if ( this . connection === undefined ) {
2021-03-30 16:08:49 +02:00
this . scene . sleep ( ) ;
this . scene . launch ( ReconnectingSceneName ) ;
}
} , 1000 ) ;
}
2020-06-22 11:58:07 +02:00
}
2020-07-23 18:09:24 +02:00
this . createPromiseResolve ( ) ;
2020-07-27 22:36:07 +02:00
2020-10-13 20:39:29 +02:00
this . userInputManager . spaceEvent ( ( ) = > {
2020-07-27 22:36:07 +02:00
this . outlinedItem ? . activate ( ) ;
} ) ;
2020-08-17 22:51:37 +02:00
2020-12-15 18:00:04 +01:00
this . openChatIcon = new OpenChatIcon ( this , 2 , this . game . renderer . height - 2 )
2020-08-17 16:12:53 +02:00
2020-08-17 15:20:03 +02:00
this . reposition ( ) ;
2020-08-24 14:19:36 +02:00
// From now, this game scene will be notified of reposition events
2021-06-24 17:02:57 +02:00
this . biggestAvailableAreaStoreUnsubscribe = biggestAvailableAreaStore . subscribe ( ( box ) = > this . updateCameraOffset ( box ) ) ;
2021-06-17 10:07:15 +02:00
2020-11-10 17:00:23 +01:00
this . triggerOnMapLayerPropertyChange ( ) ;
2021-03-09 16:21:14 +01:00
this . listenToIframeEvents ( ) ;
2020-10-12 11:22:41 +02:00
2021-03-30 16:08:49 +02:00
2021-06-25 16:32:48 +02:00
if ( ! this . room . isDisconnected ( ) ) {
2021-03-30 16:08:49 +02:00
this . connect ( ) ;
}
2021-05-11 10:56:50 +02:00
2021-03-31 11:21:06 +02:00
this . emoteManager = new EmoteManager ( this ) ;
2021-06-03 15:40:44 +02:00
let oldPeerNumber = 0 ;
this . peerStoreUnsubscribe = peerStore . subscribe ( ( peers ) = > {
const newPeerNumber = peers . size ;
2021-06-25 16:32:48 +02:00
if ( newPeerNumber > oldPeerNumber ) {
2021-06-03 15:40:44 +02:00
this . sound . play ( 'audio-webrtc-in' , {
volume : 0.2
} ) ;
2021-06-25 16:32:48 +02:00
} else if ( newPeerNumber < oldPeerNumber ) {
2021-06-03 15:40:44 +02:00
this . sound . play ( 'audio-webrtc-out' , {
volume : 0.2
} ) ;
}
oldPeerNumber = newPeerNumber ;
} ) ;
2021-03-30 16:08:49 +02:00
}
/ * *
* Initializes the connection to Pusher .
* /
private connect ( ) : void {
2020-10-06 18:09:23 +02:00
const camera = this . cameras . main ;
connectionManager . connectToRoomSocket (
this . RoomId ,
2020-12-04 11:30:35 +01:00
this . playerName ,
this . characterLayers ,
2020-10-06 18:09:23 +02:00
{
x : this.startX ,
y : this.startY
} ,
{
left : camera.scrollX ,
top : camera.scrollY ,
right : camera.scrollX + camera . width ,
bottom : camera.scrollY + camera . height ,
2021-04-02 21:21:11 +02:00
} ,
this . companion
2021-06-24 08:56:29 +02:00
) . then ( ( onConnect : OnConnectInterface ) = > {
2020-12-03 16:39:44 +01:00
this . connection = onConnect . connection ;
2020-10-06 18:09:23 +02:00
2020-12-03 16:39:44 +01:00
this . connection . onUserJoins ( ( message : MessageUserJoined ) = > {
2020-10-06 18:09:23 +02:00
const userMessage : AddPlayerInterface = {
userId : message.userId ,
characterLayers : message.characterLayers ,
name : message.name ,
2021-04-02 21:21:11 +02:00
position : message.position ,
2021-06-08 16:30:58 +02:00
visitCardUrl : message.visitCardUrl ,
2021-04-02 21:21:11 +02:00
companion : message.companion
2020-10-06 18:09:23 +02:00
}
this . addPlayer ( userMessage ) ;
} ) ;
2020-12-03 16:39:44 +01:00
this . connection . onUserMoved ( ( message : UserMovedMessage ) = > {
2020-10-06 18:09:23 +02:00
const position = message . getPosition ( ) ;
2021-06-25 16:32:48 +02:00
if ( position === undefined ) {
2020-10-06 18:09:23 +02:00
throw new Error ( 'Position missing from UserMovedMessage' ) ;
}
const messageUserMoved : MessageUserMovedInterface = {
userId : message.getUserid ( ) ,
position : ProtobufClientUtils.toPointInterface ( position )
}
this . updatePlayerPosition ( messageUserMoved ) ;
} ) ;
2020-12-03 16:39:44 +01:00
this . connection . onUserLeft ( ( userId : number ) = > {
2020-10-06 18:09:23 +02:00
this . removePlayer ( userId ) ;
} ) ;
2020-12-03 16:39:44 +01:00
this . connection . onGroupUpdatedOrCreated ( ( groupPositionMessage : GroupCreatedUpdatedMessageInterface ) = > {
2020-10-06 18:09:23 +02:00
this . shareGroupPosition ( groupPositionMessage ) ;
} )
2020-12-03 16:39:44 +01:00
this . connection . onGroupDeleted ( ( groupId : number ) = > {
2020-10-06 18:09:23 +02:00
try {
this . deleteGroup ( groupId ) ;
2021-06-25 16:32:48 +02:00
} catch ( e ) {
2020-10-06 18:09:23 +02:00
console . error ( e ) ;
}
} )
2020-12-03 16:39:44 +01:00
this . connection . onServerDisconnected ( ( ) = > {
2020-10-06 18:09:23 +02:00
console . log ( 'Player disconnected from server. Reloading scene.' ) ;
2021-03-26 14:12:22 +01:00
this . cleanupClosingScene ( ) ;
2020-10-06 18:09:23 +02:00
2020-10-13 20:39:29 +02:00
const gameSceneKey = 'somekey' + Math . round ( Math . random ( ) * 10000 ) ;
2020-12-18 15:58:49 +01:00
const game : Phaser.Scene = new GameScene ( this . room , this . MapUrlFile , gameSceneKey ) ;
2020-10-08 18:51:24 +02:00
this . scene . add ( gameSceneKey , game , true ,
2020-10-06 18:09:23 +02:00
{
initPosition : {
x : this.CurrentPlayer.x ,
y : this.CurrentPlayer.y
2020-11-13 18:00:22 +01:00
} ,
reconnecting : true
2020-10-06 18:09:23 +02:00
} ) ;
this . scene . stop ( this . scene . key ) ;
this . scene . remove ( this . scene . key ) ;
} )
2020-12-03 16:39:44 +01:00
this . connection . onActionableEvent ( ( message = > {
2020-10-06 18:09:23 +02:00
const item = this . actionableItems . get ( message . itemId ) ;
2021-06-25 16:32:48 +02:00
if ( item === undefined ) {
2020-10-13 20:39:29 +02:00
console . warn ( 'Received an event about object "' + message . itemId + '" but cannot find this item on the map.' ) ;
2020-10-06 18:09:23 +02:00
return ;
}
item . fire ( message . event , message . state , message . parameters ) ;
} ) ) ;
2020-10-19 11:07:49 +02:00
/ * *
* Triggered when we receive the JWT token to connect to Jitsi
* /
2020-12-03 16:39:44 +01:00
this . connection . onStartJitsiRoom ( ( jwt , room ) = > {
2020-10-16 19:13:26 +02:00
this . startJitsi ( room , jwt ) ;
} ) ;
2020-10-06 18:09:23 +02:00
// When connection is performed, let's connect SimplePeer
2020-12-04 11:30:35 +01:00
this . simplePeer = new SimplePeer ( this . connection , ! this . room . isPublic , this . playerName ) ;
2021-05-18 16:38:56 +02:00
peerStore . connectToSimplePeer ( this . simplePeer ) ;
2021-06-11 11:29:36 +02:00
screenSharingPeerStore . connectToSimplePeer ( this . simplePeer ) ;
2021-06-15 14:45:01 +02:00
videoFocusStore . connectToSimplePeer ( this . simplePeer ) ;
2020-10-06 18:09:23 +02:00
this . GlobalMessageManager = new GlobalMessageManager ( this . connection ) ;
2021-03-01 17:47:00 +01:00
userMessageManager . setReceiveBanListener ( this . bannedUser . bind ( this ) ) ;
2020-10-06 18:09:23 +02:00
const self = this ;
this . simplePeer . registerPeerConnectionListener ( {
2021-06-11 11:29:36 +02:00
onConnect ( peer ) {
2021-03-19 15:03:55 +01:00
self . openChatIcon . setVisible ( true ) ;
audioManager . decreaseVolume ( ) ;
2020-10-06 18:09:23 +02:00
} ,
onDisconnect ( userId : number ) {
2021-06-25 16:32:48 +02:00
if ( self . simplePeer . getNbConnections ( ) === 0 ) {
2021-03-19 15:03:55 +01:00
self . openChatIcon . setVisible ( false ) ;
audioManager . restoreVolume ( ) ;
2020-10-06 18:09:23 +02:00
}
}
} )
//listen event to share position of user
this . CurrentPlayer . on ( hasMovedEventName , this . pushPlayerPosition . bind ( this ) )
this . CurrentPlayer . on ( hasMovedEventName , this . outlineItem . bind ( this ) )
2021-05-18 11:33:16 +02:00
this . CurrentPlayer . on ( hasMovedEventName , ( event : HasPlayerMovedEvent ) = > {
2020-10-06 18:09:23 +02:00
this . gameMap . setPosition ( event . x , event . y ) ;
} )
2020-12-03 16:39:44 +01:00
//this.initUsersPosition(roomJoinedMessage.users);
this . connectionAnswerPromiseResolve ( onConnect . room ) ;
// Analyze tags to find if we are admin. If yes, show console.
2020-12-11 13:06:10 +01:00
2020-12-11 13:00:11 +01:00
2020-12-18 16:08:04 +01:00
this . scene . wake ( ) ;
this . scene . stop ( ReconnectingSceneName ) ;
2020-12-03 16:39:44 +01:00
2020-11-10 17:00:23 +01:00
//init user position and play trigger to check layers properties
this . gameMap . setPosition ( this . CurrentPlayer . x , this . CurrentPlayer . y ) ;
2020-10-06 18:09:23 +02:00
} ) ;
2020-08-17 15:20:03 +02:00
}
2021-01-07 12:43:21 +01:00
//todo: into dedicated classes
private initCirclesCanvas ( ) : void {
// Let's generate the circle for the group delimiter
let circleElement = Object . values ( this . textures . list ) . find ( ( object : Texture ) = > object . key === 'circleSprite-white' ) ;
2021-06-25 16:32:48 +02:00
if ( circleElement ) {
2021-01-07 12:43:21 +01:00
this . textures . remove ( 'circleSprite-white' ) ;
}
circleElement = Object . values ( this . textures . list ) . find ( ( object : Texture ) = > object . key === 'circleSprite-red' ) ;
2021-06-25 16:32:48 +02:00
if ( circleElement ) {
2021-01-07 12:43:21 +01:00
this . textures . remove ( 'circleSprite-red' ) ;
}
//create white circle canvas use to create sprite
this . circleTexture = this . textures . createCanvas ( 'circleSprite-white' , 96 , 96 ) ;
const context = this . circleTexture . context ;
context . beginPath ( ) ;
context . arc ( 48 , 48 , 48 , 0 , 2 * Math . PI , false ) ;
// context.lineWidth = 5;
context . strokeStyle = '#ffffff' ;
context . stroke ( ) ;
this . circleTexture . refresh ( ) ;
//create red circle canvas use to create sprite
this . circleRedTexture = this . textures . createCanvas ( 'circleSprite-red' , 96 , 96 ) ;
const contextRed = this . circleRedTexture . context ;
contextRed . beginPath ( ) ;
contextRed . arc ( 48 , 48 , 48 , 0 , 2 * Math . PI , false ) ;
2021-06-24 08:56:29 +02:00
//context.lineWidth = 5;
2021-01-07 12:43:21 +01:00
contextRed . strokeStyle = '#ff0000' ;
contextRed . stroke ( ) ;
this . circleRedTexture . refresh ( ) ;
}
2021-01-03 12:45:18 +01:00
2021-06-24 08:56:29 +02:00
private safeParseJSONstring ( jsonString : string | undefined , propertyName : string ) {
2021-02-09 20:31:49 +01:00
try {
return jsonString ? JSON . parse ( jsonString ) : { } ;
2021-06-25 16:32:48 +02:00
} catch ( e ) {
2021-02-10 11:08:57 +01:00
console . warn ( 'Invalid JSON found in property "' + propertyName + '" of the map:' + jsonString , e ) ;
2021-02-09 20:31:49 +01:00
return { }
}
}
2021-06-24 08:56:29 +02:00
private triggerOnMapLayerPropertyChange() {
2020-11-18 18:15:57 +01:00
this . gameMap . onPropertyChange ( 'exitSceneUrl' , ( newValue , oldValue ) = > {
2021-06-25 16:32:48 +02:00
if ( newValue ) this . onMapExit ( newValue as string ) ;
2021-02-17 19:29:59 +01:00
} ) ;
this . gameMap . onPropertyChange ( 'exitUrl' , ( newValue , oldValue ) = > {
2021-06-25 16:32:48 +02:00
if ( newValue ) this . onMapExit ( newValue as string ) ;
2020-11-18 18:15:57 +01:00
} ) ;
2020-11-21 15:40:24 +01:00
this . gameMap . onPropertyChange ( 'openWebsite' , ( newValue , oldValue , allProps ) = > {
2021-06-25 16:32:48 +02:00
if ( newValue === undefined ) {
2020-11-21 15:40:24 +01:00
layoutManager . removeActionButton ( 'openWebsite' , this . userInputManager ) ;
2020-11-10 17:00:23 +01:00
coWebsiteManager . closeCoWebsite ( ) ;
2021-06-24 08:56:29 +02:00
} else {
2020-11-21 15:40:24 +01:00
const openWebsiteFunction = ( ) = > {
2021-03-06 16:00:07 +01:00
coWebsiteManager . loadCoWebsite ( newValue as string , this . MapUrlFile , allProps . get ( 'openWebsiteAllowApi' ) as boolean | undefined , allProps . get ( 'openWebsitePolicy' ) as string | undefined ) ;
2020-11-21 15:40:24 +01:00
layoutManager . removeActionButton ( 'openWebsite' , this . userInputManager ) ;
} ;
2020-11-23 20:34:05 +01:00
const openWebsiteTriggerValue = allProps . get ( TRIGGER_WEBSITE_PROPERTIES ) ;
2021-06-25 16:32:48 +02:00
if ( openWebsiteTriggerValue && openWebsiteTriggerValue === ON_ACTION_TRIGGER_BUTTON ) {
2021-01-25 12:21:40 +01:00
let message = allProps . get ( WEBSITE_MESSAGE_PROPERTIES ) ;
2021-06-25 16:32:48 +02:00
if ( message === undefined ) {
2021-03-09 20:01:48 +01:00
message = 'Press SPACE or touch here to open web site' ;
2021-01-25 12:21:40 +01:00
}
layoutManager . addActionButton ( 'openWebsite' , message . toString ( ) , ( ) = > {
2020-11-21 15:40:24 +01:00
openWebsiteFunction ( ) ;
} , this . userInputManager ) ;
2021-06-24 08:56:29 +02:00
} else {
2020-11-21 15:40:24 +01:00
openWebsiteFunction ( ) ;
}
2020-11-10 17:00:23 +01:00
}
} ) ;
this . gameMap . onPropertyChange ( 'jitsiRoom' , ( newValue , oldValue , allProps ) = > {
2021-06-25 16:32:48 +02:00
if ( newValue === undefined ) {
2020-11-21 15:40:24 +01:00
layoutManager . removeActionButton ( 'jitsiRoom' , this . userInputManager ) ;
2020-11-10 17:00:23 +01:00
this . stopJitsi ( ) ;
2021-06-24 08:56:29 +02:00
} else {
2020-11-21 15:40:24 +01:00
const openJitsiRoomFunction = ( ) = > {
2021-02-10 11:40:59 +01:00
const roomName = jitsiFactory . getRoomName ( newValue . toString ( ) , this . instance ) ;
2021-06-24 08:56:29 +02:00
const jitsiUrl = allProps . get ( "jitsiUrl" ) as string | undefined ;
2021-06-25 16:32:48 +02:00
if ( JITSI_PRIVATE_MODE && ! jitsiUrl ) {
2021-06-24 08:56:29 +02:00
const adminTag = allProps . get ( "jitsiRoomAdminTag" ) as string | undefined ;
2020-11-21 15:40:24 +01:00
2021-04-23 13:44:04 +02:00
this . connection ? . emitQueryJitsiJwtMessage ( roomName , adminTag ) ;
2020-11-21 15:40:24 +01:00
} else {
2021-02-10 11:40:59 +01:00
this . startJitsi ( roomName , undefined ) ;
2020-11-21 15:40:24 +01:00
}
layoutManager . removeActionButton ( 'jitsiRoom' , this . userInputManager ) ;
}
2020-11-10 17:00:23 +01:00
2020-11-23 20:34:05 +01:00
const jitsiTriggerValue = allProps . get ( TRIGGER_JITSI_PROPERTIES ) ;
2021-06-25 16:32:48 +02:00
if ( jitsiTriggerValue && jitsiTriggerValue === ON_ACTION_TRIGGER_BUTTON ) {
2021-01-25 12:21:40 +01:00
let message = allProps . get ( JITSI_MESSAGE_PROPERTIES ) ;
2021-06-25 16:32:48 +02:00
if ( message === undefined ) {
2021-03-09 20:01:48 +01:00
message = 'Press SPACE or touch here to enter Jitsi Meet room' ;
2021-01-25 12:21:40 +01:00
}
layoutManager . addActionButton ( 'jitsiRoom' , message . toString ( ) , ( ) = > {
2020-11-21 15:40:24 +01:00
openJitsiRoomFunction ( ) ;
} , this . userInputManager ) ;
2021-06-24 08:56:29 +02:00
} else {
2020-11-21 15:40:24 +01:00
openJitsiRoomFunction ( ) ;
2020-11-10 17:00:23 +01:00
}
}
2021-01-25 12:21:40 +01:00
} ) ;
2020-11-10 17:00:23 +01:00
this . gameMap . onPropertyChange ( 'silent' , ( newValue , oldValue ) = > {
2021-06-25 16:32:48 +02:00
if ( newValue === undefined || newValue === false || newValue === '' ) {
2021-04-23 13:44:04 +02:00
this . connection ? . setSilent ( false ) ;
2020-11-10 17:00:23 +01:00
} else {
2021-04-23 13:44:04 +02:00
this . connection ? . setSilent ( true ) ;
2020-11-10 17:00:23 +01:00
}
} ) ;
2021-03-11 22:34:49 +01:00
this . gameMap . onPropertyChange ( 'playAudio' , ( newValue , oldValue , allProps ) = > {
2021-06-24 08:56:29 +02:00
const volume = allProps . get ( AUDIO_VOLUME_PROPERTY ) as number | undefined ;
const loop = allProps . get ( AUDIO_LOOP_PROPERTY ) as boolean | undefined ;
2021-03-11 22:34:49 +01:00
newValue === undefined ? audioManager . unloadAudio ( ) : audioManager . playAudio ( newValue , this . getMapDirUrl ( ) , volume , loop ) ;
2021-01-03 12:45:18 +01:00
} ) ;
2021-03-11 22:39:45 +01:00
// TODO: This legacy property should be removed at some point
2021-01-03 12:45:18 +01:00
this . gameMap . onPropertyChange ( 'playAudioLoop' , ( newValue , oldValue ) = > {
2021-03-11 22:39:45 +01:00
newValue === undefined ? audioManager . unloadAudio ( ) : audioManager . playAudio ( newValue , this . getMapDirUrl ( ) , undefined , true ) ;
2021-01-03 12:45:18 +01:00
} ) ;
2021-03-08 18:57:59 +01:00
this . gameMap . onPropertyChange ( 'zone' , ( newValue , oldValue ) = > {
2021-06-25 16:32:48 +02:00
if ( newValue === undefined || newValue === false || newValue === '' ) {
2021-03-08 18:57:59 +01:00
iframeListener . sendLeaveEvent ( oldValue as string ) ;
2021-03-10 17:22:39 +01:00
2021-03-08 18:57:59 +01:00
} else {
iframeListener . sendEnterEvent ( newValue as string ) ;
}
} ) ;
2020-11-10 17:00:23 +01:00
}
2020-12-11 13:00:11 +01:00
2021-03-09 16:21:14 +01:00
private listenToIframeEvents ( ) : void {
2021-06-24 08:56:29 +02:00
this . iframeSubscriptionList = [ ] ;
this . iframeSubscriptionList . push ( iframeListener . openPopupStream . subscribe ( ( openPopupEvent ) = > {
2021-03-12 16:39:29 +01:00
2021-06-24 08:56:29 +02:00
let objectLayerSquare : ITiledMapObject ;
2021-03-28 16:36:02 +02:00
const targetObjectData = this . getObjectLayerData ( openPopupEvent . targetObject ) ;
2021-06-25 16:32:48 +02:00
if ( targetObjectData !== undefined ) {
2021-06-24 08:56:29 +02:00
objectLayerSquare = targetObjectData ;
2021-03-28 16:36:02 +02:00
} 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." ) ;
2021-03-10 17:22:39 +01:00
return ;
}
2021-03-09 16:21:14 +01:00
const escapedMessage = HtmlUtils . escapeHtml ( openPopupEvent . message ) ;
2021-06-25 11:34:27 +02:00
let html = ` <div id="container" hidden><div class="nes-container with-title is-centered"> ` ;
html += escapedMessage ;
if ( openPopupEvent . input ) {
html += ` <input id="popupinput- ${ openPopupEvent . popupId } " class="nes-input" /> `
}
html += ` </div> ` ;
2021-03-23 17:15:22 +01:00
const buttonContainer = ` <div class="buttonContainer"</div> ` ;
2021-03-22 16:10:21 +01:00
html += buttonContainer ;
2021-03-09 18:05:07 +01:00
let id = 0 ;
2021-06-25 16:32:48 +02:00
for ( const button of openPopupEvent . buttons ) {
2021-06-24 08:56:29 +02:00
html += ` <button type="button" class="nes-btn is- ${ HtmlUtils . escapeHtml ( button . className ? ? '' ) } " id="popup- ${ openPopupEvent . popupId } - ${ id } "> ${ HtmlUtils . escapeHtml ( button . label ) } </button> ` ;
2021-03-09 18:05:07 +01:00
id ++ ;
}
2021-04-09 14:35:15 +02:00
html += '</div>' ;
2021-06-24 08:56:29 +02:00
const domElement = this . add . dom ( objectLayerSquare . x ,
2021-04-09 14:35:15 +02:00
objectLayerSquare . y ) . createFromHTML ( html ) ;
2021-03-09 16:21:14 +01:00
2021-06-24 08:56:29 +02:00
const container : HTMLDivElement = domElement . getChildByID ( "container" ) as HTMLDivElement ;
2021-03-10 17:22:39 +01:00
container . style . width = objectLayerSquare . width + "px" ;
2021-03-09 16:21:14 +01:00
domElement . scale = 0 ;
domElement . setClassName ( 'popUpElement' ) ;
2021-03-09 18:05:07 +01:00
2021-04-09 14:35:15 +02:00
setTimeout ( ( ) = > {
( container ) . hidden = false ;
} , 100 ) ;
2021-03-10 17:22:39 +01:00
2021-03-09 18:05:07 +01:00
id = 0 ;
2021-06-25 16:32:48 +02:00
for ( const button of openPopupEvent . buttons ) {
2021-03-09 18:05:07 +01:00
const button = HtmlUtils . getElementByIdOrFail < HTMLButtonElement > ( ` popup- ${ openPopupEvent . popupId } - ${ id } ` ) ;
const btnId = id ;
button . onclick = ( ) = > {
2021-06-25 11:34:27 +02:00
let inputValue = '' ;
if ( openPopupEvent . input ) {
inputValue = HtmlUtils . getElementByIdOrFail < HTMLInputElement > ( ` popupinput- ${ openPopupEvent . popupId } ` ) . value ;
}
iframeListener . sendButtonClickedEvent ( openPopupEvent . popupId , btnId , openPopupEvent . input , inputValue ) ;
2021-03-09 18:05:07 +01:00
}
id ++ ;
}
2021-03-09 16:21:14 +01:00
this . tweens . add ( {
2021-06-24 08:56:29 +02:00
targets : domElement ,
scale : 1 ,
ease : "EaseOut" ,
duration : 400 ,
2021-03-09 16:21:14 +01:00
} ) ;
this . popUpElements . set ( openPopupEvent . popupId , domElement ) ;
2021-04-19 10:19:30 +02:00
} ) ) ;
2021-03-09 16:21:14 +01:00
2021-06-24 08:56:29 +02:00
this . iframeSubscriptionList . push ( iframeListener . closePopupStream . subscribe ( ( closePopupEvent ) = > {
2021-03-09 18:51:30 +01:00
const popUpElement = this . popUpElements . get ( closePopupEvent . popupId ) ;
2021-06-25 16:32:48 +02:00
if ( popUpElement === undefined ) {
2021-06-24 08:56:29 +02:00
console . error ( 'Could not close popup with ID ' , closePopupEvent . popupId , '. Maybe it has already been closed?' ) ;
2021-03-09 16:21:14 +01:00
}
2021-03-09 18:51:30 +01:00
this . tweens . add ( {
2021-06-24 08:56:29 +02:00
targets : popUpElement ,
scale : 0 ,
ease : "EaseOut" ,
duration : 400 ,
onComplete : ( ) = > {
2021-03-09 18:51:30 +01:00
popUpElement ? . destroy ( ) ;
this . popUpElements . delete ( closePopupEvent . popupId ) ;
} ,
} ) ;
2021-04-19 10:19:30 +02:00
} ) ) ;
2021-03-12 16:39:29 +01:00
2021-06-24 08:56:29 +02:00
this . iframeSubscriptionList . push ( iframeListener . disablePlayerControlStream . subscribe ( ( ) = > {
2021-03-22 16:10:21 +01:00
this . userInputManager . disableControls ( ) ;
2021-04-19 10:19:30 +02:00
} ) ) ;
2021-06-24 08:56:29 +02:00
this . iframeSubscriptionList . push ( iframeListener . playSoundStream . subscribe ( ( playSoundEvent ) = > {
const url = new URL ( playSoundEvent . url , this . MapUrlFile ) ;
soundManager . playSound ( this . load , this . sound , url . toString ( ) , playSoundEvent . config ) ;
} ) )
2021-04-21 16:47:19 +02:00
2021-06-24 08:56:29 +02:00
this . iframeSubscriptionList . push ( iframeListener . stopSoundStream . subscribe ( ( stopSoundEvent ) = > {
2021-04-23 15:35:34 +02:00
const url = new URL ( stopSoundEvent . url , this . MapUrlFile ) ;
2021-06-24 08:56:29 +02:00
soundManager . stopSound ( this . sound , url . toString ( ) ) ;
2021-04-23 15:35:34 +02:00
} ) )
2021-06-24 08:56:29 +02:00
this . iframeSubscriptionList . push ( iframeListener . loadSoundStream . subscribe ( ( loadSoundEvent ) = > {
2021-04-23 15:35:34 +02:00
const url = new URL ( loadSoundEvent . url , this . MapUrlFile ) ;
2021-06-24 08:56:29 +02:00
soundManager . loadSound ( this . load , this . sound , url . toString ( ) ) ;
2021-04-23 15:35:34 +02:00
} ) )
2021-06-24 08:56:29 +02:00
this . iframeSubscriptionList . push ( iframeListener . enablePlayerControlStream . subscribe ( ( ) = > {
2021-03-22 16:10:21 +01:00
this . userInputManager . restoreControls ( ) ;
2021-04-19 10:19:30 +02:00
} ) ) ;
2021-06-24 08:56:29 +02:00
this . iframeSubscriptionList . push ( iframeListener . loadPageStream . subscribe ( ( url : string ) = > {
this . loadNextGame ( url ) . then ( ( ) = > {
this . events . once ( EVENT_TYPE . POST_UPDATE , ( ) = > {
2021-04-21 11:54:37 +02:00
this . onMapExit ( url ) ;
} )
2021-04-21 11:20:17 +02:00
} )
} ) ) ;
2021-06-24 08:56:29 +02:00
let scriptedBubbleSprite : Sprite ;
this . iframeSubscriptionList . push ( iframeListener . displayBubbleStream . subscribe ( ( ) = > {
scriptedBubbleSprite = new Sprite ( this , this . CurrentPlayer . x + 25 , this . CurrentPlayer . y , 'circleSprite-white' ) ;
2021-03-22 16:10:21 +01:00
scriptedBubbleSprite . setDisplayOrigin ( 48 , 48 ) ;
this . add . existing ( scriptedBubbleSprite ) ;
2021-04-19 10:19:30 +02:00
} ) ) ;
2021-06-24 08:56:29 +02:00
this . iframeSubscriptionList . push ( iframeListener . removeBubbleStream . subscribe ( ( ) = > {
2021-03-22 16:10:21 +01:00
scriptedBubbleSprite . destroy ( ) ;
2021-04-19 10:19:30 +02:00
} ) ) ;
2021-03-12 16:39:29 +01:00
2021-04-21 07:44:12 +02:00
this . iframeSubscriptionList . push ( iframeListener . unregisterIFrameStream . subscribe ( ( ) = > {
const allProps = this . gameMap . getCurrentProperties ( ) ;
if ( allProps . get ( "openWebsite" ) == null ) {
layoutManager . removeActionButton ( 'openWebsite' , this . userInputManager ) ;
} else {
const openWebsiteFunction = ( ) = > {
coWebsiteManager . loadCoWebsite ( allProps . get ( "openWebsite" ) as string , this . MapUrlFile , allProps . get ( 'openWebsiteAllowApi' ) as boolean | undefined , allProps . get ( 'openWebsitePolicy' ) as string | undefined ) ;
layoutManager . removeActionButton ( 'openWebsite' , this . userInputManager ) ;
} ;
let message = allProps . get ( WEBSITE_MESSAGE_PROPERTIES ) ;
if ( message === undefined ) {
message = 'Press SPACE or touch here to open web site' ;
}
layoutManager . addActionButton ( 'openWebsite' , message . toString ( ) , ( ) = > {
openWebsiteFunction ( ) ;
} , this . userInputManager ) ;
}
} ) ) ;
2021-06-25 11:34:27 +02:00
this . iframeSubscriptionList . push ( iframeListener . showLayerStream . subscribe ( ( layerEvent ) = > {
2021-05-10 11:20:07 +02:00
this . setLayerVisibility ( layerEvent . name , true ) ;
} ) ) ;
2021-05-16 14:52:07 +02:00
2021-06-25 16:32:48 +02:00
this . iframeSubscriptionList . push ( iframeListener . hideLayerStream . subscribe ( ( layerEvent ) = > {
2021-05-10 11:20:07 +02:00
this . setLayerVisibility ( layerEvent . name , false ) ;
} ) ) ;
2021-05-12 14:30:12 +02:00
this . iframeSubscriptionList . push ( iframeListener . setPropertyStream . subscribe ( ( setProperty ) = > {
this . setPropertyLayer ( setProperty . layerName , setProperty . propertyName , setProperty . propertyValue ) ;
2021-05-16 14:52:07 +02:00
} ) ) ;
2021-05-12 14:30:12 +02:00
2021-05-18 15:41:16 +02:00
this . iframeSubscriptionList . push ( iframeListener . dataLayerChangeStream . subscribe ( ( ) = > {
iframeListener . sendDataLayerEvent ( { data : this.gameMap.getMap ( ) } ) ;
} ) )
2021-05-25 17:21:02 +02:00
this . iframeSubscriptionList . push ( iframeListener . gameStateStream . subscribe ( ( ) = > {
2021-06-23 11:32:11 +02:00
iframeListener . sendGameStateEvent ( {
2021-05-25 17:21:02 +02:00
mapUrl : this.MapUrlFile ,
startLayerName : this.startLayerName ,
uuid : localUserStore.getLocalUser ( ) ? . uuid ,
nickname : localUserStore.getName ( ) ,
roomId : this.RoomId ,
2021-05-27 18:25:27 +02:00
tags : this.connection ? this . connection . getAllTags ( ) : [ ]
2021-05-25 17:21:02 +02:00
} )
} ) ) ;
2021-05-20 10:57:36 +02:00
2021-05-12 14:30:12 +02:00
}
private setPropertyLayer ( layerName : string , propertyName : string , propertyValue : string | number | boolean | undefined ) : void {
const layer = this . gameMap . findLayer ( layerName ) ;
2021-06-25 16:32:48 +02:00
if ( layer === undefined ) {
2021-05-12 14:30:12 +02:00
console . warn ( 'Could not find layer "' + layerName + '" when calling setProperty' ) ;
return ;
}
2021-06-25 16:32:48 +02:00
const property = ( layer . properties as ITiledMapLayerProperty [ ] ) ? . find ( ( property ) = > property . name === propertyName ) ;
if ( property === undefined ) {
layer . properties = [ ] ;
layer . properties . push ( { name : propertyName , type : typeof propertyValue , value : propertyValue } ) ;
return ;
}
property . value = propertyValue ;
2021-03-09 16:21:14 +01:00
}
2021-05-10 11:20:07 +02:00
private setLayerVisibility ( layerName : string , visible : boolean ) : void {
2021-05-19 09:36:11 +02:00
const phaserLayer = this . gameMap . findPhaserLayer ( layerName ) ;
2021-06-25 16:32:48 +02:00
if ( phaserLayer === undefined ) {
2021-05-10 11:20:07 +02:00
console . warn ( 'Could not find layer "' + layerName + '" when calling WA.hideLayer / WA.showLayer' ) ;
return ;
}
2021-05-19 09:36:11 +02:00
phaserLayer . setVisible ( visible ) ;
2021-05-10 11:20:07 +02:00
this . dirty = true ;
}
2021-03-05 18:25:27 +01:00
private getMapDirUrl ( ) : string {
return this . MapUrlFile . substr ( 0 , this . MapUrlFile . lastIndexOf ( '/' ) ) ;
}
2020-11-18 18:15:57 +01:00
private onMapExit ( exitKey : string ) {
2021-06-25 16:32:48 +02:00
if ( this . mapTransitioning ) return ;
2021-05-10 19:13:53 +02:00
this . mapTransitioning = true ;
2021-06-25 16:32:48 +02:00
const { roomId , hash } = Room . getIdFromIdentifier ( exitKey , this . MapUrlFile , this . instance ) ;
if ( ! roomId ) throw new Error ( 'Could not find the room from its exit key: ' + exitKey ) ;
2020-11-19 14:32:18 +01:00
urlManager . pushStartLayerNameToUrl ( hash ) ;
2021-04-27 16:40:56 +02:00
const menuScene : MenuScene = this . scene . get ( MenuSceneName ) as MenuScene
menuScene . reset ( )
2021-06-25 16:32:48 +02:00
if ( roomId !== this . scene . key ) {
if ( this . scene . get ( roomId ) === null ) {
2021-02-13 14:15:09 +01:00
console . error ( "next room not loaded" , exitKey ) ;
return ;
}
2020-12-04 11:30:35 +01:00
this . cleanupClosingScene ( ) ;
2020-11-18 18:15:57 +01:00
this . scene . stop ( ) ;
this . scene . remove ( this . scene . key ) ;
2020-11-19 14:32:18 +01:00
this . scene . start ( roomId ) ;
2020-11-18 18:15:57 +01:00
} else {
//if the exit points to the current map, we simply teleport the user back to the startLayer
2020-11-19 14:32:18 +01:00
this . initPositionFromLayerName ( hash || defaultStartLayerName ) ;
2020-11-18 18:15:57 +01:00
this . CurrentPlayer . x = this . startX ;
this . CurrentPlayer . y = this . startY ;
2021-05-10 19:13:53 +02:00
setTimeout ( ( ) = > this . mapTransitioning = false , 500 ) ;
2020-11-18 18:15:57 +01:00
}
}
2020-11-10 17:00:23 +01:00
2020-12-04 11:30:35 +01:00
public cleanupClosingScene ( ) : void {
2021-01-23 01:16:13 +01:00
// stop playing audio, close any open website, stop any open Jitsi
coWebsiteManager . closeCoWebsite ( ) ;
2021-03-07 21:02:38 +01:00
// Stop the script, if any
const scripts = this . getScriptUrls ( this . mapFile ) ;
2021-06-25 16:32:48 +02:00
for ( const script of scripts ) {
2021-03-07 21:02:38 +01:00
iframeListener . unregisterScript ( script ) ;
}
2021-01-23 01:16:13 +01:00
this . stopJitsi ( ) ;
2021-03-05 18:25:27 +01:00
audioManager . unloadAudio ( ) ;
2020-12-04 11:30:35 +01:00
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
2021-03-05 18:25:27 +01:00
this . connection ? . closeConnection ( ) ;
2021-04-29 23:47:30 +02:00
this . simplePeer ? . closeAllConnections ( ) ;
2021-03-05 18:25:27 +01:00
this . simplePeer ? . unregister ( ) ;
this . messageSubscription ? . unsubscribe ( ) ;
2021-05-04 11:29:37 +02:00
this . userInputManager . destroy ( ) ;
this . pinchManager ? . destroy ( ) ;
2021-05-21 16:25:12 +02:00
this . emoteManager . destroy ( ) ;
2021-06-03 15:40:44 +02:00
this . peerStoreUnsubscribe ( ) ;
2021-06-24 17:02:57 +02:00
this . biggestAvailableAreaStoreUnsubscribe ( ) ;
2021-04-19 10:19:30 +02:00
2021-05-31 12:06:11 +02:00
mediaManager . hideGameOverlay ( ) ;
2021-04-19 10:19:30 +02:00
2021-06-25 16:32:48 +02:00
for ( const iframeEvents of this . iframeSubscriptionList ) {
2021-04-19 10:19:30 +02:00
iframeEvents . unsubscribe ( ) ;
}
2020-12-04 11:30:35 +01:00
}
2021-01-07 12:43:21 +01:00
private removeAllRemotePlayers ( ) : void {
this . MapPlayersByKey . forEach ( ( player : RemotePlayer ) = > {
player . destroy ( ) ;
2021-04-09 18:30:30 +02:00
2021-06-25 16:32:48 +02:00
if ( player . companion ) {
2021-04-09 18:30:30 +02:00
player . companion . destroy ( ) ;
}
2021-01-07 12:43:21 +01:00
this . MapPlayers . remove ( player ) ;
} ) ;
this . MapPlayersByKey = new Map < number , RemotePlayer > ( ) ;
}
2020-11-17 15:02:21 +01:00
private initStartXAndStartY() {
// If there is an init position passed
2021-06-25 16:32:48 +02:00
if ( this . initPosition !== null ) {
2020-11-17 15:02:21 +01:00
this . startX = this . initPosition . x ;
this . startY = this . initPosition . y ;
} else {
// Now, let's find the start layer
2021-06-25 16:32:48 +02:00
if ( this . startLayerName ) {
2020-11-19 14:32:18 +01:00
this . initPositionFromLayerName ( this . startLayerName ) ;
2020-11-17 15:02:21 +01:00
}
2021-06-25 16:32:48 +02:00
if ( this . startX === undefined ) {
2020-11-17 15:02:21 +01:00
// If we have no start layer specified or if the hash passed does not exist, let's go with the default start position.
2020-11-19 14:32:18 +01:00
this . initPositionFromLayerName ( defaultStartLayerName ) ;
2020-11-17 15:02:21 +01:00
}
}
// Still no start position? Something is wrong with the map, we need a "start" layer.
2021-06-25 16:32:48 +02:00
if ( this . startX === undefined ) {
2020-11-17 15:02:21 +01:00
console . warn ( 'This map is missing a layer named "start" that contains the available default start positions.' ) ;
// Let's start in the middle of the map
this . startX = this . mapFile . width * 16 ;
this . startY = this . mapFile . height * 16 ;
}
}
private initPositionFromLayerName ( layerName : string ) {
2021-06-25 16:32:48 +02:00
for ( const layer of this . gameMap . flatLayers ) {
if ( ( layerName === layer . name || layer . name . endsWith ( '/' + layerName ) ) && layer . type === 'tilelayer' && ( layerName === defaultStartLayerName || this . isStartLayer ( layer ) ) ) {
2020-11-17 15:02:21 +01:00
const startPosition = this . startUser ( layer ) ;
2021-06-24 08:56:29 +02:00
this . startX = startPosition . x + this . mapFile . tilewidth / 2 ;
this . startY = startPosition . y + this . mapFile . tileheight / 2 ;
2020-11-17 15:02:21 +01:00
}
}
2020-04-13 19:56:41 +02:00
2020-11-17 15:02:21 +01:00
}
2020-04-13 19:56:41 +02:00
2021-06-24 08:56:29 +02:00
private getExitUrl ( layer : ITiledMapLayer ) : string | undefined {
return this . getProperty ( layer , "exitUrl" ) as string | undefined ;
2020-10-15 15:50:51 +02:00
}
2020-11-18 18:15:57 +01:00
/ * *
* @deprecated the map property exitSceneUrl is deprecated
* /
2021-06-24 08:56:29 +02:00
private getExitSceneUrl ( layer : ITiledMapLayer ) : string | undefined {
return this . getProperty ( layer , "exitSceneUrl" ) as string | undefined ;
2020-05-11 18:49:20 +02:00
}
2020-06-07 13:23:32 +02:00
private isStartLayer ( layer : ITiledMapLayer ) : boolean {
return this . getProperty ( layer , "startLayer" ) == true ;
}
2021-03-07 21:02:38 +01:00
private getScriptUrls ( map : ITiledMap ) : string [ ] {
return ( this . getProperties ( map , "script" ) as string [ ] ) . map ( ( script ) = > ( new URL ( script , this . MapUrlFile ) ) . toString ( ) ) ;
}
2021-06-24 08:56:29 +02:00
private getProperty ( layer : ITiledMapLayer | ITiledMap , name : string ) : string | boolean | number | undefined {
const properties : ITiledMapLayerProperty [ ] | undefined = layer . properties ;
2021-06-25 16:32:48 +02:00
if ( ! properties ) {
2020-05-23 17:27:49 +02:00
return undefined ;
}
2020-11-04 13:33:58 +01:00
const obj = properties . find ( ( property : ITiledMapLayerProperty ) = > property . name . toLowerCase ( ) === name . toLowerCase ( ) ) ;
2021-06-25 16:32:48 +02:00
if ( obj === undefined ) {
2020-05-23 17:27:49 +02:00
return undefined ;
}
return obj . value ;
}
2021-06-24 08:56:29 +02:00
private getProperties ( layer : ITiledMapLayer | ITiledMap , name : string ) : ( string | number | boolean | undefined ) [ ] {
const properties : ITiledMapLayerProperty [ ] | undefined = layer . properties ;
2021-06-25 16:32:48 +02:00
if ( ! properties ) {
2021-03-07 21:02:38 +01:00
return [ ] ;
}
return properties . filter ( ( property : ITiledMapLayerProperty ) = > property . name . toLowerCase ( ) === name . toLowerCase ( ) ) . map ( ( property ) = > property . value ) ;
}
2020-10-15 15:50:51 +02:00
//todo: push that into the gameManager
2021-06-25 16:32:48 +02:00
private loadNextGame ( exitSceneIdentifier : string ) : Promise < void > {
const { roomId , hash } = Room . getIdFromIdentifier ( exitSceneIdentifier , this . MapUrlFile , this . instance ) ;
2020-10-13 18:44:50 +02:00
const room = new Room ( roomId ) ;
2021-06-25 16:32:48 +02:00
return gameManager . loadMap ( room , this . scene ) . catch ( ( ) = > { } ) ;
2020-05-09 21:28:50 +02:00
}
2021-04-15 22:39:35 +02:00
private startUser ( layer : ITiledMapTileLayer ) : PositionInterface {
2020-06-19 16:36:40 +02:00
const tiles = layer . data ;
2021-06-25 16:32:48 +02:00
if ( typeof ( tiles ) === 'string' ) {
2020-06-19 16:36:40 +02:00
throw new Error ( 'The content of a JSON map must be filled as a JSON array, not as a string' ) ;
}
2021-06-24 08:56:29 +02:00
const possibleStartPositions : PositionInterface [ ] = [ ] ;
tiles . forEach ( ( objectKey : number , key : number ) = > {
2021-06-25 16:32:48 +02:00
if ( objectKey === 0 ) {
2020-05-09 21:28:50 +02:00
return ;
}
2020-06-09 23:13:26 +02:00
const y = Math . floor ( key / layer . width ) ;
const x = key % layer . width ;
2020-05-09 21:28:50 +02:00
2021-06-25 16:32:48 +02:00
possibleStartPositions . push ( { x : x * this . mapFile . tilewidth , y : y * this . mapFile . tilewidth } ) ;
2020-05-09 21:28:50 +02:00
} ) ;
2020-06-03 11:14:04 +02:00
// Get a value at random amongst allowed values
2021-06-25 16:32:48 +02:00
if ( possibleStartPositions . length === 0 ) {
2021-06-24 08:56:29 +02:00
console . warn ( 'The start layer "' + layer . name + '" for this map is empty.' ) ;
2020-06-03 11:14:04 +02:00
return {
x : 0 ,
y : 0
} ;
}
// Choose one of the available start positions at random amongst the list of available start positions.
return possibleStartPositions [ Math . floor ( Math . random ( ) * possibleStartPositions . length ) ] ;
2020-05-09 21:28:50 +02:00
}
2020-04-13 19:56:41 +02:00
//todo: in a dedicated class/function?
initCamera() {
2021-06-24 08:56:29 +02:00
this . cameras . main . setBounds ( 0 , 0 , this . Map . widthInPixels , this . Map . heightInPixels ) ;
2021-04-21 18:01:26 +02:00
this . cameras . main . startFollow ( this . CurrentPlayer , true ) ;
2021-06-21 14:07:03 +02:00
biggestAvailableAreaStore . recompute ( ) ;
2020-04-10 12:54:05 +02:00
}
2020-04-13 15:34:09 +02:00
createCollisionWithPlayer() {
//add collision layer
2021-06-25 16:32:48 +02:00
for ( const phaserLayer of this . gameMap . phaserLayers ) {
2021-06-24 12:10:44 +02:00
this . physics . add . collider ( this . CurrentPlayer , phaserLayer , ( object1 : GameObject , object2 : GameObject ) = > {
//this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name)
} ) ;
phaserLayer . setCollisionByProperty ( { collides : true } ) ;
2021-06-25 16:32:48 +02:00
if ( DEBUG_MODE ) {
2021-06-24 12:10:44 +02:00
//debug code to see the collision hitbox of the object in the top layer
phaserLayer . renderDebug ( this . add . graphics ( ) , {
tileColor : null , //non-colliding tiles
collidingTileColor : new Phaser . Display . Color ( 243 , 134 , 48 , 200 ) , // Colliding tiles,
faceColor : new Phaser . Display . Color ( 40 , 39 , 37 , 255 ) // Colliding face edges
2020-04-13 19:40:10 +02:00
} ) ;
}
2021-06-24 12:10:44 +02:00
//});
2021-05-12 14:30:12 +02:00
}
2020-04-13 15:34:09 +02:00
}
2021-06-24 08:56:29 +02:00
createCurrentPlayer() {
2020-05-23 17:27:49 +02:00
//TODO create animation moving between exit and start
2021-01-26 15:21:23 +01:00
const texturesPromise = lazyLoadPlayerCharacterTextures ( this . load , this . characterLayers ) ;
2020-12-18 14:30:46 +01:00
try {
this . CurrentPlayer = new Player (
this ,
this . startX ,
this . startY ,
this . playerName ,
2021-01-07 17:11:22 +01:00
texturesPromise ,
2021-03-11 16:13:05 +01:00
PlayerAnimationDirections . Down ,
2020-12-18 14:30:46 +01:00
false ,
2021-04-02 21:21:11 +02:00
this . userInputManager ,
2021-04-06 19:10:18 +02:00
this . companion ,
this . companion !== null ? lazyLoadCompanionResource ( this . load , this . companion ) : undefined
2020-12-18 14:30:46 +01:00
) ;
2021-06-02 15:35:20 +02:00
this . CurrentPlayer . on ( 'pointerdown' , ( pointer : Phaser.Input.Pointer ) = > {
2021-06-25 16:32:48 +02:00
if ( pointer . wasTouch && ( pointer . event as TouchEvent ) . touches . length > 1 ) {
2021-06-02 15:35:20 +02:00
return ; //we don't want the menu to open when pinching on a touch screen.
}
2021-06-29 09:52:13 +02:00
this . CurrentPlayer . openOrCloseEmoteMenu ( this . emoteManager . getEmotes ( ) ) ;
2021-05-10 17:10:41 +02:00
} )
this . CurrentPlayer . on ( requestEmoteEventName , ( emoteKey : string ) = > {
this . connection ? . emitEmoteEvent ( emoteKey ) ;
} )
2021-06-25 16:32:48 +02:00
} catch ( err ) {
if ( err instanceof TextureError ) {
2020-12-18 14:30:46 +01:00
gameManager . leaveGame ( this , SelectCharacterSceneName , new SelectCharacterScene ( ) ) ;
}
2020-12-18 16:30:22 +01:00
throw err ;
2020-12-18 14:30:46 +01:00
}
2020-04-13 15:34:09 +02:00
//create collision
this . createCollisionWithPlayer ( ) ;
2020-05-02 16:54:52 +02:00
}
2020-05-04 18:38:04 +02:00
2021-05-18 11:33:16 +02:00
pushPlayerPosition ( event : HasPlayerMovedEvent ) {
2021-06-25 16:32:48 +02:00
if ( this . lastMoveEventSent === event ) {
2020-06-01 22:42:18 +02:00
return ;
}
// If the player is not moving, let's send the info right now.
2021-06-25 16:32:48 +02:00
if ( event . moving === false ) {
2020-06-01 22:42:18 +02:00
this . doPushPlayerPosition ( event ) ;
return ;
}
// If the player is moving, and if it changed direction, let's send an event
2021-06-25 16:32:48 +02:00
if ( event . direction !== this . lastMoveEventSent . direction ) {
2020-06-01 22:42:18 +02:00
this . doPushPlayerPosition ( event ) ;
return ;
}
// If more than 200ms happened since last event sent
2021-06-25 16:32:48 +02:00
if ( this . currentTick - this . lastSentTick >= POSITION_DELAY ) {
2020-06-01 22:42:18 +02:00
this . doPushPlayerPosition ( event ) ;
return ;
}
// Otherwise, do nothing.
}
2020-07-23 18:09:24 +02:00
/ * *
* Finds the correct item to outline and outline it ( if there is an item to be outlined )
* @param event
* /
2021-05-18 11:33:16 +02:00
private outlineItem ( event : HasPlayerMovedEvent ) : void {
2020-07-23 18:09:24 +02:00
let x = event . x ;
let y = event . y ;
2021-06-25 16:32:48 +02:00
switch ( event . direction ) {
2021-03-11 16:13:05 +01:00
case PlayerAnimationDirections . Up :
2020-07-23 18:09:24 +02:00
y -= 32 ;
break ;
2021-03-11 16:13:05 +01:00
case PlayerAnimationDirections . Down :
2020-07-23 18:09:24 +02:00
y += 32 ;
break ;
2021-03-11 16:13:05 +01:00
case PlayerAnimationDirections . Left :
2020-07-23 18:09:24 +02:00
x -= 32 ;
break ;
2021-03-11 16:13:05 +01:00
case PlayerAnimationDirections . Right :
2020-07-23 18:09:24 +02:00
x += 32 ;
break ;
default :
throw new Error ( 'Unexpected direction "' + event . direction + '"' ) ;
}
let shortestDistance : number = Infinity ;
2021-06-24 08:56:29 +02:00
let selectedItem : ActionableItem | null = null ;
2021-06-25 16:32:48 +02:00
for ( const item of this . actionableItems . values ( ) ) {
2020-07-23 18:47:28 +02:00
const distance = item . actionableDistance ( x , y ) ;
2021-06-25 16:32:48 +02:00
if ( distance !== null && distance < shortestDistance ) {
2020-07-23 18:09:24 +02:00
shortestDistance = distance ;
selectedItem = item ;
}
}
2021-06-25 16:32:48 +02:00
if ( this . outlinedItem === selectedItem ) {
2020-07-23 18:09:24 +02:00
return ;
}
this . outlinedItem ? . notSelectable ( ) ;
this . outlinedItem = selectedItem ;
this . outlinedItem ? . selectable ( ) ;
}
2021-05-18 11:33:16 +02:00
private doPushPlayerPosition ( event : HasPlayerMovedEvent ) : void {
2020-06-01 22:42:18 +02:00
this . lastMoveEventSent = event ;
this . lastSentTick = this . currentTick ;
2020-09-15 10:06:11 +02:00
const camera = this . cameras . main ;
2021-04-23 13:44:04 +02:00
this . connection ? . sharePosition ( event . x , event . y , event . direction , event . moving , {
2020-09-15 10:06:11 +02:00
left : camera.scrollX ,
top : camera.scrollY ,
right : camera.scrollX + camera . width ,
bottom : camera.scrollY + camera . height ,
} ) ;
2021-05-18 11:33:16 +02:00
iframeListener . hasPlayerMoved ( event ) ;
2020-04-13 15:34:09 +02:00
}
2020-04-18 17:16:39 +02:00
/ * *
* @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 .
* /
2021-06-24 08:56:29 +02:00
update ( time : number , delta : number ) : void {
2021-04-23 13:29:23 +02:00
this . dirty = false ;
2020-06-01 22:42:18 +02:00
this . currentTick = time ;
2020-04-18 17:16:39 +02:00
this . CurrentPlayer . moveUser ( delta ) ;
2021-04-23 13:29:23 +02:00
2020-06-19 18:18:43 +02:00
// Let's handle all events
2021-06-25 16:32:48 +02:00
while ( this . pendingEvents . length !== 0 ) {
2021-04-23 13:29:23 +02:00
this . dirty = true ;
2020-06-19 18:24:32 +02:00
const event = this . pendingEvents . dequeue ( ) ;
2021-06-25 16:32:48 +02:00
switch ( event . type ) {
2020-06-19 18:18:43 +02:00
case "InitUserPositionEvent" :
this . doInitUsersPosition ( event . event ) ;
break ;
case "AddPlayerEvent" :
this . doAddPlayer ( event . event ) ;
break ;
case "RemovePlayerEvent" :
this . doRemovePlayer ( event . userId ) ;
break ;
case "UserMovedEvent" :
this . doUpdatePlayerPosition ( event . event ) ;
break ;
case "GroupCreatedUpdatedEvent" :
this . doShareGroupPosition ( event . event ) ;
break ;
case "DeleteGroupEvent" :
this . doDeleteGroup ( event . groupId ) ;
break ;
}
}
2020-06-02 13:44:42 +02:00
// Let's move all users
2020-06-09 23:13:26 +02:00
const updatedPlayersPositions = this . playersPositionInterpolator . getUpdatedPositions ( time ) ;
2021-05-18 11:33:16 +02:00
updatedPlayersPositions . forEach ( ( moveEvent : HasPlayerMovedEvent , userId : number ) = > {
2021-04-23 13:29:23 +02:00
this . dirty = true ;
2021-03-19 15:40:07 +01:00
const player : RemotePlayer | undefined = this . MapPlayersByKey . get ( userId ) ;
2021-06-25 16:32:48 +02:00
if ( player === undefined ) {
2021-03-19 15:40:07 +01:00
throw new Error ( 'Cannot find player with ID "' + userId + '"' ) ;
2020-06-02 13:44:42 +02:00
}
player . updatePosition ( moveEvent ) ;
} ) ;
2020-05-09 21:28:50 +02:00
}
2020-06-19 18:18:43 +02:00
/ * *
* Called by the connexion when the full list of user position is received .
* /
2020-06-22 11:58:07 +02:00
private initUsersPosition ( usersPosition : MessageUserPositionInterface [ ] ) : void {
2020-06-19 18:18:43 +02:00
this . pendingEvents . enqueue ( {
type : "InitUserPositionEvent" ,
event : usersPosition
} ) ;
}
/ * *
* Put all the players on the map on map load .
* /
private doInitUsersPosition ( usersPosition : MessageUserPositionInterface [ ] ) : void {
2021-04-23 13:44:04 +02:00
const currentPlayerId = this . connection ? . getUserId ( ) ;
2021-01-07 12:43:21 +01:00
this . removeAllRemotePlayers ( ) ;
2020-05-19 19:11:12 +02:00
// load map
2021-06-24 08:56:29 +02:00
usersPosition . forEach ( ( userPosition : MessageUserPositionInterface ) = > {
2021-06-25 16:32:48 +02:00
if ( userPosition . userId === currentPlayerId ) {
2020-05-19 19:11:12 +02:00
return ;
}
this . addPlayer ( userPosition ) ;
} ) ;
}
2020-06-19 18:18:43 +02:00
/ * *
* Called by the connexion when a new player arrives on a map
* /
2021-06-24 08:56:29 +02:00
public addPlayer ( addPlayerData : AddPlayerInterface ) : void {
2020-06-19 18:18:43 +02:00
this . pendingEvents . enqueue ( {
type : "AddPlayerEvent" ,
event : addPlayerData
} ) ;
}
2021-01-17 20:34:35 +01:00
2021-06-24 08:56:29 +02:00
private doAddPlayer ( addPlayerData : AddPlayerInterface ) : void {
2020-05-23 14:00:36 +02:00
//check if exist player, if exist, move position
2021-06-25 16:32:48 +02:00
if ( this . MapPlayersByKey . has ( addPlayerData . userId ) ) {
2020-05-23 14:00:36 +02:00
this . updatePlayerPosition ( {
userId : addPlayerData.userId ,
position : addPlayerData.position
} ) ;
return ;
}
2020-10-20 16:39:23 +02:00
2021-01-26 15:21:23 +01:00
const texturesPromise = lazyLoadPlayerCharacterTextures ( this . load , addPlayerData . characterLayers ) ;
2020-06-09 23:13:26 +02:00
const player = new RemotePlayer (
2020-05-19 19:11:12 +02:00
addPlayerData . userId ,
2020-04-13 15:34:09 +02:00
this ,
2020-05-19 19:11:12 +02:00
addPlayerData . position . x ,
addPlayerData . position . y ,
addPlayerData . name ,
2021-01-07 17:11:22 +01:00
texturesPromise ,
2021-03-11 16:13:05 +01:00
addPlayerData . position . direction as PlayerAnimationDirections ,
2021-04-02 21:21:11 +02:00
addPlayerData . position . moving ,
2021-06-08 16:30:58 +02:00
addPlayerData . visitCardUrl ,
2021-04-06 19:10:18 +02:00
addPlayerData . companion ,
addPlayerData . companion !== null ? lazyLoadCompanionResource ( this . load , addPlayerData . companion ) : undefined
2020-04-13 15:34:09 +02:00
) ;
this . MapPlayers . add ( player ) ;
2020-05-19 19:11:12 +02:00
this . MapPlayersByKey . set ( player . userId , player ) ;
player . updatePosition ( addPlayerData . position ) ;
2020-04-07 20:41:35 +02:00
}
2020-05-08 00:35:36 +02:00
2020-06-19 18:18:43 +02:00
/ * *
* Called by the connexion when a player is removed from the map
* /
2020-09-18 13:57:38 +02:00
public removePlayer ( userId : number ) {
2020-06-19 18:18:43 +02:00
this . pendingEvents . enqueue ( {
type : "RemovePlayerEvent" ,
userId
} ) ;
}
2020-09-18 13:57:38 +02:00
private doRemovePlayer ( userId : number ) {
2020-06-09 23:13:26 +02:00
const player = this . MapPlayersByKey . get ( userId ) ;
2021-06-25 16:32:48 +02:00
if ( player === undefined ) {
2020-05-19 19:11:12 +02:00
console . error ( 'Cannot find user with id ' , userId ) ;
2020-06-04 18:11:07 +02:00
} else {
player . destroy ( ) ;
2021-04-09 18:30:30 +02:00
2021-06-25 16:32:48 +02:00
if ( player . companion ) {
2021-04-09 18:30:30 +02:00
player . companion . destroy ( ) ;
}
2020-06-04 18:11:07 +02:00
this . MapPlayers . remove ( player ) ;
2020-05-19 19:11:12 +02:00
}
this . MapPlayersByKey . delete ( userId ) ;
2020-06-02 13:44:42 +02:00
this . playersPositionInterpolator . removePlayer ( userId ) ;
2020-05-19 19:11:12 +02:00
}
2020-06-19 18:18:43 +02:00
public updatePlayerPosition ( message : MessageUserMovedInterface ) : void {
this . pendingEvents . enqueue ( {
type : "UserMovedEvent" ,
event : message
} ) ;
}
private doUpdatePlayerPosition ( message : MessageUserMovedInterface ) : void {
2021-06-24 08:56:29 +02:00
const player : RemotePlayer | undefined = this . MapPlayersByKey . get ( message . userId ) ;
2021-06-25 16:32:48 +02:00
if ( player === undefined ) {
2020-06-22 18:42:54 +02:00
//throw new Error('Cannot find player with ID "' + message.userId +'"');
2021-06-24 08:56:29 +02:00
console . error ( 'Cannot update position of player with ID "' + message . userId + '": player not found' ) ;
2020-06-22 18:42:54 +02:00
return ;
2020-05-19 19:11:12 +02:00
}
2020-06-02 13:44:42 +02:00
// We do not update the player position directly (because it is sent only every 200ms).
// Instead we use the PlayersPositionInterpolator that will do a smooth animation over the next 200ms.
2021-06-25 16:32:48 +02:00
const playerMovement = new PlayerMovement ( { x : player.x , y : player.y } , this . currentTick , message . position , this . currentTick + POSITION_DELAY ) ;
2020-06-02 13:44:42 +02:00
this . playersPositionInterpolator . updatePlayerPosition ( player . userId , playerMovement ) ;
2020-05-19 19:11:12 +02:00
}
2020-06-19 18:18:43 +02:00
public shareGroupPosition ( groupPositionMessage : GroupCreatedUpdatedMessageInterface ) {
this . pendingEvents . enqueue ( {
type : "GroupCreatedUpdatedEvent" ,
event : groupPositionMessage
} ) ;
}
private doShareGroupPosition ( groupPositionMessage : GroupCreatedUpdatedMessageInterface ) {
2020-10-21 23:08:05 +02:00
//delete previous group
this . doDeleteGroup ( groupPositionMessage . groupId ) ;
2020-05-08 00:35:36 +02:00
2020-10-21 23:08:05 +02:00
// TODO: circle radius should not be hard stored
//create new group
const sprite = new Sprite (
this ,
Math . round ( groupPositionMessage . position . x ) ,
Math . round ( groupPositionMessage . position . y ) ,
2021-04-14 15:36:25 +02:00
groupPositionMessage . groupSize === MAX_PER_GROUP ? 'circleSprite-red' : 'circleSprite-white'
2020-10-21 23:08:05 +02:00
) ;
sprite . setDisplayOrigin ( 48 , 48 ) ;
this . add . existing ( sprite ) ;
this . groups . set ( groupPositionMessage . groupId , sprite ) ;
return sprite ;
2020-05-08 00:35:36 +02:00
}
2020-09-21 11:24:03 +02:00
deleteGroup ( groupId : number ) : void {
2020-06-19 18:18:43 +02:00
this . pendingEvents . enqueue ( {
type : "DeleteGroupEvent" ,
groupId
} ) ;
}
2020-09-21 11:24:03 +02:00
doDeleteGroup ( groupId : number ) : void {
2020-06-09 23:13:26 +02:00
const group = this . groups . get ( groupId ) ;
2021-06-25 16:32:48 +02:00
if ( ! group ) {
2020-05-11 13:17:02 +02:00
return ;
}
2020-06-04 18:11:07 +02:00
group . destroy ( ) ;
2020-05-08 00:35:36 +02:00
this . groups . delete ( groupId ) ;
}
2020-05-23 17:27:49 +02:00
2020-10-08 18:51:24 +02:00
2020-07-27 22:36:07 +02:00
/ * *
* Sends to the server an event emitted by one of the ActionableItems .
* /
emitActionableEvent ( itemId : number , eventName : string , state : unknown , parameters : unknown ) {
2021-04-23 13:44:04 +02:00
this . connection ? . emitActionableEvent ( itemId , eventName , state , parameters ) ;
2020-07-27 22:36:07 +02:00
}
2020-08-17 22:51:37 +02:00
2021-05-31 10:20:30 +02:00
public onResize ( ) : void {
super . onResize ( ) ;
2020-09-15 16:21:41 +02:00
this . reposition ( ) ;
// Send new viewport to server
const camera = this . cameras . main ;
2021-04-23 13:44:04 +02:00
this . connection ? . setViewport ( {
2020-09-15 16:21:41 +02:00
left : camera.scrollX ,
top : camera.scrollY ,
right : camera.scrollX + camera . width ,
bottom : camera.scrollY + camera . height ,
} ) ;
}
2021-06-24 08:56:29 +02:00
private getObjectLayerData ( objectName : string ) : ITiledMapObject | undefined {
2021-06-25 16:32:48 +02:00
for ( const layer of this . mapFile . layers ) {
if ( layer . type === 'objectgroup' && layer . name === 'floorLayer' ) {
for ( const object of layer . objects ) {
if ( object . name === objectName ) {
2021-03-10 17:22:39 +01:00
return object ;
}
}
}
}
return undefined ;
}
2020-08-17 15:20:03 +02:00
private reposition ( ) : void {
2021-02-03 23:11:33 +01:00
this . openChatIcon . setY ( this . game . renderer . height - 2 ) ;
2020-08-24 14:19:36 +02:00
// Recompute camera offset if needed
2021-06-21 14:07:03 +02:00
biggestAvailableAreaStore . recompute ( ) ;
2020-08-24 14:19:36 +02:00
}
/ * *
2021-05-04 11:29:37 +02:00
* Updates the offset of the character compared to the center of the screen according to the layout manager
* ( tries to put the character in the center of the remaining space if there is a discussion going on .
2020-08-24 14:19:36 +02:00
* /
2021-06-17 10:35:08 +02:00
private updateCameraOffset ( array : Box ) : void {
2021-05-05 13:14:00 +02:00
const xCenter = ( array . xEnd - array . xStart ) / 2 + array . xStart ;
const yCenter = ( array . yEnd - array . yStart ) / 2 + array . yStart ;
2020-08-24 14:19:36 +02:00
2021-05-04 11:29:37 +02:00
const game = HtmlUtils . querySelectorOrFail < HTMLCanvasElement > ( '#game canvas' ) ;
2020-08-24 14:19:36 +02:00
// Let's put this in Game coordinates by applying the zoom level:
2021-01-17 20:34:35 +01:00
2021-06-24 08:56:29 +02:00
this . cameras . main . setFollowOffset ( ( xCenter - game . offsetWidth / 2 ) * window . devicePixelRatio / this . scale . zoom , ( yCenter - game . offsetHeight / 2 ) * window . devicePixelRatio / this . scale . zoom ) ;
2020-08-24 14:19:36 +02:00
}
2020-10-19 11:07:49 +02:00
public startJitsi ( roomName : string , jwt? : string ) : void {
2021-02-10 11:08:57 +01:00
const allProps = this . gameMap . getCurrentProperties ( ) ;
2021-06-24 08:56:29 +02:00
const jitsiConfig = this . safeParseJSONstring ( allProps . get ( "jitsiConfig" ) as string | undefined , 'jitsiConfig' ) ;
const jitsiInterfaceConfig = this . safeParseJSONstring ( allProps . get ( "jitsiInterfaceConfig" ) as string | undefined , 'jitsiInterfaceConfig' ) ;
const jitsiUrl = allProps . get ( "jitsiUrl" ) as string | undefined ;
2021-02-10 11:08:57 +01:00
2021-03-16 20:37:12 +01:00
jitsiFactory . start ( roomName , this . playerName , jwt , jitsiConfig , jitsiInterfaceConfig , jitsiUrl ) ;
2021-04-23 13:44:04 +02:00
this . connection ? . setSilent ( true ) ;
2020-10-16 19:13:26 +02:00
mediaManager . hideGameOverlay ( ) ;
2020-11-17 18:03:44 +01:00
//permit to stop jitsi when user close iframe
2021-06-24 08:56:29 +02:00
mediaManager . addTriggerCloseJitsiFrameButton ( 'close-jisi' , ( ) = > {
2020-11-17 18:03:44 +01:00
this . stopJitsi ( ) ;
} ) ;
2020-10-16 19:13:26 +02:00
}
public stopJitsi ( ) : void {
2021-03-05 18:25:27 +01:00
this . connection ? . setSilent ( false ) ;
2020-10-23 16:16:30 +02:00
jitsiFactory . stop ( ) ;
2020-10-16 19:13:26 +02:00
mediaManager . showGameOverlay ( ) ;
2020-11-17 18:03:44 +01:00
mediaManager . removeTriggerCloseJitsiFrameButton ( 'close-jisi' ) ;
2021-04-21 07:44:12 +02:00
const allProps = this . gameMap . getCurrentProperties ( ) ;
if ( allProps . get ( "jitsiRoom" ) === undefined ) {
layoutManager . removeActionButton ( 'jitsiRoom' , this . userInputManager ) ;
} else {
const openJitsiRoomFunction = ( ) = > {
const roomName = jitsiFactory . getRoomName ( allProps . get ( "jitsiRoom" ) as string , this . instance ) ;
const jitsiUrl = allProps . get ( "jitsiUrl" ) as string | undefined ;
if ( JITSI_PRIVATE_MODE && ! jitsiUrl ) {
const adminTag = allProps . get ( "jitsiRoomAdminTag" ) as string | undefined ;
2021-04-28 07:07:51 +02:00
this . connection && this . connection . emitQueryJitsiJwtMessage ( roomName , adminTag ) ;
2021-04-21 07:44:12 +02:00
} else {
this . startJitsi ( roomName , undefined ) ;
}
layoutManager . removeActionButton ( 'jitsiRoom' , this . userInputManager ) ;
}
let message = allProps . get ( JITSI_MESSAGE_PROPERTIES ) ;
if ( message === undefined ) {
message = 'Press SPACE or touch here to enter Jitsi Meet room' ;
}
layoutManager . addActionButton ( 'jitsiRoom' , message . toString ( ) , ( ) = > {
openJitsiRoomFunction ( ) ;
} , this . userInputManager ) ;
}
2020-10-16 19:13:26 +02:00
}
2020-10-20 16:39:23 +02:00
2021-03-11 16:14:34 +01:00
//todo: put this into an 'orchestrator' scene (EntryScene?)
2021-06-24 08:56:29 +02:00
private bannedUser() {
2021-01-26 14:04:42 +01:00
this . cleanupClosingScene ( ) ;
2021-03-22 16:10:21 +01:00
this . userInputManager . disableControls ( ) ;
2021-01-26 08:57:10 +01:00
this . scene . start ( ErrorSceneName , {
title : 'Banned' ,
2021-03-05 18:25:27 +01:00
subTitle : 'You were banned from WorkAdventure' ,
message : 'If you want more information, you may contact us at: workadventure@thecodingmachine.com'
2021-01-26 08:57:10 +01:00
} ) ;
2021-01-25 14:10:16 +01:00
}
2020-12-04 11:30:35 +01:00
2021-03-11 16:14:34 +01:00
//todo: put this into an 'orchestrator' scene (EntryScene?)
2021-06-24 08:56:29 +02:00
private showWorldFullError ( message : string | null ) : void {
2021-03-11 16:14:34 +01:00
this . cleanupClosingScene ( ) ;
this . scene . stop ( ReconnectingSceneName ) ;
2021-04-29 23:47:30 +02:00
this . scene . remove ( ReconnectingSceneName ) ;
2021-03-28 16:53:15 +02:00
this . userInputManager . disableControls ( ) ;
2021-04-29 23:47:30 +02:00
//FIX ME to use status code
2021-06-25 16:32:48 +02:00
if ( message == undefined ) {
2021-04-29 23:47:30 +02:00
this . scene . start ( ErrorSceneName , {
title : 'Connection rejected' ,
subTitle : 'The world you are trying to join is full. Try again later.' ,
message : 'If you want more information, you may contact us at: workadventure@thecodingmachine.com'
} ) ;
2021-06-24 08:56:29 +02:00
} else {
2021-04-29 23:47:30 +02:00
this . scene . start ( ErrorSceneName , {
title : 'Connection rejected' ,
2021-06-24 08:56:29 +02:00
subTitle : 'You cannot join the World. Try again later. \n\r \n\r Error: ' + message + '.' ,
2021-04-29 23:47:30 +02:00
message : 'If you want more information, you may contact administrator or contact us at: workadventure@thecodingmachine.com'
} ) ;
}
2021-03-05 18:25:27 +01:00
}
2021-05-05 09:35:24 +02:00
zoomByFactor ( zoomFactor : number ) {
waScaleManager . zoomModifier *= zoomFactor ;
2021-06-21 14:07:03 +02:00
biggestAvailableAreaStore . recompute ( ) ;
2021-03-05 18:25:27 +01:00
}
2020-04-07 19:23:21 +02:00
}