diff --git a/back/src/Controller/AuthenticateController.ts b/back/src/Controller/AuthenticateController.ts index db7cfb9f..bf68768d 100644 --- a/back/src/Controller/AuthenticateController.ts +++ b/back/src/Controller/AuthenticateController.ts @@ -31,7 +31,6 @@ export class AuthenticateController extends BaseController { res.onAborted(() => { console.warn('Login request was aborted'); }) - const host = req.getHeader('host'); const param = await res.json(); //todo: what to do if the organizationMemberToken is already used? @@ -45,6 +44,7 @@ export class AuthenticateController extends BaseController { const worldSlug = data.worldSlug; const roomSlug = data.roomSlug; const mapUrlStart = data.mapUrlStart; + const textures = data.textures; const authToken = jwtTokenManager.createJWTToken(userUuid); res.writeStatus("200 OK"); @@ -56,6 +56,7 @@ export class AuthenticateController extends BaseController { worldSlug, roomSlug, mapUrlStart, + textures })); } catch (e) { diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index 49b08bb7..f6253be5 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -1,4 +1,4 @@ -import {ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.." +import {CharacterLayer, ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; //TODO fix import by "_Model/.." import {GameRoomPolicyTypes} from "../Model/GameRoom"; import {PointInterface} from "../Model/Websocket/PointInterface"; import { @@ -18,10 +18,9 @@ import {UserMovesMessage} from "../Messages/generated/messages_pb"; import {TemplatedApp} from "uWebSockets.js" import {parse} from "query-string"; import {jwtTokenManager} from "../Services/JWTTokenManager"; -import {adminApi, fetchMemberDataByUuidResponse} from "../Services/AdminApi"; -import {socketManager} from "../Services/SocketManager"; +import {adminApi, CharacterTexture, FetchMemberDataByUuidResponse} from "../Services/AdminApi"; +import {SocketManager, socketManager} from "../Services/SocketManager"; import {emitInBatch, resetPing} from "../Services/IoSocketHelpers"; -import Jwt from "jsonwebtoken"; import {clientEventsEmitter} from "../Services/ClientEventsEmitter"; import {ADMIN_API_TOKEN} from "../Enum/EnvironmentVariable"; @@ -159,25 +158,28 @@ export class IoSocketController { characterLayers = [ characterLayers ]; } - const userUuid = await jwtTokenManager.getUserUuidFromToken(token); let memberTags: string[] = []; + let memberTextures: CharacterTexture[] = []; const room = await socketManager.getOrCreateRoom(roomId); - if (!room.anonymous && room.policyType !== GameRoomPolicyTypes.ANONYMUS_POLICY) { - try { - const userData = await adminApi.fetchMemberDataByUuid(userUuid); - memberTags = userData.tags; - if (room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY && !room.canAccess(memberTags)) { - throw new Error('No correct tags') - } - console.log('access granted for user '+userUuid+' and room '+roomId); - } catch (e) { - console.log('access not granted for user '+userUuid+' and room '+roomId); - throw new Error('Client cannot acces this ressource.') + try { + const userData = await adminApi.fetchMemberDataByUuid(userUuid); + //console.log('USERDATA', userData) + memberTags = userData.tags; + memberTextures = userData.textures; + if (!room.anonymous && room.policyType === GameRoomPolicyTypes.USE_TAGS_POLICY && !room.canAccess(memberTags)) { + throw new Error('No correct tags') } + //console.log('access granted for user '+userUuid+' and room '+roomId); + } catch (e) { + console.log('access not granted for user '+userUuid+' and room '+roomId); + throw new Error('Client cannot acces this ressource.') } + // Generate characterLayers objects from characterLayers string[] + const characterLayerObjs: CharacterLayer[] = SocketManager.mergeCharacterLayersAndCustomTextures(characterLayers, memberTextures); + if (upgradeAborted.aborted) { console.log("Ouch! Client disconnected before we could upgrade it!"); /* You must not upgrade now */ @@ -192,8 +194,9 @@ export class IoSocketController { userUuid, roomId, name, - characterLayers, + characterLayers: characterLayerObjs, tags: memberTags, + textures: memberTextures, position: { x: x, y: y, @@ -233,7 +236,7 @@ export class IoSocketController { resetPing(client); //get data information and shwo messages - adminApi.fetchMemberDataByUuid(client.userUuid).then((res: fetchMemberDataByUuidResponse) => { + adminApi.fetchMemberDataByUuid(client.userUuid).then((res: FetchMemberDataByUuidResponse) => { if (!res.messages) { return; } @@ -311,6 +314,7 @@ export class IoSocketController { client.name = ws.name; client.tags = ws.tags; + client.textures = ws.textures; client.characterLayers = ws.characterLayers; client.roomId = ws.roomId; return client; diff --git a/back/src/Model/Websocket/ExSocketInterface.ts b/back/src/Model/Websocket/ExSocketInterface.ts index dd897e1c..205032bc 100644 --- a/back/src/Model/Websocket/ExSocketInterface.ts +++ b/back/src/Model/Websocket/ExSocketInterface.ts @@ -3,6 +3,12 @@ import {Identificable} from "./Identificable"; import {ViewportInterface} from "_Model/Websocket/ViewportMessage"; import {BatchMessage, SubMessage} from "../../Messages/generated/messages_pb"; import {WebSocket} from "uWebSockets.js" +import {CharacterTexture} from "../../Services/AdminApi"; + +export interface CharacterLayer { + name: string, + url: string|undefined +} export interface ExSocketInterface extends WebSocket, Identificable { token: string; @@ -10,7 +16,7 @@ export interface ExSocketInterface extends WebSocket, Identificable { //userId: number; // A temporary (autoincremented) identifier for this user userUuid: string; // A unique identifier for this user name: string; - characterLayers: string[]; + characterLayers: CharacterLayer[]; position: PointInterface; viewport: ViewportInterface; /** @@ -21,5 +27,6 @@ export interface ExSocketInterface extends WebSocket, Identificable { batchTimeout: NodeJS.Timeout|null; pingTimeout: NodeJS.Timeout|null; disconnecting: boolean, - tags: string[] + tags: string[], + textures: CharacterTexture[], } diff --git a/back/src/Model/Websocket/ProtobufUtils.ts b/back/src/Model/Websocket/ProtobufUtils.ts index 42adbd4c..c31eb9a8 100644 --- a/back/src/Model/Websocket/ProtobufUtils.ts +++ b/back/src/Model/Websocket/ProtobufUtils.ts @@ -1,6 +1,11 @@ import {PointInterface} from "./PointInterface"; -import {ItemEventMessage, PointMessage, PositionMessage} from "../../Messages/generated/messages_pb"; -import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface"; +import { + CharacterLayerMessage, + ItemEventMessage, + PointMessage, + PositionMessage +} from "../../Messages/generated/messages_pb"; +import {CharacterLayer, ExSocketInterface} from "_Model/Websocket/ExSocketInterface"; import Direction = PositionMessage.Direction; import {ItemEventMessageInterface} from "_Model/Websocket/ItemEventMessage"; import {PositionInterface} from "_Model/PositionInterface"; @@ -89,4 +94,15 @@ export class ProtobufUtils { return itemEventMessage; } + + public static toCharacterLayerMessages(characterLayers: CharacterLayer[]): CharacterLayerMessage[] { + return characterLayers.map(function(characterLayer): CharacterLayerMessage { + const message = new CharacterLayerMessage(); + message.setName(characterLayer.name); + if (characterLayer.url) { + message.setUrl(characterLayer.url); + } + return message; + }); + } } diff --git a/back/src/Services/AdminApi.ts b/back/src/Services/AdminApi.ts index 9f51fb2e..9c46a41b 100644 --- a/back/src/Services/AdminApi.ts +++ b/back/src/Services/AdminApi.ts @@ -1,5 +1,6 @@ import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable"; import Axios from "axios"; +import {v4} from "uuid"; export interface AdminApiData { organizationSlug: string @@ -9,12 +10,21 @@ export interface AdminApiData { tags: string[] policy_type: number userUuid: string - messages?: unknown[] + messages?: unknown[], + textures: CharacterTexture[] } -export interface fetchMemberDataByUuidResponse { +export interface CharacterTexture { + id: number, + level: number, + url: string, + rights: string +} + +export interface FetchMemberDataByUuidResponse { uuid: string; tags: string[]; + textures: CharacterTexture[]; messages: unknown[]; } @@ -43,16 +53,31 @@ class AdminApi { return res.data; } - async fetchMemberDataByUuid(uuid: string): Promise { + async fetchMemberDataByUuid(uuid: string): Promise { if (!ADMIN_API_URL) { return Promise.reject('No admin backoffice set!'); } - const res = await Axios.get(ADMIN_API_URL+'/api/membership/'+uuid, - { headers: {"Authorization" : `${ADMIN_API_TOKEN}`} } - ) - return res.data; + try { + const res = await Axios.get(ADMIN_API_URL+'/api/membership/'+uuid, + { headers: {"Authorization" : `${ADMIN_API_TOKEN}`} } + ) + return res.data; + } catch (e) { + if (e?.response?.status == 404) { + // If we get an HTTP 404, the token is invalid. Let's perform an anonymous login! + console.warn('Cannot find user with uuid "'+uuid+'". Performing an anonymous login instead.'); + return { + uuid: v4(), + tags: [], + textures: [], + messages: [], + } + } else { + throw e; + } + } } - + async fetchMemberDataByToken(organizationMemberToken: string): Promise { if (!ADMIN_API_URL) { return Promise.reject('No admin backoffice set!'); @@ -74,7 +99,7 @@ class AdminApi { ) return res.data; } - + reportPlayer(reportedUserUuid: string, reportedUserComment: string, reporterUserUuid: string) { return Axios.post(`${ADMIN_API_URL}/api/report`, { reportedUserUuid, diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index 9fd343be..50bd149e 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -1,5 +1,5 @@ import {GameRoom} from "../Model/GameRoom"; -import {ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; +import {CharacterLayer, ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; import { GroupDeleteMessage, GroupUpdateMessage, @@ -23,6 +23,7 @@ import { WebRtcStartMessage, QueryJitsiJwtMessage, SendJitsiJwtMessage, + CharacterLayerMessage, SendUserMessage } from "../Messages/generated/messages_pb"; import {PointInterface} from "../Model/Websocket/PointInterface"; @@ -34,7 +35,7 @@ import {isSetPlayerDetailsMessage} from "../Model/Websocket/SetPlayerDetailsMess import {GROUP_RADIUS, JITSI_ISS, MINIMUM_DISTANCE, SECRET_JITSI_KEY} from "../Enum/EnvironmentVariable"; import {Movable} from "../Model/Movable"; import {PositionInterface} from "../Model/PositionInterface"; -import {adminApi} from "./AdminApi"; +import {adminApi, CharacterTexture} from "./AdminApi"; import Direction = PositionMessage.Direction; import {Gauge} from "prom-client"; import {emitError, emitInBatch} from "./IoSocketHelpers"; @@ -54,7 +55,7 @@ export interface AdminSocketData { users: AdminSocketUsersList, } -class SocketManager { +export class SocketManager { private Worlds: Map = new Map(); private sockets: Map = new Map(); private nbClientsGauge: Gauge; @@ -71,7 +72,7 @@ class SocketManager { help: 'Number of clients per room', labelNames: [ 'room' ] }); - + clientEventsEmitter.registerToClientJoin((clientUUid, roomId) => { this.nbClientsGauge.inc(); // Let's log server load when a user joins @@ -83,7 +84,7 @@ class SocketManager { console.log('A user left (', this.sockets.size, ' connected users)'); }); } - + getAdminSocketDataFor(roomId:string): AdminSocketData { const data:AdminSocketData = { rooms: {}, @@ -125,7 +126,7 @@ class SocketManager { const userJoinedMessage = new UserJoinedMessage(); userJoinedMessage.setUserid(thing.id); userJoinedMessage.setName(player.name); - userJoinedMessage.setCharacterlayersList(player.characterLayers); + userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(player.characterLayers)); userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(player.position)); roomJoinedMessage.addUser(userJoinedMessage); @@ -251,8 +252,7 @@ class SocketManager { return; } client.name = playerDetails.name; - client.characterLayers = playerDetails.characterLayers; - + client.characterLayers = SocketManager.mergeCharacterLayersAndCustomTextures(playerDetails.characterLayers, client.textures); } handleSilentMessage(client: ExSocketInterface, silentMessage: SilentMessage) { @@ -438,7 +438,7 @@ class SocketManager { } userJoinedMessage.setUserid(clientUser.userId); userJoinedMessage.setName(clientUser.name); - userJoinedMessage.setCharacterlayersList(clientUser.characterLayers); + userJoinedMessage.setCharacterlayersList(ProtobufUtils.toCharacterLayerMessages(clientUser.characterLayers)); userJoinedMessage.setPosition(ProtobufUtils.toPositionMessage(clientUser.position)); const subMessage = new SubMessage(); @@ -691,6 +691,33 @@ class SocketManager { } return socket; } + + /** + * 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; + } } export const socketManager = new SocketManager(); diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts index 26432df5..971903f1 100644 --- a/front/src/Connexion/ConnectionManager.ts +++ b/front/src/Connexion/ConnectionManager.ts @@ -21,7 +21,7 @@ class ConnectionManager { if(connexionType === GameConnexionTypes.register) { const organizationMemberToken = urlManager.getOrganizationToken(); const data = await Axios.post(`${API_URL}/register`, {organizationMemberToken}).then(res => res.data); - this.localUser = new LocalUser(data.userUuid, data.authToken); + this.localUser = new LocalUser(data.userUuid, data.authToken, data.textures); localUserStore.saveUser(this.localUser); const organizationSlug = data.organizationSlug; @@ -34,7 +34,7 @@ class ConnectionManager { } else if (connexionType === GameConnexionTypes.anonymous || connexionType === GameConnexionTypes.empty) { const localUser = localUserStore.getLocalUser(); - if (localUser && localUser.jwtToken && localUser.uuid) { + if (localUser && localUser.jwtToken && localUser.uuid && localUser.textures) { this.localUser = localUser; try { await this.verifyToken(localUser.jwtToken); @@ -78,12 +78,12 @@ class ConnectionManager { private async anonymousLogin(): Promise { const data = await Axios.post(`${API_URL}/anonymLogin`).then(res => res.data); - this.localUser = new LocalUser(data.userUuid, data.authToken); + this.localUser = new LocalUser(data.userUuid, data.authToken, []); localUserStore.saveUser(this.localUser); } public initBenchmark(): void { - this.localUser = new LocalUser('', 'test'); + this.localUser = new LocalUser('', 'test', []); } public connectToRoomSocket(roomId: string, name: string, characterLayers: string[], position: PositionInterface, viewport: ViewportInterface): Promise { diff --git a/front/src/Connexion/ConnexionModels.ts b/front/src/Connexion/ConnexionModels.ts index fd2149c5..d1d80aff 100644 --- a/front/src/Connexion/ConnexionModels.ts +++ b/front/src/Connexion/ConnexionModels.ts @@ -1,6 +1,7 @@ import {PlayerAnimationNames} from "../Phaser/Player/Animation"; import {UserSimplePeerInterface} from "../WebRtc/SimplePeer"; import {SignalData} from "simple-peer"; +import {BodyResourceDescriptionInterface} from "../Phaser/Entity/body_character"; export enum EventMessage{ WEBRTC_SIGNAL = "webrtc-signal", @@ -49,7 +50,7 @@ export class Point implements PointInterface{ export interface MessageUserPositionInterface { userId: number; name: string; - characterLayers: string[]; + characterLayers: BodyResourceDescriptionInterface[]; position: PointInterface; } @@ -61,7 +62,7 @@ export interface MessageUserMovedInterface { export interface MessageUserJoined { userId: number; name: string; - characterLayers: string[]; + characterLayers: BodyResourceDescriptionInterface[]; position: PointInterface } diff --git a/front/src/Connexion/LocalUser.ts b/front/src/Connexion/LocalUser.ts index 1411f66c..06d98b70 100644 --- a/front/src/Connexion/LocalUser.ts +++ b/front/src/Connexion/LocalUser.ts @@ -1,9 +1,11 @@ +export interface CharacterTexture { + id: number, + level: number, + url: string, + rights: string +} + export class LocalUser { - public uuid: string; - public jwtToken: string; - - constructor(uuid:string, jwtToken: string) { - this.uuid = uuid; - this.jwtToken = jwtToken; + constructor(public readonly uuid:string, public readonly jwtToken: string, public readonly textures: CharacterTexture[]) { } -} \ No newline at end of file +} diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 9d04cedd..7c57558a 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -22,7 +22,11 @@ import { WebRtcSignalToServerMessage, WebRtcStartMessage, ReportPlayerMessage, - TeleportMessageMessage, QueryJitsiJwtMessage, SendJitsiJwtMessage, SendUserMessage + TeleportMessageMessage, + QueryJitsiJwtMessage, + SendJitsiJwtMessage, + CharacterLayerMessage, + SendUserMessage } from "../Messages/generated/messages_pb" import {UserSimplePeerInterface} from "../WebRtc/SimplePeer"; @@ -36,6 +40,7 @@ import { ViewportInterface, WebRtcDisconnectMessageInterface, WebRtcSignalReceivedMessageInterface, } from "./ConnexionModels"; +import {BodyResourceDescriptionInterface} from "../Phaser/Entity/body_character"; export class RoomConnection implements RoomConnection { private readonly socket: WebSocket; @@ -169,10 +174,10 @@ export class RoomConnection implements RoomConnection { } } - public emitPlayerDetailsMessage(userName: string, characterLayersSelected: string[]) { + public emitPlayerDetailsMessage(userName: string, characterLayersSelected: BodyResourceDescriptionInterface[]) { const message = new SetPlayerDetailsMessage(); message.setName(userName); - message.setCharacterlayersList(characterLayersSelected); + message.setCharacterlayersList(characterLayersSelected.map((characterLayer) => characterLayer.name)); const clientToServerMessage = new ClientToServerMessage(); clientToServerMessage.setSetplayerdetailsmessage(message); @@ -277,10 +282,18 @@ export class RoomConnection implements RoomConnection { if (position === undefined) { throw new Error('Invalid JOIN_ROOM message'); } + + const characterLayers = message.getCharacterlayersList().map((characterLayer: CharacterLayerMessage): BodyResourceDescriptionInterface => { + return { + name: characterLayer.getName(), + img: characterLayer.getUrl() + } + }) + return { userId: message.getUserid(), name: message.getName(), - characterLayers: message.getCharacterlayersList(), + characterLayers, position: ProtobufClientUtils.toPointInterface(position) } } diff --git a/front/src/Phaser/Entity/Character.ts b/front/src/Phaser/Entity/Character.ts index 6f6f769e..a1ed30d5 100644 --- a/front/src/Phaser/Entity/Character.ts +++ b/front/src/Phaser/Entity/Character.ts @@ -61,22 +61,7 @@ export abstract class Character extends Container { this.sprites = new Map(); - for (const texture of textures) { - const sprite = new Sprite(scene, 0, 0, texture, frame); - sprite.setInteractive({useHandCursor: true}); - this.add(sprite); - this.getPlayerAnimations(texture).forEach(d => { - this.scene.anims.create({ - key: d.key, - frames: this.scene.anims.generateFrameNumbers(d.frameModel, {start: d.frameStart, end: d.frameEnd}), - frameRate: d.frameRate, - repeat: d.repeat - }); - }) - // Needed, otherwise, animations are not handled correctly. - this.scene.sys.updateList.add(sprite); - this.sprites.set(texture, sprite); - } + this.addTextures(textures, frame); /*this.teleportation = new Sprite(scene, -20, -10, 'teleportation', 3); this.teleportation.setInteractive(); @@ -107,6 +92,25 @@ export abstract class Character extends Container { this.playAnimation(direction, moving); } + public addTextures(textures: string[], frame?: string | number): void { + for (const texture of textures) { + const sprite = new Sprite(this.scene, 0, 0, texture, frame); + sprite.setInteractive({useHandCursor: true}); + this.add(sprite); + this.getPlayerAnimations(texture).forEach(d => { + this.scene.anims.create({ + key: d.key, + frames: this.scene.anims.generateFrameNumbers(d.frameModel, {start: d.frameStart, end: d.frameEnd}), + frameRate: d.frameRate, + repeat: d.repeat + }); + }) + // Needed, otherwise, animations are not handled correctly. + this.scene.sys.updateList.add(sprite); + this.sprites.set(texture, sprite); + } + } + private getPlayerAnimations(name: string): AnimationData[] { return [{ key: `${name}-${PlayerAnimationNames.WalkDown}`, diff --git a/front/src/Phaser/Entity/body_character.ts b/front/src/Phaser/Entity/body_character.ts index 6fbeaadb..fd5fa467 100644 --- a/front/src/Phaser/Entity/body_character.ts +++ b/front/src/Phaser/Entity/body_character.ts @@ -1,5 +1,6 @@ import LoaderPlugin = Phaser.Loader.LoaderPlugin; import {PLAYER_RESOURCES, PlayerResourceDescriptionInterface} from "./Character"; +import {CharacterTexture} from "../../Connexion/LocalUser"; export interface BodyResourceDescriptionInterface { name: string, @@ -312,6 +313,15 @@ export const loadAllLayers = (load: LoaderPlugin) => { } } +export const loadCustomTexture = (load: LoaderPlugin, texture: CharacterTexture) => { + const name = 'customCharacterTexture'+texture.id; + load.spritesheet( + name, + texture.url, + {frameWidth: 32, frameHeight: 32} + ); +} + export const OBJECTS: Array = [ {name:'layout_modes', img:'resources/objects/layout_modes.png'}, {name:'teleportation', img:'resources/objects/teleportation.png'}, diff --git a/front/src/Phaser/Game/AddPlayerInterface.ts b/front/src/Phaser/Game/AddPlayerInterface.ts index 91563dd0..e0c7df0f 100644 --- a/front/src/Phaser/Game/AddPlayerInterface.ts +++ b/front/src/Phaser/Game/AddPlayerInterface.ts @@ -1,8 +1,9 @@ import {PointInterface} from "../../Connexion/ConnexionModels"; +import {BodyResourceDescriptionInterface} from "../Entity/body_character"; export interface AddPlayerInterface { userId: number; name: string; - characterLayers: string[]; + characterLayers: BodyResourceDescriptionInterface[]; position: PointInterface; } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index f82a6ce2..b8632b06 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -32,7 +32,7 @@ import {RemotePlayer} from "../Entity/RemotePlayer"; import {Queue} from 'queue-typescript'; import {SimplePeer, UserSimplePeerInterface} from "../../WebRtc/SimplePeer"; import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene"; -import {loadAllLayers, loadObject, loadPlayerCharacters} from "../Entity/body_character"; +import {loadAllLayers, loadCustomTexture, loadObject, loadPlayerCharacters} from "../Entity/body_character"; import {CenterListener, layoutManager, LayoutMode} from "../../WebRtc/LayoutManager"; import Texture = Phaser.Textures.Texture; import Sprite = Phaser.GameObjects.Sprite; @@ -475,7 +475,6 @@ export class GameScene extends ResizableScene implements CenterListener { if (newValue === undefined) { this.stopJitsi(); } else { - console.log("JITSI_PRIVATE_MODE", JITSI_PRIVATE_MODE); if (JITSI_PRIVATE_MODE) { const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined; @@ -1022,7 +1021,7 @@ export class GameScene extends ResizableScene implements CenterListener { /** * Create new player */ - private doAddPlayer(addPlayerData : AddPlayerInterface) : void { + private async doAddPlayer(addPlayerData : AddPlayerInterface) : Promise { //check if exist player, if exist, move position if(this.MapPlayersByKey.has(addPlayerData.userId)){ this.updatePlayerPosition({ @@ -1031,6 +1030,20 @@ export class GameScene extends ResizableScene implements CenterListener { }); return; } + // Load textures (in case it is a custom texture) + const characterLayerList: string[] = []; + const loadPromises: Promise[] = []; + for (const characterLayer of addPlayerData.characterLayers) { + characterLayerList.push(characterLayer.name); + if (characterLayer.img) { + console.log('LOADING ', characterLayer.name, characterLayer.img) + loadPromises.push(this.loadSpritesheet(characterLayer.name, characterLayer.img)); + } + } + if (loadPromises.length > 0) { + this.load.start(); + } + //initialise player const player = new RemotePlayer( addPlayerData.userId, @@ -1038,7 +1051,7 @@ export class GameScene extends ResizableScene implements CenterListener { addPlayerData.position.x, addPlayerData.position.y, addPlayerData.name, - addPlayerData.characterLayers, + [], // Let's go with no textures and let's load textures when promises have returned. addPlayerData.position.direction, addPlayerData.position.moving ); @@ -1046,10 +1059,15 @@ export class GameScene extends ResizableScene implements CenterListener { this.MapPlayersByKey.set(player.userId, player); player.updatePosition(addPlayerData.position); + + await Promise.all(loadPromises); + + player.addTextures(characterLayerList, 1); //init collision /*this.physics.add.collider(this.CurrentPlayer, player, (CurrentPlayer: CurrentGamerInterface, MapPlayer: GamerInterface) => { CurrentPlayer.say("Hello, how are you ? "); });*/ + } /** @@ -1245,4 +1263,18 @@ export class GameScene extends ResizableScene implements CenterListener { CoWebsiteManager.closeCoWebsite(); mediaManager.showGameOverlay(); } + + private loadSpritesheet(name: string, url: string): Promise { + return new Promise(((resolve, reject) => { + this.load.spritesheet( + name, + url, + {frameWidth: 32, frameHeight: 32} + ); + this.load.on('filecomplete-spritesheet-'+name, () => { + console.log('RESOURCE LOADED!'); + resolve(); + }); + })) + } } diff --git a/front/src/Phaser/Login/CustomizeScene.ts b/front/src/Phaser/Login/CustomizeScene.ts index 7b4afa51..41ec95c9 100644 --- a/front/src/Phaser/Login/CustomizeScene.ts +++ b/front/src/Phaser/Login/CustomizeScene.ts @@ -2,12 +2,13 @@ import {EnableCameraSceneName} from "./EnableCameraScene"; import {TextField} from "../Components/TextField"; import Image = Phaser.GameObjects.Image; import Rectangle = Phaser.GameObjects.Rectangle; -import {LAYERS, loadAllLayers} from "../Entity/body_character"; +import {BodyResourceDescriptionInterface, LAYERS, loadAllLayers, loadCustomTexture} from "../Entity/body_character"; import Sprite = Phaser.GameObjects.Sprite; import Container = Phaser.GameObjects.Container; import {gameManager} from "../Game/GameManager"; import {ResizableScene} from "./ResizableScene"; import {localUserStore} from "../../Connexion/LocalUserStore"; +import {PlayerResourceDescriptionInterface} from "../Entity/Character"; export const CustomizeSceneName = "CustomizeScene"; @@ -36,6 +37,7 @@ export class CustomizeScene extends ResizableScene { private selectedLayers: number[] = [0]; private containersRow: Container[][] = []; private activeRow:number = 0; + private layers: BodyResourceDescriptionInterface[][] = []; constructor() { super({ @@ -51,6 +53,23 @@ export class CustomizeScene extends ResizableScene { //load all the png files loadAllLayers(this.load); + + // load custom layers + this.layers = LAYERS; + + const localUser = localUserStore.getLocalUser(); + + const textures = localUser?.textures; + if (textures) { + for (const texture of textures) { + loadCustomTexture(this.load, texture); + const name = 'customCharacterTexture'+texture.id; + this.layers[texture.level].unshift({ + name, + img: texture.url + }); + } + } } create() { @@ -94,7 +113,7 @@ export class CustomizeScene extends ResizableScene { let i = 0; for (const layerItem of this.selectedLayers) { if (layerItem !== undefined) { - layers.push(LAYERS[i][layerItem].name); + layers.push(this.layers[i][layerItem].name); } i++; } @@ -108,7 +127,7 @@ export class CustomizeScene extends ResizableScene { this.input.keyboard.on('keydown-LEFT', () => this.moveCursorHorizontally(-1)); this.input.keyboard.on('keydown-DOWN', () => this.moveCursorVertically(1)); this.input.keyboard.on('keydown-UP', () => this.moveCursorVertically(-1)); - + const customCursorPosition = localUserStore.getCustomCursorPosition(); if (customCursorPosition) { this.activeRow = customCursorPosition.activeRow; @@ -117,34 +136,34 @@ export class CustomizeScene extends ResizableScene { this.updateSelectedLayer(); } } - + private moveCursorHorizontally(index: number): void { this.selectedLayers[this.activeRow] += index; if (this.selectedLayers[this.activeRow] < 0) { this.selectedLayers[this.activeRow] = 0 - } else if(this.selectedLayers[this.activeRow] > LAYERS[this.activeRow].length - 1) { - this.selectedLayers[this.activeRow] = LAYERS[this.activeRow].length - 1 + } else if(this.selectedLayers[this.activeRow] > this.layers[this.activeRow].length - 1) { + this.selectedLayers[this.activeRow] = this.layers[this.activeRow].length - 1 } this.moveLayers(); this.updateSelectedLayer(); this.saveInLocalStorage(); } - + private moveCursorVertically(index:number): void { this.activeRow += index; if (this.activeRow < 0) { this.activeRow = 0 - } else if (this.activeRow > LAYERS.length - 1) { - this.activeRow = LAYERS.length - 1 + } else if (this.activeRow > this.layers.length - 1) { + this.activeRow = this.layers.length - 1 } this.moveLayers(); this.saveInLocalStorage(); } - + private saveInLocalStorage() { localUserStore.setCustomCursorPosition(this.activeRow, this.selectedLayers); } - + update(time: number, delta: number): void { super.update(time, delta); this.enterField.setVisible(!!(Math.floor(time / 500) % 2)); @@ -153,7 +172,7 @@ export class CustomizeScene extends ResizableScene { /** * @param x, the layer's vertical position * @param y, the layer's horizontal position - * @param layerNumber, index of the LAYERS array + * @param layerNumber, index of the this.layers array * create the layer and display it on the scene */ private createCustomizeLayer(x: number, y: number, layerNumber: number): void { @@ -161,7 +180,7 @@ export class CustomizeScene extends ResizableScene { this.selectedLayers[layerNumber] = 0; let alpha = 0; let layerPosX = 0; - for (let i = 0; i < LAYERS[layerNumber].length; i++) { + for (let i = 0; i < this.layers[layerNumber].length; i++) { const container = this.generateCharacter(300 + x + layerPosX, y, layerNumber, i); this.containersRow[layerNumber][i] = container; @@ -190,13 +209,13 @@ export class CustomizeScene extends ResizableScene { const children: Array = new Array(); for (let j = 0; j <= layerNumber; j++) { if (j === layerNumber) { - children.push(this.generateLayers(0, 0, LAYERS[j][selectedItem].name)); + children.push(this.generateLayers(0, 0, this.layers[j][selectedItem].name)); } else { const layer = this.selectedLayers[j]; if (layer === undefined) { continue; } - children.push(this.generateLayers(0, 0, LAYERS[j][layer].name)); + children.push(this.generateLayers(0, 0, this.layers[j][layer].name)); } } return children; diff --git a/maps/characters/tenue-f-jolicode.png b/maps/characters/tenue-f-jolicode.png new file mode 100644 index 00000000..f3214968 Binary files /dev/null and b/maps/characters/tenue-f-jolicode.png differ diff --git a/maps/characters/tenue-f-vanoix.png b/maps/characters/tenue-f-vanoix.png new file mode 100644 index 00000000..bcf70d0e Binary files /dev/null and b/maps/characters/tenue-f-vanoix.png differ diff --git a/maps/characters/tenue-f.png b/maps/characters/tenue-f.png new file mode 100644 index 00000000..d42952ff Binary files /dev/null and b/maps/characters/tenue-f.png differ diff --git a/maps/characters/tenue-m-jolicode.png b/maps/characters/tenue-m-jolicode.png new file mode 100644 index 00000000..24c863d9 Binary files /dev/null and b/maps/characters/tenue-m-jolicode.png differ diff --git a/maps/characters/tenue-m-vanoix.png b/maps/characters/tenue-m-vanoix.png new file mode 100644 index 00000000..72a0713b Binary files /dev/null and b/maps/characters/tenue-m-vanoix.png differ diff --git a/maps/characters/tenue-m.png b/maps/characters/tenue-m.png new file mode 100644 index 00000000..ad6a976e Binary files /dev/null and b/maps/characters/tenue-m.png differ diff --git a/messages/messages.proto b/messages/messages.proto index 45872f22..63fb6059 100644 --- a/messages/messages.proto +++ b/messages/messages.proto @@ -31,6 +31,11 @@ message SilentMessage { bool silent = 1; } +message CharacterLayerMessage { + string url = 1; + string name = 2; +} + /*********** CLIENT TO SERVER MESSAGES *************/ message SetPlayerDetailsMessage { @@ -129,7 +134,7 @@ message GroupDeleteMessage { message UserJoinedMessage { int32 userId = 1; string name = 2; - repeated string characterLayers = 3; + repeated CharacterLayerMessage characterLayers = 3; PositionMessage position = 4; }