From f65491e7098d4875ac506557ede5fb1398062415 Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Mon, 6 Dec 2021 14:37:06 +0100 Subject: [PATCH 01/95] Remove unused import --- front/src/Connexion/ConnectionManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts index 05d84367..883e705e 100644 --- a/front/src/Connexion/ConnectionManager.ts +++ b/front/src/Connexion/ConnectionManager.ts @@ -1,5 +1,5 @@ import Axios from "axios"; -import { PUSHER_URL, START_ROOM_URL } from "../Enum/EnvironmentVariable"; +import { PUSHER_URL } from "../Enum/EnvironmentVariable"; import { RoomConnection } from "./RoomConnection"; import type { OnConnectInterface, PositionInterface, ViewportInterface } from "./ConnexionModels"; import { GameConnexionTypes, urlManager } from "../Url/UrlManager"; From f340e8114da5cd8329c5dd5ae340d8ec96d6df21 Mon Sep 17 00:00:00 2001 From: danb Date: Tue, 19 Oct 2021 15:35:19 +0200 Subject: [PATCH 02/95] Implement automatic following of other players. * initiate following by reacting to the interact event * subscribe to remote player and update positions in relation to them instead of reacting to user input * cancel following if the user moves actively again --- front/src/Phaser/Player/Player.ts | 107 ++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 4 deletions(-) diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index a1924457..0e576235 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -1,15 +1,18 @@ import { PlayerAnimationDirections } from "./Animation"; import type { GameScene } from "../Game/GameScene"; -import { UserInputEvent, UserInputManager } from "../UserInput/UserInputManager"; +import { ActiveEventList, UserInputEvent, UserInputManager } from "../UserInput/UserInputManager"; import { Character } from "../Entity/Character"; +import type { RemotePlayer } from "../Entity/RemotePlayer"; import { userMovingStore } from "../../Stores/GameStore"; export const hasMovedEventName = "hasMoved"; export const requestEmoteEventName = "requestEmote"; export class Player extends Character { - private previousDirection: string = PlayerAnimationDirections.Down; + private previousDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down; private wasMoving: boolean = false; + private timeCounter: number = 0; + private follow: { followPlayer: RemotePlayer; direction: PlayerAnimationDirections } | null = null; constructor( Scene: GameScene, @@ -29,17 +32,17 @@ export class Player extends Character { this.getBody().setImmovable(false); } - moveUser(delta: number): void { + private inputStep(activeEvents: ActiveEventList, delta: number) { //if user client on shift, camera and player speed let direction = null; let moving = false; - const activeEvents = this.userInputManager.getEventListForGameTick(); const speedMultiplier = activeEvents.get(UserInputEvent.SpeedUp) ? 25 : 9; const moveAmount = speedMultiplier * 20; let x = 0; let y = 0; + if (activeEvents.get(UserInputEvent.MoveUp)) { y = -moveAmount; direction = PlayerAnimationDirections.Up; @@ -49,6 +52,7 @@ export class Player extends Character { direction = PlayerAnimationDirections.Down; moving = true; } + if (activeEvents.get(UserInputEvent.MoveLeft)) { x = -moveAmount; direction = PlayerAnimationDirections.Left; @@ -58,6 +62,7 @@ export class Player extends Character { direction = PlayerAnimationDirections.Right; moving = true; } + moving = moving || activeEvents.get(UserInputEvent.JoystickMove); if (x !== 0 || y !== 0) { @@ -89,10 +94,104 @@ export class Player extends Character { if (direction !== null) { this.previousDirection = direction; } + this.wasMoving = moving; userMovingStore.set(moving); } + private followStep(activeEvents: ActiveEventList, delta: number) { + if (this.follow === null) { + return; + } + + this.timeCounter += delta; + if (this.timeCounter < 128) { + return; + } + this.timeCounter = 0; + + const xDist = this.follow.followPlayer.x - this.x; + const yDist = this.follow.followPlayer.y - this.y; + + const distance = Math.pow(xDist, 2) + Math.pow(yDist, 2); + + if (distance < 650) { + this.stop(); + } else { + const moveAmount = 9 * 20; + const xDir = xDist / Math.sqrt(distance); + const yDir = yDist / Math.sqrt(distance); + + this.move(xDir * moveAmount, yDir * moveAmount); + + if (Math.abs(xDist) > Math.abs(yDist)) { + if (xDist < 0) { + this.follow.direction = PlayerAnimationDirections.Left; + } else { + this.follow.direction = PlayerAnimationDirections.Right; + } + } else { + if (yDist < 0) { + this.follow.direction = PlayerAnimationDirections.Up; + } else { + this.follow.direction = PlayerAnimationDirections.Down; + } + } + } + + this.emit(hasMovedEventName, { + moving: true, + direction: this.follow.direction, + x: this.x, + y: this.y, + }); + + this.previousDirection = this.follow.direction; + + this.wasMoving = true; + userMovingStore.set(true); + } + + moveUser(delta: number): void { + const activeEvents = this.userInputManager.getEventListForGameTick(); + + if (activeEvents.get(UserInputEvent.Interact)) { + const sortedPlayers = Array.from(this.scene.MapPlayersByKey.values()).sort((p1, p2) => { + const distToP1 = Math.pow(p1.x - this.x, 2) + Math.pow(p1.y - this.y, 2); + const distToP2 = Math.pow(p2.x - this.x, 2) + Math.pow(p2.y - this.y, 2); + if (distToP1 > distToP2) { + return 1; + } else if (distToP1 < distToP2) { + return -1; + } else { + return 0; + } + }); + + if (typeof sortedPlayers !== "undefined" && sortedPlayers.length > 0) { + this.follow = { + followPlayer: sortedPlayers[0], + direction: this.previousDirection, + }; + } + } + + if ( + activeEvents.get(UserInputEvent.MoveUp) || + activeEvents.get(UserInputEvent.MoveDown) || + activeEvents.get(UserInputEvent.MoveLeft) || + activeEvents.get(UserInputEvent.MoveRight) + ) { + this.follow = null; + } + + if (this.follow === null) { + this.inputStep(activeEvents, delta); + } else { + this.followStep(activeEvents, delta); + } + } + public isMoving(): boolean { return this.wasMoving; } From 7c7144527c09a01d134b154a20bbe0282bd0854b Mon Sep 17 00:00:00 2001 From: danb Date: Thu, 28 Oct 2021 16:20:57 +0200 Subject: [PATCH 03/95] Add minimum distance to enable following --- front/src/Phaser/Player/Player.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index 0e576235..5846c10e 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -157,22 +157,26 @@ export class Player extends Character { if (activeEvents.get(UserInputEvent.Interact)) { const sortedPlayers = Array.from(this.scene.MapPlayersByKey.values()).sort((p1, p2) => { - const distToP1 = Math.pow(p1.x - this.x, 2) + Math.pow(p1.y - this.y, 2); - const distToP2 = Math.pow(p2.x - this.x, 2) + Math.pow(p2.y - this.y, 2); - if (distToP1 > distToP2) { + const sdistToP1 = Math.pow(p1.x - this.x, 2) + Math.pow(p1.y - this.y, 2); + const sdistToP2 = Math.pow(p2.x - this.x, 2) + Math.pow(p2.y - this.y, 2); + if (sdistToP1 > sdistToP2) { return 1; - } else if (distToP1 < distToP2) { + } else if (sdistToP1 < sdistToP2) { return -1; } else { return 0; } }); + const minFollowDist = 10000; if (typeof sortedPlayers !== "undefined" && sortedPlayers.length > 0) { - this.follow = { - followPlayer: sortedPlayers[0], - direction: this.previousDirection, - }; + const sdist = Math.pow(sortedPlayers[0].x - this.x, 2) + Math.pow(sortedPlayers[0].y - this.y, 2); + if (sdist < minFollowDist) { + this.follow = { + followPlayer: sortedPlayers[0], + direction: this.previousDirection, + }; + } } } From 372dda792f115814107b1b05013d85425bcc00bf Mon Sep 17 00:00:00 2001 From: danb Date: Thu, 18 Nov 2021 12:23:10 +0100 Subject: [PATCH 04/95] Fix issue of interrupted conversation in follow-mode --- front/src/Phaser/Player/Player.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index 5846c10e..17ad1cc1 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -100,6 +100,8 @@ export class Player extends Character { } private followStep(activeEvents: ActiveEventList, delta: number) { + let moving = false; + if (this.follow === null) { return; } @@ -137,10 +139,12 @@ export class Player extends Character { this.follow.direction = PlayerAnimationDirections.Down; } } + + moving = true; } this.emit(hasMovedEventName, { - moving: true, + moving: moving, direction: this.follow.direction, x: this.x, y: this.y, @@ -148,8 +152,8 @@ export class Player extends Character { this.previousDirection = this.follow.direction; - this.wasMoving = true; - userMovingStore.set(true); + this.wasMoving = moving; + userMovingStore.set(moving); } moveUser(delta: number): void { From e7f1395809dc8c088762235e3f0f1caecabc9e5b Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Tue, 30 Nov 2021 11:48:59 +0100 Subject: [PATCH 05/95] Stop before running into followed Avatar; stop sprite animation --- front/src/Phaser/Game/PlayerMovement.ts | 2 +- front/src/Phaser/Player/Player.ts | 2 +- front/tests/Phaser/Game/PlayerMovementTest.ts | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/front/src/Phaser/Game/PlayerMovement.ts b/front/src/Phaser/Game/PlayerMovement.ts index 7758f010..274cbee1 100644 --- a/front/src/Phaser/Game/PlayerMovement.ts +++ b/front/src/Phaser/Game/PlayerMovement.ts @@ -41,7 +41,7 @@ export class PlayerMovement { oldX: this.startPosition.x, oldY: this.startPosition.y, direction: this.endPosition.direction, - moving: true, + moving: this.endPosition.moving, }; } } diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index 17ad1cc1..adf7a302 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -117,7 +117,7 @@ export class Player extends Character { const distance = Math.pow(xDist, 2) + Math.pow(yDist, 2); - if (distance < 650) { + if (distance < 2000) { this.stop(); } else { const moveAmount = 9 * 20; diff --git a/front/tests/Phaser/Game/PlayerMovementTest.ts b/front/tests/Phaser/Game/PlayerMovementTest.ts index 70f7b95d..bd5f40b4 100644 --- a/front/tests/Phaser/Game/PlayerMovementTest.ts +++ b/front/tests/Phaser/Game/PlayerMovementTest.ts @@ -74,7 +74,7 @@ describe("Interpolation / Extrapolation", () => { }); }); - it("should should keep moving until it stops", () => { + it("should keep moving until it stops", () => { const playerMovement = new PlayerMovement({ x: 100, y: 200 }, 42000, @@ -95,7 +95,7 @@ describe("Interpolation / Extrapolation", () => { oldX: 100, oldY: 200, direction: 'up', - moving: true + moving: false }); }); }) From b30d0989c86a45e9c438959759433291ecb9cb89 Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Tue, 7 Dec 2021 22:14:51 +0100 Subject: [PATCH 06/95] Fix indentation in messages.proto --- messages/protos/messages.proto | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/messages/protos/messages.proto b/messages/protos/messages.proto index 117ab582..2dfd9aa2 100644 --- a/messages/protos/messages.proto +++ b/messages/protos/messages.proto @@ -67,12 +67,12 @@ message ReportPlayerMessage { } message EmotePromptMessage { - string emote = 2; + string emote = 2; } message EmoteEventMessage { - int32 actorUserId = 1; - string emote = 2; + int32 actorUserId = 1; + string emote = 2; } message QueryJitsiJwtMessage { @@ -236,14 +236,14 @@ message SendUserMessage{ message WorldFullWarningMessage{ } message WorldFullWarningToRoomMessage{ - string roomId = 1; + string roomId = 1; } message RefreshRoomPromptMessage{ - string roomId = 1; + string roomId = 1; } message RefreshRoomMessage{ - string roomId = 1; - int32 versionNumber = 2; + string roomId = 1; + int32 versionNumber = 2; } message WorldFullMessage{ From 1fca99c0d1317b97b6ace0bca3d892eb7363128b Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Sun, 12 Dec 2021 00:24:39 +0100 Subject: [PATCH 07/95] Send follow me request to all players --- back/src/RoomManager.ts | 7 +++++++ back/src/Services/SocketManager.ts | 12 ++++++++++++ front/src/Connexion/RoomConnection.ts | 12 ++++++++++++ messages/protos/messages.proto | 15 +++++++++++++++ pusher/src/Controller/IoSocketController.ts | 6 ++++++ pusher/src/Services/SocketManager.ts | 7 +++++++ 6 files changed, 59 insertions(+) diff --git a/back/src/RoomManager.ts b/back/src/RoomManager.ts index 8dbde018..16a9d023 100644 --- a/back/src/RoomManager.ts +++ b/back/src/RoomManager.ts @@ -8,6 +8,7 @@ import { BatchToPusherMessage, BatchToPusherRoomMessage, EmotePromptMessage, + FollowMeRequestMessage, EmptyMessage, ItemEventMessage, JoinRoomMessage, @@ -116,6 +117,12 @@ const roomManager: IRoomManagerServer = { user, message.getEmotepromptmessage() as EmotePromptMessage ); + } else if (message.hasFollowmerequestmessage()) { + socketManager.handleFollowMeRequestMessage( + room, + user, + message.getFollowmerequestmessage() as FollowMeRequestMessage + ); } else if (message.hasSendusermessage()) { const sendUserMessage = message.getSendusermessage(); if (sendUserMessage !== undefined) { diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index 8989df75..069e298b 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -30,6 +30,7 @@ import { BanUserMessage, RefreshRoomMessage, EmotePromptMessage, + FollowMeRequestMessage, VariableMessage, BatchToPusherRoomMessage, SubToPusherRoomMessage, @@ -833,6 +834,17 @@ export class SocketManager { emoteEventMessage.setActoruserid(user.id); room.emitEmoteEvent(user, emoteEventMessage); } + + handleFollowMeRequestMessage(room: GameRoom, user: User, requestMessage: FollowMeRequestMessage) { + console.log("Handling follow me request message"); + console.log(user.name); + requestMessage.setPlayername(user.name); + room.getUsers().forEach((recipient) => { + const clientMessage = new ServerToClientMessage(); + clientMessage.setFollowmerequestmessage(requestMessage); + recipient.socket.write(clientMessage); + }); + } } export const socketManager = new SocketManager(); diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 9e4025b1..de7cfcc2 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -30,6 +30,7 @@ import { PingMessage, EmoteEventMessage, EmotePromptMessage, + FollowMeRequestMessage, SendUserMessage, BanUserMessage, VariableMessage, @@ -257,6 +258,9 @@ export class RoomConnection implements RoomConnection { warningContainerStore.activateWarningContainer(); } else if (message.hasRefreshroommessage()) { //todo: implement a way to notify the user the room was refreshed. + } else if (message.hasFollowmerequestmessage()) { + const requestMessage = message.getFollowmerequestmessage() as FollowMeRequestMessage; + console.log("Follow me request from " + requestMessage.getPlayername()); } else if (message.hasErrormessage()) { const errorMessage = message.getErrormessage() as ErrorMessage; console.error("An error occurred server side: " + errorMessage.getMessage()); @@ -712,6 +716,14 @@ export class RoomConnection implements RoomConnection { this.socket.send(clientToServerMessage.serializeBinary().buffer); } + public emitFollowMeRequest(): void { + console.log("Emitting follow me request"); + const message = new FollowMeRequestMessage(); + const clientToServerMessage = new ClientToServerMessage(); + clientToServerMessage.setFollowmerequestmessage(message); + this.socket.send(clientToServerMessage.serializeBinary().buffer); + } + public getAllTags(): string[] { return this.tags; } diff --git a/messages/protos/messages.proto b/messages/protos/messages.proto index 2dfd9aa2..e9e75205 100644 --- a/messages/protos/messages.proto +++ b/messages/protos/messages.proto @@ -80,6 +80,15 @@ message QueryJitsiJwtMessage { string tag = 2; // FIXME: rather than reading the tag from the query, we should read it from the current map! } +message FollowMeRequestMessage { + string playerName = 1; +} + +message FollowMeResponseMessage { + string playerName = 1; + bool accepted = 2; +} + message ClientToServerMessage { oneof message { UserMovesMessage userMovesMessage = 2; @@ -95,6 +104,8 @@ message ClientToServerMessage { QueryJitsiJwtMessage queryJitsiJwtMessage = 12; EmotePromptMessage emotePromptMessage = 13; VariableMessage variableMessage = 14; + FollowMeRequestMessage followMeRequestMessage = 15; + FollowMeResponseMessage followMeResponseMessage = 16; } } @@ -285,6 +296,8 @@ message ServerToClientMessage { WorldConnexionMessage worldConnexionMessage = 18; //EmoteEventMessage emoteEventMessage = 19; TokenExpiredMessage tokenExpiredMessage = 20; + FollowMeRequestMessage followMeRequestMessage = 21; + FollowMeResponseMessage followMeResponseMessage = 22; } } @@ -365,6 +378,8 @@ message PusherToBackMessage { BanUserMessage banUserMessage = 13; EmotePromptMessage emotePromptMessage = 14; VariableMessage variableMessage = 15; + FollowMeRequestMessage followMeRequestMessage = 16; + FollowMeResponseMessage followMeResponseMessage = 17; } } diff --git a/pusher/src/Controller/IoSocketController.ts b/pusher/src/Controller/IoSocketController.ts index df29db57..5d66c4df 100644 --- a/pusher/src/Controller/IoSocketController.ts +++ b/pusher/src/Controller/IoSocketController.ts @@ -17,6 +17,7 @@ import { ServerToClientMessage, CompanionMessage, EmotePromptMessage, + FollowMeRequestMessage, VariableMessage, } from "../Messages/generated/messages_pb"; import { UserMovesMessage } from "../Messages/generated/messages_pb"; @@ -469,6 +470,11 @@ export class IoSocketController { client, message.getEmotepromptmessage() as EmotePromptMessage ); + } else if (message.hasFollowmerequestmessage()) { + socketManager.handleFollowMeRequest( + client, + message.getFollowmerequestmessage() as FollowMeRequestMessage + ); } /* Ok is false if backpressure was built up, wait for drain */ diff --git a/pusher/src/Services/SocketManager.ts b/pusher/src/Services/SocketManager.ts index 4f4b086f..2df167bd 100644 --- a/pusher/src/Services/SocketManager.ts +++ b/pusher/src/Services/SocketManager.ts @@ -8,6 +8,7 @@ import { CharacterLayerMessage, EmoteEventMessage, EmotePromptMessage, + FollowMeRequestMessage, GroupDeleteMessage, ItemEventMessage, JoinRoomMessage, @@ -269,6 +270,12 @@ export class SocketManager implements ZoneEventListener { this.handleViewport(client, viewport.toObject()); } + handleFollowMeRequest(client: ExSocketInterface, requestMessage: FollowMeRequestMessage): void { + const pusherToBackMessage = new PusherToBackMessage(); + pusherToBackMessage.setFollowmerequestmessage(requestMessage); + client.backConnection.write(pusherToBackMessage); + } + onEmote(emoteMessage: EmoteEventMessage, listener: ExSocketInterface): void { const subMessage = new SubMessage(); subMessage.setEmoteeventmessage(emoteMessage); From d6ef60a3d898238265f247d23951d78cf0365585 Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Sun, 12 Dec 2021 02:17:39 +0100 Subject: [PATCH 08/95] Send request to group members only --- back/src/Model/Group.ts | 10 ++++++++++ back/src/Services/SocketManager.ts | 21 +++++++++++++++------ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/back/src/Model/Group.ts b/back/src/Model/Group.ts index 931ddda5..570eaedf 100644 --- a/back/src/Model/Group.ts +++ b/back/src/Model/Group.ts @@ -106,6 +106,16 @@ export class Group implements Movable { return this.users.size <= 1; } + includes(user: User): boolean { + let found = false; + this.users.forEach((currentUser: User) => { + if (currentUser.name === user.name) { + found = true; + } + }); + return found; + } + join(user: User): void { // Broadcast on the right event this.connectCallback(user, this); diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index 069e298b..cc950163 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -836,13 +836,22 @@ export class SocketManager { } handleFollowMeRequestMessage(room: GameRoom, user: User, requestMessage: FollowMeRequestMessage) { - console.log("Handling follow me request message"); - console.log(user.name); + // Find group including the requesting user + let foundGroups = room.getGroups().filter((grp) => grp.includes(user)); + if (!foundGroups[0]) { + return; + } + let group = foundGroups[0]; + + // Send invitations to other group members requestMessage.setPlayername(user.name); - room.getUsers().forEach((recipient) => { - const clientMessage = new ServerToClientMessage(); - clientMessage.setFollowmerequestmessage(requestMessage); - recipient.socket.write(clientMessage); + const clientMessage = new ServerToClientMessage(); + clientMessage.setFollowmerequestmessage(requestMessage); + group.getUsers().forEach((currentUser: User) => { + if (user.name !== currentUser.name) { + console.log("Inviting " + currentUser.name + " to follow " + user.name); + currentUser.socket.write(clientMessage); + } }); } } From 0a410d289d8f0fdf553f70dd58da0ec830306a13 Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Sun, 12 Dec 2021 16:56:26 +0100 Subject: [PATCH 09/95] Implement follow request / confirmation UI --- back/src/Model/GameRoom.ts | 25 ++ back/src/RoomManager.ts | 22 +- back/src/Services/SocketManager.ts | 44 ++-- front/src/Components/App.svelte | 7 + .../InteractMenu/InteractMenu.svelte | 238 ++++++++++++++++++ front/src/Connexion/RoomConnection.ts | 79 +++++- front/src/Phaser/Player/Player.ts | 63 +++-- front/src/Stores/InteractStore.ts | 17 ++ messages/protos/messages.proto | 28 ++- pusher/src/Controller/IoSocketController.ts | 17 +- pusher/src/Services/SocketManager.ts | 20 +- 11 files changed, 481 insertions(+), 79 deletions(-) create mode 100644 front/src/Components/InteractMenu/InteractMenu.svelte create mode 100644 front/src/Stores/InteractStore.ts diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index 5c114f19..1b0db5eb 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -14,6 +14,7 @@ import { SubToPusherRoomMessage, VariableMessage, VariableWithTagMessage, + ServerToClientMessage, } from "../Messages/generated/messages_pb"; import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils"; import { RoomSocket, ZoneSocket } from "src/RoomManager"; @@ -95,10 +96,20 @@ export class GameRoom { return Array.from(this.groups.values()); } + public getGroupIncludingUser(user: User): Group | undefined { + const foundGroups = this.getGroups().filter((grp) => grp.includes(user)); + return foundGroups[0]; + } + public getUsers(): Map { return this.users; } + public getUserByName(name: string): User | undefined { + let foundUsers = Array.from(this.users.values()); + foundUsers = foundUsers.filter((user: User) => user.name === name); + return foundUsers[0]; + } public getUserByUuid(uuid: string): User | undefined { return this.usersByUuid.get(uuid); } @@ -226,6 +237,20 @@ export class GameRoom { } } + public sendToOthersInGroupIncludingUser(user: User, message: ServerToClientMessage): void { + this.getGroupIncludingUser(user) + ?.getUsers() + .forEach((currentUser: User) => { + if (currentUser.name !== user.name) { + currentUser.socket.write(message); + } + }); + } + + public sendToUserWithName(name: string, message: ServerToClientMessage): void { + this.getUserByName(name)?.socket.write(message); + } + setSilent(user: User, silent: boolean) { if (user.silent === silent) { return; diff --git a/back/src/RoomManager.ts b/back/src/RoomManager.ts index 16a9d023..9020c130 100644 --- a/back/src/RoomManager.ts +++ b/back/src/RoomManager.ts @@ -8,7 +8,9 @@ import { BatchToPusherMessage, BatchToPusherRoomMessage, EmotePromptMessage, - FollowMeRequestMessage, + FollowRequestMessage, + FollowConfirmationMessage, + FollowAbortMessage, EmptyMessage, ItemEventMessage, JoinRoomMessage, @@ -117,11 +119,23 @@ const roomManager: IRoomManagerServer = { user, message.getEmotepromptmessage() as EmotePromptMessage ); - } else if (message.hasFollowmerequestmessage()) { - socketManager.handleFollowMeRequestMessage( + } else if (message.hasFollowrequestmessage()) { + socketManager.handleFollowRequestMessage( room, user, - message.getFollowmerequestmessage() as FollowMeRequestMessage + message.getFollowrequestmessage() as FollowRequestMessage + ); + } else if (message.hasFollowconfirmationmessage()) { + socketManager.handleFollowConfirmationMessage( + room, + user, + message.getFollowconfirmationmessage() as FollowConfirmationMessage + ); + } else if (message.hasFollowabortmessage()) { + socketManager.handleFollowAbortMessage( + room, + user, + message.getFollowabortmessage() as FollowAbortMessage ); } else if (message.hasSendusermessage()) { const sendUserMessage = message.getSendusermessage(); diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index cc950163..3ab53719 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -30,7 +30,9 @@ import { BanUserMessage, RefreshRoomMessage, EmotePromptMessage, - FollowMeRequestMessage, + FollowRequestMessage, + FollowConfirmationMessage, + FollowAbortMessage, VariableMessage, BatchToPusherRoomMessage, SubToPusherRoomMessage, @@ -835,24 +837,30 @@ export class SocketManager { room.emitEmoteEvent(user, emoteEventMessage); } - handleFollowMeRequestMessage(room: GameRoom, user: User, requestMessage: FollowMeRequestMessage) { - // Find group including the requesting user - let foundGroups = room.getGroups().filter((grp) => grp.includes(user)); - if (!foundGroups[0]) { - return; - } - let group = foundGroups[0]; - - // Send invitations to other group members - requestMessage.setPlayername(user.name); + handleFollowRequestMessage(room: GameRoom, user: User, message: FollowRequestMessage) { const clientMessage = new ServerToClientMessage(); - clientMessage.setFollowmerequestmessage(requestMessage); - group.getUsers().forEach((currentUser: User) => { - if (user.name !== currentUser.name) { - console.log("Inviting " + currentUser.name + " to follow " + user.name); - currentUser.socket.write(clientMessage); - } - }); + clientMessage.setFollowrequestmessage(message); + room.sendToOthersInGroupIncludingUser(user, clientMessage); + } + + handleFollowConfirmationMessage(room: GameRoom, user: User, message: FollowConfirmationMessage) { + const clientMessage = new ServerToClientMessage(); + clientMessage.setFollowconfirmationmessage(message); + room.sendToUserWithName(message.getLeader(), clientMessage); + } + + handleFollowAbortMessage(room: GameRoom, user: User, message: FollowAbortMessage) { + if (message.getRole() === "leader") { + const clientMessage = new ServerToClientMessage(); + clientMessage.setFollowabortmessage(message); + room.sendToOthersInGroupIncludingUser(user, clientMessage); + } else { + const recipient = message.getPlayername(); + message.setPlayername(user.name); + const clientMessage = new ServerToClientMessage(); + clientMessage.setFollowabortmessage(message); + room.sendToUserWithName(recipient, clientMessage); + } } } diff --git a/front/src/Components/App.svelte b/front/src/Components/App.svelte index 4886cc4e..b14801c9 100644 --- a/front/src/Components/App.svelte +++ b/front/src/Components/App.svelte @@ -42,6 +42,8 @@ import AudioManager from "./AudioManager/AudioManager.svelte"; import { showReportScreenStore, userReportEmpty } from "../Stores/ShowReportScreenStore"; import ReportMenu from "./ReportMenu/ReportMenu.svelte"; + import { followStateStore, followStates } from "../Stores/InteractStore"; + import InteractMenu from "./InteractMenu/InteractMenu.svelte"; export let game: Game; @@ -102,6 +104,11 @@ {/if} + {#if $followStateStore !== followStates.off} +
+ +
+ {/if} {#if $menuIconVisiblilityStore}
diff --git a/front/src/Components/InteractMenu/InteractMenu.svelte b/front/src/Components/InteractMenu/InteractMenu.svelte new file mode 100644 index 00000000..cb78f5bc --- /dev/null +++ b/front/src/Components/InteractMenu/InteractMenu.svelte @@ -0,0 +1,238 @@ + + + + + +{#if followState === followStates.requesting} +
+
+

Interaction

+
+ {#if followRole === followRoles.follower} +
+

Do you want to follow {followUsers[0]}?

+
+
+ + +
+ {:else if followRole === followRoles.leader} +
+

Ask others to follow you?

+
+
+ + +
+ {/if} +
+{/if} + +{#if followState === followStates.ending} +
+
+

Interaction

+
+ {#if followRole === followRoles.follower} +
+

Do you want to stop following {followUsers[0]}?

+
+ {:else if followRole === followRoles.leader} +
+

Do you want to stop leading the way?

+
+ {/if} +
+ + +
+
+{/if} + +{#if followState === followStates.active || followState === followStates.ending} +
+
+ {#if followRole === followRoles.follower} +

Following {followUsers[0]}

+ {:else if followUsers.length === 0} +

Waiting for followers' confirmation

+ {:else if followUsers.length === 1} +

{followUsers[0]} is following you

+ {:else if followUsers.length === 2} +

{followUsers[0]} and {followUsers[1]} are following you

+ {:else} +

{followUsers[0]}, {followUsers[1]} and {followUsers[2]} are following you

+ {/if} +
+
+{/if} + + diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index de7cfcc2..e3f36cb7 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -30,7 +30,9 @@ import { PingMessage, EmoteEventMessage, EmotePromptMessage, - FollowMeRequestMessage, + FollowRequestMessage, + FollowConfirmationMessage, + FollowAbortMessage, SendUserMessage, BanUserMessage, VariableMessage, @@ -58,7 +60,15 @@ import { adminMessagesService } from "./AdminMessagesService"; import { worldFullMessageStream } from "./WorldFullMessageStream"; import { connectionManager } from "./ConnectionManager"; import { emoteEventStream } from "./EmoteEventStream"; +import { get } from "svelte/store"; import { warningContainerStore } from "../Stores/MenuStore"; +import { + followStateStore, + followRoleStore, + followUsersStore, + followRoles, + followStates, +} from "../Stores/InteractStore"; const manualPingDelay = 20000; @@ -258,9 +268,32 @@ export class RoomConnection implements RoomConnection { warningContainerStore.activateWarningContainer(); } else if (message.hasRefreshroommessage()) { //todo: implement a way to notify the user the room was refreshed. - } else if (message.hasFollowmerequestmessage()) { - const requestMessage = message.getFollowmerequestmessage() as FollowMeRequestMessage; - console.log("Follow me request from " + requestMessage.getPlayername()); + } else if (message.hasFollowrequestmessage()) { + const requestMessage = message.getFollowrequestmessage() as FollowRequestMessage; + console.log("Got follow request from " + requestMessage.getPlayername()); + followStateStore.set(followStates.requesting); + followRoleStore.set(followRoles.follower); + followUsersStore.set([requestMessage.getPlayername()]); + } else if (message.hasFollowconfirmationmessage()) { + const responseMessage = message.getFollowconfirmationmessage() as FollowConfirmationMessage; + console.log("Got follow response from " + responseMessage.getFollower()); + followUsersStore.set([...get(followUsersStore), responseMessage.getFollower()]); + } else if (message.hasFollowabortmessage()) { + const abortMessage = message.getFollowabortmessage() as FollowAbortMessage; + console.log("Got follow abort message from", abortMessage.getRole()); + if (abortMessage.getRole() === followRoles.leader) { + followStateStore.set(followStates.off); + followRoleStore.set(followRoles.leader); + followUsersStore.set([]); + } else { + let followers = get(followUsersStore); + followers = followers.filter((name) => name !== abortMessage.getPlayername()); + followUsersStore.set(followers); + if (followers.length === 0) { + followStateStore.set(followStates.off); + followRoleStore.set(followRoles.leader); + } + } } else if (message.hasErrormessage()) { const errorMessage = message.getErrormessage() as ErrorMessage; console.error("An error occurred server side: " + errorMessage.getMessage()); @@ -716,11 +749,41 @@ export class RoomConnection implements RoomConnection { this.socket.send(clientToServerMessage.serializeBinary().buffer); } - public emitFollowMeRequest(): void { - console.log("Emitting follow me request"); - const message = new FollowMeRequestMessage(); + public emitFollowRequest(user: string | null): void { + if (!user) { + return; + } + console.log("Emitting follow request"); + const message = new FollowRequestMessage(); + message.setPlayername(user); const clientToServerMessage = new ClientToServerMessage(); - clientToServerMessage.setFollowmerequestmessage(message); + clientToServerMessage.setFollowrequestmessage(message); + this.socket.send(clientToServerMessage.serializeBinary().buffer); + } + + public emitFollowConfirmation(leader: string, follower: string | null): void { + if (!follower) { + return; + } + console.log("Emitting follow confirmation"); + const message = new FollowConfirmationMessage(); + message.setLeader(leader); + message.setFollower(follower); + const clientToServerMessage = new ClientToServerMessage(); + clientToServerMessage.setFollowconfirmationmessage(message); + this.socket.send(clientToServerMessage.serializeBinary().buffer); + } + + public emitFollowAbort(role: string, user: string | null): void { + if (!user) { + return; + } + console.log("Emitting follow abort"); + const message = new FollowAbortMessage(); + message.setRole(role); + message.setPlayername(user); + const clientToServerMessage = new ClientToServerMessage(); + clientToServerMessage.setFollowabortmessage(message); this.socket.send(clientToServerMessage.serializeBinary().buffer); } diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index adf7a302..d8de9ab6 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -3,7 +3,16 @@ import type { GameScene } from "../Game/GameScene"; import { ActiveEventList, UserInputEvent, UserInputManager } from "../UserInput/UserInputManager"; import { Character } from "../Entity/Character"; import type { RemotePlayer } from "../Entity/RemotePlayer"; + +import { get } from "svelte/store"; import { userMovingStore } from "../../Stores/GameStore"; +import { + followStateStore, + followRoleStore, + followUsersStore, + followRoles, + followStates, +} from "../../Stores/InteractStore"; export const hasMovedEventName = "hasMoved"; export const requestEmoteEventName = "requestEmote"; @@ -156,44 +165,34 @@ export class Player extends Character { userMovingStore.set(moving); } - moveUser(delta: number): void { + public enableFollowing() { + Array.from(this.scene.MapPlayersByKey.values()).forEach((player) => { + if (player.PlayerValue !== get(followUsersStore)[0]) { + return; + } + this.follow = { + followPlayer: player, + direction: this.previousDirection, + }; + followStateStore.set(followStates.active); + }); + } + + public moveUser(delta: number): void { const activeEvents = this.userInputManager.getEventListForGameTick(); + const state = get(followStateStore); + const role = get(followRoleStore); if (activeEvents.get(UserInputEvent.Interact)) { - const sortedPlayers = Array.from(this.scene.MapPlayersByKey.values()).sort((p1, p2) => { - const sdistToP1 = Math.pow(p1.x - this.x, 2) + Math.pow(p1.y - this.y, 2); - const sdistToP2 = Math.pow(p2.x - this.x, 2) + Math.pow(p2.y - this.y, 2); - if (sdistToP1 > sdistToP2) { - return 1; - } else if (sdistToP1 < sdistToP2) { - return -1; - } else { - return 0; - } - }); - - const minFollowDist = 10000; - if (typeof sortedPlayers !== "undefined" && sortedPlayers.length > 0) { - const sdist = Math.pow(sortedPlayers[0].x - this.x, 2) + Math.pow(sortedPlayers[0].y - this.y, 2); - if (sdist < minFollowDist) { - this.follow = { - followPlayer: sortedPlayers[0], - direction: this.previousDirection, - }; - } + if (state === followStates.off && this.scene.groups.size > 0) { + followStateStore.set(followStates.requesting); + followRoleStore.set(followRoles.leader); + } else if (state === followStates.active) { + followStateStore.set(followStates.ending); } } - if ( - activeEvents.get(UserInputEvent.MoveUp) || - activeEvents.get(UserInputEvent.MoveDown) || - activeEvents.get(UserInputEvent.MoveLeft) || - activeEvents.get(UserInputEvent.MoveRight) - ) { - this.follow = null; - } - - if (this.follow === null) { + if ((state !== followStates.active && state !== followStates.ending) || role !== followRoles.follower) { this.inputStep(activeEvents, delta); } else { this.followStep(activeEvents, delta); diff --git a/front/src/Stores/InteractStore.ts b/front/src/Stores/InteractStore.ts new file mode 100644 index 00000000..960a6954 --- /dev/null +++ b/front/src/Stores/InteractStore.ts @@ -0,0 +1,17 @@ +import { writable } from "svelte/store"; + +export const followStates = { + off: "off", + requesting: "requesting", + active: "active", + ending: "ending", +}; + +export const followRoles = { + leader: "leader", + follower: "follower", +}; + +export const followStateStore = writable(followStates.off); +export const followRoleStore = writable(followRoles.leader); +export const followUsersStore = writable([]); diff --git a/messages/protos/messages.proto b/messages/protos/messages.proto index e9e75205..8e5f7c6b 100644 --- a/messages/protos/messages.proto +++ b/messages/protos/messages.proto @@ -80,13 +80,18 @@ message QueryJitsiJwtMessage { string tag = 2; // FIXME: rather than reading the tag from the query, we should read it from the current map! } -message FollowMeRequestMessage { +message FollowRequestMessage { string playerName = 1; } -message FollowMeResponseMessage { - string playerName = 1; - bool accepted = 2; +message FollowConfirmationMessage { + string leader = 1; + string follower = 2; +} + +message FollowAbortMessage { + string role = 1; + string playerName = 2; } message ClientToServerMessage { @@ -104,8 +109,9 @@ message ClientToServerMessage { QueryJitsiJwtMessage queryJitsiJwtMessage = 12; EmotePromptMessage emotePromptMessage = 13; VariableMessage variableMessage = 14; - FollowMeRequestMessage followMeRequestMessage = 15; - FollowMeResponseMessage followMeResponseMessage = 16; + FollowRequestMessage followRequestMessage = 15; + FollowConfirmationMessage followConfirmationMessage = 16; + FollowAbortMessage followAbortMessage = 17; } } @@ -296,8 +302,9 @@ message ServerToClientMessage { WorldConnexionMessage worldConnexionMessage = 18; //EmoteEventMessage emoteEventMessage = 19; TokenExpiredMessage tokenExpiredMessage = 20; - FollowMeRequestMessage followMeRequestMessage = 21; - FollowMeResponseMessage followMeResponseMessage = 22; + FollowRequestMessage followRequestMessage = 21; + FollowConfirmationMessage followConfirmationMessage = 22; + FollowAbortMessage followAbortMessage = 23; } } @@ -378,8 +385,9 @@ message PusherToBackMessage { BanUserMessage banUserMessage = 13; EmotePromptMessage emotePromptMessage = 14; VariableMessage variableMessage = 15; - FollowMeRequestMessage followMeRequestMessage = 16; - FollowMeResponseMessage followMeResponseMessage = 17; + FollowRequestMessage followRequestMessage = 16; + FollowConfirmationMessage followConfirmationMessage = 17; + FollowAbortMessage followAbortMessage = 18; } } diff --git a/pusher/src/Controller/IoSocketController.ts b/pusher/src/Controller/IoSocketController.ts index 5d66c4df..930eb4cf 100644 --- a/pusher/src/Controller/IoSocketController.ts +++ b/pusher/src/Controller/IoSocketController.ts @@ -17,7 +17,9 @@ import { ServerToClientMessage, CompanionMessage, EmotePromptMessage, - FollowMeRequestMessage, + FollowRequestMessage, + FollowConfirmationMessage, + FollowAbortMessage, VariableMessage, } from "../Messages/generated/messages_pb"; import { UserMovesMessage } from "../Messages/generated/messages_pb"; @@ -470,11 +472,18 @@ export class IoSocketController { client, message.getEmotepromptmessage() as EmotePromptMessage ); - } else if (message.hasFollowmerequestmessage()) { - socketManager.handleFollowMeRequest( + } else if (message.hasFollowrequestmessage()) { + socketManager.handleFollowRequest( client, - message.getFollowmerequestmessage() as FollowMeRequestMessage + message.getFollowrequestmessage() as FollowRequestMessage ); + } else if (message.hasFollowconfirmationmessage()) { + socketManager.handleFollowConfirmation( + client, + message.getFollowconfirmationmessage() as FollowConfirmationMessage + ); + } else if (message.hasFollowabortmessage()) { + socketManager.handleFollowAbort(client, message.getFollowabortmessage() as FollowAbortMessage); } /* Ok is false if backpressure was built up, wait for drain */ diff --git a/pusher/src/Services/SocketManager.ts b/pusher/src/Services/SocketManager.ts index 2df167bd..2bbf83c1 100644 --- a/pusher/src/Services/SocketManager.ts +++ b/pusher/src/Services/SocketManager.ts @@ -8,7 +8,9 @@ import { CharacterLayerMessage, EmoteEventMessage, EmotePromptMessage, - FollowMeRequestMessage, + FollowRequestMessage, + FollowConfirmationMessage, + FollowAbortMessage, GroupDeleteMessage, ItemEventMessage, JoinRoomMessage, @@ -270,9 +272,21 @@ export class SocketManager implements ZoneEventListener { this.handleViewport(client, viewport.toObject()); } - handleFollowMeRequest(client: ExSocketInterface, requestMessage: FollowMeRequestMessage): void { + handleFollowRequest(client: ExSocketInterface, message: FollowRequestMessage): void { const pusherToBackMessage = new PusherToBackMessage(); - pusherToBackMessage.setFollowmerequestmessage(requestMessage); + pusherToBackMessage.setFollowrequestmessage(message); + client.backConnection.write(pusherToBackMessage); + } + + handleFollowConfirmation(client: ExSocketInterface, message: FollowConfirmationMessage): void { + const pusherToBackMessage = new PusherToBackMessage(); + pusherToBackMessage.setFollowconfirmationmessage(message); + client.backConnection.write(pusherToBackMessage); + } + + handleFollowAbort(client: ExSocketInterface, message: FollowAbortMessage): void { + const pusherToBackMessage = new PusherToBackMessage(); + pusherToBackMessage.setFollowabortmessage(message); client.backConnection.write(pusherToBackMessage); } From c2f550123666ddc67367ab3fd9698adfec4b751b Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Mon, 13 Dec 2021 05:06:52 +0100 Subject: [PATCH 10/95] Do not leave group when following is active --- back/src/Model/GameRoom.ts | 3 ++- back/src/Model/User.ts | 16 ++++++++++++++++ back/src/Services/SocketManager.ts | 16 ++++++++++++++++ back/tests/PositionNotifierTest.ts | 8 ++++---- 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index 1b0db5eb..e8803762 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -139,6 +139,7 @@ export class GameRoom { joinRoomMessage.getIpaddress(), position, false, + [], this.positionNotifier, socket, joinRoomMessage.getTagList(), @@ -231,7 +232,7 @@ export class GameRoom { // If the user is part of a group: // should he leave the group? const distance = GameRoom.computeDistanceBetweenPositions(user.getPosition(), user.group.getPosition()); - if (distance > this.groupRadius) { + if (user.following.length === 0 && distance > this.groupRadius) { this.leaveGroup(user); } } diff --git a/back/src/Model/User.ts b/back/src/Model/User.ts index 186fb32a..09c0d3d9 100644 --- a/back/src/Model/User.ts +++ b/back/src/Model/User.ts @@ -25,6 +25,7 @@ export class User implements Movable { public readonly IPAddress: string, private position: PointInterface, public silent: boolean, + public following: string[], private positionNotifier: PositionNotifier, public readonly socket: UserSocket, public readonly tags: string[], @@ -48,6 +49,21 @@ export class User implements Movable { this.positionNotifier.updatePosition(this, position, oldPosition); } + public addFollower(name: string): void { + if (this.following.includes(name)) { + return; + } + this.following.push(name); + } + + public delFollower(name: string): void { + const idx = this.following.indexOf(name); + if (idx === -1) { + return; + } + this.following.splice(idx, 1); + } + private batchedMessages: BatchMessage = new BatchMessage(); private batchTimeout: NodeJS.Timeout | null = null; diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index 3ab53719..94ad2ed3 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -847,19 +847,35 @@ export class SocketManager { const clientMessage = new ServerToClientMessage(); clientMessage.setFollowconfirmationmessage(message); room.sendToUserWithName(message.getLeader(), clientMessage); + + room.getUserByName(message.getLeader())?.addFollower(user.name); + user.addFollower(message.getLeader()); } handleFollowAbortMessage(room: GameRoom, user: User, message: FollowAbortMessage) { if (message.getRole() === "leader") { + // Forward message const clientMessage = new ServerToClientMessage(); clientMessage.setFollowabortmessage(message); room.sendToOthersInGroupIncludingUser(user, clientMessage); + + // Update followers + room.getGroupIncludingUser(user) + ?.getUsers() + .forEach((user) => { + user.following = []; + }); } else { + // Forward message const recipient = message.getPlayername(); message.setPlayername(user.name); const clientMessage = new ServerToClientMessage(); clientMessage.setFollowabortmessage(message); room.sendToUserWithName(recipient, clientMessage); + + // Update followers + room.getUserByName(recipient)?.delFollower(user.name); + user.following = []; } } } diff --git a/back/tests/PositionNotifierTest.ts b/back/tests/PositionNotifierTest.ts index 1aaf2e13..955ed40f 100644 --- a/back/tests/PositionNotifierTest.ts +++ b/back/tests/PositionNotifierTest.ts @@ -26,14 +26,14 @@ describe("PositionNotifier", () => { y: 500, moving: false, direction: 'down' - }, false, positionNotifier, {} as UserSocket, [], null, 'foo', []); + }, false, [], positionNotifier, {} as UserSocket, [], null, 'foo', []); const user2 = new User(2, 'test', '10.0.0.2', { x: -9999, y: -9999, moving: false, direction: 'down' - }, false, positionNotifier, {} as UserSocket, [], null, 'foo', []); + }, false, [], positionNotifier, {} as UserSocket, [], null, 'foo', []); positionNotifier.addZoneListener({} as ZoneSocket, 0, 0); positionNotifier.addZoneListener({} as ZoneSocket, 0, 1); @@ -101,14 +101,14 @@ describe("PositionNotifier", () => { y: 500, moving: false, direction: 'down' - }, false, positionNotifier, {} as UserSocket, [], null, 'foo', []); + }, false, [], positionNotifier, {} as UserSocket, [], null, 'foo', []); const user2 = new User(2, 'test', '10.0.0.2', { x: 0, y: 0, moving: false, direction: 'down' - }, false, positionNotifier, {} as UserSocket, [], null, 'foo', []); + }, false, [], positionNotifier, {} as UserSocket, [], null, 'foo', []); const listener = {} as ZoneSocket; positionNotifier.addZoneListener(listener, 0, 0); From 7bff782f7fae5f2fb308da7499bc6613a63416d1 Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Tue, 14 Dec 2021 01:35:00 +0100 Subject: [PATCH 11/95] Improve follow abort message; avoid having to change it in backend --- back/src/Services/SocketManager.ts | 23 +++++++--------- .../InteractMenu/InteractMenu.svelte | 4 +-- front/src/Connexion/RoomConnection.ts | 27 ++++++++++--------- messages/protos/messages.proto | 6 ++--- 4 files changed, 28 insertions(+), 32 deletions(-) diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index 94ad2ed3..727cf430 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -853,28 +853,23 @@ export class SocketManager { } handleFollowAbortMessage(room: GameRoom, user: User, message: FollowAbortMessage) { - if (message.getRole() === "leader") { + const clientMessage = new ServerToClientMessage(); + clientMessage.setFollowabortmessage(message); + if (user.name === message.getLeader()) { // Forward message - const clientMessage = new ServerToClientMessage(); - clientMessage.setFollowabortmessage(message); room.sendToOthersInGroupIncludingUser(user, clientMessage); // Update followers - room.getGroupIncludingUser(user) - ?.getUsers() - .forEach((user) => { - user.following = []; - }); + const group = room.getGroupIncludingUser(user); + group?.getUsers().forEach((user) => { + user.following = []; + }); } else { // Forward message - const recipient = message.getPlayername(); - message.setPlayername(user.name); - const clientMessage = new ServerToClientMessage(); - clientMessage.setFollowabortmessage(message); - room.sendToUserWithName(recipient, clientMessage); + room.sendToUserWithName(message.getLeader(), clientMessage); // Update followers - room.getUserByName(recipient)?.delFollower(user.name); + room.getUserByName(message.getLeader())?.delFollower(user.name); user.following = []; } } diff --git a/front/src/Components/InteractMenu/InteractMenu.svelte b/front/src/Components/InteractMenu/InteractMenu.svelte index cb78f5bc..6f5e62b2 100644 --- a/front/src/Components/InteractMenu/InteractMenu.svelte +++ b/front/src/Components/InteractMenu/InteractMenu.svelte @@ -67,9 +67,9 @@ vim: ft=typescript function reset() { if (followRole === followRoles.leader && followUsers.length > 0) { - gameScene.connection?.emitFollowAbort(followRole, gameManager.getPlayerName()); + gameScene.connection?.emitFollowAbort(gameManager.getPlayerName(), "*"); } else { - gameScene.connection?.emitFollowAbort(followRole, followUsers[0]); + gameScene.connection?.emitFollowAbort(followUsers[0], gameManager.getPlayerName()); } followStateStore.set(followStates.off); followRoleStore.set(followRoles.leader); diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index e3f36cb7..6d0abaef 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -270,26 +270,27 @@ export class RoomConnection implements RoomConnection { //todo: implement a way to notify the user the room was refreshed. } else if (message.hasFollowrequestmessage()) { const requestMessage = message.getFollowrequestmessage() as FollowRequestMessage; - console.log("Got follow request from " + requestMessage.getPlayername()); + console.log("Got follow request from " + requestMessage.getLeader()); followStateStore.set(followStates.requesting); followRoleStore.set(followRoles.follower); - followUsersStore.set([requestMessage.getPlayername()]); + followUsersStore.set([requestMessage.getLeader()]); } else if (message.hasFollowconfirmationmessage()) { const responseMessage = message.getFollowconfirmationmessage() as FollowConfirmationMessage; console.log("Got follow response from " + responseMessage.getFollower()); followUsersStore.set([...get(followUsersStore), responseMessage.getFollower()]); } else if (message.hasFollowabortmessage()) { const abortMessage = message.getFollowabortmessage() as FollowAbortMessage; - console.log("Got follow abort message from", abortMessage.getRole()); - if (abortMessage.getRole() === followRoles.leader) { + console.log("Got follow abort message"); + if (get(followRoleStore) === followRoles.follower) { followStateStore.set(followStates.off); followRoleStore.set(followRoles.leader); followUsersStore.set([]); } else { let followers = get(followUsersStore); - followers = followers.filter((name) => name !== abortMessage.getPlayername()); + const oldFollowerCount = followers.length; + followers = followers.filter((name) => name !== abortMessage.getFollower()); followUsersStore.set(followers); - if (followers.length === 0) { + if (followers.length === 0 && oldFollowerCount > 0) { followStateStore.set(followStates.off); followRoleStore.set(followRoles.leader); } @@ -755,14 +756,14 @@ export class RoomConnection implements RoomConnection { } console.log("Emitting follow request"); const message = new FollowRequestMessage(); - message.setPlayername(user); + message.setLeader(user); const clientToServerMessage = new ClientToServerMessage(); clientToServerMessage.setFollowrequestmessage(message); this.socket.send(clientToServerMessage.serializeBinary().buffer); } - public emitFollowConfirmation(leader: string, follower: string | null): void { - if (!follower) { + public emitFollowConfirmation(leader: string | null, follower: string | null): void { + if (!leader || !follower) { return; } console.log("Emitting follow confirmation"); @@ -774,14 +775,14 @@ export class RoomConnection implements RoomConnection { this.socket.send(clientToServerMessage.serializeBinary().buffer); } - public emitFollowAbort(role: string, user: string | null): void { - if (!user) { + public emitFollowAbort(leader: string | null, follower: string | null): void { + if (!leader || !follower) { return; } console.log("Emitting follow abort"); const message = new FollowAbortMessage(); - message.setRole(role); - message.setPlayername(user); + message.setLeader(leader); + message.setFollower(follower); const clientToServerMessage = new ClientToServerMessage(); clientToServerMessage.setFollowabortmessage(message); this.socket.send(clientToServerMessage.serializeBinary().buffer); diff --git a/messages/protos/messages.proto b/messages/protos/messages.proto index 8e5f7c6b..7152f43f 100644 --- a/messages/protos/messages.proto +++ b/messages/protos/messages.proto @@ -81,7 +81,7 @@ message QueryJitsiJwtMessage { } message FollowRequestMessage { - string playerName = 1; + string leader = 1; } message FollowConfirmationMessage { @@ -90,8 +90,8 @@ message FollowConfirmationMessage { } message FollowAbortMessage { - string role = 1; - string playerName = 2; + string leader = 1; + string follower = 2; } message ClientToServerMessage { From 290e5131e95c086013c033f91056bab769096003 Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Tue, 14 Dec 2021 18:47:51 +0100 Subject: [PATCH 12/95] Clean up follow implementation; stop following when leader leaves the scene --- front/src/Phaser/Entity/Character.ts | 32 +++++------ front/src/Phaser/Game/GameScene.ts | 4 ++ front/src/Phaser/Player/Player.ts | 83 ++++++++-------------------- 3 files changed, 40 insertions(+), 79 deletions(-) diff --git a/front/src/Phaser/Entity/Character.ts b/front/src/Phaser/Entity/Character.ts index 2e0bd363..1666063f 100644 --- a/front/src/Phaser/Entity/Character.ts +++ b/front/src/Phaser/Entity/Character.ts @@ -32,7 +32,7 @@ export abstract class Character extends Container { private readonly playerName: Text; public PlayerValue: string; public sprites: Map; - private lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down; + protected lastDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down; //private teleportation: Sprite; private invisible: boolean; public companion?: Companion; @@ -266,24 +266,20 @@ export abstract class Character extends Container { body.setVelocity(x, y); - // up or down animations are prioritized over left and right - if (body.velocity.y < 0) { - //moving up - this.lastDirection = PlayerAnimationDirections.Up; - this.playAnimation(PlayerAnimationDirections.Up, true); - } else if (body.velocity.y > 0) { - //moving down - this.lastDirection = PlayerAnimationDirections.Down; - this.playAnimation(PlayerAnimationDirections.Down, true); - } else if (body.velocity.x > 0) { - //moving right - this.lastDirection = PlayerAnimationDirections.Right; - this.playAnimation(PlayerAnimationDirections.Right, true); - } else if (body.velocity.x < 0) { - //moving left - this.lastDirection = PlayerAnimationDirections.Left; - this.playAnimation(PlayerAnimationDirections.Left, true); + if (Math.abs(body.velocity.x) > Math.abs(body.velocity.y)) { + if (body.velocity.x < 0) { + this.lastDirection = PlayerAnimationDirections.Left; + } else if (body.velocity.x > 0) { + this.lastDirection = PlayerAnimationDirections.Right; + } + } else { + if (body.velocity.y < 0) { + this.lastDirection = PlayerAnimationDirections.Up; + } else if (body.velocity.y > 0) { + this.lastDirection = PlayerAnimationDirections.Down; + } } + this.playAnimation(this.lastDirection, true); this.setDepth(this.y); diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index ae89e2c3..6d735182 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1715,6 +1715,10 @@ ${escapedMessage} }); } + public findPlayer(testFunction: (player: RemotePlayer) => boolean): RemotePlayer | undefined { + return Array.from(this.MapPlayersByKey.values()).find(testFunction); + } + /** * Called by the connexion when a new player arrives on a map */ diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index d8de9ab6..61951514 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -18,11 +18,6 @@ export const hasMovedEventName = "hasMoved"; export const requestEmoteEventName = "requestEmote"; export class Player extends Character { - private previousDirection: PlayerAnimationDirections = PlayerAnimationDirections.Down; - private wasMoving: boolean = false; - private timeCounter: number = 0; - private follow: { followPlayer: RemotePlayer; direction: PlayerAnimationDirections } | null = null; - constructor( Scene: GameScene, x: number, @@ -41,9 +36,9 @@ export class Player extends Character { this.getBody().setImmovable(false); } - private inputStep(activeEvents: ActiveEventList, delta: number) { + private inputStep(activeEvents: ActiveEventList) { //if user client on shift, camera and player speed - let direction = null; + let direction = this.lastDirection; let moving = false; const speedMultiplier = activeEvents.get(UserInputEvent.SpeedUp) ? 25 : 9; @@ -77,22 +72,22 @@ export class Player extends Character { if (x !== 0 || y !== 0) { this.move(x, y); this.emit(hasMovedEventName, { moving, direction, x: this.x, y: this.y, oldX: x, oldY: y }); - } else if (this.wasMoving && moving) { + } else if (get(userMovingStore) && moving) { // slow joystick movement this.move(0, 0); this.emit(hasMovedEventName, { moving, - direction: this.previousDirection, + direction: direction, x: this.x, y: this.y, oldX: x, oldY: y, }); - } else if (this.wasMoving && !moving) { + } else if (get(userMovingStore) && !moving) { this.stop(); this.emit(hasMovedEventName, { moving, - direction: this.previousDirection, + direction: direction, x: this.x, y: this.y, oldX: x, @@ -100,35 +95,27 @@ export class Player extends Character { }); } - if (direction !== null) { - this.previousDirection = direction; - } - - this.wasMoving = moving; userMovingStore.set(moving); } - private followStep(activeEvents: ActiveEventList, delta: number) { - let moving = false; - - if (this.follow === null) { + private followStep(delta: number) { + const player = this.scene.findPlayer((p) => p.PlayerValue === get(followUsersStore)[0]); + if (!player) { + this.scene.connection?.emitFollowAbort(get(followUsersStore)[0], this.PlayerValue); + followStateStore.set(followStates.off); return; } - this.timeCounter += delta; - if (this.timeCounter < 128) { - return; - } - this.timeCounter = 0; - - const xDist = this.follow.followPlayer.x - this.x; - const yDist = this.follow.followPlayer.y - this.y; - + const xDist = player.x - this.x; + const yDist = player.y - this.y; const distance = Math.pow(xDist, 2) + Math.pow(yDist, 2); + let moving = false; + let direction = this.lastDirection; if (distance < 2000) { this.stop(); } else { + moving = true; const moveAmount = 9 * 20; const xDir = xDist / Math.sqrt(distance); const yDir = yDist / Math.sqrt(distance); @@ -136,46 +123,24 @@ export class Player extends Character { this.move(xDir * moveAmount, yDir * moveAmount); if (Math.abs(xDist) > Math.abs(yDist)) { - if (xDist < 0) { - this.follow.direction = PlayerAnimationDirections.Left; - } else { - this.follow.direction = PlayerAnimationDirections.Right; - } + direction = xDist < 0 ? PlayerAnimationDirections.Left : PlayerAnimationDirections.Right; } else { - if (yDist < 0) { - this.follow.direction = PlayerAnimationDirections.Up; - } else { - this.follow.direction = PlayerAnimationDirections.Down; - } + direction = yDist < 0 ? PlayerAnimationDirections.Up : PlayerAnimationDirections.Down; } - - moving = true; } this.emit(hasMovedEventName, { moving: moving, - direction: this.follow.direction, + direction: direction, x: this.x, y: this.y, }); - this.previousDirection = this.follow.direction; - - this.wasMoving = moving; userMovingStore.set(moving); } public enableFollowing() { - Array.from(this.scene.MapPlayersByKey.values()).forEach((player) => { - if (player.PlayerValue !== get(followUsersStore)[0]) { - return; - } - this.follow = { - followPlayer: player, - direction: this.previousDirection, - }; - followStateStore.set(followStates.active); - }); + followStateStore.set(followStates.active); } public moveUser(delta: number): void { @@ -193,13 +158,9 @@ export class Player extends Character { } if ((state !== followStates.active && state !== followStates.ending) || role !== followRoles.follower) { - this.inputStep(activeEvents, delta); + this.inputStep(activeEvents); } else { - this.followStep(activeEvents, delta); + this.followStep(delta); } } - - public isMoving(): boolean { - return this.wasMoving; - } } From 1ab8165951d71e76cec0fced877a537fab48a9e3 Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Wed, 15 Dec 2021 00:10:28 +0100 Subject: [PATCH 13/95] Process input events in follow mode as well --- front/src/Phaser/Player/Player.ts | 129 +++++++++++------------------- 1 file changed, 48 insertions(+), 81 deletions(-) diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index 61951514..285163e8 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -36,107 +36,73 @@ export class Player extends Character { this.getBody().setImmovable(false); } - private inputStep(activeEvents: ActiveEventList) { - //if user client on shift, camera and player speed - let direction = this.lastDirection; - let moving = false; - - const speedMultiplier = activeEvents.get(UserInputEvent.SpeedUp) ? 25 : 9; - const moveAmount = speedMultiplier * 20; - - let x = 0; - let y = 0; - + private inputStep(activeEvents: ActiveEventList, x: number, y: number) { + // Process input events if (activeEvents.get(UserInputEvent.MoveUp)) { - y = -moveAmount; - direction = PlayerAnimationDirections.Up; - moving = true; + y = y - 1; } else if (activeEvents.get(UserInputEvent.MoveDown)) { - y = moveAmount; - direction = PlayerAnimationDirections.Down; - moving = true; + y = y + 1; } if (activeEvents.get(UserInputEvent.MoveLeft)) { - x = -moveAmount; - direction = PlayerAnimationDirections.Left; - moving = true; + x = x - 1; } else if (activeEvents.get(UserInputEvent.MoveRight)) { - x = moveAmount; - direction = PlayerAnimationDirections.Right; - moving = true; + x = x + 1; } - moving = moving || activeEvents.get(UserInputEvent.JoystickMove); + // Compute movement deltas + const speedMultiplier = activeEvents.get(UserInputEvent.SpeedUp) ? 25 : 9; + const moveAmount = speedMultiplier * 20; + x = x * moveAmount; + y = y * moveAmount; - if (x !== 0 || y !== 0) { + // Compute moving state + const joystickMovement = activeEvents.get(UserInputEvent.JoystickMove); + const moving = x !== 0 || y !== 0 || joystickMovement; + + // Compute direction + let direction = this.lastDirection; + if (moving && !joystickMovement) { + if (Math.abs(x) > Math.abs(y)) { + direction = x < 0 ? PlayerAnimationDirections.Left : PlayerAnimationDirections.Right; + } else { + direction = y < 0 ? PlayerAnimationDirections.Up : PlayerAnimationDirections.Down; + } + } + + // Send movement events + const emit = () => this.emit(hasMovedEventName, { moving, direction, x: this.x, y: this.y }); + if (moving) { this.move(x, y); - this.emit(hasMovedEventName, { moving, direction, x: this.x, y: this.y, oldX: x, oldY: y }); - } else if (get(userMovingStore) && moving) { - // slow joystick movement - this.move(0, 0); - this.emit(hasMovedEventName, { - moving, - direction: direction, - x: this.x, - y: this.y, - oldX: x, - oldY: y, - }); - } else if (get(userMovingStore) && !moving) { + emit(); + } else if (get(userMovingStore)) { this.stop(); - this.emit(hasMovedEventName, { - moving, - direction: direction, - x: this.x, - y: this.y, - oldX: x, - oldY: y, - }); + emit(); } + // Update state userMovingStore.set(moving); } - private followStep(delta: number) { + private computeFollowMovement(): number[] { + // Find followed WOKA and abort following if we lost it const player = this.scene.findPlayer((p) => p.PlayerValue === get(followUsersStore)[0]); if (!player) { this.scene.connection?.emitFollowAbort(get(followUsersStore)[0], this.PlayerValue); followStateStore.set(followStates.off); - return; + return [0, 0]; } - const xDist = player.x - this.x; - const yDist = player.y - this.y; - const distance = Math.pow(xDist, 2) + Math.pow(yDist, 2); - - let moving = false; - let direction = this.lastDirection; + // Compute movement direction + const xDistance = player.x - this.x; + const yDistance = player.y - this.y; + const distance = Math.pow(xDistance, 2) + Math.pow(yDistance, 2); if (distance < 2000) { - this.stop(); - } else { - moving = true; - const moveAmount = 9 * 20; - const xDir = xDist / Math.sqrt(distance); - const yDir = yDist / Math.sqrt(distance); - - this.move(xDir * moveAmount, yDir * moveAmount); - - if (Math.abs(xDist) > Math.abs(yDist)) { - direction = xDist < 0 ? PlayerAnimationDirections.Left : PlayerAnimationDirections.Right; - } else { - direction = yDist < 0 ? PlayerAnimationDirections.Up : PlayerAnimationDirections.Down; - } + return [0, 0]; } - - this.emit(hasMovedEventName, { - moving: moving, - direction: direction, - x: this.x, - y: this.y, - }); - - userMovingStore.set(moving); + const xMovement = xDistance / Math.sqrt(distance); + const yMovement = yDistance / Math.sqrt(distance); + return [xMovement, yMovement]; } public enableFollowing() { @@ -157,10 +123,11 @@ export class Player extends Character { } } - if ((state !== followStates.active && state !== followStates.ending) || role !== followRoles.follower) { - this.inputStep(activeEvents); - } else { - this.followStep(delta); + let x = 0; + let y = 0; + if ((state === followStates.active || state === followStates.ending) && role === followRoles.follower) { + [x, y] = this.computeFollowMovement(); } + this.inputStep(activeEvents, x, y); } } From e5286824037763701da23d9dc8fedcd97665101d Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Wed, 15 Dec 2021 13:21:30 +0100 Subject: [PATCH 14/95] Use User.group instead of iterating over groups Thanks @moufmouf for pointing this out. --- back/src/Model/GameRoom.ts | 21 +++++---------------- back/src/Services/SocketManager.ts | 3 +-- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index e8803762..79ed9d3c 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -92,15 +92,6 @@ export class GameRoom { return gameRoom; } - public getGroups(): Group[] { - return Array.from(this.groups.values()); - } - - public getGroupIncludingUser(user: User): Group | undefined { - const foundGroups = this.getGroups().filter((grp) => grp.includes(user)); - return foundGroups[0]; - } - public getUsers(): Map { return this.users; } @@ -239,13 +230,11 @@ export class GameRoom { } public sendToOthersInGroupIncludingUser(user: User, message: ServerToClientMessage): void { - this.getGroupIncludingUser(user) - ?.getUsers() - .forEach((currentUser: User) => { - if (currentUser.name !== user.name) { - currentUser.socket.write(message); - } - }); + user.group?.getUsers().forEach((currentUser: User) => { + if (currentUser.name !== user.name) { + currentUser.socket.write(message); + } + }); } public sendToUserWithName(name: string, message: ServerToClientMessage): void { diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index 727cf430..5818afa9 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -860,8 +860,7 @@ export class SocketManager { room.sendToOthersInGroupIncludingUser(user, clientMessage); // Update followers - const group = room.getGroupIncludingUser(user); - group?.getUsers().forEach((user) => { + user.group?.getUsers().forEach((user) => { user.following = []; }); } else { From 2bd71790b58c0ac26edb187dc402ac1342ab4b58 Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Wed, 15 Dec 2021 14:48:45 +0100 Subject: [PATCH 15/95] Use user IDs instead of names --- back/src/Model/GameRoom.ts | 9 ------ back/src/Model/User.ts | 12 ++++---- back/src/Services/SocketManager.ts | 12 ++++---- .../InteractMenu/InteractMenu.svelte | 28 +++++++++---------- front/src/Connexion/RoomConnection.ts | 24 ++++++++-------- front/src/Phaser/Game/GameScene.ts | 4 --- front/src/Phaser/Player/Player.ts | 4 +-- front/src/Stores/InteractStore.ts | 2 +- messages/protos/messages.proto | 10 +++---- 9 files changed, 48 insertions(+), 57 deletions(-) diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index 79ed9d3c..9afeae55 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -96,11 +96,6 @@ export class GameRoom { return this.users; } - public getUserByName(name: string): User | undefined { - let foundUsers = Array.from(this.users.values()); - foundUsers = foundUsers.filter((user: User) => user.name === name); - return foundUsers[0]; - } public getUserByUuid(uuid: string): User | undefined { return this.usersByUuid.get(uuid); } @@ -237,10 +232,6 @@ export class GameRoom { }); } - public sendToUserWithName(name: string, message: ServerToClientMessage): void { - this.getUserByName(name)?.socket.write(message); - } - setSilent(user: User, silent: boolean) { if (user.silent === silent) { return; diff --git a/back/src/Model/User.ts b/back/src/Model/User.ts index 09c0d3d9..13a61c3f 100644 --- a/back/src/Model/User.ts +++ b/back/src/Model/User.ts @@ -25,7 +25,7 @@ export class User implements Movable { public readonly IPAddress: string, private position: PointInterface, public silent: boolean, - public following: string[], + public following: number[], private positionNotifier: PositionNotifier, public readonly socket: UserSocket, public readonly tags: string[], @@ -49,15 +49,15 @@ export class User implements Movable { this.positionNotifier.updatePosition(this, position, oldPosition); } - public addFollower(name: string): void { - if (this.following.includes(name)) { + public addFollower(userId: number): void { + if (this.following.includes(userId)) { return; } - this.following.push(name); + this.following.push(userId); } - public delFollower(name: string): void { - const idx = this.following.indexOf(name); + public delFollower(userId: number): void { + const idx = this.following.indexOf(userId); if (idx === -1) { return; } diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index 5818afa9..8c7eecac 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -846,16 +846,17 @@ export class SocketManager { handleFollowConfirmationMessage(room: GameRoom, user: User, message: FollowConfirmationMessage) { const clientMessage = new ServerToClientMessage(); clientMessage.setFollowconfirmationmessage(message); - room.sendToUserWithName(message.getLeader(), clientMessage); + const leader = room.getUserById(message.getLeader()); + leader?.socket.write(clientMessage); - room.getUserByName(message.getLeader())?.addFollower(user.name); + leader?.addFollower(user.id); user.addFollower(message.getLeader()); } handleFollowAbortMessage(room: GameRoom, user: User, message: FollowAbortMessage) { const clientMessage = new ServerToClientMessage(); clientMessage.setFollowabortmessage(message); - if (user.name === message.getLeader()) { + if (user.id === message.getLeader()) { // Forward message room.sendToOthersInGroupIncludingUser(user, clientMessage); @@ -865,10 +866,11 @@ export class SocketManager { }); } else { // Forward message - room.sendToUserWithName(message.getLeader(), clientMessage); + const leader = room.getUserById(message.getLeader()); + leader?.socket.write(clientMessage); // Update followers - room.getUserByName(message.getLeader())?.delFollower(user.name); + leader?.delFollower(user.id); user.following = []; } } diff --git a/front/src/Components/InteractMenu/InteractMenu.svelte b/front/src/Components/InteractMenu/InteractMenu.svelte index 6f5e62b2..dae7f099 100644 --- a/front/src/Components/InteractMenu/InteractMenu.svelte +++ b/front/src/Components/InteractMenu/InteractMenu.svelte @@ -19,7 +19,7 @@ vim: ft=typescript let followState: string; let followRole: string; - let followUsers: string[]; + let followUsers: number[]; let stateUnsubscriber: Unsubscriber; let roleUnsubscriber: Unsubscriber; let nameUnsubscriber: Unsubscriber; @@ -51,14 +51,18 @@ vim: ft=typescript } }); + function name(userId: number): string | undefined { + return gameScene.MapPlayersByKey.get(userId)?.PlayerValue; + } + function sendFollowRequest() { - gameScene.connection?.emitFollowRequest(gameManager.getPlayerName()); + gameScene.connection?.emitFollowRequest(); followStateStore.set(followStates.active); } function acceptFollowRequest() { gameScene.CurrentPlayer.enableFollowing(); - gameScene.connection?.emitFollowConfirmation(followUsers[0], gameManager.getPlayerName()); + gameScene.connection?.emitFollowConfirmation(); } function abortEnding() { @@ -66,11 +70,7 @@ vim: ft=typescript } function reset() { - if (followRole === followRoles.leader && followUsers.length > 0) { - gameScene.connection?.emitFollowAbort(gameManager.getPlayerName(), "*"); - } else { - gameScene.connection?.emitFollowAbort(followUsers[0], gameManager.getPlayerName()); - } + gameScene.connection?.emitFollowAbort(); followStateStore.set(followStates.off); followRoleStore.set(followRoles.leader); followUsersStore.set([]); @@ -92,7 +92,7 @@ vim: ft=typescript {#if followRole === followRoles.follower}
-

Do you want to follow {followUsers[0]}?

+

Do you want to follow {name(followUsers[0])}?

@@ -117,7 +117,7 @@ vim: ft=typescript
{#if followRole === followRoles.follower}
-

Do you want to stop following {followUsers[0]}?

+

Do you want to stop following {name(followUsers[0])}?

{:else if followRole === followRoles.leader}
@@ -135,15 +135,15 @@ vim: ft=typescript
{#if followRole === followRoles.follower} -

Following {followUsers[0]}

+

Following {name(followUsers[0])}

{:else if followUsers.length === 0}

Waiting for followers' confirmation

{:else if followUsers.length === 1} -

{followUsers[0]} is following you

+

{name(followUsers[0])} is following you

{:else if followUsers.length === 2} -

{followUsers[0]} and {followUsers[1]} are following you

+

{name(followUsers[0])} and {name(followUsers[1])} are following you

{:else} -

{followUsers[0]}, {followUsers[1]} and {followUsers[2]} are following you

+

{name(followUsers[0])}, {name(followUsers[1])} and {name(followUsers[2])} are following you

{/if}
diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 6d0abaef..49b2dc4c 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -750,39 +750,41 @@ export class RoomConnection implements RoomConnection { this.socket.send(clientToServerMessage.serializeBinary().buffer); } - public emitFollowRequest(user: string | null): void { - if (!user) { + public emitFollowRequest(): void { + if (!this.userId) { return; } console.log("Emitting follow request"); const message = new FollowRequestMessage(); - message.setLeader(user); + message.setLeader(this.userId); const clientToServerMessage = new ClientToServerMessage(); clientToServerMessage.setFollowrequestmessage(message); this.socket.send(clientToServerMessage.serializeBinary().buffer); } - public emitFollowConfirmation(leader: string | null, follower: string | null): void { - if (!leader || !follower) { + public emitFollowConfirmation(): void { + if (!this.userId) { return; } console.log("Emitting follow confirmation"); const message = new FollowConfirmationMessage(); - message.setLeader(leader); - message.setFollower(follower); + message.setLeader(get(followUsersStore)[0]); + message.setFollower(this.userId); const clientToServerMessage = new ClientToServerMessage(); clientToServerMessage.setFollowconfirmationmessage(message); this.socket.send(clientToServerMessage.serializeBinary().buffer); } - public emitFollowAbort(leader: string | null, follower: string | null): void { - if (!leader || !follower) { + public emitFollowAbort(): void { + const isLeader = get(followRoleStore) === followRoles.leader; + const hasFollowers = get(followUsersStore).length > 0; + if (!this.userId || (isLeader && !hasFollowers)) { return; } console.log("Emitting follow abort"); const message = new FollowAbortMessage(); - message.setLeader(leader); - message.setFollower(follower); + message.setLeader(isLeader ? this.userId : get(followUsersStore)[0]); + message.setFollower(isLeader ? 0 : this.userId); const clientToServerMessage = new ClientToServerMessage(); clientToServerMessage.setFollowabortmessage(message); this.socket.send(clientToServerMessage.serializeBinary().buffer); diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 6d735182..ae89e2c3 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -1715,10 +1715,6 @@ ${escapedMessage} }); } - public findPlayer(testFunction: (player: RemotePlayer) => boolean): RemotePlayer | undefined { - return Array.from(this.MapPlayersByKey.values()).find(testFunction); - } - /** * Called by the connexion when a new player arrives on a map */ diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index 285163e8..159816e3 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -86,9 +86,9 @@ export class Player extends Character { private computeFollowMovement(): number[] { // Find followed WOKA and abort following if we lost it - const player = this.scene.findPlayer((p) => p.PlayerValue === get(followUsersStore)[0]); + const player = this.scene.MapPlayersByKey.get(get(followUsersStore)[0]); if (!player) { - this.scene.connection?.emitFollowAbort(get(followUsersStore)[0], this.PlayerValue); + this.scene.connection?.emitFollowAbort(); followStateStore.set(followStates.off); return [0, 0]; } diff --git a/front/src/Stores/InteractStore.ts b/front/src/Stores/InteractStore.ts index 960a6954..6c85ab17 100644 --- a/front/src/Stores/InteractStore.ts +++ b/front/src/Stores/InteractStore.ts @@ -14,4 +14,4 @@ export const followRoles = { export const followStateStore = writable(followStates.off); export const followRoleStore = writable(followRoles.leader); -export const followUsersStore = writable([]); +export const followUsersStore = writable([]); diff --git a/messages/protos/messages.proto b/messages/protos/messages.proto index 7152f43f..96fecb69 100644 --- a/messages/protos/messages.proto +++ b/messages/protos/messages.proto @@ -81,17 +81,17 @@ message QueryJitsiJwtMessage { } message FollowRequestMessage { - string leader = 1; + int32 leader = 1; } message FollowConfirmationMessage { - string leader = 1; - string follower = 2; + int32 leader = 1; + int32 follower = 2; } message FollowAbortMessage { - string leader = 1; - string follower = 2; + int32 leader = 1; + int32 follower = 2; } message ClientToServerMessage { From e3e7fba53924d38a3448dae9c5932525481711f8 Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Wed, 15 Dec 2021 14:57:44 +0100 Subject: [PATCH 16/95] Rename InteractMenu/-Store => FollowMenu/-Store --- front/src/Components/App.svelte | 6 +++--- .../InteractMenu.svelte => FollowMenu/FollowMenu.svelte} | 2 +- front/src/Connexion/RoomConnection.ts | 8 +------- front/src/Phaser/Player/Player.ts | 2 +- front/src/Stores/{InteractStore.ts => FollowStore.ts} | 0 5 files changed, 6 insertions(+), 12 deletions(-) rename front/src/Components/{InteractMenu/InteractMenu.svelte => FollowMenu/FollowMenu.svelte} (99%) rename front/src/Stores/{InteractStore.ts => FollowStore.ts} (100%) diff --git a/front/src/Components/App.svelte b/front/src/Components/App.svelte index b14801c9..5f09beef 100644 --- a/front/src/Components/App.svelte +++ b/front/src/Components/App.svelte @@ -42,8 +42,8 @@ import AudioManager from "./AudioManager/AudioManager.svelte"; import { showReportScreenStore, userReportEmpty } from "../Stores/ShowReportScreenStore"; import ReportMenu from "./ReportMenu/ReportMenu.svelte"; - import { followStateStore, followStates } from "../Stores/InteractStore"; - import InteractMenu from "./InteractMenu/InteractMenu.svelte"; + import { followStateStore, followStates } from "../Stores/FollowStore"; + import FollowMenu from "./FollowMenu/FollowMenu.svelte"; export let game: Game; @@ -106,7 +106,7 @@ {/if} {#if $followStateStore !== followStates.off}
- +
{/if} {#if $menuIconVisiblilityStore} diff --git a/front/src/Components/InteractMenu/InteractMenu.svelte b/front/src/Components/FollowMenu/FollowMenu.svelte similarity index 99% rename from front/src/Components/InteractMenu/InteractMenu.svelte rename to front/src/Components/FollowMenu/FollowMenu.svelte index dae7f099..590fd61c 100644 --- a/front/src/Components/InteractMenu/InteractMenu.svelte +++ b/front/src/Components/FollowMenu/FollowMenu.svelte @@ -13,7 +13,7 @@ vim: ft=typescript followUsersStore, followRoles, followStates, - } from "../../Stores/InteractStore"; + } from "../../Stores/FollowStore"; const gameScene = gameManager.getCurrentGameScene(); diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 49b2dc4c..f8d385d4 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -62,13 +62,7 @@ import { connectionManager } from "./ConnectionManager"; import { emoteEventStream } from "./EmoteEventStream"; import { get } from "svelte/store"; import { warningContainerStore } from "../Stores/MenuStore"; -import { - followStateStore, - followRoleStore, - followUsersStore, - followRoles, - followStates, -} from "../Stores/InteractStore"; +import { followStateStore, followRoleStore, followUsersStore, followRoles, followStates } from "../Stores/FollowStore"; const manualPingDelay = 20000; diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index 159816e3..f34d1478 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -12,7 +12,7 @@ import { followUsersStore, followRoles, followStates, -} from "../../Stores/InteractStore"; +} from "../../Stores/FollowStore"; export const hasMovedEventName = "hasMoved"; export const requestEmoteEventName = "requestEmote"; diff --git a/front/src/Stores/InteractStore.ts b/front/src/Stores/FollowStore.ts similarity index 100% rename from front/src/Stores/InteractStore.ts rename to front/src/Stores/FollowStore.ts From d3297a448e061bbdb3843facc57692b3dedae5e1 Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Wed, 15 Dec 2021 19:47:14 +0100 Subject: [PATCH 17/95] Add setting that ignores follow invites --- front/src/Components/Menu/SettingsSubMenu.svelte | 14 ++++++++++++++ front/src/Connexion/LocalUserStore.ts | 8 ++++++++ front/src/Connexion/RoomConnection.ts | 12 ++++++------ 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/front/src/Components/Menu/SettingsSubMenu.svelte b/front/src/Components/Menu/SettingsSubMenu.svelte index 93d3eaa9..1db14036 100644 --- a/front/src/Components/Menu/SettingsSubMenu.svelte +++ b/front/src/Components/Menu/SettingsSubMenu.svelte @@ -8,6 +8,7 @@ let fullscreen: boolean = localUserStore.getFullscreen(); let notification: boolean = localUserStore.getNotification() === "granted"; let forceCowebsiteTrigger: boolean = localUserStore.getForceCowebsiteTrigger(); + let ignoreFollowRequests: boolean = localUserStore.getIgnoreFollowRequests(); let valueGame: number = localUserStore.getGameQualityValue(); let valueVideo: number = localUserStore.getVideoQualityValue(); let previewValueGame = valueGame; @@ -59,6 +60,10 @@ localUserStore.setForceCowebsiteTrigger(forceCowebsiteTrigger); } + function changeIgnoreFollowRequests() { + localUserStore.setIgnoreFollowRequests(ignoreFollowRequests); + } + function closeMenu() { menuVisiblilityStore.set(false); } @@ -123,6 +128,15 @@ /> Always ask before opening websites and Jitsi Meet rooms +
diff --git a/front/src/Connexion/LocalUserStore.ts b/front/src/Connexion/LocalUserStore.ts index 30755034..4dce6924 100644 --- a/front/src/Connexion/LocalUserStore.ts +++ b/front/src/Connexion/LocalUserStore.ts @@ -14,6 +14,7 @@ const audioPlayerMuteKey = "audioMute"; const helpCameraSettingsShown = "helpCameraSettingsShown"; const fullscreenKey = "fullscreen"; const forceCowebsiteTriggerKey = "forceCowebsiteTrigger"; +const ignoreFollowRequests = "ignoreFollowRequests"; const lastRoomUrl = "lastRoomUrl"; const authToken = "authToken"; const state = "state"; @@ -128,6 +129,13 @@ class LocalUserStore { return localStorage.getItem(forceCowebsiteTriggerKey) === "true"; } + setIgnoreFollowRequests(value: boolean): void { + localStorage.setItem(ignoreFollowRequests, value.toString()); + } + getIgnoreFollowRequests(): boolean { + return localStorage.getItem(ignoreFollowRequests) === "true"; + } + setLastRoomUrl(roomUrl: string): void { localStorage.setItem(lastRoomUrl, roomUrl.toString()); if ("caches" in window) { diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index f8d385d4..f4cbc251 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -63,6 +63,7 @@ import { emoteEventStream } from "./EmoteEventStream"; import { get } from "svelte/store"; import { warningContainerStore } from "../Stores/MenuStore"; import { followStateStore, followRoleStore, followUsersStore, followRoles, followStates } from "../Stores/FollowStore"; +import { localUserStore } from "./LocalUserStore"; const manualPingDelay = 20000; @@ -264,17 +265,16 @@ export class RoomConnection implements RoomConnection { //todo: implement a way to notify the user the room was refreshed. } else if (message.hasFollowrequestmessage()) { const requestMessage = message.getFollowrequestmessage() as FollowRequestMessage; - console.log("Got follow request from " + requestMessage.getLeader()); - followStateStore.set(followStates.requesting); - followRoleStore.set(followRoles.follower); - followUsersStore.set([requestMessage.getLeader()]); + if (!localUserStore.getIgnoreFollowRequests()) { + followStateStore.set(followStates.requesting); + followRoleStore.set(followRoles.follower); + followUsersStore.set([requestMessage.getLeader()]); + } } else if (message.hasFollowconfirmationmessage()) { const responseMessage = message.getFollowconfirmationmessage() as FollowConfirmationMessage; - console.log("Got follow response from " + responseMessage.getFollower()); followUsersStore.set([...get(followUsersStore), responseMessage.getFollower()]); } else if (message.hasFollowabortmessage()) { const abortMessage = message.getFollowabortmessage() as FollowAbortMessage; - console.log("Got follow abort message"); if (get(followRoleStore) === followRoles.follower) { followStateStore.set(followStates.off); followRoleStore.set(followRoles.leader); From a48137663321e1657d03c0c5e422b865674520d3 Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Wed, 15 Dec 2021 20:11:42 +0100 Subject: [PATCH 18/95] Clean up remaining debug log messages --- front/src/Connexion/RoomConnection.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index f4cbc251..0c8390b8 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -748,7 +748,6 @@ export class RoomConnection implements RoomConnection { if (!this.userId) { return; } - console.log("Emitting follow request"); const message = new FollowRequestMessage(); message.setLeader(this.userId); const clientToServerMessage = new ClientToServerMessage(); @@ -760,7 +759,6 @@ export class RoomConnection implements RoomConnection { if (!this.userId) { return; } - console.log("Emitting follow confirmation"); const message = new FollowConfirmationMessage(); message.setLeader(get(followUsersStore)[0]); message.setFollower(this.userId); @@ -775,7 +773,6 @@ export class RoomConnection implements RoomConnection { if (!this.userId || (isLeader && !hasFollowers)) { return; } - console.log("Emitting follow abort"); const message = new FollowAbortMessage(); message.setLeader(isLeader ? this.userId : get(followUsersStore)[0]); message.setFollower(isLeader ? 0 : this.userId); From ab994183e5eb0be3c503d41798e55f9307cb7b68 Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Thu, 16 Dec 2021 21:26:30 +0100 Subject: [PATCH 19/95] Fix not following users getting "stuck" in groups --- back/src/Model/GameRoom.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index 9afeae55..43605778 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -217,9 +217,22 @@ export class GameRoom { } else { // If the user is part of a group: // should he leave the group? - const distance = GameRoom.computeDistanceBetweenPositions(user.getPosition(), user.group.getPosition()); - if (user.following.length === 0 && distance > this.groupRadius) { - this.leaveGroup(user); + const leaveIfOutOfRadius = (user: User) => { + if (user.group === undefined) { + return; + } + const usrPos = user.getPosition(); + const grpPos = user.group.getPosition(); + const distance = GameRoom.computeDistanceBetweenPositions(usrPos, grpPos); + if (distance > this.groupRadius) { + this.leaveGroup(user); + } + }; + if (user.following.length > 0) { + const users = user.group.getUsers().filter((u) => u.following.length === 0); + users.forEach((foreignUser) => leaveIfOutOfRadius(foreignUser)); + } else { + leaveIfOutOfRadius(user); } } } From 44ff9e30d5df375509c8b2afff64f75f3f5dd619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 17 Dec 2021 11:54:37 +0100 Subject: [PATCH 20/95] Using id instead of name to identify other Wokas --- back/src/Model/GameRoom.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index 43605778..ba555c92 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -239,7 +239,7 @@ export class GameRoom { public sendToOthersInGroupIncludingUser(user: User, message: ServerToClientMessage): void { user.group?.getUsers().forEach((currentUser: User) => { - if (currentUser.name !== user.name) { + if (currentUser.id !== user.id) { currentUser.socket.write(message); } }); From fd9cb09de618a82250fea7f2ede5f4fe54821621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 17 Dec 2021 11:55:27 +0100 Subject: [PATCH 21/95] Checking if a user should leave a group when someone moves in the group every time. This fixes a long standing issue. --- back/src/Model/GameRoom.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index ba555c92..0e8c5a10 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -228,12 +228,8 @@ export class GameRoom { this.leaveGroup(user); } }; - if (user.following.length > 0) { - const users = user.group.getUsers().filter((u) => u.following.length === 0); - users.forEach((foreignUser) => leaveIfOutOfRadius(foreignUser)); - } else { - leaveIfOutOfRadius(user); - } + const users = user.group.getUsers().filter((u) => u.following.length === 0); + users.forEach((foreignUser) => leaveIfOutOfRadius(foreignUser)); } } From cd805fab310c32ea28e73093b8b8f7903c318a3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 17 Dec 2021 18:26:17 +0100 Subject: [PATCH 22/95] Refactoring following code to make a distinction beween followed and following users. Also, moving sending messages to the User class. --- back/src/Model/GameRoom.ts | 3 +- back/src/Model/Group.ts | 12 ++++++++ back/src/Model/User.ts | 49 ++++++++++++++++++++++-------- back/src/Services/SocketManager.ts | 32 ++++++++----------- 4 files changed, 63 insertions(+), 33 deletions(-) diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index 0e8c5a10..07b611e8 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -125,7 +125,6 @@ export class GameRoom { joinRoomMessage.getIpaddress(), position, false, - [], this.positionNotifier, socket, joinRoomMessage.getTagList(), @@ -228,7 +227,7 @@ export class GameRoom { this.leaveGroup(user); } }; - const users = user.group.getUsers().filter((u) => u.following.length === 0); + const users = user.group.getUsers().filter((u) => !u.hasFollowers() && !u.following); users.forEach((foreignUser) => leaveIfOutOfRadius(foreignUser)); } } diff --git a/back/src/Model/Group.ts b/back/src/Model/Group.ts index 570eaedf..db9f6305 100644 --- a/back/src/Model/Group.ts +++ b/back/src/Model/Group.ts @@ -152,4 +152,16 @@ export class Group implements Movable { get getSize() { return this.users.size; } + + /** + * A group can have at most one person leading the way in it. + */ + get leader(): User|undefined { + for (const user of this.users) { + if (user.hasFollowers()) { + return user; + } + } + return undefined; + } } diff --git a/back/src/Model/User.ts b/back/src/Model/User.ts index 13a61c3f..4619ce2c 100644 --- a/back/src/Model/User.ts +++ b/back/src/Model/User.ts @@ -6,7 +6,7 @@ import { PositionNotifier } from "_Model/PositionNotifier"; import { ServerDuplexStream } from "grpc"; import { BatchMessage, - CompanionMessage, + CompanionMessage, FollowAbortMessage, FollowConfirmationMessage, PusherToBackMessage, ServerToClientMessage, SubMessage, @@ -18,6 +18,8 @@ export type UserSocket = ServerDuplexStream; public group?: Group; + private _following: User|undefined; + private followedBy: Set = new Set(); public constructor( public id: number, @@ -25,7 +27,6 @@ export class User implements Movable { public readonly IPAddress: string, private position: PointInterface, public silent: boolean, - public following: number[], private positionNotifier: PositionNotifier, public readonly socket: UserSocket, public readonly tags: string[], @@ -49,19 +50,43 @@ export class User implements Movable { this.positionNotifier.updatePosition(this, position, oldPosition); } - public addFollower(userId: number): void { - if (this.following.includes(userId)) { - return; - } - this.following.push(userId); + public addFollower(follower: User): void { + this.followedBy.add(follower); + follower._following = this; + + const message = new FollowConfirmationMessage(); + message.setFollower(follower.id); + message.setLeader(this.id); + const clientMessage = new ServerToClientMessage(); + clientMessage.setFollowconfirmationmessage(message); + this.socket.write(clientMessage); } - public delFollower(userId: number): void { - const idx = this.following.indexOf(userId); - if (idx === -1) { - return; + public delFollower(follower: User): void { + this.followedBy.delete(follower); + follower._following = undefined; + + const message = new FollowAbortMessage(); + message.setFollower(follower.id); + message.setLeader(this.id); + const clientMessage = new ServerToClientMessage(); + clientMessage.setFollowabortmessage(message); + this.socket.write(clientMessage); + follower.socket.write(clientMessage); + } + + public hasFollowers(): boolean { + return this.followedBy.size !== 0; + } + + get following(): User | undefined { + return this._following; + } + + public stopLeading(): void { + for (const follower of this.followedBy) { + this.delFollower(follower); } - this.following.splice(idx, 1); } private batchedMessages: BatchMessage = new BatchMessage(); diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index 8c7eecac..d7178d3d 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -844,34 +844,28 @@ export class SocketManager { } handleFollowConfirmationMessage(room: GameRoom, user: User, message: FollowConfirmationMessage) { - const clientMessage = new ServerToClientMessage(); - clientMessage.setFollowconfirmationmessage(message); const leader = room.getUserById(message.getLeader()); - leader?.socket.write(clientMessage); + if (!leader) { + console.info('Could not find user "', message.getLeader(), '" while handling a follow confirmation in room "', room.roomUrl,'". Maybe the user just left.'); + return; + } - leader?.addFollower(user.id); - user.addFollower(message.getLeader()); + // By security, we look at the group leader. If the group leader is NOT the leader in the message, everybody should + // stop following the group leader (to avoid having 2 group leaders) + if (user?.group?.leader && user?.group?.leader !== leader) { + user?.group?.leader?.stopLeading(); + } + + leader.addFollower(user); } handleFollowAbortMessage(room: GameRoom, user: User, message: FollowAbortMessage) { - const clientMessage = new ServerToClientMessage(); - clientMessage.setFollowabortmessage(message); if (user.id === message.getLeader()) { - // Forward message - room.sendToOthersInGroupIncludingUser(user, clientMessage); - - // Update followers - user.group?.getUsers().forEach((user) => { - user.following = []; - }); + user?.group?.leader?.stopLeading(); } else { // Forward message const leader = room.getUserById(message.getLeader()); - leader?.socket.write(clientMessage); - - // Update followers - leader?.delFollower(user.id); - user.following = []; + leader?.delFollower(user); } } } From c96b65549f92de55652d427aa81eda16a684f7ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 17 Dec 2021 18:29:52 +0100 Subject: [PATCH 23/95] Performing proper cleanup when a user leaves --- back/src/Model/GameRoom.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index 07b611e8..9b0fef45 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -154,6 +154,14 @@ export class GameRoom { if (userObj !== undefined && typeof userObj.group !== "undefined") { this.leaveGroup(userObj); } + + if (user.hasFollowers()) { + user.stopLeading(); + } + if (user.following) { + user.following.delFollower(user); + } + this.users.delete(user.id); this.usersByUuid.delete(user.uuid); From 5c385c520a4f8848bb69947390877b166371ba89 Mon Sep 17 00:00:00 2001 From: PizZaKatZe Date: Sat, 18 Dec 2021 11:30:58 +0100 Subject: [PATCH 24/95] Cleanup; pretty --- back/src/Model/GameRoom.ts | 15 +++++++-------- back/src/Model/Group.ts | 2 +- back/src/Model/User.ts | 6 ++++-- back/src/Services/SocketManager.ts | 7 ++++--- back/tests/PositionNotifierTest.ts | 8 ++++---- 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index 9b0fef45..e7f23549 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -224,19 +224,18 @@ export class GameRoom { } else { // If the user is part of a group: // should he leave the group? - const leaveIfOutOfRadius = (user: User) => { - if (user.group === undefined) { + const users = user.group.getUsers().filter((u) => !u.hasFollowers() && !u.following); + users.forEach((foreignUser: User) => { + if (foreignUser.group === undefined) { return; } - const usrPos = user.getPosition(); - const grpPos = user.group.getPosition(); + const usrPos = foreignUser.getPosition(); + const grpPos = foreignUser.group.getPosition(); const distance = GameRoom.computeDistanceBetweenPositions(usrPos, grpPos); if (distance > this.groupRadius) { - this.leaveGroup(user); + this.leaveGroup(foreignUser); } - }; - const users = user.group.getUsers().filter((u) => !u.hasFollowers() && !u.following); - users.forEach((foreignUser) => leaveIfOutOfRadius(foreignUser)); + }); } } diff --git a/back/src/Model/Group.ts b/back/src/Model/Group.ts index db9f6305..08d1d21e 100644 --- a/back/src/Model/Group.ts +++ b/back/src/Model/Group.ts @@ -156,7 +156,7 @@ export class Group implements Movable { /** * A group can have at most one person leading the way in it. */ - get leader(): User|undefined { + get leader(): User | undefined { for (const user of this.users) { if (user.hasFollowers()) { return user; diff --git a/back/src/Model/User.ts b/back/src/Model/User.ts index 4619ce2c..50f140fc 100644 --- a/back/src/Model/User.ts +++ b/back/src/Model/User.ts @@ -6,7 +6,9 @@ import { PositionNotifier } from "_Model/PositionNotifier"; import { ServerDuplexStream } from "grpc"; import { BatchMessage, - CompanionMessage, FollowAbortMessage, FollowConfirmationMessage, + CompanionMessage, + FollowAbortMessage, + FollowConfirmationMessage, PusherToBackMessage, ServerToClientMessage, SubMessage, @@ -18,7 +20,7 @@ export type UserSocket = ServerDuplexStream; public group?: Group; - private _following: User|undefined; + private _following: User | undefined; private followedBy: Set = new Set(); public constructor( diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index d7178d3d..bafce4d3 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -846,12 +846,13 @@ export class SocketManager { handleFollowConfirmationMessage(room: GameRoom, user: User, message: FollowConfirmationMessage) { const leader = room.getUserById(message.getLeader()); if (!leader) { - console.info('Could not find user "', message.getLeader(), '" while handling a follow confirmation in room "', room.roomUrl,'". Maybe the user just left.'); + const message = `Could not follow user "{message.getLeader()}" in room "{room.roomUrl}".`; + console.info(message, "Maybe the user just left."); return; } - // By security, we look at the group leader. If the group leader is NOT the leader in the message, everybody should - // stop following the group leader (to avoid having 2 group leaders) + // By security, we look at the group leader. If the group leader is NOT the leader in the message, + // everybody should stop following the group leader (to avoid having 2 group leaders) if (user?.group?.leader && user?.group?.leader !== leader) { user?.group?.leader?.stopLeading(); } diff --git a/back/tests/PositionNotifierTest.ts b/back/tests/PositionNotifierTest.ts index 955ed40f..1aaf2e13 100644 --- a/back/tests/PositionNotifierTest.ts +++ b/back/tests/PositionNotifierTest.ts @@ -26,14 +26,14 @@ describe("PositionNotifier", () => { y: 500, moving: false, direction: 'down' - }, false, [], positionNotifier, {} as UserSocket, [], null, 'foo', []); + }, false, positionNotifier, {} as UserSocket, [], null, 'foo', []); const user2 = new User(2, 'test', '10.0.0.2', { x: -9999, y: -9999, moving: false, direction: 'down' - }, false, [], positionNotifier, {} as UserSocket, [], null, 'foo', []); + }, false, positionNotifier, {} as UserSocket, [], null, 'foo', []); positionNotifier.addZoneListener({} as ZoneSocket, 0, 0); positionNotifier.addZoneListener({} as ZoneSocket, 0, 1); @@ -101,14 +101,14 @@ describe("PositionNotifier", () => { y: 500, moving: false, direction: 'down' - }, false, [], positionNotifier, {} as UserSocket, [], null, 'foo', []); + }, false, positionNotifier, {} as UserSocket, [], null, 'foo', []); const user2 = new User(2, 'test', '10.0.0.2', { x: 0, y: 0, moving: false, direction: 'down' - }, false, [], positionNotifier, {} as UserSocket, [], null, 'foo', []); + }, false, positionNotifier, {} as UserSocket, [], null, 'foo', []); const listener = {} as ZoneSocket; positionNotifier.addZoneListener(listener, 0, 0); From 2cd088c0499b9de561ea80df0c9053b8c216d12d Mon Sep 17 00:00:00 2001 From: Lurkars Date: Sat, 18 Dec 2021 10:43:23 +0100 Subject: [PATCH 25/95] Change follow request to "F" button, use nes-css buttons --- .../Components/FollowMenu/FollowMenu.svelte | 28 ++++++------------- front/src/Phaser/Player/Player.ts | 2 +- .../src/Phaser/UserInput/UserInputManager.ts | 5 ++++ 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/front/src/Components/FollowMenu/FollowMenu.svelte b/front/src/Components/FollowMenu/FollowMenu.svelte index 590fd61c..983fc507 100644 --- a/front/src/Components/FollowMenu/FollowMenu.svelte +++ b/front/src/Components/FollowMenu/FollowMenu.svelte @@ -95,16 +95,19 @@ vim: ft=typescript

Do you want to follow {name(followUsers[0])}?

- - + +
{:else if followRole === followRoles.leader}

Ask others to follow you?

- - + +
{/if} @@ -125,8 +128,8 @@ vim: ft=typescript {/if}
- - + +
{/if} @@ -206,19 +209,6 @@ vim: ft=typescript font-weight: bold; height: 2.5em; } - - .accept { - background-color: #00ff0088; - } - .accept:hover { - background-color: #00ff00cc; - } - .deny { - background-color: #ff000088; - } - .deny:hover { - background-color: #ff0000cc; - } } } diff --git a/front/src/Phaser/Player/Player.ts b/front/src/Phaser/Player/Player.ts index f34d1478..f37927e3 100644 --- a/front/src/Phaser/Player/Player.ts +++ b/front/src/Phaser/Player/Player.ts @@ -114,7 +114,7 @@ export class Player extends Character { const state = get(followStateStore); const role = get(followRoleStore); - if (activeEvents.get(UserInputEvent.Interact)) { + if (activeEvents.get(UserInputEvent.Follow)) { if (state === followStates.off && this.scene.groups.size > 0) { followStateStore.set(followStates.requesting); followRoleStore.set(followRoles.leader); diff --git a/front/src/Phaser/UserInput/UserInputManager.ts b/front/src/Phaser/UserInput/UserInputManager.ts index bb7041d2..71dbb6c3 100644 --- a/front/src/Phaser/UserInput/UserInputManager.ts +++ b/front/src/Phaser/UserInput/UserInputManager.ts @@ -16,6 +16,7 @@ export enum UserInputEvent { MoveDown, SpeedUp, Interact, + Follow, Shout, JoystickMove, } @@ -147,6 +148,10 @@ export class UserInputManager { event: UserInputEvent.Interact, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE, false), }, + { + event: UserInputEvent.Follow, + keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.F, false), + }, { event: UserInputEvent.Shout, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.F, false), From 3916d9c58e803e30ef6d4a24cc0a0c7c75c75b96 Mon Sep 17 00:00:00 2001 From: Lurkars Date: Sun, 19 Dec 2021 12:51:19 +0100 Subject: [PATCH 26/95] Add follow button to ui, improved flow --- front/src/Components/App.svelte | 4 +- .../Components/FollowMenu/FollowMenu.svelte | 40 +++++++++++++++++++ front/src/Components/images/follow.svg | 1 + front/src/Phaser/Player/Player.ts | 7 ++++ front/src/Stores/FollowStore.ts | 1 + 5 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 front/src/Components/images/follow.svg diff --git a/front/src/Components/App.svelte b/front/src/Components/App.svelte index 5f09beef..36f815bd 100644 --- a/front/src/Components/App.svelte +++ b/front/src/Components/App.svelte @@ -42,7 +42,7 @@ import AudioManager from "./AudioManager/AudioManager.svelte"; import { showReportScreenStore, userReportEmpty } from "../Stores/ShowReportScreenStore"; import ReportMenu from "./ReportMenu/ReportMenu.svelte"; - import { followStateStore, followStates } from "../Stores/FollowStore"; + import { followStateStore, followRoleStore, followStates, followRoles } from "../Stores/FollowStore"; import FollowMenu from "./FollowMenu/FollowMenu.svelte"; export let game: Game; @@ -104,7 +104,7 @@ {/if} - {#if $followStateStore !== followStates.off} + {#if $followStateStore !== followStates.off || $followRoleStore === followRoles.open}
diff --git a/front/src/Components/FollowMenu/FollowMenu.svelte b/front/src/Components/FollowMenu/FollowMenu.svelte index 983fc507..e26032bb 100644 --- a/front/src/Components/FollowMenu/FollowMenu.svelte +++ b/front/src/Components/FollowMenu/FollowMenu.svelte @@ -6,6 +6,7 @@ vim: ft=typescript import type { Unsubscriber } from "svelte/store"; import { get } from "svelte/store"; import { gameManager } from "../../Phaser/Game/GameManager"; + import followImg from "../images/follow.svg"; import { followStateStore, @@ -76,6 +77,11 @@ vim: ft=typescript followUsersStore.set([]); } + function request() { + followStateStore.set(followStates.requesting); + followRoleStore.set(followRoles.leader); + } + function onKeyDown(e: KeyboardEvent) { if (e.key === "Escape") { reset(); @@ -152,6 +158,33 @@ vim: ft=typescript {/if} +{#if followRole === followRoles.open} + +{/if} + +{#if followState === followStates.active || followState === followStates.ending} + {#if followRole === followRoles.follower} + + {:else if followUsers.length > 0} + + {/if} +{/if} + diff --git a/front/src/Stores/TypeMessageStore/BanMessageStore.ts b/front/src/Stores/TypeMessageStore/BanMessageStore.ts index b8c042b4..4d9a7448 100644 --- a/front/src/Stores/TypeMessageStore/BanMessageStore.ts +++ b/front/src/Stores/TypeMessageStore/BanMessageStore.ts @@ -1,5 +1,3 @@ -import { writable } from "svelte/store"; +import { createMessageStore } from "./MessageStore"; -export const banMessageVisibleStore = writable(false); - -export const banMessageContentStore = writable(""); +export const banMessageStore = createMessageStore(); diff --git a/front/src/Stores/TypeMessageStore/MessageStore.ts b/front/src/Stores/TypeMessageStore/MessageStore.ts new file mode 100644 index 00000000..b6845f34 --- /dev/null +++ b/front/src/Stores/TypeMessageStore/MessageStore.ts @@ -0,0 +1,29 @@ +import { writable } from "svelte/store"; +import { v4 as uuidv4 } from "uuid"; + +export interface Message { + id: string; + text: string; +} + +/** + * A store that contains a list of messages to be displayed. + */ +export function createMessageStore() { + const { subscribe, update } = writable([]); + + return { + subscribe, + addMessage: (text: string): void => { + update((messages: Message[]) => { + return [...messages, { id: uuidv4(), text }]; + }); + }, + clearMessageById: (id: string): void => { + update((messages: Message[]) => { + messages = messages.filter((message) => message.id !== id); + return messages; + }); + }, + }; +} diff --git a/front/src/Stores/TypeMessageStore/TextMessageStore.ts b/front/src/Stores/TypeMessageStore/TextMessageStore.ts index e3969016..bf644125 100644 --- a/front/src/Stores/TypeMessageStore/TextMessageStore.ts +++ b/front/src/Stores/TypeMessageStore/TextMessageStore.ts @@ -1,5 +1,3 @@ -import { writable } from "svelte/store"; +import { createMessageStore } from "./MessageStore"; -export const textMessageVisibleStore = writable(false); - -export const textMessageContentStore = writable(""); +export const textMessageStore = createMessageStore(); From 05bedf0c22ee0ce0f3c512acb3a2815cd6bfcf93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 24 Dec 2021 13:20:49 +0100 Subject: [PATCH 45/95] Adding an "out-of-bounds" notion for a group. This allows hiding a group when some players are out of the radius but the group still exists because of the "follow" feature. --- back/src/Model/GameRoom.ts | 21 ++++++++++++++++----- back/src/Model/Group.ts | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index 4e7cf441..ff8b7cf0 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -219,8 +219,8 @@ export class GameRoom { if (user.silent) { return; } - - if (user.group === undefined) { + const group = user.group; + if (group === undefined) { // If the user is not part of a group: // should he join a group? @@ -251,18 +251,29 @@ export class GameRoom { } else { // If the user is part of a group: // should he leave the group? - const users = user.group.getUsers().filter((u) => !u.hasFollowers() && !u.following); - users.forEach((foreignUser: User) => { + let noOneOutOfBounds = true; + group.getUsers().forEach((foreignUser: User) => { if (foreignUser.group === undefined) { return; } const usrPos = foreignUser.getPosition(); const grpPos = foreignUser.group.getPosition(); const distance = GameRoom.computeDistanceBetweenPositions(usrPos, grpPos); + if (distance > this.groupRadius) { - this.leaveGroup(foreignUser); + if (foreignUser.hasFollowers() || foreignUser.following) { + // If one user is out of the group bounds BUT following, the group still exists... but should be hidden. + // We put it in 'outOfBounds' mode + group.setOutOfBounds(true); + noOneOutOfBounds = false; + } else { + this.leaveGroup(foreignUser); + } } }); + if (noOneOutOfBounds && !user.group?.isEmpty()) { + group.setOutOfBounds(false); + } } } diff --git a/back/src/Model/Group.ts b/back/src/Model/Group.ts index c59334ec..c35ac7a8 100644 --- a/back/src/Model/Group.ts +++ b/back/src/Model/Group.ts @@ -16,6 +16,10 @@ export class Group implements Movable { private wasDestroyed: boolean = false; private roomId: string; private currentZone: Zone | null = null; + /** + * When outOfBounds = true, a user if out of the bounds of the group BUT still considered inside it (because we are in following mode) + */ + private outOfBounds = false; constructor( roomId: string, @@ -78,6 +82,10 @@ export class Group implements Movable { this.x = x; this.y = y; + if (this.outOfBounds) { + return; + } + if (oldX === undefined) { this.currentZone = this.positionNotifier.enter(this); } else { @@ -154,4 +162,14 @@ export class Group implements Movable { } return undefined; } + + setOutOfBounds(outOfBounds: boolean): void { + if (this.outOfBounds === true && outOfBounds === false) { + this.positionNotifier.enter(this); + this.outOfBounds = false; + } else if (this.outOfBounds === false && outOfBounds === true) { + this.positionNotifier.leave(this); + this.outOfBounds = true; + } + } } From fefe7c8aa5053b0d1befcb268a3c063bcb3b0411 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 24 Dec 2021 13:48:35 +0100 Subject: [PATCH 46/95] Removing the "Ask others to follow you?" popup No need to ask twice, the user just pressed the "follow" button. --- front/src/Components/FollowMenu/FollowMenu.svelte | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/front/src/Components/FollowMenu/FollowMenu.svelte b/front/src/Components/FollowMenu/FollowMenu.svelte index c38571bd..035bc06f 100644 --- a/front/src/Components/FollowMenu/FollowMenu.svelte +++ b/front/src/Components/FollowMenu/FollowMenu.svelte @@ -58,6 +58,7 @@ vim: ft=typescript function sendFollowRequest() { gameScene.connection?.emitFollowRequest(); + followRoleStore.set(followRoles.leader); followStateStore.set(followStates.active); } @@ -75,11 +76,6 @@ vim: ft=typescript followUsersStore.stopFollowing(); } - function request() { - followStateStore.set(followStates.requesting); - followRoleStore.set(followRoles.leader); - } - function onKeyDown(e: KeyboardEvent) { if (e.key === "Escape") { reset(); @@ -106,12 +102,7 @@ vim: ft=typescript {:else if followRole === followRoles.leader}
-

Ask others to follow you?

-
-
- - +

Should never be displayed

{/if} @@ -160,7 +151,7 @@ vim: ft=typescript {/if} From d6e8bd522ff8df10f20fee88eb2cb6bb2bb2a467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 24 Dec 2021 14:07:29 +0100 Subject: [PATCH 47/95] Fixing issue if group was deleted when out of bounds. --- back/src/Model/GameRoom.ts | 1 - back/src/Model/Group.ts | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index ff8b7cf0..aefade43 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -312,7 +312,6 @@ export class GameRoom { } group.leave(user); if (group.isEmpty()) { - this.positionNotifier.leave(group); group.destroy(); if (!this.groups.has(group)) { throw new Error(`Could not find group ${group.getId()} referenced by user ${user.id} in World.`); diff --git a/back/src/Model/Group.ts b/back/src/Model/Group.ts index c35ac7a8..0782bd1b 100644 --- a/back/src/Model/Group.ts +++ b/back/src/Model/Group.ts @@ -141,6 +141,10 @@ export class Group implements Movable { * Usually used when there is only one user left. */ destroy(): void { + if (!this.outOfBounds) { + this.positionNotifier.leave(this); + } + for (const user of this.users) { this.leave(user); } From 87bc7f8099ac5fcf8aadfbc7e4528d2d647ae9cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 24 Dec 2021 14:36:38 +0100 Subject: [PATCH 48/95] Slightly improving follow request popup design --- .../Components/FollowMenu/FollowMenu.svelte | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/front/src/Components/FollowMenu/FollowMenu.svelte b/front/src/Components/FollowMenu/FollowMenu.svelte index 035bc06f..59194db2 100644 --- a/front/src/Components/FollowMenu/FollowMenu.svelte +++ b/front/src/Components/FollowMenu/FollowMenu.svelte @@ -87,12 +87,9 @@ vim: ft=typescript {#if followState === followStates.requesting}
-
-

Interaction

-
{#if followRole === followRoles.follower} -
-

Do you want to follow {name(followUsers[0])}?

+
+

Do you want to follow {name(followUsers[0])}?

@@ -197,7 +197,6 @@ vim: ft=typescript color: whitesmoke; position: relative; - height: 19vh; width: 60vw; top: 60vh; margin: auto; @@ -220,17 +219,11 @@ vim: ft=typescript section.interact-menu-action { display: grid; - grid-template-columns: 50% 50%; + grid-gap: 10%; + grid-template-columns: 45% 45%; margin-bottom: 20px; - - button { - display: inline-block; - margin: 4px; - padding: 0px; - border: medium solid black; - font-weight: bold; - height: 2.5em; - } + margin-left: 5%; + margin-right: 5%; } } From 9a3bca065976c3981d7552cccf84567818005773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 24 Dec 2021 14:59:40 +0100 Subject: [PATCH 49/95] Updating Node version to latest minor in Pusher and Back for prod images --- back/Dockerfile | 6 +++--- pusher/Dockerfile | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/back/Dockerfile b/back/Dockerfile index e95145cd..b63db51c 100644 --- a/back/Dockerfile +++ b/back/Dockerfile @@ -1,11 +1,11 @@ # protobuf build -FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder +FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d WORKDIR /usr/src COPY messages . RUN yarn install && yarn proto # typescript build -FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder2 +FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d WORKDIR /usr/src COPY back/yarn.lock back/package.json ./ RUN yarn install @@ -15,7 +15,7 @@ ENV NODE_ENV=production RUN yarn run tsc # final production image -FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 +FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d WORKDIR /usr/src COPY back/yarn.lock back/package.json ./ COPY --from=builder2 /usr/src/dist /usr/src/dist diff --git a/pusher/Dockerfile b/pusher/Dockerfile index 3ead0913..5a448ee8 100644 --- a/pusher/Dockerfile +++ b/pusher/Dockerfile @@ -1,11 +1,11 @@ # protobuf build -FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder +FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d WORKDIR /usr/src COPY messages . RUN yarn install && yarn proto # typescript build -FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder2 +FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d WORKDIR /usr/src COPY pusher/yarn.lock pusher/package.json ./ RUN yarn install @@ -16,7 +16,7 @@ ENV NODE_ENV=production RUN yarn run tsc # final production image -FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 +FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d WORKDIR /usr/src COPY pusher/yarn.lock pusher/package.json ./ COPY --from=builder2 /usr/src/dist /usr/src/dist From a4a89735f6874aa02189b6ab1330efda68c9f8b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 24 Dec 2021 15:04:01 +0100 Subject: [PATCH 50/95] Fixing missing builders --- back/Dockerfile | 4 ++-- pusher/Dockerfile | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/back/Dockerfile b/back/Dockerfile index b63db51c..a8ce3c1c 100644 --- a/back/Dockerfile +++ b/back/Dockerfile @@ -1,11 +1,11 @@ # protobuf build -FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d +FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder WORKDIR /usr/src COPY messages . RUN yarn install && yarn proto # typescript build -FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d +FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder2 WORKDIR /usr/src COPY back/yarn.lock back/package.json ./ RUN yarn install diff --git a/pusher/Dockerfile b/pusher/Dockerfile index 5a448ee8..cd5bfc59 100644 --- a/pusher/Dockerfile +++ b/pusher/Dockerfile @@ -1,11 +1,11 @@ # protobuf build -FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d +FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder WORKDIR /usr/src COPY messages . RUN yarn install && yarn proto # typescript build -FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d +FROM node:14.18.2-buster-slim@sha256:20bedf0c09de887379e59a41c04284974f5fb529cf0e13aab613473ce298da3d as builder2 WORKDIR /usr/src COPY pusher/yarn.lock pusher/package.json ./ RUN yarn install From 840f8626ada4c611ca76285d8128a9bb8aa8d3da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 24 Dec 2021 15:39:28 +0100 Subject: [PATCH 51/95] Refactoring code to use Svelte native "$" store unpacking. --- .../Components/FollowMenu/FollowMenu.svelte | 74 +++++-------------- front/src/Stores/FollowStore.ts | 1 - 2 files changed, 20 insertions(+), 55 deletions(-) diff --git a/front/src/Components/FollowMenu/FollowMenu.svelte b/front/src/Components/FollowMenu/FollowMenu.svelte index 59194db2..6506b1d6 100644 --- a/front/src/Components/FollowMenu/FollowMenu.svelte +++ b/front/src/Components/FollowMenu/FollowMenu.svelte @@ -18,40 +18,6 @@ vim: ft=typescript const gameScene = gameManager.getCurrentGameScene(); - let followState: string; - let followRole: string; - let followUsers: number[]; - let stateUnsubscriber: Unsubscriber; - let roleUnsubscriber: Unsubscriber; - let nameUnsubscriber: Unsubscriber; - - onMount(() => { - followState = get(followStateStore); - followRole = get(followRoleStore); - followUsers = get(followUsersStore); - stateUnsubscriber = followStateStore.subscribe((state) => { - followState = state; - }); - roleUnsubscriber = followRoleStore.subscribe((role) => { - followRole = role; - }); - nameUnsubscriber = followUsersStore.subscribe((users) => { - followUsers = users; - }); - }); - - onDestroy(() => { - if (stateUnsubscriber) { - stateUnsubscriber(); - } - if (roleUnsubscriber) { - roleUnsubscriber(); - } - if (nameUnsubscriber) { - nameUnsubscriber(); - } - }); - function name(userId: number): string | undefined { return gameScene.MapPlayersByKey.get(userId)?.PlayerValue; } @@ -85,11 +51,11 @@ vim: ft=typescript -{#if followState === followStates.requesting} +{#if $followStateStore === followStates.requesting}
- {#if followRole === followRoles.follower} + {#if $followRoleStore === followRoles.follower}
-

Do you want to follow {name(followUsers[0])}?

+

Do you want to follow {name($followUsersStore[0])}?

- {:else if followRole === followRoles.leader} + {:else if $followRoleStore === followRoles.leader}

Should never be displayed

@@ -105,16 +71,16 @@ vim: ft=typescript
{/if} -{#if followState === followStates.ending} +{#if $followStateStore === followStates.ending}

Interaction

- {#if followRole === followRoles.follower} + {#if $followRoleStore === followRoles.follower}
-

Do you want to stop following {name(followUsers[0])}?

+

Do you want to stop following {name($followUsersStore[0])}?

- {:else if followRole === followRoles.leader} + {:else if $followRoleStore === followRoles.leader}

Do you want to stop leading the way?

@@ -126,20 +92,20 @@ vim: ft=typescript
{/if} -{#if followState === followStates.active || followState === followStates.ending} +{#if $followStateStore === followStates.active || $followStateStore === followStates.ending}
- {#if followRole === followRoles.follower} -

Following {name(followUsers[0])}

- {:else if followUsers.length === 0} + {#if $followRoleStore === followRoles.follower} +

Following {name($followUsersStore[0])}

+ {:else if $followUsersStore.length === 0}

Waiting for followers' confirmation

- {:else if followUsers.length === 1} -

{name(followUsers[0])} is following you

- {:else if followUsers.length === 2} -

{name(followUsers[0])} and {name(followUsers[1])} are following you

+ {:else if $followUsersStore.length === 1} +

{name($followUsersStore[0])} is following you

+ {:else if $followUsersStore.length === 2} +

{name($followUsersStore[0])} and {name($followUsersStore[1])} are following you

{:else}

- {followUsers.slice(0, -1).map(name).join(", ")} and {name(followUsers[followUsers.length - 1])} are following + {$followUsersStore.slice(0, -1).map(name).join(", ")} and {name($followUsersStore[$followUsersStore.length - 1])} are following you

{/if} @@ -147,7 +113,7 @@ vim: ft=typescript
{/if} -{#if followState === followStates.off} +{#if $followStateStore === followStates.off} From 435676773990fdf31cc8c2cbf655937fd49fe11c Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Tue, 23 Nov 2021 16:51:39 +0100 Subject: [PATCH 74/95] Adds the camera to available APIs with retrieving of the worldView --- docs/maps/api-player.md | 2 +- front/src/Api/Events/HasCameraMovedEvent.ts | 18 ++++++++++++ front/src/Api/Events/IframeEvent.ts | 3 ++ front/src/Api/IframeListener.ts | 13 +++++++++ front/src/Api/iframe/Room/EmbeddedWebsite.ts | 6 ++-- front/src/Api/iframe/camera.ts | 29 +++++++++++++++++++ .../src/Phaser/Game/EmbeddedWebsiteManager.ts | 7 +++-- front/src/Phaser/Game/GameScene.ts | 13 +++++++++ front/src/iframe_api.ts | 2 ++ 9 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 front/src/Api/Events/HasCameraMovedEvent.ts create mode 100644 front/src/Api/iframe/camera.ts diff --git a/docs/maps/api-player.md b/docs/maps/api-player.md index 4375dca6..ad08448b 100644 --- a/docs/maps/api-player.md +++ b/docs/maps/api-player.md @@ -97,7 +97,7 @@ You need to wait for the end of the initialization before calling `WA.player.get ```typescript WA.onInit().then(() => { - console.log('Tags: ', WA.player.getPosition()); + console.log('Position: ', WA.player.getPosition()); }) ``` diff --git a/front/src/Api/Events/HasCameraMovedEvent.ts b/front/src/Api/Events/HasCameraMovedEvent.ts new file mode 100644 index 00000000..23f85385 --- /dev/null +++ b/front/src/Api/Events/HasCameraMovedEvent.ts @@ -0,0 +1,18 @@ +import * as tg from "generic-type-guard"; + +export const isHasCameraMovedEvent = new tg.IsInterface() + .withProperties({ + x: tg.isNumber, + y: tg.isNumber, + width: tg.isNumber, + height: tg.isNumber, + }) + .get(); + +/** + * A message sent from the game to the iFrame to notify a movement from the camera. + */ + +export type HasCameraMovedEvent = tg.GuardedType; + +export type HasCameraMovedEventCallback = (event: HasCameraMovedEvent) => void; diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 2744eaa9..5b1c0f02 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -32,6 +32,7 @@ import { isPlayerPropertyEvent } from "./PlayerPropertyEvent"; import type { ChangeZoneEvent } from "./ChangeZoneEvent"; import { isColorEvent } from "./ColorEvent"; import { isPlayerPosition } from "./PlayerPosition"; +import type { HasCameraMovedEvent } from "./HasCameraMovedEvent"; export interface TypedMessageEvent extends MessageEvent { data: T; @@ -52,6 +53,7 @@ export type IframeEventMap = { displayBubble: null; removeBubble: null; onPlayerMove: undefined; + onCameraMove: undefined; showLayer: LayerEvent; hideLayer: LayerEvent; setProperty: SetPropertyEvent; @@ -84,6 +86,7 @@ export interface IframeResponseEventMap { leaveZoneEvent: ChangeZoneEvent; buttonClickedEvent: ButtonClickedEvent; hasPlayerMoved: HasPlayerMovedEvent; + hasCameraMoved: HasCameraMovedEvent; menuItemClicked: MenuItemClickedEvent; setVariable: SetVariableEvent; messageTriggered: MessageReferenceEvent; diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 67b49344..093cacc5 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -31,6 +31,7 @@ import type { SetVariableEvent } from "./Events/SetVariableEvent"; import { ModifyEmbeddedWebsiteEvent, isEmbeddedWebsiteEvent } from "./Events/EmbeddedWebsiteEvent"; import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Stores/MenuStore"; import type { ChangeLayerEvent } from "./Events/ChangeLayerEvent"; +import type { HasCameraMovedEvent } from "./Events/HasCameraMovedEvent"; import type { ChangeZoneEvent } from "./Events/ChangeZoneEvent"; type AnswererCallback = ( @@ -95,6 +96,7 @@ class IframeListener { private readonly iframeCloseCallbacks = new Map void)[]>(); private readonly scripts = new Map(); private sendPlayerMove: boolean = false; + private sendCameraMove: boolean = false; // Note: we are forced to type this in unknown and later cast with "as" because of https://github.com/microsoft/TypeScript/issues/31904 private answerers: { @@ -226,6 +228,8 @@ class IframeListener { this._removeBubbleStream.next(); } else if (payload.type == "onPlayerMove") { this.sendPlayerMove = true; + } else if (payload.type == "onCameraMove") { + this.sendCameraMove = true; } else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) { this._setTilesStream.next(payload.data); } else if (payload.type == "modifyEmbeddedWebsite" && isEmbeddedWebsiteEvent(payload.data)) { @@ -442,6 +446,15 @@ class IframeListener { } } + hasCameraMoved(event: HasCameraMovedEvent) { + if (this.sendCameraMove) { + this.postMessage({ + type: "hasCameraMoved", + data: event, + }); + } + } + sendButtonClickedEvent(popupId: number, buttonId: number): void { this.postMessage({ type: "buttonClickedEvent", diff --git a/front/src/Api/iframe/Room/EmbeddedWebsite.ts b/front/src/Api/iframe/Room/EmbeddedWebsite.ts index 1a2761bd..d9c2d986 100644 --- a/front/src/Api/iframe/Room/EmbeddedWebsite.ts +++ b/front/src/Api/iframe/Room/EmbeddedWebsite.ts @@ -13,7 +13,7 @@ export class EmbeddedWebsite { private _allowApi: boolean; private _position: Rectangle; private readonly origin: "map" | "player" | undefined; - private _scale: number | undefined; + private _scale: number; constructor(private config: CreateEmbeddedWebsiteEvent) { this.name = config.name; @@ -23,7 +23,7 @@ export class EmbeddedWebsite { this._allowApi = config.allowApi ?? false; this._position = config.position; this.origin = config.origin; - this._scale = config.scale; + this._scale = config.scale ?? 1; } public get url() { @@ -116,7 +116,7 @@ export class EmbeddedWebsite { }); } - public get scale() { + public get scale(): number { return this._scale; } diff --git a/front/src/Api/iframe/camera.ts b/front/src/Api/iframe/camera.ts new file mode 100644 index 00000000..e2fb258e --- /dev/null +++ b/front/src/Api/iframe/camera.ts @@ -0,0 +1,29 @@ +import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution"; +import { Subject } from "rxjs"; +import type { HasCameraMovedEvent, HasCameraMovedEventCallback } from "../Events/HasCameraMovedEvent"; +import { apiCallback } from "./registeredCallbacks"; +import { isHasCameraMovedEvent } from "../Events/HasCameraMovedEvent"; + +const moveStream = new Subject(); + +export class WorkAdventureCameraCommands extends IframeApiContribution { + callbacks = [ + apiCallback({ + type: "hasCameraMoved", + typeChecker: isHasCameraMovedEvent, + callback: (payloadData) => { + moveStream.next(payloadData); + }, + }), + ]; + + onCameraMove(callback: HasCameraMovedEventCallback): void { + moveStream.subscribe(callback); + sendToWorkadventure({ + type: "onCameraMove", + data: null, + }); + } +} + +export default new WorkAdventureCameraCommands(); diff --git a/front/src/Phaser/Game/EmbeddedWebsiteManager.ts b/front/src/Phaser/Game/EmbeddedWebsiteManager.ts index 99c4bf5f..36e6b305 100644 --- a/front/src/Phaser/Game/EmbeddedWebsiteManager.ts +++ b/front/src/Phaser/Game/EmbeddedWebsiteManager.ts @@ -30,6 +30,7 @@ export class EmbeddedWebsiteManager { height: rect["height"], }, origin: website.origin, + scale: website.scale, }; }); @@ -144,9 +145,9 @@ export class EmbeddedWebsiteManager { name, url, /*x, - y, - width, - height,*/ + y, + width, + height,*/ allow, allowApi, visible, diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 417fab75..9febe432 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -93,6 +93,8 @@ import { MapStore } from "../../Stores/Utils/MapStore"; import { SetPlayerDetailsMessage } from "../../Messages/generated/messages_pb"; import { followUsersColorStore, followUsersStore } from "../../Stores/FollowStore"; import { getColorRgbFromHue } from "../../WebRtc/ColorGenerator"; +import Camera = Phaser.Cameras.Scene2D.Camera; +import type { HasCameraMovedEvent } from "../../Api/Events/HasCameraMovedEvent"; export interface GameSceneInitInterface { initPosition: PointInterface | null; @@ -783,6 +785,17 @@ export class GameScene extends DirtyScene { this.gameMap.setPosition(event.x, event.y); }); + //listen event to share the actual worldView when the camera is updated + this.cameras.main.on("followupdate", (camera: Camera) => { + const worldView: HasCameraMovedEvent = { + x: camera.worldView.x, + y: camera.worldView.y, + width: camera.worldView.width, + height: camera.worldView.height, + }; + iframeListener.hasCameraMoved(worldView); + }); + // Set up variables manager this.sharedVariablesManager = new SharedVariablesManager( this.connection, diff --git a/front/src/iframe_api.ts b/front/src/iframe_api.ts index f33bc8d7..6b3ec8c3 100644 --- a/front/src/iframe_api.ts +++ b/front/src/iframe_api.ts @@ -20,6 +20,7 @@ import type { ButtonDescriptor } from "./Api/iframe/Ui/ButtonDescriptor"; import type { Popup } from "./Api/iframe/Ui/Popup"; import type { Sound } from "./Api/iframe/Sound/Sound"; import { answerPromises, queryWorkadventure } from "./Api/iframe/IframeApiContribution"; +import camera from "./Api/iframe/camera"; const globalState = createState("global"); @@ -46,6 +47,7 @@ const wa = { sound, room, player, + camera, state: globalState, onInit(): Promise { From 1e073d8a0e5294923fe79d6343fa29a6233b6ab0 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Tue, 23 Nov 2021 17:39:45 +0100 Subject: [PATCH 75/95] Refactoring and documentation update --- docs/maps/api-camera.md | 23 +++++++++++++++++++ docs/maps/api-reference.md | 1 + front/src/Api/Events/IframeEvent.ts | 6 ++--- ...MovedEvent.ts => WasCameraUpdatedEvent.ts} | 6 ++--- front/src/Api/IframeListener.ts | 13 ++++++----- front/src/Api/iframe/camera.ts | 14 +++++------ front/src/Phaser/Game/GameScene.ts | 6 ++--- 7 files changed, 47 insertions(+), 22 deletions(-) create mode 100644 docs/maps/api-camera.md rename front/src/Api/Events/{HasCameraMovedEvent.ts => WasCameraUpdatedEvent.ts} (55%) diff --git a/docs/maps/api-camera.md b/docs/maps/api-camera.md new file mode 100644 index 00000000..b5b85b64 --- /dev/null +++ b/docs/maps/api-camera.md @@ -0,0 +1,23 @@ +{.section-title.accent.text-primary} +# API Camera functions Reference + +### Listen to the camera update + +``` +WA.camera.onCameraUpdate(callback: WasCameraUpdatedEventCallback): void +``` + +Listens to the updating of the camera linked to the player. It will trigger for every update of the camera's properties (position or scale for instance) or of the Game Object it is linked to (undestand: if the player moves). An event will then be sent. + +The event has the following attributes : +* **x (number):** coordinate X of the camera's world view (the area looked at by the camera). +* **y (number):** coordinate Y of the camera's world view. +* **width (number):** the width of the camera's world view. +* **height (number):** the height of the camera's world view. + +**callback:** the function that will be called when the camera is updated. + +Example : +```javascript +WA.camera.onCameraUpdate((worldView) => console.log(worldView)); +``` \ No newline at end of file diff --git a/docs/maps/api-reference.md b/docs/maps/api-reference.md index d044668f..a0869075 100644 --- a/docs/maps/api-reference.md +++ b/docs/maps/api-reference.md @@ -10,5 +10,6 @@ - [UI functions](api-ui.md) - [Sound functions](api-sound.md) - [Controls functions](api-controls.md) +- [Camera functions](api-camera.md) - [List of deprecated functions](api-deprecated.md) diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 5b1c0f02..ee7b0f64 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -32,7 +32,7 @@ import { isPlayerPropertyEvent } from "./PlayerPropertyEvent"; import type { ChangeZoneEvent } from "./ChangeZoneEvent"; import { isColorEvent } from "./ColorEvent"; import { isPlayerPosition } from "./PlayerPosition"; -import type { HasCameraMovedEvent } from "./HasCameraMovedEvent"; +import type { WasCameraUpdatedEvent } from "./WasCameraUpdatedEvent"; export interface TypedMessageEvent extends MessageEvent { data: T; @@ -53,7 +53,7 @@ export type IframeEventMap = { displayBubble: null; removeBubble: null; onPlayerMove: undefined; - onCameraMove: undefined; + onCameraUpdate: undefined; showLayer: LayerEvent; hideLayer: LayerEvent; setProperty: SetPropertyEvent; @@ -86,7 +86,7 @@ export interface IframeResponseEventMap { leaveZoneEvent: ChangeZoneEvent; buttonClickedEvent: ButtonClickedEvent; hasPlayerMoved: HasPlayerMovedEvent; - hasCameraMoved: HasCameraMovedEvent; + wasCameraUpdated: WasCameraUpdatedEvent; menuItemClicked: MenuItemClickedEvent; setVariable: SetVariableEvent; messageTriggered: MessageReferenceEvent; diff --git a/front/src/Api/Events/HasCameraMovedEvent.ts b/front/src/Api/Events/WasCameraUpdatedEvent.ts similarity index 55% rename from front/src/Api/Events/HasCameraMovedEvent.ts rename to front/src/Api/Events/WasCameraUpdatedEvent.ts index 23f85385..8f37753c 100644 --- a/front/src/Api/Events/HasCameraMovedEvent.ts +++ b/front/src/Api/Events/WasCameraUpdatedEvent.ts @@ -1,6 +1,6 @@ import * as tg from "generic-type-guard"; -export const isHasCameraMovedEvent = new tg.IsInterface() +export const isWasCameraUpdatedEvent = new tg.IsInterface() .withProperties({ x: tg.isNumber, y: tg.isNumber, @@ -13,6 +13,6 @@ export const isHasCameraMovedEvent = new tg.IsInterface() * A message sent from the game to the iFrame to notify a movement from the camera. */ -export type HasCameraMovedEvent = tg.GuardedType; +export type WasCameraUpdatedEvent = tg.GuardedType; -export type HasCameraMovedEventCallback = (event: HasCameraMovedEvent) => void; +export type WasCameraUpdatedEventCallback = (event: WasCameraUpdatedEvent) => void; diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 093cacc5..b790c0ca 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -31,6 +31,7 @@ import type { SetVariableEvent } from "./Events/SetVariableEvent"; import { ModifyEmbeddedWebsiteEvent, isEmbeddedWebsiteEvent } from "./Events/EmbeddedWebsiteEvent"; import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Stores/MenuStore"; import type { ChangeLayerEvent } from "./Events/ChangeLayerEvent"; +import type { WasCameraUpdatedEvent } from "./Events/WasCameraUpdatedEvent"; import type { HasCameraMovedEvent } from "./Events/HasCameraMovedEvent"; import type { ChangeZoneEvent } from "./Events/ChangeZoneEvent"; @@ -96,7 +97,7 @@ class IframeListener { private readonly iframeCloseCallbacks = new Map void)[]>(); private readonly scripts = new Map(); private sendPlayerMove: boolean = false; - private sendCameraMove: boolean = false; + private sendCameraUpdate: boolean = false; // Note: we are forced to type this in unknown and later cast with "as" because of https://github.com/microsoft/TypeScript/issues/31904 private answerers: { @@ -228,8 +229,8 @@ class IframeListener { this._removeBubbleStream.next(); } else if (payload.type == "onPlayerMove") { this.sendPlayerMove = true; - } else if (payload.type == "onCameraMove") { - this.sendCameraMove = true; + } else if (payload.type == "onCameraUpdate") { + this.sendCameraUpdate = true; } else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) { this._setTilesStream.next(payload.data); } else if (payload.type == "modifyEmbeddedWebsite" && isEmbeddedWebsiteEvent(payload.data)) { @@ -446,10 +447,10 @@ class IframeListener { } } - hasCameraMoved(event: HasCameraMovedEvent) { - if (this.sendCameraMove) { + sendCameraUpdated(event: WasCameraUpdatedEvent) { + if (this.sendCameraUpdate) { this.postMessage({ - type: "hasCameraMoved", + type: "wasCameraUpdated", data: event, }); } diff --git a/front/src/Api/iframe/camera.ts b/front/src/Api/iframe/camera.ts index e2fb258e..4f62b94c 100644 --- a/front/src/Api/iframe/camera.ts +++ b/front/src/Api/iframe/camera.ts @@ -1,26 +1,26 @@ import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution"; import { Subject } from "rxjs"; -import type { HasCameraMovedEvent, HasCameraMovedEventCallback } from "../Events/HasCameraMovedEvent"; +import type { WasCameraUpdatedEvent, WasCameraUpdatedEventCallback } from "../Events/WasCameraUpdatedEvent"; import { apiCallback } from "./registeredCallbacks"; -import { isHasCameraMovedEvent } from "../Events/HasCameraMovedEvent"; +import { isWasCameraUpdatedEvent } from "../Events/WasCameraUpdatedEvent"; -const moveStream = new Subject(); +const moveStream = new Subject(); export class WorkAdventureCameraCommands extends IframeApiContribution { callbacks = [ apiCallback({ - type: "hasCameraMoved", - typeChecker: isHasCameraMovedEvent, + type: "wasCameraUpdated", + typeChecker: isWasCameraUpdatedEvent, callback: (payloadData) => { moveStream.next(payloadData); }, }), ]; - onCameraMove(callback: HasCameraMovedEventCallback): void { + onCameraUpdate(callback: WasCameraUpdatedEventCallback): void { moveStream.subscribe(callback); sendToWorkadventure({ - type: "onCameraMove", + type: "onCameraUpdate", data: null, }); } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 9febe432..7ae2707f 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -94,7 +94,7 @@ import { SetPlayerDetailsMessage } from "../../Messages/generated/messages_pb"; import { followUsersColorStore, followUsersStore } from "../../Stores/FollowStore"; import { getColorRgbFromHue } from "../../WebRtc/ColorGenerator"; import Camera = Phaser.Cameras.Scene2D.Camera; -import type { HasCameraMovedEvent } from "../../Api/Events/HasCameraMovedEvent"; +import type { WasCameraUpdatedEvent } from "../../Api/Events/WasCameraUpdatedEvent"; export interface GameSceneInitInterface { initPosition: PointInterface | null; @@ -787,13 +787,13 @@ export class GameScene extends DirtyScene { //listen event to share the actual worldView when the camera is updated this.cameras.main.on("followupdate", (camera: Camera) => { - const worldView: HasCameraMovedEvent = { + const worldView: WasCameraUpdatedEvent = { x: camera.worldView.x, y: camera.worldView.y, width: camera.worldView.width, height: camera.worldView.height, }; - iframeListener.hasCameraMoved(worldView); + iframeListener.sendCameraUpdated(worldView); }); // Set up variables manager From 2a7c8f3786d54838caff63afd70dda53141650a6 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Fri, 26 Nov 2021 14:46:02 +0100 Subject: [PATCH 76/95] Reverts adding scale to the camera updated event and uses it directly from the website manager --- .../src/Phaser/Game/EmbeddedWebsiteManager.ts | 68 +++++++++---------- front/src/Phaser/Game/GameScene.ts | 4 +- 2 files changed, 35 insertions(+), 37 deletions(-) diff --git a/front/src/Phaser/Game/EmbeddedWebsiteManager.ts b/front/src/Phaser/Game/EmbeddedWebsiteManager.ts index 36e6b305..d58d7eaf 100644 --- a/front/src/Phaser/Game/EmbeddedWebsiteManager.ts +++ b/front/src/Phaser/Game/EmbeddedWebsiteManager.ts @@ -26,8 +26,8 @@ export class EmbeddedWebsiteManager { position: { x: website.phaserObject.x, y: website.phaserObject.y, - width: rect["width"], - height: rect["height"], + width: website.phaserObject.width, + height: website.phaserObject.height, }, origin: website.origin, scale: website.scale, @@ -111,14 +111,18 @@ export class EmbeddedWebsiteManager { website.phaserObject.y = embeddedWebsiteEvent.y; } if (embeddedWebsiteEvent?.width !== undefined) { - website.iframe.style.width = embeddedWebsiteEvent.width + "px"; + website.position.width = embeddedWebsiteEvent.width; + website.iframe.style.width = embeddedWebsiteEvent.width / website.phaserObject.scale + "px"; } if (embeddedWebsiteEvent?.height !== undefined) { - website.iframe.style.height = embeddedWebsiteEvent.height + "px"; + website.position.height = embeddedWebsiteEvent.height; + website.iframe.style.height = embeddedWebsiteEvent.height / website.phaserObject.scale + "px"; } if (embeddedWebsiteEvent?.scale !== undefined) { website.phaserObject.scale = embeddedWebsiteEvent.scale; + website.iframe.style.width = website.position.width / embeddedWebsiteEvent.scale + "px"; + website.iframe.style.height = website.position.height / embeddedWebsiteEvent.scale + "px"; } } ); @@ -145,9 +149,9 @@ export class EmbeddedWebsiteManager { name, url, /*x, - y, - width, - height,*/ +y, +width, +height,*/ allow, allowApi, visible, @@ -173,49 +177,43 @@ export class EmbeddedWebsiteManager { const absoluteUrl = new URL(embeddedWebsiteEvent.url, this.gameScene.MapUrlFile).toString(); const iframe = document.createElement("iframe"); + const scale = embeddedWebsiteEvent.scale ?? 1; + iframe.src = absoluteUrl; iframe.tabIndex = -1; - iframe.style.width = embeddedWebsiteEvent.position.width + "px"; - iframe.style.height = embeddedWebsiteEvent.position.height + "px"; + iframe.style.width = embeddedWebsiteEvent.position.width / scale + "px"; + iframe.style.height = embeddedWebsiteEvent.position.height / scale + "px"; iframe.style.margin = "0"; iframe.style.padding = "0"; iframe.style.border = "none"; - let embeddedWebsite; - let domElement; + const domElement = new DOMElement( + this.gameScene, + embeddedWebsiteEvent.position.x, + embeddedWebsiteEvent.position.y, + iframe + ); + domElement.setOrigin(0, 0); + if (embeddedWebsiteEvent.scale) { + domElement.scale = embeddedWebsiteEvent.scale; + } + domElement.setVisible(visible); switch (embeddedWebsiteEvent.origin) { case "player": - domElement = new DOMElement( - this.gameScene, - embeddedWebsiteEvent.position.x, - embeddedWebsiteEvent.position.y, - iframe - ); - if (embeddedWebsiteEvent.scale) { - domElement.scale = embeddedWebsiteEvent.scale; - } this.gameScene.CurrentPlayer.add(domElement); - - embeddedWebsite = { - ...embeddedWebsiteEvent, - phaserObject: domElement, - iframe: iframe, - }; - break; case "map": default: - embeddedWebsite = { - ...embeddedWebsiteEvent, - phaserObject: this.gameScene.add - .dom(embeddedWebsiteEvent.position.x, embeddedWebsiteEvent.position.y, iframe) - .setVisible(visible) - .setOrigin(0, 0), - iframe: iframe, - }; + this.gameScene.add.existing(domElement); } + const embeddedWebsite = { + ...embeddedWebsiteEvent, + phaserObject: domElement, + iframe: iframe, + }; + if (embeddedWebsiteEvent.allowApi) { iframeListener.registerIframe(iframe); } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 7ae2707f..55599510 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -787,13 +787,13 @@ export class GameScene extends DirtyScene { //listen event to share the actual worldView when the camera is updated this.cameras.main.on("followupdate", (camera: Camera) => { - const worldView: WasCameraUpdatedEvent = { + const cameraEvent: WasCameraUpdatedEvent = { x: camera.worldView.x, y: camera.worldView.y, width: camera.worldView.width, height: camera.worldView.height, }; - iframeListener.sendCameraUpdated(worldView); + iframeListener.sendCameraUpdated(cameraEvent); }); // Set up variables manager From 3abc571e791fb24787336f8f5c8e86ecc81a67e9 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Mon, 6 Dec 2021 18:44:37 +0100 Subject: [PATCH 77/95] Corrects scale managing and camera event listening --- front/src/Api/Events/WasCameraUpdatedEvent.ts | 1 + front/src/Api/IframeListener.ts | 16 +++--- .../src/Phaser/Game/EmbeddedWebsiteManager.ts | 7 +-- front/src/Phaser/Game/GameScene.ts | 51 ++++++++++++++----- 4 files changed, 50 insertions(+), 25 deletions(-) diff --git a/front/src/Api/Events/WasCameraUpdatedEvent.ts b/front/src/Api/Events/WasCameraUpdatedEvent.ts index 8f37753c..34e39a84 100644 --- a/front/src/Api/Events/WasCameraUpdatedEvent.ts +++ b/front/src/Api/Events/WasCameraUpdatedEvent.ts @@ -6,6 +6,7 @@ export const isWasCameraUpdatedEvent = new tg.IsInterface() y: tg.isNumber, width: tg.isNumber, height: tg.isNumber, + zoom: tg.isNumber, }) .get(); diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index b790c0ca..3c79bbe2 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -87,6 +87,9 @@ class IframeListener { private readonly _loadSoundStream: Subject = new Subject(); public readonly loadSoundStream = this._loadSoundStream.asObservable(); + private readonly _trackCameraUpdateStream: Subject = new Subject(); + public readonly trackCameraUpdateStream = this._trackCameraUpdateStream.asObservable(); + private readonly _setTilesStream: Subject = new Subject(); public readonly setTilesStream = this._setTilesStream.asObservable(); @@ -97,7 +100,6 @@ class IframeListener { private readonly iframeCloseCallbacks = new Map void)[]>(); private readonly scripts = new Map(); private sendPlayerMove: boolean = false; - private sendCameraUpdate: boolean = false; // Note: we are forced to type this in unknown and later cast with "as" because of https://github.com/microsoft/TypeScript/issues/31904 private answerers: { @@ -230,7 +232,7 @@ class IframeListener { } else if (payload.type == "onPlayerMove") { this.sendPlayerMove = true; } else if (payload.type == "onCameraUpdate") { - this.sendCameraUpdate = true; + this._trackCameraUpdateStream.next(); } else if (payload.type == "setTiles" && isSetTilesEvent(payload.data)) { this._setTilesStream.next(payload.data); } else if (payload.type == "modifyEmbeddedWebsite" && isEmbeddedWebsiteEvent(payload.data)) { @@ -448,12 +450,10 @@ class IframeListener { } sendCameraUpdated(event: WasCameraUpdatedEvent) { - if (this.sendCameraUpdate) { - this.postMessage({ - type: "wasCameraUpdated", - data: event, - }); - } + this.postMessage({ + type: "wasCameraUpdated", + data: event, + }); } sendButtonClickedEvent(popupId: number, buttonId: number): void { diff --git a/front/src/Phaser/Game/EmbeddedWebsiteManager.ts b/front/src/Phaser/Game/EmbeddedWebsiteManager.ts index d58d7eaf..387940c7 100644 --- a/front/src/Phaser/Game/EmbeddedWebsiteManager.ts +++ b/front/src/Phaser/Game/EmbeddedWebsiteManager.ts @@ -16,7 +16,8 @@ export class EmbeddedWebsiteManager { if (website === undefined) { throw new Error('Cannot find embedded website with name "' + name + '"'); } - const rect = website.iframe.getBoundingClientRect(); + + const scale = website.scale ?? 1; return { url: website.url, name: website.name, @@ -26,8 +27,8 @@ export class EmbeddedWebsiteManager { position: { x: website.phaserObject.x, y: website.phaserObject.y, - width: website.phaserObject.width, - height: website.phaserObject.height, + width: website.phaserObject.width * scale, + height: website.phaserObject.height * scale, }, origin: website.origin, scale: website.scale, diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 55599510..ec12aae8 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -212,6 +212,8 @@ export class GameScene extends DirtyScene { private objectsByType = new Map(); private embeddedWebsiteManager!: EmbeddedWebsiteManager; private loader: Loader; + private lastCameraEvent: WasCameraUpdatedEvent | undefined; + private firstCameraUpdateSent: boolean = false; constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) { super({ @@ -785,17 +787,6 @@ export class GameScene extends DirtyScene { this.gameMap.setPosition(event.x, event.y); }); - //listen event to share the actual worldView when the camera is updated - this.cameras.main.on("followupdate", (camera: Camera) => { - const cameraEvent: WasCameraUpdatedEvent = { - x: camera.worldView.x, - y: camera.worldView.y, - width: camera.worldView.width, - height: camera.worldView.height, - }; - iframeListener.sendCameraUpdated(cameraEvent); - }); - // Set up variables manager this.sharedVariablesManager = new SharedVariablesManager( this.connection, @@ -1115,9 +1106,33 @@ ${escapedMessage} ); this.iframeSubscriptionList.push( - iframeListener.stopSoundStream.subscribe((stopSoundEvent) => { - const url = new URL(stopSoundEvent.url, this.MapUrlFile); - soundManager.stopSound(this.sound, url.toString()); + iframeListener.trackCameraUpdateStream.subscribe(() => { + if (!this.firstCameraUpdateSent) { + this.cameras.main.on("followupdate", (camera: Camera) => { + const cameraEvent: WasCameraUpdatedEvent = { + x: camera.worldView.x, + y: camera.worldView.y, + width: camera.worldView.width, + height: camera.worldView.height, + zoom: camera.scaleManager.zoom, + }; + if ( + this.lastCameraEvent?.x == cameraEvent.x && + this.lastCameraEvent?.y == cameraEvent.y && + this.lastCameraEvent?.width == cameraEvent.width && + this.lastCameraEvent?.height == cameraEvent.height && + this.lastCameraEvent?.zoom == cameraEvent.zoom + ) { + return; + } + + this.lastCameraEvent = cameraEvent; + iframeListener.sendCameraUpdated(cameraEvent); + this.firstCameraUpdateSent = true; + }); + + iframeListener.sendCameraUpdated(this.cameras.main); + } }) ); @@ -1180,6 +1195,12 @@ ${escapedMessage} }) ); + this.iframeSubscriptionList.push( + iframeListener.setPropertyStream.subscribe((setProperty) => { + this.setPropertyLayer(setProperty.layerName, setProperty.propertyName, setProperty.propertyValue); + }) + ); + iframeListener.registerAnswerer("openCoWebsite", async (openCoWebsite, source) => { if (!source) { throw new Error("Unknown query source"); @@ -1986,6 +2007,7 @@ ${escapedMessage} this.loader.resize(); } + private getObjectLayerData(objectName: string): ITiledMapObject | undefined { for (const layer of this.mapFile.layers) { if (layer.type === "objectgroup" && layer.name === "floorLayer") { @@ -1998,6 +2020,7 @@ ${escapedMessage} } return undefined; } + private reposition(): void { // Recompute camera offset if needed biggestAvailableAreaStore.recompute(); From ddb4ae88239f456970b7682aacf624935f84402a Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Tue, 7 Dec 2021 18:47:40 +0100 Subject: [PATCH 78/95] Documentation --- docs/maps/api-camera.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/maps/api-camera.md b/docs/maps/api-camera.md index b5b85b64..7ae9ce2b 100644 --- a/docs/maps/api-camera.md +++ b/docs/maps/api-camera.md @@ -7,7 +7,7 @@ WA.camera.onCameraUpdate(callback: WasCameraUpdatedEventCallback): void ``` -Listens to the updating of the camera linked to the player. It will trigger for every update of the camera's properties (position or scale for instance) or of the Game Object it is linked to (undestand: if the player moves). An event will then be sent. +Listens to the updating of the camera linked to the player. It will trigger for every update of the camera's properties (position or scale for instance). An event will then be sent. The event has the following attributes : * **x (number):** coordinate X of the camera's world view (the area looked at by the camera). From 9cf64c3c765a9c97cad55c8049172cc164755569 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Wed, 8 Dec 2021 11:48:16 +0100 Subject: [PATCH 79/95] Documentation on type Position --- docs/maps/api-player.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/maps/api-player.md b/docs/maps/api-player.md index ad08448b..2bfad463 100644 --- a/docs/maps/api-player.md +++ b/docs/maps/api-player.md @@ -92,6 +92,11 @@ WA.player.getPosition(): Promise ``` The player's current position is available using the `WA.player.getPosition()` function. +`Position` has the following attributes : +* **x (number) :** The coordinate x of the current player's position. +* **y (number) :** The coordinate y of the current player's position. + + {.alert.alert-info} You need to wait for the end of the initialization before calling `WA.player.getPosition()` From 864ff49af58e0540ff37a86be4a6d5a0cfea8454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dicte=20Q?= <37311765+HimeShaman@users.noreply.github.com> Date: Wed, 8 Dec 2021 10:05:46 +0100 Subject: [PATCH 80/95] Update docs/maps/api-camera.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Négrier --- docs/maps/api-camera.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/maps/api-camera.md b/docs/maps/api-camera.md index 7ae9ce2b..405de418 100644 --- a/docs/maps/api-camera.md +++ b/docs/maps/api-camera.md @@ -1,7 +1,7 @@ {.section-title.accent.text-primary} # API Camera functions Reference -### Listen to the camera update +### Listen to camera updates ``` WA.camera.onCameraUpdate(callback: WasCameraUpdatedEventCallback): void From 73efdab52f5307a5a45fcd0db5e216d993a09897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dicte=20Q?= <37311765+HimeShaman@users.noreply.github.com> Date: Wed, 8 Dec 2021 10:06:07 +0100 Subject: [PATCH 81/95] Update docs/maps/api-camera.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Négrier --- docs/maps/api-camera.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/maps/api-camera.md b/docs/maps/api-camera.md index 405de418..e0b5d7f7 100644 --- a/docs/maps/api-camera.md +++ b/docs/maps/api-camera.md @@ -7,7 +7,7 @@ WA.camera.onCameraUpdate(callback: WasCameraUpdatedEventCallback): void ``` -Listens to the updating of the camera linked to the player. It will trigger for every update of the camera's properties (position or scale for instance). An event will then be sent. +Listens to updates of the camera viewport. It will trigger for every update of the camera's properties (position or scale for instance). An event will be sent. The event has the following attributes : * **x (number):** coordinate X of the camera's world view (the area looked at by the camera). From adc71b5695977452b666435c616b5748b9e11749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dicte=20Q?= <37311765+HimeShaman@users.noreply.github.com> Date: Wed, 8 Dec 2021 10:06:34 +0100 Subject: [PATCH 82/95] Update front/src/Api/Events/IframeEvent.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Négrier --- front/src/Api/Events/IframeEvent.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index ee7b0f64..4e87d4d4 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -218,7 +218,6 @@ export const isIframeQuery = (event: any): event is IframeQuery Date: Wed, 8 Dec 2021 10:14:31 +0100 Subject: [PATCH 83/95] Update front/src/Api/Events/IframeEvent.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Négrier --- front/src/Api/Events/IframeEvent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index 4e87d4d4..dac8509d 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -65,7 +65,7 @@ export type IframeEventMap = { registerMenu: MenuRegisterEvent; unregisterMenu: UnregisterMenuEvent; setTiles: SetTilesEvent; - modifyEmbeddedWebsite: Partial; // Note: name should be compulsory in fact; + modifyEmbeddedWebsite: Partial; // Note: name should be compulsory in fact }; export interface IframeEvent { type: T; From 99f9d56c5ce27be73c7f4d1e840f6d3b1b454a97 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Tue, 28 Dec 2021 15:05:58 +0100 Subject: [PATCH 84/95] Updates room documentation for embeddedWebsite properties --- docs/maps/api-room.md | 140 ++++++++++++++++------------ front/src/Api/Events/IframeEvent.ts | 9 -- front/src/Api/IframeListener.ts | 1 - front/src/Api/iframe/player.ts | 14 --- 4 files changed, 78 insertions(+), 86 deletions(-) diff --git a/docs/maps/api-room.md b/docs/maps/api-room.md index 72947df8..bcc53332 100644 --- a/docs/maps/api-room.md +++ b/docs/maps/api-room.md @@ -1,8 +1,11 @@ {.section-title.accent.text-primary} + # API Room functions Reference ### Working with group layers -If you use group layers in your map, to reference a layer in a group you will need to use a `/` to join layer names together. + +If you use group layers in your map, to reference a layer in a group you will need to use a `/` to join layer names +together. Example :
@@ -12,6 +15,7 @@ Example :
The name of the layers of this map are : + * `entries/start` * `bottom/ground/under` * `bottom/build/carpet` @@ -26,29 +30,32 @@ WA.room.onLeaveLayer(name: string): Subscription Listens to the position of the current user. The event is triggered when the user enters or leaves a given layer. -* **name**: the name of the layer who as defined in Tiled. +* **name**: the name of the layer who as defined in Tiled. Example: ```javascript WA.room.onEnterLayer('myLayer').subscribe(() => { - WA.chat.sendChatMessage("Hello!", 'Mr Robot'); + WA.chat.sendChatMessage("Hello!", 'Mr Robot'); }); WA.room.onLeaveLayer('myLayer').subscribe(() => { - WA.chat.sendChatMessage("Goodbye!", 'Mr Robot'); + WA.chat.sendChatMessage("Goodbye!", 'Mr Robot'); }); ``` ### Show / Hide a layer + ``` WA.room.showLayer(layerName : string): void WA.room.hideLayer(layerName : string) : void ``` -These 2 methods can be used to show and hide a layer. -if `layerName` is the name of a group layer, show/hide all the layer in that group layer. + +These 2 methods can be used to show and hide a layer. if `layerName` is the name of a group layer, show/hide all the +layer in that group layer. Example : + ```javascript WA.room.showLayer('bottom'); //... @@ -61,12 +68,14 @@ WA.room.hideLayer('bottom'); WA.room.setProperty(layerName : string, propertyName : string, propertyValue : string | number | boolean | undefined) : void; ``` -Set the value of the `propertyName` property of the layer `layerName` at `propertyValue`. If the property doesn't exist, create the property `propertyName` and set the value of the property at `propertyValue`. +Set the value of the `propertyName` property of the layer `layerName` at `propertyValue`. If the property doesn't exist, +create the property `propertyName` and set the value of the property at `propertyValue`. Note : To unset a property from a layer, use `setProperty` with `propertyValue` set to `undefined`. Example : + ```javascript WA.room.setProperty('wikiLayer', 'openWebsite', 'https://www.wikipedia.org/'); ``` @@ -79,13 +88,12 @@ WA.room.id: string; The ID of the current room is available from the `WA.room.id` property. -{.alert.alert-info} -You need to wait for the end of the initialization before accessing `WA.room.id` +{.alert.alert-info} You need to wait for the end of the initialization before accessing `WA.room.id` ```typescript WA.onInit().then(() => { - console.log('Room id: ', WA.room.id); - // Will output something like: 'https://play.workadventu.re/@/myorg/myworld/myroom', or 'https://play.workadventu.re/_/global/mymap.org/map.json" + console.log('Room id: ', WA.room.id); + // Will output something like: 'https://play.workadventu.re/@/myorg/myworld/myroom', or 'https://play.workadventu.re/_/global/mymap.org/map.json" }) ``` @@ -97,19 +105,17 @@ WA.room.mapURL: string; The URL of the map is available from the `WA.room.mapURL` property. -{.alert.alert-info} -You need to wait for the end of the initialization before accessing `WA.room.mapURL` +{.alert.alert-info} You need to wait for the end of the initialization before accessing `WA.room.mapURL` ```typescript WA.onInit().then(() => { - console.log('Map URL: ', WA.room.mapURL); - // Will output something like: 'https://mymap.org/map.json" + console.log('Map URL: ', WA.room.mapURL); + // Will output something like: 'https://mymap.org/map.json" }) ``` - - ### Getting map data + ``` WA.room.getTiledMap(): Promise ``` @@ -121,12 +127,16 @@ const map = await WA.room.getTiledMap(); console.log("Map generated with Tiled version ", map.tiledversion); ``` -Check the [Tiled documentation to learn more about the format of the JSON map](https://doc.mapeditor.org/en/stable/reference/json-map-format/). +Check +the [Tiled documentation to learn more about the format of the JSON map](https://doc.mapeditor.org/en/stable/reference/json-map-format/) +. ### Changing tiles + ``` WA.room.setTiles(tiles: TileDescriptor[]): void ``` + Replace the tile at the `x` and `y` coordinates in the layer named `layer` by the tile with the id `tile`. If `tile` is a string, it's not the id of the tile but the value of the property `name`. @@ -137,43 +147,48 @@ If `tile` is a string, it's not the id of the tile but the value of the property `TileDescriptor` has the following attributes : + * **x (number) :** The coordinate x of the tile that you want to replace. * **y (number) :** The coordinate y of the tile that you want to replace. * **tile (number | string) :** The id of the tile that will be placed in the map. * **layer (string) :** The name of the layer where the tile will be placed. -**Important !** : If you use `tile` as a number, be sure to add the `firstgid` of the tileset of the tile that you want to the id of the tile in Tiled Editor. +**Important !** : If you use `tile` as a number, be sure to add the `firstgid` of the tileset of the tile that you want +to the id of the tile in Tiled Editor. Note: If you want to unset a tile, use `setTiles` with `tile` set to `null`. Example : + ```javascript WA.room.setTiles([ - {x: 6, y: 4, tile: 'blue', layer: 'setTiles'}, - {x: 7, y: 4, tile: 109, layer: 'setTiles'}, - {x: 8, y: 4, tile: 109, layer: 'setTiles'}, - {x: 9, y: 4, tile: 'blue', layer: 'setTiles'} - ]); + { x: 6, y: 4, tile: 'blue', layer: 'setTiles' }, + { x: 7, y: 4, tile: 109, layer: 'setTiles' }, + { x: 8, y: 4, tile: 109, layer: 'setTiles' }, + { x: 9, y: 4, tile: 'blue', layer: 'setTiles' } +]); ``` ### Loading a tileset + ``` WA.room.loadTileset(url: string): Promise ``` + Load a tileset in JSON format from an url and return the id of the first tile of the loaded tileset. You can create a tileset file in Tile Editor. ```javascript WA.room.loadTileset("Assets/Tileset.json").then((firstId) => { - WA.room.setTiles([{x: 4, y: 4, tile: firstId, layer: 'bottom'}]); + WA.room.setTiles([{ x: 4, y: 4, tile: firstId, layer: 'bottom' }]); }) ``` - ## Embedding websites in a map -You can use the scripting API to embed websites in a map, or to edit websites that are already embedded (using the ["website" objects](website-in-map.md)). +You can use the scripting API to embed websites in a map, or to edit websites that are already embedded (using +the ["website" objects](website-in-map.md)). ### Getting an instance of a website already embedded in the map @@ -181,8 +196,8 @@ You can use the scripting API to embed websites in a map, or to edit websites th WA.room.website.get(objectName: string): Promise ``` -You can get an instance of an embedded website by using the `WA.room.website.get()` method. -It returns a promise of an `EmbeddedWebsite` instance. +You can get an instance of an embedded website by using the `WA.room.website.get()` method. It returns a promise of +an `EmbeddedWebsite` instance. ```javascript // Get an existing website object where 'my_website' is the name of the object (on any layer object of the map) @@ -191,7 +206,6 @@ website.url = 'https://example.com'; website.visible = true; ``` - ### Adding a new website in a map ``` @@ -201,34 +215,38 @@ interface CreateEmbeddedWebsiteEvent { name: string; // A unique name for this iframe url: string; // The URL the iframe points to. position: { - x: number, // In pixels, relative to the map coordinates - y: number, // In pixels, relative to the map coordinates - width: number, // In pixels, sensitive to zoom level - height: number, // In pixels, sensitive to zoom level + x: number, // relative to the map or player coordinates, depending on origin + y: number, // relative to the map or player coordinates, depending on origin + width: number, // In pixels, sensitive to zoom level and scale + height: number, // In pixels, sensitive to zoom level and scale }, visible?: boolean, // Whether to display the iframe or not allowApi?: boolean, // Whether the scripting API should be available to the iframe allow?: string, // The list of feature policies allowed + origin: "player" | "map" // The origin used to place the x and y coordinates of the iframe's top-left corner + scale: number, // A ratio used to resize the iframe (will affect the iframe's width and height when it is rendered } ``` -You can create an instance of an embedded website by using the `WA.room.website.create()` method. -It returns an `EmbeddedWebsite` instance. +You can create an instance of an embedded website by using the `WA.room.website.create()` method. It returns +an `EmbeddedWebsite` instance. ```javascript // Create a new website object const website = WA.room.website.create({ - name: "my_website", - url: "https://example.com", - position: { - x: 64, - y: 128, - width: 320, - height: 240, - }, - visible: true, - allowApi: true, - allow: "fullscreen", + name: "my_website", + url: "https://example.com", + position: { + x: 64, + y: 128, + width: 320, + height: 240, + }, + visible: true, + allowApi: true, + allow: "fullscreen", + origin: "map", + scale: 1, }); ``` @@ -240,30 +258,28 @@ WA.room.website.delete(name: string): Promise Use `WA.room.website.delete` to completely remove an embedded website from your map. - ### The EmbeddedWebsite class Instances of the `EmbeddedWebsite` class represent the website displayed on the map. ```typescript class EmbeddedWebsite { - readonly name: string; - url: string; - visible: boolean; - allow: string; - allowApi: boolean; - x: number; // In pixels, relative to the map coordinates - y: number; // In pixels, relative to the map coordinates - width: number; // In pixels, sensitive to zoom level - height: number; // In pixels, sensitive to zoom level + readonly name: string; + url: string; + visible: boolean; + allow: string; + allowApi: boolean; + x: number; // In pixels, relative to the map or player coordinates, depending on origin + y: number; // In pixels, relative to the map or player coordinates, depending on origin + width: number; // In pixels, sensitive to zoom level and scale + height: number; // In pixels, sensitive to zoom level and scale + origin: "player" | "map"; + scale: number; } ``` When you modify a property of an `EmbeddedWebsite` instance, the iframe is automatically modified in the map. - -{.alert.alert-warning} -The websites you add/edit/delete via the scripting API are only shown locally. If you want them -to be displayed for every player, you can use [variables](api-start.md) to share a common state -between all users. +{.alert.alert-warning} The websites you add/edit/delete via the scripting API are only shown locally. If you want them +to be displayed for every player, you can use [variables](api-start.md) to share a common state between all users. diff --git a/front/src/Api/Events/IframeEvent.ts b/front/src/Api/Events/IframeEvent.ts index dac8509d..8fb488dc 100644 --- a/front/src/Api/Events/IframeEvent.ts +++ b/front/src/Api/Events/IframeEvent.ts @@ -28,7 +28,6 @@ import type { MessageReferenceEvent } from "./ui/TriggerActionMessageEvent"; import { isMessageReferenceEvent, isTriggerActionMessageEvent } from "./ui/TriggerActionMessageEvent"; import type { MenuRegisterEvent, UnregisterMenuEvent } from "./ui/MenuRegisterEvent"; import type { ChangeLayerEvent } from "./ChangeLayerEvent"; -import { isPlayerPropertyEvent } from "./PlayerPropertyEvent"; import type { ChangeZoneEvent } from "./ChangeZoneEvent"; import { isColorEvent } from "./ColorEvent"; import { isPlayerPosition } from "./PlayerPosition"; @@ -158,14 +157,6 @@ export const iframeQueryMapTypeGuards = { query: isCreateEmbeddedWebsiteEvent, answer: tg.isUndefined, }, - getPlayerProperty: { - query: tg.isString, - answer: isPlayerPropertyEvent, - }, - setPlayerProperty: { - query: isPlayerPropertyEvent, - answer: tg.isUndefined, - }, setPlayerOutline: { query: isColorEvent, answer: tg.isUndefined, diff --git a/front/src/Api/IframeListener.ts b/front/src/Api/IframeListener.ts index 3c79bbe2..216a9510 100644 --- a/front/src/Api/IframeListener.ts +++ b/front/src/Api/IframeListener.ts @@ -32,7 +32,6 @@ import { ModifyEmbeddedWebsiteEvent, isEmbeddedWebsiteEvent } from "./Events/Emb import { handleMenuRegistrationEvent, handleMenuUnregisterEvent } from "../Stores/MenuStore"; import type { ChangeLayerEvent } from "./Events/ChangeLayerEvent"; import type { WasCameraUpdatedEvent } from "./Events/WasCameraUpdatedEvent"; -import type { HasCameraMovedEvent } from "./Events/HasCameraMovedEvent"; import type { ChangeZoneEvent } from "./Events/ChangeZoneEvent"; type AnswererCallback = ( diff --git a/front/src/Api/iframe/player.ts b/front/src/Api/iframe/player.ts index 2a642d75..0c71ae33 100644 --- a/front/src/Api/iframe/player.ts +++ b/front/src/Api/iframe/player.ts @@ -110,20 +110,6 @@ export class WorkadventurePlayerCommands extends IframeApiContribution { - return queryWorkadventure({ - type: "getPlayerProperty", - data: name, - }); - } - - setPlayerProperty(property: PlayerPropertyEvent) { - queryWorkadventure({ - type: "setPlayerProperty", - data: property, - }).catch((e) => console.error(e)); - } } export type Position = { From d9482d484b575c656808de696beb3f249094beee Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Wed, 29 Dec 2021 17:46:46 +0100 Subject: [PATCH 85/95] WIP enable/disable tutorial according to the map 'tutorial' property --- front/src/Phaser/Game/GameMapProperties.ts | 1 + front/src/Phaser/Game/GameScene.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/front/src/Phaser/Game/GameMapProperties.ts b/front/src/Phaser/Game/GameMapProperties.ts index 65ce6ab8..2da56ef8 100644 --- a/front/src/Phaser/Game/GameMapProperties.ts +++ b/front/src/Phaser/Game/GameMapProperties.ts @@ -31,6 +31,7 @@ export enum GameMapProperties { SILENT = "silent", START = "start", START_LAYER = "startLayer", + TUTORIAL = "tutorial", URL = "url", WRITABLE_BY = "writableBy", ZONE = "zone", diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index ec12aae8..e32fa98a 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -603,6 +603,10 @@ export class GameScene extends DirtyScene { for (const script of scripts) { scriptPromises.push(iframeListener.registerScript(script)); } + if (this.isTutorialEnabled()) { + //TODO: split tutorial and bundle scripts + scriptPromises.push(iframeListener.registerScript(new URL("bundle.js", this.MapUrlFile).toString())); + } this.userInputManager.spaceEvent(() => { this.outlinedItem?.activate(); @@ -1397,7 +1401,6 @@ ${escapedMessage} this.connection?.emitPlayerOutlineColor(null); }); - iframeListener.registerAnswerer("getPlayerPosition", () => { return { x: this.CurrentPlayer.x, @@ -1569,6 +1572,10 @@ ${escapedMessage} ); } + private isTutorialEnabled(): boolean { + return this.getProperty(this.mapFile, GameMapProperties.TUTORIAL) as boolean; + } + private getProperty(layer: ITiledMapLayer | ITiledMap, name: string): string | boolean | number | undefined { const properties: ITiledMapProperty[] | undefined = layer.properties; if (!properties) { From 85d45071fa3a78ac79f43c70dc8fb41a5daf8c83 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Thu, 30 Dec 2021 15:50:52 +0100 Subject: [PATCH 86/95] Makes onCameraUpdate subscribe-able --- front/src/Api/iframe/camera.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/front/src/Api/iframe/camera.ts b/front/src/Api/iframe/camera.ts index 4f62b94c..a832290e 100644 --- a/front/src/Api/iframe/camera.ts +++ b/front/src/Api/iframe/camera.ts @@ -1,6 +1,6 @@ import { IframeApiContribution, sendToWorkadventure } from "./IframeApiContribution"; import { Subject } from "rxjs"; -import type { WasCameraUpdatedEvent, WasCameraUpdatedEventCallback } from "../Events/WasCameraUpdatedEvent"; +import type { WasCameraUpdatedEvent } from "../Events/WasCameraUpdatedEvent"; import { apiCallback } from "./registeredCallbacks"; import { isWasCameraUpdatedEvent } from "../Events/WasCameraUpdatedEvent"; @@ -17,12 +17,12 @@ export class WorkAdventureCameraCommands extends IframeApiContribution { sendToWorkadventure({ type: "onCameraUpdate", data: null, }); + return moveStream; } } From 8157ee4603df3db2db3a5a37b537fe2b92557c06 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Mon, 3 Jan 2022 10:36:23 +0100 Subject: [PATCH 87/95] Completes documentation --- docs/maps/api-camera.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/maps/api-camera.md b/docs/maps/api-camera.md index e0b5d7f7..f78df51f 100644 --- a/docs/maps/api-camera.md +++ b/docs/maps/api-camera.md @@ -4,7 +4,7 @@ ### Listen to camera updates ``` -WA.camera.onCameraUpdate(callback: WasCameraUpdatedEventCallback): void +WA.camera.onCameraUpdate: Subscription ``` Listens to updates of the camera viewport. It will trigger for every update of the camera's properties (position or scale for instance). An event will be sent. @@ -19,5 +19,7 @@ The event has the following attributes : Example : ```javascript -WA.camera.onCameraUpdate((worldView) => console.log(worldView)); +WA.camera.onCameraUpdate.subscribe((worldView) => console.log(worldView)); +//later... +WA.camera.onCameraUpdate().unsubscribe(); ``` \ No newline at end of file From ac27ab7e3e91febbbfef4907d6cd2d066fe6fd81 Mon Sep 17 00:00:00 2001 From: Benedicte Quimbert Date: Mon, 3 Jan 2022 11:22:01 +0100 Subject: [PATCH 88/95] Revert "WIP enable/disable tutorial according to the map 'tutorial' property" This reverts commit 47a6710b60e3856bf57c0700fe33bec95c6fc6dd. --- front/src/Phaser/Game/GameMapProperties.ts | 1 - front/src/Phaser/Game/GameScene.ts | 8 -------- 2 files changed, 9 deletions(-) diff --git a/front/src/Phaser/Game/GameMapProperties.ts b/front/src/Phaser/Game/GameMapProperties.ts index 2da56ef8..65ce6ab8 100644 --- a/front/src/Phaser/Game/GameMapProperties.ts +++ b/front/src/Phaser/Game/GameMapProperties.ts @@ -31,7 +31,6 @@ export enum GameMapProperties { SILENT = "silent", START = "start", START_LAYER = "startLayer", - TUTORIAL = "tutorial", URL = "url", WRITABLE_BY = "writableBy", ZONE = "zone", diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index e32fa98a..69683e25 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -603,10 +603,6 @@ export class GameScene extends DirtyScene { for (const script of scripts) { scriptPromises.push(iframeListener.registerScript(script)); } - if (this.isTutorialEnabled()) { - //TODO: split tutorial and bundle scripts - scriptPromises.push(iframeListener.registerScript(new URL("bundle.js", this.MapUrlFile).toString())); - } this.userInputManager.spaceEvent(() => { this.outlinedItem?.activate(); @@ -1572,10 +1568,6 @@ ${escapedMessage} ); } - private isTutorialEnabled(): boolean { - return this.getProperty(this.mapFile, GameMapProperties.TUTORIAL) as boolean; - } - private getProperty(layer: ITiledMapLayer | ITiledMap, name: string): string | boolean | number | undefined { const properties: ITiledMapProperty[] | undefined = layer.properties; if (!properties) { From 7c34e0a435240ed08e1f4576446844a2b33a7971 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dicte=20Q?= <37311765+HimeShaman@users.noreply.github.com> Date: Mon, 3 Jan 2022 14:19:55 +0100 Subject: [PATCH 89/95] Update docs/maps/api-camera.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Négrier --- docs/maps/api-camera.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/maps/api-camera.md b/docs/maps/api-camera.md index f78df51f..c9dc413f 100644 --- a/docs/maps/api-camera.md +++ b/docs/maps/api-camera.md @@ -4,7 +4,7 @@ ### Listen to camera updates ``` -WA.camera.onCameraUpdate: Subscription +WA.camera.onCameraUpdate(): Subscription ``` Listens to updates of the camera viewport. It will trigger for every update of the camera's properties (position or scale for instance). An event will be sent. From 5d0aa835a2c5d55a870a36743ffe48f3877869f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dicte=20Q?= <37311765+HimeShaman@users.noreply.github.com> Date: Mon, 3 Jan 2022 14:21:59 +0100 Subject: [PATCH 90/95] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Négrier --- docs/maps/api-camera.md | 5 ++--- docs/maps/api-player.md | 4 ++-- docs/maps/api-room.md | 20 ++++++++++---------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/docs/maps/api-camera.md b/docs/maps/api-camera.md index c9dc413f..cb1fe72d 100644 --- a/docs/maps/api-camera.md +++ b/docs/maps/api-camera.md @@ -19,7 +19,6 @@ The event has the following attributes : Example : ```javascript -WA.camera.onCameraUpdate.subscribe((worldView) => console.log(worldView)); +const subscription = WA.camera.onCameraUpdate().subscribe((worldView) => console.log(worldView)); //later... -WA.camera.onCameraUpdate().unsubscribe(); -``` \ No newline at end of file +subscription.unsubscribe(); \ No newline at end of file diff --git a/docs/maps/api-player.md b/docs/maps/api-player.md index 2bfad463..58d5701a 100644 --- a/docs/maps/api-player.md +++ b/docs/maps/api-player.md @@ -101,8 +101,8 @@ The player's current position is available using the `WA.player.getPosition()` f You need to wait for the end of the initialization before calling `WA.player.getPosition()` ```typescript -WA.onInit().then(() => { - console.log('Position: ', WA.player.getPosition()); +WA.onInit().then(async () => { + console.log('Position: ', await WA.player.getPosition()); }) ``` diff --git a/docs/maps/api-room.md b/docs/maps/api-room.md index bcc53332..7d438a1f 100644 --- a/docs/maps/api-room.md +++ b/docs/maps/api-room.md @@ -215,16 +215,16 @@ interface CreateEmbeddedWebsiteEvent { name: string; // A unique name for this iframe url: string; // The URL the iframe points to. position: { - x: number, // relative to the map or player coordinates, depending on origin - y: number, // relative to the map or player coordinates, depending on origin - width: number, // In pixels, sensitive to zoom level and scale - height: number, // In pixels, sensitive to zoom level and scale + x: number, // In "game" pixels, relative to the map or player coordinates, depending on origin + y: number, // In "game" pixels, relative to the map or player coordinates, depending on origin + width: number, // In "game" pixels + height: number, // In "game" pixels }, visible?: boolean, // Whether to display the iframe or not allowApi?: boolean, // Whether the scripting API should be available to the iframe allow?: string, // The list of feature policies allowed - origin: "player" | "map" // The origin used to place the x and y coordinates of the iframe's top-left corner - scale: number, // A ratio used to resize the iframe (will affect the iframe's width and height when it is rendered + origin: "player" | "map" // The origin used to place the x and y coordinates of the iframe's top-left corner, defaults to "map" + scale: number, // A ratio used to resize the iframe } ``` @@ -269,10 +269,10 @@ class EmbeddedWebsite { visible: boolean; allow: string; allowApi: boolean; - x: number; // In pixels, relative to the map or player coordinates, depending on origin - y: number; // In pixels, relative to the map or player coordinates, depending on origin - width: number; // In pixels, sensitive to zoom level and scale - height: number; // In pixels, sensitive to zoom level and scale + x: number; // In "game" pixels, relative to the map or player coordinates, depending on origin + y: number; // In "game" pixels, relative to the map or player coordinates, depending on origin + width: number; // In "game" pixels + height: number; // In "game" pixels origin: "player" | "map"; scale: number; } From bf070c33b5421cfbbdefa5f26306fb2f55bb3c80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 3 Jan 2022 15:23:28 +0100 Subject: [PATCH 91/95] Migrating front protobuf encode/decode to ts-proto lib The ts-proto lib has the huge advantage of producing code the "Typescript" way and not the "Java" way. In particular, for "oneof" types in protobuf, it is generating "ADT" that can be used to check if we forgot or not to deal with a type. --- .github/workflows/continuous_integration.yml | 2 +- .github/workflows/push-to-npm.yml | 2 +- front/Dockerfile | 5 +- front/package.json | 1 + front/src/Connexion/AdminMessagesService.ts | 6 +- front/src/Connexion/ConnectionManager.ts | 4 +- front/src/Connexion/ConnexionModels.ts | 36 +- front/src/Connexion/EmoteEventStream.ts | 17 - front/src/Connexion/RoomConnection.ts | 1068 +++++++++-------- front/src/Connexion/WorldFullMessageStream.ts | 12 - front/src/Messages/.gitignore | 1 - .../Messages/ts-proto-generated/.gitignore | 1 + front/src/Network/ProtobufClientUtils.ts | 20 +- front/src/Phaser/Game/EmoteManager.ts | 8 +- front/src/Phaser/Game/GameScene.ts | 56 +- .../src/Phaser/Game/SharedVariablesManager.ts | 2 +- front/src/Stores/PlayersStore.ts | 8 +- front/src/WebRtc/SimplePeer.ts | 14 +- front/yarn.lock | 143 ++- messages/package.json | 8 +- messages/ts-proto-generated/.gitignore | 2 + messages/yarn.lock | 75 +- 22 files changed, 865 insertions(+), 626 deletions(-) delete mode 100644 front/src/Connexion/EmoteEventStream.ts delete mode 100644 front/src/Connexion/WorldFullMessageStream.ts delete mode 100644 front/src/Messages/.gitignore create mode 100644 front/src/Messages/ts-proto-generated/.gitignore create mode 100644 messages/ts-proto-generated/.gitignore diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 1ad73027..ea67c3c1 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -39,7 +39,7 @@ jobs: working-directory: "messages" - name: "Build proto messages" - run: yarn run proto && yarn run copy-to-front && yarn run json-copy-to-front + run: yarn run ts-proto && yarn run copy-to-front-ts-proto && yarn run json-copy-to-front working-directory: "messages" - name: "Create index.html" diff --git a/.github/workflows/push-to-npm.yml b/.github/workflows/push-to-npm.yml index 71f2824f..571a16e6 100644 --- a/.github/workflows/push-to-npm.yml +++ b/.github/workflows/push-to-npm.yml @@ -36,7 +36,7 @@ jobs: working-directory: "messages" - name: "Build proto messages" - run: yarn run proto && yarn run copy-to-front && yarn run json-copy-to-front + run: yarn run ts-proto && yarn run copy-to-front-ts-proto && yarn run json-copy-to-front working-directory: "messages" - name: "Create index.html" diff --git a/front/Dockerfile b/front/Dockerfile index f781a37c..49cf6046 100644 --- a/front/Dockerfile +++ b/front/Dockerfile @@ -1,13 +1,14 @@ FROM node:14.15.4-buster-slim@sha256:cbae886186467bbfd274b82a234a1cdfbbd31201c2a6ee63a6893eefcf3c6e76 as builder WORKDIR /usr/src COPY messages . -RUN yarn install && yarn proto +RUN yarn install && yarn ts-proto # we are rebuilding on each deploy to cope with the PUSHER_URL environment URL FROM thecodingmachine/nodejs:14-apache COPY --chown=docker:docker front . -COPY --from=builder --chown=docker:docker /usr/src/generated /var/www/html/src/Messages/generated +COPY --from=builder --chown=docker:docker /usr/src/ts-proto-generated/protos /var/www/html/src/Messages/ts-proto-generated +RUN sed -i 's/import { Observable } from "rxjs";/import type { Observable } from "rxjs";/g' /var/www/html/src/Messages/ts-proto-generated/messages.ts COPY --from=builder --chown=docker:docker /usr/src/JsonMessages /var/www/html/src/Messages/JsonMessages # Removing the iframe.html file from the final image as this adds a XSS attack. diff --git a/front/package.json b/front/package.json index eae92cd2..935c254f 100644 --- a/front/package.json +++ b/front/package.json @@ -62,6 +62,7 @@ "simple-peer": "^9.11.0", "socket.io-client": "^2.3.0", "standardized-audio-context": "^25.2.4", + "ts-proto": "^1.96.0", "uuidv4": "^6.2.10" }, "scripts": { diff --git a/front/src/Connexion/AdminMessagesService.ts b/front/src/Connexion/AdminMessagesService.ts index 0b217760..4b7030ed 100644 --- a/front/src/Connexion/AdminMessagesService.ts +++ b/front/src/Connexion/AdminMessagesService.ts @@ -1,5 +1,5 @@ import { Subject } from "rxjs"; -import type { BanUserMessage, SendUserMessage } from "../Messages/generated/messages_pb"; +import type { BanUserMessage, SendUserMessage } from "../Messages/ts-proto-generated/messages"; export enum AdminMessageEventTypes { admin = "message", @@ -26,8 +26,8 @@ class AdminMessagesService { onSendusermessage(message: SendUserMessage | BanUserMessage) { this._messageStream.next({ - type: message.getType() as unknown as AdminMessageEventTypes, - text: message.getMessage(), + type: message.type as unknown as AdminMessageEventTypes, + text: message.message, }); } } diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts index 026cc20a..19750ee8 100644 --- a/front/src/Connexion/ConnectionManager.ts +++ b/front/src/Connexion/ConnectionManager.ts @@ -280,7 +280,7 @@ class ConnectionManager { reject(error); }); - connection.onConnectingError((event: CloseEvent) => { + connection.connectionErrorStream.subscribe((event: CloseEvent) => { console.log("An error occurred while connecting to socket server. Retrying"); reject( new Error( @@ -292,7 +292,7 @@ class ConnectionManager { ); }); - connection.onConnect((connect: OnConnectInterface) => { + connection.roomJoinedMessageStream.subscribe((connect: OnConnectInterface) => { resolve(connect); }); }).catch((err) => { diff --git a/front/src/Connexion/ConnexionModels.ts b/front/src/Connexion/ConnexionModels.ts index 6200e0c9..bf834a02 100644 --- a/front/src/Connexion/ConnexionModels.ts +++ b/front/src/Connexion/ConnexionModels.ts @@ -1,44 +1,12 @@ import type { SignalData } from "simple-peer"; import type { RoomConnection } from "./RoomConnection"; import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures"; - -export enum EventMessage { - CONNECT = "connect", - WEBRTC_SIGNAL = "webrtc-signal", - WEBRTC_SCREEN_SHARING_SIGNAL = "webrtc-screen-sharing-signal", - WEBRTC_START = "webrtc-start", - //START_ROOM = "start-room", // From server to client: list of all room users/groups/items - JOIN_ROOM = "join-room", // bi-directional - USER_POSITION = "user-position", // From client to server - USER_MOVED = "user-moved", // From server to client - USER_LEFT = "user-left", // From server to client - MESSAGE_ERROR = "message-error", - WEBRTC_DISCONNECT = "webrtc-disconect", - GROUP_CREATE_UPDATE = "group-create-update", - GROUP_DELETE = "group-delete", - SET_PLAYER_DETAILS = "set-player-details", // Send the name and character to the server (on connect), receive back the id. - ITEM_EVENT = "item-event", - USER_DETAILS_UPDATED = "user-details-updated", - - CONNECT_ERROR = "connect_error", - CONNECTING_ERROR = "connecting_error", - SET_SILENT = "set_silent", // Set or unset the silent mode for this user. - SET_VIEWPORT = "set-viewport", - BATCH = "batch", - - PLAY_GLOBAL_MESSAGE = "play-global-message", - STOP_GLOBAL_MESSAGE = "stop-global-message", - - TELEPORT = "teleport", - USER_MESSAGE = "user-message", - START_JITSI_ROOM = "start-jitsi-room", - SET_VARIABLE = "set-variable", -} +import { PositionMessage_Direction } from "../Messages/ts-proto-generated/messages"; export interface PointInterface { x: number; y: number; - direction: string; + direction: string; // TODO: modify this to the enum from ts-proto moving: boolean; } diff --git a/front/src/Connexion/EmoteEventStream.ts b/front/src/Connexion/EmoteEventStream.ts deleted file mode 100644 index 32f1daa0..00000000 --- a/front/src/Connexion/EmoteEventStream.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Subject } from "rxjs"; - -interface EmoteEvent { - userId: number; - emote: string; -} - -class EmoteEventStream { - private _stream: Subject = new Subject(); - public stream = this._stream.asObservable(); - - fire(userId: number, emote: string) { - this._stream.next({ userId, emote }); - } -} - -export const emoteEventStream = new EmoteEventStream(); diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 328f1aec..b7aa30ce 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -1,50 +1,9 @@ import { PUSHER_URL, UPLOADER_URL } from "../Enum/EnvironmentVariable"; import Axios from "axios"; -import { - BatchMessage, - ClientToServerMessage, - GroupDeleteMessage, - GroupUpdateMessage, - ItemEventMessage, - PlayGlobalMessage, - PositionMessage, - RoomJoinedMessage, - ServerToClientMessage, - SetPlayerDetailsMessage, - SilentMessage, - StopGlobalMessage, - UserJoinedMessage, - UserLeftMessage, - UserMovedMessage, - UserMovesMessage, - ViewportMessage, - WebRtcDisconnectMessage, - WebRtcSignalToClientMessage, - WebRtcSignalToServerMessage, - WebRtcStartMessage, - ReportPlayerMessage, - TeleportMessageMessage, - QueryJitsiJwtMessage, - SendJitsiJwtMessage, - CharacterLayerMessage, - PingMessage, - EmoteEventMessage, - EmotePromptMessage, - FollowRequestMessage, - FollowConfirmationMessage, - FollowAbortMessage, - SendUserMessage, - BanUserMessage, - VariableMessage, - ErrorMessage, - PlayerDetailsUpdatedMessage, -} from "../Messages/generated/messages_pb"; import type { UserSimplePeerInterface } from "../WebRtc/SimplePeer"; -import Direction = PositionMessage.Direction; import { ProtobufClientUtils } from "../Network/ProtobufClientUtils"; -import { - EventMessage, +import type { GroupCreatedUpdatedMessageInterface, ItemEventMessageInterface, MessageUserJoined, @@ -59,13 +18,44 @@ import { } from "./ConnexionModels"; import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures"; import { adminMessagesService } from "./AdminMessagesService"; -import { worldFullMessageStream } from "./WorldFullMessageStream"; import { connectionManager } from "./ConnectionManager"; -import { emoteEventStream } from "./EmoteEventStream"; import { get } from "svelte/store"; import { warningContainerStore } from "../Stores/MenuStore"; import { followStateStore, followRoleStore, followUsersStore } from "../Stores/FollowStore"; import { localUserStore } from "./LocalUserStore"; +import { + RefreshRoomMessage, + ServerToClientMessage as ServerToClientMessageTsProto, + TokenExpiredMessage, + WorldConnexionMessage, + WorldFullMessage, + ErrorMessage as ErrorMessageTsProto, + UserMovedMessage as UserMovedMessageTsProto, + GroupUpdateMessage as GroupUpdateMessageTsProto, + GroupDeleteMessage as GroupDeleteMessageTsProto, + UserJoinedMessage as UserJoinedMessageTsProto, + UserLeftMessage as UserLeftMessageTsProto, + ItemEventMessage as ItemEventMessageTsProto, + EmoteEventMessage as EmoteEventMessageTsProto, + VariableMessage as VariableMessageTsProto, + PlayerDetailsUpdatedMessage as PlayerDetailsUpdatedMessageTsProto, + WorldFullWarningMessage, + WebRtcDisconnectMessage as WebRtcDisconnectMessageTsProto, + PlayGlobalMessage as PlayGlobalMessageTsProto, + StopGlobalMessage as StopGlobalMessageTsProto, + SendJitsiJwtMessage as SendJitsiJwtMessageTsProto, + SendUserMessage as SendUserMessageTsProto, + BanUserMessage as BanUserMessageTsProto, + ClientToServerMessage as ClientToServerMessageTsProto, + PositionMessage as PositionMessageTsProto, + ViewportMessage as ViewportMessageTsProto, + PositionMessage_Direction, + SetPlayerDetailsMessage as SetPlayerDetailsMessageTsProto, + PingMessage as PingMessageTsProto, +} from "../Messages/ts-proto-generated/messages"; +import { Subject } from "rxjs"; +import { OpenPopupEvent } from "../Api/Events/OpenPopupEvent"; +import { match } from "assert"; const manualPingDelay = 20000; @@ -78,6 +68,85 @@ export class RoomConnection implements RoomConnection { private tags: string[] = []; private _userRoomToken: string | undefined; + private readonly _errorMessageStream = new Subject(); + public readonly errorMessageStream = this._errorMessageStream.asObservable(); + + private readonly _roomJoinedMessageStream = new Subject<{ + connection: RoomConnection; + room: RoomJoinedMessageInterface; + }>(); + public readonly roomJoinedMessageStream = this._roomJoinedMessageStream.asObservable(); + + private readonly _webRtcStartMessageStream = new Subject(); + public readonly webRtcStartMessageStream = this._webRtcStartMessageStream.asObservable(); + + private readonly _webRtcSignalToClientMessageStream = new Subject(); + public readonly webRtcSignalToClientMessageStream = this._webRtcSignalToClientMessageStream.asObservable(); + + private readonly _webRtcScreenSharingSignalToClientMessageStream = + new Subject(); + public readonly webRtcScreenSharingSignalToClientMessageStream = + this._webRtcScreenSharingSignalToClientMessageStream.asObservable(); + + private readonly _webRtcDisconnectMessageStream = new Subject(); + public readonly webRtcDisconnectMessageStream = this._webRtcDisconnectMessageStream.asObservable(); + + private readonly _playGlobalMessageStream = new Subject(); + public readonly playGlobalMessageStream = this._playGlobalMessageStream.asObservable(); + + private readonly _stopGlobalMessageStream = new Subject(); + public readonly stopGlobalMessageStream = this._stopGlobalMessageStream.asObservable(); + + private readonly _teleportMessageMessageStream = new Subject(); + public readonly teleportMessageMessageStream = this._teleportMessageMessageStream.asObservable(); + + private readonly _sendJitsiJwtMessageStream = new Subject(); + public readonly sendJitsiJwtMessageStream = this._sendJitsiJwtMessageStream.asObservable(); + + private readonly _worldFullMessageStream = new Subject(); + public readonly worldFullMessageStream = this._worldFullMessageStream.asObservable(); + + private readonly _worldConnexionMessageStream = new Subject(); + public readonly worldConnexionMessageStream = this._worldConnexionMessageStream.asObservable(); + + private readonly _tokenExpiredMessageStream = new Subject(); + public readonly tokenExpiredMessageStream = this._tokenExpiredMessageStream.asObservable(); + + private readonly _userMovedMessageStream = new Subject(); + public readonly userMovedMessageStream = this._userMovedMessageStream.asObservable(); + + private readonly _groupUpdateMessageStream = new Subject(); + public readonly groupUpdateMessageStream = this._groupUpdateMessageStream.asObservable(); + + private readonly _groupDeleteMessageStream = new Subject(); + public readonly groupDeleteMessageStream = this._groupDeleteMessageStream.asObservable(); + + private readonly _userJoinedMessageStream = new Subject(); + public readonly userJoinedMessageStream = this._userJoinedMessageStream.asObservable(); + + private readonly _userLeftMessageStream = new Subject(); + public readonly userLeftMessageStream = this._userLeftMessageStream.asObservable(); + + private readonly _itemEventMessageStream = new Subject<{ + itemId: number; + event: string; + parameters: unknown; + state: unknown; + }>(); + public readonly itemEventMessageStream = this._itemEventMessageStream.asObservable(); + + private readonly _emoteEventMessageStream = new Subject(); + public readonly emoteEventMessageStream = this._emoteEventMessageStream.asObservable(); + + private readonly _variableMessageStream = new Subject<{ name: string; value: unknown }>(); + public readonly variableMessageStream = this._variableMessageStream.asObservable(); + + private readonly _playerDetailsUpdatedMessageStream = new Subject(); + public readonly playerDetailsUpdatedMessageStream = this._playerDetailsUpdatedMessageStream.asObservable(); + + private readonly _connectionErrorStream = new Subject(); + public readonly connectionErrorStream = this._connectionErrorStream.asObservable(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any public static setWebsocketFactory(websocketFactory: (url: string) => any): void { RoomConnection.websocketFactory = websocketFactory; @@ -136,8 +205,8 @@ export class RoomConnection implements RoomConnection { this.socket.onopen = (ev) => { //we manually ping every 20s to not be logged out by the server, even when the game is in background. - const pingMessage = new PingMessage(); - interval = setInterval(() => this.socket.send(pingMessage.serializeBinary().buffer), manualPingDelay); + const pingMessage = PingMessageTsProto.encode({}).finish(); + interval = setInterval(() => this.socket.send(pingMessage), manualPingDelay); }; this.socket.addEventListener("close", (event) => { @@ -147,147 +216,264 @@ export class RoomConnection implements RoomConnection { // If we are not connected yet (if a JoinRoomMessage was not sent), we need to retry. if (this.userId === null && !this.closed) { - this.dispatch(EventMessage.CONNECTING_ERROR, event); + this._connectionErrorStream.next(event); } }); this.socket.onmessage = (messageEvent) => { const arrayBuffer: ArrayBuffer = messageEvent.data; - const message = ServerToClientMessage.deserializeBinary(new Uint8Array(arrayBuffer)); - if (message.hasBatchmessage()) { - for (const subMessage of (message.getBatchmessage() as BatchMessage).getPayloadList()) { - let event: string | null = null; - let payload; - if (subMessage.hasUsermovedmessage()) { - event = EventMessage.USER_MOVED; - payload = subMessage.getUsermovedmessage(); - } else if (subMessage.hasGroupupdatemessage()) { - event = EventMessage.GROUP_CREATE_UPDATE; - payload = subMessage.getGroupupdatemessage(); - } else if (subMessage.hasGroupdeletemessage()) { - event = EventMessage.GROUP_DELETE; - payload = subMessage.getGroupdeletemessage(); - } else if (subMessage.hasUserjoinedmessage()) { - event = EventMessage.JOIN_ROOM; - payload = subMessage.getUserjoinedmessage(); - } else if (subMessage.hasUserleftmessage()) { - event = EventMessage.USER_LEFT; - payload = subMessage.getUserleftmessage(); - } else if (subMessage.hasItemeventmessage()) { - event = EventMessage.ITEM_EVENT; - payload = subMessage.getItemeventmessage(); - } else if (subMessage.hasEmoteeventmessage()) { - const emoteMessage = subMessage.getEmoteeventmessage() as EmoteEventMessage; - emoteEventStream.fire(emoteMessage.getActoruserid(), emoteMessage.getEmote()); - } else if (subMessage.hasPlayerdetailsupdatedmessage()) { - event = EventMessage.USER_DETAILS_UPDATED; - payload = subMessage.getPlayerdetailsupdatedmessage(); - } else if (subMessage.hasErrormessage()) { - const errorMessage = subMessage.getErrormessage() as ErrorMessage; - console.error("An error occurred server side: " + errorMessage.getMessage()); - } else if (subMessage.hasVariablemessage()) { - event = EventMessage.SET_VARIABLE; - payload = subMessage.getVariablemessage(); + const serverToClientMessage = ServerToClientMessageTsProto.decode(new Uint8Array(arrayBuffer)); + //const message = ServerToClientMessage.deserializeBinary(new Uint8Array(arrayBuffer)); + + const message = serverToClientMessage.message; + if (message === undefined) { + return; + } + + switch (message.$case) { + case "batchMessage": { + for (const subMessageWrapper of message.batchMessage.payload) { + const subMessage = subMessageWrapper.message; + if (subMessage === undefined) { + return; + } + switch (subMessage.$case) { + case "errorMessage": { + this._errorMessageStream.next(subMessage.errorMessage); + console.error("An error occurred server side: " + subMessage.errorMessage.message); + break; + } + case "userJoinedMessage": { + this._userJoinedMessageStream.next( + this.toMessageUserJoined(subMessage.userJoinedMessage) + ); + break; + } + case "userLeftMessage": { + this._userLeftMessageStream.next(subMessage.userLeftMessage); + break; + } + case "userMovedMessage": { + this._userMovedMessageStream.next(subMessage.userMovedMessage); + break; + } + case "groupUpdateMessage": { + this._groupUpdateMessageStream.next( + this.toGroupCreatedUpdatedMessage(subMessage.groupUpdateMessage) + ); + break; + } + case "groupDeleteMessage": { + this._groupDeleteMessageStream.next(subMessage.groupDeleteMessage); + break; + } + case "itemEventMessage": { + this._itemEventMessageStream.next({ + itemId: subMessage.itemEventMessage.itemId, + event: subMessage.itemEventMessage.event, + parameters: JSON.parse(subMessage.itemEventMessage.parametersJson), + state: JSON.parse(subMessage.itemEventMessage.stateJson), + }); + break; + } + case "emoteEventMessage": { + this._emoteEventMessageStream.next(subMessage.emoteEventMessage); + break; + } + case "playerDetailsUpdatedMessage": { + this._playerDetailsUpdatedMessageStream.next(subMessage.playerDetailsUpdatedMessage); + break; + } + case "variableMessage": { + const name = subMessage.variableMessage.name; + const serializedValue = subMessage.variableMessage.value; + let value: unknown = undefined; + if (serializedValue) { + try { + value = JSON.parse(serializedValue); + } catch (e) { + console.error( + 'Unable to unserialize value received from server for variable "' + + name + + '". Value received: "' + + serializedValue + + '". Error: ', + e + ); + } + } + + this._variableMessageStream.next({ name, value }); + break; + } + default: { + // Security check: if we forget a "case", the line below will catch the error at compile-time. + const tmp: never = subMessage; + } + } + } + break; + } + case "roomJoinedMessage": { + const roomJoinedMessage = message.roomJoinedMessage; + + const items: { [itemId: number]: unknown } = {}; + for (const item of roomJoinedMessage.item) { + items[item.itemId] = JSON.parse(item.stateJson); + } + + const variables = new Map(); + for (const variable of roomJoinedMessage.variable) { + try { + variables.set(variable.name, JSON.parse(variable.value)); + } catch (e) { + console.error( + 'Unable to unserialize value received from server for variable "' + + variable.name + + '". Value received: "' + + variable.value + + '". Error: ', + e + ); + } + } + + this.userId = roomJoinedMessage.currentUserId; + this.tags = roomJoinedMessage.tag; + this._userRoomToken = roomJoinedMessage.userRoomToken; + + this._roomJoinedMessageStream.next({ + connection: this, + room: { + items, + variables, + } as RoomJoinedMessageInterface, + }); + break; + } + case "worldFullMessage": { + this._worldFullMessageStream.next(null); + this.closed = true; + break; + } + case "tokenExpiredMessage": { + connectionManager.logout(); + this.closed = true; //technically, this isn't needed since loadOpenIDScreen() will do window.location.assign() but I prefer to leave it for consistency + break; + } + case "worldConnexionMessage": { + this._worldFullMessageStream.next(message.worldConnexionMessage.message); + this.closed = true; + break; + } + case "webRtcSignalToClientMessage": { + this._webRtcSignalToClientMessageStream.next({ + userId: message.webRtcSignalToClientMessage.userId, + signal: JSON.parse(message.webRtcSignalToClientMessage.signal), + webRtcUser: message.webRtcSignalToClientMessage.webrtcUserName + ? message.webRtcSignalToClientMessage.webrtcUserName + : undefined, + webRtcPassword: message.webRtcSignalToClientMessage.webrtcPassword + ? message.webRtcSignalToClientMessage.webrtcPassword + : undefined, + }); + break; + } + case "webRtcScreenSharingSignalToClientMessage": { + this._webRtcScreenSharingSignalToClientMessageStream.next({ + userId: message.webRtcScreenSharingSignalToClientMessage.userId, + signal: JSON.parse(message.webRtcScreenSharingSignalToClientMessage.signal), + webRtcUser: message.webRtcScreenSharingSignalToClientMessage.webrtcUserName + ? message.webRtcScreenSharingSignalToClientMessage.webrtcUserName + : undefined, + webRtcPassword: message.webRtcScreenSharingSignalToClientMessage.webrtcPassword + ? message.webRtcScreenSharingSignalToClientMessage.webrtcPassword + : undefined, + }); + break; + } + case "webRtcStartMessage": { + this._webRtcStartMessageStream.next({ + userId: message.webRtcStartMessage.userId, + initiator: message.webRtcStartMessage.initiator, + webRtcUser: message.webRtcStartMessage.webrtcUserName + ? message.webRtcStartMessage.webrtcUserName + : undefined, + webRtcPassword: message.webRtcStartMessage.webrtcPassword + ? message.webRtcStartMessage.webrtcPassword + : undefined, + }); + break; + } + case "webRtcDisconnectMessage": { + this._webRtcDisconnectMessageStream.next(message.webRtcDisconnectMessage); + break; + } + case "playGlobalMessage": { + // FIXME: WHY IS THIS UNUSED? CAN WE REMOVE THIS??? + // FIXME: WHY IS THIS UNUSED? CAN WE REMOVE THIS??? + this._playGlobalMessageStream.next(message.playGlobalMessage); + break; + } + case "stopGlobalMessage": { + // FIXME: WHY IS THIS UNUSED? CAN WE REMOVE THIS??? + // FIXME: WHY IS THIS UNUSED? CAN WE REMOVE THIS??? + this._stopGlobalMessageStream.next(message.stopGlobalMessage); + break; + } + case "teleportMessageMessage": { + // FIXME: WHY IS THIS UNUSED? CAN WE REMOVE THIS??? + this._teleportMessageMessageStream.next(message.teleportMessageMessage.map); + break; + } + case "sendJitsiJwtMessage": { + this._sendJitsiJwtMessageStream.next(message.sendJitsiJwtMessage); + break; + } + case "sendUserMessage": { + adminMessagesService.onSendusermessage(message.sendUserMessage); + break; + } + case "banUserMessage": { + adminMessagesService.onSendusermessage(message.banUserMessage); + break; + } + case "worldFullWarningMessage": { + warningContainerStore.activateWarningContainer(); + break; + } + case "refreshRoomMessage": { + //todo: implement a way to notify the user the room was refreshed. + break; + } + case "followRequestMessage": { + if (!localUserStore.getIgnoreFollowRequests()) { + followUsersStore.addFollowRequest(message.followRequestMessage.leader); + } + break; + } + case "followConfirmationMessage": { + followUsersStore.addFollower(message.followConfirmationMessage.follower); + break; + } + case "followAbortMessage": { + if (get(followRoleStore) === "follower") { + followUsersStore.stopFollowing(); } else { - throw new Error("Unexpected batch message type"); - } - - if (event) { - this.dispatch(event, payload); + followUsersStore.removeFollower(message.followAbortMessage.follower); } + break; } - } else if (message.hasRoomjoinedmessage()) { - const roomJoinedMessage = message.getRoomjoinedmessage() as RoomJoinedMessage; - - const items: { [itemId: number]: unknown } = {}; - for (const item of roomJoinedMessage.getItemList()) { - items[item.getItemid()] = JSON.parse(item.getStatejson()); + case "errorMessage": { + this._errorMessageStream.next(message.errorMessage); + console.error("An error occurred server side: " + message.errorMessage.message); + break; } - - const variables = new Map(); - for (const variable of roomJoinedMessage.getVariableList()) { - try { - variables.set(variable.getName(), JSON.parse(variable.getValue())); - } catch (e) { - console.error( - 'Unable to unserialize value received from server for variable "' + - variable.getName() + - '". Value received: "' + - variable.getValue() + - '". Error: ', - e - ); - } + default: { + // Security check: if we forget a "case", the line below will catch the error at compile-time. + const tmp: never = message; } - - this.userId = roomJoinedMessage.getCurrentuserid(); - this.tags = roomJoinedMessage.getTagList(); - this._userRoomToken = roomJoinedMessage.getUserroomtoken(); - - this.dispatch(EventMessage.CONNECT, { - connection: this, - room: { - items, - variables, - } as RoomJoinedMessageInterface, - }); - } else if (message.hasWorldfullmessage()) { - worldFullMessageStream.onMessage(); - this.closed = true; - } else if (message.hasTokenexpiredmessage()) { - connectionManager.logout(); - this.closed = true; //technically, this isn't needed since loadOpenIDScreen() will do window.location.assign() but I prefer to leave it for consistency - } else if (message.hasWorldconnexionmessage()) { - worldFullMessageStream.onMessage(message.getWorldconnexionmessage()?.getMessage()); - this.closed = true; - } else if (message.hasWebrtcsignaltoclientmessage()) { - this.dispatch(EventMessage.WEBRTC_SIGNAL, message.getWebrtcsignaltoclientmessage()); - } else if (message.hasWebrtcscreensharingsignaltoclientmessage()) { - this.dispatch( - EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, - message.getWebrtcscreensharingsignaltoclientmessage() - ); - } else if (message.hasWebrtcstartmessage()) { - this.dispatch(EventMessage.WEBRTC_START, message.getWebrtcstartmessage()); - } else if (message.hasWebrtcdisconnectmessage()) { - this.dispatch(EventMessage.WEBRTC_DISCONNECT, message.getWebrtcdisconnectmessage()); - } else if (message.hasPlayglobalmessage()) { - this.dispatch(EventMessage.PLAY_GLOBAL_MESSAGE, message.getPlayglobalmessage()); - } else if (message.hasStopglobalmessage()) { - this.dispatch(EventMessage.STOP_GLOBAL_MESSAGE, message.getStopglobalmessage()); - } else if (message.hasTeleportmessagemessage()) { - this.dispatch(EventMessage.TELEPORT, message.getTeleportmessagemessage()); - } else if (message.hasSendjitsijwtmessage()) { - this.dispatch(EventMessage.START_JITSI_ROOM, message.getSendjitsijwtmessage()); - } else if (message.hasSendusermessage()) { - adminMessagesService.onSendusermessage(message.getSendusermessage() as SendUserMessage); - } else if (message.hasBanusermessage()) { - adminMessagesService.onSendusermessage(message.getBanusermessage() as BanUserMessage); - } else if (message.hasWorldfullwarningmessage()) { - warningContainerStore.activateWarningContainer(); - } else if (message.hasRefreshroommessage()) { - //todo: implement a way to notify the user the room was refreshed. - } else if (message.hasFollowrequestmessage()) { - const requestMessage = message.getFollowrequestmessage() as FollowRequestMessage; - if (!localUserStore.getIgnoreFollowRequests()) { - followUsersStore.addFollowRequest(requestMessage.getLeader()); - } - } else if (message.hasFollowconfirmationmessage()) { - const responseMessage = message.getFollowconfirmationmessage() as FollowConfirmationMessage; - followUsersStore.addFollower(responseMessage.getFollower()); - } else if (message.hasFollowabortmessage()) { - const abortMessage = message.getFollowabortmessage() as FollowAbortMessage; - if (get(followRoleStore) === "follower") { - followUsersStore.stopFollowing(); - } else { - followUsersStore.removeFollower(abortMessage.getFollower()); - } - } else if (message.hasErrormessage()) { - const errorMessage = message.getErrormessage() as ErrorMessage; - console.error("An error occurred server side: " + errorMessage.getMessage()); - } else { - throw new Error("Unknown message received"); } }; } @@ -314,17 +500,25 @@ export class RoomConnection implements RoomConnection { }*/ public emitPlayerOutlineColor(color: number | null) { - const message = new SetPlayerDetailsMessage(); + let message: SetPlayerDetailsMessageTsProto; if (color === null) { - message.setRemoveoutlinecolor(true); + message = SetPlayerDetailsMessageTsProto.fromPartial({ + removeOutlineColor: true, + }); } else { - message.setOutlinecolor(color); + message = SetPlayerDetailsMessageTsProto.fromPartial({ + outlineColor: color, + }); } - const clientToServerMessage = new ClientToServerMessage(); - clientToServerMessage.setSetplayerdetailsmessage(message); + const bytes = ClientToServerMessageTsProto.encode({ + message: { + $case: "setPlayerDetailsMessage", + setPlayerDetailsMessage: message, + }, + }).finish(); - this.socket.send(clientToServerMessage.serializeBinary().buffer); + this.socket.send(bytes); } public closeConnection(): void { @@ -332,41 +526,35 @@ export class RoomConnection implements RoomConnection { this.closed = true; } - private toPositionMessage(x: number, y: number, direction: string, moving: boolean): PositionMessage { - const positionMessage = new PositionMessage(); - positionMessage.setX(Math.floor(x)); - positionMessage.setY(Math.floor(y)); - let directionEnum: Direction; - switch (direction) { - case "up": - directionEnum = Direction.UP; - break; - case "down": - directionEnum = Direction.DOWN; - break; - case "left": - directionEnum = Direction.LEFT; - break; - case "right": - directionEnum = Direction.RIGHT; - break; - default: - throw new Error("Unexpected direction"); - } - positionMessage.setDirection(directionEnum); - positionMessage.setMoving(moving); - - return positionMessage; + private toPositionMessage(x: number, y: number, direction: string, moving: boolean): PositionMessageTsProto { + return { + x: Math.floor(x), + y: Math.floor(y), + moving, + direction: (() => { + switch (direction) { + case "up": + return PositionMessage_Direction.UP; + case "down": + return PositionMessage_Direction.DOWN; + case "left": + return PositionMessage_Direction.LEFT; + case "right": + return PositionMessage_Direction.RIGHT; + default: + throw new Error("Unexpected direction"); + } + })(), + }; } - private toViewportMessage(viewport: ViewportInterface): ViewportMessage { - const viewportMessage = new ViewportMessage(); - viewportMessage.setLeft(Math.floor(viewport.left)); - viewportMessage.setRight(Math.floor(viewport.right)); - viewportMessage.setTop(Math.floor(viewport.top)); - viewportMessage.setBottom(Math.floor(viewport.bottom)); - - return viewportMessage; + private toViewportMessage(viewport: ViewportInterface): ViewportMessageTsProto { + return { + left: Math.floor(viewport.left), + right: Math.floor(viewport.right), + top: Math.floor(viewport.top), + bottom: Math.floor(viewport.bottom), + }; } public sharePosition(x: number, y: number, direction: string, moving: boolean, viewport: ViewportInterface): void { @@ -378,81 +566,77 @@ export class RoomConnection implements RoomConnection { const viewportMessage = this.toViewportMessage(viewport); - const userMovesMessage = new UserMovesMessage(); - userMovesMessage.setPosition(positionMessage); - userMovesMessage.setViewport(viewportMessage); + const bytes = ClientToServerMessageTsProto.encode({ + message: { + $case: "userMovesMessage", + userMovesMessage: { + position: positionMessage, + viewport: viewportMessage, + }, + }, + }).finish(); - //console.log('Sending position ', positionMessage.getX(), positionMessage.getY()); - const clientToServerMessage = new ClientToServerMessage(); - clientToServerMessage.setUsermovesmessage(userMovesMessage); - - this.socket.send(clientToServerMessage.serializeBinary().buffer); + this.socket.send(bytes); } public setSilent(silent: boolean): void { - const silentMessage = new SilentMessage(); - silentMessage.setSilent(silent); + const bytes = ClientToServerMessageTsProto.encode({ + message: { + $case: "silentMessage", + silentMessage: { + silent, + }, + }, + }).finish(); - const clientToServerMessage = new ClientToServerMessage(); - clientToServerMessage.setSilentmessage(silentMessage); - - this.socket.send(clientToServerMessage.serializeBinary().buffer); + this.socket.send(bytes); } public setViewport(viewport: ViewportInterface): void { - const viewportMessage = new ViewportMessage(); - viewportMessage.setTop(Math.round(viewport.top)); - viewportMessage.setBottom(Math.round(viewport.bottom)); - viewportMessage.setLeft(Math.round(viewport.left)); - viewportMessage.setRight(Math.round(viewport.right)); + const bytes = ClientToServerMessageTsProto.encode({ + message: { + $case: "viewportMessage", + viewportMessage: this.toViewportMessage(viewport), + }, + }).finish(); - const clientToServerMessage = new ClientToServerMessage(); - clientToServerMessage.setViewportmessage(viewportMessage); - - this.socket.send(clientToServerMessage.serializeBinary().buffer); + this.socket.send(bytes); } - public onUserJoins(callback: (message: MessageUserJoined) => void): void { + /* public onUserJoins(callback: (message: MessageUserJoined) => void): void { this.onMessage(EventMessage.JOIN_ROOM, (message: UserJoinedMessage) => { callback(this.toMessageUserJoined(message)); }); - } + }*/ // TODO: move this to protobuf utils - private toMessageUserJoined(message: UserJoinedMessage): MessageUserJoined { - const position = message.getPosition(); + private toMessageUserJoined(message: UserJoinedMessageTsProto): MessageUserJoined { + const position = message.position; 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(), - }; - }); + const characterLayers = message.characterLayers.map((characterLayer): BodyResourceDescriptionInterface => { + return { + name: characterLayer.name, + img: characterLayer.url, + }; + }); - const companion = message.getCompanion(); + const companion = message.companion; return { - userId: message.getUserid(), - name: message.getName(), + userId: message.userId, + name: message.name, characterLayers, - visitCardUrl: message.getVisitcardurl(), + visitCardUrl: message.visitCardUrl, position: ProtobufClientUtils.toPointInterface(position), - companion: companion ? companion.getName() : null, - userUuid: message.getUseruuid(), - outlineColor: message.getHasoutline() ? message.getOutlinecolor() : undefined, + companion: companion ? companion.name : null, + userUuid: message.userUuid, + outlineColor: message.hasOutline ? message.outlineColor : undefined, }; } - public onUserMoved(callback: (message: UserMovedMessage) => void): void { - this.onMessage(EventMessage.USER_MOVED, callback); - //this.socket.on(EventMessage.USER_MOVED, callback); - } - /** * Registers a listener on a message that is part of a batch */ @@ -465,114 +649,49 @@ export class RoomConnection implements RoomConnection { callbacks.push(callback); } - public onUserLeft(callback: (userId: number) => void): void { - this.onMessage(EventMessage.USER_LEFT, (message: UserLeftMessage) => { - callback(message.getUserid()); - }); - } - - public onGroupUpdatedOrCreated( - callback: (groupCreateUpdateMessage: GroupCreatedUpdatedMessageInterface) => void - ): void { - this.onMessage(EventMessage.GROUP_CREATE_UPDATE, (message: GroupUpdateMessage) => { - callback(this.toGroupCreatedUpdatedMessage(message)); - }); - } - - private toGroupCreatedUpdatedMessage(message: GroupUpdateMessage): GroupCreatedUpdatedMessageInterface { - const position = message.getPosition(); + private toGroupCreatedUpdatedMessage(message: GroupUpdateMessageTsProto): GroupCreatedUpdatedMessageInterface { + const position = message.position; if (position === undefined) { throw new Error("Missing position in GROUP_CREATE_UPDATE"); } return { - groupId: message.getGroupid(), - position: position.toObject(), - groupSize: message.getGroupsize(), + groupId: message.groupId, + position: position, + groupSize: message.groupSize, }; } - public onGroupDeleted(callback: (groupId: number) => void): void { - this.onMessage(EventMessage.GROUP_DELETE, (message: GroupDeleteMessage) => { - callback(message.getGroupid()); - }); - } - - public onConnectingError(callback: (event: CloseEvent) => void): void { - this.onMessage(EventMessage.CONNECTING_ERROR, (event: CloseEvent) => { - callback(event); - }); - } - public onConnectError(callback: (error: Event) => void): void { this.socket.addEventListener("error", callback); } - public onConnect(callback: (roomConnection: OnConnectInterface) => void): void { - //this.socket.addEventListener('open', callback) - this.onMessage(EventMessage.CONNECT, callback); - } - - /** - * Triggered when we receive all the details of a room (users, groups, ...) - */ - /*public onStartRoom(callback: (event: RoomJoinedMessageInterface) => void): void { - this.onMessage(EventMessage.START_ROOM, callback); - }*/ - public sendWebrtcSignal(signal: unknown, receiverId: number) { - const webRtcSignal = new WebRtcSignalToServerMessage(); - webRtcSignal.setReceiverid(receiverId); - webRtcSignal.setSignal(JSON.stringify(signal)); + const bytes = ClientToServerMessageTsProto.encode({ + message: { + $case: "webRtcSignalToServerMessage", + webRtcSignalToServerMessage: { + receiverId, + signal: JSON.stringify(signal), + }, + }, + }).finish(); - const clientToServerMessage = new ClientToServerMessage(); - clientToServerMessage.setWebrtcsignaltoservermessage(webRtcSignal); - - this.socket.send(clientToServerMessage.serializeBinary().buffer); + this.socket.send(bytes); } public sendWebrtcScreenSharingSignal(signal: unknown, receiverId: number) { - const webRtcSignal = new WebRtcSignalToServerMessage(); - webRtcSignal.setReceiverid(receiverId); - webRtcSignal.setSignal(JSON.stringify(signal)); + const bytes = ClientToServerMessageTsProto.encode({ + message: { + $case: "webRtcScreenSharingSignalToServerMessage", + webRtcScreenSharingSignalToServerMessage: { + receiverId, + signal: JSON.stringify(signal), + }, + }, + }).finish(); - const clientToServerMessage = new ClientToServerMessage(); - clientToServerMessage.setWebrtcscreensharingsignaltoservermessage(webRtcSignal); - - this.socket.send(clientToServerMessage.serializeBinary().buffer); - } - - public receiveWebrtcStart(callback: (message: UserSimplePeerInterface) => void) { - this.onMessage(EventMessage.WEBRTC_START, (message: WebRtcStartMessage) => { - callback({ - userId: message.getUserid(), - initiator: message.getInitiator(), - webRtcUser: message.getWebrtcusername() ?? undefined, - webRtcPassword: message.getWebrtcpassword() ?? undefined, - }); - }); - } - - public receiveWebrtcSignal(callback: (message: WebRtcSignalReceivedMessageInterface) => void) { - this.onMessage(EventMessage.WEBRTC_SIGNAL, (message: WebRtcSignalToClientMessage) => { - callback({ - userId: message.getUserid(), - signal: JSON.parse(message.getSignal()), - webRtcUser: message.getWebrtcusername() ?? undefined, - webRtcPassword: message.getWebrtcpassword() ?? undefined, - }); - }); - } - - public receiveWebrtcScreenSharingSignal(callback: (message: WebRtcSignalReceivedMessageInterface) => void) { - this.onMessage(EventMessage.WEBRTC_SCREEN_SHARING_SIGNAL, (message: WebRtcSignalToClientMessage) => { - callback({ - userId: message.getUserid(), - signal: JSON.parse(message.getSignal()), - webRtcUser: message.getWebrtcusername() ?? undefined, - webRtcPassword: message.getWebrtcpassword() ?? undefined, - }); - }); + this.socket.send(bytes); } public onServerDisconnected(callback: () => void): void { @@ -594,61 +713,34 @@ export class RoomConnection implements RoomConnection { return this.userId; } - disconnectMessage(callback: (message: WebRtcDisconnectMessageInterface) => void): void { - this.onMessage(EventMessage.WEBRTC_DISCONNECT, (message: WebRtcDisconnectMessage) => { - callback({ - userId: message.getUserid(), - }); - }); - } - emitActionableEvent(itemId: number, event: string, state: unknown, parameters: unknown): void { - const itemEventMessage = new ItemEventMessage(); - itemEventMessage.setItemid(itemId); - itemEventMessage.setEvent(event); - itemEventMessage.setStatejson(JSON.stringify(state)); - itemEventMessage.setParametersjson(JSON.stringify(parameters)); + const bytes = ClientToServerMessageTsProto.encode({ + message: { + $case: "itemEventMessage", + itemEventMessage: { + itemId, + event, + stateJson: JSON.stringify(state), + parametersJson: JSON.stringify(parameters), + }, + }, + }).finish(); - const clientToServerMessage = new ClientToServerMessage(); - clientToServerMessage.setItemeventmessage(itemEventMessage); - - this.socket.send(clientToServerMessage.serializeBinary().buffer); + this.socket.send(bytes); } emitSetVariableEvent(name: string, value: unknown): void { - const variableMessage = new VariableMessage(); - variableMessage.setName(name); - variableMessage.setValue(JSON.stringify(value)); + const bytes = ClientToServerMessageTsProto.encode({ + message: { + $case: "variableMessage", + variableMessage: { + name, + value: JSON.stringify(value), + }, + }, + }).finish(); - const clientToServerMessage = new ClientToServerMessage(); - clientToServerMessage.setVariablemessage(variableMessage); - - this.socket.send(clientToServerMessage.serializeBinary().buffer); - } - - onActionableEvent(callback: (message: ItemEventMessageInterface) => void): void { - this.onMessage(EventMessage.ITEM_EVENT, (message: ItemEventMessage) => { - callback({ - itemId: message.getItemid(), - event: message.getEvent(), - parameters: JSON.parse(message.getParametersjson()), - state: JSON.parse(message.getStatejson()), - }); - }); - } - - onPlayerDetailsUpdated(callback: (message: PlayerDetailsUpdatedMessageInterface) => void): void { - this.onMessage(EventMessage.USER_DETAILS_UPDATED, (message: PlayerDetailsUpdatedMessage) => { - const details = message.getDetails(); - if (details === undefined) { - throw new Error("Malformed message. Missing details in PlayerDetailsUpdatedMessage"); - } - callback({ - userId: message.getUserid(), - outlineColor: details.getOutlinecolor(), - removeOutlineColor: details.getRemoveoutlinecolor(), - }); - }); + this.socket.send(bytes); } public uploadAudio(file: FormData) { @@ -662,91 +754,48 @@ export class RoomConnection implements RoomConnection { }); } - /* public receivePlayGlobalMessage(callback: (message: PlayGlobalMessageInterface) => void) { - return this.onMessage(EventMessage.PLAY_GLOBAL_MESSAGE, (message: PlayGlobalMessage) => { - callback({ - id: message.getId(), - type: message.getType(), - message: message.getMessage(), - }); - }); - }*/ - - public receiveStopGlobalMessage(callback: (messageId: string) => void) { - return this.onMessage(EventMessage.STOP_GLOBAL_MESSAGE, (message: StopGlobalMessage) => { - callback(message.getId()); - }); - } - - public receiveTeleportMessage(callback: (messageId: string) => void) { - return this.onMessage(EventMessage.TELEPORT, (message: TeleportMessageMessage) => { - callback(message.getMap()); - }); - } - public emitGlobalMessage(message: PlayGlobalMessageInterface): void { - const playGlobalMessage = new PlayGlobalMessage(); - playGlobalMessage.setType(message.type); - playGlobalMessage.setContent(message.content); - playGlobalMessage.setBroadcasttoworld(message.broadcastToWorld); + const bytes = ClientToServerMessageTsProto.encode({ + message: { + $case: "playGlobalMessage", + playGlobalMessage: { + type: message.type, + content: message.content, + broadcastToWorld: message.broadcastToWorld, + }, + }, + }).finish(); - const clientToServerMessage = new ClientToServerMessage(); - clientToServerMessage.setPlayglobalmessage(playGlobalMessage); - - this.socket.send(clientToServerMessage.serializeBinary().buffer); + this.socket.send(bytes); } public emitReportPlayerMessage(reportedUserUuid: string, reportComment: string): void { - const reportPlayerMessage = new ReportPlayerMessage(); - reportPlayerMessage.setReporteduseruuid(reportedUserUuid); - reportPlayerMessage.setReportcomment(reportComment); + const bytes = ClientToServerMessageTsProto.encode({ + message: { + $case: "reportPlayerMessage", + reportPlayerMessage: { + reportedUserUuid, + reportComment, + }, + }, + }).finish(); - const clientToServerMessage = new ClientToServerMessage(); - clientToServerMessage.setReportplayermessage(reportPlayerMessage); - - this.socket.send(clientToServerMessage.serializeBinary().buffer); + this.socket.send(bytes); } public emitQueryJitsiJwtMessage(jitsiRoom: string, tag: string | undefined): void { - const queryJitsiJwtMessage = new QueryJitsiJwtMessage(); - queryJitsiJwtMessage.setJitsiroom(jitsiRoom); - if (tag !== undefined) { - queryJitsiJwtMessage.setTag(tag); - } + const bytes = ClientToServerMessageTsProto.encode({ + message: { + $case: "queryJitsiJwtMessage", + queryJitsiJwtMessage: { + jitsiRoom, + tag: tag ?? "", // empty string is sent as "undefined" by ts-proto + // TODO: when we migrated "pusher" to ts-proto, migrate this to a StringValue + }, + }, + }).finish(); - const clientToServerMessage = new ClientToServerMessage(); - clientToServerMessage.setQueryjitsijwtmessage(queryJitsiJwtMessage); - - this.socket.send(clientToServerMessage.serializeBinary().buffer); - } - - public onStartJitsiRoom(callback: (jwt: string, room: string) => void): void { - this.onMessage(EventMessage.START_JITSI_ROOM, (message: SendJitsiJwtMessage) => { - callback(message.getJwt(), message.getJitsiroom()); - }); - } - - public onSetVariable(callback: (name: string, value: unknown) => void): void { - this.onMessage(EventMessage.SET_VARIABLE, (message: VariableMessage) => { - const name = message.getName(); - const serializedValue = message.getValue(); - let value: unknown = undefined; - if (serializedValue) { - try { - value = JSON.parse(serializedValue); - } catch (e) { - console.error( - 'Unable to unserialize value received from server for variable "' + - name + - '". Value received: "' + - serializedValue + - '". Error: ', - e - ); - } - } - callback(name, value); - }); + this.socket.send(bytes); } public hasTag(tag: string): boolean { @@ -758,36 +807,51 @@ export class RoomConnection implements RoomConnection { } public emitEmoteEvent(emoteName: string): void { - const emoteMessage = new EmotePromptMessage(); - emoteMessage.setEmote(emoteName); + const bytes = ClientToServerMessageTsProto.encode({ + message: { + $case: "emotePromptMessage", + emotePromptMessage: { + emote: emoteName, + }, + }, + }).finish(); - const clientToServerMessage = new ClientToServerMessage(); - clientToServerMessage.setEmotepromptmessage(emoteMessage); - - this.socket.send(clientToServerMessage.serializeBinary().buffer); + this.socket.send(bytes); } public emitFollowRequest(): void { if (!this.userId) { return; } - const message = new FollowRequestMessage(); - message.setLeader(this.userId); - const clientToServerMessage = new ClientToServerMessage(); - clientToServerMessage.setFollowrequestmessage(message); - this.socket.send(clientToServerMessage.serializeBinary().buffer); + + const bytes = ClientToServerMessageTsProto.encode({ + message: { + $case: "followRequestMessage", + followRequestMessage: { + leader: this.userId, + }, + }, + }).finish(); + + this.socket.send(bytes); } public emitFollowConfirmation(): void { if (!this.userId) { return; } - const message = new FollowConfirmationMessage(); - message.setLeader(get(followUsersStore)[0]); - message.setFollower(this.userId); - const clientToServerMessage = new ClientToServerMessage(); - clientToServerMessage.setFollowconfirmationmessage(message); - this.socket.send(clientToServerMessage.serializeBinary().buffer); + + const bytes = ClientToServerMessageTsProto.encode({ + message: { + $case: "followConfirmationMessage", + followConfirmationMessage: { + leader: get(followUsersStore)[0], + follower: this.userId, + }, + }, + }).finish(); + + this.socket.send(bytes); } public emitFollowAbort(): void { @@ -796,12 +860,18 @@ export class RoomConnection implements RoomConnection { if (!this.userId || (isLeader && !hasFollowers)) { return; } - const message = new FollowAbortMessage(); - message.setLeader(isLeader ? this.userId : get(followUsersStore)[0]); - message.setFollower(isLeader ? 0 : this.userId); - const clientToServerMessage = new ClientToServerMessage(); - clientToServerMessage.setFollowabortmessage(message); - this.socket.send(clientToServerMessage.serializeBinary().buffer); + + const bytes = ClientToServerMessageTsProto.encode({ + message: { + $case: "followAbortMessage", + followAbortMessage: { + leader: isLeader ? this.userId : get(followUsersStore)[0], + follower: isLeader ? 0 : this.userId, + }, + }, + }).finish(); + + this.socket.send(bytes); } public getAllTags(): string[] { diff --git a/front/src/Connexion/WorldFullMessageStream.ts b/front/src/Connexion/WorldFullMessageStream.ts deleted file mode 100644 index 01ce6f20..00000000 --- a/front/src/Connexion/WorldFullMessageStream.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Subject } from "rxjs"; - -class WorldFullMessageStream { - private _stream: Subject = new Subject(); - public stream = this._stream.asObservable(); - - onMessage(message?: string) { - this._stream.next(message); - } -} - -export const worldFullMessageStream = new WorldFullMessageStream(); diff --git a/front/src/Messages/.gitignore b/front/src/Messages/.gitignore deleted file mode 100644 index 9e0adcc1..00000000 --- a/front/src/Messages/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/generated/ diff --git a/front/src/Messages/ts-proto-generated/.gitignore b/front/src/Messages/ts-proto-generated/.gitignore new file mode 100644 index 00000000..72e8ffc0 --- /dev/null +++ b/front/src/Messages/ts-proto-generated/.gitignore @@ -0,0 +1 @@ +* diff --git a/front/src/Network/ProtobufClientUtils.ts b/front/src/Network/ProtobufClientUtils.ts index 9ba0f40b..3e172d0f 100644 --- a/front/src/Network/ProtobufClientUtils.ts +++ b/front/src/Network/ProtobufClientUtils.ts @@ -1,21 +1,21 @@ -import { PositionMessage } from "../Messages/generated/messages_pb"; -import Direction = PositionMessage.Direction; +import { PositionMessage, PositionMessage_Direction } from "../Messages/ts-proto-generated/messages"; + import type { PointInterface } from "../Connexion/ConnexionModels"; export class ProtobufClientUtils { public static toPointInterface(position: PositionMessage): PointInterface { let direction: string; - switch (position.getDirection()) { - case Direction.UP: + switch (position.direction) { + case PositionMessage_Direction.UP: direction = "up"; break; - case Direction.DOWN: + case PositionMessage_Direction.DOWN: direction = "down"; break; - case Direction.LEFT: + case PositionMessage_Direction.LEFT: direction = "left"; break; - case Direction.RIGHT: + case PositionMessage_Direction.RIGHT: direction = "right"; break; default: @@ -24,10 +24,10 @@ export class ProtobufClientUtils { // sending to all clients in room except sender return { - x: position.getX(), - y: position.getY(), + x: position.x, + y: position.y, direction, - moving: position.getMoving(), + moving: position.moving, }; } } diff --git a/front/src/Phaser/Game/EmoteManager.ts b/front/src/Phaser/Game/EmoteManager.ts index 06e8b099..097ebf45 100644 --- a/front/src/Phaser/Game/EmoteManager.ts +++ b/front/src/Phaser/Game/EmoteManager.ts @@ -1,13 +1,13 @@ -import { emoteEventStream } from "../../Connexion/EmoteEventStream"; import type { GameScene } from "./GameScene"; import type { Subscription } from "rxjs"; +import type { RoomConnection } from "../../Connexion/RoomConnection"; export class EmoteManager { private subscription: Subscription; - constructor(private scene: GameScene) { - this.subscription = emoteEventStream.stream.subscribe((event) => { - const actor = this.scene.MapPlayersByKey.get(event.userId); + constructor(private scene: GameScene, private connection: RoomConnection) { + this.subscription = connection.emoteEventMessageStream.subscribe((event) => { + const actor = this.scene.MapPlayersByKey.get(event.actorUserId); if (actor) { actor.playEmote(event.emote); } diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 4800e259..bab589e1 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -40,7 +40,6 @@ import { ReconnectingSceneName } from "../Reconnecting/ReconnectingScene"; import { GameMap } from "./GameMap"; import { PlayerMovement } from "./PlayerMovement"; import { PlayersPositionInterpolator } from "./PlayersPositionInterpolator"; -import { worldFullMessageStream } from "../../Connexion/WorldFullMessageStream"; import { DirtyScene } from "./DirtyScene"; import { TextUtils } from "../Components/TextUtils"; import { joystickBaseImg, joystickBaseKey, joystickThumbImg, joystickThumbKey } from "../Components/MobileJoystick"; @@ -60,7 +59,6 @@ import type { PositionInterface, RoomJoinedMessageInterface, } from "../../Connexion/ConnexionModels"; -import type { UserMovedMessage } from "../../Messages/generated/messages_pb"; import type { RoomConnection } from "../../Connexion/RoomConnection"; import type { ActionableItem } from "../Items/ActionableItem"; import type { ItemFactoryInterface } from "../Items/ItemFactoryInterface"; @@ -90,7 +88,6 @@ import SpriteSheetFile = Phaser.Loader.FileTypes.SpriteSheetFile; import { deepCopy } from "deep-copy-ts"; import FILE_LOAD_ERROR = Phaser.Loader.Events.FILE_LOAD_ERROR; import { MapStore } from "../../Stores/Utils/MapStore"; -import { SetPlayerDetailsMessage } from "../../Messages/generated/messages_pb"; import { followUsersColorStore, followUsersStore } from "../../Stores/FollowStore"; import { getColorRgbFromHue } from "../../WebRtc/ColorGenerator"; @@ -448,10 +445,6 @@ export class GameScene extends DirtyScene { this.pinchManager = new PinchManager(this); } - this.messageSubscription = worldFullMessageStream.stream.subscribe((message) => - this.showWorldFullError(message) - ); - const playerName = gameManager.getPlayerName(); if (!playerName) { throw "playerName is not set"; @@ -617,8 +610,6 @@ export class GameScene extends DirtyScene { this.connect(); } - this.emoteManager = new EmoteManager(this); - let oldPeerNumber = 0; this.peerStoreUnsubscribe = peerStore.subscribe((peers) => { const newPeerNumber = peers.size; @@ -693,7 +684,7 @@ export class GameScene extends DirtyScene { playersStore.connectToRoomConnection(this.connection); userIsAdminStore.set(this.connection.hasTag("admin")); - this.connection.onUserJoins((message: MessageUserJoined) => { + this.connection.userJoinedMessageStream.subscribe((message) => { const userMessage: AddPlayerInterface = { userId: message.userId, characterLayers: message.characterLayers, @@ -707,31 +698,33 @@ export class GameScene extends DirtyScene { this.addPlayer(userMessage); }); - this.connection.onUserMoved((message: UserMovedMessage) => { - const position = message.getPosition(); + this.connection.userMovedMessageStream.subscribe((message) => { + const position = message.position; if (position === undefined) { throw new Error("Position missing from UserMovedMessage"); } const messageUserMoved: MessageUserMovedInterface = { - userId: message.getUserid(), + userId: message.userId, position: ProtobufClientUtils.toPointInterface(position), }; this.updatePlayerPosition(messageUserMoved); }); - this.connection.onUserLeft((userId: number) => { - this.removePlayer(userId); + this.connection.userLeftMessageStream.subscribe((message) => { + this.removePlayer(message.userId); }); - this.connection.onGroupUpdatedOrCreated((groupPositionMessage: GroupCreatedUpdatedMessageInterface) => { - this.shareGroupPosition(groupPositionMessage); - }); + this.connection.groupUpdateMessageStream.subscribe( + (groupPositionMessage: GroupCreatedUpdatedMessageInterface) => { + this.shareGroupPosition(groupPositionMessage); + } + ); - this.connection.onGroupDeleted((groupId: number) => { + this.connection.groupDeleteMessageStream.subscribe((message) => { try { - this.deleteGroup(groupId); + this.deleteGroup(message.groupId); } catch (e) { console.error(e); } @@ -743,7 +736,7 @@ export class GameScene extends DirtyScene { this.createSuccessorGameScene(true, true); }); - this.connection.onActionableEvent((message) => { + this.connection.itemEventMessageStream.subscribe((message) => { const item = this.actionableItems.get(message.itemId); if (item === undefined) { console.warn( @@ -756,18 +749,29 @@ export class GameScene extends DirtyScene { item.fire(message.event, message.state, message.parameters); }); - this.connection.onPlayerDetailsUpdated((message) => { + this.connection.playerDetailsUpdatedMessageStream.subscribe((message) => { + if (message.details === undefined) { + throw new Error("Malformed message. Missing details in PlayerDetailsUpdatedMessage"); + } this.pendingEvents.enqueue({ type: "PlayerDetailsUpdated", - details: message, + details: { + userId: message.userId, + outlineColor: message.details.outlineColor, + removeOutlineColor: message.details.removeOutlineColor, + }, }); }); /** * Triggered when we receive the JWT token to connect to Jitsi */ - this.connection.onStartJitsiRoom((jwt, room) => { - this.startJitsi(room, jwt); + this.connection.sendJitsiJwtMessageStream.subscribe((message) => { + this.startJitsi(message.jitsiRoom, message.jwt); + }); + + this.messageSubscription = this.connection.worldFullMessageStream.subscribe((message) => { + this.showWorldFullError(message); }); // When connection is performed, let's connect SimplePeer @@ -842,6 +846,8 @@ export class GameScene extends DirtyScene { }); }); + this.emoteManager = new EmoteManager(this, this.connection); + // this.gameMap.onLeaveLayer((layers) => { // layers.forEach((layer) => { // iframeListener.sendLeaveLayerEvent(layer.name); diff --git a/front/src/Phaser/Game/SharedVariablesManager.ts b/front/src/Phaser/Game/SharedVariablesManager.ts index 5b5867dc..62d39d1a 100644 --- a/front/src/Phaser/Game/SharedVariablesManager.ts +++ b/front/src/Phaser/Game/SharedVariablesManager.ts @@ -41,7 +41,7 @@ export class SharedVariablesManager { this._variables.set(name, value); } - roomConnection.onSetVariable((name, value) => { + roomConnection.variableMessageStream.subscribe(({ name, value }) => { this._variables.set(name, value); // On server change, let's notify the iframes diff --git a/front/src/Stores/PlayersStore.ts b/front/src/Stores/PlayersStore.ts index 07c18b96..0676235a 100644 --- a/front/src/Stores/PlayersStore.ts +++ b/front/src/Stores/PlayersStore.ts @@ -3,6 +3,7 @@ import type { PlayerInterface } from "../Phaser/Game/PlayerInterface"; import type { RoomConnection } from "../Connexion/RoomConnection"; import { getRandomColor } from "../WebRtc/ColorGenerator"; import { localUserStore } from "../Connexion/LocalUserStore"; +import room from "../Api/iframe/room"; let idCount = 0; @@ -19,7 +20,8 @@ function createPlayersStore() { connectToRoomConnection: (roomConnection: RoomConnection) => { players = new Map(); set(players); - roomConnection.onUserJoins((message) => { + // TODO: it would be cool to unsubscribe properly here + roomConnection.userJoinedMessageStream.subscribe((message) => { update((users) => { users.set(message.userId, { userId: message.userId, @@ -33,9 +35,9 @@ function createPlayersStore() { return users; }); }); - roomConnection.onUserLeft((userId) => { + roomConnection.userLeftMessageStream.subscribe((message) => { update((users) => { - users.delete(userId); + users.delete(message.userId); return users; }); }); diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index ccbd0012..f4016015 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -75,23 +75,25 @@ export class SimplePeer { */ private initialise() { //receive signal by gemer - this.Connection.receiveWebrtcSignal((message: WebRtcSignalReceivedMessageInterface) => { + this.Connection.webRtcSignalToClientMessageStream.subscribe((message: WebRtcSignalReceivedMessageInterface) => { this.receiveWebrtcSignal(message); }); //receive signal by gemer - this.Connection.receiveWebrtcScreenSharingSignal((message: WebRtcSignalReceivedMessageInterface) => { - this.receiveWebrtcScreenSharingSignal(message); - }); + this.Connection.webRtcScreenSharingSignalToClientMessageStream.subscribe( + (message: WebRtcSignalReceivedMessageInterface) => { + this.receiveWebrtcScreenSharingSignal(message); + } + ); mediaManager.showGameOverlay(); //receive message start - this.Connection.receiveWebrtcStart((message: UserSimplePeerInterface) => { + this.Connection.webRtcStartMessageStream.subscribe((message: UserSimplePeerInterface) => { this.receiveWebrtcStart(message); }); - this.Connection.disconnectMessage((data: WebRtcDisconnectMessageInterface): void => { + this.Connection.webRtcDisconnectMessageStream.subscribe((data: WebRtcDisconnectMessageInterface): void => { this.closeConnection(data.userId); }); } diff --git a/front/yarn.lock b/front/yarn.lock index 1557a08e..faf5e681 100644 --- a/front/yarn.lock +++ b/front/yarn.lock @@ -150,6 +150,59 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.2.tgz#adea7b6953cbb34651766b0548468e743c6a2353" integrity sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q== +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78= + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A= + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU= + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E= + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik= + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0= + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q= + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= + "@sentry/types@^6.11.0": version "6.12.0" resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.12.0.tgz#b7395688a79403c6df8d8bb8d81deb8222519853" @@ -293,6 +346,11 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/long@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" + integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== + "@types/mime@^1": version "1.3.2" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" @@ -317,11 +375,26 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-15.3.0.tgz#d6fed7d6bc6854306da3dea1af9f874b00783e26" integrity sha512-8/bnjSZD86ZfpBsDlCIkNXIvm+h6wi9g7IqL+kmFkQ+Wvu3JrasgLElfiPgoo8V8vVfnEi0QVS12gbl94h9YsQ== +"@types/node@>=13.7.0": + version "17.0.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.5.tgz#57ca67ec4e57ad9e4ef5a6bab48a15387a1c83e0" + integrity sha512-w3mrvNXLeDYV1GKTZorGJQivK6XLCoGwpnyJFbJVK/aTBQUxOCaa/GlFAAN3OTDFcb7h5tiFG+YXCO2By+riZw== + +"@types/object-hash@^1.3.0": + version "1.3.4" + resolved "https://registry.yarnpkg.com/@types/object-hash/-/object-hash-1.3.4.tgz#079ba142be65833293673254831b5e3e847fe58b" + integrity sha512-xFdpkAkikBgqBdG9vIlsqffDV8GpvnPEzs0IUtr1v3BEB97ijsFQ4RXVbUZwjFThhB4MDSTUfvmxUD5PGx0wXA== + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/prettier@^1.19.0": + version "1.19.1" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.19.1.tgz#33509849f8e679e4add158959fdb086440e9553f" + integrity sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ== + "@types/pug@^2.0.4": version "2.0.4" resolved "https://registry.yarnpkg.com/@types/pug/-/pug-2.0.4.tgz#8772fcd0418e3cd2cc171555d73007415051f4b2" @@ -1656,6 +1729,11 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== +dataloader@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-1.4.0.tgz#bca11d867f5d3f1b9ed9f737bd15970c65dff5c8" + integrity sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw== + debug@2.6.9, debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -3662,7 +3740,7 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.20: +lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -3695,6 +3773,11 @@ lokijs@^1.5.12: resolved "https://registry.yarnpkg.com/lokijs/-/lokijs-1.5.12.tgz#cb55b37009bdf09ee7952a6adddd555b893653a0" integrity sha512-Q5ALD6JiS6xAUWCwX3taQmgwxyveCtIIuL08+ml0nHwT3k0S/GIFJN+Hd38b1qYIMaE5X++iqsqWVksz7SYW+Q== +long@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + lower-case@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" @@ -4098,6 +4181,11 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" +object-hash@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df" + integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA== + object-inspect@^1.9.0: version "1.10.3" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369" @@ -4590,6 +4678,11 @@ prettier-plugin-svelte@^2.5.0: resolved "https://registry.yarnpkg.com/prettier-plugin-svelte/-/prettier-plugin-svelte-2.5.0.tgz#7922534729f7febe59b4c56c3f5360539f0d8ab1" integrity sha512-+iHY2uGChOngrgKielJUnqo74gIL/EO5oeWm8MftFWjEi213lq9QYTOwm1pv4lI1nA61tdgf80CF2i5zMcu1kw== +prettier@^2.0.2: + version "2.5.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a" + integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg== + prettier@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.1.tgz#76903c3f8c4449bc9ac597acefa24dc5ad4cbea6" @@ -4618,6 +4711,25 @@ progress@^2.0.0: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== +protobufjs@^6.8.8: + version "6.11.2" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.2.tgz#de39fabd4ed32beaa08e9bb1e30d08544c1edf8b" + integrity sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/long" "^4.0.1" + "@types/node" ">=13.7.0" + long "^4.0.0" + proxy-addr@~2.0.5: version "2.0.6" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" @@ -5819,6 +5931,35 @@ ts-node@^10.4.0: make-error "^1.1.1" yn "3.1.1" +ts-poet@^4.5.0: + version "4.6.1" + resolved "https://registry.yarnpkg.com/ts-poet/-/ts-poet-4.6.1.tgz#015dc823d726655af9f095c900f84ed7c60e2dd3" + integrity sha512-DXJ+mBJIDp+jiaUgB4N5I/sczHHDU2FWacdbDNVAVS4Mh4hb7ckpvUWVW7m7/nAOcjR0r4Wt+7AoO7FeJKExfA== + dependencies: + "@types/prettier" "^1.19.0" + lodash "^4.17.15" + prettier "^2.0.2" + +ts-proto-descriptors@^1.2.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/ts-proto-descriptors/-/ts-proto-descriptors-1.3.1.tgz#760ebaaa19475b03662f7b358ffea45b9c5348f5" + integrity sha512-Cybb3fqceMwA6JzHdC32dIo8eVGVmXrM6TWhdk1XQVVHT/6OQqk0ioyX1dIdu3rCIBhRmWUhUE4HsyK+olmgMw== + dependencies: + long "^4.0.0" + protobufjs "^6.8.8" + +ts-proto@^1.96.0: + version "1.96.0" + resolved "https://registry.yarnpkg.com/ts-proto/-/ts-proto-1.96.0.tgz#63768d7da533b337aee84db065dd66773bd4cac9" + integrity sha512-fKwaGzi8EOCU9xwmcXK917jj1WhFdLbFkPRawQ+5CAZM9eSXr/mpkz/yEctXCiuei364z6jAB2Odb64KCDFTPQ== + dependencies: + "@types/object-hash" "^1.3.0" + dataloader "^1.4.0" + object-hash "^1.3.1" + protobufjs "^6.8.8" + ts-poet "^4.5.0" + ts-proto-descriptors "^1.2.1" + tsconfig-paths@^3.9.0: version "3.9.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" diff --git a/messages/package.json b/messages/package.json index 6095a626..e6f04ba5 100644 --- a/messages/package.json +++ b/messages/package.json @@ -4,13 +4,14 @@ "description": "", "scripts": { "proto": "grpc_tools_node_protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --grpc_out=generated --js_out=\"import_style=commonjs,binary:generated\" --ts_out=generated -I ./protos protos/*.proto", + "ts-proto": "grpc_tools_node_protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_out=ts-proto-generated --ts_proto_opt=oneof=unions --ts_proto_opt=esModuleInterop=true protos/*.proto", "copy-to-back": "rm -rf ../back/src/Messages/generated && cp -rf generated/ ../back/src/Messages/generated", - "copy-to-front": "rm -rf ../front/src/Messages/generated && cp -rf generated/ ../front/src/Messages/generated", + "copy-to-front-ts-proto": "sed 's/import { Observable } from \"rxjs\";/import type { Observable } from \"rxjs\";/g' ts-proto-generated/protos/messages.ts > ../front/src/Messages/ts-proto-generated/messages.ts", "copy-to-pusher": "rm -rf ../pusher/src/Messages/generated && cp -rf generated/ ../pusher/src/Messages/generated", "json-copy-to-pusher": "rm -rf ../pusher/src/Messages/JsonMessages/* && cp -rf JsonMessages/* ../pusher/src/Messages/JsonMessages/", "json-copy-to-front": "rm -rf ../front/src/Messages/JsonMessages/* && cp -rf JsonMessages/* ../front/src/Messages/JsonMessages/", "precommit": "lint-staged", - "proto-all": "yarn run proto && yarn run copy-to-back && yarn run copy-to-front && yarn run copy-to-pusher && yarn run json-copy-to-pusher && yarn run json-copy-to-front", + "proto-all": "yarn run proto && yarn run ts-proto && yarn run copy-to-back && yarn run copy-to-front-ts-proto && yarn run copy-to-pusher && yarn run json-copy-to-pusher && yarn run json-copy-to-front", "proto:watch": "yarn run proto-all; inotifywait -q -m -e close_write protos/messages.proto JsonMessages/ | while read -r filename event; do yarn run proto-all; done", "pretty": "yarn prettier --write 'JsonMessages/**/*.ts'", "pretty-check": "yarn prettier --check 'JsonMessages/**/*.ts'" @@ -18,7 +19,8 @@ "dependencies": { "generic-type-guard": "^3.5.0", "google-protobuf": "^3.13.0", - "grpc": "^1.24.4" + "grpc": "^1.24.4", + "ts-proto": "^1.96.0" }, "devDependencies": { "@types/google-protobuf": "^3.7.4", diff --git a/messages/ts-proto-generated/.gitignore b/messages/ts-proto-generated/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/messages/ts-proto-generated/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/messages/yarn.lock b/messages/yarn.lock index 5d040c6f..8ba2fe90 100644 --- a/messages/yarn.lock +++ b/messages/yarn.lock @@ -174,6 +174,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.7.tgz#8ea1e8f8eae2430cf440564b98c6dfce1ec5945d" integrity sha512-Zw1vhUSQZYw+7u5dAwNbIA9TuTotpzY/OF7sJM9FqPOF3SPjKnxrjoTktXDZgUjybf4cWVBP7O8wvKdSaGHweg== +"@types/node@>=13.7.0": + version "17.0.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.5.tgz#57ca67ec4e57ad9e4ef5a6bab48a15387a1c83e0" + integrity sha512-w3mrvNXLeDYV1GKTZorGJQivK6XLCoGwpnyJFbJVK/aTBQUxOCaa/GlFAAN3OTDFcb7h5tiFG+YXCO2By+riZw== + "@types/node@^12.12.29": version "12.19.4" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.19.4.tgz#cdfbb62e26c7435ed9aab9c941393cc3598e9b46" @@ -184,6 +189,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.30.tgz#1ed6e01e4ca576d5aec9cc802cc3bcf94c274192" integrity sha512-HmqFpNzp3TSELxU/bUuRK+xzarVOAsR00hzcvM0TXrMlt/+wcSLa5q6YhTb6/cA6wqDCZLDcfd8fSL95x5h7AA== +"@types/object-hash@^1.3.0": + version "1.3.4" + resolved "https://registry.yarnpkg.com/@types/object-hash/-/object-hash-1.3.4.tgz#079ba142be65833293673254831b5e3e847fe58b" + integrity sha512-xFdpkAkikBgqBdG9vIlsqffDV8GpvnPEzs0IUtr1v3BEB97ijsFQ4RXVbUZwjFThhB4MDSTUfvmxUD5PGx0wXA== + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" @@ -194,6 +204,11 @@ resolved "https://registry.yarnpkg.com/@types/parsimmon/-/parsimmon-1.10.4.tgz#7639e16015440d9baf622f83c12dae47787226b7" integrity sha512-M56NfQHfaWuaj6daSgCVs7jh8fXLI3LmxjRoQxmOvYesgIkI+9HPsDLO0vd7wX7cwA0D0ZWFEJdp0VPwLdS+bQ== +"@types/prettier@^1.19.0": + version "1.19.1" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.19.1.tgz#33509849f8e679e4add158959fdb086440e9553f" + integrity sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ== + "@typescript-eslint/eslint-plugin@^4.7.0": version "4.7.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.7.0.tgz#85c9bbda00c0cb604d3c241f7bc7fb171a2d3479" @@ -1156,6 +1171,11 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" +dataloader@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-1.4.0.tgz#bca11d867f5d3f1b9ed9f737bd15970c65dff5c8" + integrity sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw== + date.js@^0.3.1: version "0.3.3" resolved "https://registry.yarnpkg.com/date.js/-/date.js-0.3.3.tgz#ef1e92332f507a638795dbb985e951882e50bbda" @@ -3154,6 +3174,11 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" +object-hash@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df" + integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA== + object-inspect@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" @@ -3423,7 +3448,7 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prettier@^2.3.1: +prettier@^2.0.2, prettier@^2.3.1: version "2.5.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a" integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg== @@ -3467,6 +3492,25 @@ protobufjs@^6.10.1: "@types/node" "^13.7.0" long "^4.0.0" +protobufjs@^6.8.8: + version "6.11.2" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.2.tgz#de39fabd4ed32beaa08e9bb1e30d08544c1edf8b" + integrity sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/long" "^4.0.1" + "@types/node" ">=13.7.0" + long "^4.0.0" + psl@^1.1.28: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" @@ -4245,6 +4289,35 @@ tough-cookie@~2.5.0: psl "^1.1.28" punycode "^2.1.1" +ts-poet@^4.5.0: + version "4.6.1" + resolved "https://registry.yarnpkg.com/ts-poet/-/ts-poet-4.6.1.tgz#015dc823d726655af9f095c900f84ed7c60e2dd3" + integrity sha512-DXJ+mBJIDp+jiaUgB4N5I/sczHHDU2FWacdbDNVAVS4Mh4hb7ckpvUWVW7m7/nAOcjR0r4Wt+7AoO7FeJKExfA== + dependencies: + "@types/prettier" "^1.19.0" + lodash "^4.17.15" + prettier "^2.0.2" + +ts-proto-descriptors@^1.2.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/ts-proto-descriptors/-/ts-proto-descriptors-1.3.1.tgz#760ebaaa19475b03662f7b358ffea45b9c5348f5" + integrity sha512-Cybb3fqceMwA6JzHdC32dIo8eVGVmXrM6TWhdk1XQVVHT/6OQqk0ioyX1dIdu3rCIBhRmWUhUE4HsyK+olmgMw== + dependencies: + long "^4.0.0" + protobufjs "^6.8.8" + +ts-proto@^1.96.0: + version "1.96.0" + resolved "https://registry.yarnpkg.com/ts-proto/-/ts-proto-1.96.0.tgz#63768d7da533b337aee84db065dd66773bd4cac9" + integrity sha512-fKwaGzi8EOCU9xwmcXK917jj1WhFdLbFkPRawQ+5CAZM9eSXr/mpkz/yEctXCiuei364z6jAB2Odb64KCDFTPQ== + dependencies: + "@types/object-hash" "^1.3.0" + dataloader "^1.4.0" + object-hash "^1.3.1" + protobufjs "^6.8.8" + ts-poet "^4.5.0" + ts-proto-descriptors "^1.2.1" + tsconfig-paths@^3.9.0: version "3.9.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" From 6bd9a998f97b6ad1bc40b29b50fd52a4c2cebbcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 3 Jan 2022 16:16:32 +0100 Subject: [PATCH 92/95] Removing unused messages --- back/src/RoomManager.ts | 5 ----- back/src/Services/SocketManager.ts | 9 --------- front/src/Connexion/RoomConnection.ts | 18 ------------------ messages/protos/messages.proto | 4 ---- 4 files changed, 36 deletions(-) diff --git a/back/src/RoomManager.ts b/back/src/RoomManager.ts index 3bb425b7..d375fbd8 100644 --- a/back/src/RoomManager.ts +++ b/back/src/RoomManager.ts @@ -106,11 +106,6 @@ const roomManager: IRoomManagerServer = { user, message.getWebrtcscreensharingsignaltoservermessage() as WebRtcSignalToServerMessage ); - } else if (message.hasPlayglobalmessage()) { - socketManager.emitPlayGlobalMessage( - room, - message.getPlayglobalmessage() as PlayGlobalMessage - ); } else if (message.hasQueryjitsijwtmessage()) { socketManager.handleQueryJitsiJwtMessage( user, diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index c9da7c96..9233811b 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -531,15 +531,6 @@ export class SocketManager { } } - emitPlayGlobalMessage(room: GameRoom, playGlobalMessage: PlayGlobalMessage) { - const serverToClientMessage = new ServerToClientMessage(); - serverToClientMessage.setPlayglobalmessage(playGlobalMessage); - - for (const [id, user] of room.getUsers().entries()) { - user.socket.write(serverToClientMessage); - } - } - public getWorlds(): Map> { return this.roomsPromises; } diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index b7aa30ce..96c6dd23 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -91,12 +91,6 @@ export class RoomConnection implements RoomConnection { private readonly _webRtcDisconnectMessageStream = new Subject(); public readonly webRtcDisconnectMessageStream = this._webRtcDisconnectMessageStream.asObservable(); - private readonly _playGlobalMessageStream = new Subject(); - public readonly playGlobalMessageStream = this._playGlobalMessageStream.asObservable(); - - private readonly _stopGlobalMessageStream = new Subject(); - public readonly stopGlobalMessageStream = this._stopGlobalMessageStream.asObservable(); - private readonly _teleportMessageMessageStream = new Subject(); public readonly teleportMessageMessageStream = this._teleportMessageMessageStream.asObservable(); @@ -410,18 +404,6 @@ export class RoomConnection implements RoomConnection { this._webRtcDisconnectMessageStream.next(message.webRtcDisconnectMessage); break; } - case "playGlobalMessage": { - // FIXME: WHY IS THIS UNUSED? CAN WE REMOVE THIS??? - // FIXME: WHY IS THIS UNUSED? CAN WE REMOVE THIS??? - this._playGlobalMessageStream.next(message.playGlobalMessage); - break; - } - case "stopGlobalMessage": { - // FIXME: WHY IS THIS UNUSED? CAN WE REMOVE THIS??? - // FIXME: WHY IS THIS UNUSED? CAN WE REMOVE THIS??? - this._stopGlobalMessageStream.next(message.stopGlobalMessage); - break; - } case "teleportMessageMessage": { // FIXME: WHY IS THIS UNUSED? CAN WE REMOVE THIS??? this._teleportMessageMessageStream.next(message.teleportMessageMessage.map); diff --git a/messages/protos/messages.proto b/messages/protos/messages.proto index 3c05037a..8ac7bbf0 100644 --- a/messages/protos/messages.proto +++ b/messages/protos/messages.proto @@ -296,8 +296,6 @@ message ServerToClientMessage { WebRtcSignalToClientMessage webRtcSignalToClientMessage = 5; WebRtcSignalToClientMessage webRtcScreenSharingSignalToClientMessage = 6; WebRtcDisconnectMessage webRtcDisconnectMessage = 7; - PlayGlobalMessage playGlobalMessage = 8; - StopGlobalMessage stopGlobalMessage = 9; TeleportMessageMessage teleportMessageMessage = 10; SendJitsiJwtMessage sendJitsiJwtMessage = 11; SendUserMessage sendUserMessage = 12; @@ -390,8 +388,6 @@ message PusherToBackMessage { SetPlayerDetailsMessage setPlayerDetailsMessage = 5; WebRtcSignalToServerMessage webRtcSignalToServerMessage = 6; WebRtcSignalToServerMessage webRtcScreenSharingSignalToServerMessage = 7; - PlayGlobalMessage playGlobalMessage = 8; - StopGlobalMessage stopGlobalMessage = 9; ReportPlayerMessage reportPlayerMessage = 10; QueryJitsiJwtMessage queryJitsiJwtMessage = 11; SendUserMessage sendUserMessage = 12; From 7b239e8ebbe28613a0c0587715bd6ca3b78f059b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 3 Jan 2022 16:28:49 +0100 Subject: [PATCH 93/95] Disabling running end to end tests for forks Github Actions does not provide big enough VMs for E2E tests. So we need to spin our own instances on AWS. But we cannot share the credentials with the forks for obvious security reasons. We therefore need to completely disable E2E tests for forks :( --- .github/workflows/end_to_end_tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/end_to_end_tests.yml b/.github/workflows/end_to_end_tests.yml index a1cb5e5d..ea9ba41c 100644 --- a/.github/workflows/end_to_end_tests.yml +++ b/.github/workflows/end_to_end_tests.yml @@ -12,6 +12,7 @@ on: jobs: start-runner: + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) name: Start self-hosted EC2 runner runs-on: ubuntu-latest outputs: @@ -109,12 +110,14 @@ jobs: if: ${{ always() }} # required to stop the runner even if the error happened in the previous jobs steps: - name: Configure AWS credentials + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ${{ secrets.AWS_REGION }} - name: Stop EC2 runner + if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) uses: machulav/ec2-github-runner@v2 with: mode: stop From 6e27ffb2d55ae4113458e25732adff07b192a038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Thu, 16 Dec 2021 18:18:55 +0100 Subject: [PATCH 94/95] Starting fixing unhandled promises --- front/.eslintrc.js | 1 - front/src/Administration/AnalyticsClient.ts | 20 +++++++++---------- front/src/Api/iframe/Ui/ActionMessage.ts | 2 +- front/src/Api/iframe/state.ts | 2 +- .../AudioManager/AudioManager.svelte | 2 +- .../Menu/GlobalMessagesSubMenu.svelte | 6 +++--- .../src/Components/Menu/ProfileSubMenu.svelte | 4 ++-- 7 files changed, 18 insertions(+), 19 deletions(-) diff --git a/front/.eslintrc.js b/front/.eslintrc.js index 117cb7e6..dc2b6bd6 100644 --- a/front/.eslintrc.js +++ b/front/.eslintrc.js @@ -35,7 +35,6 @@ module.exports = { "no-unused-vars": "off", "@typescript-eslint/no-explicit-any": "error", // TODO: remove those ignored rules and write a stronger code! - "@typescript-eslint/no-floating-promises": "off", "@typescript-eslint/no-unsafe-call": "off", "@typescript-eslint/restrict-plus-operands": "off", "@typescript-eslint/no-unsafe-assignment": "off", diff --git a/front/src/Administration/AnalyticsClient.ts b/front/src/Administration/AnalyticsClient.ts index fb2b604b..4248339b 100644 --- a/front/src/Administration/AnalyticsClient.ts +++ b/front/src/Administration/AnalyticsClient.ts @@ -20,62 +20,62 @@ class AnalyticsClient { identifyUser(uuid: string, email: string | null) { this.posthogPromise?.then((posthog) => { posthog.identify(uuid, { uuid, email, wa: true }); - }); + }).catch(e => console.error(e)); } loggedWithSso() { this.posthogPromise?.then((posthog) => { posthog.capture("wa-logged-sso"); - }); + }).catch(e => console.error(e)); } loggedWithToken() { this.posthogPromise?.then((posthog) => { posthog.capture("wa-logged-token"); - }); + }).catch(e => console.error(e)); } enteredRoom(roomId: string, roomGroup: string | null) { this.posthogPromise?.then((posthog) => { posthog.capture("$pageView", { roomId, roomGroup }); posthog.capture("enteredRoom"); - }); + }).catch(e => console.error(e)); } openedMenu() { this.posthogPromise?.then((posthog) => { posthog.capture("wa-opened-menu"); - }); + }).catch(e => console.error(e)); } launchEmote(emote: string) { this.posthogPromise?.then((posthog) => { posthog.capture("wa-emote-launch", { emote }); - }); + }).catch(e => console.error(e)); } enteredJitsi(roomName: string, roomId: string) { this.posthogPromise?.then((posthog) => { posthog.capture("wa-entered-jitsi", { roomName, roomId }); - }); + }).catch(e => console.error(e)); } validationName() { this.posthogPromise?.then((posthog) => { posthog.capture("wa-name-validation"); - }); + }).catch(e => console.error(e)); } validationWoka(scene: string) { this.posthogPromise?.then((posthog) => { posthog.capture("wa-woka-validation", { scene }); - }); + }).catch(e => console.error(e)); } validationVideo() { this.posthogPromise?.then((posthog) => { posthog.capture("wa-video-validation"); - }); + }).catch(e => console.error(e)); } } export const analyticsClient = new AnalyticsClient(); diff --git a/front/src/Api/iframe/Ui/ActionMessage.ts b/front/src/Api/iframe/Ui/ActionMessage.ts index 912603b9..f4e6a937 100644 --- a/front/src/Api/iframe/Ui/ActionMessage.ts +++ b/front/src/Api/iframe/Ui/ActionMessage.ts @@ -26,7 +26,7 @@ export class ActionMessage { this.message = actionMessageOptions.message; this.type = actionMessageOptions.type ?? "message"; this.callback = actionMessageOptions.callback; - this.create(); + this.create().catch(e => console.error(e)); } private async create() { diff --git a/front/src/Api/iframe/state.ts b/front/src/Api/iframe/state.ts index 7021b251..ccc671f6 100644 --- a/front/src/Api/iframe/state.ts +++ b/front/src/Api/iframe/state.ts @@ -95,7 +95,7 @@ export function createState(target: "global" | "player"): WorkadventureStateComm set(target: WorkadventureStateCommands, p: PropertyKey, value: unknown, receiver: unknown): boolean { // Note: when using "set", there is no way to wait, so we ignore the return of the promise. // User must use WA.state.saveVariable to have error message. - target.saveVariable(p.toString(), value); + target.saveVariable(p.toString(), value).catch(e => console.error(e)); return true; }, has(target: WorkadventureStateCommands, p: PropertyKey): boolean { diff --git a/front/src/Components/AudioManager/AudioManager.svelte b/front/src/Components/AudioManager/AudioManager.svelte index c4ca44f9..b62d8fbe 100644 --- a/front/src/Components/AudioManager/AudioManager.svelte +++ b/front/src/Components/AudioManager/AudioManager.svelte @@ -25,7 +25,7 @@ HTMLAudioPlayer.loop = get(audioManagerVolumeStore).loop; HTMLAudioPlayer.volume = get(audioManagerVolumeStore).volume; HTMLAudioPlayer.muted = get(audioManagerVolumeStore).muted; - HTMLAudioPlayer.play(); + void HTMLAudioPlayer.play(); }); unsubscriberVolumeStore = audioManagerVolumeStore.subscribe((audioManager: audioManagerVolume) => { const reduceVolume = audioManager.talking && audioManager.decreaseWhileTalking; diff --git a/front/src/Components/Menu/GlobalMessagesSubMenu.svelte b/front/src/Components/Menu/GlobalMessagesSubMenu.svelte index 524e5e50..e755a243 100644 --- a/front/src/Components/Menu/GlobalMessagesSubMenu.svelte +++ b/front/src/Components/Menu/GlobalMessagesSubMenu.svelte @@ -19,12 +19,12 @@ uploadAudioActive = true; } - function send() { + async function send(): Promise { if (inputSendTextActive) { - handleSendText.sendTextMessage(broadcastToWorld); + return handleSendText.sendTextMessage(broadcastToWorld); } if (uploadAudioActive) { - handleSendAudio.sendAudioMessage(broadcastToWorld); + return handleSendAudio.sendAudioMessage(broadcastToWorld); } } diff --git a/front/src/Components/Menu/ProfileSubMenu.svelte b/front/src/Components/Menu/ProfileSubMenu.svelte index 07356f6c..87bf57c9 100644 --- a/front/src/Components/Menu/ProfileSubMenu.svelte +++ b/front/src/Components/Menu/ProfileSubMenu.svelte @@ -41,10 +41,10 @@ gameManager.leaveGame(SelectCharacterSceneName, new SelectCharacterScene()); } - function logOut() { + async function logOut() { disableMenuStores(); loginSceneVisibleStore.set(true); - connectionManager.logout(); + return connectionManager.logout(); } function getProfileUrl() { From 24baf5664cf1cc04cdd70fb726b0e2ba667a5e4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Tue, 4 Jan 2022 16:48:47 +0100 Subject: [PATCH 95/95] All promises are now handled --- front/src/Administration/AnalyticsClient.ts | 82 +++++++----- front/src/Api/iframe/Ui/ActionMessage.ts | 2 +- front/src/Api/iframe/state.ts | 2 +- .../Components/Menu/SettingsSubMenu.svelte | 22 ++-- front/src/Components/UI/AudioPlaying.svelte | 2 +- front/src/Connexion/ConnectionManager.ts | 4 +- front/src/Connexion/LocalUserStore.ts | 9 +- front/src/Connexion/RoomConnection.ts | 2 +- front/src/Phaser/Companion/Companion.ts | 16 ++- .../CompanionTexturesLoadingManager.ts | 2 +- front/src/Phaser/Components/Loader.ts | 8 +- .../Phaser/Game/GameMapPropertiesListener.ts | 7 +- front/src/Phaser/Game/GameScene.ts | 97 ++++++++------ front/src/Phaser/Login/CustomizeScene.ts | 28 ++-- .../src/Phaser/Login/SelectCharacterScene.ts | 14 +- front/src/Stores/MediaStore.ts | 29 ++--- front/src/Stores/ScreenSharingStore.ts | 2 +- front/src/Url/UrlManager.ts | 2 +- front/src/WebRtc/CoWebsiteManager.ts | 122 +++++++++--------- front/src/WebRtc/JitsiFactory.ts | 8 +- front/src/iframe_api.ts | 10 +- 21 files changed, 262 insertions(+), 208 deletions(-) diff --git a/front/src/Administration/AnalyticsClient.ts b/front/src/Administration/AnalyticsClient.ts index 4248339b..4c1ca93a 100644 --- a/front/src/Administration/AnalyticsClient.ts +++ b/front/src/Administration/AnalyticsClient.ts @@ -18,64 +18,84 @@ class AnalyticsClient { } identifyUser(uuid: string, email: string | null) { - this.posthogPromise?.then((posthog) => { - posthog.identify(uuid, { uuid, email, wa: true }); - }).catch(e => console.error(e)); + this.posthogPromise + ?.then((posthog) => { + posthog.identify(uuid, { uuid, email, wa: true }); + }) + .catch((e) => console.error(e)); } loggedWithSso() { - this.posthogPromise?.then((posthog) => { - posthog.capture("wa-logged-sso"); - }).catch(e => console.error(e)); + this.posthogPromise + ?.then((posthog) => { + posthog.capture("wa-logged-sso"); + }) + .catch((e) => console.error(e)); } loggedWithToken() { - this.posthogPromise?.then((posthog) => { - posthog.capture("wa-logged-token"); - }).catch(e => console.error(e)); + this.posthogPromise + ?.then((posthog) => { + posthog.capture("wa-logged-token"); + }) + .catch((e) => console.error(e)); } enteredRoom(roomId: string, roomGroup: string | null) { - this.posthogPromise?.then((posthog) => { - posthog.capture("$pageView", { roomId, roomGroup }); - posthog.capture("enteredRoom"); - }).catch(e => console.error(e)); + this.posthogPromise + ?.then((posthog) => { + posthog.capture("$pageView", { roomId, roomGroup }); + posthog.capture("enteredRoom"); + }) + .catch((e) => console.error(e)); } openedMenu() { - this.posthogPromise?.then((posthog) => { - posthog.capture("wa-opened-menu"); - }).catch(e => console.error(e)); + this.posthogPromise + ?.then((posthog) => { + posthog.capture("wa-opened-menu"); + }) + .catch((e) => console.error(e)); } launchEmote(emote: string) { - this.posthogPromise?.then((posthog) => { - posthog.capture("wa-emote-launch", { emote }); - }).catch(e => console.error(e)); + this.posthogPromise + ?.then((posthog) => { + posthog.capture("wa-emote-launch", { emote }); + }) + .catch((e) => console.error(e)); } enteredJitsi(roomName: string, roomId: string) { - this.posthogPromise?.then((posthog) => { - posthog.capture("wa-entered-jitsi", { roomName, roomId }); - }).catch(e => console.error(e)); + this.posthogPromise + ?.then((posthog) => { + posthog.capture("wa-entered-jitsi", { roomName, roomId }); + }) + .catch((e) => console.error(e)); } validationName() { - this.posthogPromise?.then((posthog) => { - posthog.capture("wa-name-validation"); - }).catch(e => console.error(e)); + this.posthogPromise + ?.then((posthog) => { + posthog.capture("wa-name-validation"); + }) + .catch((e) => console.error(e)); } validationWoka(scene: string) { - this.posthogPromise?.then((posthog) => { - posthog.capture("wa-woka-validation", { scene }); - }).catch(e => console.error(e)); + this.posthogPromise + ?.then((posthog) => { + posthog.capture("wa-woka-validation", { scene }); + }) + .catch((e) => console.error(e)); } validationVideo() { - this.posthogPromise?.then((posthog) => { - posthog.capture("wa-video-validation"); - }).catch(e => console.error(e)); + this.posthogPromise + ?.then((posthog) => { + posthog.capture("wa-video-validation"); + }) + .catch((e) => console.error(e)); } } export const analyticsClient = new AnalyticsClient(); diff --git a/front/src/Api/iframe/Ui/ActionMessage.ts b/front/src/Api/iframe/Ui/ActionMessage.ts index f4e6a937..ff0908ff 100644 --- a/front/src/Api/iframe/Ui/ActionMessage.ts +++ b/front/src/Api/iframe/Ui/ActionMessage.ts @@ -26,7 +26,7 @@ export class ActionMessage { this.message = actionMessageOptions.message; this.type = actionMessageOptions.type ?? "message"; this.callback = actionMessageOptions.callback; - this.create().catch(e => console.error(e)); + this.create().catch((e) => console.error(e)); } private async create() { diff --git a/front/src/Api/iframe/state.ts b/front/src/Api/iframe/state.ts index ccc671f6..278b208e 100644 --- a/front/src/Api/iframe/state.ts +++ b/front/src/Api/iframe/state.ts @@ -95,7 +95,7 @@ export function createState(target: "global" | "player"): WorkadventureStateComm set(target: WorkadventureStateCommands, p: PropertyKey, value: unknown, receiver: unknown): boolean { // Note: when using "set", there is no way to wait, so we ignore the return of the promise. // User must use WA.state.saveVariable to have error message. - target.saveVariable(p.toString(), value).catch(e => console.error(e)); + target.saveVariable(p.toString(), value).catch((e) => console.error(e)); return true; }, has(target: WorkadventureStateCommands, p: PropertyKey): boolean { diff --git a/front/src/Components/Menu/SettingsSubMenu.svelte b/front/src/Components/Menu/SettingsSubMenu.svelte index 1db14036..1ad1ac8b 100644 --- a/front/src/Components/Menu/SettingsSubMenu.svelte +++ b/front/src/Components/Menu/SettingsSubMenu.svelte @@ -33,9 +33,9 @@ const body = HtmlUtils.querySelectorOrFail("body"); if (body) { if (document.fullscreenElement !== null && !fullscreen) { - document.exitFullscreen(); + document.exitFullscreen().catch((e) => console.error(e)); } else { - body.requestFullscreen(); + body.requestFullscreen().catch((e) => console.error(e)); } localUserStore.setFullscreen(fullscreen); } @@ -45,14 +45,16 @@ if (Notification.permission === "granted") { localUserStore.setNotification(notification ? "granted" : "denied"); } else { - Notification.requestPermission().then((response) => { - if (response === "granted") { - localUserStore.setNotification(notification ? "granted" : "denied"); - } else { - localUserStore.setNotification("denied"); - notification = false; - } - }); + Notification.requestPermission() + .then((response) => { + if (response === "granted") { + localUserStore.setNotification(notification ? "granted" : "denied"); + } else { + localUserStore.setNotification("denied"); + notification = false; + } + }) + .catch((e) => console.error(e)); } } diff --git a/front/src/Components/UI/AudioPlaying.svelte b/front/src/Components/UI/AudioPlaying.svelte index 09ffd639..a8d12ec9 100644 --- a/front/src/Components/UI/AudioPlaying.svelte +++ b/front/src/Components/UI/AudioPlaying.svelte @@ -12,7 +12,7 @@ } afterUpdate(() => { - audio.play(); + audio.play().catch((e) => console.error(e)); }); diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts index 19750ee8..20c4aae9 100644 --- a/front/src/Connexion/ConnectionManager.ts +++ b/front/src/Connexion/ConnectionManager.ts @@ -188,7 +188,7 @@ class ConnectionManager { //Set last room visited! (connected or nor, must to be saved in localstorage and cache API) //use href to keep # value - localUserStore.setLastRoomUrl(this._currentRoom.href); + await localUserStore.setLastRoomUrl(this._currentRoom.href); //todo: add here some kind of warning if authToken has expired. if (!this.authToken && !this._currentRoom.authenticationMandatory) { @@ -301,7 +301,7 @@ class ConnectionManager { this.reconnectingTimeout = setTimeout(() => { //todo: allow a way to break recursion? //todo: find a way to avoid recursive function. Otherwise, the call stack will grow indefinitely. - this.connectToRoomSocket(roomUrl, name, characterLayers, position, viewport, companion).then( + void this.connectToRoomSocket(roomUrl, name, characterLayers, position, viewport, companion).then( (connection) => resolve(connection) ); }, 4000 + Math.floor(Math.random() * 2000)); diff --git a/front/src/Connexion/LocalUserStore.ts b/front/src/Connexion/LocalUserStore.ts index 4f03a546..cc84f043 100644 --- a/front/src/Connexion/LocalUserStore.ts +++ b/front/src/Connexion/LocalUserStore.ts @@ -136,13 +136,12 @@ class LocalUserStore { return localStorage.getItem(ignoreFollowRequests) === "true"; } - setLastRoomUrl(roomUrl: string): void { + async setLastRoomUrl(roomUrl: string): Promise { localStorage.setItem(lastRoomUrl, roomUrl.toString()); if ("caches" in window) { - caches.open(cacheAPIIndex).then((cache) => { - const stringResponse = new Response(JSON.stringify({ roomUrl })); - cache.put(`/${lastRoomUrl}`, stringResponse); - }); + const cache = await caches.open(cacheAPIIndex); + const stringResponse = new Response(JSON.stringify({ roomUrl })); + await cache.put(`/${lastRoomUrl}`, stringResponse); } } getLastRoomUrl(): string { diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 96c6dd23..d360c705 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -352,7 +352,7 @@ export class RoomConnection implements RoomConnection { break; } case "tokenExpiredMessage": { - connectionManager.logout(); + 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 break; } diff --git a/front/src/Phaser/Companion/Companion.ts b/front/src/Phaser/Companion/Companion.ts index 80b0236e..6157ebaa 100644 --- a/front/src/Phaser/Companion/Companion.ts +++ b/front/src/Phaser/Companion/Companion.ts @@ -41,13 +41,15 @@ export class Companion extends Container { this.companionName = name; this._pictureStore = writable(undefined); - texturePromise.then((resource) => { - this.addResource(resource); - this.invisible = false; - return this.getSnapshot().then((htmlImageElementSrc) => { - this._pictureStore.set(htmlImageElementSrc); - }); - }); + texturePromise + .then((resource) => { + this.addResource(resource); + this.invisible = false; + return this.getSnapshot().then((htmlImageElementSrc) => { + this._pictureStore.set(htmlImageElementSrc); + }); + }) + .catch((e) => console.error(e)); this.scene.physics.world.enableBody(this); diff --git a/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts b/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts index bd87ba75..98cceafa 100644 --- a/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts +++ b/front/src/Phaser/Companion/CompanionTexturesLoadingManager.ts @@ -3,7 +3,7 @@ import { COMPANION_RESOURCES, CompanionResourceDescriptionInterface } from "./Co export const getAllCompanionResources = (loader: LoaderPlugin): CompanionResourceDescriptionInterface[] => { COMPANION_RESOURCES.forEach((resource: CompanionResourceDescriptionInterface) => { - lazyLoadCompanionResource(loader, resource.name); + lazyLoadCompanionResource(loader, resource.name).catch((e) => console.error(e)); }); return COMPANION_RESOURCES; diff --git a/front/src/Phaser/Components/Loader.ts b/front/src/Phaser/Components/Loader.ts index e013e758..7eb08e6d 100644 --- a/front/src/Phaser/Components/Loader.ts +++ b/front/src/Phaser/Components/Loader.ts @@ -72,9 +72,11 @@ export class Loader { if (this.loadingText) { this.loadingText.destroy(); } - promiseLoadLogoTexture.then((resLoadingImage: Phaser.GameObjects.Image) => { - resLoadingImage.destroy(); - }); + promiseLoadLogoTexture + .then((resLoadingImage: Phaser.GameObjects.Image) => { + resLoadingImage.destroy(); + }) + .catch((e) => console.error(e)); this.progress.destroy(); this.progressContainer.destroy(); if (this.scene instanceof DirtyScene) { diff --git a/front/src/Phaser/Game/GameMapPropertiesListener.ts b/front/src/Phaser/Game/GameMapPropertiesListener.ts index 2dc36df8..f6c28862 100644 --- a/front/src/Phaser/Game/GameMapPropertiesListener.ts +++ b/front/src/Phaser/Game/GameMapPropertiesListener.ts @@ -123,7 +123,7 @@ export class GameMapPropertiesListener { .then((coWebsite) => { const coWebsiteOpen = this.coWebsitesOpenByLayer.get(layer); if (coWebsiteOpen && coWebsiteOpen.state === OpenCoWebsiteState.MUST_BE_CLOSE) { - coWebsiteManager.closeCoWebsite(coWebsite); + coWebsiteManager.closeCoWebsite(coWebsite).catch((e) => console.error(e)); this.coWebsitesOpenByLayer.delete(layer); this.coWebsitesActionTriggerByLayer.delete(layer); } else { @@ -132,7 +132,8 @@ export class GameMapPropertiesListener { state: OpenCoWebsiteState.OPENED, }); } - }); + }) + .catch((e) => console.error(e)); layoutManagerActionStore.removeAction(actionUuid); }; @@ -198,7 +199,7 @@ export class GameMapPropertiesListener { } if (coWebsiteOpen.coWebsite !== undefined) { - coWebsiteManager.closeCoWebsite(coWebsiteOpen.coWebsite); + coWebsiteManager.closeCoWebsite(coWebsiteOpen.coWebsite).catch((e) => console.error(e)); } this.coWebsitesOpenByLayer.delete(layer); diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 42b0c06e..b6d7274c 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -241,7 +241,7 @@ export class GameScene extends DirtyScene { const textures = localUser?.textures; if (textures) { for (const texture of textures) { - loadCustomTexture(this.load, texture); + loadCustomTexture(this.load, texture).catch((e) => console.error(e)); } } @@ -268,7 +268,7 @@ export class GameScene extends DirtyScene { this.load.on( "filecomplete-tilemapJSON-" + this.MapUrlFile, (key: string, type: string, data: unknown) => { - this.onMapLoad(data); + this.onMapLoad(data).catch((e) => console.error(e)); } ); return; @@ -292,14 +292,14 @@ export class GameScene extends DirtyScene { this.load.on( "filecomplete-tilemapJSON-" + this.MapUrlFile, (key: string, type: string, data: unknown) => { - this.onMapLoad(data); + this.onMapLoad(data).catch((e) => console.error(e)); } ); // If the map has already been loaded as part of another GameScene, the "on load" event will not be triggered. // In this case, we check in the cache to see if the map is here and trigger the event manually. if (this.cache.tilemap.exists(this.MapUrlFile)) { const data = this.cache.tilemap.get(this.MapUrlFile); - this.onMapLoad(data); + this.onMapLoad(data).catch((e) => console.error(e)); } return; } @@ -320,7 +320,7 @@ export class GameScene extends DirtyScene { }); this.load.scenePlugin("AnimatedTiles", AnimatedTiles, "animatedTiles", "animatedTiles"); this.load.on("filecomplete-tilemapJSON-" + this.MapUrlFile, (key: string, type: string, data: unknown) => { - this.onMapLoad(data); + this.onMapLoad(data).catch((e) => console.error(e)); }); //TODO strategy to add access token this.load.tilemapTiledJSON(this.MapUrlFile, this.MapUrlFile); @@ -328,7 +328,7 @@ export class GameScene extends DirtyScene { // In this case, we check in the cache to see if the map is here and trigger the event manually. if (this.cache.tilemap.exists(this.MapUrlFile)) { const data = this.cache.tilemap.get(this.MapUrlFile); - this.onMapLoad(data); + this.onMapLoad(data).catch((e) => console.error(e)); } //eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -406,21 +406,23 @@ export class GameScene extends DirtyScene { this.load.on("complete", () => { // FIXME: the factory might fail because the resources might not be loaded yet... // We would need to add a loader ended event in addition to the createPromise - this.createPromise.then(async () => { - itemFactory.create(this); + this.createPromise + .then(async () => { + itemFactory.create(this); - const roomJoinedAnswer = await this.connectionAnswerPromise; + const roomJoinedAnswer = await this.connectionAnswerPromise; - for (const object of objectsOfType) { - // TODO: we should pass here a factory to create sprites (maybe?) + for (const object of objectsOfType) { + // TODO: we should pass here a factory to create sprites (maybe?) - // Do we have a state for this object? - const state = roomJoinedAnswer.items[object.id]; + // Do we have a state for this object? + const state = roomJoinedAnswer.items[object.id]; - const actionableItem = itemFactory.factory(this, object, state); - this.actionableItems.set(actionableItem.getId(), actionableItem); - } - }); + const actionableItem = itemFactory.factory(this, object, state); + this.actionableItems.set(actionableItem.getId(), actionableItem); + } + }) + .catch((e) => console.error(e)); }); } } @@ -486,11 +488,11 @@ export class GameScene extends DirtyScene { if (exitSceneUrl !== undefined) { this.loadNextGame( Room.getRoomPathFromExitSceneUrl(exitSceneUrl, window.location.toString(), this.MapUrlFile) - ); + ).catch((e) => console.error(e)); } const exitUrl = this.getExitUrl(layer); if (exitUrl !== undefined) { - this.loadNextGameFromExitUrl(exitUrl); + this.loadNextGameFromExitUrl(exitUrl).catch((e) => console.error(e)); } } if (layer.type === "objectgroup") { @@ -530,7 +532,7 @@ export class GameScene extends DirtyScene { } this.gameMap.exitUrls.forEach((exitUrl) => { - this.loadNextGameFromExitUrl(exitUrl); + this.loadNextGameFromExitUrl(exitUrl).catch((e) => console.error(e)); }); this.startPositionCalculator = new StartPositionCalculator( @@ -551,7 +553,10 @@ export class GameScene extends DirtyScene { mediaManager.setUserInputManager(this.userInputManager); if (localUserStore.getFullscreen()) { - document.querySelector("body")?.requestFullscreen(); + document + .querySelector("body") + ?.requestFullscreen() + .catch((e) => console.error(e)); } //notify game manager can to create currentUser in map @@ -657,9 +662,16 @@ export class GameScene extends DirtyScene { } }); - Promise.all([this.connectionAnswerPromise as Promise, ...scriptPromises]).then(() => { - this.scene.wake(); - }); + Promise.all([this.connectionAnswerPromise as Promise, ...scriptPromises]) + .then(() => { + this.scene.wake(); + }) + .catch((e) => + console.error( + "Some scripts failed to load ot the connection failed to establish to WorkAdventure server", + e + ) + ); } /** @@ -859,7 +871,8 @@ export class GameScene extends DirtyScene { // iframeListener.sendLeaveLayerEvent(layer.name); // }); // }); - }); + }) + .catch((e) => console.error(e)); } //todo: into dedicated classes @@ -912,7 +925,7 @@ export class GameScene extends DirtyScene { if (newValue) { this.onMapExit( Room.getRoomPathFromExitSceneUrl(newValue as string, window.location.toString(), this.MapUrlFile) - ); + ).catch((e) => console.error(e)); } else { setTimeout(() => { layoutManagerActionStore.removeAction("roomAccessDenied"); @@ -921,7 +934,9 @@ export class GameScene extends DirtyScene { }); this.gameMap.onPropertyChange(GameMapProperties.EXIT_URL, (newValue, oldValue) => { if (newValue) { - this.onMapExit(Room.getRoomPathFromExitUrl(newValue as string, window.location.toString())); + this.onMapExit(Room.getRoomPathFromExitUrl(newValue as string, window.location.toString())).catch((e) => + console.error(e) + ); } else { setTimeout(() => { layoutManagerActionStore.removeAction("roomAccessDenied"); @@ -1107,7 +1122,9 @@ ${escapedMessage} this.iframeSubscriptionList.push( iframeListener.playSoundStream.subscribe((playSoundEvent) => { const url = new URL(playSoundEvent.url, this.MapUrlFile); - soundManager.playSound(this.load, this.sound, url.toString(), playSoundEvent.config); + soundManager + .playSound(this.load, this.sound, url.toString(), playSoundEvent.config) + .catch((e) => console.error(e)); }) ); @@ -1145,7 +1162,7 @@ ${escapedMessage} this.iframeSubscriptionList.push( iframeListener.loadSoundStream.subscribe((loadSoundEvent) => { const url = new URL(loadSoundEvent.url, this.MapUrlFile); - soundManager.loadSound(this.load, this.sound, url.toString()); + soundManager.loadSound(this.load, this.sound, url.toString()).catch((e) => console.error(e)); }) ); @@ -1156,11 +1173,15 @@ ${escapedMessage} ); this.iframeSubscriptionList.push( iframeListener.loadPageStream.subscribe((url: string) => { - this.loadNextGameFromExitUrl(url).then(() => { - this.events.once(EVENT_TYPE.POST_UPDATE, () => { - this.onMapExit(Room.getRoomPathFromExitUrl(url, window.location.toString())); - }); - }); + this.loadNextGameFromExitUrl(url) + .then(() => { + this.events.once(EVENT_TYPE.POST_UPDATE, () => { + this.onMapExit(Room.getRoomPathFromExitUrl(url, window.location.toString())).catch((e) => + console.error(e) + ); + }); + }) + .catch((e) => console.error(e)); }) ); let scriptedBubbleSprite: Sprite; @@ -1417,7 +1438,7 @@ ${escapedMessage} propertyValue: string | number | boolean | undefined ): void { if (propertyName === GameMapProperties.EXIT_URL && typeof propertyValue === "string") { - this.loadNextGameFromExitUrl(propertyValue); + this.loadNextGameFromExitUrl(propertyValue).catch((e) => console.error(e)); } this.gameMap.setLayerProperty(layerName, propertyName, propertyValue); } @@ -1502,7 +1523,7 @@ ${escapedMessage} public cleanupClosingScene(): void { // stop playing audio, close any open website, stop any open Jitsi - coWebsiteManager.closeCoWebsites(); + coWebsiteManager.closeCoWebsites().catch((e) => console.error(e)); // Stop the script, if any const scripts = this.getScriptUrls(this.mapFile); for (const script of scripts) { @@ -2044,7 +2065,9 @@ ${escapedMessage} const jitsiUrl = allProps.get(GameMapProperties.JITSI_URL) as string | undefined; const jitsiWidth = allProps.get(GameMapProperties.JITSI_WIDTH) as number | undefined; - jitsiFactory.start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl, jitsiWidth); + jitsiFactory + .start(roomName, this.playerName, jwt, jitsiConfig, jitsiInterfaceConfig, jitsiUrl, jitsiWidth) + .catch((e) => console.error(e)); this.connection?.setSilent(true); mediaManager.hideGameOverlay(); analyticsClient.enteredJitsi(roomName, this.room.id); diff --git a/front/src/Phaser/Login/CustomizeScene.ts b/front/src/Phaser/Login/CustomizeScene.ts index d5629c88..5c208edd 100644 --- a/front/src/Phaser/Login/CustomizeScene.ts +++ b/front/src/Phaser/Login/CustomizeScene.ts @@ -40,19 +40,21 @@ export class CustomizeScene extends AbstractCharacterScene { } preload() { - this.loadCustomSceneSelectCharacters().then((bodyResourceDescriptions) => { - bodyResourceDescriptions.forEach((bodyResourceDescription) => { - if ( - bodyResourceDescription.level == undefined || - bodyResourceDescription.level < 0 || - bodyResourceDescription.level > 5 - ) { - throw "Texture level is null"; - } - this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription); - }); - this.lazyloadingAttempt = true; - }); + this.loadCustomSceneSelectCharacters() + .then((bodyResourceDescriptions) => { + bodyResourceDescriptions.forEach((bodyResourceDescription) => { + if ( + bodyResourceDescription.level == undefined || + bodyResourceDescription.level < 0 || + bodyResourceDescription.level > 5 + ) { + throw "Texture level is null"; + } + this.layers[bodyResourceDescription.level].unshift(bodyResourceDescription); + }); + this.lazyloadingAttempt = true; + }) + .catch((e) => console.error(e)); this.layers = loadAllLayers(this.load); this.lazyloadingAttempt = false; diff --git a/front/src/Phaser/Login/SelectCharacterScene.ts b/front/src/Phaser/Login/SelectCharacterScene.ts index 64fa9791..4e372e0e 100644 --- a/front/src/Phaser/Login/SelectCharacterScene.ts +++ b/front/src/Phaser/Login/SelectCharacterScene.ts @@ -41,12 +41,14 @@ export class SelectCharacterScene extends AbstractCharacterScene { } preload() { - this.loadSelectSceneCharacters().then((bodyResourceDescriptions) => { - bodyResourceDescriptions.forEach((bodyResourceDescription) => { - this.playerModels.push(bodyResourceDescription); - }); - this.lazyloadingAttempt = true; - }); + this.loadSelectSceneCharacters() + .then((bodyResourceDescriptions) => { + bodyResourceDescriptions.forEach((bodyResourceDescription) => { + this.playerModels.push(bodyResourceDescription); + }); + this.lazyloadingAttempt = true; + }) + .catch((e) => console.error(e)); this.playerModels = loadAllDefaultModels(this.load); this.lazyloadingAttempt = false; diff --git a/front/src/Stores/MediaStore.ts b/front/src/Stores/MediaStore.ts index a0f1a92b..7eeac35d 100644 --- a/front/src/Stores/MediaStore.ts +++ b/front/src/Stores/MediaStore.ts @@ -360,32 +360,27 @@ const implementCorrectTrackBehavior = getNavigatorType() === NavigatorType.firef /** * Stops the camera from filming */ -function applyCameraConstraints(currentStream: MediaStream | null, constraints: MediaTrackConstraints | boolean): void { +async function applyCameraConstraints( + currentStream: MediaStream | null, + constraints: MediaTrackConstraints | boolean +): Promise { if (!currentStream) { - return; - } - for (const track of currentStream.getVideoTracks()) { - toggleConstraints(track, constraints).catch((e) => - console.error("Error while setting new camera constraints:", e) - ); + return []; } + return Promise.all(currentStream.getVideoTracks().map((track) => toggleConstraints(track, constraints))); } /** * Stops the microphone from listening */ -function applyMicrophoneConstraints( +async function applyMicrophoneConstraints( currentStream: MediaStream | null, constraints: MediaTrackConstraints | boolean -): void { +): Promise { if (!currentStream) { - return; - } - for (const track of currentStream.getAudioTracks()) { - toggleConstraints(track, constraints).catch((e) => - console.error("Error while setting new audio constraints:", e) - ); + return []; } + return Promise.all(currentStream.getAudioTracks().map((track) => toggleConstraints(track, constraints))); } async function toggleConstraints(track: MediaStreamTrack, constraints: MediaTrackConstraints | boolean): Promise { @@ -477,8 +472,8 @@ export const localStreamStore = derived, LocalS } } - applyMicrophoneConstraints(currentStream, constraints.audio || false); - applyCameraConstraints(currentStream, constraints.video || false); + applyMicrophoneConstraints(currentStream, constraints.audio || false).catch((e) => console.error(e)); + applyCameraConstraints(currentStream, constraints.video || false).catch((e) => console.error(e)); if (implementCorrectTrackBehavior) { //on good navigators like firefox, we can instantiate the stream once and simply disable or enable the tracks as needed diff --git a/front/src/Stores/ScreenSharingStore.ts b/front/src/Stores/ScreenSharingStore.ts index d68dbf8b..dc2c495c 100644 --- a/front/src/Stores/ScreenSharingStore.ts +++ b/front/src/Stores/ScreenSharingStore.ts @@ -156,7 +156,7 @@ export const screenSharingLocalStreamStore = derived console.error(e)); } ); diff --git a/front/src/Url/UrlManager.ts b/front/src/Url/UrlManager.ts index 50dbedc9..95ea12f2 100644 --- a/front/src/Url/UrlManager.ts +++ b/front/src/Url/UrlManager.ts @@ -41,7 +41,7 @@ class UrlManager { if (window.location.pathname === room.id) return; //Set last room visited! (connected or nor, must to be saved in localstorage and cache API) //use href to keep # value - localUserStore.setLastRoomUrl(room.href); + localUserStore.setLastRoomUrl(room.href).catch((e) => console.error(e)); const hash = window.location.hash; const search = room.search.toString(); history.pushState({}, "WorkAdventure", room.id + (search ? "?" + search : "") + hash); diff --git a/front/src/WebRtc/CoWebsiteManager.ts b/front/src/WebRtc/CoWebsiteManager.ts index 7a003604..8bff2acb 100644 --- a/front/src/WebRtc/CoWebsiteManager.ts +++ b/front/src/WebRtc/CoWebsiteManager.ts @@ -149,7 +149,7 @@ class CoWebsiteManager { } buttonCloseCoWebsites.blur(); - this.closeCoWebsites(); + this.closeCoWebsites().catch((e) => console.error(e)); }); const buttonFullScreenFrame = HtmlUtils.getElementByIdOrFail(cowebsiteFullScreenButtonId); @@ -515,70 +515,72 @@ class CoWebsiteManager { throw new Error("Too many we"); } - Promise.resolve(callback(this.cowebsiteBufferDom)).then((iframe) => { - iframe?.classList.add("pixel"); + Promise.resolve(callback(this.cowebsiteBufferDom)) + .then((iframe) => { + iframe?.classList.add("pixel"); - if (!iframe.id) { - do { - iframe.id = "cowebsite-iframe-" + (Math.random() + 1).toString(36).substring(7); - } while (this.getCoWebsiteById(iframe.id)); - } - - const onloadPromise = new Promise((resolve) => { - iframe.onload = () => resolve(); - }); - - const icon = this.generateCoWebsiteIcon(iframe); - - const coWebsite = { - iframe, - icon, - position: position ?? this.coWebsites.length, - }; - - // Iframe management on mobile - icon.addEventListener("click", () => { - if (this.isSmallScreen()) { - this.moveRightPreviousCoWebsite(coWebsite, 0); + if (!iframe.id) { + do { + iframe.id = "cowebsite-iframe-" + (Math.random() + 1).toString(36).substring(7); + } while (this.getCoWebsiteById(iframe.id)); } - }); - this.coWebsites.push(coWebsite); - this.cowebsiteSubIconsDom.appendChild(icon); + const onloadPromise = new Promise((resolve) => { + iframe.onload = () => resolve(); + }); - const onTimeoutPromise = new Promise((resolve) => { - setTimeout(() => resolve(), 2000); - }); + const icon = this.generateCoWebsiteIcon(iframe); - this.currentOperationPromise = this.currentOperationPromise - .then(() => Promise.race([onloadPromise, onTimeoutPromise])) - .then(() => { - if (coWebsite.position === 0) { - this.openMain(); - if (widthPercent) { - this.widthPercent = widthPercent; - } + const coWebsite = { + iframe, + icon, + position: position ?? this.coWebsites.length, + }; - setTimeout(() => { - this.fire(); + // Iframe management on mobile + icon.addEventListener("click", () => { + if (this.isSmallScreen()) { + this.moveRightPreviousCoWebsite(coWebsite, 0); + } + }); + + this.coWebsites.push(coWebsite); + this.cowebsiteSubIconsDom.appendChild(icon); + + const onTimeoutPromise = new Promise((resolve) => { + setTimeout(() => resolve(), 2000); + }); + + this.currentOperationPromise = this.currentOperationPromise + .then(() => Promise.race([onloadPromise, onTimeoutPromise])) + .then(() => { + if (coWebsite.position === 0) { + this.openMain(); + if (widthPercent) { + this.widthPercent = widthPercent; + } + + setTimeout(() => { + this.fire(); + position !== undefined + ? this.moveRightPreviousCoWebsite(coWebsite, coWebsite.position) + : this.moveCoWebsite(coWebsite, coWebsite.position); + }, animationTime); + } else { position !== undefined ? this.moveRightPreviousCoWebsite(coWebsite, coWebsite.position) : this.moveCoWebsite(coWebsite, coWebsite.position); - }, animationTime); - } else { - position !== undefined - ? this.moveRightPreviousCoWebsite(coWebsite, coWebsite.position) - : this.moveCoWebsite(coWebsite, coWebsite.position); - } + } - return resolve(coWebsite); - }) - .catch((err) => { - console.error("Error loadCoWebsite => ", err); - this.removeCoWebsiteFromStack(coWebsite); - return reject(); - }); - }); + return resolve(coWebsite); + }) + .catch((err) => { + console.error("Error loadCoWebsite => ", err); + this.removeCoWebsiteFromStack(coWebsite); + return reject(); + }); + }) + .catch((e) => console.error("Error loadCoWebsite >=> ", e)); }); } @@ -603,17 +605,21 @@ class CoWebsiteManager { return this.currentOperationPromise; } - public closeJitsi() { + public async closeJitsi() { const jitsi = this.searchJitsi(); if (jitsi) { - this.closeCoWebsite(jitsi); + return this.closeCoWebsite(jitsi); } } public closeCoWebsites(): Promise { this.currentOperationPromise = this.currentOperationPromise.then(() => { + const promises: Promise[] = []; this.coWebsites.forEach((coWebsite: CoWebsite) => { - this.closeCoWebsite(coWebsite); + promises.push(this.closeCoWebsite(coWebsite)); + }); + return Promise.all(promises).then(() => { + return; }); }); return this.currentOperationPromise; diff --git a/front/src/WebRtc/JitsiFactory.ts b/front/src/WebRtc/JitsiFactory.ts index 0f205f47..c067a255 100644 --- a/front/src/WebRtc/JitsiFactory.ts +++ b/front/src/WebRtc/JitsiFactory.ts @@ -1,5 +1,5 @@ import { JITSI_URL } from "../Enum/EnvironmentVariable"; -import { coWebsiteManager } from "./CoWebsiteManager"; +import { CoWebsite, coWebsiteManager } from "./CoWebsiteManager"; import { requestedCameraState, requestedMicrophoneState } from "../Stores/MediaStore"; import { get } from "svelte/store"; @@ -140,8 +140,8 @@ class JitsiFactory { interfaceConfig?: object, jitsiUrl?: string, jitsiWidth?: number - ): void { - coWebsiteManager.addCoWebsite( + ): Promise { + return coWebsiteManager.addCoWebsite( async (cowebsiteDiv) => { // Jitsi meet external API maintains some data in local storage // which is sent via the appData URL parameter when joining a @@ -200,7 +200,7 @@ class JitsiFactory { const jitsiCoWebsite = coWebsiteManager.searchJitsi(); if (jitsiCoWebsite) { - coWebsiteManager.closeJitsi(); + coWebsiteManager.closeJitsi().catch((e) => console.error(e)); } this.jitsiApi.removeListener("audioMuteStatusChanged", this.audioCallback); diff --git a/front/src/iframe_api.ts b/front/src/iframe_api.ts index 6b3ec8c3..66ee77c0 100644 --- a/front/src/iframe_api.ts +++ b/front/src/iframe_api.ts @@ -9,7 +9,7 @@ import { } from "./Api/Events/IframeEvent"; import chat from "./Api/iframe/chat"; import type { IframeCallback } from "./Api/iframe/IframeApiContribution"; -import nav from "./Api/iframe/nav"; +import nav, { CoWebsite } from "./Api/iframe/nav"; import controls from "./Api/iframe/controls"; import ui from "./Api/iframe/ui"; import sound from "./Api/iframe/sound"; @@ -136,17 +136,17 @@ const wa = { /** * @deprecated Use WA.nav.openCoWebSite instead */ - openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = ""): void { + openCoWebSite(url: string, allowApi: boolean = false, allowPolicy: string = ""): Promise { console.warn("Method WA.openCoWebSite is deprecated. Please use WA.nav.openCoWebSite instead"); - nav.openCoWebSite(url, allowApi, allowPolicy); + return nav.openCoWebSite(url, allowApi, allowPolicy); }, /** * @deprecated Use WA.nav.closeCoWebSite instead */ - closeCoWebSite(): void { + closeCoWebSite(): Promise { console.warn("Method WA.closeCoWebSite is deprecated. Please use WA.nav.closeCoWebSite instead"); - nav.closeCoWebSite(); + return nav.closeCoWebSite(); }, /**