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