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

This commit is contained in:
_Bastler
2022-03-22 08:37:18 +01:00
70 changed files with 608 additions and 1028 deletions
@@ -11,6 +11,8 @@
import { HtmlUtils } from "../../WebRtc/HtmlUtils";
import { i18nJson } from "../../i18n/locales";
import uploadFile from "../images/jitsi.png";
export let index: number;
export let coWebsite: CoWebsite;
export let vertical: boolean;
@@ -24,7 +26,7 @@
onMount(() => {
const fallback = location.protocol + "%2F%2F" + location.host + "%2Fresources%2Ficons%2Fwebsite.png";
icon.src = isJitsi
? "/resources/logos/jitsi.png"
? uploadFile
: `${ICON_URL}/icon?url=${coWebsite.getUrl().hostname}&size=64..96..256&fallback_icon_color=14304c&fallback_icon_url=${fallback}`;
icon.alt = coWebsite.getUrl().hostname;
icon.onload = () => {
@@ -269,6 +271,10 @@
.cowebsite-thumbnail-hint {
display: inline-block;
}
&.hide {
display: none;
}
}
}
</style>
@@ -1,8 +1,6 @@
<script lang="ts">
import { fly } from "svelte/transition";
import { helpCameraSettingsVisibleStore } from "../../Stores/HelpCameraSettingsStore";
import firefoxImg from "./images/help-setting-camera-permission-firefox.png";
import chromeImg from "./images/help-setting-camera-permission-chrome.png";
import { getNavigatorType, isAndroid as isAndroidFct, NavigatorType } from "../../WebRtc/DeviceUtils";
import LL from "../../i18n/i18n-svelte";
@@ -33,9 +31,9 @@
<p class="err">
{$LL.camera.help.firefoxContent()}
</p>
<img src={firefoxImg} alt="" />
<img src={$LL.camera.help.screen.firefox()} alt="" />
{:else if isChrome && !isAndroid}
<img src={chromeImg} alt="" />
<img src={$LL.camera.help.screen.chrome()} alt="" />
{/if}
</p>
</section>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

+14 -1
View File
@@ -3,6 +3,7 @@
import { LoginScene, LoginSceneName } from "../../Phaser/Login/LoginScene";
import { DISPLAY_TERMS_OF_USE, MAX_USERNAME_LENGTH } from "../../Enum/EnvironmentVariable";
import logoImg from "../images/logo.png";
import poweredByWorkAdventureImg from "../images/Powered_By_WorkAdventure_Big.png";
import { gameManager } from "../../Phaser/Game/GameManager";
import LL from "../../i18n/i18n-svelte";
@@ -13,6 +14,8 @@
let name = gameManager.getPlayerName() || "";
let startValidating = false;
let logo = gameManager.currentStartedRoom.loginSceneLogo ?? logoImg;
function submit() {
startValidating = true;
@@ -25,7 +28,7 @@
<form class="loginScene" on:submit|preventDefault={submit}>
<section class="text-center">
<img src={logoImg} alt="WorkAdventure logo" />
<img src={logo} alt="" />
</section>
<section class="text-center">
<h2>{$LL.login.input.name.placeholder()}</h2>
@@ -60,6 +63,11 @@
<section class="action">
<button type="submit" class="nes-btn is-primary loginSceneFormSubmit">{$LL.login.continue()}</button>
</section>
{#if logo !== logoImg}
<section class="text-center powered-by">
<img src={poweredByWorkAdventureImg} alt="Powered by WorkAdventure" />
</section>
{/if}
</form>
<style lang="scss">
@@ -132,6 +140,11 @@
width: 100%;
margin: 20px 0;
}
&.powered-by {
position: fixed;
bottom: 0;
}
}
}
</style>
Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

+12
View File
@@ -31,6 +31,8 @@ export class Room {
private _group: string | null = null;
private _expireOn: Date | undefined;
private _canReport: boolean = false;
private _loadingLogo: string | undefined;
private _loginSceneLogo: string | undefined;
private constructor(private roomUrl: URL) {
this.id = roomUrl.pathname;
@@ -126,6 +128,8 @@ export class Room {
this._expireOn = new Date(data.expireOn);
}
this._canReport = data.canReport ?? false;
this._loadingLogo = data.loadingLogo ?? undefined;
this._loginSceneLogo = data.loginSceneLogo ?? undefined;
return new MapDetail(data.mapUrl);
} else {
throw new Error("Data received by the /map endpoint of the Pusher is not in a valid format.");
@@ -234,4 +238,12 @@ export class Room {
get canReport(): boolean {
return this._canReport;
}
get loadingLogo(): string | undefined {
return this._loadingLogo;
}
get loginSceneLogo(): string | undefined {
return this._loginSceneLogo;
}
}
+2
View File
@@ -349,7 +349,9 @@ export class RoomConnection implements RoomConnection {
break;
}
}
if (this.closed) {
this.closeConnection();
break;
}
+71 -39
View File
@@ -1,10 +1,12 @@
import ImageFrameConfig = Phaser.Types.Loader.FileTypes.ImageFrameConfig;
import { DirtyScene } from "../Game/DirtyScene";
import { gameManager } from "../Game/GameManager";
import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin";
import CancelablePromise from "cancelable-promise";
import Image = Phaser.GameObjects.Image;
import Texture = Phaser.Textures.Texture;
const LogoNameIndex: string = "logoLoading";
const TextName: string = "Loading...";
const LogoResource: string = "static/images/logo.png";
const LogoFrame: ImageFrameConfig = { frameWidth: 310, frameHeight: 60 };
const loadingBarHeight: number = 16;
const padding: number = 5;
@@ -14,9 +16,15 @@ export class Loader {
private progress!: Phaser.GameObjects.Graphics;
private progressAmount: number = 0;
private logo: Phaser.GameObjects.Image | undefined;
private logoPoweredBy: Phaser.GameObjects.Image | undefined;
private poweredByLogo: Phaser.GameObjects.Image | undefined;
private loadingText: Phaser.GameObjects.Text | null = null;
private logoNameIndex!: string;
private superLoad: SuperLoaderPlugin;
public constructor(private scene: Phaser.Scene) {}
public constructor(private scene: Phaser.Scene) {
this.superLoad = new SuperLoaderPlugin(scene);
}
public addLoader(): void {
// If there is nothing to load, do not display the loader.
@@ -24,43 +32,53 @@ export class Loader {
return;
}
const logoResource = gameManager.currentStartedRoom.loadingLogo ?? "static/images/logo.png";
this.logoNameIndex = "logoLoading" + logoResource;
const loadingBarWidth: number = Math.floor(this.scene.game.renderer.width / 3);
const promiseLoadLogoTexture = new Promise<Phaser.GameObjects.Image>((res) => {
if (this.scene.load.textureManager.exists(LogoNameIndex)) {
return res(
(this.logo = this.scene.add.image(
this.scene.game.renderer.width / 2,
this.scene.game.renderer.height / 2 - 150,
LogoNameIndex
))
);
} else {
//add loading if logo image is not ready
this.loadingText = this.scene.add.text(
//add loading if logo image until logo image is ready
this.loadingText = this.scene.add.text(
this.scene.game.renderer.width / 2,
this.scene.game.renderer.height / 2 - 50,
TextName
);
const logoPromise = this.superLoad.image(this.logoNameIndex, logoResource);
logoPromise
.then((texture) => {
this.logo = this.scene.add.image(
this.scene.game.renderer.width / 2,
this.scene.game.renderer.height / 2 - 50,
TextName
this.scene.game.renderer.height / 2 - 150,
texture
);
}
this.scene.load.spritesheet(LogoNameIndex, LogoResource, LogoFrame);
this.scene.load.once(`filecomplete-spritesheet-${LogoNameIndex}`, () => {
if (this.loadingText) {
this.loadingText.destroy();
}
return res(
(this.logo = this.scene.add.image(
this.loadingText?.destroy();
})
.catch((e) => console.warn("Could not load logo: ", logoResource, e));
let poweredByLogoPromise: CancelablePromise<Texture> | undefined;
if (gameManager.currentStartedRoom.loadingLogo) {
poweredByLogoPromise = this.superLoad.image(
"poweredByLogo",
"static/images/Powered_By_WorkAdventure_Small.png"
);
poweredByLogoPromise
.then((texture) => {
this.poweredByLogo = this.scene.add.image(
this.scene.game.renderer.width / 2,
this.scene.game.renderer.height / 2 - 150,
LogoNameIndex
))
this.scene.game.renderer.height - 50,
texture
);
})
.catch((e) =>
console.warn('Could not load image "static/images/Powered_By_WorkAdventure_Small.png"', e)
);
});
});
}
this.progressContainer = this.scene.add.graphics();
this.progress = this.scene.add.graphics();
this.progressContainer.fillStyle(0x444444, 0.8);
this.progress = this.scene.add.graphics();
this.resize();
@@ -68,26 +86,35 @@ export class Loader {
this.progressAmount = value;
this.drawProgress();
});
const resizeFunction = this.resize.bind(this);
this.scene.scale.on(Phaser.Scale.Events.RESIZE, resizeFunction);
this.scene.load.on("complete", () => {
if (this.loadingText) {
this.loadingText.destroy();
}
promiseLoadLogoTexture
.then((resLoadingImage: Phaser.GameObjects.Image) => {
resLoadingImage.destroy();
})
.catch((e) => console.error(e));
logoPromise.cancel();
poweredByLogoPromise?.cancel();
this.logo?.destroy();
this.poweredByLogo?.destroy();
this.progress.destroy();
this.progressContainer.destroy();
if (this.scene instanceof DirtyScene) {
this.scene.markDirty();
}
this.scene.scale.off(Phaser.Scale.Events.RESIZE, resizeFunction);
});
}
public removeLoader(): void {
if (this.scene.load.textureManager.exists(LogoNameIndex)) {
this.scene.load.textureManager.remove(LogoNameIndex);
if (this.scene.load.textureManager.exists(this.logoNameIndex)) {
this.scene.load.textureManager.remove(this.logoNameIndex);
}
if (this.scene.load.textureManager.exists("poweredByLogo")) {
this.scene.load.textureManager.remove("poweredByLogo");
}
}
@@ -114,6 +141,11 @@ export class Loader {
this.logo.x = this.scene.game.renderer.width / 2;
this.logo.y = this.scene.game.renderer.height / 2 - 150;
}
if (this.poweredByLogo) {
this.poweredByLogo.x = this.scene.game.renderer.width / 2;
this.poweredByLogo.y = this.scene.game.renderer.height - 40;
}
}
private drawProgress() {
+2 -2
View File
@@ -57,8 +57,8 @@ export class SoundMeter {
this.context = context;
this.analyser = this.context.createAnalyser();
this.analyser.fftSize = 2048;
const bufferLength = this.analyser.fftSize;
this.analyser.fftSize = 256;
const bufferLength = this.analyser.frequencyBinCount;
this.dataArray = new Uint8Array(bufferLength);
}
+31 -6
View File
@@ -18,6 +18,7 @@ import { createColorStore } from "../../Stores/OutlineColorStore";
import type { OutlineableInterface } from "../Game/OutlineableInterface";
import type CancelablePromise from "cancelable-promise";
import { TalkIcon } from "../Components/TalkIcon";
import { Deferred } from "ts-deferred";
const playerNameY = -25;
@@ -50,6 +51,11 @@ export abstract class Character extends Container implements OutlineableInterfac
private readonly outlineColorStoreUnsubscribe: Unsubscriber;
private texturePromise: CancelablePromise<string[] | void> | undefined;
/**
* A deferred promise that resolves when the texture of the character is actually displayed.
*/
private textureLoadedDeferred = new Deferred<void>();
constructor(
scene: GameScene,
x: number,
@@ -78,12 +84,13 @@ export abstract class Character extends Container implements OutlineableInterfac
this.addTextures(textures, frame);
this.invisible = false;
this.playAnimation(direction, moving);
this.textureLoadedDeferred.resolve();
return this.getSnapshot().then((htmlImageElementSrc) => {
this._pictureStore.set(htmlImageElementSrc);
});
})
.catch(() => {
return lazyLoadPlayerCharacterTextures(scene.load, [
return lazyLoadPlayerCharacterTextures(scene.superLoad, [
{
id: "color_22",
img: "resources/customisation/character_color/character_color21.png",
@@ -92,11 +99,20 @@ export abstract class Character extends Container implements OutlineableInterfac
id: "eyes_23",
img: "resources/customisation/character_eyes/character_eyes23.png",
},
]).then((textures) => {
this.addTextures(textures, frame);
this.invisible = false;
this.playAnimation(direction, moving);
});
])
.then((textures) => {
this.addTextures(textures, frame);
this.invisible = false;
this.playAnimation(direction, moving);
this.textureLoadedDeferred.resolve();
return this.getSnapshot().then((htmlImageElementSrc) => {
this._pictureStore.set(htmlImageElementSrc);
});
})
.catch((e) => {
this.textureLoadedDeferred.reject(e);
throw e;
});
})
.finally(() => {
this.texturePromise = undefined;
@@ -514,4 +530,13 @@ export abstract class Character extends Container implements OutlineableInterfac
public characterFarAwayOutline(): void {
this.outlineColorStore.characterFarAway();
}
/**
* Returns a promise that resolves as soon as a texture is displayed for the user.
* The promise will return when the required texture is loaded OR when the fallback texture is loaded (in case
* the required texture could not be loaded).
*/
public getTextureLoadedPromise(): PromiseLike<void> {
return this.textureLoadedDeferred.promise;
}
}
+5 -21
View File
@@ -1,5 +1,7 @@
//The list of all the player textures, both the default models and the partial textures used for customization
import { WokaList, WokaPartType } from "../../Messages/JsonMessages/PlayerTextures";
export interface BodyResourceDescriptionListInterface {
[key: string]: BodyResourceDescriptionInterface;
}
@@ -33,24 +35,6 @@ export enum PlayerTexturesKey {
Woka = "woka",
}
type PlayerTexturesMetadata = Record<PlayerTexturesKey, PlayerTexturesCategory>;
interface PlayerTexturesCategory {
collections: PlayerTexturesCollection[];
required?: boolean;
}
interface PlayerTexturesCollection {
name: string;
textures: PlayerTexturesRecord[];
}
interface PlayerTexturesRecord {
id: string;
name: string;
url: string;
}
export class PlayerTextures {
private PLAYER_RESOURCES: BodyResourceDescriptionListInterface = {};
private COLOR_RESOURCES: BodyResourceDescriptionListInterface = {};
@@ -61,7 +45,7 @@ export class PlayerTextures {
private ACCESSORIES_RESOURCES: BodyResourceDescriptionListInterface = {};
private LAYERS: BodyResourceDescriptionListInterface[] = [];
public loadPlayerTexturesMetadata(metadata: PlayerTexturesMetadata): void {
public loadPlayerTexturesMetadata(metadata: WokaList): void {
this.mapTexturesMetadataIntoResources(metadata);
}
@@ -88,7 +72,7 @@ export class PlayerTextures {
return this.LAYERS;
}
private mapTexturesMetadataIntoResources(metadata: PlayerTexturesMetadata): void {
private mapTexturesMetadataIntoResources(metadata: WokaList): void {
this.PLAYER_RESOURCES = this.getMappedResources(metadata.woka);
this.COLOR_RESOURCES = this.getMappedResources(metadata.body);
this.EYES_RESOURCES = this.getMappedResources(metadata.eyes);
@@ -107,7 +91,7 @@ export class PlayerTextures {
];
}
private getMappedResources(category: PlayerTexturesCategory): BodyResourceDescriptionListInterface {
private getMappedResources(category: WokaPartType): BodyResourceDescriptionListInterface {
const resources: BodyResourceDescriptionListInterface = {};
if (!category) {
return {};
@@ -2,6 +2,8 @@ import LoaderPlugin = Phaser.Loader.LoaderPlugin;
import type { CharacterTexture } from "../../Connexion/LocalUser";
import { BodyResourceDescriptionInterface, mapLayerToLevel, PlayerTextures, PlayerTexturesKey } from "./PlayerTextures";
import CancelablePromise from "cancelable-promise";
import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin";
import Texture = Phaser.Textures.Texture;
export interface FrameConfig {
frameWidth: number;
@@ -35,81 +37,33 @@ export const loadAllDefaultModels = (
};
export const loadWokaTexture = (
loaderPlugin: LoaderPlugin,
superLoaderPlugin: SuperLoaderPlugin,
texture: BodyResourceDescriptionInterface
): CancelablePromise<BodyResourceDescriptionInterface> => {
return createLoadingPromise(loaderPlugin, texture, {
): CancelablePromise<Texture> => {
return superLoaderPlugin.spritesheet(texture.id, texture.img, {
frameWidth: 32,
frameHeight: 32,
});
};
export const lazyLoadPlayerCharacterTextures = (
loadPlugin: LoaderPlugin,
superLoaderPlugin: SuperLoaderPlugin,
textures: BodyResourceDescriptionInterface[]
): CancelablePromise<string[]> => {
const promisesList: CancelablePromise<unknown>[] = [];
textures.forEach((texture) => {
try {
//TODO refactor
if (!loadPlugin.textureManager.exists(texture.id)) {
promisesList.push(
createLoadingPromise(loadPlugin, texture, {
frameWidth: 32,
frameHeight: 32,
})
);
}
} catch (err) {
console.error(err);
}
});
let returnPromise: CancelablePromise<Array<string | BodyResourceDescriptionInterface>>;
if (promisesList.length > 0) {
loadPlugin.start();
returnPromise = CancelablePromise.all(promisesList).then(() => textures);
} else {
returnPromise = CancelablePromise.resolve(textures);
const promisesList: CancelablePromise<Texture>[] = [];
for (const texture of textures) {
promisesList.push(
superLoaderPlugin.spritesheet(texture.id, texture.img, {
frameWidth: 32,
frameHeight: 32,
})
);
}
const returnPromise: CancelablePromise<Texture[]> = CancelablePromise.all(promisesList);
//If the loading fail, we render the default model instead.
return returnPromise.then((keys) =>
keys.map((key) => {
return typeof key !== "string" ? key.id : key;
return returnPromise.then(() =>
textures.map((key) => {
return key.id;
})
);
};
export const createLoadingPromise = (
loadPlugin: LoaderPlugin,
playerResourceDescriptor: BodyResourceDescriptionInterface,
frameConfig: FrameConfig
) => {
return new CancelablePromise<BodyResourceDescriptionInterface>((res, rej, cancel) => {
if (loadPlugin.textureManager.exists(playerResourceDescriptor.id)) {
return res(playerResourceDescriptor);
}
cancel(() => {
loadPlugin.off("loaderror");
loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.id);
return;
});
loadPlugin.spritesheet(playerResourceDescriptor.id, playerResourceDescriptor.img, frameConfig);
const errorCallback = (file: { src: string }) => {
if (file.src !== playerResourceDescriptor.img) return;
console.error("failed loading player resource: ", playerResourceDescriptor);
rej(playerResourceDescriptor);
loadPlugin.off("filecomplete-spritesheet-" + playerResourceDescriptor.id, successCallback);
loadPlugin.off("loaderror", errorCallback);
};
const successCallback = () => {
loadPlugin.off("loaderror", errorCallback);
res(playerResourceDescriptor);
};
loadPlugin.once("filecomplete-spritesheet-" + playerResourceDescriptor.id, successCallback);
loadPlugin.on("loaderror", errorCallback);
});
};
+1 -1
View File
@@ -19,7 +19,7 @@ export class GameManager {
private companion: string | null;
private startRoom!: Room;
private cameraSetup?: { video: unknown; audio: unknown };
currentGameSceneName: string | null = null;
private currentGameSceneName: string | null = null;
// Note: this scenePlugin is the scenePlugin of the EntryScene. We should always provide a key in methods called on this scenePlugin.
private scenePlugin!: Phaser.Scenes.ScenePlugin;
+22 -24
View File
@@ -18,7 +18,7 @@ import { soundManager } from "./SoundManager";
import { SharedVariablesManager } from "./SharedVariablesManager";
import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager";
import { lazyLoadPlayerCharacterTextures, loadWokaTexture } from "../Entity/PlayerTexturesLoadingManager";
import { lazyLoadPlayerCharacterTextures } from "../Entity/PlayerTexturesLoadingManager";
import { lazyLoadCompanionResource } from "../Companion/CompanionTexturesLoadingManager";
import { iframeListener } from "../../Api/IframeListener";
import { DEBUG_MODE, JITSI_URL, MAX_PER_GROUP, POSITION_DELAY } from "../../Enum/EnvironmentVariable";
@@ -100,6 +100,8 @@ import { SimpleCoWebsite } from "../../WebRtc/CoWebsite/SimpleCoWebsite";
import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite";
import type { VideoPeer } from "../../WebRtc/VideoPeer";
import CancelablePromise from "cancelable-promise";
import { Deferred } from "ts-deferred";
import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin";
export interface GameSceneInitInterface {
initPosition: PointInterface | null;
reconnecting: boolean;
@@ -165,13 +167,9 @@ export class GameScene extends DirtyScene {
private playersPositionInterpolator = new PlayersPositionInterpolator();
public connection: RoomConnection | undefined;
private simplePeer!: SimplePeer;
private connectionAnswerPromise: Promise<RoomJoinedMessageInterface>;
private connectionAnswerPromiseResolve!: (
value: RoomJoinedMessageInterface | PromiseLike<RoomJoinedMessageInterface>
) => void;
private connectionAnswerPromiseDeferred: Deferred<RoomJoinedMessageInterface>;
// A promise that will resolve when the "create" method is called (signaling loading is ended)
private createPromise: Promise<void>;
private createPromiseResolve!: (value?: void | PromiseLike<void>) => void;
private createPromiseDeferred: Deferred<void>;
private iframeSubscriptionList!: Array<Subscription>;
private peerStoreUnsubscribe!: Unsubscriber;
private emoteUnsubscribe!: Unsubscriber;
@@ -221,6 +219,7 @@ export class GameScene extends DirtyScene {
private loader: Loader;
private lastCameraEvent: WasCameraUpdatedEvent | undefined;
private firstCameraUpdateSent: boolean = false;
public readonly superLoad: SuperLoaderPlugin;
constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) {
super({
@@ -233,13 +232,10 @@ export class GameScene extends DirtyScene {
this.MapUrlFile = MapUrlFile;
this.roomUrl = room.key;
this.createPromise = new Promise<void>((resolve, reject): void => {
this.createPromiseResolve = resolve;
});
this.connectionAnswerPromise = new Promise<RoomJoinedMessageInterface>((resolve, reject): void => {
this.connectionAnswerPromiseResolve = resolve;
});
this.createPromiseDeferred = new Deferred<void>();
this.connectionAnswerPromiseDeferred = new Deferred<RoomJoinedMessageInterface>();
this.loader = new Loader(this);
this.superLoad = new SuperLoaderPlugin(this);
}
//hook preload scene
@@ -409,11 +405,11 @@ export class GameScene extends DirtyScene {
this.load.on("complete", () => {
// FIXME: the factory might fail because the resources might not be loaded yet...
// We would need to add a loader ended event in addition to the createPromise
this.createPromise
this.createPromiseDeferred.promise
.then(async () => {
itemFactory.create(this);
const roomJoinedAnswer = await this.connectionAnswerPromise;
const roomJoinedAnswer = await this.connectionAnswerPromiseDeferred.promise;
for (const object of objectsOfType) {
// TODO: we should pass here a factory to create sprites (maybe?)
@@ -625,7 +621,7 @@ export class GameScene extends DirtyScene {
}
}
this.createPromiseResolve();
this.createPromiseDeferred.resolve();
// Now, let's load the script, if any
const scripts = this.getScriptUrls(this.mapFile);
const disableModuleMode = this.getProperty(this.mapFile, GameMapProperties.SCRIPT_DISABLE_MODULE_SUPPORT) as
@@ -728,7 +724,11 @@ export class GameScene extends DirtyScene {
}
});
Promise.all([this.connectionAnswerPromise as Promise<unknown>, ...scriptPromises])
Promise.all([
this.connectionAnswerPromiseDeferred.promise as Promise<unknown>,
...scriptPromises,
this.CurrentPlayer.getTextureLoadedPromise() as Promise<unknown>,
])
.then(() => {
this.scene.wake();
})
@@ -765,7 +765,7 @@ export class GameScene extends DirtyScene {
.then((onConnect: OnConnectInterface) => {
this.connection = onConnect.connection;
lazyLoadPlayerCharacterTextures(this.load, onConnect.room.characterLayers)
lazyLoadPlayerCharacterTextures(this.superLoad, onConnect.room.characterLayers)
.then((layers) => {
this.currentPlayerTexturesResolve(layers);
})
@@ -894,7 +894,7 @@ export class GameScene extends DirtyScene {
);
//this.initUsersPosition(roomJoinedMessage.users);
this.connectionAnswerPromiseResolve(onConnect.room);
this.connectionAnswerPromiseDeferred.resolve(onConnect.room);
// Analyze tags to find if we are admin. If yes, show console.
if (this.scene.isSleeping()) {
@@ -1328,7 +1328,7 @@ export class GameScene extends DirtyScene {
iframeListener.registerAnswerer("getState", async () => {
// 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;
await this.connectionAnswerPromiseDeferred.promise;
return {
mapUrl: this.MapUrlFile,
startLayerName: this.startPositionCalculator.startLayerName,
@@ -1351,7 +1351,7 @@ export class GameScene extends DirtyScene {
})
);
iframeListener.registerAnswerer("loadTileset", (eventTileset) => {
return this.connectionAnswerPromise.then(() => {
return this.connectionAnswerPromiseDeferred.promise.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;
@@ -1952,7 +1952,7 @@ export class GameScene extends DirtyScene {
return;
}
const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, addPlayerData.characterLayers);
const texturesPromise = lazyLoadPlayerCharacterTextures(this.superLoad, addPlayerData.characterLayers);
const player = new RemotePlayer(
addPlayerData.userId,
addPlayerData.userUuid,
@@ -2123,8 +2123,6 @@ export class GameScene extends DirtyScene {
right: camera.scrollX + camera.width,
bottom: camera.scrollY + camera.height,
});
this.loader.resize();
}
private getObjectLayerData(objectName: string): ITiledMapObject | undefined {
@@ -3,39 +3,16 @@ import { BodyResourceDescriptionInterface, PlayerTexturesKey } from "../Entity/P
import { loadWokaTexture } from "../Entity/PlayerTexturesLoadingManager";
import type CancelablePromise from "cancelable-promise";
import { PlayerTextures } from "../Entity/PlayerTextures";
import Texture = Phaser.Textures.Texture;
import { SuperLoaderPlugin } from "../Services/SuperLoaderPlugin";
export abstract class AbstractCharacterScene extends ResizableScene {
protected playerTextures: PlayerTextures;
protected superLoad: SuperLoaderPlugin;
constructor(params: { key: string }) {
super(params);
this.playerTextures = new PlayerTextures();
}
loadCustomSceneSelectCharacters(): Promise<BodyResourceDescriptionInterface[]> {
const textures = this.playerTextures.getTexturesResources(PlayerTexturesKey.Woka);
const promises: CancelablePromise<BodyResourceDescriptionInterface>[] = [];
if (textures) {
for (const texture of Object.values(textures)) {
if (texture.level === -1) {
continue;
}
promises.push(loadWokaTexture(this.load, texture));
}
}
return Promise.all(promises);
}
loadSelectSceneCharacters(): Promise<BodyResourceDescriptionInterface[]> {
const promises: CancelablePromise<BodyResourceDescriptionInterface>[] = [];
for (const textures of this.playerTextures.getLayers()) {
for (const texture of Object.values(textures)) {
if (texture.level !== -1) {
continue;
}
promises.push(loadWokaTexture(this.load, texture));
}
}
return Promise.all(promises);
this.superLoad = new SuperLoaderPlugin(this);
}
}
+22 -36
View File
@@ -16,6 +16,7 @@ import { get } from "svelte/store";
import { analyticsClient } from "../../Administration/AnalyticsClient";
import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils";
import { PUSHER_URL } from "../../Enum/EnvironmentVariable";
import { wokaList } from "../../Messages/JsonMessages/PlayerTextures";
export const CustomizeSceneName = "CustomizeScene";
@@ -41,45 +42,30 @@ export class CustomizeScene extends AbstractCharacterScene {
}
preload() {
const wokaMetadataKey = "woka-list";
const wokaMetadataKey = "woka-list" + gameManager.currentStartedRoom.href;
this.cache.json.remove(wokaMetadataKey);
// FIXME: window.location.href is wrong. We need the URL of the main room (so we need to apply any redirect before!)
this.load.json(
wokaMetadataKey,
`${PUSHER_URL}/woka/list?roomUrl=` + encodeURIComponent(window.location.href),
undefined,
{
responseType: "text",
headers: {
Authorization: localUserStore.getAuthToken() ?? "",
this.superLoad
.json(
wokaMetadataKey,
`${PUSHER_URL}/woka/list?roomUrl=` + encodeURIComponent(gameManager.currentStartedRoom.href),
undefined,
{
responseType: "text",
headers: {
Authorization: localUserStore.getAuthToken() ?? "",
},
withCredentials: true,
},
withCredentials: true,
}
);
this.load.once(`filecomplete-json-${wokaMetadataKey}`, () => {
this.playerTextures.loadPlayerTexturesMetadata(this.cache.json.get(wokaMetadataKey));
this.loadCustomSceneSelectCharacters()
.then((bodyResourceDescriptions) => {
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
if (
bodyResourceDescription.level == undefined ||
bodyResourceDescription.level < 0 ||
bodyResourceDescription.level > 5
) {
throw new Error("Texture level is null");
}
this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription);
});
this.lazyloadingAttempt = true;
})
.catch((e) => console.error(e));
(key, type, data) => {
this.playerTextures.loadPlayerTexturesMetadata(wokaList.parse(data));
this.layers = loadAllLayers(this.load, this.playerTextures);
this.lazyloadingAttempt = false;
//this function must stay at the end of preload function
this.loader.addLoader();
});
this.layers = loadAllLayers(this.load, this.playerTextures);
this.lazyloadingAttempt = false;
}
)
.catch((e) => console.error(e));
//this function must stay at the end of preload function
this.loader.addLoader();
}
create() {
+22 -28
View File
@@ -16,6 +16,7 @@ import { analyticsClient } from "../../Administration/AnalyticsClient";
import { isMediaBreakpointUp } from "../../Utils/BreakpointsUtils";
import { PUSHER_URL } from "../../Enum/EnvironmentVariable";
import { customizeAvailableStore } from "../../Stores/SelectCharacterSceneStore";
import { wokaList } from "../../Messages/JsonMessages/PlayerTextures";
//todo: put this constants in a dedicated file
export const SelectCharacterSceneName = "SelectCharacterScene";
@@ -44,38 +45,31 @@ export class SelectCharacterScene extends AbstractCharacterScene {
}
preload() {
const wokaMetadataKey = "woka-list";
const wokaMetadataKey = "woka-list" + gameManager.currentStartedRoom.href;
this.cache.json.remove(wokaMetadataKey);
// FIXME: window.location.href is wrong. We need the URL of the main room (so we need to apply any redirect before!)
this.load.json(
wokaMetadataKey,
`${PUSHER_URL}/woka/list?roomUrl=` + encodeURIComponent(window.location.href),
undefined,
{
responseType: "text",
headers: {
Authorization: localUserStore.getAuthToken() ?? "",
this.superLoad
.json(
wokaMetadataKey,
`${PUSHER_URL}/woka/list?roomUrl=` + encodeURIComponent(gameManager.currentStartedRoom.href),
undefined,
{
responseType: "text",
headers: {
Authorization: localUserStore.getAuthToken() ?? "",
},
withCredentials: true,
},
withCredentials: true,
}
);
this.load.once(`filecomplete-json-${wokaMetadataKey}`, () => {
this.playerTextures.loadPlayerTexturesMetadata(this.cache.json.get(wokaMetadataKey));
this.loadSelectSceneCharacters()
.then((bodyResourceDescriptions) => {
bodyResourceDescriptions.forEach((bodyResourceDescription) => {
this.playerModels.push(bodyResourceDescription);
});
this.lazyloadingAttempt = true;
})
.catch((e) => console.error(e));
this.playerModels = loadAllDefaultModels(this.load, this.playerTextures);
this.lazyloadingAttempt = false;
(key, type, data) => {
this.playerTextures.loadPlayerTexturesMetadata(wokaList.parse(data));
this.playerModels = loadAllDefaultModels(this.load, this.playerTextures);
this.lazyloadingAttempt = false;
}
)
.catch((e) => console.error(e));
//this function must stay at the end of preload function
this.loader.addLoader();
});
//this function must stay at the end of preload function
this.loader.addLoader();
}
create() {
@@ -0,0 +1,176 @@
import CancelablePromise from "cancelable-promise";
import { Scene } from "phaser";
import Texture = Phaser.Textures.Texture;
/**
* A wrapper around Phaser LoaderPlugin. Each method returns a (cancelable) Promise that resolves as soon as
* the file is loaded.
*
* As a bonus, if the scene is destroyed BEFORE the promise resolves, the promise is canceled automatically.
* So there is no risk of trying to add a resource on a closed scene.
*/
export class SuperLoaderPlugin {
constructor(private scene: Scene) {}
public spritesheet(
key: string,
url: string,
frameConfig?: Phaser.Types.Loader.FileTypes.ImageFrameConfig,
xhrSettings?: Phaser.Types.Loader.XHRSettingsObject
) {
return this.loadResource<Texture>(
() => {
this.scene.load.spritesheet(key, url, frameConfig, xhrSettings);
},
key,
url,
() => {
if (this.scene.load.textureManager.exists(key)) {
return this.scene.load.textureManager.get(key);
}
return undefined;
},
"spritesheet"
);
}
public image(key: string, url: string, xhrSettings?: Phaser.Types.Loader.XHRSettingsObject) {
return this.loadResource<Texture>(
() => {
this.scene.load.image(key, url, xhrSettings);
},
key,
url,
() => {
if (this.scene.load.textureManager.exists(key)) {
return this.scene.load.textureManager.get(key);
}
return undefined;
},
"image"
);
}
/**
* @param key
* @param url
* @param dataKey
* @param xhrSettings
* @param immediateCallback The function returns a promise BUT the "then" promise will be triggered after the current Javascript thread finishes. In case of Phaser loader, this can be a problem if you want to add additional resources to the loader in the callback. The "immediateCallback" triggers directly in the
*/
public json(
key: string,
url: string,
dataKey?: string,
xhrSettings?: Phaser.Types.Loader.XHRSettingsObject,
immediateCallback?: (key: string, type: string, data: unknown) => void
) {
return this.loadResource<unknown>(
() => {
this.scene.load.json(key, url, dataKey, xhrSettings);
},
key,
url,
() => {
if (this.scene.load.cacheManager.json.exists(key)) {
return this.scene.load.cacheManager.json.get(key);
}
return undefined;
},
"json",
immediateCallback
);
}
/**
* @param callback The function that calls the loader to load a resource
* @param key The key of the resource to be loaded
* @param fromCache A function that checks in the cache if the resource is already available
* @param type The type of resource loaded
* @param immediateCallback The function returns a promise BUT the "then" promise will be triggered after the current Javascript thread finishes. In case of Phaser loader, this can be a problem if you want to add additional resources to the loader in the callback. The "immediateCallback" triggers directly in the
* @private
*/
private loadResource<T>(
callback: () => void,
key: string,
url: string,
fromCache: () => T | undefined,
type: string,
immediateCallback?: (key: string, type: string, data: unknown) => void
): CancelablePromise<T> {
// If for some reason, the "url" is empty, let's reject the promise.
if (!url) {
console.error("Tried to load an empty " + type + ". URL is missing.");
return CancelablePromise.reject(Error("Failed loading " + type + ": URL is empty"));
}
return new CancelablePromise<T>((res, rej, cancel) => {
if (this.scene.scene.settings.status === Phaser.Scenes.DESTROYED) {
rej(new Error("Trying to load a " + type + " in a Scene that is already destroyed."));
}
const resource = fromCache();
if (resource !== undefined) {
return res(resource);
}
let destroySceneEventRegistered = false;
const unloadCallbacks = () => {
this.scene.load.off("filecomplete-" + type + "-" + key, successCallback);
this.scene.load.off("loaderror", errorCallback);
if (destroySceneEventRegistered) {
this.scene.events.off(Phaser.Scenes.Events.DESTROY, unloadCallbacks);
}
};
const errorCallback = (file: { src: string }) => {
if (file.src !== url) return;
console.error("Failed loading " + type + ": ", url);
rej(new Error('Failed loading "+type+": "' + url + '"'));
unloadCallbacks();
};
const successCallback = (key: string, type: string, data: unknown) => {
this.scene.load.off("loaderror", errorCallback);
this.scene.events.off(Phaser.Scenes.Events.DESTROY, unloadCallbacks);
const resource = fromCache();
if (!resource) {
return rej(new Error("Newly loaded resource not available in cache"));
}
res(resource);
// The "then" callbacks registered on the promise are not executed immediately when "res" is called.
// Instead, they are called after the current JS thread finishes.
// In some cases, we want the callbacks to execute right now. In particular if we load a map / json file
// that contains references to other files that needs to be loaded.
if (immediateCallback) {
immediateCallback(key, type, data);
}
console.log("Resolve done for ", url);
};
cancel(() => {
unloadCallbacks();
});
callback();
this.scene.load.once("filecomplete-" + type + "-" + key, successCallback);
this.scene.load.on("loaderror", errorCallback);
if (this.scene.scene.settings.status > Phaser.Scenes.LOADING) {
// When the scene is destroyed, let's remove our callbacks.
// We only need to register this destroy event is the scene is not in loading state (otherwise, Phaser
// will take care of that for us).
destroySceneEventRegistered = true;
this.scene.events.once(Phaser.Scenes.Events.DESTROY, unloadCallbacks);
// Let's start the loader if we are no more in the scene loading state
this.scene.load.start();
// Due to a bug, if the loader is already started, additional items are not added.... unless we
// explicitly call the "update" method.
this.scene.load.update();
}
});
}
}
+8 -1
View File
@@ -545,8 +545,12 @@ export const obtainedMediaConstraintStore = derived<Readable<MediaStreamConstrai
export const localVolumeStore = readable<number | undefined>(undefined, (set) => {
let timeout: ReturnType<typeof setTimeout>;
let soundMeter: SoundMeter;
const unsubscribe = localStreamStore.subscribe((localStreamStoreValue) => {
clearInterval(timeout);
if (soundMeter) {
soundMeter.stop();
}
if (localStreamStoreValue.type === "error") {
set(undefined);
return;
@@ -557,7 +561,7 @@ export const localVolumeStore = readable<number | undefined>(undefined, (set) =>
set(undefined);
return;
}
const soundMeter = new SoundMeter(mediaStream);
soundMeter = new SoundMeter(mediaStream);
let error = false;
timeout = setInterval(() => {
@@ -575,6 +579,9 @@ export const localVolumeStore = readable<number | undefined>(undefined, (set) =>
return () => {
unsubscribe();
clearInterval(timeout);
if (soundMeter) {
soundMeter.stop();
}
};
});
+4 -1
View File
@@ -619,7 +619,10 @@ class CoWebsiteManager {
setTimeout(() => {
this.fire();
}, animationTime);
} else if (!highlightedEmbed) {
} else if (
!highlightedEmbed &&
this.getCoWebsites().find((searchCoWebsite) => searchCoWebsite.getId() === coWebsite.getId())
) {
highlightedEmbedScreen.toggleHighlight({
type: "cowebsite",
embed: coWebsite,
+4 -1
View File
@@ -200,7 +200,10 @@ class JitsiFactory {
options.onload = () => doResolve(); //we want for the iframe to be loaded before triggering animations.
this.jitsiApi = new window.JitsiMeetExternalAPI(domain, options);
this.jitsiApi.executeCommand("displayName", playerName);
this.jitsiApi.addListener("videoConferenceJoined", () => {
this.jitsiApi?.executeCommand("displayName", playerName);
});
this.jitsiApi.addListener("audioMuteStatusChanged", this.audioCallback);
this.jitsiApi.addListener("videoMuteStatusChanged", this.videoCallback);
+9 -1
View File
@@ -74,12 +74,17 @@ export class VideoPeer extends Peer {
this.volumeStore = readable<number | undefined>(undefined, (set) => {
let timeout: ReturnType<typeof setTimeout>;
let soundMeter: SoundMeter;
const unsubscribe = this.streamStore.subscribe((mediaStream) => {
clearInterval(timeout);
if (soundMeter) {
soundMeter.stop();
}
if (mediaStream === null || mediaStream.getAudioTracks().length <= 0) {
set(undefined);
return;
}
const soundMeter = new SoundMeter(mediaStream);
soundMeter = new SoundMeter(mediaStream);
let error = false;
timeout = setInterval(() => {
@@ -97,6 +102,9 @@ export class VideoPeer extends Peer {
return () => {
unsubscribe();
clearInterval(timeout);
if (soundMeter) {
soundMeter.stop();
}
};
});
+4
View File
@@ -13,6 +13,10 @@ const camera: NonNullable<Translation["camera"]> = {
'Bitte klicke auf "Diese Entscheidungen speichern" Schaltfläche um erneute Nachfragen nach der Freigabe in Firefox zu verhindern.',
refresh: "Aktualisieren",
continue: "Ohne Kamera fortfahren",
screen: {
firefox: "/resources/help-setting-camera-permission/de-DE-chrome.png",
chrome: "/resources/help-setting-camera-permission/de-DE-chrome.png",
},
},
my: {
silentZone: "Stiller Bereich",
+4
View File
@@ -13,6 +13,10 @@ const camera: BaseTranslation = {
'Please click the "Remember this decision" checkbox, if you don\'t want Firefox to keep asking you the authorization.',
refresh: "Refresh",
continue: "Continue without webcam",
screen: {
firefox: "/resources/help-setting-camera-permission/en-US-firefox.png",
chrome: "/resources/help-setting-camera-permission/en-US-firefox.png",
},
},
my: {
silentZone: "Silent zone",