From 378a95962a4acbeb6af71496664023b6d84dff92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Wed, 23 Feb 2022 21:08:21 +0100 Subject: [PATCH] Heavy changes: refactoring the pusher to always send the textures (and the front to accept them) --- back/src/Services/SocketManager.ts | 1 + front/src/Connexion/ConnectionManager.ts | 36 ++------ front/src/Connexion/ConnexionModels.ts | 2 + front/src/Connexion/LocalUser.ts | 13 ++- front/src/Connexion/Room.ts | 10 +-- front/src/Connexion/RoomConnection.ts | 37 ++++++-- front/src/Phaser/Entity/Character.ts | 11 ++- front/src/Phaser/Entity/PlayerTextures.ts | 15 +++- .../Entity/PlayerTexturesLoadingManager.ts | 40 +++------ front/src/Phaser/Game/GameScene.ts | 30 ++++--- .../Phaser/Login/AbstractCharacterScene.ts | 30 ++++--- front/src/Phaser/Login/CustomizeScene.ts | 4 +- .../src/Phaser/Login/SelectCharacterScene.ts | 1 - .../Phaser/Game/PlayerTexturesLoadingTest.ts | 28 ------ messages/JsonMessages/AdminApiData.ts | 4 +- messages/JsonMessages/CharacterTexture.ts | 16 ---- messages/JsonMessages/MapDetailsData.ts | 4 +- messages/JsonMessages/RegisterData.ts | 4 +- messages/protos/messages.proto | 6 ++ .../src/Controller/AuthenticateController.ts | 2 - pusher/src/Controller/IoSocketController.ts | 89 ++++++++++++++++--- pusher/src/Controller/WokaListController.ts | 21 ++--- pusher/src/Enum/PlayerTextures.ts | 11 +-- .../src/Model/Websocket/ExSocketInterface.ts | 9 +- pusher/src/Model/Websocket/ProtobufUtils.ts | 10 ++- pusher/src/Services/AdminApi.ts | 40 ++++++--- pusher/src/Services/AdminWokaService.ts | 10 +-- pusher/src/Services/LocalWokaService.ts | 18 +++- pusher/src/Services/SocketManager.ts | 51 ++++------- pusher/src/Services/WokaService.ts | 5 ++ pusher/src/Services/WokaServiceInterface.ts | 2 +- 31 files changed, 290 insertions(+), 270 deletions(-) delete mode 100644 front/tests/Phaser/Game/PlayerTexturesLoadingTest.ts delete mode 100644 messages/JsonMessages/CharacterTexture.ts create mode 100644 pusher/src/Services/WokaService.ts diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index 9233811b..1d26f001 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -103,6 +103,7 @@ export class SocketManager { const roomJoinedMessage = new RoomJoinedMessage(); roomJoinedMessage.setTagList(joinRoomMessage.getTagList()); roomJoinedMessage.setUserroomtoken(joinRoomMessage.getUserroomtoken()); + roomJoinedMessage.setCharacterlayerList(joinRoomMessage.getCharacterlayerList()); for (const [itemId, item] of room.getItemsState().entries()) { const itemStateMessage = new ItemStateMessage(); diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts index 391da7bf..998595b7 100644 --- a/front/src/Connexion/ConnectionManager.ts +++ b/front/src/Connexion/ConnectionManager.ts @@ -134,7 +134,7 @@ class ConnectionManager { console.error("Invalid data received from /register route. Data: ", data); throw new Error("Invalid data received from /register route."); } - this.localUser = new LocalUser(data.userUuid, data.textures, data.email); + this.localUser = new LocalUser(data.userUuid, data.email); this.authToken = data.authToken; localUserStore.saveUser(this.localUser); localUserStore.setAuthToken(this.authToken); @@ -214,32 +214,6 @@ class ConnectionManager { } } this.localUser = localUserStore.getLocalUser() as LocalUser; //if authToken exist in localStorage then localUser cannot be null - - if (this._currentRoom.textures != undefined && this._currentRoom.textures.length > 0) { - //check if texture was changed - if (this.localUser.textures.length === 0) { - this.localUser.textures = this._currentRoom.textures; - } else { - // TODO: the local store should NOT be used as a buffer for all the texture we were authorized to have. Bad idea. - // Instead, it is the responsibility of the ADMIN to return the EXACT list of textures we can have in a given context - // + this list can change over time or over rooms. - - // 1- a room could forbid a particular dress code. In this case, the user MUST change its skin. - // 2- a room can allow "external skins from other maps" => important: think about fediverse! => switch to URLs? (with a whitelist mechanism?) => but what about NFTs? - - // Note: stocker des URL dans le localstorage pour les utilisateurs actuels: mauvaise idée (empêche de mettre l'URL à jour dans le futur) => en même temps, problème avec le portage de user d'un serveur à l'autre - // Réfléchir à une notion de "character server" ?? - - this._currentRoom.textures.forEach((newTexture) => { - const alreadyExistTexture = this.localUser.textures.find((c) => newTexture.id === c.id); - if (this.localUser.textures.findIndex((c) => newTexture.id === c.id) !== -1) { - return; - } - this.localUser.textures.push(newTexture); - }); - } - localUserStore.saveUser(this.localUser); - } } if (this._currentRoom == undefined) { return Promise.reject(new Error("Invalid URL")); @@ -265,7 +239,7 @@ class ConnectionManager { public async anonymousLogin(isBenchmark: boolean = false): Promise { const data = await axiosWithRetry.post(`${PUSHER_URL}/anonymLogin`).then((res) => res.data); - this.localUser = new LocalUser(data.userUuid, [], data.email); + this.localUser = new LocalUser(data.userUuid, data.email); this.authToken = data.authToken; if (!isBenchmark) { // In benchmark, we don't have a local storage. @@ -275,7 +249,7 @@ class ConnectionManager { } public initBenchmark(): void { - this.localUser = new LocalUser("", []); + this.localUser = new LocalUser(""); } public connectToRoomSocket( @@ -352,13 +326,13 @@ class ConnectionManager { throw new Error("No Auth code provided"); } } - const { authToken, userUuid, textures, email } = await Axios.get(`${PUSHER_URL}/login-callback`, { + const { authToken, userUuid, email } = await Axios.get(`${PUSHER_URL}/login-callback`, { params: { code, nonce, token, playUri: this.currentRoom?.key }, }).then((res) => { return res.data; }); localUserStore.setAuthToken(authToken); - this.localUser = new LocalUser(userUuid, textures, email); + this.localUser = new LocalUser(userUuid, email); localUserStore.saveUser(this.localUser); this.authToken = authToken; diff --git a/front/src/Connexion/ConnexionModels.ts b/front/src/Connexion/ConnexionModels.ts index bf834a02..bd12d866 100644 --- a/front/src/Connexion/ConnexionModels.ts +++ b/front/src/Connexion/ConnexionModels.ts @@ -2,6 +2,7 @@ import type { SignalData } from "simple-peer"; import type { RoomConnection } from "./RoomConnection"; import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures"; import { PositionMessage_Direction } from "../Messages/ts-proto-generated/messages"; +import { CharacterLayer } from "../../../back/src/Model/Websocket/CharacterLayer"; export interface PointInterface { x: number; @@ -83,6 +84,7 @@ export interface RoomJoinedMessageInterface { //groups: GroupCreatedUpdatedMessageInterface[], items: { [itemId: number]: unknown }; variables: Map; + characterLayers: BodyResourceDescriptionInterface[]; } export interface PlayGlobalMessageInterface { diff --git a/front/src/Connexion/LocalUser.ts b/front/src/Connexion/LocalUser.ts index d4498883..fce7593d 100644 --- a/front/src/Connexion/LocalUser.ts +++ b/front/src/Connexion/LocalUser.ts @@ -1,10 +1,11 @@ import { MAX_USERNAME_LENGTH } from "../Enum/EnvironmentVariable"; +export type LayerNames = "woka" | "body" | "eyes" | "hair" | "clothes" | "hat" | "accessory"; + export interface CharacterTexture { - id: number; - level: number; + id: string; + layer: LayerNames; url: string; - rights: string; } export const maxUserNameLength: number = MAX_USERNAME_LENGTH; @@ -24,9 +25,5 @@ export function areCharacterLayersValid(value: string[] | null): boolean { } export class LocalUser { - constructor( - public readonly uuid: string, - public textures: CharacterTexture[], - public email: string | null = null - ) {} + constructor(public readonly uuid: string, public email: string | null = null) {} } diff --git a/front/src/Connexion/Room.ts b/front/src/Connexion/Room.ts index 213c3019..778f7e35 100644 --- a/front/src/Connexion/Room.ts +++ b/front/src/Connexion/Room.ts @@ -9,7 +9,7 @@ import { isMapDetailsData } from "../Messages/JsonMessages/MapDetailsData"; import { isRoomRedirect } from "../Messages/JsonMessages/RoomRedirect"; export class MapDetail { - constructor(public readonly mapUrl: string, public readonly textures: CharacterTexture[] | undefined) {} + constructor(public readonly mapUrl: string) {} } export interface RoomRedirect { @@ -25,7 +25,6 @@ export class Room { private _authenticationMandatory: boolean = DISABLE_ANONYMOUS; private _iframeAuthentication?: string = OPID_LOGIN_SCREEN_PROVIDER; private _mapUrl: string | undefined; - private _textures: CharacterTexture[] | undefined; private instance: string | undefined; private readonly _search: URLSearchParams; private _contactPage: string | undefined; @@ -118,7 +117,6 @@ export class Room { } else if (isMapDetailsData(data)) { console.log("Map ", this.id, " resolves to URL ", data.mapUrl); this._mapUrl = data.mapUrl; - this._textures = data.textures; this._group = data.group; this._authenticationMandatory = data.authenticationMandatory != null ? data.authenticationMandatory : DISABLE_ANONYMOUS; @@ -128,7 +126,7 @@ export class Room { this._expireOn = new Date(data.expireOn); } this._canReport = data.canReport ?? false; - return new MapDetail(data.mapUrl, data.textures); + return new MapDetail(data.mapUrl); } else { throw new Error("Data received by the /map endpoint of the Pusher is not in a valid format."); } @@ -205,10 +203,6 @@ export class Room { return this.roomUrl.toString(); } - get textures(): CharacterTexture[] | undefined { - return this._textures; - } - get mapUrl(): string { if (!this._mapUrl) { throw new Error("Map URL not fetched yet"); diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 4e2f8397..3ea8375b 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -20,7 +20,7 @@ import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTe import { adminMessagesService } from "./AdminMessagesService"; import { connectionManager } from "./ConnectionManager"; import { get } from "svelte/store"; -import { warningContainerStore } from "../Stores/MenuStore"; +import { menuIconVisiblilityStore, menuVisiblilityStore, warningContainerStore } from "../Stores/MenuStore"; import { followStateStore, followRoleStore, followUsersStore } from "../Stores/FollowStore"; import { localUserStore } from "./LocalUserStore"; import { @@ -52,10 +52,14 @@ import { PositionMessage_Direction, SetPlayerDetailsMessage as SetPlayerDetailsMessageTsProto, PingMessage as PingMessageTsProto, + CharacterLayerMessage, } from "../Messages/ts-proto-generated/messages"; import { Subject } from "rxjs"; import { OpenPopupEvent } from "../Api/Events/OpenPopupEvent"; import { match } from "assert"; +import { selectCharacterSceneVisibleStore } from "../Stores/SelectCharacterStore"; +import { gameManager } from "../Phaser/Game/GameManager"; +import { SelectCharacterScene, SelectCharacterSceneName } from "../Phaser/Login/SelectCharacterScene"; const manualPingDelay = 20000; @@ -336,12 +340,16 @@ export class RoomConnection implements RoomConnection { this.userId = roomJoinedMessage.currentUserId; this.tags = roomJoinedMessage.tag; this._userRoomToken = roomJoinedMessage.userRoomToken; + const characterLayers = roomJoinedMessage.characterLayer.map( + this.mapCharactgerLayerToBodyResourceDescription.bind(this) + ); this._roomJoinedMessageStream.next({ connection: this, room: { items, variables, + characterLayers, } as RoomJoinedMessageInterface, }); break; @@ -351,6 +359,15 @@ export class RoomConnection implements RoomConnection { this.closed = true; break; } + case "invalidTextureMessage": { + menuVisiblilityStore.set(false); + menuIconVisiblilityStore.set(false); + selectCharacterSceneVisibleStore.set(true); + gameManager.leaveGame(SelectCharacterSceneName, new SelectCharacterScene()); + + this.closed = true; + break; + } case "tokenExpiredMessage": { connectionManager.logout().catch((e) => console.error(e)); this.closed = true; //technically, this isn't needed since loadOpenIDScreen() will do window.location.assign() but I prefer to leave it for consistency @@ -591,6 +608,15 @@ export class RoomConnection implements RoomConnection { }); }*/ + private mapCharactgerLayerToBodyResourceDescription( + characterLayer: CharacterLayerMessage + ): BodyResourceDescriptionInterface { + return { + name: characterLayer.name, + img: characterLayer.url, + }; + } + // TODO: move this to protobuf utils private toMessageUserJoined(message: UserJoinedMessageTsProto): MessageUserJoined { const position = message.position; @@ -598,12 +624,9 @@ export class RoomConnection implements RoomConnection { throw new Error("Invalid JOIN_ROOM message"); } - const characterLayers = message.characterLayers.map((characterLayer): BodyResourceDescriptionInterface => { - return { - name: characterLayer.name, - img: characterLayer.url, - }; - }); + const characterLayers = message.characterLayers.map( + this.mapCharactgerLayerToBodyResourceDescription.bind(this) + ); const companion = message.companion; diff --git a/front/src/Phaser/Entity/Character.ts b/front/src/Phaser/Entity/Character.ts index fa12ae96..79669d55 100644 --- a/front/src/Phaser/Entity/Character.ts +++ b/front/src/Phaser/Entity/Character.ts @@ -83,7 +83,16 @@ export abstract class Character extends Container implements OutlineableInterfac }); }) .catch(() => { - return lazyLoadPlayerCharacterTextures(scene.load, ["color_22", "eyes_23"]).then((textures) => { + return lazyLoadPlayerCharacterTextures(scene.load, [ + { + name: "color_22", + img: "resources/customisation/character_color/character_color21.png", + }, + { + name: "eyes_23", + img: "resources/customisation/character_eyes/character_eyes23.png", + }, + ]).then((textures) => { this.addTextures(textures, frame); this.invisible = false; this.playAnimation(direction, moving); diff --git a/front/src/Phaser/Entity/PlayerTextures.ts b/front/src/Phaser/Entity/PlayerTextures.ts index 34e7f433..657556b8 100644 --- a/front/src/Phaser/Entity/PlayerTextures.ts +++ b/front/src/Phaser/Entity/PlayerTextures.ts @@ -1,7 +1,5 @@ //The list of all the player textures, both the default models and the partial textures used for customization -import { PUSHER_URL } from "../../Enum/EnvironmentVariable"; - export interface BodyResourceDescriptionListInterface { [key: string]: BodyResourceDescriptionInterface; } @@ -12,6 +10,19 @@ export interface BodyResourceDescriptionInterface { level?: number; } +/** + * Temporary object to map layers to the old "level" concept. + */ +export const mapLayerToLevel = { + woka: -1, + body: 0, + eyes: 1, + hair: 2, + clothes: 3, + hat: 4, + accessory: 5, +}; + enum PlayerTexturesKey { Accessory = "accessory", Body = "body", diff --git a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts index 39be830d..0f872e5c 100644 --- a/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts +++ b/front/src/Phaser/Entity/PlayerTexturesLoadingManager.ts @@ -1,6 +1,6 @@ import LoaderPlugin = Phaser.Loader.LoaderPlugin; import type { CharacterTexture } from "../../Connexion/LocalUser"; -import { BodyResourceDescriptionInterface, PlayerTextures } from "./PlayerTextures"; +import { BodyResourceDescriptionInterface, mapLayerToLevel, PlayerTextures } from "./PlayerTextures"; import CancelablePromise from "cancelable-promise"; export interface FrameConfig { @@ -28,13 +28,11 @@ export const loadAllDefaultModels = (load: LoaderPlugin): BodyResourceDescriptio return returnArray; }; -export const loadCustomTexture = ( +export const loadWokaTexture = ( loaderPlugin: LoaderPlugin, - texture: CharacterTexture + texture: BodyResourceDescriptionInterface ): CancelablePromise => { - const name = "customCharacterTexture" + texture.id; - const playerResourceDescriptor: BodyResourceDescriptionInterface = { name, img: texture.url, level: texture.level }; - return createLoadingPromise(loaderPlugin, playerResourceDescriptor, { + return createLoadingPromise(loaderPlugin, texture, { frameWidth: 32, frameHeight: 32, }); @@ -42,16 +40,15 @@ export const loadCustomTexture = ( export const lazyLoadPlayerCharacterTextures = ( loadPlugin: LoaderPlugin, - texturekeys: Array + textures: BodyResourceDescriptionInterface[] ): CancelablePromise => { const promisesList: CancelablePromise[] = []; - texturekeys.forEach((textureKey: string | BodyResourceDescriptionInterface) => { + textures.forEach((texture) => { try { //TODO refactor - const playerResourceDescriptor = getRessourceDescriptor(textureKey); - if (playerResourceDescriptor && !loadPlugin.textureManager.exists(playerResourceDescriptor.name)) { + if (!loadPlugin.textureManager.exists(texture.name)) { promisesList.push( - createLoadingPromise(loadPlugin, playerResourceDescriptor, { + createLoadingPromise(loadPlugin, texture, { frameWidth: 32, frameHeight: 32, }) @@ -64,9 +61,9 @@ export const lazyLoadPlayerCharacterTextures = ( let returnPromise: CancelablePromise>; if (promisesList.length > 0) { loadPlugin.start(); - returnPromise = CancelablePromise.all(promisesList).then(() => texturekeys); + returnPromise = CancelablePromise.all(promisesList).then(() => textures); } else { - returnPromise = CancelablePromise.resolve(texturekeys); + returnPromise = CancelablePromise.resolve(textures); } //If the loading fail, we render the default model instead. @@ -77,23 +74,6 @@ export const lazyLoadPlayerCharacterTextures = ( ); }; -export const getRessourceDescriptor = ( - textureKey: string | BodyResourceDescriptionInterface -): BodyResourceDescriptionInterface => { - if (typeof textureKey !== "string" && textureKey.img) { - return textureKey; - } - const textureName: string = typeof textureKey === "string" ? textureKey : textureKey.name; - const playerResource = PlayerTextures.PLAYER_RESOURCES[textureName]; - if (playerResource !== undefined) return playerResource; - - for (let i = 0; i < PlayerTextures.LAYERS.length; i++) { - const playerResource = PlayerTextures.LAYERS[i][textureName]; - if (playerResource !== undefined) return playerResource; - } - throw new Error("Could not find a data for texture " + textureName); -}; - export const createLoadingPromise = ( loadPlugin: LoaderPlugin, playerResourceDescriptor: BodyResourceDescriptionInterface, diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 0a44cea3..7248c2ed 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -18,7 +18,7 @@ import { soundManager } from "./SoundManager"; import { SharedVariablesManager } from "./SharedVariablesManager"; import { EmbeddedWebsiteManager } from "./EmbeddedWebsiteManager"; -import { lazyLoadPlayerCharacterTextures, loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager"; +import { lazyLoadPlayerCharacterTextures, loadWokaTexture } 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"; @@ -97,6 +97,8 @@ import { startLayerNamesStore } from "../../Stores/StartLayerNamesStore"; import { JitsiCoWebsite } from "../../WebRtc/CoWebsite/JitsiCoWebsite"; import { SimpleCoWebsite } from "../../WebRtc/CoWebsite/SimpleCoWebsite"; import type { CoWebsite } from "../../WebRtc/CoWebsite/CoWesbite"; +import { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures"; +import CancelablePromise from "cancelable-promise"; export interface GameSceneInitInterface { initPosition: PointInterface | null; reconnecting: boolean; @@ -244,13 +246,6 @@ export class GameScene extends DirtyScene { //initialize frame event of scripting API this.listenToIframeEvents(); - const localUser = localUserStore.getLocalUser(); - const textures = localUser?.textures; - if (textures) { - for (const texture of textures) { - loadCustomTexture(this.load, texture).catch((e) => console.error(e)); - } - } this.load.image("iconTalk", "/resources/icons/icon_talking.png"); if (touchScreenManager.supportTouchScreen) { @@ -745,6 +740,14 @@ export class GameScene extends DirtyScene { .then((onConnect: OnConnectInterface) => { this.connection = onConnect.connection; + lazyLoadPlayerCharacterTextures(this.load, onConnect.room.characterLayers) + .then((layers) => { + this.currentPlayerTexturesResolve(layers); + }) + .catch((e) => { + this.currentPlayerTexturesReject(e); + }); + playersStore.connectToRoomConnection(this.connection); userIsAdminStore.set(this.connection.hasTag("admin")); @@ -1689,16 +1692,23 @@ ${escapedMessage} } } + // The promise that will resolve to the current player texture. This will be available only after connection is established. + private currentPlayerTexturesResolve!: (value: string[]) => void; + private currentPlayerTexturesReject!: (reason: unknown) => void; + private currentPlayerTexturesPromise: CancelablePromise = new CancelablePromise((resolve, reject) => { + this.currentPlayerTexturesResolve = resolve; + this.currentPlayerTexturesReject = reject; + }); + private createCurrentPlayer() { //TODO create animation moving between exit and start - const texturesPromise = lazyLoadPlayerCharacterTextures(this.load, this.characterLayers); try { this.CurrentPlayer = new Player( this, this.startPositionCalculator.startPosition.x, this.startPositionCalculator.startPosition.y, this.playerName, - texturesPromise, + this.currentPlayerTexturesPromise, PlayerAnimationDirections.Down, false, this.companion, diff --git a/front/src/Phaser/Login/AbstractCharacterScene.ts b/front/src/Phaser/Login/AbstractCharacterScene.ts index 67e2ba3d..bc260718 100644 --- a/front/src/Phaser/Login/AbstractCharacterScene.ts +++ b/front/src/Phaser/Login/AbstractCharacterScene.ts @@ -1,41 +1,45 @@ import { ResizableScene } from "./ResizableScene"; import { localUserStore } from "../../Connexion/LocalUserStore"; import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures"; -import { loadCustomTexture } from "../Entity/PlayerTexturesLoadingManager"; +import { loadWokaTexture } from "../Entity/PlayerTexturesLoadingManager"; import type { CharacterTexture } from "../../Connexion/LocalUser"; import type CancelablePromise from "cancelable-promise"; +import { PlayerTextures } from "../Entity/PlayerTextures"; +import { Loader } from "../Components/Loader"; +import { CustomizeSceneName } from "./CustomizeScene"; export abstract class AbstractCharacterScene extends ResizableScene { + protected playerTextures: PlayerTextures; + + constructor(params: { key: string }) { + super(params); + this.playerTextures = new PlayerTextures(); + } + loadCustomSceneSelectCharacters(): Promise { - const textures = this.getTextures(); + const textures = PlayerTextures.PLAYER_RESOURCES; const promises: CancelablePromise[] = []; if (textures) { - for (const texture of textures) { + for (const texture of Object.values(textures)) { if (texture.level === -1) { continue; } - promises.push(loadCustomTexture(this.load, texture)); + promises.push(loadWokaTexture(this.load, texture)); } } return Promise.all(promises); } loadSelectSceneCharacters(): Promise { - const textures = this.getTextures(); const promises: CancelablePromise[] = []; - if (textures) { - for (const texture of textures) { + for (const textures of PlayerTextures.LAYERS) { + for (const texture of Object.values(textures)) { if (texture.level !== -1) { continue; } - promises.push(loadCustomTexture(this.load, texture)); + promises.push(loadWokaTexture(this.load, texture)); } } return Promise.all(promises); } - - private getTextures(): CharacterTexture[] | undefined { - const localUser = localUserStore.getLocalUser(); - return localUser?.textures; - } } diff --git a/front/src/Phaser/Login/CustomizeScene.ts b/front/src/Phaser/Login/CustomizeScene.ts index 8b0e4222..b87e3640 100644 --- a/front/src/Phaser/Login/CustomizeScene.ts +++ b/front/src/Phaser/Login/CustomizeScene.ts @@ -5,7 +5,7 @@ import Sprite = Phaser.GameObjects.Sprite; import { gameManager } from "../Game/GameManager"; import { localUserStore } from "../../Connexion/LocalUserStore"; import { Loader } from "../Components/Loader"; -import { BodyResourceDescriptionInterface, PlayerTextures } from "../Entity/PlayerTextures"; +import type { BodyResourceDescriptionInterface } from "../Entity/PlayerTextures"; import { AbstractCharacterScene } from "./AbstractCharacterScene"; import { areCharacterLayersValid } from "../../Connexion/LocalUser"; import { SelectCharacterSceneName } from "./SelectCharacterScene"; @@ -32,14 +32,12 @@ export class CustomizeScene extends AbstractCharacterScene { private moveVertically: number = 0; private loader: Loader; - private playerTextures: PlayerTextures; constructor() { super({ key: CustomizeSceneName, }); this.loader = new Loader(this); - this.playerTextures = new PlayerTextures(); } preload() { diff --git a/front/src/Phaser/Login/SelectCharacterScene.ts b/front/src/Phaser/Login/SelectCharacterScene.ts index 17163eb6..67cdc952 100644 --- a/front/src/Phaser/Login/SelectCharacterScene.ts +++ b/front/src/Phaser/Login/SelectCharacterScene.ts @@ -33,7 +33,6 @@ export class SelectCharacterScene extends AbstractCharacterScene { protected lazyloadingAttempt = true; //permit to update texture loaded after renderer private loader: Loader; - private playerTextures: PlayerTextures; constructor() { super({ diff --git a/front/tests/Phaser/Game/PlayerTexturesLoadingTest.ts b/front/tests/Phaser/Game/PlayerTexturesLoadingTest.ts deleted file mode 100644 index 6c984a9e..00000000 --- a/front/tests/Phaser/Game/PlayerTexturesLoadingTest.ts +++ /dev/null @@ -1,28 +0,0 @@ -import "jasmine"; -import { getRessourceDescriptor } from "../../../src/Phaser/Entity/PlayerTexturesLoadingManager"; - -describe("getRessourceDescriptor()", () => { - it(", if given a valid descriptor as parameter, should return it", () => { - const desc = getRessourceDescriptor({ name: "name", img: "url" }); - expect(desc.name).toEqual("name"); - expect(desc.img).toEqual("url"); - }); - - it(", if given a string as parameter, should search through hardcoded values", () => { - const desc = getRessourceDescriptor("male1"); - expect(desc.name).toEqual("male1"); - expect(desc.img).toEqual("resources/characters/pipoya/Male 01-1.png"); - }); - - it(", if given a string as parameter, should search through hardcoded values (bis)", () => { - const desc = getRessourceDescriptor("color_2"); - expect(desc.name).toEqual("color_2"); - expect(desc.img).toEqual("resources/customisation/character_color/character_color1.png"); - }); - - it(", if given a descriptor without url as parameter, should search through hardcoded values", () => { - const desc = getRessourceDescriptor({ name: "male1", img: "" }); - expect(desc.name).toEqual("male1"); - expect(desc.img).toEqual("resources/characters/pipoya/Male 01-1.png"); - }); -}); diff --git a/messages/JsonMessages/AdminApiData.ts b/messages/JsonMessages/AdminApiData.ts index 314963be..1fd7765e 100644 --- a/messages/JsonMessages/AdminApiData.ts +++ b/messages/JsonMessages/AdminApiData.ts @@ -1,5 +1,5 @@ import * as tg from "generic-type-guard"; -import { isCharacterTexture } from "./CharacterTexture"; +//import { isCharacterTexture } from "./CharacterTexture"; /* * WARNING! The original file is in /messages/JsonMessages. @@ -12,7 +12,7 @@ export const isAdminApiData = new tg.IsInterface() email: tg.isNullable(tg.isString), roomUrl: tg.isString, mapUrlStart: tg.isString, - textures: tg.isArray(isCharacterTexture), +// textures: tg.isArray(isCharacterTexture), }) .withOptionalProperties({ messages: tg.isArray(tg.isUnknown), diff --git a/messages/JsonMessages/CharacterTexture.ts b/messages/JsonMessages/CharacterTexture.ts deleted file mode 100644 index eb2ec15e..00000000 --- a/messages/JsonMessages/CharacterTexture.ts +++ /dev/null @@ -1,16 +0,0 @@ -import * as tg from "generic-type-guard"; - -/* - * WARNING! The original file is in /messages/JsonMessages. - * All other files are automatically copied from this file on container startup / build - */ - -export const isCharacterTexture = new tg.IsInterface() - .withProperties({ - id: tg.isNumber, - level: tg.isNumber, - url: tg.isString, - rights: tg.isString, - }) - .get(); -export type CharacterTexture = tg.GuardedType; diff --git a/messages/JsonMessages/MapDetailsData.ts b/messages/JsonMessages/MapDetailsData.ts index 39866eee..87b2b95a 100644 --- a/messages/JsonMessages/MapDetailsData.ts +++ b/messages/JsonMessages/MapDetailsData.ts @@ -1,5 +1,5 @@ import * as tg from "generic-type-guard"; -import { isCharacterTexture } from "./CharacterTexture"; +//import { isCharacterTexture } from "./CharacterTexture"; import { isNumber } from "generic-type-guard"; /* @@ -12,7 +12,7 @@ export const isMapDetailsData = new tg.IsInterface() mapUrl: tg.isString, policy_type: isNumber, //isNumericEnum(GameRoomPolicyTypes), tags: tg.isArray(tg.isString), - textures: tg.isArray(isCharacterTexture), +// textures: tg.isArray(isCharacterTexture), authenticationMandatory: tg.isUnion(tg.isNullable(tg.isBoolean), tg.isUndefined), roomSlug: tg.isNullable(tg.isString), // deprecated contactPage: tg.isNullable(tg.isString), diff --git a/messages/JsonMessages/RegisterData.ts b/messages/JsonMessages/RegisterData.ts index 473ee592..1fe3426f 100644 --- a/messages/JsonMessages/RegisterData.ts +++ b/messages/JsonMessages/RegisterData.ts @@ -1,5 +1,5 @@ import * as tg from "generic-type-guard"; -import { isCharacterTexture } from "./CharacterTexture"; +//import { isCharacterTexture } from "./CharacterTexture"; /* * WARNING! The original file is in /messages/JsonMessages. @@ -13,7 +13,7 @@ export const isRegisterData = new tg.IsInterface() organizationMemberToken: tg.isNullable(tg.isString), mapUrlStart: tg.isString, userUuid: tg.isString, - textures: tg.isArray(isCharacterTexture), +// textures: tg.isArray(isCharacterTexture), authToken: tg.isString, }) .withOptionalProperties({ diff --git a/messages/protos/messages.proto b/messages/protos/messages.proto index 8ac7bbf0..d0768480 100644 --- a/messages/protos/messages.proto +++ b/messages/protos/messages.proto @@ -34,6 +34,7 @@ message SilentMessage { message CharacterLayerMessage { string url = 1; string name = 2; + string layer = 3; } message CompanionMessage { @@ -223,6 +224,8 @@ message RoomJoinedMessage { repeated string tag = 5; repeated VariableMessage variable = 6; string userRoomToken = 7; + // We send the current skin of the current player. + repeated CharacterLayerMessage characterLayer = 8; } message WebRtcStartMessage { @@ -274,6 +277,8 @@ message WorldFullMessage{ } message TokenExpiredMessage{ } +message InvalidTextureMessage{ +} message WorldConnexionMessage{ string message = 2; @@ -310,6 +315,7 @@ message ServerToClientMessage { FollowRequestMessage followRequestMessage = 21; FollowConfirmationMessage followConfirmationMessage = 22; FollowAbortMessage followAbortMessage = 23; + InvalidTextureMessage invalidTextureMessage = 24; } } diff --git a/pusher/src/Controller/AuthenticateController.ts b/pusher/src/Controller/AuthenticateController.ts index f6d8e41f..d2e6c11a 100644 --- a/pusher/src/Controller/AuthenticateController.ts +++ b/pusher/src/Controller/AuthenticateController.ts @@ -273,7 +273,6 @@ export class AuthenticateController extends BaseHttpController { const email = data.email; const roomUrl = data.roomUrl; const mapUrlStart = data.mapUrlStart; - const textures = data.textures; const authToken = jwtTokenManager.createAuthToken(email || userUuid); res.json({ @@ -283,7 +282,6 @@ export class AuthenticateController extends BaseHttpController { roomUrl, mapUrlStart, organizationMemberToken, - textures, } as RegisterData); } catch (e) { console.error("register => ERROR", e); diff --git a/pusher/src/Controller/IoSocketController.ts b/pusher/src/Controller/IoSocketController.ts index eb8f4e75..0400477a 100644 --- a/pusher/src/Controller/IoSocketController.ts +++ b/pusher/src/Controller/IoSocketController.ts @@ -1,4 +1,4 @@ -import { CharacterLayer, ExSocketInterface } from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.." +import { ExSocketInterface } from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.." import { GameRoomPolicyTypes } from "../Model/PusherRoom"; import { PointInterface } from "../Model/Websocket/PointInterface"; import { @@ -31,11 +31,46 @@ import { emitInBatch } from "../Services/IoSocketHelpers"; import { ADMIN_API_URL, ADMIN_SOCKETS_TOKEN, DISABLE_ANONYMOUS, SOCKET_IDLE_TIMER } from "../Enum/EnvironmentVariable"; import { Zone } from "_Model/Zone"; import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface"; -import { CharacterTexture } from "../Messages/JsonMessages/CharacterTexture"; import { isAdminMessageInterface } from "../Model/Websocket/Admin/AdminMessages"; import Axios from "axios"; import { InvalidTokenError } from "../Controller/InvalidTokenError"; import HyperExpress from "hyper-express"; +import { localWokaService } from "../Services/LocalWokaService"; +import { WebSocket } from "uWebSockets.js"; +import { WokaDetail } from "../Enum/PlayerTextures"; + +/** + * The object passed between the "open" and the "upgrade" methods when opening a websocket + */ +interface UpgradeData { + // Data passed here is accessible on the "websocket" socket object. + rejected: false; + token: string; + userUuid: string; + IPAddress: string; + roomId: string; + name: string; + companion: CompanionMessage | undefined; + characterLayers: WokaDetail[]; + messages: unknown[]; + tags: string[]; + visitCardUrl: string | null; + userRoomToken: string | undefined; + position: PointInterface; + viewport: { + top: number; + right: number; + bottom: number; + left: number; + }; +} + +interface UpgradeFailedData { + rejected: true; + reason: "tokenInvalid" | "textureInvalid" | null; + message: string; + roomId: string; +} export class IoSocketController { private nextUserId: number = 1; @@ -244,7 +279,7 @@ export class IoSocketController { let memberVisitCardUrl: string | null = null; let memberMessages: unknown; let memberUserRoomToken: string | undefined; - let memberTextures: CharacterTexture[] = []; + let memberTextures: WokaDetail[] = []; const room = await socketManager.getOrCreateRoom(roomId); let userData: FetchMemberDataByUuidResponse = { email: userIdentifier, @@ -256,6 +291,9 @@ export class IoSocketController { anonymous: true, userRoomToken: undefined, }; + + let characterLayerObjs: WokaDetail[]; + if (ADMIN_API_URL) { try { try { @@ -308,6 +346,8 @@ export class IoSocketController { ) { throw new Error("Use the login URL to connect"); } + + characterLayerObjs = memberTextures; } catch (e) { console.log( "access not granted for user " + @@ -318,11 +358,31 @@ export class IoSocketController { console.error(e); throw new Error("User cannot access this world"); } + } else { + const fetchedTextures = await localWokaService.fetchWokaDetails(characterLayers); + if (fetchedTextures === undefined) { + // The textures we want to use do not exist! + // We need to go in error. + res.upgrade( + { + rejected: true, + reason: "textureInvalid", + message: "", + roomId, + } as UpgradeFailedData, + websocketKey, + websocketProtocol, + websocketExtensions, + context + ); + return; + } + characterLayerObjs = fetchedTextures; } // Generate characterLayers objects from characterLayers string[] - const characterLayerObjs: CharacterLayer[] = - SocketManager.mergeCharacterLayersAndCustomTextures(characterLayers, memberTextures); + /*const characterLayerObjs: CharacterLayer[] = + SocketManager.mergeCharacterLayersAndCustomTextures(characterLayers, memberTextures);*/ if (upgradeAborted.aborted) { console.log("Ouch! Client disconnected before we could upgrade it!"); @@ -334,7 +394,7 @@ export class IoSocketController { res.upgrade( { // Data passed here is accessible on the "websocket" socket object. - url, + rejected: false, token, userUuid: userData.userUuid, IPAddress, @@ -346,7 +406,6 @@ export class IoSocketController { tags: memberTags, visitCardUrl: memberVisitCardUrl, userRoomToken: memberUserRoomToken, - textures: memberTextures, position: { x: x, y: y, @@ -359,7 +418,7 @@ export class IoSocketController { bottom, left, }, - }, + } as UpgradeData, /* Spell these correctly */ websocketKey, websocketProtocol, @@ -374,7 +433,7 @@ export class IoSocketController { reason: e instanceof InvalidTokenError ? tokenInvalidException : null, message: e.message, roomId, - }, + } as UpgradeFailedData, websocketKey, websocketProtocol, websocketExtensions, @@ -387,7 +446,7 @@ export class IoSocketController { reason: null, message: "500 Internal Server Error", roomId, - }, + } as UpgradeFailedData, websocketKey, websocketProtocol, websocketExtensions, @@ -398,20 +457,23 @@ export class IoSocketController { })(); }, /* Handlers */ - open: (ws) => { + open: (_ws: WebSocket) => { + const ws = _ws as WebSocket & (UpgradeData | UpgradeFailedData); if (ws.rejected === true) { // If there is a room in the error, let's check if we need to clean it. if (ws.roomId) { - socketManager.deleteRoomIfEmptyFromId(ws.roomId as string); + socketManager.deleteRoomIfEmptyFromId(ws.roomId); } //FIX ME to use status code if (ws.reason === tokenInvalidException) { socketManager.emitTokenExpiredMessage(ws); + } else if (ws.reason === "textureInvalid") { + socketManager.emitInvalidTextureMessage(ws); } else if (ws.message === "World is full") { socketManager.emitWorldFullMessage(ws); } else { - socketManager.emitConnexionErrorMessage(ws, ws.message as string); + socketManager.emitConnexionErrorMessage(ws, ws.message); } setTimeout(() => ws.close(), 0); return; @@ -535,7 +597,6 @@ export class IoSocketController { client.name = ws.name; client.tags = ws.tags; client.visitCardUrl = ws.visitCardUrl; - client.textures = ws.textures; client.characterLayers = ws.characterLayers; client.companion = ws.companion; client.roomId = ws.roomId; diff --git a/pusher/src/Controller/WokaListController.ts b/pusher/src/Controller/WokaListController.ts index 851aa341..0ce10966 100644 --- a/pusher/src/Controller/WokaListController.ts +++ b/pusher/src/Controller/WokaListController.ts @@ -1,24 +1,13 @@ import { hasToken } from "../Middleware/HasToken"; import { BaseHttpController } from "./BaseHttpController"; -import { ADMIN_API_URL } from "../Enum/EnvironmentVariable"; -import { adminWokaService } from "..//Services/AdminWokaService"; -import { localWokaService } from "..//Services/LocalWokaService"; -import { WokaServiceInterface } from "src/Services/WokaServiceInterface"; -import { Server } from "hyper-express"; +import { wokaService } from "../Services/WokaService"; export class WokaListController extends BaseHttpController { - private wokaService: WokaServiceInterface; - - constructor(app: Server) { - super(app); - this.wokaService = ADMIN_API_URL ? adminWokaService : localWokaService; - } - routes() { // eslint-disable-next-line @typescript-eslint/no-misused-promises this.app.get("/woka-list", {}, async (req, res) => { const token = req.header("Authorization"); - const wokaList = await this.wokaService.getWokaList(token); + const wokaList = await wokaService.getWokaList(token); if (!wokaList) { return res.status(500).send("Error on getting woka list"); @@ -28,20 +17,20 @@ export class WokaListController extends BaseHttpController { }); // eslint-disable-next-line @typescript-eslint/no-misused-promises - this.app.post("/woka-details", async (req, res) => { + /*this.app.post("/woka-details", async (req, res) => { const body = await req.json(); if (!body || !body.textureIds) { return res.status(400); } const textureIds = body.textureIds; - const wokaDetails = await this.wokaService.fetchWokaDetails(textureIds); + const wokaDetails = await wokaService.fetchWokaDetails(textureIds); if (!wokaDetails) { return res.json({ details: [] }); } return res.json(wokaDetails); - }); + });*/ } } diff --git a/pusher/src/Enum/PlayerTextures.ts b/pusher/src/Enum/PlayerTextures.ts index 9b597cbc..cc7c43e1 100644 --- a/pusher/src/Enum/PlayerTextures.ts +++ b/pusher/src/Enum/PlayerTextures.ts @@ -49,16 +49,11 @@ export const isWokaDetail = new tg.IsInterface() id: tg.isString, }) .withOptionalProperties({ - texture: tg.isString, + url: tg.isString, + layer: tg.isString, }) .get(); export type WokaDetail = tg.GuardedType; -export const isWokaDetailsResult = new tg.IsInterface() - .withProperties({ - details: tg.isArray(isWokaDetail), - }) - .get(); - -export type WokaDetailsResult = tg.GuardedType; +export type WokaDetailsResult = WokaDetail[]; diff --git a/pusher/src/Model/Websocket/ExSocketInterface.ts b/pusher/src/Model/Websocket/ExSocketInterface.ts index 13045a11..b5815635 100644 --- a/pusher/src/Model/Websocket/ExSocketInterface.ts +++ b/pusher/src/Model/Websocket/ExSocketInterface.ts @@ -13,14 +13,10 @@ import { ClientDuplexStream } from "grpc"; import { Zone } from "_Model/Zone"; import { CharacterTexture } from "../../Messages/JsonMessages/CharacterTexture"; import { compressors } from "hyper-express"; +import { WokaDetail } from "_Enum/PlayerTextures"; export type BackConnection = ClientDuplexStream; -export interface CharacterLayer { - name: string; - url: string | undefined; -} - export interface ExSocketInterface extends compressors.WebSocket, Identificable { token: string; roomId: string; @@ -28,7 +24,7 @@ export interface ExSocketInterface extends compressors.WebSocket, Identificable userUuid: string; // A unique identifier for this user IPAddress: string; // IP address name: string; - characterLayers: CharacterLayer[]; + characterLayers: WokaDetail[]; position: PointInterface; viewport: ViewportInterface; companion?: CompanionMessage; @@ -42,7 +38,6 @@ export interface ExSocketInterface extends compressors.WebSocket, Identificable messages: unknown; tags: string[]; visitCardUrl: string | null; - textures: CharacterTexture[]; backConnection: BackConnection; listenedZones: Set; userRoomToken: string | undefined; diff --git a/pusher/src/Model/Websocket/ProtobufUtils.ts b/pusher/src/Model/Websocket/ProtobufUtils.ts index bd9cb9c2..daf2aeb8 100644 --- a/pusher/src/Model/Websocket/ProtobufUtils.ts +++ b/pusher/src/Model/Websocket/ProtobufUtils.ts @@ -5,10 +5,11 @@ import { PointMessage, PositionMessage, } from "../../Messages/generated/messages_pb"; -import { CharacterLayer, ExSocketInterface } from "_Model/Websocket/ExSocketInterface"; +import { ExSocketInterface } from "_Model/Websocket/ExSocketInterface"; import Direction = PositionMessage.Direction; import { ItemEventMessageInterface } from "_Model/Websocket/ItemEventMessage"; import { PositionInterface } from "_Model/PositionInterface"; +import { WokaDetail } from "_Enum/PlayerTextures"; export class ProtobufUtils { public static toPositionMessage(point: PointInterface): PositionMessage { @@ -94,13 +95,16 @@ export class ProtobufUtils { return itemEventMessage; } - public static toCharacterLayerMessages(characterLayers: CharacterLayer[]): CharacterLayerMessage[] { + public static toCharacterLayerMessages(characterLayers: WokaDetail[]): CharacterLayerMessage[] { return characterLayers.map(function (characterLayer): CharacterLayerMessage { const message = new CharacterLayerMessage(); - message.setName(characterLayer.name); + message.setName(characterLayer.id); if (characterLayer.url) { message.setUrl(characterLayer.url); } + if (characterLayer.layer) { + message.setLayer(characterLayer.layer); + } return message; }); } diff --git a/pusher/src/Services/AdminApi.ts b/pusher/src/Services/AdminApi.ts index d3b4d414..540939e5 100644 --- a/pusher/src/Services/AdminApi.ts +++ b/pusher/src/Services/AdminApi.ts @@ -1,25 +1,33 @@ import { ADMIN_API_TOKEN, ADMIN_API_URL, ADMIN_URL, OPID_PROFILE_SCREEN_PROVIDER } from "../Enum/EnvironmentVariable"; -import Axios from "axios"; -import { CharacterTexture } from "../Messages/JsonMessages/CharacterTexture"; +import Axios, { AxiosResponse } from "axios"; import { MapDetailsData } from "../Messages/JsonMessages/MapDetailsData"; import { RoomRedirect } from "../Messages/JsonMessages/RoomRedirect"; import { AdminApiData, isAdminApiData } from "../Messages/JsonMessages/AdminApiData"; +import * as tg from "generic-type-guard"; +import { isNumber } from "generic-type-guard"; +import { isWokaDetail } from "../Enum/PlayerTextures"; export interface AdminBannedData { is_banned: boolean; message: string; } -export interface FetchMemberDataByUuidResponse { - email: string; - userUuid: string; - tags: string[]; - visitCardUrl: string | null; - textures: CharacterTexture[]; - messages: unknown[]; - anonymous?: boolean; - userRoomToken: string | undefined; -} +const isFetchMemberDataByUuidResponse = new tg.IsInterface() + .withProperties({ + email: tg.isString, + userUuid: tg.isString, + tags: tg.isArray(tg.isString), + visitCardUrl: tg.isNullable(tg.isString), + textures: tg.isArray(isWokaDetail), + messages: tg.isArray(tg.isUnknown), + }) + .withOptionalProperties({ + anonymous: tg.isBoolean, + userRoomToken: tg.isString, + }) + .get(); + +export type FetchMemberDataByUuidResponse = tg.GuardedType; class AdminApi { /** @@ -52,10 +60,16 @@ class AdminApi { if (!ADMIN_API_URL) { return Promise.reject(new Error("No admin backoffice set!")); } - const res = await Axios.get(ADMIN_API_URL + "/api/room/access", { + const res = await Axios.get>(ADMIN_API_URL + "/api/room/access", { params: { userIdentifier, roomId, ipAddress }, headers: { Authorization: `${ADMIN_API_TOKEN}` }, }); + if (!isFetchMemberDataByUuidResponse(res.data)) { + throw new Error( + "Invalid answer received from the admin for the /api/map endpoint. Received: " + + JSON.stringify(res.data) + ); + } return res.data; } diff --git a/pusher/src/Services/AdminWokaService.ts b/pusher/src/Services/AdminWokaService.ts index 0598fb2a..a14458bc 100644 --- a/pusher/src/Services/AdminWokaService.ts +++ b/pusher/src/Services/AdminWokaService.ts @@ -1,6 +1,6 @@ import axios from "axios"; import { ADMIN_API_TOKEN, ADMIN_API_URL } from "../Enum/EnvironmentVariable"; -import { isWokaDetailsResult, isWokaList, WokaDetailsResult, WokaList } from "../Enum/PlayerTextures"; +import { isWokaList, WokaList } from "../Enum/PlayerTextures"; import { WokaServiceInterface } from "./WokaServiceInterface"; class AdminWokaService implements WokaServiceInterface { @@ -32,7 +32,7 @@ class AdminWokaService implements WokaServiceInterface { * * If one of the textures cannot be found, undefined is returned */ - fetchWokaDetails(textureIds: string[]): Promise { + /*fetchWokaDetails(textureIds: string[]): Promise { return axios .post( `${ADMIN_API_URL}/api/woka-details`, @@ -49,11 +49,11 @@ class AdminWokaService implements WokaServiceInterface { } const result: WokaDetailsResult = res.data; - if (result.details.length !== textureIds.length) { + if (result.length !== textureIds.length) { return undefined; } - for (const detail of result.details) { + for (const detail of result) { if (!detail.texture) { return undefined; } @@ -65,7 +65,7 @@ class AdminWokaService implements WokaServiceInterface { console.error(`Cannot get woka details from admin API with ids: ${textureIds}`, err); return undefined; }); - } + }*/ } export const adminWokaService = new AdminWokaService(); diff --git a/pusher/src/Services/LocalWokaService.ts b/pusher/src/Services/LocalWokaService.ts index 3356c48d..0f96fc3c 100644 --- a/pusher/src/Services/LocalWokaService.ts +++ b/pusher/src/Services/LocalWokaService.ts @@ -23,7 +23,13 @@ class LocalWokaService implements WokaServiceInterface { */ async fetchWokaDetails(textureIds: string[]): Promise { const wokaData: WokaList = await require("../../data/woka.json"); - const textures = new Map(); + const textures = new Map< + string, + { + url: string; + layer: string; + } + >(); const searchIds = new Set(textureIds); for (const part of wokaPartNames) { @@ -37,7 +43,10 @@ class LocalWokaService implements WokaServiceInterface { const texture = collection.textures.find((texture) => texture.id === id); if (texture) { - textures.set(id, texture.url); + textures.set(id, { + url: texture.url, + layer: part, + }); searchIds.delete(id); } } @@ -53,11 +62,12 @@ class LocalWokaService implements WokaServiceInterface { textures.forEach((value, key) => { details.push({ id: key, - texture: value, + url: value.url, + layer: value.layer, }); }); - return { details }; + return details; } } diff --git a/pusher/src/Services/SocketManager.ts b/pusher/src/Services/SocketManager.ts index b67c359d..981c580a 100644 --- a/pusher/src/Services/SocketManager.ts +++ b/pusher/src/Services/SocketManager.ts @@ -1,5 +1,5 @@ import { PusherRoom } from "../Model/PusherRoom"; -import { CharacterLayer, ExSocketInterface } from "../Model/Websocket/ExSocketInterface"; +import { ExSocketInterface } from "../Model/Websocket/ExSocketInterface"; import { AdminMessage, AdminPusherToBackMessage, @@ -38,6 +38,7 @@ import { ErrorMessage, WorldFullMessage, PlayerDetailsUpdatedMessage, + InvalidTextureMessage, } from "../Messages/generated/messages_pb"; import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils"; import { ADMIN_API_URL, JITSI_ISS, JITSI_URL, SECRET_JITSI_KEY } from "../Enum/EnvironmentVariable"; @@ -52,7 +53,7 @@ import Debug from "debug"; import { ExAdminSocketInterface } from "_Model/Websocket/ExAdminSocketInterface"; import { WebSocket } from "uWebSockets.js"; import { isRoomRedirect } from "../Messages/JsonMessages/RoomRedirect"; -import { CharacterTexture } from "../Messages/JsonMessages/CharacterTexture"; +//import { CharacterTexture } from "../Messages/JsonMessages/CharacterTexture"; import { compressors } from "hyper-express"; const debug = Debug("socket"); @@ -175,10 +176,13 @@ export class SocketManager implements ZoneEventListener { for (const characterLayer of client.characterLayers) { const characterLayerMessage = new CharacterLayerMessage(); - characterLayerMessage.setName(characterLayer.name); + characterLayerMessage.setName(characterLayer.id); if (characterLayer.url !== undefined) { characterLayerMessage.setUrl(characterLayer.url); } + if (characterLayer.layer !== undefined) { + characterLayerMessage.setLayer(characterLayer.layer); + } joinRoomMessage.addCharacterlayer(characterLayerMessage); } @@ -545,36 +549,6 @@ export class SocketManager implements ZoneEventListener { }); } - /** - * Merges the characterLayers received from the front (as an array of string) with the custom textures from the back. - */ - static mergeCharacterLayersAndCustomTextures( - characterLayers: string[], - memberTextures: CharacterTexture[] - ): CharacterLayer[] { - const characterLayerObjs: CharacterLayer[] = []; - for (const characterLayer of characterLayers) { - if (characterLayer.startsWith("customCharacterTexture")) { - const customCharacterLayerId: number = +characterLayer.substr(22); - for (const memberTexture of memberTextures) { - if (memberTexture.id == customCharacterLayerId) { - characterLayerObjs.push({ - name: characterLayer, - url: memberTexture.url, - }); - break; - } - } - } else { - characterLayerObjs.push({ - name: characterLayer, - url: undefined, - }); - } - } - return characterLayerObjs; - } - public onUserEnters(user: UserDescriptor, listener: ExSocketInterface): void { const subMessage = new SubMessage(); subMessage.setUserjoinedmessage(user.toUserJoinedMessage()); @@ -642,6 +616,17 @@ export class SocketManager implements ZoneEventListener { } } + public emitInvalidTextureMessage(client: compressors.WebSocket) { + const errorMessage = new InvalidTextureMessage(); + + const serverToClientMessage = new ServerToClientMessage(); + serverToClientMessage.setInvalidtexturemessage(errorMessage); + + if (!client.disconnecting) { + client.send(serverToClientMessage.serializeBinary().buffer, true); + } + } + public emitConnexionErrorMessage(client: compressors.WebSocket, message: string) { const errorMessage = new WorldConnexionMessage(); errorMessage.setMessage(message); diff --git a/pusher/src/Services/WokaService.ts b/pusher/src/Services/WokaService.ts new file mode 100644 index 00000000..1944ab69 --- /dev/null +++ b/pusher/src/Services/WokaService.ts @@ -0,0 +1,5 @@ +import { ADMIN_API_URL } from "../Enum/EnvironmentVariable"; +import { adminWokaService } from "./AdminWokaService"; +import { localWokaService } from "./LocalWokaService"; + +export const wokaService = ADMIN_API_URL ? adminWokaService : localWokaService; diff --git a/pusher/src/Services/WokaServiceInterface.ts b/pusher/src/Services/WokaServiceInterface.ts index 71ee7202..7165a209 100644 --- a/pusher/src/Services/WokaServiceInterface.ts +++ b/pusher/src/Services/WokaServiceInterface.ts @@ -14,5 +14,5 @@ export interface WokaServiceInterface { * * If one of the textures cannot be found, undefined is returned (and the user should be redirected to Woka choice page!) */ - fetchWokaDetails(textureIds: string[]): Promise; + //fetchWokaDetails(textureIds: string[]): Promise; }