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); }