2021-06-25 16:32:48 +02:00
|
|
|
import type { Subscription } from "rxjs";
|
|
|
|
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,
|
2021-06-29 18:39:43 +02:00
|
|
|
RoomJoinedMessageInterface,
|
2020-09-25 18:29:22 +02:00
|
|
|
} from "../../Connexion/ConnexionModels";
|
2021-06-29 18:39:43 +02:00
|
|
|
import { DEBUG_MODE, JITSI_PRIVATE_MODE, MAX_PER_GROUP, POSITION_DELAY } from "../../Enum/EnvironmentVariable";
|
2021-06-25 18:14:40 +02:00
|
|
|
|
|
|
|
import { Queue } from "queue-typescript";
|
2020-11-23 20:34:05 +01:00
|
|
|
import {
|
2021-06-29 18:39:43 +02:00
|
|
|
AUDIO_LOOP_PROPERTY,
|
|
|
|
AUDIO_VOLUME_PROPERTY,
|
2021-06-24 10:49:55 +02:00
|
|
|
Box,
|
2021-02-17 19:28:41 +01:00
|
|
|
JITSI_MESSAGE_PROPERTIES,
|
2021-01-25 12:21:40 +01:00
|
|
|
ON_ACTION_TRIGGER_BUTTON,
|
|
|
|
TRIGGER_JITSI_PROPERTIES,
|
|
|
|
TRIGGER_WEBSITE_PROPERTIES,
|
2021-06-29 18:39:43 +02:00
|
|
|
WEBSITE_MESSAGE_PROPERTIES,
|
2020-11-23 20:34:05 +01:00
|
|
|
} from "../../WebRtc/LayoutManager";
|
2021-06-23 12:42:24 +02:00
|
|
|
import { coWebsiteManager } from "../../WebRtc/CoWebsiteManager";
|
|
|
|
import type { UserMovedMessage } from "../../Messages/generated/messages_pb";
|
|
|
|
import { ProtobufClientUtils } from "../../Network/ProtobufClientUtils";
|
|
|
|
import type { RoomConnection } from "../../Connexion/RoomConnection";
|
|
|
|
import { Room } from "../../Connexion/Room";
|
|
|
|
import { jitsiFactory } from "../../WebRtc/JitsiFactory";
|
|
|
|
import { urlManager } from "../../Url/UrlManager";
|
|
|
|
import { audioManager } from "../../WebRtc/AudioManager";
|
|
|
|
import { TextureError } from "../../Exception/TextureError";
|
|
|
|
import { localUserStore } from "../../Connexion/LocalUserStore";
|
|
|
|
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
|
2021-06-25 16:32:48 +02:00
|
|
|
import { mediaManager } from "../../WebRtc/MediaManager";
|
2021-06-25 18:14:40 +02:00
|
|
|
import { SimplePeer } from "../../WebRtc/SimplePeer";
|
2021-06-25 16:32:48 +02:00
|
|
|
import { addLoader } from "../Components/Loader";
|
|
|
|
import { OpenChatIcon, openChatIconName } from "../Components/OpenChatIcon";
|
|
|
|
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-07-06 17:24:16 +02:00
|
|
|
import type { ITiledMap, ITiledMapLayer, ITiledMapProperty, ITiledMapObject, ITiledTileSet } from "../Map/ITiledMap";
|
2021-06-29 18:39:43 +02:00
|
|
|
import { MenuScene, MenuSceneName } from "../Menu/MenuScene";
|
2021-06-25 16:32:48 +02:00
|
|
|
import { PlayerAnimationDirections } from "../Player/Animation";
|
|
|
|
import { hasMovedEventName, Player, requestEmoteEventName } from "../Player/Player";
|
|
|
|
import { ErrorSceneName } from "../Reconnecting/ErrorScene";
|
|
|
|
import { ReconnectingSceneName } from "../Reconnecting/ReconnectingScene";
|
|
|
|
import { UserInputManager } from "../UserInput/UserInputManager";
|
|
|
|
import type { AddPlayerInterface } from "./AddPlayerInterface";
|
|
|
|
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-23 12:42:24 +02:00
|
|
|
import { worldFullMessageStream } from "../../Connexion/WorldFullMessageStream";
|
2021-04-06 19:10:18 +02:00
|
|
|
import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager";
|
2021-06-23 12:42:24 +02:00
|
|
|
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";
|
|
|
|
import { EmoteManager } from "./EmoteManager";
|
2021-06-29 18:39:43 +02:00
|
|
|
import EVENT_TYPE = Phaser.Scenes.Events;
|
|
|
|
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-25 18:14:40 +02:00
|
|
|
import { StartPositionCalculator } from "./StartPositionCalculator";
|
2021-06-29 18:39:43 +02:00
|
|
|
import { soundManager } from "./SoundManager";
|
|
|
|
import { peerStore, screenSharingPeerStore } from "../../Stores/PeerStore";
|
|
|
|
import { videoFocusStore } from "../../Stores/VideoFocusStore";
|
|
|
|
import { biggestAvailableAreaStore } from "../../Stores/BiggestAvailableAreaStore";
|
2021-07-02 11:31:44 +02:00
|
|
|
import { SharedVariablesManager } from "./SharedVariablesManager";
|
2021-07-07 11:24:51 +02:00
|
|
|
import { playersStore } from "../../Stores/PlayersStore";
|
2021-07-13 11:00:32 +02:00
|
|
|
import { chatVisibilityStore } from "../../Stores/ChatStore";
|
2021-07-23 20:22:45 +02:00
|
|
|
import { PropertyUtils } from "../Map/PropertyUtils";
|
2021-07-28 18:03:19 +02:00
|
|
|
import Tileset = Phaser.Tilemaps.Tileset;
|
2021-07-30 14:08:27 +02:00
|
|
|
import { userIsAdminStore } from "../../Stores/GameStore";
|
2021-08-05 11:19:28 +02:00
|
|
|
import { layoutManagerActionStore } from "../../Stores/LayoutManagerStore";
|
2021-08-03 11:13:08 +02:00
|
|
|
import { get } from "svelte/store";
|
2021-08-03 18:29:10 +02:00
|
|
|
import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager";
|
2020-04-11 18:17:36 +02:00
|
|
|
|
2020-06-07 13:23:32 +02:00
|
|
|
export interface GameSceneInitInterface {
|
2021-06-29 18:39:43 +02:00
|
|
|
initPosition: PointInterface | null;
|
|
|
|
reconnecting: boolean;
|
2020-05-23 15:43:26 +02:00
|
|
|
}
|
|
|
|
|
2020-06-19 18:18:43 +02:00
|
|
|
interface InitUserPositionEventInterface {
|
2021-06-29 18:39:43 +02:00
|
|
|
type: "InitUserPositionEvent";
|
|
|
|
event: MessageUserPositionInterface[];
|
2020-06-19 18:18:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
interface AddPlayerEventInterface {
|
2021-06-29 18:39:43 +02:00
|
|
|
type: "AddPlayerEvent";
|
|
|
|
event: AddPlayerInterface;
|
2020-06-19 18:18:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
interface RemovePlayerEventInterface {
|
2021-06-29 18:39:43 +02:00
|
|
|
type: "RemovePlayerEvent";
|
|
|
|
userId: number;
|
2020-06-19 18:18:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
interface UserMovedEventInterface {
|
2021-06-29 18:39:43 +02:00
|
|
|
type: "UserMovedEvent";
|
|
|
|
event: MessageUserMovedInterface;
|
2020-06-19 18:18:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
interface GroupCreatedUpdatedEventInterface {
|
2021-06-29 18:39:43 +02:00
|
|
|
type: "GroupCreatedUpdatedEvent";
|
|
|
|
event: GroupCreatedUpdatedMessageInterface;
|
2020-06-19 18:18:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
interface DeleteGroupEventInterface {
|
2021-06-29 18:39:43 +02:00
|
|
|
type: "DeleteGroupEvent";
|
|
|
|
groupId: number;
|
2020-06-19 18:18:43 +02:00
|
|
|
}
|
|
|
|
|
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
|
|
|
circleTexture!: CanvasTexture;
|
2020-10-21 16:07:42 +02:00
|
|
|
circleRedTexture!: CanvasTexture;
|
2021-06-29 18:39:43 +02:00
|
|
|
pendingEvents: Queue<
|
|
|
|
| InitUserPositionEventInterface
|
|
|
|
| AddPlayerEventInterface
|
|
|
|
| RemovePlayerEventInterface
|
|
|
|
| UserMovedEventInterface
|
|
|
|
| GroupCreatedUpdatedEventInterface
|
|
|
|
| DeleteGroupEventInterface
|
|
|
|
> = new Queue<
|
|
|
|
| InitUserPositionEventInterface
|
|
|
|
| AddPlayerEventInterface
|
|
|
|
| RemovePlayerEventInterface
|
|
|
|
| UserMovedEventInterface
|
|
|
|
| GroupCreatedUpdatedEventInterface
|
|
|
|
| DeleteGroupEventInterface
|
|
|
|
>();
|
2021-06-24 08:56:29 +02:00
|
|
|
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-07-27 22:36:07 +02:00
|
|
|
private connectionAnswerPromise: Promise<RoomJoinedMessageInterface>;
|
2021-06-29 18:39:43 +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-07-13 11:00:32 +02:00
|
|
|
private chatVisibilityUnsubscribe!: () => void;
|
2021-06-24 17:02:57 +02:00
|
|
|
private biggestAvailableAreaStoreUnsubscribe!: () => void;
|
2020-05-09 19:41:21 +02:00
|
|
|
MapUrlFile: string;
|
2021-07-13 19:09:07 +02:00
|
|
|
roomUrl: string;
|
2020-05-23 17:27:49 +02:00
|
|
|
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 = {
|
2021-06-29 18:39:43 +02:00
|
|
|
direction: "",
|
2020-06-01 22:42:18 +02:00
|
|
|
moving: false,
|
|
|
|
x: -1000,
|
2021-06-29 18:39:43 +02:00
|
|
|
y: -1000,
|
|
|
|
};
|
2020-06-01 22:42:18 +02:00
|
|
|
|
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-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-07-27 14:29:09 +02:00
|
|
|
private mapTransitioning: boolean = false; //used to prevent transitions happening at the same time.
|
2021-03-31 11:21:06 +02:00
|
|
|
private emoteManager!: EmoteManager;
|
2021-06-29 18:39:43 +02:00
|
|
|
private preloading: boolean = true;
|
2021-07-02 11:31:44 +02:00
|
|
|
private startPositionCalculator!: StartPositionCalculator;
|
|
|
|
private sharedVariablesManager!: SharedVariablesManager;
|
2021-07-23 20:22:45 +02:00
|
|
|
private objectsByType = new Map<string, ITiledMapObject[]>();
|
2021-08-03 18:29:10 +02:00
|
|
|
private embeddedWebsiteManager!: EmbeddedWebsiteManager;
|
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({
|
2021-07-13 19:09:07 +02:00
|
|
|
key: customKey ?? room.key,
|
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;
|
2021-07-13 19:09:07 +02:00
|
|
|
this.roomUrl = room.key;
|
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-07-29 15:53:27 +02:00
|
|
|
//initialize frame event of scripting API
|
|
|
|
this.listenToIframeEvents();
|
|
|
|
|
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
|
|
|
|
2021-06-29 18:39:43 +02: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-29 18:39:43 +02:00
|
|
|
this.load.audio("audio-webrtc-in", "/resources/objects/webrtc-in.mp3");
|
|
|
|
this.load.audio("audio-webrtc-out", "/resources/objects/webrtc-out.mp3");
|
2021-06-03 15:40:44 +02:00
|
|
|
//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-29 18:39:43 +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-06-29 18:39:43 +02:00
|
|
|
this.MapUrlFile = this.MapUrlFile.replace("http://", "https://");
|
2021-03-28 18:35:01 +02:00
|
|
|
this.load.tilemapTiledJSON(this.MapUrlFile, this.MapUrlFile);
|
2021-06-29 18:39:43 +02:00
|
|
|
this.load.on(
|
|
|
|
"filecomplete-tilemapJSON-" + this.MapUrlFile,
|
|
|
|
(key: string, type: string, data: unknown) => {
|
|
|
|
this.onMapLoad(data);
|
|
|
|
}
|
|
|
|
);
|
2021-03-28 18:35:01 +02:00
|
|
|
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);
|
2021-06-29 18:39:43 +02:00
|
|
|
const host = url.host.split(":")[0];
|
|
|
|
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;
|
2021-06-29 18:39:43 +02:00
|
|
|
this.MapUrlFile = this.MapUrlFile.replace("https://", "http://");
|
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.load.tilemapTiledJSON(this.MapUrlFile, this.MapUrlFile);
|
2021-06-29 18:39:43 +02:00
|
|
|
this.load.on(
|
|
|
|
"filecomplete-tilemapJSON-" + this.MapUrlFile,
|
|
|
|
(key: string, type: string, data: unknown) => {
|
|
|
|
this.onMapLoad(data);
|
|
|
|
}
|
|
|
|
);
|
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
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
//once preloading is over, we don't want loading errors to crash the game, so we need to disable this behavior after preloading.
|
|
|
|
console.error("Error when loading: ", file);
|
|
|
|
if (this.preloading) {
|
|
|
|
this.scene.start(ErrorSceneName, {
|
|
|
|
title: "Network error",
|
|
|
|
subTitle: "An error occurred while loading resource:",
|
|
|
|
message: this.originalMapUrl ?? file.src,
|
|
|
|
});
|
|
|
|
}
|
2020-07-07 22:52:22 +02:00
|
|
|
});
|
2021-06-29 18:39:43 +02:00
|
|
|
this.load.scenePlugin("AnimatedTiles", AnimatedTiles, "animatedTiles", "animatedTiles");
|
|
|
|
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
|
|
|
|
2021-06-29 18:39:43 +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: {
|
2021-06-29 18:39:43 +02:00
|
|
|
families: ["Press Start 2P"],
|
|
|
|
urls: ["/resources/fonts/fonts.css"],
|
|
|
|
testString: "abcdefg",
|
2021-05-26 20:31:33 +02:00
|
|
|
},
|
|
|
|
});
|
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;
|
2021-06-29 18:39:43 +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-29 18:39:43 +02:00
|
|
|
if (typeof tileset.name === "undefined" || typeof tileset.image === "undefined") {
|
|
|
|
console.warn("Don't know how to handle tileset ", tileset);
|
2020-06-03 10:45:25 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
//TODO strategy to add access token
|
2020-07-07 18:22:10 +02:00
|
|
|
this.load.image(`${url}/${tileset.image}`, `${url}/${tileset.image}`);
|
2021-06-29 18:39:43 +02:00
|
|
|
});
|
2020-07-23 18:09:24 +02:00
|
|
|
|
|
|
|
// Scan the object layers for objects to load and load them.
|
2021-07-23 20:22:45 +02:00
|
|
|
this.objectsByType = 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) {
|
2021-06-29 18:39:43 +02:00
|
|
|
if (layer.type === "objectgroup") {
|
2021-06-25 16:32:48 +02:00
|
|
|
for (const object of layer.objects) {
|
2021-06-24 08:56:29 +02:00
|
|
|
let objectsOfType: ITiledMapObject[] | undefined;
|
2021-07-23 20:22:45 +02:00
|
|
|
if (!this.objectsByType.has(object.type)) {
|
2020-07-23 18:09:24 +02:00
|
|
|
objectsOfType = new Array<ITiledMapObject>();
|
|
|
|
} else {
|
2021-07-23 20:22:45 +02:00
|
|
|
objectsOfType = this.objectsByType.get(object.type);
|
2021-06-25 16:32:48 +02:00
|
|
|
if (objectsOfType === undefined) {
|
2021-06-29 18:39:43 +02:00
|
|
|
throw new Error("Unexpected object type not found");
|
2020-07-23 18:09:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
objectsOfType.push(object);
|
2021-07-23 20:22:45 +02:00
|
|
|
this.objectsByType.set(object.type, objectsOfType);
|
2020-07-23 18:09:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-23 20:22:45 +02:00
|
|
|
for (const [itemType, objectsOfType] of this.objectsByType) {
|
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) {
|
2021-06-29 18:39:43 +02:00
|
|
|
case "computer": {
|
|
|
|
const module = await import("../Items/Computer/computer");
|
2020-07-23 18:47:28 +02:00
|
|
|
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.
|
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
this.load.on("complete", () => {
|
2020-07-23 18:09:24 +02:00
|
|
|
// 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-06-29 18:39:43 +02:00
|
|
|
this.preloading = false;
|
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-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-06-29 18:39:43 +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) {
|
2021-06-29 18:39:43 +02:00
|
|
|
throw "playerName is not set";
|
2020-12-04 11:30:35 +01:00
|
|
|
}
|
|
|
|
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
|
|
|
|
2021-07-27 14:29:09 +02:00
|
|
|
//initialise map
|
2020-11-18 18:15:57 +01:00
|
|
|
this.Map = this.add.tilemap(this.MapUrlFile);
|
2021-06-29 18:39:43 +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) => {
|
2021-06-29 18:39:43 +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
|
|
|
|
2021-08-03 18:29:10 +02:00
|
|
|
this.embeddedWebsiteManager = new EmbeddedWebsiteManager(this);
|
|
|
|
|
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) {
|
2021-06-29 18:39:43 +02:00
|
|
|
if (layer.type === "tilelayer") {
|
2020-11-18 18:15:57 +01:00
|
|
|
const exitSceneUrl = this.getExitSceneUrl(layer);
|
2021-06-25 16:32:48 +02:00
|
|
|
if (exitSceneUrl !== undefined) {
|
2021-07-13 19:09:07 +02:00
|
|
|
this.loadNextGame(
|
|
|
|
Room.getRoomPathFromExitSceneUrl(exitSceneUrl, window.location.toString(), this.MapUrlFile)
|
|
|
|
);
|
2020-11-18 18:15:57 +01:00
|
|
|
}
|
|
|
|
const exitUrl = this.getExitUrl(layer);
|
2021-06-25 16:32:48 +02:00
|
|
|
if (exitUrl !== undefined) {
|
2021-07-13 19:09:07 +02:00
|
|
|
this.loadNextGameFromExitUrl(exitUrl);
|
2020-11-18 18:15:57 +01:00
|
|
|
}
|
2020-05-09 21:28:50 +02:00
|
|
|
}
|
2021-06-29 18:39:43 +02:00
|
|
|
if (layer.type === "objectgroup") {
|
2021-06-25 16:32:48 +02:00
|
|
|
for (const object of layer.objects) {
|
|
|
|
if (object.text) {
|
2021-04-16 21:29:05 +02:00
|
|
|
TextUtils.createTextFromITiledMapObject(this, object);
|
|
|
|
}
|
2021-07-23 20:22:45 +02:00
|
|
|
if (object.type === "website") {
|
|
|
|
// Let's load iframes in the map
|
|
|
|
const url = PropertyUtils.mustFindStringProperty(
|
|
|
|
"url",
|
|
|
|
object.properties,
|
|
|
|
'in the "' + object.name + '" object of type "website"'
|
|
|
|
);
|
2021-08-03 18:29:10 +02:00
|
|
|
const allowApi = PropertyUtils.findBooleanProperty("allowApi", object.properties);
|
|
|
|
|
|
|
|
// TODO: add a "allow" property to iframe
|
|
|
|
this.embeddedWebsiteManager.createEmbeddedWebsite(
|
|
|
|
object.name,
|
|
|
|
url,
|
|
|
|
object.x,
|
|
|
|
object.y,
|
|
|
|
object.width,
|
|
|
|
object.height,
|
|
|
|
object.visible,
|
|
|
|
allowApi ?? false,
|
|
|
|
""
|
2021-07-23 23:06:59 +02:00
|
|
|
);
|
2021-07-23 20:22:45 +02:00
|
|
|
}
|
2021-04-16 21:29:05 +02:00
|
|
|
}
|
|
|
|
}
|
2020-06-07 23:00:05 +02:00
|
|
|
}
|
2020-04-13 15:34:09 +02:00
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
this.gameMap.exitUrls.forEach((exitUrl) => {
|
2021-07-13 19:09:07 +02:00
|
|
|
this.loadNextGameFromExitUrl(exitUrl);
|
2021-06-29 18:39:43 +02:00
|
|
|
});
|
2020-04-13 15:34:09 +02:00
|
|
|
|
2021-06-25 18:14:40 +02:00
|
|
|
this.startPositionCalculator = new StartPositionCalculator(
|
|
|
|
this.gameMap,
|
|
|
|
this.mapFile,
|
|
|
|
this.initPosition,
|
|
|
|
urlManager.getStartLayerNameFromUrl()
|
|
|
|
);
|
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
|
|
|
|
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-06-29 18:39:43 +02:00
|
|
|
document.querySelector("body")?.requestFullscreen();
|
2021-04-07 12:42:56 +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-05-13 15:24:35 +02:00
|
|
|
this.animatedTiles.init(this.Map);
|
2021-06-29 18:39:43 +02:00
|
|
|
this.events.on("tileanimationupdate", () => (this.dirty = true));
|
2021-06-22 14:21:15 +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-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
|
|
|
|
2021-06-29 18:39:43 +02: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-29 18:39:43 +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();
|
2020-10-12 11:22:41 +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-29 18:39:43 +02:00
|
|
|
this.sound.play("audio-webrtc-in", {
|
|
|
|
volume: 0.2,
|
2021-06-03 15:40:44 +02:00
|
|
|
});
|
2021-06-25 16:32:48 +02:00
|
|
|
} else if (newPeerNumber < oldPeerNumber) {
|
2021-06-29 18:39:43 +02:00
|
|
|
this.sound.play("audio-webrtc-out", {
|
|
|
|
volume: 0.2,
|
2021-06-03 15:40:44 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
oldPeerNumber = newPeerNumber;
|
|
|
|
});
|
2021-07-13 11:00:32 +02:00
|
|
|
|
|
|
|
this.chatVisibilityUnsubscribe = chatVisibilityStore.subscribe((v) => {
|
|
|
|
this.openChatIcon.setVisible(!v);
|
|
|
|
});
|
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;
|
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
connectionManager
|
|
|
|
.connectToRoomSocket(
|
2021-07-13 19:09:07 +02:00
|
|
|
this.roomUrl,
|
2021-06-29 18:39:43 +02:00
|
|
|
this.playerName,
|
|
|
|
this.characterLayers,
|
|
|
|
{
|
2021-06-25 18:14:40 +02:00
|
|
|
...this.startPositionCalculator.startPosition,
|
2021-06-29 18:39:43 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
left: camera.scrollX,
|
|
|
|
top: camera.scrollY,
|
|
|
|
right: camera.scrollX + camera.width,
|
|
|
|
bottom: camera.scrollY + camera.height,
|
|
|
|
},
|
|
|
|
this.companion
|
|
|
|
)
|
|
|
|
.then((onConnect: OnConnectInterface) => {
|
|
|
|
this.connection = onConnect.connection;
|
|
|
|
|
2021-07-06 17:13:08 +02:00
|
|
|
playersStore.connectToRoomConnection(this.connection);
|
|
|
|
|
2021-07-30 14:08:27 +02:00
|
|
|
userIsAdminStore.set(this.connection.hasTag("admin"));
|
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
this.connection.onUserJoins((message: MessageUserJoined) => {
|
|
|
|
const userMessage: AddPlayerInterface = {
|
|
|
|
userId: message.userId,
|
|
|
|
characterLayers: message.characterLayers,
|
|
|
|
name: message.name,
|
|
|
|
position: message.position,
|
|
|
|
visitCardUrl: message.visitCardUrl,
|
|
|
|
companion: message.companion,
|
2021-07-07 11:24:51 +02:00
|
|
|
userUuid: message.userUuid,
|
2021-06-29 18:39:43 +02:00
|
|
|
};
|
|
|
|
this.addPlayer(userMessage);
|
|
|
|
});
|
2020-10-06 18:09:23 +02:00
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
this.connection.onUserMoved((message: UserMovedMessage) => {
|
|
|
|
const position = message.getPosition();
|
|
|
|
if (position === undefined) {
|
|
|
|
throw new Error("Position missing from UserMovedMessage");
|
|
|
|
}
|
2020-10-06 18:09:23 +02:00
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
const messageUserMoved: MessageUserMovedInterface = {
|
|
|
|
userId: message.getUserid(),
|
|
|
|
position: ProtobufClientUtils.toPointInterface(position),
|
|
|
|
};
|
2020-10-06 18:09:23 +02:00
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
this.updatePlayerPosition(messageUserMoved);
|
|
|
|
});
|
2020-10-06 18:09:23 +02:00
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
this.connection.onUserLeft((userId: number) => {
|
|
|
|
this.removePlayer(userId);
|
|
|
|
});
|
2020-10-06 18:09:23 +02:00
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
this.connection.onGroupUpdatedOrCreated((groupPositionMessage: GroupCreatedUpdatedMessageInterface) => {
|
|
|
|
this.shareGroupPosition(groupPositionMessage);
|
|
|
|
});
|
2020-10-06 18:09:23 +02:00
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
this.connection.onGroupDeleted((groupId: number) => {
|
|
|
|
try {
|
|
|
|
this.deleteGroup(groupId);
|
|
|
|
} catch (e) {
|
|
|
|
console.error(e);
|
|
|
|
}
|
|
|
|
});
|
2020-10-06 18:09:23 +02:00
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
this.connection.onServerDisconnected(() => {
|
|
|
|
console.log("Player disconnected from server. Reloading scene.");
|
|
|
|
this.cleanupClosingScene();
|
2020-10-06 18:09:23 +02:00
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
const gameSceneKey = "somekey" + Math.round(Math.random() * 10000);
|
|
|
|
const game: Phaser.Scene = new GameScene(this.room, this.MapUrlFile, gameSceneKey);
|
|
|
|
this.scene.add(gameSceneKey, game, true, {
|
2020-10-06 18:09:23 +02:00
|
|
|
initPosition: {
|
|
|
|
x: this.CurrentPlayer.x,
|
2021-06-29 18:39:43 +02:00
|
|
|
y: this.CurrentPlayer.y,
|
2020-11-13 18:00:22 +01:00
|
|
|
},
|
2021-06-29 18:39:43 +02:00
|
|
|
reconnecting: true,
|
2020-10-06 18:09:23 +02:00
|
|
|
});
|
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
this.scene.stop(this.scene.key);
|
|
|
|
this.scene.remove(this.scene.key);
|
|
|
|
});
|
2020-10-16 19:13:26 +02:00
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
this.connection.onActionableEvent((message) => {
|
|
|
|
const item = this.actionableItems.get(message.itemId);
|
|
|
|
if (item === undefined) {
|
|
|
|
console.warn(
|
|
|
|
'Received an event about object "' +
|
|
|
|
message.itemId +
|
|
|
|
'" but cannot find this item on the map.'
|
|
|
|
);
|
|
|
|
return;
|
2020-10-06 18:09:23 +02:00
|
|
|
}
|
2021-06-29 18:39:43 +02:00
|
|
|
item.fire(message.event, message.state, message.parameters);
|
|
|
|
});
|
2020-10-06 18:09:23 +02:00
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
/**
|
|
|
|
* Triggered when we receive the JWT token to connect to Jitsi
|
|
|
|
*/
|
|
|
|
this.connection.onStartJitsiRoom((jwt, room) => {
|
|
|
|
this.startJitsi(room, jwt);
|
|
|
|
});
|
|
|
|
|
|
|
|
// When connection is performed, let's connect SimplePeer
|
|
|
|
this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic, this.playerName);
|
|
|
|
peerStore.connectToSimplePeer(this.simplePeer);
|
|
|
|
screenSharingPeerStore.connectToSimplePeer(this.simplePeer);
|
|
|
|
videoFocusStore.connectToSimplePeer(this.simplePeer);
|
|
|
|
userMessageManager.setReceiveBanListener(this.bannedUser.bind(this));
|
|
|
|
|
|
|
|
const self = this;
|
|
|
|
this.simplePeer.registerPeerConnectionListener({
|
|
|
|
onConnect(peer) {
|
2021-07-07 18:07:58 +02:00
|
|
|
//self.openChatIcon.setVisible(true);
|
2021-06-29 18:39:43 +02:00
|
|
|
audioManager.decreaseVolume();
|
|
|
|
},
|
|
|
|
onDisconnect(userId: number) {
|
|
|
|
if (self.simplePeer.getNbConnections() === 0) {
|
2021-07-07 18:07:58 +02:00
|
|
|
//self.openChatIcon.setVisible(false);
|
2021-06-29 18:39:43 +02:00
|
|
|
audioManager.restoreVolume();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
});
|
2020-10-06 18:09:23 +02:00
|
|
|
|
2021-06-29 18:39:43 +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: HasPlayerMovedEvent) => {
|
|
|
|
this.gameMap.setPosition(event.x, event.y);
|
|
|
|
});
|
2020-12-11 13:06:10 +01:00
|
|
|
|
2021-07-02 11:31:44 +02:00
|
|
|
// Set up variables manager
|
2021-07-16 11:22:36 +02:00
|
|
|
this.sharedVariablesManager = new SharedVariablesManager(
|
|
|
|
this.connection,
|
|
|
|
this.gameMap,
|
|
|
|
onConnect.room.variables
|
|
|
|
);
|
2021-07-02 11:31:44 +02:00
|
|
|
|
2021-06-29 18:39:43 +02: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:00:11 +01:00
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
this.scene.wake();
|
|
|
|
this.scene.stop(ReconnectingSceneName);
|
2020-12-03 16:39:44 +01:00
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
//init user position and play trigger to check layers properties
|
|
|
|
this.gameMap.setPosition(this.CurrentPlayer.x, this.CurrentPlayer.y);
|
|
|
|
});
|
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
|
2021-06-29 18:39:43 +02:00
|
|
|
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-06-29 18:39:43 +02:00
|
|
|
this.textures.remove("circleSprite-white");
|
2021-01-07 12:43:21 +01:00
|
|
|
}
|
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
circleElement = Object.values(this.textures.list).find((object: Texture) => object.key === "circleSprite-red");
|
2021-06-25 16:32:48 +02:00
|
|
|
if (circleElement) {
|
2021-06-29 18:39:43 +02:00
|
|
|
this.textures.remove("circleSprite-red");
|
2021-01-07 12:43:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//create white circle canvas use to create sprite
|
2021-06-29 18:39:43 +02:00
|
|
|
this.circleTexture = this.textures.createCanvas("circleSprite-white", 96, 96);
|
2021-01-07 12:43:21 +01:00
|
|
|
const context = this.circleTexture.context;
|
|
|
|
context.beginPath();
|
|
|
|
context.arc(48, 48, 48, 0, 2 * Math.PI, false);
|
|
|
|
// context.lineWidth = 5;
|
2021-06-29 18:39:43 +02:00
|
|
|
context.strokeStyle = "#ffffff";
|
2021-01-07 12:43:21 +01:00
|
|
|
context.stroke();
|
|
|
|
this.circleTexture.refresh();
|
|
|
|
|
|
|
|
//create red circle canvas use to create sprite
|
2021-06-29 18:39:43 +02:00
|
|
|
this.circleRedTexture = this.textures.createCanvas("circleSprite-red", 96, 96);
|
2021-01-07 12:43:21 +01:00
|
|
|
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-06-29 18:39:43 +02:00
|
|
|
contextRed.strokeStyle = "#ff0000";
|
2021-01-07 12:43:21 +01:00
|
|
|
contextRed.stroke();
|
|
|
|
this.circleRedTexture.refresh();
|
|
|
|
}
|
|
|
|
|
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-06-29 18:39:43 +02:00
|
|
|
return {};
|
2021-02-09 20:31:49 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-24 08:56:29 +02:00
|
|
|
private triggerOnMapLayerPropertyChange() {
|
2021-06-29 18:39:43 +02:00
|
|
|
this.gameMap.onPropertyChange("exitSceneUrl", (newValue, oldValue) => {
|
2021-07-13 19:09:07 +02:00
|
|
|
if (newValue)
|
|
|
|
this.onMapExit(
|
|
|
|
Room.getRoomPathFromExitSceneUrl(newValue as string, window.location.toString(), this.MapUrlFile)
|
|
|
|
);
|
2021-02-17 19:29:59 +01:00
|
|
|
});
|
2021-06-29 18:39:43 +02:00
|
|
|
this.gameMap.onPropertyChange("exitUrl", (newValue, oldValue) => {
|
2021-07-13 19:09:07 +02:00
|
|
|
if (newValue) this.onMapExit(Room.getRoomPathFromExitUrl(newValue as string, window.location.toString()));
|
2020-11-18 18:15:57 +01:00
|
|
|
});
|
2021-06-29 18:39:43 +02:00
|
|
|
this.gameMap.onPropertyChange("openWebsite", (newValue, oldValue, allProps) => {
|
2021-06-25 16:32:48 +02:00
|
|
|
if (newValue === undefined) {
|
2021-08-05 11:19:28 +02:00
|
|
|
layoutManagerActionStore.removeAction("openWebsite");
|
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-06-29 18:39:43 +02:00
|
|
|
coWebsiteManager.loadCoWebsite(
|
|
|
|
newValue as string,
|
|
|
|
this.MapUrlFile,
|
|
|
|
allProps.get("openWebsiteAllowApi") as boolean | undefined,
|
|
|
|
allProps.get("openWebsitePolicy") as string | undefined
|
|
|
|
);
|
2021-08-05 11:19:28 +02:00
|
|
|
layoutManagerActionStore.removeAction("openWebsite");
|
2020-11-21 15:40:24 +01:00
|
|
|
};
|
|
|
|
|
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-06-29 18:39:43 +02:00
|
|
|
message = "Press SPACE or touch here to open web site";
|
2021-01-25 12:21:40 +01:00
|
|
|
}
|
2021-08-03 11:13:08 +02:00
|
|
|
layoutManagerActionStore.addAction({
|
2021-08-05 12:07:12 +02:00
|
|
|
uuid: "openWebsite",
|
2021-08-05 11:19:28 +02:00
|
|
|
type: "message",
|
2021-08-03 11:13:08 +02:00
|
|
|
message: message,
|
|
|
|
callback: () => openWebsiteFunction(),
|
|
|
|
userInputManager: 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
|
|
|
}
|
|
|
|
});
|
2021-06-29 18:39:43 +02:00
|
|
|
this.gameMap.onPropertyChange("jitsiRoom", (newValue, oldValue, allProps) => {
|
2021-06-25 16:32:48 +02:00
|
|
|
if (newValue === undefined) {
|
2021-08-05 11:19:28 +02:00
|
|
|
layoutManagerActionStore.removeAction("jitsi");
|
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
|
|
|
}
|
2021-08-05 11:19:28 +02:00
|
|
|
layoutManagerActionStore.removeAction("jitsi");
|
2021-06-29 18:39:43 +02:00
|
|
|
};
|
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-06-29 18:39:43 +02:00
|
|
|
message = "Press SPACE or touch here to enter Jitsi Meet room";
|
2021-01-25 12:21:40 +01:00
|
|
|
}
|
2021-08-03 11:13:08 +02:00
|
|
|
layoutManagerActionStore.addAction({
|
2021-08-05 11:19:28 +02:00
|
|
|
uuid: "jitsi",
|
|
|
|
type: "message",
|
2021-08-03 11:13:08 +02:00
|
|
|
message: message,
|
|
|
|
callback: () => openJitsiRoomFunction(),
|
|
|
|
userInputManager: 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
|
|
|
});
|
2021-06-29 18:39:43 +02:00
|
|
|
this.gameMap.onPropertyChange("silent", (newValue, oldValue) => {
|
|
|
|
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-06-29 18:39:43 +02: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-06-29 18:39:43 +02: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-06-29 18:39:43 +02:00
|
|
|
this.gameMap.onPropertyChange("playAudioLoop", (newValue, oldValue) => {
|
|
|
|
newValue === undefined
|
|
|
|
? audioManager.unloadAudio()
|
|
|
|
: audioManager.playAudio(newValue, this.getMapDirUrl(), undefined, true);
|
2021-01-03 12:45:18 +01:00
|
|
|
});
|
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
this.gameMap.onPropertyChange("zone", (newValue, oldValue) => {
|
|
|
|
if (newValue === undefined || newValue === false || newValue === "") {
|
2021-03-08 18:57:59 +01:00
|
|
|
iframeListener.sendLeaveEvent(oldValue as string);
|
|
|
|
} 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 = [];
|
2021-06-29 18:39:43 +02:00
|
|
|
this.iframeSubscriptionList.push(
|
|
|
|
iframeListener.openPopupStream.subscribe((openPopupEvent) => {
|
|
|
|
let objectLayerSquare: ITiledMapObject;
|
|
|
|
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."
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const escapedMessage = HtmlUtils.escapeHtml(openPopupEvent.message);
|
2021-06-29 20:21:00 +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-06-29 18:39:43 +02:00
|
|
|
const buttonContainer = `<div class="buttonContainer"</div>`;
|
|
|
|
html += buttonContainer;
|
|
|
|
let id = 0;
|
|
|
|
for (const button of openPopupEvent.buttons) {
|
|
|
|
html += `<button type="button" class="nes-btn is-${HtmlUtils.escapeHtml(
|
|
|
|
button.className ?? ""
|
|
|
|
)}" id="popup-${openPopupEvent.popupId}-${id}">${HtmlUtils.escapeHtml(button.label)}</button>`;
|
|
|
|
id++;
|
2021-03-09 18:05:07 +01:00
|
|
|
}
|
2021-06-29 18:39:43 +02:00
|
|
|
html += "</div>";
|
|
|
|
const domElement = this.add.dom(objectLayerSquare.x, objectLayerSquare.y).createFromHTML(html);
|
2021-03-12 16:39:29 +01:00
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
const container: HTMLDivElement = domElement.getChildByID("container") as HTMLDivElement;
|
|
|
|
container.style.width = objectLayerSquare.width + "px";
|
|
|
|
domElement.scale = 0;
|
|
|
|
domElement.setClassName("popUpElement");
|
2021-03-09 16:21:14 +01:00
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
setTimeout(() => {
|
|
|
|
container.hidden = false;
|
|
|
|
}, 100);
|
|
|
|
|
|
|
|
id = 0;
|
|
|
|
for (const button of openPopupEvent.buttons) {
|
|
|
|
const button = HtmlUtils.getElementByIdOrFail<HTMLButtonElement>(
|
|
|
|
`popup-${openPopupEvent.popupId}-${id}`
|
|
|
|
);
|
|
|
|
const btnId = id;
|
|
|
|
button.onclick = () => {
|
2021-06-29 20:21:00 +02:00
|
|
|
let inputValue = '';
|
|
|
|
if(openPopupEvent.input) {
|
|
|
|
inputValue = HtmlUtils.getElementByIdOrFail<HTMLInputElement>(`popupinput-${openPopupEvent.popupId}`).value;
|
|
|
|
}
|
|
|
|
iframeListener.sendButtonClickedEvent(openPopupEvent.popupId, btnId, openPopupEvent.input, inputValue);
|
2021-06-29 18:39:43 +02:00
|
|
|
};
|
|
|
|
id++;
|
2021-03-09 18:05:07 +01:00
|
|
|
}
|
2021-06-29 18:39:43 +02:00
|
|
|
this.tweens.add({
|
|
|
|
targets: domElement,
|
|
|
|
scale: 1,
|
|
|
|
ease: "EaseOut",
|
|
|
|
duration: 400,
|
|
|
|
});
|
2021-03-09 16:21:14 +01:00
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
this.popUpElements.set(openPopupEvent.popupId, domElement);
|
|
|
|
})
|
|
|
|
);
|
2021-03-09 16:21:14 +01:00
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
this.iframeSubscriptionList.push(
|
|
|
|
iframeListener.closePopupStream.subscribe((closePopupEvent) => {
|
|
|
|
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-06-29 18:39:43 +02:00
|
|
|
this.tweens.add({
|
|
|
|
targets: popUpElement,
|
|
|
|
scale: 0,
|
|
|
|
ease: "EaseOut",
|
|
|
|
duration: 400,
|
|
|
|
onComplete: () => {
|
|
|
|
popUpElement?.destroy();
|
|
|
|
this.popUpElements.delete(closePopupEvent.popupId);
|
|
|
|
},
|
|
|
|
});
|
|
|
|
})
|
|
|
|
);
|
2021-03-12 16:39:29 +01:00
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
this.iframeSubscriptionList.push(
|
|
|
|
iframeListener.disablePlayerControlStream.subscribe(() => {
|
|
|
|
this.userInputManager.disableControls();
|
2021-04-21 11:20:17 +02:00
|
|
|
})
|
2021-06-29 18:39:43 +02:00
|
|
|
);
|
2021-04-19 10:19:30 +02:00
|
|
|
|
2021-06-29 18:39:43 +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-29 18:39:43 +02:00
|
|
|
this.iframeSubscriptionList.push(
|
|
|
|
iframeListener.stopSoundStream.subscribe((stopSoundEvent) => {
|
|
|
|
const url = new URL(stopSoundEvent.url, this.MapUrlFile);
|
|
|
|
soundManager.stopSound(this.sound, url.toString());
|
|
|
|
})
|
|
|
|
);
|
2021-04-23 15:35:34 +02:00
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
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-23 15:35:34 +02:00
|
|
|
|
2021-06-25 18:14:40 +02:00
|
|
|
this.iframeSubscriptionList.push(
|
|
|
|
iframeListener.enablePlayerControlStream.subscribe(() => {
|
2021-06-29 18:39:43 +02:00
|
|
|
this.userInputManager.restoreControls();
|
2021-04-21 11:20:17 +02:00
|
|
|
})
|
2021-06-29 18:39:43 +02:00
|
|
|
);
|
|
|
|
this.iframeSubscriptionList.push(
|
|
|
|
iframeListener.loadPageStream.subscribe((url: string) => {
|
2021-07-13 19:09:07 +02:00
|
|
|
this.loadNextGameFromExitUrl(url).then(() => {
|
2021-06-29 18:39:43 +02:00
|
|
|
this.events.once(EVENT_TYPE.POST_UPDATE, () => {
|
2021-07-13 19:09:07 +02:00
|
|
|
this.onMapExit(Room.getRoomPathFromExitUrl(url, window.location.toString()));
|
2021-06-29 18:39:43 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
})
|
|
|
|
);
|
2021-06-24 08:56:29 +02:00
|
|
|
let scriptedBubbleSprite: Sprite;
|
2021-06-29 18:39:43 +02:00
|
|
|
this.iframeSubscriptionList.push(
|
|
|
|
iframeListener.displayBubbleStream.subscribe(() => {
|
|
|
|
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
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
this.iframeSubscriptionList.push(
|
|
|
|
iframeListener.removeBubbleStream.subscribe(() => {
|
|
|
|
scriptedBubbleSprite.destroy();
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
this.iframeSubscriptionList.push(
|
|
|
|
iframeListener.showLayerStream.subscribe((layerEvent) => {
|
|
|
|
this.setLayerVisibility(layerEvent.name, true);
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
this.iframeSubscriptionList.push(
|
|
|
|
iframeListener.hideLayerStream.subscribe((layerEvent) => {
|
|
|
|
this.setLayerVisibility(layerEvent.name, false);
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
this.iframeSubscriptionList.push(
|
|
|
|
iframeListener.setPropertyStream.subscribe((setProperty) => {
|
|
|
|
this.setPropertyLayer(setProperty.layerName, setProperty.propertyName, setProperty.propertyValue);
|
2021-05-25 17:21:02 +02:00
|
|
|
})
|
2021-06-29 18:39:43 +02:00
|
|
|
);
|
2021-05-20 10:57:36 +02:00
|
|
|
|
2021-07-06 17:24:16 +02:00
|
|
|
iframeListener.registerAnswerer("getMapData", () => {
|
2021-07-05 11:53:33 +02:00
|
|
|
return {
|
2021-07-06 17:24:16 +02:00
|
|
|
data: this.gameMap.getMap(),
|
|
|
|
};
|
2021-07-05 11:53:33 +02:00
|
|
|
});
|
2021-06-29 18:39:43 +02:00
|
|
|
|
2021-07-06 17:24:16 +02:00
|
|
|
iframeListener.registerAnswerer("getState", async () => {
|
2021-07-05 11:53:33 +02:00
|
|
|
// The sharedVariablesManager is not instantiated before the connection is established. So we need to wait
|
|
|
|
// for the connection to send back the answer.
|
|
|
|
await this.connectionAnswerPromise;
|
2021-07-02 17:26:28 +02:00
|
|
|
return {
|
2021-07-02 16:41:24 +02:00
|
|
|
mapUrl: this.MapUrlFile,
|
|
|
|
startLayerName: this.startPositionCalculator.startLayerName,
|
|
|
|
uuid: localUserStore.getLocalUser()?.uuid,
|
2021-07-05 11:53:33 +02:00
|
|
|
nickname: this.playerName,
|
2021-07-13 19:09:07 +02:00
|
|
|
roomId: this.roomUrl,
|
2021-07-02 16:41:24 +02:00
|
|
|
tags: this.connection ? this.connection.getAllTags() : [],
|
2021-07-05 11:53:33 +02:00
|
|
|
variables: this.sharedVariablesManager.variables,
|
2021-07-02 17:26:28 +02:00
|
|
|
};
|
2021-07-02 16:41:24 +02:00
|
|
|
});
|
2021-06-29 18:39:43 +02:00
|
|
|
this.iframeSubscriptionList.push(
|
2021-07-02 14:35:28 +02:00
|
|
|
iframeListener.setTilesStream.subscribe((eventTiles) => {
|
|
|
|
for (const eventTile of eventTiles) {
|
|
|
|
this.gameMap.putTile(eventTile.tile, eventTile.x, eventTile.y, eventTile.layer);
|
|
|
|
}
|
2021-07-29 14:14:40 +02:00
|
|
|
this.markDirty();
|
2021-06-29 18:39:43 +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-07-01 13:48:03 +02:00
|
|
|
|
2021-06-28 14:58:49 +02:00
|
|
|
this.iframeSubscriptionList.push(iframeListener.setTilesStream.subscribe((eventTiles) => {
|
2021-06-24 11:31:29 +02:00
|
|
|
for (const eventTile of eventTiles) {
|
2021-06-28 09:33:13 +02:00
|
|
|
this.gameMap.putTile(eventTile.tile, eventTile.x, eventTile.y, eventTile.layer);
|
2021-06-24 11:31:29 +02:00
|
|
|
}
|
|
|
|
}))
|
2021-05-20 10:57:36 +02:00
|
|
|
|
2021-07-28 18:03:19 +02:00
|
|
|
iframeListener.registerAnswerer("loadTileset", (eventTileset) => {
|
2021-07-29 15:53:27 +02:00
|
|
|
return this.connectionAnswerPromise.then(() => {
|
|
|
|
const jsonTilesetDir = eventTileset.url.substr(0, eventTileset.url.lastIndexOf("/"));
|
|
|
|
//Initialise the firstgid to 1 because if there is no tileset in the tilemap, the firstgid will be 1
|
|
|
|
let newFirstgid = 1;
|
|
|
|
const lastTileset = this.mapFile.tilesets[this.mapFile.tilesets.length - 1];
|
|
|
|
if (lastTileset) {
|
|
|
|
//If there is at least one tileset in the tilemap then calculate the firstgid of the new tileset
|
|
|
|
newFirstgid = lastTileset.firstgid + lastTileset.tilecount;
|
|
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
this.load.on("filecomplete-json-" + eventTileset.url, () => {
|
|
|
|
let jsonTileset = this.cache.json.get(eventTileset.url);
|
2021-07-29 17:56:28 +02:00
|
|
|
const imageUrl = jsonTilesetDir + "/" + jsonTileset.image;
|
|
|
|
this.load.image(imageUrl, imageUrl);
|
|
|
|
this.load.on("filecomplete-image-" + imageUrl, () => {
|
2021-07-29 15:53:27 +02:00
|
|
|
//Add the firstgid of the tileset to the json file
|
|
|
|
jsonTileset = { ...jsonTileset, firstgid: newFirstgid };
|
|
|
|
this.mapFile.tilesets.push(jsonTileset);
|
|
|
|
this.Map.tilesets.push(
|
|
|
|
new Tileset(
|
|
|
|
jsonTileset.name,
|
|
|
|
jsonTileset.firstgid,
|
|
|
|
jsonTileset.tileWidth,
|
|
|
|
jsonTileset.tileHeight,
|
|
|
|
jsonTileset.margin,
|
|
|
|
jsonTileset.spacing,
|
|
|
|
jsonTileset.tiles
|
|
|
|
)
|
|
|
|
);
|
|
|
|
this.Terrains.push(
|
|
|
|
this.Map.addTilesetImage(
|
|
|
|
jsonTileset.name,
|
2021-07-29 17:56:28 +02:00
|
|
|
imageUrl,
|
2021-07-29 15:53:27 +02:00
|
|
|
jsonTileset.tilewidth,
|
|
|
|
jsonTileset.tileheight,
|
|
|
|
jsonTileset.margin,
|
|
|
|
jsonTileset.spacing
|
|
|
|
)
|
|
|
|
);
|
|
|
|
//destroy the tilemapayer because they are unique and we need to reuse their key and layerdData
|
|
|
|
for (const layer of this.Map.layers) {
|
|
|
|
layer.tilemapLayer.destroy(false);
|
|
|
|
}
|
|
|
|
//Create a new GameMap with the changed file
|
|
|
|
this.gameMap = new GameMap(this.mapFile, this.Map, this.Terrains);
|
|
|
|
//Destroy the colliders of the old tilemapLayer
|
|
|
|
this.physics.add.world.colliders.destroy();
|
|
|
|
//Create new colliders with the new GameMap
|
|
|
|
this.createCollisionWithPlayer();
|
|
|
|
//Create new trigger with the new GameMap
|
|
|
|
this.triggerOnMapLayerPropertyChange();
|
|
|
|
resolve(newFirstgid);
|
|
|
|
});
|
2021-07-28 18:03:19 +02:00
|
|
|
});
|
2021-07-29 15:53:27 +02:00
|
|
|
this.load.on("loaderror", () => {
|
|
|
|
console.error("Error while loading " + eventTileset.url + ".");
|
|
|
|
reject(-1);
|
|
|
|
});
|
|
|
|
|
|
|
|
this.load.json(eventTileset.url, eventTileset.url);
|
|
|
|
this.load.start();
|
2021-07-28 18:03:19 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2021-05-20 10:57:36 +02:00
|
|
|
|
2021-08-05 12:02:00 +02:00
|
|
|
iframeListener.registerAnswerer("triggerActionMessage", (message) =>
|
|
|
|
layoutManagerActionStore.addAction({
|
|
|
|
uuid: message.uuid,
|
|
|
|
type: "message",
|
|
|
|
message: message.message,
|
|
|
|
callback: () => {
|
|
|
|
layoutManagerActionStore.removeAction(message.uuid);
|
|
|
|
iframeListener.sendActionMessageTriggered(message.uuid);
|
|
|
|
},
|
|
|
|
userInputManager: this.userInputManager,
|
|
|
|
})
|
2021-07-02 18:49:22 +02:00
|
|
|
);
|
2021-06-23 17:32:32 +02:00
|
|
|
|
2021-08-04 19:31:17 +02:00
|
|
|
iframeListener.registerAnswerer("removeActionMessage", (message) => {
|
2021-08-05 11:19:28 +02:00
|
|
|
layoutManagerActionStore.removeAction(message.uuid);
|
2021-07-23 16:41:38 +02:00
|
|
|
});
|
2021-08-03 18:29:10 +02:00
|
|
|
|
|
|
|
this.iframeSubscriptionList.push(
|
|
|
|
iframeListener.modifyEmbeddedWebsiteStream.subscribe((embeddedWebsite) => {
|
|
|
|
// TODO
|
|
|
|
// TODO
|
|
|
|
// TODO
|
|
|
|
// TODO
|
|
|
|
// TODO
|
|
|
|
// TODO
|
|
|
|
// TODO
|
|
|
|
// TODO
|
|
|
|
// TODO
|
|
|
|
// TODO
|
|
|
|
// TODO
|
|
|
|
// TODO
|
|
|
|
// TODO
|
|
|
|
// TODO
|
|
|
|
// TODO
|
|
|
|
// TODO
|
|
|
|
})
|
|
|
|
);
|
2021-05-12 14:30:12 +02:00
|
|
|
}
|
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
private setPropertyLayer(
|
|
|
|
layerName: string,
|
|
|
|
propertyName: string,
|
|
|
|
propertyValue: string | number | boolean | undefined
|
|
|
|
): void {
|
2021-05-12 14:30:12 +02:00
|
|
|
const layer = this.gameMap.findLayer(layerName);
|
2021-06-29 18:39:43 +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-07-07 14:59:40 +02:00
|
|
|
if (propertyName === "exitUrl" && typeof propertyValue === "string") {
|
2021-07-13 19:09:07 +02:00
|
|
|
this.loadNextGameFromExitUrl(propertyValue);
|
2021-07-07 14:59:40 +02:00
|
|
|
}
|
2021-07-02 14:40:18 +02:00
|
|
|
if (layer.properties === undefined) {
|
2021-06-29 18:39:43 +02:00
|
|
|
layer.properties = [];
|
2021-07-02 14:40:18 +02:00
|
|
|
}
|
2021-07-02 14:45:27 +02:00
|
|
|
const property = layer.properties.find((property) => property.name === propertyName);
|
2021-06-25 18:14:40 +02:00
|
|
|
if (property === undefined) {
|
2021-07-07 14:59:40 +02:00
|
|
|
if (propertyValue === undefined) {
|
|
|
|
return;
|
|
|
|
}
|
2021-06-29 18:39:43 +02:00
|
|
|
layer.properties.push({ name: propertyName, type: typeof propertyValue, value: propertyValue });
|
|
|
|
return;
|
|
|
|
}
|
2021-07-07 14:59:40 +02:00
|
|
|
if (propertyValue === undefined) {
|
|
|
|
const index = layer.properties.indexOf(property);
|
|
|
|
layer.properties.splice(index, 1);
|
|
|
|
}
|
2021-06-29 18:39:43 +02:00
|
|
|
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-07-07 16:58:54 +02:00
|
|
|
if (phaserLayer != undefined) {
|
|
|
|
phaserLayer.setVisible(visible);
|
|
|
|
phaserLayer.setCollisionByProperty({ collides: true }, visible);
|
|
|
|
} else {
|
|
|
|
const phaserLayers = this.gameMap.findPhaserLayers(layerName + "/");
|
2021-07-07 14:26:53 +02:00
|
|
|
if (phaserLayers === []) {
|
|
|
|
console.warn(
|
|
|
|
'Could not find layer with name that contains "' +
|
|
|
|
layerName +
|
|
|
|
'" when calling WA.hideLayer / WA.showLayer'
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
for (let i = 0; i < phaserLayers.length; i++) {
|
|
|
|
phaserLayers[i].setVisible(visible);
|
|
|
|
phaserLayers[i].setCollisionByProperty({ collides: true }, visible);
|
|
|
|
}
|
2021-05-10 11:20:07 +02:00
|
|
|
}
|
2021-07-07 14:26:53 +02:00
|
|
|
this.markDirty();
|
2021-05-10 11:20:07 +02:00
|
|
|
}
|
|
|
|
|
2021-03-05 18:25:27 +01:00
|
|
|
private getMapDirUrl(): string {
|
2021-06-29 18:39:43 +02:00
|
|
|
return this.MapUrlFile.substr(0, this.MapUrlFile.lastIndexOf("/"));
|
2021-03-05 18:25:27 +01:00
|
|
|
}
|
|
|
|
|
2021-07-13 19:09:07 +02:00
|
|
|
private async onMapExit(roomUrl: URL) {
|
2021-06-25 16:32:48 +02:00
|
|
|
if (this.mapTransitioning) return;
|
2021-05-10 19:13:53 +02:00
|
|
|
this.mapTransitioning = true;
|
2021-07-13 19:09:07 +02:00
|
|
|
|
|
|
|
let targetRoom: Room;
|
|
|
|
try {
|
|
|
|
targetRoom = await Room.createRoom(roomUrl);
|
2021-08-03 18:29:10 +02:00
|
|
|
} catch (e /*: unknown*/) {
|
2021-07-13 19:09:07 +02:00
|
|
|
console.error('Error while fetching new room "' + roomUrl.toString() + '"', e);
|
|
|
|
this.mapTransitioning = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (roomUrl.hash) {
|
|
|
|
urlManager.pushStartLayerNameToUrl(roomUrl.hash);
|
2021-06-23 12:42:24 +02:00
|
|
|
}
|
2021-07-13 19:09:07 +02:00
|
|
|
|
2021-06-29 18:39:43 +02:00
|
|
|
const menuScene: MenuScene = this.scene.get(MenuSceneName) as MenuScene;
|
|
|
|
menuScene.reset();
|
2021-07-13 19:09:07 +02:00
|
|
|
|
|
|
|
if (!targetRoom.isEqual(this.room)) {
|
|
|
|
if (this.scene.get(targetRoom.key) === null) {
|
|
|
|
console.error("next room not loaded", targetRoom.key);
|
2021-02-13 14:15:09 +01:00
|
|
|
return;
|
|
|
|
}
|
2020-12-04 11:30:35 +01:00
|
|
|
this.cleanupClosingScene();
|
2020-11-18 18:15:57 +01:00
|
|
|
this.scene.stop();
|
2021-07-13 19:09:07 +02:00
|
|
|
this.scene.start(targetRoom.key);
|
2020-11-18 18:15:57 +01:00
|
|
|
this.scene.remove(this.scene.key);
|
|
|
|
} else {
|
|
|
|
//if the exit points to the current map, we simply teleport the user back to the startLayer
|
2021-07-13 19:09:07 +02:00
|
|
|
this.startPositionCalculator.initPositionFromLayerName(roomUrl.hash, roomUrl.hash);
|
2021-06-25 18:03:43 +02:00
|
|
|
this.CurrentPlayer.x = this.startPositionCalculator.startPosition.x;
|
|
|
|
this.CurrentPlayer.y = this.startPositionCalculator.startPosition.y;
|
2021-06-29 18:39:43 +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-07-13 11:00:32 +02:00
|
|
|
this.chatVisibilityUnsubscribe();
|
2021-06-24 17:02:57 +02:00
|
|
|
this.biggestAvailableAreaStoreUnsubscribe();
|
2021-07-07 11:24:51 +02:00
|
|
|
iframeListener.unregisterAnswerer("getState");
|
2021-07-28 18:03:19 +02:00
|
|
|
iframeListener.unregisterAnswerer("loadTileset");
|
2021-07-23 16:41:38 +02:00
|
|
|
iframeListener.unregisterAnswerer("getMapData");
|
2021-07-23 14:59:56 +02:00
|
|
|
iframeListener.unregisterAnswerer("getState");
|
2021-08-04 19:31:17 +02:00
|
|
|
iframeListener.unregisterAnswerer("triggerActionMessage");
|
|
|
|
iframeListener.unregisterAnswerer("removeActionMessage");
|
2021-07-02 11:31:44 +02:00
|
|
|
this.sharedVariablesManager?.close();
|
2021-08-03 18:29:10 +02:00
|
|
|
this.embeddedWebsiteManager?.close();
|
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>();
|
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2021-03-07 21:02:38 +01:00
|
|
|
private getScriptUrls(map: ITiledMap): string[] {
|
2021-06-29 18:39:43 +02:00
|
|
|
return (this.getProperties(map, "script") as string[]).map((script) =>
|
|
|
|
new URL(script, this.MapUrlFile).toString()
|
|
|
|
);
|
2021-03-07 21:02:38 +01:00
|
|
|
}
|
|
|
|
|
2021-06-24 08:56:29 +02:00
|
|
|
private getProperty(layer: ITiledMapLayer | ITiledMap, name: string): string | boolean | number | undefined {
|
2021-07-05 17:25:23 +02:00
|
|
|
const properties: ITiledMapProperty[] | undefined = layer.properties;
|
2021-06-25 16:32:48 +02:00
|
|
|
if (!properties) {
|
2020-05-23 17:27:49 +02:00
|
|
|
return undefined;
|
|
|
|
}
|
2021-06-29 18:39:43 +02:00
|
|
|
const obj = properties.find(
|
2021-07-05 17:25:23 +02:00
|
|
|
(property: ITiledMapProperty) => property.name.toLowerCase() === name.toLowerCase()
|
2021-06-29 18:39:43 +02:00
|
|
|
);
|
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)[] {
|
2021-07-05 17:25:23 +02:00
|
|
|
const properties: ITiledMapProperty[] | undefined = layer.properties;
|
2021-06-25 16:32:48 +02:00
|
|
|
if (!properties) {
|
2021-03-07 21:02:38 +01:00
|
|
|
return [];
|
|
|
|
}
|
2021-06-29 18:39:43 +02:00
|
|
|
return properties
|
2021-07-05 17:25:23 +02:00
|
|
|
.filter((property: ITiledMapProperty) => property.name.toLowerCase() === name.toLowerCase())
|
2021-06-29 18:39:43 +02:00
|
|
|
.map((property) => property.value);
|
2021-03-07 21:02:38 +01:00
|
|
|
}
|
|
|
|
|
2021-07-13 19:09:07 +02:00
|
|
|
private loadNextGameFromExitUrl(exitUrl: string): Promise<void> {
|
|
|
|
return this.loadNextGame(Room.getRoomPathFromExitUrl(exitUrl, window.location.toString()));
|
|
|
|
}
|
|
|
|
|
2020-10-15 15:50:51 +02:00
|
|
|
//todo: push that into the gameManager
|
2021-07-13 19:09:07 +02:00
|
|
|
private async loadNextGame(exitRoomPath: URL): Promise<void> {
|
|
|
|
try {
|
|
|
|
const room = await Room.createRoom(exitRoomPath);
|
|
|
|
return gameManager.loadMap(room, this.scene);
|
2021-08-03 18:29:10 +02:00
|
|
|
} catch (e /*: unknown*/) {
|
2021-07-13 19:09:07 +02:00
|
|
|
console.warn('Error while pre-loading exit room "' + exitRoomPath.toString() + '"', e);
|
|
|
|
}
|
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)
|
|
|
|
});
|
2021-06-29 18:39:43 +02:00
|
|
|
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,
|
2021-06-29 18:39:43 +02:00
|
|
|
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,
|
2021-06-25 18:03:43 +02:00
|
|
|
this.startPositionCalculator.startPosition.x,
|
|
|
|
this.startPositionCalculator.startPosition.y,
|
2020-12-18 14:30:46 +01:00
|
|
|
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-29 18:39:43 +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
|
|
|
|
2021-07-21 18:54:43 +02:00
|
|
|
this.CurrentPlayer.openOrCloseEmoteMenu();
|
2021-05-10 17:10:41 +02:00
|
|
|
})
|
|
|
|
this.CurrentPlayer.on(requestEmoteEventName, (emoteKey: string) => {
|
|
|
|
this.connection?.emitEmoteEvent(emoteKey);
|
2021-06-29 18:39:43 +02:00
|
|
|
});
|
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",
|
2021-06-29 18:39:43 +02:00
|
|
|
event: usersPosition,
|
2020-06-19 18:18:43 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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",
|
2021-06-29 18:39:43 +02:00
|
|
|
event: addPlayerData,
|
2020-06-19 18:18:43 +02:00
|
|
|
});
|
|
|
|
}
|
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,
|
2021-06-29 18:39:43 +02:00
|
|
|
position: addPlayerData.position,
|
2020-05-23 14:00:36 +02:00
|
|
|
});
|
|
|
|
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",
|
2021-06-29 18:39:43 +02:00
|
|
|
userId,
|
2020-06-19 18:18:43 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
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) {
|
2021-06-29 18:39:43 +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",
|
2021-06-29 18:39:43 +02:00
|
|
|
event: message,
|
2020-06-19 18:18:43 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
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-29 18:39:43 +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",
|
2021-06-29 18:39:43 +02:00
|
|
|
event: groupPositionMessage,
|
2020-06-19 18:18:43 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
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-06-29 18:39:43 +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",
|
2021-06-29 18:39:43 +02:00
|
|
|
groupId,
|
2020-06-19 18:18:43 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
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-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) {
|
2021-06-29 18:39:43 +02:00
|
|
|
if (layer.type === "objectgroup" && layer.name === "floorLayer") {
|
2021-06-25 16:32:48 +02:00
|
|
|
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-06-29 18:39:43 +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-29 18:39:43 +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-29 18:39:43 +02:00
|
|
|
const jitsiConfig = this.safeParseJSONstring(allProps.get("jitsiConfig") as string | undefined, "jitsiConfig");
|
|
|
|
const jitsiInterfaceConfig = this.safeParseJSONstring(
|
|
|
|
allProps.get("jitsiInterfaceConfig") as string | undefined,
|
|
|
|
"jitsiInterfaceConfig"
|
|
|
|
);
|
2021-06-24 08:56:29 +02: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);
|
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-29 18:39:43 +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, {
|
2021-06-29 18:39:43 +02:00
|
|
|
title: "Banned",
|
|
|
|
subTitle: "You were banned from WorkAdventure",
|
2021-07-19 17:05:23 +02:00
|
|
|
message: "If you want more information, you may contact us at: hello@workadventu.re",
|
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, {
|
2021-06-29 18:39:43 +02:00
|
|
|
title: "Connection rejected",
|
|
|
|
subTitle: "The world you are trying to join is full. Try again later.",
|
2021-07-19 17:05:23 +02:00
|
|
|
message: "If you want more information, you may contact us at: hello@workadventu.re",
|
2021-04-29 23:47:30 +02:00
|
|
|
});
|
2021-06-24 08:56:29 +02:00
|
|
|
} else {
|
2021-04-29 23:47:30 +02:00
|
|
|
this.scene.start(ErrorSceneName, {
|
2021-06-29 18:39:43 +02:00
|
|
|
title: "Connection rejected",
|
|
|
|
subTitle: "You cannot join the World. Try again later. \n\r \n\r Error: " + message + ".",
|
|
|
|
message:
|
2021-07-19 17:05:23 +02:00
|
|
|
"If you want more information, you may contact administrator or contact us at: hello@workadventu.re",
|
2021-04-29 23:47:30 +02:00
|
|
|
});
|
|
|
|
}
|
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
|
|
|
}
|