merged develop
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user