Merge branch 'develop' of github.com:thecodingmachine/workadventure into skiprender2

# Conflicts:
#	front/src/Phaser/Game/GameScene.ts
#	front/src/index.ts
This commit is contained in:
David Négrier 2021-05-06 16:25:13 +02:00
commit 6b4d064f19
143 changed files with 6943 additions and 1272 deletions

View file

@ -46,7 +46,7 @@ export abstract class DirtyScene extends ResizableScene {
return this.dirty || this.objectListChanged;
}
public onResize(): void {
public onResize(ev: UIEvent): void {
this.objectListChanged = true;
}
}

View file

@ -34,7 +34,7 @@ export class GameManager {
public async init(scenePlugin: Phaser.Scenes.ScenePlugin): Promise<string> {
this.startRoom = await connectionManager.initGameConnexion();
await this.loadMap(this.startRoom, scenePlugin);
if (!this.playerName) {
return LoginSceneName;
} else if (!this.characterLayers || !this.characterLayers.length) {
@ -89,10 +89,7 @@ export class GameManager {
console.log('starting '+ (this.currentGameSceneName || this.startRoom.id))
scenePlugin.start(this.currentGameSceneName || this.startRoom.id);
scenePlugin.launch(MenuSceneName);
if (!localUserStore.getHelpCameraSettingsShown()) {
scenePlugin.launch(HelpCameraSettingsSceneName);//700
}
scenePlugin.launch(HelpCameraSettingsSceneName);//700
}
public gameSceneIsCreated(scene: GameScene) {

View file

@ -1,4 +1,5 @@
import {ITiledMap} from "../Map/ITiledMap";
import {ITiledMap, ITiledMapLayer} from "../Map/ITiledMap";
import {LayersIterator} from "../Map/LayersIterator";
export type PropertyChangeCallback = (newValue: string | number | boolean | undefined, oldValue: string | number | boolean | undefined, allProps: Map<string, string | boolean | number>) => void;
@ -10,8 +11,10 @@ export class GameMap {
private key: number|undefined;
private lastProperties = new Map<string, string|boolean|number>();
private callbacks = new Map<string, Array<PropertyChangeCallback>>();
public readonly layersIterator: LayersIterator;
public constructor(private map: ITiledMap) {
this.layersIterator = new LayersIterator(map);
}
/**
@ -55,7 +58,7 @@ export class GameMap {
private getProperties(key: number): Map<string, string|boolean|number> {
const properties = new Map<string, string|boolean|number>();
for (const layer of this.map.layers) {
for (const layer of this.layersIterator) {
if (layer.type !== 'tilelayer') {
continue;
}

View file

@ -15,10 +15,16 @@ import {
JITSI_PRIVATE_MODE,
MAX_PER_GROUP,
POSITION_DELAY,
RESOLUTION,
ZOOM_LEVEL
} from "../../Enum/EnvironmentVariable";
import {ITiledMap, ITiledMapLayer, ITiledMapLayerProperty, ITiledMapObject, ITiledTileSet} from "../Map/ITiledMap";
import {
ITiledMap,
ITiledMapLayer,
ITiledMapLayerProperty,
ITiledMapObject,
ITiledText,
ITiledMapTileLayer,
ITiledTileSet
} from "../Map/ITiledMap";
import {AddPlayerInterface} from "./AddPlayerInterface";
import {PlayerAnimationDirections} from "../Player/Animation";
import {PlayerMovement} from "./PlayerMovement";
@ -58,10 +64,6 @@ import {Room} from "../../Connexion/Room";
import {jitsiFactory} from "../../WebRtc/JitsiFactory";
import {urlManager} from "../../Url/UrlManager";
import {audioManager} from "../../WebRtc/AudioManager";
import {IVirtualJoystick} from "../../types";
const {
default: VirtualJoystick,
} = require("phaser3-rex-plugins/plugins/virtualjoystick.js");
import {PresentationModeIcon} from "../Components/PresentationModeIcon";
import {ChatModeIcon} from "../Components/ChatModeIcon";
import {OpenChatIcon, openChatIconName} from "../Components/OpenChatIcon";
@ -84,6 +86,11 @@ import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoading
import RenderTexture = Phaser.GameObjects.RenderTexture;
import Tilemap = Phaser.Tilemaps.Tilemap;
import {DirtyScene} from "./DirtyScene";
import {TextUtils} from "../Components/TextUtils";
import {touchScreenManager} from "../../Touch/TouchScreenManager";
import {PinchManager} from "../UserInput/PinchManager";
import {joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey} from "../Components/MobileJoystick";
import {waScaleManager} from "../Services/WaScaleManager";
export interface GameSceneInitInterface {
initPosition: PointInterface|null,
@ -139,7 +146,7 @@ export class GameScene extends DirtyScene implements CenterListener {
pendingEvents: Queue<InitUserPositionEventInterface|AddPlayerEventInterface|RemovePlayerEventInterface|UserMovedEventInterface|GroupCreatedUpdatedEventInterface|DeleteGroupEventInterface> = new Queue<InitUserPositionEventInterface|AddPlayerEventInterface|RemovePlayerEventInterface|UserMovedEventInterface|GroupCreatedUpdatedEventInterface|DeleteGroupEventInterface>();
private initPosition: PositionInterface|null = null;
private playersPositionInterpolator = new PlayersPositionInterpolator();
public connection!: RoomConnection;
public connection: RoomConnection|undefined;
private simplePeer!: SimplePeer;
private GlobalMessageManager!: GlobalMessageManager;
public ConsoleGlobalMessageManager!: ConsoleGlobalMessageManager;
@ -178,7 +185,7 @@ export class GameScene extends DirtyScene implements CenterListener {
private messageSubscription: Subscription|null = null;
private popUpElements : Map<number, DOMElement> = new Map<number, Phaser.GameObjects.DOMElement>();
private originalMapUrl: string|undefined;
public virtualJoystick!: IVirtualJoystick;
private pinchManager: PinchManager|undefined;
constructor(private room: Room, MapUrlFile: string, customKey?: string|undefined) {
super({
@ -197,8 +204,7 @@ export class GameScene extends DirtyScene implements CenterListener {
})
this.connectionAnswerPromise = new Promise<RoomJoinedMessageInterface>((resolve, reject): void => {
this.connectionAnswerPromiseResolve = resolve;
})
});
}
//hook preload scene
@ -212,6 +218,10 @@ export class GameScene extends DirtyScene implements CenterListener {
}
this.load.image(openChatIconName, 'resources/objects/talk.png');
if (touchScreenManager.supportTouchScreen) {
this.load.image(joystickBaseKey, joystickBaseImg);
this.load.image(joystickThumbKey, joystickThumbImg);
}
this.load.on(FILE_LOAD_ERROR, (file: {src: string}) => {
// If we happen to be in HTTP and we are trying to load a URL in HTTPS only... (this happens only in dev environments)
if (window.location.protocol === 'http:' && file.src === this.MapUrlFile && file.src.startsWith('http:') && this.originalMapUrl === undefined) {
@ -259,6 +269,7 @@ export class GameScene extends DirtyScene implements CenterListener {
this.load.spritesheet('layout_modes', 'resources/objects/layout_modes.png', {frameWidth: 32, frameHeight: 32});
this.load.bitmapFont('main_font', 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
//this function must stay at the end of preload function
addLoader(this);
}
@ -364,7 +375,11 @@ export class GameScene extends DirtyScene implements CenterListener {
urlManager.pushRoomIdToUrl(this.room);
this.startLayerName = urlManager.getStartLayerNameFromUrl();
this.messageSubscription = worldFullMessageStream.stream.subscribe((message) => this.showWorldFullError())
if (touchScreenManager.supportTouchScreen) {
this.pinchManager = new PinchManager(this);
}
this.messageSubscription = worldFullMessageStream.stream.subscribe((message) => this.showWorldFullError(message))
const playerName = gameManager.getPlayerName();
if (!playerName) {
@ -388,7 +403,7 @@ export class GameScene extends DirtyScene implements CenterListener {
//add layer on map
this.Layers = new Array<Phaser.Tilemaps.TilemapLayer>();
let depth = -2;
for (const layer of this.mapFile.layers) {
for (const layer of this.gameMap.layersIterator) {
if (layer.type === 'tilelayer') {
this.addLayer(this.Map.createLayer(layer.name, this.Terrains, 0, 0).setDepth(depth));
@ -404,9 +419,16 @@ export class GameScene extends DirtyScene implements CenterListener {
if (layer.type === 'objectgroup' && layer.name === 'floorLayer') {
depth = 10000;
}
if (layer.type === 'objectgroup') {
for (const object of layer.objects) {
if (object.text) {
TextUtils.createTextFromITiledMapObject(this, object);
}
}
}
}
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.');
throw new Error('Your map MUST contain a layer of type "objectgroup" whose name is "floorLayer" that represents the layer characters are drawn at. This layer cannot be contained in a group.');
}
this.initStartXAndStartY();
@ -417,26 +439,10 @@ export class GameScene extends DirtyScene implements CenterListener {
//initialise list of other player
this.MapPlayers = this.physics.add.group({immovable: true});
this.virtualJoystick = new VirtualJoystick(this, {
x: this.game.renderer.width / 2,
y: this.game.renderer.height / 2,
radius: 20,
base: this.add.circle(0, 0, 20),
thumb: this.add.circle(0, 0, 10),
enable: true,
dir: "8dir",
});
this.virtualJoystick.visible = true;
//create input to move
mediaManager.setUserInputManager(this.userInputManager);
this.userInputManager = new UserInputManager(this, this.virtualJoystick);
// Listener event to reposition virtual joystick
// whatever place you click in game area
this.input.on('pointerdown', (pointer: { x: number; y: number; }) => {
this.virtualJoystick.x = pointer.x;
this.virtualJoystick.y = pointer.y;
});
//create input to move
this.userInputManager = new UserInputManager(this);
mediaManager.setUserInputManager(this.userInputManager);
if (localUserStore.getFullscreen()) {
document.querySelector('body')?.requestFullscreen();
@ -729,7 +735,7 @@ export class GameScene extends DirtyScene implements CenterListener {
if (JITSI_PRIVATE_MODE && !jitsiUrl) {
const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined;
this.connection.emitQueryJitsiJwtMessage(roomName, adminTag);
this.connection?.emitQueryJitsiJwtMessage(roomName, adminTag);
} else {
this.startJitsi(roomName, undefined);
}
@ -752,9 +758,9 @@ export class GameScene extends DirtyScene implements CenterListener {
});
this.gameMap.onPropertyChange('silent', (newValue, oldValue) => {
if (newValue === undefined || newValue === false || newValue === '') {
this.connection.setSilent(false);
this.connection?.setSilent(false);
} else {
this.connection.setSilent(true);
this.connection?.setSilent(true);
}
});
this.gameMap.onPropertyChange('playAudio', (newValue, oldValue, allProps) => {
@ -909,9 +915,11 @@ ${escapedMessage}
audioManager.unloadAudio();
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
this.connection?.closeConnection();
this.simplePeer.closeAllConnections();
this.simplePeer?.closeAllConnections();
this.simplePeer?.unregister();
this.messageSubscription?.unsubscribe();
this.userInputManager.destroy();
this.pinchManager?.destroy();
for(const iframeEvents of this.iframeSubscriptionList){
iframeEvents.unsubscribe();
@ -973,13 +981,14 @@ ${escapedMessage}
}
private initPositionFromLayerName(layerName: string) {
for (const layer of this.mapFile.layers) {
if (layerName === layer.name && layer.type === 'tilelayer' && (layerName === defaultStartLayerName || this.isStartLayer(layer))) {
for (const layer of this.gameMap.layersIterator) {
if ((layerName === layer.name || layer.name.endsWith('/'+layerName)) && layer.type === 'tilelayer' && (layerName === defaultStartLayerName || this.isStartLayer(layer))) {
const startPosition = this.startUser(layer);
this.startX = startPosition.x + this.mapFile.tilewidth/2;
this.startY = startPosition.y + this.mapFile.tileheight/2;
}
}
}
private getExitUrl(layer: ITiledMapLayer): string|undefined {
@ -1002,7 +1011,7 @@ ${escapedMessage}
}
private getProperty(layer: ITiledMapLayer|ITiledMap, name: string): string|boolean|number|undefined {
const properties: ITiledMapLayerProperty[] = layer.properties;
const properties: ITiledMapLayerProperty[]|undefined = layer.properties;
if (!properties) {
return undefined;
}
@ -1014,7 +1023,7 @@ ${escapedMessage}
}
private getProperties(layer: ITiledMapLayer|ITiledMap, name: string): (string|number|boolean|undefined)[] {
const properties: ITiledMapLayerProperty[] = layer.properties;
const properties: ITiledMapLayerProperty[]|undefined = layer.properties;
if (!properties) {
return [];
}
@ -1028,7 +1037,7 @@ ${escapedMessage}
await gameManager.loadMap(room, this.scene);
}
private startUser(layer: ITiledMapLayer): PositionInterface {
private startUser(layer: ITiledMapTileLayer): PositionInterface {
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');
@ -1058,8 +1067,8 @@ ${escapedMessage}
//todo: in a dedicated class/function?
initCamera() {
this.cameras.main.setBounds(0,0, this.Map.widthInPixels, this.Map.heightInPixels);
this.cameras.main.startFollow(this.CurrentPlayer, true);
this.updateCameraOffset();
this.cameras.main.setZoom(ZOOM_LEVEL);
}
addLayer(Layer : Phaser.Tilemaps.TilemapLayer){
@ -1185,7 +1194,7 @@ ${escapedMessage}
this.lastMoveEventSent = event;
this.lastSentTick = this.currentTick;
const camera = this.cameras.main;
this.connection.sharePosition(event.x, event.y, event.direction, event.moving, {
this.connection?.sharePosition(event.x, event.y, event.direction, event.moving, {
left: camera.scrollX,
top: camera.scrollY,
right: camera.scrollX + camera.width,
@ -1198,13 +1207,8 @@ ${escapedMessage}
* @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 {
// TODO: add Events.ADDED_TO_SCENE to the scene to track new objects.
// When an object is added, add ANIMATION_UPDATE event on this object (and remove the listener on Events.REMOVE_FROM_SCENE)
// This way, we can set the dirty flag only when an animation is added!!!
this.dirty = false;
mediaManager.setLastUpdateScene();
mediaManager.updateScene();
this.currentTick = time;
if (this.CurrentPlayer.isMoving()) {
this.dirty = true;
@ -1269,7 +1273,7 @@ ${escapedMessage}
* Put all the players on the map on map load.
*/
private doInitUsersPosition(usersPosition: MessageUserPositionInterface[]): void {
const currentPlayerId = this.connection.getUserId();
const currentPlayerId = this.connection?.getUserId();
this.removeAllRemotePlayers();
// load map
usersPosition.forEach((userPosition : MessageUserPositionInterface) => {
@ -1413,16 +1417,16 @@ ${escapedMessage}
* 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);
this.connection?.emitActionableEvent(itemId, eventName, state, parameters);
}
public onResize(): void {
super.onResize();
public onResize(ev: UIEvent): void {
super.onResize(ev);
this.reposition();
// Send new viewport to server
const camera = this.cameras.main;
this.connection.setViewport({
this.connection?.setViewport({
left: camera.scrollX,
top: camera.scrollY,
right: camera.scrollX + camera.width,
@ -1452,19 +1456,18 @@ ${escapedMessage}
}
/**
* 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.
* 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.
*/
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;
const xCenter = (array.xEnd - array.xStart) / 2 + array.xStart;
const yCenter = (array.yEnd - array.yStart) / 2 + array.yStart;
const game = HtmlUtils.querySelectorOrFail<HTMLCanvasElement>('#game canvas');
// Let's put this in Game coordinates by applying the zoom level:
xCenter /= ZOOM_LEVEL * RESOLUTION;
yCenter /= ZOOM_LEVEL * RESOLUTION;
this.cameras.main.startFollow(this.CurrentPlayer, true, 1, 1, xCenter - this.game.renderer.width / 2, yCenter - this.game.renderer.height / 2);
this.cameras.main.setFollowOffset((xCenter - game.offsetWidth/2) * window.devicePixelRatio / this.scale.zoom , (yCenter - game.offsetHeight/2) * window.devicePixelRatio / this.scale.zoom);
}
public onCenterChange(): void {
@ -1478,7 +1481,7 @@ ${escapedMessage}
const jitsiUrl = allProps.get("jitsiUrl") as string|undefined;
jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl);
this.connection.setSilent(true);
this.connection?.setSilent(true);
mediaManager.hideGameOverlay();
//permit to stop jitsi when user close iframe
@ -1507,14 +1510,29 @@ ${escapedMessage}
}
//todo: put this into an 'orchestrator' scene (EntryScene?)
private showWorldFullError(): void {
private showWorldFullError(message: string|null): void {
this.cleanupClosingScene();
this.scene.stop(ReconnectingSceneName);
this.scene.remove(ReconnectingSceneName);
this.userInputManager.disableControls();
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'
});
//FIX ME to use status code
if(message == undefined){
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'
});
}else{
this.scene.start(ErrorSceneName, {
title: 'Connection rejected',
subTitle: 'You cannot join the World. Try again later. \n\r \n\r Error: '+message+'.',
message: 'If you want more information, you may contact administrator or contact us at: workadventure@thecodingmachine.com'
});
}
}
zoomByFactor(zoomFactor: number) {
waScaleManager.zoomModifier *= zoomFactor;
this.updateCameraOffset();
}
}