merged develop

This commit is contained in:
Piotr 'pwh' Hanusiak
2022-03-24 14:56:42 +01:00
137 changed files with 1628 additions and 1402 deletions
+31 -6
View File
@@ -23,6 +23,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;
const interactiveRadius = 35;
@@ -46,6 +47,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,
@@ -74,12 +80,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",
@@ -88,11 +95,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;
@@ -453,4 +469,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);
});
};
+45 -22
View File
@@ -1,5 +1,5 @@
import { requestVisitCardsStore } from "../../Stores/GameStore";
import { ActionsMenuData, actionsMenuStore } from "../../Stores/ActionsMenuStore";
import { ActionsMenuAction, ActionsMenuData, actionsMenuStore } from "../../Stores/ActionsMenuStore";
import { Character } from "../Entity/Character";
import type { GameScene } from "../Game/GameScene";
import type { PointInterface } from "../../Connexion/ConnexionModels";
@@ -8,21 +8,28 @@ import type { Unsubscriber } from "svelte/store";
import type { ActivatableInterface } from "../Game/ActivatableInterface";
import type CancelablePromise from "cancelable-promise";
import LL from "../../i18n/i18n-svelte";
import { blackListManager } from "../../WebRtc/BlackListManager";
import { showReportScreenStore } from "../../Stores/ShowReportScreenStore";
export enum RemotePlayerEvent {
Clicked = "Clicked",
}
/**
* Class representing the sprite of a remote player (a player that plays on another computer)
*/
export class RemotePlayer extends Character implements ActivatableInterface {
public userId: number;
public readonly userId: number;
public readonly userUuid: string;
public readonly activationRadius: number;
private registeredActions: { actionName: string; callback: Function }[];
private visitCardUrl: string | null;
private isActionsMenuInitialized: boolean = false;
private actionsMenuStoreUnsubscriber: Unsubscriber;
constructor(
userId: number,
userUuid: string,
Scene: GameScene,
x: number,
y: number,
@@ -39,10 +46,9 @@ export class RemotePlayer extends Character implements ActivatableInterface {
//set data
this.userId = userId;
this.userUuid = userUuid;
this.visitCardUrl = visitCardUrl;
this.registeredActions = [];
this.registerDefaultActionsMenuActions();
this.setClickable(this.registeredActions.length > 0);
this.setClickable(this.getDefaultActionsMenuActions().length > 0);
this.activationRadius = activationRadius ?? 96;
this.actionsMenuStoreUnsubscriber = actionsMenuStore.subscribe((value: ActionsMenuData | undefined) => {
this.isActionsMenuInitialized = value ? true : false;
@@ -63,17 +69,19 @@ export class RemotePlayer extends Character implements ActivatableInterface {
}
}
public registerActionsMenuAction(action: { actionName: string; callback: Function }): void {
this.registeredActions.push(action);
this.updateIsClickable();
public registerActionsMenuAction(action: ActionsMenuAction): void {
actionsMenuStore.addAction({
...action,
priority: action.priority ?? 0,
callback: () => {
action.callback();
actionsMenuStore.clear();
},
});
}
public unregisterActionsMenuAction(actionName: string) {
const index = this.registeredActions.findIndex((action) => action.actionName === actionName);
if (index !== -1) {
this.registeredActions.splice(index, 1);
}
this.updateIsClickable();
actionsMenuStore.removeAction(actionName);
}
public activate(): void {
@@ -90,37 +98,52 @@ export class RemotePlayer extends Character implements ActivatableInterface {
return this.isClickable();
}
private updateIsClickable(): void {
this.setClickable(this.registeredActions.length > 0);
}
private toggleActionsMenu(): void {
if (this.isActionsMenuInitialized) {
actionsMenuStore.clear();
return;
}
actionsMenuStore.initialize(this.playerName);
for (const action of this.registeredActions) {
actionsMenuStore.addAction(action.actionName, action.callback);
for (const action of this.getDefaultActionsMenuActions()) {
actionsMenuStore.addAction(action);
}
}
private registerDefaultActionsMenuActions(): void {
private getDefaultActionsMenuActions(): ActionsMenuAction[] {
const actions: ActionsMenuAction[] = [];
if (this.visitCardUrl) {
this.registeredActions.push({
actions.push({
actionName: LL.woka.menu.businessCard(),
protected: true,
priority: 1,
callback: () => {
requestVisitCardsStore.set(this.visitCardUrl);
actionsMenuStore.clear();
},
});
}
actions.push({
actionName: blackListManager.isBlackListed(this.userUuid)
? LL.report.block.unblock()
: LL.report.block.block(),
protected: true,
priority: -1,
style: "is-error",
callback: () => {
showReportScreenStore.set({ userId: this.userId, userName: this.name });
actionsMenuStore.clear();
},
});
return actions;
}
private bindEventHandlers(): void {
this.on(Phaser.Input.Events.POINTER_DOWN, (event: Phaser.Input.Pointer) => {
if (event.downElement.nodeName === "CANVAS" && event.leftButtonDown()) {
this.toggleActionsMenu();
this.emit(RemotePlayerEvent.Clicked);
}
});
}