2020-12-04 11:30:35 +01:00
import { gameManager , HasMovedEvent } from "./GameManager" ;
2020-05-19 19:11:12 +02:00
import {
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" ;
2020-06-04 18:54:34 +02:00
import { CurrentGamerInterface , hasMovedEventName , Player } from "../Player/Player" ;
2021-03-05 18:25:27 +01:00
import { DEBUG_MODE , JITSI_PRIVATE_MODE , POSITION_DELAY , RESOLUTION , ZOOM_LEVEL } from "../../Enum/EnvironmentVariable" ;
import { ITiledMap , ITiledMapLayer , ITiledMapLayerProperty , ITiledMapObject , ITiledTileSet } from "../Map/ITiledMap" ;
2020-05-19 19:11:12 +02:00
import { AddPlayerInterface } from "./AddPlayerInterface" ;
2021-03-11 16:13:05 +01:00
import { PlayerAnimationDirections } from "../Player/Animation" ;
2020-06-02 13:44:42 +02:00
import { PlayerMovement } from "./PlayerMovement" ;
import { PlayersPositionInterpolator } from "./PlayersPositionInterpolator" ;
2020-06-04 18:54:34 +02:00
import { RemotePlayer } from "../Entity/RemotePlayer" ;
2020-08-16 23:19:04 +02:00
import { Queue } from 'queue-typescript' ;
2020-08-18 00:12:38 +02:00
import { SimplePeer , UserSimplePeerInterface } from "../../WebRtc/SimplePeer" ;
2020-06-22 15:00:23 +02:00
import { ReconnectingSceneName } from "../Reconnecting/ReconnectingScene" ;
2021-01-24 15:57:47 +01:00
import { lazyLoadPlayerCharacterTextures , loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager" ;
2020-11-23 20:34:05 +01:00
import {
CenterListener ,
2021-02-17 19:28:41 +01:00
JITSI_MESSAGE_PROPERTIES ,
2020-11-23 20:34:05 +01:00
layoutManager ,
LayoutMode ,
2021-01-25 12:21:40 +01:00
ON_ACTION_TRIGGER_BUTTON ,
TRIGGER_JITSI_PROPERTIES ,
TRIGGER_WEBSITE_PROPERTIES ,
2021-03-11 22:34:49 +01:00
WEBSITE_MESSAGE_PROPERTIES ,
AUDIO_VOLUME_PROPERTY ,
AUDIO_LOOP_PROPERTY
2020-11-23 20:34:05 +01:00
} from "../../WebRtc/LayoutManager" ;
2020-08-30 15:44:22 +02:00
import { GameMap } from "./GameMap" ;
2020-10-27 16:59:12 +01:00
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager" ;
2020-09-01 14:43:21 +02:00
import { mediaManager } from "../../WebRtc/MediaManager" ;
2020-07-23 18:09:24 +02:00
import { ItemFactoryInterface } from "../Items/ItemFactoryInterface" ;
import { ActionableItem } from "../Items/ActionableItem" ;
2020-07-27 22:36:07 +02:00
import { UserInputManager } from "../UserInput/UserInputManager" ;
2021-04-21 16:47:19 +02:00
import { soundManager } from "./SoundManager" ;
2020-09-24 11:16:08 +02:00
import { UserMovedMessage } from "../../Messages/generated/messages_pb" ;
2020-09-18 15:51:15 +02:00
import { ProtobufClientUtils } from "../../Network/ProtobufClientUtils" ;
2021-03-11 16:14:34 +01:00
import { connectionManager } from "../../Connexion/ConnectionManager" ;
2020-09-25 18:29:22 +02:00
import { RoomConnection } from "../../Connexion/RoomConnection" ;
2020-09-20 17:12:27 +02:00
import { GlobalMessageManager } from "../../Administration/GlobalMessageManager" ;
2021-03-01 17:47:00 +01:00
import { userMessageManager } from "../../Administration/UserMessageManager" ;
2020-09-20 17:12:27 +02:00
import { ConsoleGlobalMessageManager } from "../../Administration/ConsoleGlobalMessageManager" ;
2020-10-08 16:00:29 +02:00
import { ResizableScene } from "../Login/ResizableScene" ;
2020-10-13 17:08:24 +02:00
import { Room } from "../../Connexion/Room" ;
2020-10-23 16:16:30 +02:00
import { jitsiFactory } from "../../WebRtc/JitsiFactory" ;
2020-11-18 18:15:57 +01:00
import { urlManager } from "../../Url/UrlManager" ;
2021-01-03 12:45:18 +01:00
import { audioManager } from "../../WebRtc/AudioManager" ;
2020-12-04 11:30:35 +01:00
import { PresentationModeIcon } from "../Components/PresentationModeIcon" ;
import { ChatModeIcon } from "../Components/ChatModeIcon" ;
import { OpenChatIcon , openChatIconName } from "../Components/OpenChatIcon" ;
2020-12-18 13:56:25 +01:00
import { SelectCharacterScene , SelectCharacterSceneName } from "../Login/SelectCharacterScene" ;
2020-12-18 14:30:46 +01:00
import { TextureError } from "../../Exception/TextureError" ;
2021-01-07 17:11:22 +01:00
import { addLoader } from "../Components/Loader" ;
2021-01-17 20:34:35 +01:00
import { ErrorSceneName } from "../Reconnecting/ErrorScene" ;
2021-01-24 15:57:47 +01:00
import { localUserStore } from "../../Connexion/LocalUserStore" ;
2021-03-12 16:39:29 +01:00
import { iframeListener } from "../../Api/IframeListener" ;
import { HtmlUtils } from "../../WebRtc/HtmlUtils" ;
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-03-05 18:25:27 +01:00
import { Subscription } from "rxjs" ;
2021-03-11 16:14:34 +01:00
import { worldFullMessageStream } from "../../Connexion/WorldFullMessageStream" ;
2020-04-11 18:17:36 +02:00
2020-06-07 13:23:32 +02:00
export interface GameSceneInitInterface {
2020-11-13 18:00:22 +01:00
initPosition : PointInterface | null ,
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' ;
2020-10-08 16:00:29 +02:00
export class GameScene extends ResizableScene implements CenterListener {
2020-04-15 19:39:26 +02:00
Terrains : Array < Phaser.Tilemaps.Tileset > ;
2020-08-07 23:39:06 +02:00
CurrentPlayer ! : CurrentGamerInterface ;
MapPlayers ! : Phaser . Physics . Arcade . Group ;
2020-09-18 13:57:38 +02:00
MapPlayersByKey : Map < number , RemotePlayer > = new Map < number , RemotePlayer > ( ) ;
2020-08-07 23:39:06 +02:00
Map ! : Phaser . Tilemaps . Tilemap ;
2021-01-28 19:15:34 +01:00
Layers ! : Array < Phaser.Tilemaps.StaticTilemapLayer > ;
2020-08-07 23:39:06 +02:00
Objects ! : Array < Phaser.Physics.Arcade.Sprite > ;
mapFile ! : ITiledMap ;
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 ;
2020-06-19 18:18:43 +02:00
pendingEvents : Queue < InitUserPositionEventInterface | AddPlayerEventInterface | RemovePlayerEventInterface | UserMovedEventInterface | GroupCreatedUpdatedEventInterface | DeleteGroupEventInterface > = new Queue < InitUserPositionEventInterface | AddPlayerEventInterface | RemovePlayerEventInterface | UserMovedEventInterface | GroupCreatedUpdatedEventInterface | DeleteGroupEventInterface > ( ) ;
2020-06-04 18:11:07 +02:00
private initPosition : PositionInterface | null = null ;
2020-06-02 13:44:42 +02:00
private playersPositionInterpolator = new PlayersPositionInterpolator ( ) ;
2020-12-04 11:30:35 +01:00
public connection ! : RoomConnection ;
2020-08-07 23:39:06 +02:00
private simplePeer ! : SimplePeer ;
2020-09-16 18:38:50 +02:00
private GlobalMessageManager ! : GlobalMessageManager ;
2020-12-04 11:30:35 +01:00
public ConsoleGlobalMessageManager ! : ConsoleGlobalMessageManager ;
2020-07-27 22:36:07 +02:00
private connectionAnswerPromise : Promise < RoomJoinedMessageInterface > ;
2020-08-17 22:51:37 +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-04-19 10:19:30 +02:00
private iframeSubscriptionList ! : Array < Subscription > ;
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.
2020-06-01 22:42:18 +02:00
lastMoveEventSent : HasMovedEvent = {
direction : '' ,
moving : false ,
x : - 1000 ,
y : - 1000
}
2020-08-17 15:20:03 +02:00
private presentationModeSprite ! : Sprite ;
private chatModeSprite ! : Sprite ;
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.
private outlinedItem : ActionableItem | null = null ;
2021-01-29 21:09:10 +01:00
public userInputManager ! : UserInputManager ;
2021-03-05 18:25:27 +01: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-03-05 18:25:27 +01:00
private messageSubscription : Subscription | null = null ;
2021-03-09 16:21:14 +01:00
private popUpElements : Map < number , DOMElement > = new Map < number , Phaser.GameObjects.DOMElement > ( ) ;
2020-05-09 21:28:50 +02:00
2020-12-18 15:58:49 +01: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 ;
} )
2020-07-27 22:36:07 +02:00
this . connectionAnswerPromise = new Promise < RoomJoinedMessageInterface > ( ( resolve , reject ) : void = > {
this . connectionAnswerPromiseResolve = resolve ;
} )
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 ;
if ( textures ) {
for ( const texture of textures ) {
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' ) ;
2020-07-07 22:52:22 +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)
if ( window . location . protocol === 'http:' && file . src === this . MapUrlFile && file . src . startsWith ( 'http:' ) ) {
this . MapUrlFile = this . MapUrlFile . replace ( 'http://' , 'https://' ) ;
this . load . tilemapTiledJSON ( this . MapUrlFile , this . MapUrlFile ) ;
this . load . on ( 'filecomplete-tilemapJSON-' + this . MapUrlFile , ( key : string , type : string , data : unknown ) = > {
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:' ,
message : file.src
2020-07-07 22:52:22 +02:00
} ) ;
} ) ;
2020-11-18 18:15:57 +01: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.
2020-11-18 18:15:57 +01:00
if ( this . cache . tilemap . exists ( this . MapUrlFile ) ) {
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
2021-01-07 17:11:22 +01:00
this . load . spritesheet ( 'layout_modes' , 'resources/objects/layout_modes.png' , { frameWidth : 32 , frameHeight : 32 } ) ;
2020-05-01 23:38:09 +02:00
this . load . bitmapFont ( 'main_font' , 'resources/fonts/arcade.png' , 'resources/fonts/arcade.xml' ) ;
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 ) = > {
2020-06-03 10:45:25 +02:00
if ( typeof tileset . name === 'undefined' || typeof tileset . image === 'undefined' ) {
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
2020-07-23 18:47:28 +02:00
for ( const layer of this . mapFile . layers ) {
2020-07-23 18:09:24 +02:00
if ( layer . type === 'objectgroup' ) {
2020-07-23 18:47:28 +02:00
for ( const object of layer . objects ) {
2020-07-23 18:09:24 +02:00
let objectsOfType : ITiledMapObject [ ] | undefined ;
if ( ! objects . has ( object . type ) ) {
objectsOfType = new Array < ITiledMapObject > ( ) ;
} else {
objectsOfType = objects . get ( object . type ) ;
if ( objectsOfType === undefined ) {
throw new Error ( 'Unexpected object type not found' ) ;
}
}
objectsOfType . push ( object ) ;
objects . set ( object . type , objectsOfType ) ;
}
}
}
2020-07-23 18:47:28 +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 ;
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 ;
//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 ;
2020-07-23 18:47:28 +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 ) ;
for ( const script of scripts ) {
iframeListener . registerScript ( script ) ;
}
2020-06-03 10:45:25 +02:00
}
2020-04-07 19:23:21 +02:00
//hook initialisation
2020-05-23 15:43:26 +02:00
init ( initData : GameSceneInitInterface ) {
2020-06-04 18:11:07 +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
}
2020-11-13 18:00:22 +01:00
if ( initData . initPosition !== undefined ) {
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 {
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-03-11 16:14:34 +01:00
this . messageSubscription = worldFullMessageStream . stream . subscribe ( ( message ) = > this . showWorldFullError ( ) )
2020-12-11 13:00:11 +01:00
2020-12-04 11:30:35 +01:00
const playerName = gameManager . getPlayerName ( ) ;
if ( ! playerName ) {
throw 'playerName is not set' ;
}
this . playerName = playerName ;
2021-01-07 17:11:22 +01:00
this . characterLayers = gameManager . getCharacterLayers ( ) ;
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-08-30 15:44:22 +02:00
this . gameMap = new GameMap ( this . mapFile ) ;
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-01-28 19:15:34 +01:00
this . Layers = new Array < Phaser.Tilemaps.StaticTilemapLayer > ( ) ;
2020-04-15 23:10:12 +02:00
let depth = - 2 ;
2020-06-09 23:13:26 +02:00
for ( const layer of this . mapFile . layers ) {
2020-05-08 17:35:40 +02:00
if ( layer . type === 'tilelayer' ) {
2021-01-28 19:15:34 +01:00
this . addLayer ( this . Map . createStaticLayer ( layer . name , this . Terrains , 0 , 0 ) . setDepth ( depth ) ) ;
2020-12-11 13:00:11 +01:00
2020-11-18 18:15:57 +01:00
const exitSceneUrl = this . getExitSceneUrl ( layer ) ;
if ( exitSceneUrl !== undefined ) {
this . loadNextGame ( exitSceneUrl ) ;
}
const exitUrl = this . getExitUrl ( layer ) ;
if ( exitUrl !== undefined ) {
this . loadNextGame ( exitUrl ) ;
}
2020-05-09 21:28:50 +02:00
}
2020-05-08 17:35:40 +02:00
if ( layer . type === 'objectgroup' && layer . name === 'floorLayer' ) {
depth = 10000 ;
2020-04-15 23:10:12 +02:00
}
2020-06-07 23:00:05 +02:00
}
2020-04-15 23:10:12 +02:00
if ( depth === - 2 ) {
throw new Error ( 'Your map MUST contain a layer of type "objectgroup" whose name is "floorLayer" that represents the layer characters are drawn at.' ) ;
}
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
2020-10-13 20:39:29 +02:00
this . MapPlayers = this . physics . add . group ( { immovable : true } ) ;
2020-04-13 15:34:09 +02:00
2020-07-27 22:36:07 +02:00
//create input to move
this . userInputManager = new UserInputManager ( this ) ;
2020-11-10 12:38:32 +01:00
mediaManager . setUserInputManager ( this . userInputManager ) ;
2020-07-27 22:36:07 +02:00
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-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-03-30 16:08:49 +02:00
if ( ! this . room . isDisconnected ( ) ) {
if ( this . isReconnecting ) {
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 ) ;
} else if ( this . connection === undefined ) {
// Let's wait 1 second before printing the "connecting" screen to avoid blinking
setTimeout ( ( ) = > {
if ( this . connection === undefined ) {
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-04 11:30:35 +01:00
this . presentationModeSprite = new PresentationModeIcon ( this , 36 , this . game . renderer . height - 2 ) ;
2020-08-17 15:20:03 +02:00
this . presentationModeSprite . on ( 'pointerup' , this . switchLayoutMode . bind ( this ) ) ;
2020-12-04 11:30:35 +01:00
this . chatModeSprite = new ChatModeIcon ( this , 70 , this . game . renderer . height - 2 ) ;
2020-08-17 15:20:03 +02:00
this . chatModeSprite . on ( 'pointerup' , this . switchLayoutMode . bind ( this ) ) ;
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
// FIXME: change this to use the UserInputManager class for input
2020-12-04 11:30:35 +01:00
this . input . keyboard . on ( 'keyup-M' , ( ) = > {
2020-08-17 15:20:03 +02:00
this . switchLayoutMode ( ) ;
2020-08-16 23:19:04 +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
layoutManager . setListener ( this ) ;
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
if ( ! this . room . isDisconnected ( ) ) {
this . connect ( ) ;
}
}
/ * *
* 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 ,
2020-12-03 16:39:44 +01:00
} ) . then ( ( onConnect : OnConnectInterface ) = > {
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 ,
position : message.position
}
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 ( ) ;
if ( position === undefined ) {
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 ) ;
} catch ( e ) {
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 ) ;
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 ) ;
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 ( {
onConnect ( user : UserSimplePeerInterface ) {
self . presentationModeSprite . setVisible ( true ) ;
self . chatModeSprite . setVisible ( true ) ;
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 ) {
if ( self . simplePeer . getNbConnections ( ) === 0 ) {
self . presentationModeSprite . setVisible ( false ) ;
self . chatModeSprite . setVisible ( false ) ;
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 ) )
this . CurrentPlayer . on ( hasMovedEventName , ( event : HasMovedEvent ) = > {
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-04 11:30:35 +01:00
this . ConsoleGlobalMessageManager = new ConsoleGlobalMessageManager ( this . connection , this . userInputManager , this . connection . isAdmin ( ) ) ;
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' ) ;
if ( circleElement ) {
this . textures . remove ( 'circleSprite-white' ) ;
}
circleElement = Object . values ( this . textures . list ) . find ( ( object : Texture ) = > object . key === 'circleSprite-red' ) ;
if ( circleElement ) {
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-03-12 16:39:29 +01: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-02-10 11:08:57 +01:00
private safeParseJSONstring ( jsonString : string | undefined , propertyName : string ) {
2021-02-09 20:31:49 +01:00
try {
return jsonString ? JSON . parse ( jsonString ) : { } ;
} 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 { }
}
}
2020-11-10 17:00:23 +01:00
private triggerOnMapLayerPropertyChange ( ) {
2020-11-18 18:15:57 +01:00
this . gameMap . onPropertyChange ( 'exitSceneUrl' , ( newValue , oldValue ) = > {
if ( newValue ) this . onMapExit ( newValue as string ) ;
2021-02-17 19:29:59 +01:00
} ) ;
this . gameMap . onPropertyChange ( 'exitUrl' , ( newValue , oldValue ) = > {
2021-01-25 13:18:57 +01: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 ) = > {
2020-11-10 17:00:23 +01: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 ( ) ;
2020-11-21 15:40:24 +01:00
} else {
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 ) ;
if ( openWebsiteTriggerValue && openWebsiteTriggerValue === ON_ACTION_TRIGGER_BUTTON ) {
2021-01-25 12:21:40 +01:00
let message = allProps . get ( WEBSITE_MESSAGE_PROPERTIES ) ;
if ( message === undefined ) {
2021-02-17 19:31:05 +01:00
message = 'Press on SPACE to open the 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 ) ;
} else {
openWebsiteFunction ( ) ;
}
2020-11-10 17:00:23 +01:00
}
} ) ;
this . gameMap . onPropertyChange ( 'jitsiRoom' , ( newValue , oldValue , allProps ) = > {
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 ( ) ;
2020-11-21 15:40:24 +01:00
} else {
const openJitsiRoomFunction = ( ) = > {
2021-02-10 11:40:59 +01:00
const roomName = jitsiFactory . getRoomName ( newValue . toString ( ) , this . instance ) ;
2021-03-16 20:37:12 +01:00
const jitsiUrl = allProps . get ( "jitsiUrl" ) as string | undefined ;
if ( JITSI_PRIVATE_MODE && ! jitsiUrl ) {
2020-11-21 15:40:24 +01:00
const adminTag = allProps . get ( "jitsiRoomAdminTag" ) as string | undefined ;
2021-02-10 11:40:59 +01: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 ) ;
if ( jitsiTriggerValue && jitsiTriggerValue === ON_ACTION_TRIGGER_BUTTON ) {
2021-01-25 12:21:40 +01:00
let message = allProps . get ( JITSI_MESSAGE_PROPERTIES ) ;
if ( message === undefined ) {
2021-02-17 19:31:05 +01:00
message = 'Press on SPACE to enter in 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 ) ;
} else {
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 ) = > {
if ( newValue === undefined || newValue === false || newValue === '' ) {
this . connection . setSilent ( false ) ;
} else {
this . connection . setSilent ( true ) ;
}
} ) ;
2021-03-11 22:34:49 +01:00
this . gameMap . onPropertyChange ( 'playAudio' , ( newValue , oldValue , allProps ) = > {
const volume = allProps . get ( AUDIO_VOLUME_PROPERTY ) as number | undefined ;
const loop = allProps . get ( AUDIO_LOOP_PROPERTY ) as boolean | undefined ;
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 ) = > {
if ( newValue === undefined || newValue === false || newValue === '' ) {
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-04-19 10:19:30 +02:00
this . iframeSubscriptionList = [ ] ;
this . iframeSubscriptionList . push ( iframeListener . openPopupStream . subscribe ( ( openPopupEvent ) = > {
2021-03-12 16:39:29 +01:00
2021-03-10 17:22:39 +01:00
let objectLayerSquare : ITiledMapObject ;
2021-03-28 16:36:02 +02:00
const targetObjectData = this . getObjectLayerData ( openPopupEvent . targetObject ) ;
if ( targetObjectData !== undefined ) {
objectLayerSquare = targetObjectData ;
} 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-04-09 14:35:15 +02:00
let html = ` <div id="container" hidden><div class="nes-container with-title is-centered">
2021-03-09 16:21:14 +01:00
$ { escapedMessage }
2021-04-09 14:35:15 +02:00
< / 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 ;
for ( const button of openPopupEvent . buttons ) {
2021-03-22 16:10:21 +01: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>' ;
const domElement = this . add . dom ( objectLayerSquare . x ,
objectLayerSquare . y ) . createFromHTML ( html ) ;
2021-03-09 16:21:14 +01:00
2021-03-23 17:15:22 +01: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 ;
for ( const button of openPopupEvent . buttons ) {
const button = HtmlUtils . getElementByIdOrFail < HTMLButtonElement > ( ` popup- ${ openPopupEvent . popupId } - ${ id } ` ) ;
const btnId = id ;
button . onclick = ( ) = > {
iframeListener . sendButtonClickedEvent ( openPopupEvent . popupId , btnId ) ;
2021-04-09 14:35:15 +02:00
button . disabled = true ;
2021-03-09 18:05:07 +01:00
}
id ++ ;
}
2021-03-09 16:21:14 +01:00
this . tweens . add ( {
targets : domElement ,
scale : 1 ,
ease : "EaseOut" ,
duration : 400 ,
} ) ;
this . popUpElements . set ( openPopupEvent . popupId , domElement ) ;
2021-04-19 10:19:30 +02:00
} ) ) ;
2021-03-09 16:21:14 +01:00
2021-04-19 10:19:30 +02:00
this . iframeSubscriptionList . push ( iframeListener . closePopupStream . subscribe ( ( closePopupEvent ) = > {
2021-03-09 18:51:30 +01:00
const popUpElement = this . popUpElements . get ( closePopupEvent . popupId ) ;
if ( popUpElement === undefined ) {
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 ( {
targets : popUpElement ,
scale : 0 ,
ease : "EaseOut" ,
duration : 400 ,
onComplete : ( ) = > {
popUpElement ? . destroy ( ) ;
this . popUpElements . delete ( closePopupEvent . popupId ) ;
} ,
} ) ;
2021-04-19 10:19:30 +02:00
} ) ) ;
2021-03-12 16:39:29 +01:00
2021-04-19 10:19:30 +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-04-21 16:47:19 +02:00
this . iframeSubscriptionList . push ( iframeListener . playSoundStream . subscribe ( ( playSoundEvent ) = >
{
2021-04-23 15:35:34 +02:00
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-04-23 15:35:34 +02:00
this . iframeSubscriptionList . push ( iframeListener . stopSoundStream . subscribe ( ( stopSoundEvent ) = >
{
const url = new URL ( stopSoundEvent . url , this . MapUrlFile ) ;
soundManager . stopSound ( this . sound , url . toString ( ) ) ;
} ) )
this . iframeSubscriptionList . push ( iframeListener . loadSoundStream . subscribe ( ( loadSoundEvent ) = >
{
const url = new URL ( loadSoundEvent . url , this . MapUrlFile ) ;
soundManager . loadSound ( this . load , this . sound , url . toString ( ) ) ;
} ) )
2021-04-19 10:19:30 +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-04-23 15:35:34 +02:00
2021-03-22 16:10:21 +01:00
let scriptedBubbleSprite : Sprite ;
2021-04-19 10:19:30 +02:00
this . iframeSubscriptionList . push ( iframeListener . displayBubbleStream . subscribe ( ( ) = > {
2021-03-22 16:10:21 +01:00
scriptedBubbleSprite = new Sprite ( this , this . CurrentPlayer . x + 25 , this . CurrentPlayer . y , 'circleSprite-white' ) ;
scriptedBubbleSprite . setDisplayOrigin ( 48 , 48 ) ;
this . add . existing ( scriptedBubbleSprite ) ;
2021-04-19 10:19:30 +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-03-09 16:21:14 +01:00
}
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 ) {
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 ) ;
2020-11-18 18:15:57 +01:00
if ( roomId !== this . scene . key ) {
2021-02-13 14:15:09 +01:00
if ( this . scene . get ( roomId ) === null ) {
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 ;
}
}
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 ) ;
for ( const script of scripts ) {
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-03-26 14:12:22 +01:00
this . simplePeer . closeAllConnections ( ) ;
2021-03-05 18:25:27 +01:00
this . simplePeer ? . unregister ( ) ;
this . messageSubscription ? . unsubscribe ( ) ;
2021-04-19 10:19:30 +02:00
for ( const iframeEvents of this . iframeSubscriptionList ) {
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 ( ) ;
this . MapPlayers . remove ( player ) ;
} ) ;
this . MapPlayersByKey = new Map < number , RemotePlayer > ( ) ;
}
2020-08-17 15:20:03 +02:00
private switchLayoutMode ( ) : void {
2020-10-27 20:46:53 +01:00
//if discussion is activated, this layout cannot be activated
if ( mediaManager . activatedDiscussion ) {
return ;
}
2020-08-17 15:20:03 +02:00
const mode = layoutManager . getLayoutMode ( ) ;
if ( mode === LayoutMode . Presentation ) {
layoutManager . switchLayoutMode ( LayoutMode . VideoChat ) ;
this . presentationModeSprite . setFrame ( 1 ) ;
this . chatModeSprite . setFrame ( 2 ) ;
} else {
layoutManager . switchLayoutMode ( LayoutMode . Presentation ) ;
this . presentationModeSprite . setFrame ( 0 ) ;
this . chatModeSprite . setFrame ( 3 ) ;
}
2020-04-13 19:56:41 +02:00
}
2020-11-17 15:02:21 +01:00
private initStartXAndStartY() {
// If there is an init position passed
if ( this . initPosition !== null ) {
this . startX = this . initPosition . x ;
this . startY = this . initPosition . y ;
} else {
// Now, let's find the start layer
2020-11-19 14:32:18 +01:00
if ( this . startLayerName ) {
this . initPositionFromLayerName ( this . startLayerName ) ;
2020-11-17 15:02:21 +01:00
}
if ( this . startX === undefined ) {
// If we have no start layer specified or if the hash passed does not exist, let's go with the default start position.
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.
if ( this . startX === undefined ) {
console . warn ( 'This map is missing a layer named "start" that contains the available default start positions.' ) ;
// Let's start in the middle of the map
this . startX = this . mapFile . width * 16 ;
this . startY = this . mapFile . height * 16 ;
}
}
private initPositionFromLayerName ( layerName : string ) {
for ( const layer of this . mapFile . layers ) {
2020-11-19 14:32:18 +01:00
if ( layerName === layer . name && layer . type === 'tilelayer' && ( layerName === defaultStartLayerName || this . isStartLayer ( layer ) ) ) {
2020-11-17 15:02:21 +01:00
const startPosition = this . startUser ( layer ) ;
2020-11-25 15:58:10 +01: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-10-15 15:50:51 +02:00
private getExitUrl ( layer : ITiledMapLayer ) : string | undefined {
return this . getProperty ( layer , "exitUrl" ) as string | undefined ;
}
2020-11-18 18:15:57 +01:00
/ * *
* @deprecated the map property exitSceneUrl is deprecated
* /
2020-05-11 18:49:20 +02:00
private getExitSceneUrl ( layer : ITiledMapLayer ) : string | undefined {
2020-06-07 13:23:32 +02:00
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 ( ) ) ;
}
private getProperty ( layer : ITiledMapLayer | ITiledMap , name : string ) : string | boolean | number | undefined {
const properties : ITiledMapLayerProperty [ ] = layer . properties ;
2020-05-23 17:27:49 +02:00
if ( ! properties ) {
return undefined ;
}
2020-11-04 13:33:58 +01:00
const obj = properties . find ( ( property : ITiledMapLayerProperty ) = > property . name . toLowerCase ( ) === name . toLowerCase ( ) ) ;
2020-05-23 17:27:49 +02:00
if ( obj === undefined ) {
return undefined ;
}
return obj . value ;
}
2021-03-07 21:02:38 +01:00
private getProperties ( layer : ITiledMapLayer | ITiledMap , name : string ) : ( string | number | boolean | undefined ) [ ] {
const properties : ITiledMapLayerProperty [ ] = layer . properties ;
if ( ! properties ) {
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
2020-11-18 18:15:57 +01:00
private async loadNextGame ( exitSceneIdentifier : string ) {
const { roomId , hash } = Room . getIdFromIdentifier ( exitSceneIdentifier , this . MapUrlFile , this . instance ) ;
2020-10-13 18:44:50 +02:00
const room = new Room ( roomId ) ;
2020-11-18 18:15:57 +01:00
await gameManager . loadMap ( room , this . scene ) ;
2020-05-09 21:28:50 +02:00
}
2020-06-03 11:14:04 +02:00
private startUser ( layer : ITiledMapLayer ) : PositionInterface {
2020-06-19 16:36:40 +02:00
const tiles = layer . data ;
if ( typeof ( tiles ) === 'string' ) {
throw new Error ( 'The content of a JSON map must be filled as a JSON array, not as a string' ) ;
}
const possibleStartPositions : PositionInterface [ ] = [ ] ;
2020-05-09 21:28:50 +02:00
tiles . forEach ( ( objectKey : number , key : number ) = > {
if ( objectKey === 0 ) {
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-03-27 13:57:50 +01: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
if ( possibleStartPositions . length === 0 ) {
console . warn ( 'The start layer "' + layer . name + '" for this map is empty.' ) ;
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() {
this . cameras . main . setBounds ( 0 , 0 , this . Map . widthInPixels , this . Map . heightInPixels ) ;
2020-08-24 14:19:36 +02:00
this . updateCameraOffset ( ) ;
2020-04-13 19:56:41 +02:00
this . cameras . main . setZoom ( ZOOM_LEVEL ) ;
2020-04-10 12:54:05 +02:00
}
2021-01-28 19:15:34 +01:00
addLayer ( Layer : Phaser.Tilemaps.StaticTilemapLayer ) {
2020-04-13 15:34:09 +02:00
this . Layers . push ( Layer ) ;
}
createCollisionWithPlayer() {
//add collision layer
2021-01-28 19:15:34 +01:00
this . Layers . forEach ( ( Layer : Phaser.Tilemaps.StaticTilemapLayer ) = > {
2020-06-09 23:12:54 +02:00
this . physics . add . collider ( this . CurrentPlayer , Layer , ( object1 : GameObject , object2 : GameObject ) = > {
2020-04-13 16:56:06 +02:00
//this.CurrentPlayer.say("Collision with layer : "+ (object2 as Tile).layer.name)
2020-04-13 15:34:09 +02:00
} ) ;
Layer . setCollisionByProperty ( { collides : true } ) ;
2020-04-13 19:40:10 +02:00
if ( DEBUG_MODE ) {
//debug code to see the collision hitbox of the object in the top layer
Layer . 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 15:34:09 +02:00
} ) ;
}
2020-05-14 23:19:48 +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 ,
this . userInputManager
) ;
} catch ( err ) {
if ( err instanceof TextureError ) {
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
2020-05-02 16:54:52 +02:00
pushPlayerPosition ( event : HasMovedEvent ) {
2020-06-01 22:42:18 +02:00
if ( this . lastMoveEventSent === event ) {
return ;
}
// If the player is not moving, let's send the info right now.
if ( event . moving === false ) {
this . doPushPlayerPosition ( event ) ;
return ;
}
// If the player is moving, and if it changed direction, let's send an event
if ( event . direction !== this . lastMoveEventSent . direction ) {
this . doPushPlayerPosition ( event ) ;
return ;
}
// If more than 200ms happened since last event sent
if ( this . currentTick - this . lastSentTick >= POSITION_DELAY ) {
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
* /
private outlineItem ( event : HasMovedEvent ) : void {
let x = event . x ;
let y = event . y ;
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 ;
let selectedItem : ActionableItem | null = null ;
2020-07-27 22:36:07 +02:00
for ( const item of this . actionableItems . values ( ) ) {
2020-07-23 18:47:28 +02:00
const distance = item . actionableDistance ( x , y ) ;
2020-07-23 18:09:24 +02:00
if ( distance !== null && distance < shortestDistance ) {
shortestDistance = distance ;
selectedItem = item ;
}
}
if ( this . outlinedItem === selectedItem ) {
return ;
}
this . outlinedItem ? . notSelectable ( ) ;
this . outlinedItem = selectedItem ;
this . outlinedItem ? . selectable ( ) ;
}
2020-06-01 22:42:18 +02:00
private doPushPlayerPosition ( event : HasMovedEvent ) : void {
this . lastMoveEventSent = event ;
this . lastSentTick = this . currentTick ;
2020-09-15 10:06:11 +02:00
const camera = this . cameras . main ;
this . connection . sharePosition ( event . x , event . y , event . direction , event . moving , {
left : camera.scrollX ,
top : camera.scrollY ,
right : camera.scrollX + camera . width ,
bottom : camera.scrollY + camera . height ,
} ) ;
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 .
* /
update ( time : number , delta : number ) : void {
2020-11-21 18:47:38 +01:00
mediaManager . setLastUpdateScene ( ) ;
2020-06-01 22:42:18 +02:00
this . currentTick = time ;
2020-04-18 17:16:39 +02:00
this . CurrentPlayer . moveUser ( delta ) ;
2020-06-02 13:44:42 +02:00
2020-06-19 18:18:43 +02:00
// Let's handle all events
2020-06-19 18:24:32 +02:00
while ( this . pendingEvents . length !== 0 ) {
const event = this . pendingEvents . dequeue ( ) ;
2020-06-19 18:18:43 +02:00
switch ( event . type ) {
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 ) ;
2020-09-18 13:57:38 +02:00
updatedPlayersPositions . forEach ( ( moveEvent : HasMovedEvent , userId : number ) = > {
2021-03-19 15:40:07 +01:00
const player : RemotePlayer | undefined = this . MapPlayersByKey . get ( userId ) ;
2020-06-02 13:44:42 +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 {
2020-06-22 18:42:54 +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
usersPosition . forEach ( ( userPosition : MessageUserPositionInterface ) = > {
if ( userPosition . userId === currentPlayerId ) {
return ;
}
this . addPlayer ( userPosition ) ;
} ) ;
}
2020-06-19 18:18:43 +02:00
/ * *
* Called by the connexion when a new player arrives on a map
* /
public addPlayer ( addPlayerData : AddPlayerInterface ) : void {
this . pendingEvents . enqueue ( {
type : "AddPlayerEvent" ,
event : addPlayerData
} ) ;
}
2021-01-17 20:34:35 +01:00
2021-01-07 17:11:22 +01:00
private doAddPlayer ( addPlayerData : AddPlayerInterface ) : void {
2020-05-23 14:00:36 +02:00
//check if exist player, if exist, move position
if ( this . MapPlayersByKey . has ( addPlayerData . userId ) ) {
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 ,
2020-05-22 22:59:43 +02:00
addPlayerData . position . moving
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 ) ;
2020-05-19 19:11:12 +02:00
if ( player === undefined ) {
console . error ( 'Cannot find user with id ' , userId ) ;
2020-06-04 18:11:07 +02:00
} else {
player . destroy ( ) ;
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 {
2020-06-09 23:13:26 +02:00
const player : RemotePlayer | undefined = this . MapPlayersByKey . get ( message . userId ) ;
2020-05-19 19:11:12 +02:00
if ( player === undefined ) {
2020-06-22 18:42:54 +02:00
//throw new Error('Cannot find player with ID "' + message.userId +'"');
console . error ( 'Cannot update position of player with ID "' + message . userId + '": player not found' ) ;
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.
2020-06-09 23:13:26 +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 ) ,
groupPositionMessage . groupSize === 4 ? 'circleSprite-red' : 'circleSprite-white'
) ;
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 ) ;
2020-06-04 18:11:07 +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 ) {
this . connection . emitActionableEvent ( itemId , eventName , state , parameters ) ;
}
2020-08-17 22:51:37 +02:00
2020-10-08 16:00:29 +02:00
public onResize ( ) : void {
2020-09-15 16:21:41 +02:00
this . reposition ( ) ;
// Send new viewport to server
const camera = this . cameras . main ;
this . connection . setViewport ( {
left : camera.scrollX ,
top : camera.scrollY ,
right : camera.scrollX + camera . width ,
bottom : camera.scrollY + camera . height ,
} ) ;
}
2021-03-10 17:22:39 +01:00
private getObjectLayerData ( objectName : string ) : ITiledMapObject | undefined {
for ( const layer of this . mapFile . layers ) {
if ( layer . type === 'objectgroup' && layer . name === 'floorLayer' ) {
for ( const object of layer . objects ) {
if ( object . name === objectName ) {
return object ;
}
}
}
}
return undefined ;
}
2020-08-17 15:20:03 +02:00
private reposition ( ) : void {
this . presentationModeSprite . setY ( this . game . renderer . height - 2 ) ;
this . chatModeSprite . setY ( this . game . renderer . height - 2 ) ;
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
this . updateCameraOffset ( ) ;
}
/ * *
* Updates the offset of the character compared to the center of the screen according to the layout mananger
* ( tries to put the character in the center of the reamining space if there is a discussion going on .
* /
private updateCameraOffset ( ) : void {
const array = layoutManager . findBiggestAvailableArray ( ) ;
let xCenter = ( array . xEnd - array . xStart ) / 2 + array . xStart ;
let yCenter = ( array . yEnd - array . yStart ) / 2 + array . yStart ;
// Let's put this in Game coordinates by applying the zoom level:
xCenter /= ZOOM_LEVEL * RESOLUTION ;
yCenter /= ZOOM_LEVEL * RESOLUTION ;
2021-01-17 20:34:35 +01:00
2020-08-24 14:19:36 +02:00
this . cameras . main . startFollow ( this . CurrentPlayer , true , 1 , 1 , xCenter - this . game . renderer . width / 2 , yCenter - this . game . renderer . height / 2 ) ;
}
public onCenterChange ( ) : void {
this . updateCameraOffset ( ) ;
2020-08-17 15:20:03 +02:00
}
2020-10-16 19:13:26 +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 ( ) ;
const jitsiConfig = this . safeParseJSONstring ( allProps . get ( "jitsiConfig" ) as string | undefined , 'jitsiConfig' ) ;
const jitsiInterfaceConfig = this . safeParseJSONstring ( allProps . get ( "jitsiInterfaceConfig" ) as string | undefined , 'jitsiInterfaceConfig' ) ;
2021-03-16 20:37:12 +01:00
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 ) ;
2020-10-16 19:13:26 +02:00
this . connection . setSilent ( true ) ;
mediaManager . hideGameOverlay ( ) ;
2020-11-17 18:03:44 +01:00
//permit to stop jitsi when user close iframe
mediaManager . addTriggerCloseJitsiFrameButton ( 'close-jisi' , ( ) = > {
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' ) ;
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-01-26 08:57:10 +01: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?)
private showWorldFullError ( ) : void {
this . cleanupClosingScene ( ) ;
this . scene . stop ( ReconnectingSceneName ) ;
2021-03-28 16:53:15 +02:00
this . userInputManager . disableControls ( ) ;
2021-03-11 16:14:34 +01: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-03-05 18:25:27 +01:00
}
2020-04-07 19:23:21 +02:00
}