From 987325e78754deeee5816f5c237e8729293e848b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 25 Mar 2022 17:24:49 +0100 Subject: [PATCH 1/8] Adding RXJS as a dependency in iframe-api-typings Because some methods are returning Subjects, we need to import rxjs as a dependency of the package to have full typing available. --- front/packages/iframe-api-typings/package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/front/packages/iframe-api-typings/package.json b/front/packages/iframe-api-typings/package.json index fa35851c..d6874921 100644 --- a/front/packages/iframe-api-typings/package.json +++ b/front/packages/iframe-api-typings/package.json @@ -9,5 +9,8 @@ "license": "MIT", "publishConfig": { "access": "public" + }, + "dependencies": { + "rxjs": "^6.6.3" } } From 003bc86262fb274dcb875c585095d5b3e0c6fc0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?gr=C3=A9goire=20parant?= Date: Wed, 30 Mar 2022 11:13:27 +0200 Subject: [PATCH 2/8] Release 1.9.6 (#2021) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Change accès token with query privateAccessToken in the url Signed-off-by: Gregoire Parant * Add play uri param Signed-off-by: Gregoire Parant * PlayUri parameter Signed-off-by: Gregoire Parant * removed unused imports * send group userIds to players * sending info about group lock state. wip * listening to lockGroupMessage on front * cleanup * Updating GroupDescriptors on LockGroupMessage * remove console logs * group circles with color fill * fix GameRoom text * remove obsolete check * cr fixes #1 wip * much cleaner approach to group lock update * fix compilation error * well-known values wip * Removing old unused images Just a bit of cleanup! * update with dan suggest Signed-off-by: Gregoire Parant * Change roomId by playUri Signed-off-by: Gregoire Parant * hide voice indicator for player leaving still existing bubble conversation * Refactor access by token Signed-off-by: Gregoire Parant * Refactor connexion manager Signed-off-by: Gregoire Parant * Adds settings options and localUser functions * PrivacyShutdownStore modifies constraints according to the user's setting * switches dropdown for checkboxes and adjusts wording * Adds the todo about German translation * Adds test map * Makes the default setting: camera off and mic on * Add z-index on embeds screens and action menu * Fixes tileset import * Adds settings options and localUser functions * PrivacyShutdownStore modifies constraints according to the user's setting * switches dropdown for checkboxes and adjusts wording * Adds the todo about German translation * Adds test map * Makes the default setting: camera off and mic on * Fixes tileset import * Applying German translation, thanks to @Lurkars * removed obsolete console.log * fix linter issues * make use of proto well knows types * fixed types for RoomConnection * fix locking bubble when returning from away mode * Fix run pretty Signed-off-by: Gregoire Parant * prevent actions menu from appearing when inserting space key on chat * Bump ansi-regex from 4.1.0 to 4.1.1 in /maps Bumps [ansi-regex](https://github.com/chalk/ansi-regex) from 4.1.0 to 4.1.1. - [Release notes](https://github.com/chalk/ansi-regex/releases) - [Commits](https://github.com/chalk/ansi-regex/compare/v4.1.0...v4.1.1) --- updated-dependencies: - dependency-name: ansi-regex dependency-type: indirect ... Signed-off-by: dependabot[bot] * Bump minimist from 1.2.5 to 1.2.6 in /desktop/local-app Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6. - [Release notes](https://github.com/substack/minimist/releases) - [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6) --- updated-dependencies: - dependency-name: minimist dependency-type: indirect ... Signed-off-by: dependabot[bot] * Bump ansi-regex from 4.1.0 to 4.1.1 in /desktop/electron Bumps [ansi-regex](https://github.com/chalk/ansi-regex) from 4.1.0 to 4.1.1. - [Release notes](https://github.com/chalk/ansi-regex/releases) - [Commits](https://github.com/chalk/ansi-regex/compare/v4.1.0...v4.1.1) --- updated-dependencies: - dependency-name: ansi-regex dependency-type: indirect ... Signed-off-by: dependabot[bot] * Bump ansi-regex from 4.1.0 to 4.1.1 in /uploader Bumps [ansi-regex](https://github.com/chalk/ansi-regex) from 4.1.0 to 4.1.1. - [Release notes](https://github.com/chalk/ansi-regex/releases) - [Commits](https://github.com/chalk/ansi-regex/compare/v4.1.0...v4.1.1) --- updated-dependencies: - dependency-name: ansi-regex dependency-type: indirect ... Signed-off-by: dependabot[bot] * Bump plist from 3.0.4 to 3.0.5 in /desktop/electron Bumps [plist](https://github.com/TooTallNate/node-plist) from 3.0.4 to 3.0.5. - [Release notes](https://github.com/TooTallNate/node-plist/releases) - [Changelog](https://github.com/TooTallNate/plist.js/blob/master/History.md) - [Commits](https://github.com/TooTallNate/node-plist/commits) --- updated-dependencies: - dependency-name: plist dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: Hanusiak Piotr Co-authored-by: David Négrier Co-authored-by: Piotr 'pwh' Hanusiak Co-authored-by: Benedicte Quimbert Co-authored-by: Alexis Faizeau Co-authored-by: Bénédicte Q <37311765+HimeShaman@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- back/src/Model/GameRoom.ts | 25 +++-- back/src/Model/Group.ts | 11 ++- back/src/Model/PositionNotifier.ts | 9 ++ back/src/Model/Zone.ts | 8 ++ back/src/RoomManager.ts | 7 ++ back/src/Services/SocketManager.ts | 64 ++++++++++-- back/tests/GameRoomTest.ts | 3 + back/tests/PositionNotifierTest.ts | 2 + desktop/electron/yarn.lock | 12 +-- desktop/local-app/yarn.lock | 6 +- .../Components/ActionsMenu/ActionsMenu.svelte | 1 + front/src/Components/CameraControls.svelte | 25 ++++- .../EmbedScreens/EmbedScreensContainer.svelte | 2 + front/src/Components/MainLayout.svelte | 9 +- .../Components/Menu/SettingsSubMenu.svelte | 49 ++++++++-- .../src/Components/VisitCard/VisitCard.svelte | 1 + front/src/Components/images/lock.svg | 1 + front/src/Connexion/AdminMessagesService.ts | 2 +- front/src/Connexion/ConnectionManager.ts | 17 ++-- front/src/Connexion/ConnexionModels.ts | 8 +- front/src/Connexion/LocalUserStore.ts | 51 ++++++++++ front/src/Connexion/RoomConnection.ts | 41 ++++---- front/src/Network/ProtobufClientUtils.ts | 2 +- front/src/Phaser/Game/GameScene.ts | 26 ++++- .../src/Phaser/UserInput/UserInputManager.ts | 3 + front/src/Stores/CurrentPlayerGroupStore.ts | 3 + front/src/Stores/MediaStore.ts | 13 ++- front/src/Url/UrlManager.ts | 15 ++- front/src/i18n/de-DE/menu.ts | 7 ++ front/src/i18n/en-US/menu.ts | 7 ++ front/src/i18n/fr-FR/menu.ts | 7 ++ .../AwayModeSettings/away_mode_settings.json | 97 +++++++++++++++++++ maps/tests/index.html | 8 ++ maps/yarn.lock | 6 +- messages/package.json | 2 +- messages/protos/messages.proto | 20 +++- .../src/Controller/AuthenticateController.ts | 3 +- pusher/src/Controller/IoSocketController.ts | 6 ++ .../src/Controller/OpenIdProfileController.ts | 6 +- pusher/src/Model/Zone.ts | 21 ++-- pusher/src/Services/AdminApi.ts | 12 ++- pusher/src/Services/SocketManager.ts | 7 ++ uploader/yarn.lock | 6 +- 43 files changed, 533 insertions(+), 98 deletions(-) create mode 100644 front/src/Components/images/lock.svg create mode 100644 front/src/Stores/CurrentPlayerGroupStore.ts create mode 100644 maps/tests/AwayModeSettings/away_mode_settings.json diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index 0e8203cf..157faa4e 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -6,6 +6,7 @@ import { EmoteCallback, EntersCallback, LeavesCallback, + LockGroupCallback, MovesCallback, PlayerDetailsUpdatedCallback, } from "_Model/Zone"; @@ -44,7 +45,7 @@ export class GameRoom { // Users, sorted by ID private readonly users = new Map(); private readonly usersByUuid = new Map(); - private readonly groups = new Set(); + private readonly groups: Map = new Map(); private readonly admins = new Set(); private itemsState = new Map(); @@ -66,6 +67,7 @@ export class GameRoom { onMoves: MovesCallback, onLeaves: LeavesCallback, onEmote: EmoteCallback, + onLockGroup: LockGroupCallback, onPlayerDetailsUpdated: PlayerDetailsUpdatedCallback ) { // A zone is 10 sprites wide. @@ -76,6 +78,7 @@ export class GameRoom { onMoves, onLeaves, onEmote, + onLockGroup, onPlayerDetailsUpdated ); } @@ -90,6 +93,7 @@ export class GameRoom { onMoves: MovesCallback, onLeaves: LeavesCallback, onEmote: EmoteCallback, + onLockGroup: LockGroupCallback, onPlayerDetailsUpdated: PlayerDetailsUpdatedCallback ): Promise { const mapDetails = await GameRoom.getMapDetails(roomUrl); @@ -105,6 +109,7 @@ export class GameRoom { onMoves, onLeaves, onEmote, + onLockGroup, onPlayerDetailsUpdated ); @@ -244,7 +249,7 @@ export class GameRoom { this.disconnectCallback, this.positionNotifier ); - this.groups.add(group); + this.groups.set(group.getId(), group); } } } else { @@ -328,7 +333,7 @@ export class GameRoom { this.disconnectCallback, this.positionNotifier ); - this.groups.add(newGroup); + this.groups.set(newGroup.getId(), newGroup); } else { this.leaveGroup(user); } @@ -375,10 +380,10 @@ export class GameRoom { group.leave(user); if (group.isEmpty()) { group.destroy(); - if (!this.groups.has(group)) { + if (!this.groups.has(group.getId())) { throw new Error(`Could not find group ${group.getId()} referenced by user ${user.id} in World.`); } - this.groups.delete(group); + this.groups.delete(group.getId()); //todo: is the group garbage collected? } else { group.updatePosition(); @@ -418,7 +423,7 @@ export class GameRoom { }); this.groups.forEach((group: Group) => { - if (group.isFull()) { + if (group.isFull() || group.isLocked()) { return; } const distance = GameRoom.computeDistanceBetweenPositions(user.getPosition(), group.getPosition()); @@ -544,6 +549,10 @@ export class GameRoom { this.positionNotifier.emitEmoteEvent(user, emoteEventMessage); } + public emitLockGroupEvent(user: User, groupId: number) { + this.positionNotifier.emitLockGroupEvent(user, groupId); + } + public addRoomListener(socket: RoomSocket) { this.roomListeners.add(socket); } @@ -657,4 +666,8 @@ export class GameRoom { const variablesManager = await this.getVariableManager(); return variablesManager.getVariablesForTags(tags); } + + public getGroupById(id: number): Group | undefined { + return this.groups.get(id); + } } diff --git a/back/src/Model/Group.ts b/back/src/Model/Group.ts index c14d509f..a960e7b3 100644 --- a/back/src/Model/Group.ts +++ b/back/src/Model/Group.ts @@ -14,6 +14,7 @@ export class Group implements Movable { private x!: number; private y!: number; private wasDestroyed: boolean = false; + private locked: boolean = false; private roomId: string; private currentZone: Zone | null = null; /** @@ -141,15 +142,19 @@ export class Group implements Movable { return this.users.size >= MAX_PER_GROUP; } + isLocked(): boolean { + return this.locked; + } + isEmpty(): boolean { return this.users.size <= 1; } join(user: User): void { // Broadcast on the right event - this.connectCallback(user, this); this.users.add(user); user.group = this; + this.connectCallback(user, this); } leave(user: User): void { @@ -167,6 +172,10 @@ export class Group implements Movable { this.disconnectCallback(user, this); } + lock(lock: boolean = true): void { + this.locked = lock; + } + /** * Let's kick everybody out. * Usually used when there is only one user left. diff --git a/back/src/Model/PositionNotifier.ts b/back/src/Model/PositionNotifier.ts index b059999a..38d538ec 100644 --- a/back/src/Model/PositionNotifier.ts +++ b/back/src/Model/PositionNotifier.ts @@ -12,6 +12,7 @@ import { EmoteCallback, EntersCallback, LeavesCallback, + LockGroupCallback, MovesCallback, PlayerDetailsUpdatedCallback, Zone, @@ -50,6 +51,7 @@ export class PositionNotifier { private onUserMoves: MovesCallback, private onUserLeaves: LeavesCallback, private onEmote: EmoteCallback, + private onLockGroup: LockGroupCallback, private onPlayerDetailsUpdated: PlayerDetailsUpdatedCallback ) {} @@ -111,6 +113,7 @@ export class PositionNotifier { this.onUserMoves, this.onUserLeaves, this.onEmote, + this.onLockGroup, this.onPlayerDetailsUpdated, i, j @@ -137,6 +140,12 @@ export class PositionNotifier { zone.emitEmoteEvent(emoteEventMessage); } + public emitLockGroupEvent(user: User, groupId: number) { + const zoneDesc = this.getZoneDescriptorFromCoordinates(user.getPosition().x, user.getPosition().y); + const zone = this.getZone(zoneDesc.i, zoneDesc.j); + zone.emitLockGroupEvent(groupId); + } + public *getAllUsersInSquareAroundZone(zone: Zone): Generator { const zoneDescriptor = this.getZoneDescriptorFromCoordinates(zone.x, zone.y); for (const d of getNearbyDescriptorsMatrix(zoneDescriptor)) { diff --git a/back/src/Model/Zone.ts b/back/src/Model/Zone.ts index 5c3e92ba..2d0cefd5 100644 --- a/back/src/Model/Zone.ts +++ b/back/src/Model/Zone.ts @@ -13,6 +13,7 @@ export type EntersCallback = (thing: Movable, fromZone: Zone | null, listener: Z export type MovesCallback = (thing: Movable, position: PositionInterface, listener: ZoneSocket) => void; export type LeavesCallback = (thing: Movable, newZone: Zone | null, listener: ZoneSocket) => void; export type EmoteCallback = (emoteEventMessage: EmoteEventMessage, listener: ZoneSocket) => void; +export type LockGroupCallback = (groupId: number, listener: ZoneSocket) => void; export type PlayerDetailsUpdatedCallback = ( playerDetailsUpdatedMessage: PlayerDetailsUpdatedMessage, listener: ZoneSocket @@ -27,6 +28,7 @@ export class Zone { private onMoves: MovesCallback, private onLeaves: LeavesCallback, private onEmote: EmoteCallback, + private onLockGroup: LockGroupCallback, private onPlayerDetailsUpdated: PlayerDetailsUpdatedCallback, public readonly x: number, public readonly y: number @@ -108,6 +110,12 @@ export class Zone { } } + public emitLockGroupEvent(groupId: number) { + for (const listener of this.listeners) { + this.onLockGroup(groupId, listener); + } + } + public updatePlayerDetails(user: User, playerDetails: SetPlayerDetailsMessage) { const playerDetailsUpdatedMessage = new PlayerDetailsUpdatedMessage(); playerDetailsUpdatedMessage.setUserid(user.id); diff --git a/back/src/RoomManager.ts b/back/src/RoomManager.ts index d375fbd8..ab886f50 100644 --- a/back/src/RoomManager.ts +++ b/back/src/RoomManager.ts @@ -29,6 +29,7 @@ import { WebRtcSignalToServerMessage, WorldFullWarningToRoomMessage, ZoneMessage, + LockGroupPromptMessage, } from "./Messages/generated/messages_pb"; import { sendUnaryData, ServerDuplexStream, ServerUnaryCall, ServerWritableStream } from "grpc"; import { socketManager } from "./Services/SocketManager"; @@ -135,6 +136,12 @@ const roomManager: IRoomManagerServer = { user, message.getFollowabortmessage() as FollowAbortMessage ); + } else if (message.hasLockgrouppromptmessage()) { + socketManager.handleLockGroupPromptMessage( + room, + user, + message.getLockgrouppromptmessage() as LockGroupPromptMessage + ); } else if (message.hasSendusermessage()) { const sendUserMessage = message.getSendusermessage(); socketManager.handleSendUserMessage(user, sendUserMessage as SendUserMessage); diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index 1d26f001..8aa7f6a4 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -38,6 +38,9 @@ import { SubToPusherRoomMessage, SetPlayerDetailsMessage, PlayerDetailsUpdatedMessage, + GroupUsersUpdateMessage, + LockGroupPromptMessage, + RoomMessage, } from "../Messages/generated/messages_pb"; import { User, UserSocket } from "../Model/User"; import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils"; @@ -68,7 +71,6 @@ function emitZoneMessage(subMessage: SubToPusherMessage, socket: ZoneSocket): vo // TODO: should we batch those every 100ms? const batchMessage = new BatchToPusherMessage(); batchMessage.addPayload(subMessage); - socket.write(batchMessage); } @@ -266,18 +268,28 @@ export class SocketManager { if (roomPromise === undefined) { roomPromise = GameRoom.create( roomId, - (user: User, group: Group) => this.joinWebRtcRoom(user, group), - (user: User, group: Group) => this.disConnectedUser(user, group), + (user: User, group: Group) => { + this.joinWebRtcRoom(user, group); + this.sendGroupUsersUpdateToGroupMembers(group); + }, + (user: User, group: Group) => { + this.disConnectedUser(user, group); + this.sendGroupUsersUpdateToGroupMembers(group); + }, MINIMUM_DISTANCE, GROUP_RADIUS, - (thing: Movable, fromZone: Zone | null, listener: ZoneSocket) => - this.onZoneEnter(thing, fromZone, listener), + (thing: Movable, fromZone: Zone | null, listener: ZoneSocket) => { + this.onZoneEnter(thing, fromZone, listener); + }, (thing: Movable, position: PositionInterface, listener: ZoneSocket) => this.onClientMove(thing, position, listener), (thing: Movable, newZone: Zone | null, listener: ZoneSocket) => this.onClientLeave(thing, newZone, listener), (emoteEventMessage: EmoteEventMessage, listener: ZoneSocket) => this.onEmote(emoteEventMessage, listener), + (groupId: number, listener: ZoneSocket) => { + void this.onLockGroup(groupId, listener, roomPromise); + }, (playerDetailsUpdatedMessage: PlayerDetailsUpdatedMessage, listener: ZoneSocket) => this.onPlayerDetailsUpdated(playerDetailsUpdatedMessage, listener) ) @@ -381,10 +393,24 @@ export class SocketManager { emitZoneMessage(subMessage, client); } + private async onLockGroup( + groupId: number, + client: ZoneSocket, + roomPromise: PromiseLike | undefined + ): Promise { + if (!roomPromise) { + return; + } + const group = (await roomPromise).getGroupById(groupId); + if (!group) { + return; + } + this.emitCreateUpdateGroupEvent(client, null, group); + } + private onPlayerDetailsUpdated(playerDetailsUpdatedMessage: PlayerDetailsUpdatedMessage, client: ZoneSocket) { const subMessage = new SubToPusherMessage(); subMessage.setPlayerdetailsupdatedmessage(playerDetailsUpdatedMessage); - emitZoneMessage(subMessage, client); } @@ -398,6 +424,7 @@ export class SocketManager { groupUpdateMessage.setPosition(pointMessage); groupUpdateMessage.setGroupsize(group.getSize); groupUpdateMessage.setFromzone(this.toProtoZone(fromZone)); + groupUpdateMessage.setLocked(group.isLocked()); const subMessage = new SubToPusherMessage(); subMessage.setGroupupdatezonemessage(groupUpdateMessage); @@ -413,7 +440,6 @@ export class SocketManager { const subMessage = new SubToPusherMessage(); subMessage.setGroupleftzonemessage(groupDeleteMessage); - emitZoneMessage(subMessage, client); //user.emitInBatch(subMessage); } @@ -425,7 +451,6 @@ export class SocketManager { const subMessage = new SubToPusherMessage(); subMessage.setUserleftzonemessage(userLeftMessage); - emitZoneMessage(subMessage, client); } @@ -439,6 +464,19 @@ export class SocketManager { return undefined; } + private sendGroupUsersUpdateToGroupMembers(group: Group) { + const groupUserUpdateMessage = new GroupUsersUpdateMessage(); + groupUserUpdateMessage.setGroupid(group.getId()); + groupUserUpdateMessage.setUseridsList(group.getUsers().map((user) => user.id)); + + const clientMessage = new ServerToClientMessage(); + clientMessage.setGroupusersupdatemessage(groupUserUpdateMessage); + + group.getUsers().forEach((currentUser: User) => { + currentUser.socket.write(clientMessage); + }); + } + private joinWebRtcRoom(user: User, group: Group) { for (const otherUser of group.getUsers()) { if (user === otherUser) { @@ -634,6 +672,7 @@ export class SocketManager { const groupUpdateMessage = new GroupUpdateZoneMessage(); groupUpdateMessage.setGroupid(thing.getId()); groupUpdateMessage.setPosition(ProtobufUtils.toPointMessage(thing.getPosition())); + groupUpdateMessage.setLocked(thing.isLocked()); const subMessage = new SubToPusherMessage(); subMessage.setGroupupdatezonemessage(groupUpdateMessage); @@ -870,6 +909,15 @@ export class SocketManager { leader?.delFollower(user); } } + + handleLockGroupPromptMessage(room: GameRoom, user: User, message: LockGroupPromptMessage) { + const group = user.group; + if (!group) { + return; + } + group.lock(message.getLock()); + room.emitLockGroupEvent(user, group.getId()); + } } export const socketManager = new SocketManager(); diff --git a/back/tests/GameRoomTest.ts b/back/tests/GameRoomTest.ts index d4e83daf..fb9f09fb 100644 --- a/back/tests/GameRoomTest.ts +++ b/back/tests/GameRoomTest.ts @@ -52,6 +52,7 @@ describe("GameRoom", () => { () => {}, () => {}, emote, + () => {}, () => {} ); @@ -88,6 +89,7 @@ describe("GameRoom", () => { () => {}, () => {}, emote, + () => {}, () => {} ); @@ -128,6 +130,7 @@ describe("GameRoom", () => { () => {}, () => {}, emote, + () => {}, () => {} ); diff --git a/back/tests/PositionNotifierTest.ts b/back/tests/PositionNotifierTest.ts index bf7ddd6e..f21bb4b2 100644 --- a/back/tests/PositionNotifierTest.ts +++ b/back/tests/PositionNotifierTest.ts @@ -25,6 +25,7 @@ describe("PositionNotifier", () => { leaveTriggered = true; }, () => {}, + () => {}, () => {} ); @@ -132,6 +133,7 @@ describe("PositionNotifier", () => { leaveTriggered = true; }, () => {}, + () => {}, () => {} ); diff --git a/desktop/electron/yarn.lock b/desktop/electron/yarn.lock index 82034d10..32dd7fe9 100644 --- a/desktop/electron/yarn.lock +++ b/desktop/electron/yarn.lock @@ -295,9 +295,9 @@ ansi-escapes@^4.2.1: type-fest "^0.21.3" ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" + integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== ansi-regex@^5.0.1: version "5.0.1" @@ -2409,9 +2409,9 @@ pirates@^4.0.1: integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== plist@^3.0.1, plist@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.4.tgz#a62df837e3aed2bb3b735899d510c4f186019cbe" - integrity sha512-ksrr8y9+nXOxQB2osVNqrgvX/XQPOXaU4BQMKjYq8PvaY1U18mo+fKgBSwzK+luSyinOuPae956lSVcBwxlAMg== + version "3.0.5" + resolved "https://registry.yarnpkg.com/plist/-/plist-3.0.5.tgz#2cbeb52d10e3cdccccf0c11a63a85d830970a987" + integrity sha512-83vX4eYdQp3vP9SxuYgEM/G/pJQqLUz/V/xzPrzruLs7fz7jxGQ1msZ/mg1nwZxUSuOp4sb+/bEIbRrbzZRxDA== dependencies: base64-js "^1.5.1" xmlbuilder "^9.0.7" diff --git a/desktop/local-app/yarn.lock b/desktop/local-app/yarn.lock index 03b6bbab..52bbce0a 100644 --- a/desktop/local-app/yarn.lock +++ b/desktop/local-app/yarn.lock @@ -587,9 +587,9 @@ minimatch@^3.0.4: brace-expansion "^1.1.7" minimist@^1.2.0, minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== mkdirp@^0.5.1: version "0.5.5" diff --git a/front/src/Components/ActionsMenu/ActionsMenu.svelte b/front/src/Components/ActionsMenu/ActionsMenu.svelte index c1eb317a..d6eda249 100644 --- a/front/src/Components/ActionsMenu/ActionsMenu.svelte +++ b/front/src/Components/ActionsMenu/ActionsMenu.svelte @@ -77,6 +77,7 @@ height: max-content !important; max-height: 40vh; margin-top: 200px; + z-index: 425; pointer-events: auto; font-family: "Press Start 2P"; diff --git a/front/src/Components/CameraControls.svelte b/front/src/Components/CameraControls.svelte index bfe1d9d0..b9cb7801 100644 --- a/front/src/Components/CameraControls.svelte +++ b/front/src/Components/CameraControls.svelte @@ -10,12 +10,14 @@ import layoutPresentationImg from "./images/layout-presentation.svg"; import layoutChatImg from "./images/layout-chat.svg"; import followImg from "./images/follow.svg"; + import lockImg from "./images/lock.svg"; import { LayoutMode } from "../WebRtc/LayoutManager"; import { peerStore } from "../Stores/PeerStore"; import { onDestroy } from "svelte"; import { embedScreenLayout } from "../Stores/EmbedScreensStore"; import { followRoleStore, followStateStore, followUsersStore } from "../Stores/FollowStore"; import { gameManager } from "../Phaser/Game/GameManager"; + import { currentPlayerGroupLockStateStore } from "../Stores/CurrentPlayerGroupStore"; const gameScene = gameManager.getCurrentGameScene(); @@ -70,6 +72,10 @@ } } + function lockClick() { + gameScene.connection?.emitLockGroup(!$currentPlayerGroupLockStateStore); + } + let isSilent: boolean; const unsubscribeIsSilent = isSilentStore.subscribe((value) => { isSilent = value; @@ -95,6 +101,15 @@ +
+ +
+
diff --git a/front/src/Components/MainLayout.svelte b/front/src/Components/MainLayout.svelte index f1200640..01e45322 100644 --- a/front/src/Components/MainLayout.svelte +++ b/front/src/Components/MainLayout.svelte @@ -54,6 +54,7 @@ }); +
+ +
+

{$LL.menu.settings.privacySettings.title()}

+

{$LL.menu.settings.privacySettings.explanation()}

+ + +

{$LL.menu.settings.save.warning()}

@@ -234,12 +262,15 @@ outline: none; } } + section.settings-section-save { text-align: center; + p { margin: 16px 0; } } + section.settings-section-noSaveOption { display: flex; align-items: center; diff --git a/front/src/Components/VisitCard/VisitCard.svelte b/front/src/Components/VisitCard/VisitCard.svelte index aed71d00..fdb2374f 100644 --- a/front/src/Components/VisitCard/VisitCard.svelte +++ b/front/src/Components/VisitCard/VisitCard.svelte @@ -76,6 +76,7 @@ transform: translate(-50%, 0); margin-top: 200px; max-width: 80vw; + z-index: 350; iframe { border: 0; diff --git a/front/src/Components/images/lock.svg b/front/src/Components/images/lock.svg new file mode 100644 index 00000000..682590f9 --- /dev/null +++ b/front/src/Components/images/lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/front/src/Connexion/AdminMessagesService.ts b/front/src/Connexion/AdminMessagesService.ts index 4b7030ed..22bdd469 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/ts-proto-generated/messages"; +import type { BanUserMessage, SendUserMessage } from "../Messages/ts-proto-generated/protos/messages"; export enum AdminMessageEventTypes { admin = "message", diff --git a/front/src/Connexion/ConnectionManager.ts b/front/src/Connexion/ConnectionManager.ts index 09e7257d..c0c9597c 100644 --- a/front/src/Connexion/ConnectionManager.ts +++ b/front/src/Connexion/ConnectionManager.ts @@ -88,8 +88,7 @@ class ConnectionManager { * @return returns a promise to the Room we are going to load OR a pointer to the URL we must redirect to if authentication is needed. */ public async initGameConnexion(): Promise { - const connexionType = urlManager.getGameConnexionType(); - this.connexionType = connexionType; + this.connexionType = urlManager.getGameConnexionType(); this._currentRoom = null; const urlParams = new URLSearchParams(window.location.search); @@ -102,14 +101,15 @@ class ConnectionManager { urlParams.delete("token"); } - if (connexionType === GameConnexionTypes.login) { + if (this.connexionType === GameConnexionTypes.login) { this._currentRoom = await Room.createRoom(new URL(localUserStore.getLastRoomUrl())); const redirect = this.loadOpenIDScreen(); if (redirect !== null) { return redirect; } urlManager.pushRoomIdToUrl(this._currentRoom); - } else if (connexionType === GameConnexionTypes.jwt) { + } else if (this.connexionType === GameConnexionTypes.jwt) { + /** @deprecated */ if (!token) { const code = urlParams.get("code"); const state = urlParams.get("state"); @@ -135,8 +135,9 @@ class ConnectionManager { return redirect; } urlManager.pushRoomIdToUrl(this._currentRoom); - } else if (connexionType === GameConnexionTypes.register) { - //@deprecated + } + //@deprecated + else if (this.connexionType === GameConnexionTypes.register) { const organizationMemberToken = urlManager.getOrganizationToken(); const data = await Axios.post(`${PUSHER_URL}/register`, { organizationMemberToken }).then( (res) => res.data @@ -165,11 +166,11 @@ class ConnectionManager { ) ); urlManager.pushRoomIdToUrl(this._currentRoom); - } else if (connexionType === GameConnexionTypes.room || connexionType === GameConnexionTypes.empty) { + } else if (this.connexionType === GameConnexionTypes.room || this.connexionType === GameConnexionTypes.empty) { this.authToken = localUserStore.getAuthToken(); let roomPath: string; - if (connexionType === GameConnexionTypes.empty) { + if (this.connexionType === GameConnexionTypes.empty) { roomPath = localUserStore.getLastRoomUrl(); //get last room path from cache api try { diff --git a/front/src/Connexion/ConnexionModels.ts b/front/src/Connexion/ConnexionModels.ts index c681fd37..84ce60c1 100644 --- a/front/src/Connexion/ConnexionModels.ts +++ b/front/src/Connexion/ConnexionModels.ts @@ -43,7 +43,13 @@ export interface PositionInterface { export interface GroupCreatedUpdatedMessageInterface { position: PositionInterface; groupId: number; - groupSize: number; + groupSize?: number; + locked?: boolean; +} + +export interface GroupUsersUpdateMessageInterface { + groupId: number; + userIds: number[]; } export interface WebRtcDisconnectMessageInterface { diff --git a/front/src/Connexion/LocalUserStore.ts b/front/src/Connexion/LocalUserStore.ts index 7753fd7b..fafeb10d 100644 --- a/front/src/Connexion/LocalUserStore.ts +++ b/front/src/Connexion/LocalUserStore.ts @@ -25,11 +25,14 @@ const code = "code"; const cameraSetup = "cameraSetup"; const cacheAPIIndex = "workavdenture-cache"; const userProperties = "user-properties"; +const cameraPrivacySettings = "cameraPrivacySettings"; +const microphonePrivacySettings = "microphonePrivacySettings"; class LocalUserStore { saveUser(localUser: LocalUser) { localStorage.setItem("localUser", JSON.stringify(localUser)); } + getLocalUser(): LocalUser | null { const data = localStorage.getItem("localUser"); return data ? JSON.parse(data) : null; @@ -38,6 +41,7 @@ class LocalUserStore { setName(name: string): void { localStorage.setItem(playerNameKey, name); } + getName(): string | null { const value = localStorage.getItem(playerNameKey) || ""; return isUserNameValid(value) ? value : null; @@ -46,6 +50,7 @@ class LocalUserStore { setPlayerCharacterIndex(playerCharacterIndex: number): void { localStorage.setItem(selectedPlayerKey, "" + playerCharacterIndex); } + getPlayerCharacterIndex(): number { return parseInt(localStorage.getItem(selectedPlayerKey) || ""); } @@ -53,6 +58,7 @@ class LocalUserStore { setCustomCursorPosition(activeRow: number, selectedLayers: number[]): void { localStorage.setItem(customCursorPositionKey, JSON.stringify({ activeRow, selectedLayers })); } + getCustomCursorPosition(): { activeRow: number; selectedLayers: number[] } | null { return JSON.parse(localStorage.getItem(customCursorPositionKey) || "null"); } @@ -60,6 +66,7 @@ class LocalUserStore { setCharacterLayers(layers: string[]): void { localStorage.setItem(characterLayersKey, JSON.stringify(layers)); } + getCharacterLayers(): string[] | null { const value = JSON.parse(localStorage.getItem(characterLayersKey) || "null"); return areCharacterLayersValid(value) ? value : null; @@ -68,6 +75,7 @@ class LocalUserStore { setCompanion(companion: string | null): void { return localStorage.setItem(companionKey, JSON.stringify(companion)); } + getCompanion(): string | null { const companion = JSON.parse(localStorage.getItem(companionKey) || "null"); @@ -77,6 +85,7 @@ class LocalUserStore { return companion; } + wasCompanionSet(): boolean { return localStorage.getItem(companionKey) ? true : false; } @@ -84,6 +93,7 @@ class LocalUserStore { setGameQualityValue(value: number): void { localStorage.setItem(gameQualityKey, "" + value); } + getGameQualityValue(): number { return parseInt(localStorage.getItem(gameQualityKey) || "60"); } @@ -91,6 +101,7 @@ class LocalUserStore { setVideoQualityValue(value: number): void { localStorage.setItem(videoQualityKey, "" + value); } + getVideoQualityValue(): number { return parseInt(localStorage.getItem(videoQualityKey) || "20"); } @@ -98,6 +109,7 @@ class LocalUserStore { setAudioPlayerVolume(value: number): void { localStorage.setItem(audioPlayerVolumeKey, "" + value); } + getAudioPlayerVolume(): number { return parseFloat(localStorage.getItem(audioPlayerVolumeKey) || "1"); } @@ -105,6 +117,7 @@ class LocalUserStore { setAudioPlayerMuted(value: boolean): void { localStorage.setItem(audioPlayerMuteKey, value.toString()); } + getAudioPlayerMuted(): boolean { return localStorage.getItem(audioPlayerMuteKey) === "true"; } @@ -112,6 +125,7 @@ class LocalUserStore { setHelpCameraSettingsShown(): void { localStorage.setItem(helpCameraSettingsShown, "1"); } + getHelpCameraSettingsShown(): boolean { return localStorage.getItem(helpCameraSettingsShown) === "1"; } @@ -119,6 +133,7 @@ class LocalUserStore { setFullscreen(value: boolean): void { localStorage.setItem(fullscreenKey, value.toString()); } + getFullscreen(): boolean { return localStorage.getItem(fullscreenKey) === "true"; } @@ -126,6 +141,7 @@ class LocalUserStore { setForceCowebsiteTrigger(value: boolean): void { localStorage.setItem(forceCowebsiteTriggerKey, value.toString()); } + getForceCowebsiteTrigger(): boolean { return localStorage.getItem(forceCowebsiteTriggerKey) === "true"; } @@ -133,6 +149,7 @@ class LocalUserStore { setIgnoreFollowRequests(value: boolean): void { localStorage.setItem(ignoreFollowRequests, value.toString()); } + getIgnoreFollowRequests(): boolean { return localStorage.getItem(ignoreFollowRequests) === "true"; } @@ -155,11 +172,13 @@ class LocalUserStore { } } } + getLastRoomUrl(): string { return ( localStorage.getItem(lastRoomUrl) ?? window.location.protocol + "//" + window.location.host + START_ROOM_URL ); } + getLastRoomUrlCacheApi(): Promise { if (!("caches" in window)) { return Promise.resolve(undefined); @@ -176,6 +195,7 @@ class LocalUserStore { setAuthToken(value: string | null) { value ? localStorage.setItem(authToken, value) : localStorage.removeItem(authToken); } + getAuthToken(): string | null { return localStorage.getItem(authToken); } @@ -202,23 +222,29 @@ class LocalUserStore { } return oldValue === value; } + setState(value: string) { localStorage.setItem(state, value); } + getState(): string | null { return localStorage.getItem(state); } + generateNonce(): string { const newNonce = uuidv4(); localStorage.setItem(nonce, newNonce); return newNonce; } + getNonce(): string | null { return localStorage.getItem(nonce); } + setCode(value: string): void { localStorage.setItem(code, value); } + getCode(): string | null { return localStorage.getItem(code); } @@ -226,11 +252,36 @@ class LocalUserStore { setCameraSetup(cameraId: string) { localStorage.setItem(cameraSetup, cameraId); } + getCameraSetup(): { video: unknown; audio: unknown } | undefined { const cameraSetupValues = localStorage.getItem(cameraSetup); return cameraSetupValues != undefined ? JSON.parse(cameraSetupValues) : undefined; } + setCameraPrivacySettings(option: boolean) { + localStorage.setItem(cameraPrivacySettings, option.toString()); + } + + getCameraPrivacySettings() { + //if this setting doesn't exist in LocalUserStore, we set a default value + if (localStorage.getItem(cameraPrivacySettings) == null) { + localStorage.setItem(cameraPrivacySettings, "false"); + } + return localStorage.getItem(cameraPrivacySettings) === "true"; + } + + setMicrophonePrivacySettings(option: boolean) { + localStorage.setItem(microphonePrivacySettings, option.toString()); + } + + getMicrophonePrivacySettings() { + //if this setting doesn't exist in LocalUserStore, we set a default value + if (localStorage.getItem(microphonePrivacySettings) == null) { + localStorage.setItem(microphonePrivacySettings, "true"); + } + return localStorage.getItem(microphonePrivacySettings) === "true"; + } + getAllUserProperties(): Map { const result = new Map(); for (let i = 0; i < localStorage.length; i++) { diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index ed982536..5cfc85c1 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -5,47 +5,35 @@ import type { UserSimplePeerInterface } from "../WebRtc/SimplePeer"; import { ProtobufClientUtils } from "../Network/ProtobufClientUtils"; import type { GroupCreatedUpdatedMessageInterface, - ItemEventMessageInterface, + GroupUsersUpdateMessageInterface, MessageUserJoined, - OnConnectInterface, - PlayerDetailsUpdatedMessageInterface, PlayGlobalMessageInterface, PositionInterface, RoomJoinedMessageInterface, ViewportInterface, - WebRtcDisconnectMessageInterface, WebRtcSignalReceivedMessageInterface, } from "./ConnexionModels"; import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures"; import { adminMessagesService } from "./AdminMessagesService"; import { connectionManager } from "./ConnectionManager"; import { get } from "svelte/store"; +import { followRoleStore, followUsersStore } from "../Stores/FollowStore"; import { menuIconVisiblilityStore, menuVisiblilityStore, 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, @@ -53,10 +41,8 @@ import { SetPlayerDetailsMessage as SetPlayerDetailsMessageTsProto, PingMessage as PingMessageTsProto, CharacterLayerMessage, -} from "../Messages/ts-proto-generated/messages"; +} from "../Messages/ts-proto-generated/protos/messages"; import { Subject } from "rxjs"; -import { OpenPopupEvent } from "../Api/Events/OpenPopupEvent"; -import { match } from "assert"; import { selectCharacterSceneVisibleStore } from "../Stores/SelectCharacterStore"; import { gameManager } from "../Phaser/Game/GameManager"; import { SelectCharacterScene, SelectCharacterSceneName } from "../Phaser/Login/SelectCharacterScene"; @@ -116,6 +102,9 @@ export class RoomConnection implements RoomConnection { private readonly _groupUpdateMessageStream = new Subject(); public readonly groupUpdateMessageStream = this._groupUpdateMessageStream.asObservable(); + private readonly _groupUsersUpdateMessageStream = new Subject(); + public readonly groupUsersUpdateMessageStream = this._groupUsersUpdateMessageStream.asObservable(); + private readonly _groupDeleteMessageStream = new Subject(); public readonly groupDeleteMessageStream = this._groupDeleteMessageStream.asObservable(); @@ -443,6 +432,10 @@ export class RoomConnection implements RoomConnection { this._sendJitsiJwtMessageStream.next(message.sendJitsiJwtMessage); break; } + case "groupUsersUpdateMessage": { + this._groupUsersUpdateMessageStream.next(message.groupUsersUpdateMessage); + break; + } case "sendUserMessage": { adminMessagesService.onSendusermessage(message.sendUserMessage); break; @@ -675,6 +668,7 @@ export class RoomConnection implements RoomConnection { groupId: message.groupId, position: position, groupSize: message.groupSize, + locked: message.locked, }; } @@ -890,6 +884,19 @@ export class RoomConnection implements RoomConnection { this.socket.send(bytes); } + public emitLockGroup(lock: boolean = true): void { + const bytes = ClientToServerMessageTsProto.encode({ + message: { + $case: "lockGroupPromptMessage", + lockGroupPromptMessage: { + lock, + }, + }, + }).finish(); + + this.socket.send(bytes); + } + public getAllTags(): string[] { return this.tags; } diff --git a/front/src/Network/ProtobufClientUtils.ts b/front/src/Network/ProtobufClientUtils.ts index 3e172d0f..beec3d9f 100644 --- a/front/src/Network/ProtobufClientUtils.ts +++ b/front/src/Network/ProtobufClientUtils.ts @@ -1,4 +1,4 @@ -import { PositionMessage, PositionMessage_Direction } from "../Messages/ts-proto-generated/messages"; +import { PositionMessage, PositionMessage_Direction } from "../Messages/ts-proto-generated/protos/messages"; import type { PointInterface } from "../Connexion/ConnexionModels"; diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index eb402e97..66a2d8eb 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -76,6 +76,7 @@ import { userIsAdminStore } from "../../Stores/GameStore"; import { contactPageStore } from "../../Stores/MenuStore"; import type { WasCameraUpdatedEvent } from "../../Api/Events/WasCameraUpdatedEvent"; import { audioManagerFileStore } from "../../Stores/AudioManagerStore"; +import { currentPlayerGroupLockStateStore } from "../../Stores/CurrentPlayerGroupStore"; import EVENT_TYPE = Phaser.Scenes.Events; import Texture = Phaser.Textures.Texture; @@ -177,6 +178,7 @@ export class GameScene extends DirtyScene { private volumeStoreUnsubscribers: Map = new Map(); private localVolumeStoreUnsubscriber: Unsubscriber | undefined; private followUsersColorStoreUnsubscribe!: Unsubscriber; + private currentPlayerGroupIdStoreUnsubscribe!: Unsubscriber; private biggestAvailableAreaStoreUnsubscribe!: () => void; MapUrlFile: string; @@ -218,6 +220,7 @@ export class GameScene extends DirtyScene { private loader: Loader; private lastCameraEvent: WasCameraUpdatedEvent | undefined; private firstCameraUpdateSent: boolean = false; + private currentPlayerGroupId?: number; public readonly superLoad: SuperLoaderPlugin; constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) { @@ -839,6 +842,10 @@ export class GameScene extends DirtyScene { }); }); + this.connection.groupUsersUpdateMessageStream.subscribe((message) => { + this.currentPlayerGroupId = message.groupId; + }); + /** * Triggered when we receive the JWT token to connect to Jitsi */ @@ -969,7 +976,9 @@ export class GameScene extends DirtyScene { context.arc(48, 48, 48, 0, 2 * Math.PI, false); // context.lineWidth = 5; context.strokeStyle = "#ffffff"; + context.fillStyle = "#ffffff44"; context.stroke(); + context.fill(); this.circleTexture.refresh(); //create red circle canvas use to create sprite @@ -979,7 +988,9 @@ export class GameScene extends DirtyScene { contextRed.arc(48, 48, 48, 0, 2 * Math.PI, false); //context.lineWidth = 5; contextRed.strokeStyle = "#ff0000"; + contextRed.fillStyle = "#ff000044"; contextRed.stroke(); + contextRed.fill(); this.circleRedTexture.refresh(); } @@ -1849,12 +1860,14 @@ ${escapedMessage} case "GroupCreatedUpdatedEvent": this.doShareGroupPosition(event.event); break; - case "DeleteGroupEvent": - this.doDeleteGroup(event.groupId); - break; case "PlayerDetailsUpdated": this.doUpdatePlayerDetails(event.details); break; + case "DeleteGroupEvent": { + this.doDeleteGroup(event.groupId); + currentPlayerGroupLockStateStore.set(undefined); + break; + } default: { const tmp: never = event; } @@ -2028,11 +2041,16 @@ ${escapedMessage} this, Math.round(groupPositionMessage.position.x), Math.round(groupPositionMessage.position.y), - groupPositionMessage.groupSize === MAX_PER_GROUP ? "circleSprite-red" : "circleSprite-white" + groupPositionMessage.groupSize === MAX_PER_GROUP || groupPositionMessage.locked + ? "circleSprite-red" + : "circleSprite-white" ); sprite.setDisplayOrigin(48, 48); this.add.existing(sprite); this.groups.set(groupPositionMessage.groupId, sprite); + if (this.currentPlayerGroupId === groupPositionMessage.groupId) { + currentPlayerGroupLockStateStore.set(groupPositionMessage.locked); + } return sprite; } diff --git a/front/src/Phaser/UserInput/UserInputManager.ts b/front/src/Phaser/UserInput/UserInputManager.ts index e7f814b9..b454de56 100644 --- a/front/src/Phaser/UserInput/UserInputManager.ts +++ b/front/src/Phaser/UserInput/UserInputManager.ts @@ -280,6 +280,9 @@ export class UserInputManager { ); this.scene.input.keyboard.on("keyup-SPACE", (event: Event) => { + if (this.isInputDisabled) { + return; + } this.userInputHandler.handleSpaceKeyUpEvent(event); }); } diff --git a/front/src/Stores/CurrentPlayerGroupStore.ts b/front/src/Stores/CurrentPlayerGroupStore.ts new file mode 100644 index 00000000..91d4b50e --- /dev/null +++ b/front/src/Stores/CurrentPlayerGroupStore.ts @@ -0,0 +1,3 @@ +import { writable } from "svelte/store"; + +export const currentPlayerGroupLockStateStore = writable(undefined); diff --git a/front/src/Stores/MediaStore.ts b/front/src/Stores/MediaStore.ts index b86a97ce..557cbcd8 100644 --- a/front/src/Stores/MediaStore.ts +++ b/front/src/Stores/MediaStore.ts @@ -11,7 +11,7 @@ import { peerStore } from "./PeerStore"; import { privacyShutdownStore } from "./PrivacyShutdownStore"; import { MediaStreamConstraintsError } from "./Errors/MediaStreamConstraintsError"; import { SoundMeter } from "../Phaser/Components/SoundMeter"; -import { AudioContext } from "standardized-audio-context"; +import { visibilityStore } from "./VisibilityStore"; /** * A store that contains the camera state requested by the user (on or off). @@ -242,6 +242,7 @@ export const mediaStreamConstraintsStore = derived( privacyShutdownStore, cameraEnergySavingStore, isSilentStore, + visibilityStore, ], ( [ @@ -254,6 +255,7 @@ export const mediaStreamConstraintsStore = derived( $privacyShutdownStore, $cameraEnergySavingStore, $isSilentStore, + $visibilityStore, ], set ) => { @@ -292,7 +294,14 @@ export const mediaStreamConstraintsStore = derived( // Disable webcam for privacy reasons (the game is not visible and we were talking to no one) if ($privacyShutdownStore === true) { - currentVideoConstraint = false; + const userMicrophonePrivacySetting = localUserStore.getMicrophonePrivacySettings(); + const userCameraPrivacySetting = localUserStore.getCameraPrivacySettings(); + if (!userMicrophonePrivacySetting) { + currentAudioConstraint = false; + } + if (!userCameraPrivacySetting) { + currentVideoConstraint = false; + } } // Disable webcam for energy reasons (the user is not moving and we are talking to no one) diff --git a/front/src/Url/UrlManager.ts b/front/src/Url/UrlManager.ts index e881b167..a7abbadd 100644 --- a/front/src/Url/UrlManager.ts +++ b/front/src/Url/UrlManager.ts @@ -3,10 +3,10 @@ import { localUserStore } from "../Connexion/LocalUserStore"; export enum GameConnexionTypes { room = 1, - register, + register /*@deprecated*/, empty, unknown, - jwt, + jwt /*@deprecated*/, login, } @@ -16,11 +16,15 @@ class UrlManager { const url = window.location.pathname.toString(); if (url === "/login") { return GameConnexionTypes.login; - } else if (url === "/jwt") { + } + //@deprecated jwt url will be replace by "?token=" + else if (url === "/jwt") { return GameConnexionTypes.jwt; } else if (url.includes("_/") || url.includes("*/") || url.includes("@/")) { return GameConnexionTypes.room; - } else if (url.includes("register/")) { + } + //@deprecated register url will be replace by "?token=" + else if (url.includes("register/")) { return GameConnexionTypes.register; } else if (url === "/") { return GameConnexionTypes.empty; @@ -29,6 +33,9 @@ class UrlManager { } } + /** + * @deprecated + */ public getOrganizationToken(): string | null { const match = /\/register\/(.+)/.exec(window.location.pathname.toString()); return match ? match[1] : null; diff --git a/front/src/i18n/de-DE/menu.ts b/front/src/i18n/de-DE/menu.ts index c1b7bc18..ffc9823d 100644 --- a/front/src/i18n/de-DE/menu.ts +++ b/front/src/i18n/de-DE/menu.ts @@ -57,6 +57,13 @@ const menu: NonNullable = { language: { title: "Sprache", }, + privacySettings: { + title: "Einstellungen Abwesenheitsmodus", + explanation: + "Falls der WorkAdventure Tab nicht aktiv ist wird in den Abwesenheitsmodus umgeschaltet. Für diesen Modus kann eingestellt werden, ob die Kamera und/oder das Mikrofon deaktiviert sind solange der Tab nicht sichtbar ist.", + cameraToggle: "Kamera", + microphoneToggle: "Mikrofon", + }, save: { warning: "(Das Spiel wird nach dem Speichern neugestartet)", button: "Speichern", diff --git a/front/src/i18n/en-US/menu.ts b/front/src/i18n/en-US/menu.ts index 0883fb15..6eb6de21 100644 --- a/front/src/i18n/en-US/menu.ts +++ b/front/src/i18n/en-US/menu.ts @@ -57,6 +57,13 @@ const menu: BaseTranslation = { language: { title: "Language", }, + privacySettings: { + title: "Away mode settings", + explanation: + 'When the WorkAdventure tab is not visible, it switches to "away mode". In this mode, you can decide to automatically disable your webcam and/or microphone for as long as the tab stays hidden.', + cameraToggle: "Camera", + microphoneToggle: "Microphone", + }, save: { warning: "(Saving these settings will restart the game)", button: "Save", diff --git a/front/src/i18n/fr-FR/menu.ts b/front/src/i18n/fr-FR/menu.ts index f8c58990..1515ea8f 100644 --- a/front/src/i18n/fr-FR/menu.ts +++ b/front/src/i18n/fr-FR/menu.ts @@ -57,6 +57,13 @@ const menu: NonNullable = { language: { title: "Langage", }, + privacySettings: { + title: "Paramètres du mode absent", + explanation: + "Quand l'onglet WorkAdventure n'est pas visible, vous passez en \"mode absent\". Lorsque ce mode est actif, vous pouvez décider de garder vos webcam et/ou micro désactivés tant que vous ne revenez pas sur l'onglet", + cameraToggle: "Camera", + microphoneToggle: "Microphone", + }, save: { warning: "(La sauvegarde de ces paramètres redémarre le jeu)", button: "Sauvegarder", diff --git a/maps/tests/AwayModeSettings/away_mode_settings.json b/maps/tests/AwayModeSettings/away_mode_settings.json new file mode 100644 index 00000000..28c40940 --- /dev/null +++ b/maps/tests/AwayModeSettings/away_mode_settings.json @@ -0,0 +1,97 @@ +{ "compressionlevel":-1, + "height":20, + "infinite":false, + "layers":[ + { + "data":[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":20, + "id":42, + "name":"start", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":20, + "x":0, + "y":0 + }, + { + "data":[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + "height":20, + "id":39, + "name":"floor", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":20, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":43, + "name":"Test", + "objects":[ + { + "height":225.333333333333, + "id":13, + "name":"", + "rotation":0, + "text": + { + "text":"Test: \n- Open two windows (you can use Private Mode) so that you control two Wokas.\n\n- On woka A window: go to Menu > Settings and set your away mode options\n- On woka A window: open a new tab in the browser, so that your WA tab is not visible\n\n- On woka B window: move to woka A to check that the options were applied", + "wrap":true + }, + "type":"", + "visible":true, + "width":434.773333333333, + "x":97.9466666666667, + "y":33.8366666666667 + }, + { + "height":155, + "id":16, + "name":"", + "rotation":0, + "text": + { + "color":"#00007f", + "text":"Reminder: \nThere are 4 cases to test for your away mode (WA tab hidden) settings. \nCamera and microphone stay enabled\nOnly camera stays enabled\nOnly microphone stays enabled\nBoth are disabled", + "wrap":true + }, + "type":"", + "visible":true, + "width":407.4375, + "x":96.9479166666667, + "y":322.5 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":44, + "nextobjectid":17, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.7.2", + "tileheight":32, + "tilesets":[ + { + "columns":11, + "firstgid":1, + "image":"..\/tileset1.png", + "imageheight":352, + "imagewidth":352, + "margin":0, + "name":"tileset1", + "spacing":0, + "tilecount":121, + "tileheight":32, + "tilewidth":32 + }], + "tilewidth":32, + "type":"map", + "version":"1.6", + "width":20 +} \ No newline at end of file diff --git a/maps/tests/index.html b/maps/tests/index.html index 3a1ef520..e625aa6d 100644 --- a/maps/tests/index.html +++ b/maps/tests/index.html @@ -463,6 +463,14 @@ Testing zoom via mouse wheel + + + Success Failure Pending + + + Away mode settings + +