Merge branch 'develop' of github.com:thecodingmachine/workadventure into develop

This commit is contained in:
_Bastler 2022-03-28 17:49:25 +02:00
commit 437b11545a
18 changed files with 237 additions and 42 deletions

View File

@ -6,6 +6,7 @@ import {
EmoteCallback, EmoteCallback,
EntersCallback, EntersCallback,
LeavesCallback, LeavesCallback,
LockGroupCallback,
MovesCallback, MovesCallback,
PlayerDetailsUpdatedCallback, PlayerDetailsUpdatedCallback,
} from "_Model/Zone"; } from "_Model/Zone";
@ -44,7 +45,7 @@ export class GameRoom {
// Users, sorted by ID // Users, sorted by ID
private readonly users = new Map<number, User>(); private readonly users = new Map<number, User>();
private readonly usersByUuid = new Map<string, User>(); private readonly usersByUuid = new Map<string, User>();
private readonly groups = new Set<Group>(); private readonly groups: Map<number, Group> = new Map<number, Group>();
private readonly admins = new Set<Admin>(); private readonly admins = new Set<Admin>();
private itemsState = new Map<number, unknown>(); private itemsState = new Map<number, unknown>();
@ -66,6 +67,7 @@ export class GameRoom {
onMoves: MovesCallback, onMoves: MovesCallback,
onLeaves: LeavesCallback, onLeaves: LeavesCallback,
onEmote: EmoteCallback, onEmote: EmoteCallback,
onLockGroup: LockGroupCallback,
onPlayerDetailsUpdated: PlayerDetailsUpdatedCallback onPlayerDetailsUpdated: PlayerDetailsUpdatedCallback
) { ) {
// A zone is 10 sprites wide. // A zone is 10 sprites wide.
@ -76,6 +78,7 @@ export class GameRoom {
onMoves, onMoves,
onLeaves, onLeaves,
onEmote, onEmote,
onLockGroup,
onPlayerDetailsUpdated onPlayerDetailsUpdated
); );
} }
@ -90,6 +93,7 @@ export class GameRoom {
onMoves: MovesCallback, onMoves: MovesCallback,
onLeaves: LeavesCallback, onLeaves: LeavesCallback,
onEmote: EmoteCallback, onEmote: EmoteCallback,
onLockGroup: LockGroupCallback,
onPlayerDetailsUpdated: PlayerDetailsUpdatedCallback onPlayerDetailsUpdated: PlayerDetailsUpdatedCallback
): Promise<GameRoom> { ): Promise<GameRoom> {
const mapDetails = await GameRoom.getMapDetails(roomUrl); const mapDetails = await GameRoom.getMapDetails(roomUrl);
@ -105,6 +109,7 @@ export class GameRoom {
onMoves, onMoves,
onLeaves, onLeaves,
onEmote, onEmote,
onLockGroup,
onPlayerDetailsUpdated onPlayerDetailsUpdated
); );
@ -244,7 +249,7 @@ export class GameRoom {
this.disconnectCallback, this.disconnectCallback,
this.positionNotifier this.positionNotifier
); );
this.groups.add(group); this.groups.set(group.getId(), group);
} }
} }
} else { } else {
@ -328,7 +333,7 @@ export class GameRoom {
this.disconnectCallback, this.disconnectCallback,
this.positionNotifier this.positionNotifier
); );
this.groups.add(newGroup); this.groups.set(newGroup.getId(), newGroup);
} else { } else {
this.leaveGroup(user); this.leaveGroup(user);
} }
@ -375,10 +380,10 @@ export class GameRoom {
group.leave(user); group.leave(user);
if (group.isEmpty()) { if (group.isEmpty()) {
group.destroy(); 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.`); 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? //todo: is the group garbage collected?
} else { } else {
group.updatePosition(); group.updatePosition();
@ -418,7 +423,7 @@ export class GameRoom {
}); });
this.groups.forEach((group: Group) => { this.groups.forEach((group: Group) => {
if (group.isFull()) { if (group.isFull() || group.isLocked()) {
return; return;
} }
const distance = GameRoom.computeDistanceBetweenPositions(user.getPosition(), group.getPosition()); const distance = GameRoom.computeDistanceBetweenPositions(user.getPosition(), group.getPosition());
@ -544,6 +549,10 @@ export class GameRoom {
this.positionNotifier.emitEmoteEvent(user, emoteEventMessage); this.positionNotifier.emitEmoteEvent(user, emoteEventMessage);
} }
public emitLockGroupEvent(user: User, groupId: number) {
this.positionNotifier.emitLockGroupEvent(user, groupId);
}
public addRoomListener(socket: RoomSocket) { public addRoomListener(socket: RoomSocket) {
this.roomListeners.add(socket); this.roomListeners.add(socket);
} }
@ -657,4 +666,8 @@ export class GameRoom {
const variablesManager = await this.getVariableManager(); const variablesManager = await this.getVariableManager();
return variablesManager.getVariablesForTags(tags); return variablesManager.getVariablesForTags(tags);
} }
public getGroupById(id: number): Group | undefined {
return this.groups.get(id);
}
} }

View File

@ -14,6 +14,7 @@ export class Group implements Movable {
private x!: number; private x!: number;
private y!: number; private y!: number;
private wasDestroyed: boolean = false; private wasDestroyed: boolean = false;
private locked: boolean = false;
private roomId: string; private roomId: string;
private currentZone: Zone | null = null; private currentZone: Zone | null = null;
/** /**
@ -141,15 +142,19 @@ export class Group implements Movable {
return this.users.size >= MAX_PER_GROUP; return this.users.size >= MAX_PER_GROUP;
} }
isLocked(): boolean {
return this.locked;
}
isEmpty(): boolean { isEmpty(): boolean {
return this.users.size <= 1; return this.users.size <= 1;
} }
join(user: User): void { join(user: User): void {
// Broadcast on the right event // Broadcast on the right event
this.connectCallback(user, this);
this.users.add(user); this.users.add(user);
user.group = this; user.group = this;
this.connectCallback(user, this);
} }
leave(user: User): void { leave(user: User): void {
@ -167,6 +172,10 @@ export class Group implements Movable {
this.disconnectCallback(user, this); this.disconnectCallback(user, this);
} }
lock(lock: boolean = true): void {
this.locked = lock;
}
/** /**
* Let's kick everybody out. * Let's kick everybody out.
* Usually used when there is only one user left. * Usually used when there is only one user left.

View File

@ -12,6 +12,7 @@ import {
EmoteCallback, EmoteCallback,
EntersCallback, EntersCallback,
LeavesCallback, LeavesCallback,
LockGroupCallback,
MovesCallback, MovesCallback,
PlayerDetailsUpdatedCallback, PlayerDetailsUpdatedCallback,
Zone, Zone,
@ -50,6 +51,7 @@ export class PositionNotifier {
private onUserMoves: MovesCallback, private onUserMoves: MovesCallback,
private onUserLeaves: LeavesCallback, private onUserLeaves: LeavesCallback,
private onEmote: EmoteCallback, private onEmote: EmoteCallback,
private onLockGroup: LockGroupCallback,
private onPlayerDetailsUpdated: PlayerDetailsUpdatedCallback private onPlayerDetailsUpdated: PlayerDetailsUpdatedCallback
) {} ) {}
@ -111,6 +113,7 @@ export class PositionNotifier {
this.onUserMoves, this.onUserMoves,
this.onUserLeaves, this.onUserLeaves,
this.onEmote, this.onEmote,
this.onLockGroup,
this.onPlayerDetailsUpdated, this.onPlayerDetailsUpdated,
i, i,
j j
@ -137,6 +140,12 @@ export class PositionNotifier {
zone.emitEmoteEvent(emoteEventMessage); 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<User> { public *getAllUsersInSquareAroundZone(zone: Zone): Generator<User> {
const zoneDescriptor = this.getZoneDescriptorFromCoordinates(zone.x, zone.y); const zoneDescriptor = this.getZoneDescriptorFromCoordinates(zone.x, zone.y);
for (const d of getNearbyDescriptorsMatrix(zoneDescriptor)) { for (const d of getNearbyDescriptorsMatrix(zoneDescriptor)) {

View File

@ -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 MovesCallback = (thing: Movable, position: PositionInterface, listener: ZoneSocket) => void;
export type LeavesCallback = (thing: Movable, newZone: Zone | null, listener: ZoneSocket) => void; export type LeavesCallback = (thing: Movable, newZone: Zone | null, listener: ZoneSocket) => void;
export type EmoteCallback = (emoteEventMessage: EmoteEventMessage, listener: ZoneSocket) => void; export type EmoteCallback = (emoteEventMessage: EmoteEventMessage, listener: ZoneSocket) => void;
export type LockGroupCallback = (groupId: number, listener: ZoneSocket) => void;
export type PlayerDetailsUpdatedCallback = ( export type PlayerDetailsUpdatedCallback = (
playerDetailsUpdatedMessage: PlayerDetailsUpdatedMessage, playerDetailsUpdatedMessage: PlayerDetailsUpdatedMessage,
listener: ZoneSocket listener: ZoneSocket
@ -27,6 +28,7 @@ export class Zone {
private onMoves: MovesCallback, private onMoves: MovesCallback,
private onLeaves: LeavesCallback, private onLeaves: LeavesCallback,
private onEmote: EmoteCallback, private onEmote: EmoteCallback,
private onLockGroup: LockGroupCallback,
private onPlayerDetailsUpdated: PlayerDetailsUpdatedCallback, private onPlayerDetailsUpdated: PlayerDetailsUpdatedCallback,
public readonly x: number, public readonly x: number,
public readonly y: 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) { public updatePlayerDetails(user: User, playerDetails: SetPlayerDetailsMessage) {
const playerDetailsUpdatedMessage = new PlayerDetailsUpdatedMessage(); const playerDetailsUpdatedMessage = new PlayerDetailsUpdatedMessage();
playerDetailsUpdatedMessage.setUserid(user.id); playerDetailsUpdatedMessage.setUserid(user.id);

View File

@ -29,6 +29,7 @@ import {
WebRtcSignalToServerMessage, WebRtcSignalToServerMessage,
WorldFullWarningToRoomMessage, WorldFullWarningToRoomMessage,
ZoneMessage, ZoneMessage,
LockGroupPromptMessage,
} from "./Messages/generated/messages_pb"; } from "./Messages/generated/messages_pb";
import { sendUnaryData, ServerDuplexStream, ServerUnaryCall, ServerWritableStream } from "grpc"; import { sendUnaryData, ServerDuplexStream, ServerUnaryCall, ServerWritableStream } from "grpc";
import { socketManager } from "./Services/SocketManager"; import { socketManager } from "./Services/SocketManager";
@ -135,6 +136,12 @@ const roomManager: IRoomManagerServer = {
user, user,
message.getFollowabortmessage() as FollowAbortMessage message.getFollowabortmessage() as FollowAbortMessage
); );
} else if (message.hasLockgrouppromptmessage()) {
socketManager.handleLockGroupPromptMessage(
room,
user,
message.getLockgrouppromptmessage() as LockGroupPromptMessage
);
} else if (message.hasSendusermessage()) { } else if (message.hasSendusermessage()) {
const sendUserMessage = message.getSendusermessage(); const sendUserMessage = message.getSendusermessage();
socketManager.handleSendUserMessage(user, sendUserMessage as SendUserMessage); socketManager.handleSendUserMessage(user, sendUserMessage as SendUserMessage);

View File

@ -38,6 +38,9 @@ import {
SubToPusherRoomMessage, SubToPusherRoomMessage,
SetPlayerDetailsMessage, SetPlayerDetailsMessage,
PlayerDetailsUpdatedMessage, PlayerDetailsUpdatedMessage,
GroupUsersUpdateMessage,
LockGroupPromptMessage,
RoomMessage,
} from "../Messages/generated/messages_pb"; } from "../Messages/generated/messages_pb";
import { User, UserSocket } from "../Model/User"; import { User, UserSocket } from "../Model/User";
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils"; import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
@ -68,7 +71,6 @@ function emitZoneMessage(subMessage: SubToPusherMessage, socket: ZoneSocket): vo
// TODO: should we batch those every 100ms? // TODO: should we batch those every 100ms?
const batchMessage = new BatchToPusherMessage(); const batchMessage = new BatchToPusherMessage();
batchMessage.addPayload(subMessage); batchMessage.addPayload(subMessage);
socket.write(batchMessage); socket.write(batchMessage);
} }
@ -266,18 +268,28 @@ export class SocketManager {
if (roomPromise === undefined) { if (roomPromise === undefined) {
roomPromise = GameRoom.create( roomPromise = GameRoom.create(
roomId, roomId,
(user: User, group: Group) => this.joinWebRtcRoom(user, group), (user: User, group: Group) => {
(user: User, group: Group) => this.disConnectedUser(user, group), this.joinWebRtcRoom(user, group);
this.sendGroupUsersUpdateToGroupMembers(group);
},
(user: User, group: Group) => {
this.disConnectedUser(user, group);
this.sendGroupUsersUpdateToGroupMembers(group);
},
MINIMUM_DISTANCE, MINIMUM_DISTANCE,
GROUP_RADIUS, GROUP_RADIUS,
(thing: Movable, fromZone: Zone | null, listener: ZoneSocket) => (thing: Movable, fromZone: Zone | null, listener: ZoneSocket) => {
this.onZoneEnter(thing, fromZone, listener), this.onZoneEnter(thing, fromZone, listener);
},
(thing: Movable, position: PositionInterface, listener: ZoneSocket) => (thing: Movable, position: PositionInterface, listener: ZoneSocket) =>
this.onClientMove(thing, position, listener), this.onClientMove(thing, position, listener),
(thing: Movable, newZone: Zone | null, listener: ZoneSocket) => (thing: Movable, newZone: Zone | null, listener: ZoneSocket) =>
this.onClientLeave(thing, newZone, listener), this.onClientLeave(thing, newZone, listener),
(emoteEventMessage: EmoteEventMessage, listener: ZoneSocket) => (emoteEventMessage: EmoteEventMessage, listener: ZoneSocket) =>
this.onEmote(emoteEventMessage, listener), this.onEmote(emoteEventMessage, listener),
(groupId: number, listener: ZoneSocket) => {
void this.onLockGroup(groupId, listener, roomPromise);
},
(playerDetailsUpdatedMessage: PlayerDetailsUpdatedMessage, listener: ZoneSocket) => (playerDetailsUpdatedMessage: PlayerDetailsUpdatedMessage, listener: ZoneSocket) =>
this.onPlayerDetailsUpdated(playerDetailsUpdatedMessage, listener) this.onPlayerDetailsUpdated(playerDetailsUpdatedMessage, listener)
) )
@ -381,10 +393,24 @@ export class SocketManager {
emitZoneMessage(subMessage, client); emitZoneMessage(subMessage, client);
} }
private async onLockGroup(
groupId: number,
client: ZoneSocket,
roomPromise: PromiseLike<GameRoom> | undefined
): Promise<void> {
if (!roomPromise) {
return;
}
const group = (await roomPromise).getGroupById(groupId);
if (!group) {
return;
}
this.emitCreateUpdateGroupEvent(client, null, group);
}
private onPlayerDetailsUpdated(playerDetailsUpdatedMessage: PlayerDetailsUpdatedMessage, client: ZoneSocket) { private onPlayerDetailsUpdated(playerDetailsUpdatedMessage: PlayerDetailsUpdatedMessage, client: ZoneSocket) {
const subMessage = new SubToPusherMessage(); const subMessage = new SubToPusherMessage();
subMessage.setPlayerdetailsupdatedmessage(playerDetailsUpdatedMessage); subMessage.setPlayerdetailsupdatedmessage(playerDetailsUpdatedMessage);
emitZoneMessage(subMessage, client); emitZoneMessage(subMessage, client);
} }
@ -398,6 +424,7 @@ export class SocketManager {
groupUpdateMessage.setPosition(pointMessage); groupUpdateMessage.setPosition(pointMessage);
groupUpdateMessage.setGroupsize(group.getSize); groupUpdateMessage.setGroupsize(group.getSize);
groupUpdateMessage.setFromzone(this.toProtoZone(fromZone)); groupUpdateMessage.setFromzone(this.toProtoZone(fromZone));
groupUpdateMessage.setLocked(group.isLocked());
const subMessage = new SubToPusherMessage(); const subMessage = new SubToPusherMessage();
subMessage.setGroupupdatezonemessage(groupUpdateMessage); subMessage.setGroupupdatezonemessage(groupUpdateMessage);
@ -413,7 +440,6 @@ export class SocketManager {
const subMessage = new SubToPusherMessage(); const subMessage = new SubToPusherMessage();
subMessage.setGroupleftzonemessage(groupDeleteMessage); subMessage.setGroupleftzonemessage(groupDeleteMessage);
emitZoneMessage(subMessage, client); emitZoneMessage(subMessage, client);
//user.emitInBatch(subMessage); //user.emitInBatch(subMessage);
} }
@ -425,7 +451,6 @@ export class SocketManager {
const subMessage = new SubToPusherMessage(); const subMessage = new SubToPusherMessage();
subMessage.setUserleftzonemessage(userLeftMessage); subMessage.setUserleftzonemessage(userLeftMessage);
emitZoneMessage(subMessage, client); emitZoneMessage(subMessage, client);
} }
@ -439,6 +464,19 @@ export class SocketManager {
return undefined; 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) { private joinWebRtcRoom(user: User, group: Group) {
for (const otherUser of group.getUsers()) { for (const otherUser of group.getUsers()) {
if (user === otherUser) { if (user === otherUser) {
@ -634,6 +672,7 @@ export class SocketManager {
const groupUpdateMessage = new GroupUpdateZoneMessage(); const groupUpdateMessage = new GroupUpdateZoneMessage();
groupUpdateMessage.setGroupid(thing.getId()); groupUpdateMessage.setGroupid(thing.getId());
groupUpdateMessage.setPosition(ProtobufUtils.toPointMessage(thing.getPosition())); groupUpdateMessage.setPosition(ProtobufUtils.toPointMessage(thing.getPosition()));
groupUpdateMessage.setLocked(thing.isLocked());
const subMessage = new SubToPusherMessage(); const subMessage = new SubToPusherMessage();
subMessage.setGroupupdatezonemessage(groupUpdateMessage); subMessage.setGroupupdatezonemessage(groupUpdateMessage);
@ -870,6 +909,15 @@ export class SocketManager {
leader?.delFollower(user); 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(); export const socketManager = new SocketManager();

View File

@ -52,6 +52,7 @@ describe("GameRoom", () => {
() => {}, () => {},
() => {}, () => {},
emote, emote,
() => {},
() => {} () => {}
); );
@ -88,6 +89,7 @@ describe("GameRoom", () => {
() => {}, () => {},
() => {}, () => {},
emote, emote,
() => {},
() => {} () => {}
); );
@ -128,6 +130,7 @@ describe("GameRoom", () => {
() => {}, () => {},
() => {}, () => {},
emote, emote,
() => {},
() => {} () => {}
); );

View File

@ -25,6 +25,7 @@ describe("PositionNotifier", () => {
leaveTriggered = true; leaveTriggered = true;
}, },
() => {}, () => {},
() => {},
() => {} () => {}
); );
@ -132,6 +133,7 @@ describe("PositionNotifier", () => {
leaveTriggered = true; leaveTriggered = true;
}, },
() => {}, () => {},
() => {},
() => {} () => {}
); );

View File

@ -10,12 +10,14 @@
import layoutPresentationImg from "./images/layout-presentation.svg"; import layoutPresentationImg from "./images/layout-presentation.svg";
import layoutChatImg from "./images/layout-chat.svg"; import layoutChatImg from "./images/layout-chat.svg";
import followImg from "./images/follow.svg"; import followImg from "./images/follow.svg";
import lockImg from "./images/lock.svg";
import { LayoutMode } from "../WebRtc/LayoutManager"; import { LayoutMode } from "../WebRtc/LayoutManager";
import { peerStore } from "../Stores/PeerStore"; import { peerStore } from "../Stores/PeerStore";
import { onDestroy } from "svelte"; import { onDestroy } from "svelte";
import { embedScreenLayout } from "../Stores/EmbedScreensStore"; import { embedScreenLayout } from "../Stores/EmbedScreensStore";
import { followRoleStore, followStateStore, followUsersStore } from "../Stores/FollowStore"; import { followRoleStore, followStateStore, followUsersStore } from "../Stores/FollowStore";
import { gameManager } from "../Phaser/Game/GameManager"; import { gameManager } from "../Phaser/Game/GameManager";
import { currentPlayerGroupLockStateStore } from "../Stores/CurrentPlayerGroupStore";
const gameScene = gameManager.getCurrentGameScene(); const gameScene = gameManager.getCurrentGameScene();
@ -70,6 +72,10 @@
} }
} }
function lockClick() {
gameScene.connection?.emitLockGroup(!$currentPlayerGroupLockStateStore);
}
let isSilent: boolean; let isSilent: boolean;
const unsubscribeIsSilent = isSilentStore.subscribe((value) => { const unsubscribeIsSilent = isSilentStore.subscribe((value) => {
isSilent = value; isSilent = value;
@ -95,6 +101,15 @@
<img class="noselect" src={followImg} alt="" /> <img class="noselect" src={followImg} alt="" />
</div> </div>
<div
class="btn-lock nes-btn is-dark"
class:hide={$peerStore.size === 0 || isSilent}
class:disabled={$currentPlayerGroupLockStateStore}
on:click={lockClick}
>
<img class="noselect" src={lockImg} alt="" />
</div>
<div <div
class="btn-monitor nes-btn is-dark" class="btn-monitor nes-btn is-dark"
on:click={screenSharingClick} on:click={screenSharingClick}
@ -157,7 +172,7 @@
transform: translateY(15px); transform: translateY(15px);
transition-timing-function: ease-in-out; transition-timing-function: ease-in-out;
transition: all 0.3s; transition: all 0.3s;
margin: 0 4%; margin: 0 2%;
&.hide { &.hide {
transform: translateY(60px); transform: translateY(60px);
@ -205,6 +220,14 @@
} }
} }
.btn-lock {
pointer-events: auto;
img {
filter: brightness(0) invert(1);
}
}
@media (hover: none) { @media (hover: none) {
/** /**
* If we cannot hover over elements, let's display camera button in full. * If we cannot hover over elements, let's display camera button in full.

View File

@ -0,0 +1 @@
<svg fill="#000000" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50" width="50px" height="50px"><path d="M 25 3 C 18.363281 3 13 8.363281 13 15 L 13 20 L 9 20 C 7.355469 20 6 21.355469 6 23 L 6 47 C 6 48.644531 7.355469 50 9 50 L 41 50 C 42.644531 50 44 48.644531 44 47 L 44 23 C 44 21.355469 42.644531 20 41 20 L 37 20 L 37 15 C 37 8.363281 31.636719 3 25 3 Z M 25 5 C 30.566406 5 35 9.433594 35 15 L 35 20 L 15 20 L 15 15 C 15 9.433594 19.433594 5 25 5 Z M 9 22 L 41 22 C 41.554688 22 42 22.445313 42 23 L 42 47 C 42 47.554688 41.554688 48 41 48 L 9 48 C 8.445313 48 8 47.554688 8 47 L 8 23 C 8 22.445313 8.445313 22 9 22 Z M 25 30 C 23.300781 30 22 31.300781 22 33 C 22 33.898438 22.398438 34.6875 23 35.1875 L 23 38 C 23 39.101563 23.898438 40 25 40 C 26.101563 40 27 39.101563 27 38 L 27 35.1875 C 27.601563 34.6875 28 33.898438 28 33 C 28 31.300781 26.699219 30 25 30 Z"/></svg>

After

Width:  |  Height:  |  Size: 891 B

View File

@ -44,6 +44,12 @@ export interface GroupCreatedUpdatedMessageInterface {
position: PositionInterface; position: PositionInterface;
groupId: number; groupId: number;
groupSize: number; groupSize: number;
locked: boolean;
}
export interface GroupUsersUpdateMessageInterface {
groupId: number;
userIds: number[];
} }
export interface WebRtcDisconnectMessageInterface { export interface WebRtcDisconnectMessageInterface {

View File

@ -5,47 +5,35 @@ import type { UserSimplePeerInterface } from "../WebRtc/SimplePeer";
import { ProtobufClientUtils } from "../Network/ProtobufClientUtils"; import { ProtobufClientUtils } from "../Network/ProtobufClientUtils";
import type { import type {
GroupCreatedUpdatedMessageInterface, GroupCreatedUpdatedMessageInterface,
ItemEventMessageInterface, GroupUsersUpdateMessageInterface,
MessageUserJoined, MessageUserJoined,
OnConnectInterface,
PlayerDetailsUpdatedMessageInterface,
PlayGlobalMessageInterface, PlayGlobalMessageInterface,
PositionInterface, PositionInterface,
RoomJoinedMessageInterface, RoomJoinedMessageInterface,
ViewportInterface, ViewportInterface,
WebRtcDisconnectMessageInterface,
WebRtcSignalReceivedMessageInterface, WebRtcSignalReceivedMessageInterface,
} from "./ConnexionModels"; } from "./ConnexionModels";
import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures"; import type { BodyResourceDescriptionInterface } from "../Phaser/Entity/PlayerTextures";
import { adminMessagesService } from "./AdminMessagesService"; import { adminMessagesService } from "./AdminMessagesService";
import { connectionManager } from "./ConnectionManager"; import { connectionManager } from "./ConnectionManager";
import { get } from "svelte/store"; import { get } from "svelte/store";
import { followRoleStore, followUsersStore } from "../Stores/FollowStore";
import { menuIconVisiblilityStore, menuVisiblilityStore, warningContainerStore } from "../Stores/MenuStore"; import { menuIconVisiblilityStore, menuVisiblilityStore, warningContainerStore } from "../Stores/MenuStore";
import { followStateStore, followRoleStore, followUsersStore } from "../Stores/FollowStore";
import { localUserStore } from "./LocalUserStore"; import { localUserStore } from "./LocalUserStore";
import { import {
RefreshRoomMessage,
ServerToClientMessage as ServerToClientMessageTsProto, ServerToClientMessage as ServerToClientMessageTsProto,
TokenExpiredMessage, TokenExpiredMessage,
WorldConnexionMessage, WorldConnexionMessage,
WorldFullMessage,
ErrorMessage as ErrorMessageTsProto, ErrorMessage as ErrorMessageTsProto,
UserMovedMessage as UserMovedMessageTsProto, UserMovedMessage as UserMovedMessageTsProto,
GroupUpdateMessage as GroupUpdateMessageTsProto, GroupUpdateMessage as GroupUpdateMessageTsProto,
GroupDeleteMessage as GroupDeleteMessageTsProto, GroupDeleteMessage as GroupDeleteMessageTsProto,
UserJoinedMessage as UserJoinedMessageTsProto, UserJoinedMessage as UserJoinedMessageTsProto,
UserLeftMessage as UserLeftMessageTsProto, UserLeftMessage as UserLeftMessageTsProto,
ItemEventMessage as ItemEventMessageTsProto,
EmoteEventMessage as EmoteEventMessageTsProto, EmoteEventMessage as EmoteEventMessageTsProto,
VariableMessage as VariableMessageTsProto,
PlayerDetailsUpdatedMessage as PlayerDetailsUpdatedMessageTsProto, PlayerDetailsUpdatedMessage as PlayerDetailsUpdatedMessageTsProto,
WorldFullWarningMessage,
WebRtcDisconnectMessage as WebRtcDisconnectMessageTsProto, WebRtcDisconnectMessage as WebRtcDisconnectMessageTsProto,
PlayGlobalMessage as PlayGlobalMessageTsProto,
StopGlobalMessage as StopGlobalMessageTsProto,
SendJitsiJwtMessage as SendJitsiJwtMessageTsProto, SendJitsiJwtMessage as SendJitsiJwtMessageTsProto,
SendUserMessage as SendUserMessageTsProto,
BanUserMessage as BanUserMessageTsProto,
ClientToServerMessage as ClientToServerMessageTsProto, ClientToServerMessage as ClientToServerMessageTsProto,
PositionMessage as PositionMessageTsProto, PositionMessage as PositionMessageTsProto,
ViewportMessage as ViewportMessageTsProto, ViewportMessage as ViewportMessageTsProto,
@ -55,8 +43,6 @@ import {
CharacterLayerMessage, CharacterLayerMessage,
} from "../Messages/ts-proto-generated/messages"; } from "../Messages/ts-proto-generated/messages";
import { Subject } from "rxjs"; import { Subject } from "rxjs";
import { OpenPopupEvent } from "../Api/Events/OpenPopupEvent";
import { match } from "assert";
import { selectCharacterSceneVisibleStore } from "../Stores/SelectCharacterStore"; import { selectCharacterSceneVisibleStore } from "../Stores/SelectCharacterStore";
import { gameManager } from "../Phaser/Game/GameManager"; import { gameManager } from "../Phaser/Game/GameManager";
import { SelectCharacterScene, SelectCharacterSceneName } from "../Phaser/Login/SelectCharacterScene"; import { SelectCharacterScene, SelectCharacterSceneName } from "../Phaser/Login/SelectCharacterScene";
@ -116,6 +102,9 @@ export class RoomConnection implements RoomConnection {
private readonly _groupUpdateMessageStream = new Subject<GroupCreatedUpdatedMessageInterface>(); private readonly _groupUpdateMessageStream = new Subject<GroupCreatedUpdatedMessageInterface>();
public readonly groupUpdateMessageStream = this._groupUpdateMessageStream.asObservable(); public readonly groupUpdateMessageStream = this._groupUpdateMessageStream.asObservable();
private readonly _groupUsersUpdateMessageStream = new Subject<GroupUsersUpdateMessageInterface>();
public readonly groupUsersUpdateMessageStream = this._groupUsersUpdateMessageStream.asObservable();
private readonly _groupDeleteMessageStream = new Subject<GroupDeleteMessageTsProto>(); private readonly _groupDeleteMessageStream = new Subject<GroupDeleteMessageTsProto>();
public readonly groupDeleteMessageStream = this._groupDeleteMessageStream.asObservable(); public readonly groupDeleteMessageStream = this._groupDeleteMessageStream.asObservable();
@ -443,6 +432,10 @@ export class RoomConnection implements RoomConnection {
this._sendJitsiJwtMessageStream.next(message.sendJitsiJwtMessage); this._sendJitsiJwtMessageStream.next(message.sendJitsiJwtMessage);
break; break;
} }
case "groupUsersUpdateMessage": {
this._groupUsersUpdateMessageStream.next(message.groupUsersUpdateMessage);
break;
}
case "sendUserMessage": { case "sendUserMessage": {
adminMessagesService.onSendusermessage(message.sendUserMessage); adminMessagesService.onSendusermessage(message.sendUserMessage);
break; break;
@ -675,6 +668,7 @@ export class RoomConnection implements RoomConnection {
groupId: message.groupId, groupId: message.groupId,
position: position, position: position,
groupSize: message.groupSize, groupSize: message.groupSize,
locked: message.locked,
}; };
} }
@ -890,6 +884,19 @@ export class RoomConnection implements RoomConnection {
this.socket.send(bytes); 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[] { public getAllTags(): string[] {
return this.tags; return this.tags;
} }

View File

@ -76,6 +76,7 @@ import { userIsAdminStore } from "../../Stores/GameStore";
import { contactPageStore } from "../../Stores/MenuStore"; import { contactPageStore } from "../../Stores/MenuStore";
import type { WasCameraUpdatedEvent } from "../../Api/Events/WasCameraUpdatedEvent"; import type { WasCameraUpdatedEvent } from "../../Api/Events/WasCameraUpdatedEvent";
import { audioManagerFileStore } from "../../Stores/AudioManagerStore"; import { audioManagerFileStore } from "../../Stores/AudioManagerStore";
import { currentPlayerGroupIdStore, currentPlayerGroupLockStateStore } from "../../Stores/CurrentPlayerGroupStore";
import EVENT_TYPE = Phaser.Scenes.Events; import EVENT_TYPE = Phaser.Scenes.Events;
import Texture = Phaser.Textures.Texture; import Texture = Phaser.Textures.Texture;
@ -178,6 +179,7 @@ export class GameScene extends DirtyScene {
private volumeStoreUnsubscribers: Map<number, Unsubscriber> = new Map<number, Unsubscriber>(); private volumeStoreUnsubscribers: Map<number, Unsubscriber> = new Map<number, Unsubscriber>();
private localVolumeStoreUnsubscriber: Unsubscriber | undefined; private localVolumeStoreUnsubscriber: Unsubscriber | undefined;
private followUsersColorStoreUnsubscribe!: Unsubscriber; private followUsersColorStoreUnsubscribe!: Unsubscriber;
private currentPlayerGroupIdStoreUnsubscribe!: Unsubscriber;
private biggestAvailableAreaStoreUnsubscribe!: () => void; private biggestAvailableAreaStoreUnsubscribe!: () => void;
MapUrlFile: string; MapUrlFile: string;
@ -219,6 +221,7 @@ export class GameScene extends DirtyScene {
private loader: Loader; private loader: Loader;
private lastCameraEvent: WasCameraUpdatedEvent | undefined; private lastCameraEvent: WasCameraUpdatedEvent | undefined;
private firstCameraUpdateSent: boolean = false; private firstCameraUpdateSent: boolean = false;
private currentPlayerGroupId?: number;
public readonly superLoad: SuperLoaderPlugin; public readonly superLoad: SuperLoaderPlugin;
constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) { constructor(private room: Room, MapUrlFile: string, customKey?: string | undefined) {
@ -724,6 +727,10 @@ export class GameScene extends DirtyScene {
} }
}); });
this.currentPlayerGroupIdStoreUnsubscribe = currentPlayerGroupIdStore.subscribe((groupId) => {
this.currentPlayerGroupId = groupId;
});
Promise.all([ Promise.all([
this.connectionAnswerPromiseDeferred.promise as Promise<unknown>, this.connectionAnswerPromiseDeferred.promise as Promise<unknown>,
...scriptPromises, ...scriptPromises,
@ -855,6 +862,11 @@ export class GameScene extends DirtyScene {
}); });
}); });
this.connection.groupUsersUpdateMessageStream.subscribe((message) => {
// TODO: how else can we deduce our current group?
currentPlayerGroupIdStore.set(message.groupId);
});
/** /**
* Triggered when we receive the JWT token to connect to Jitsi * Triggered when we receive the JWT token to connect to Jitsi
*/ */
@ -985,7 +997,9 @@ export class GameScene extends DirtyScene {
context.arc(48, 48, 48, 0, 2 * Math.PI, false); context.arc(48, 48, 48, 0, 2 * Math.PI, false);
// context.lineWidth = 5; // context.lineWidth = 5;
context.strokeStyle = "#ffffff"; context.strokeStyle = "#ffffff";
context.fillStyle = "#ffffff44";
context.stroke(); context.stroke();
context.fill();
this.circleTexture.refresh(); this.circleTexture.refresh();
//create red circle canvas use to create sprite //create red circle canvas use to create sprite
@ -995,7 +1009,9 @@ export class GameScene extends DirtyScene {
contextRed.arc(48, 48, 48, 0, 2 * Math.PI, false); contextRed.arc(48, 48, 48, 0, 2 * Math.PI, false);
//context.lineWidth = 5; //context.lineWidth = 5;
contextRed.strokeStyle = "#ff0000"; contextRed.strokeStyle = "#ff0000";
contextRed.fillStyle = "#ff000044";
contextRed.stroke(); contextRed.stroke();
contextRed.fill();
this.circleRedTexture.refresh(); this.circleRedTexture.refresh();
} }
@ -1884,12 +1900,15 @@ export class GameScene extends DirtyScene {
case "GroupCreatedUpdatedEvent": case "GroupCreatedUpdatedEvent":
this.doShareGroupPosition(event.event); this.doShareGroupPosition(event.event);
break; break;
case "DeleteGroupEvent":
this.doDeleteGroup(event.groupId);
break;
case "PlayerDetailsUpdated": case "PlayerDetailsUpdated":
this.doUpdatePlayerDetails(event.details); this.doUpdatePlayerDetails(event.details);
break; break;
case "DeleteGroupEvent": {
this.doDeleteGroup(event.groupId);
currentPlayerGroupIdStore.set(undefined);
currentPlayerGroupLockStateStore.set(undefined);
break;
}
default: { default: {
const tmp: never = event; const tmp: never = event;
} }
@ -2063,11 +2082,16 @@ export class GameScene extends DirtyScene {
this, this,
Math.round(groupPositionMessage.position.x), Math.round(groupPositionMessage.position.x),
Math.round(groupPositionMessage.position.y), 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); sprite.setDisplayOrigin(48, 48);
this.add.existing(sprite); this.add.existing(sprite);
this.groups.set(groupPositionMessage.groupId, sprite); this.groups.set(groupPositionMessage.groupId, sprite);
if (this.currentPlayerGroupId === groupPositionMessage.groupId) {
currentPlayerGroupLockStateStore.set(groupPositionMessage.locked);
}
return sprite; return sprite;
} }

View File

@ -0,0 +1,4 @@
import { writable } from "svelte/store";
export const currentPlayerGroupIdStore = writable<number | undefined>(undefined);
export const currentPlayerGroupLockStateStore = writable<boolean | undefined>(undefined);

View File

@ -99,6 +99,10 @@ message FollowAbortMessage {
int32 follower = 2; int32 follower = 2;
} }
message LockGroupPromptMessage {
bool lock = 1;
}
message ClientToServerMessage { message ClientToServerMessage {
oneof message { oneof message {
UserMovesMessage userMovesMessage = 2; UserMovesMessage userMovesMessage = 2;
@ -117,6 +121,7 @@ message ClientToServerMessage {
FollowRequestMessage followRequestMessage = 15; FollowRequestMessage followRequestMessage = 15;
FollowConfirmationMessage followConfirmationMessage = 16; FollowConfirmationMessage followConfirmationMessage = 16;
FollowAbortMessage followAbortMessage = 17; FollowAbortMessage followAbortMessage = 17;
LockGroupPromptMessage lockGroupPromptMessage = 18;
} }
} }
@ -185,6 +190,7 @@ message GroupUpdateMessage {
int32 groupId = 1; int32 groupId = 1;
PointMessage position = 2; PointMessage position = 2;
int32 groupSize = 3; int32 groupSize = 3;
bool locked = 4;
} }
message GroupDeleteMessage { message GroupDeleteMessage {
@ -216,6 +222,11 @@ message ItemStateMessage {
string stateJson = 2; string stateJson = 2;
} }
message GroupUsersUpdateMessage {
int32 groupId = 1;
repeated int32 userIds = 2;
}
message RoomJoinedMessage { message RoomJoinedMessage {
//repeated UserJoinedMessage user = 1; //repeated UserJoinedMessage user = 1;
//repeated GroupUpdateMessage group = 2; //repeated GroupUpdateMessage group = 2;
@ -316,6 +327,7 @@ message ServerToClientMessage {
FollowConfirmationMessage followConfirmationMessage = 22; FollowConfirmationMessage followConfirmationMessage = 22;
FollowAbortMessage followAbortMessage = 23; FollowAbortMessage followAbortMessage = 23;
InvalidTextureMessage invalidTextureMessage = 24; InvalidTextureMessage invalidTextureMessage = 24;
GroupUsersUpdateMessage groupUsersUpdateMessage = 25;
} }
} }
@ -358,6 +370,7 @@ message GroupUpdateZoneMessage {
PointMessage position = 2; PointMessage position = 2;
int32 groupSize = 3; int32 groupSize = 3;
Zone fromZone = 4; Zone fromZone = 4;
bool locked = 5;
} }
message GroupLeftZoneMessage { message GroupLeftZoneMessage {
@ -403,6 +416,7 @@ message PusherToBackMessage {
FollowRequestMessage followRequestMessage = 16; FollowRequestMessage followRequestMessage = 16;
FollowConfirmationMessage followConfirmationMessage = 17; FollowConfirmationMessage followConfirmationMessage = 17;
FollowAbortMessage followAbortMessage = 18; FollowAbortMessage followAbortMessage = 18;
LockGroupPromptMessage lockGroupPromptMessage = 19;
} }
} }

View File

@ -21,6 +21,7 @@ import {
FollowConfirmationMessage, FollowConfirmationMessage,
FollowAbortMessage, FollowAbortMessage,
VariableMessage, VariableMessage,
LockGroupPromptMessage,
} from "../Messages/generated/messages_pb"; } from "../Messages/generated/messages_pb";
import { UserMovesMessage } from "../Messages/generated/messages_pb"; import { UserMovesMessage } from "../Messages/generated/messages_pb";
import { parse } from "query-string"; import { parse } from "query-string";
@ -561,6 +562,11 @@ export class IoSocketController {
); );
} else if (message.hasFollowabortmessage()) { } else if (message.hasFollowabortmessage()) {
socketManager.handleFollowAbort(client, message.getFollowabortmessage() as FollowAbortMessage); socketManager.handleFollowAbort(client, message.getFollowabortmessage() as FollowAbortMessage);
} else if (message.hasLockgrouppromptmessage()) {
socketManager.handleLockGroup(
client,
message.getLockgrouppromptmessage() as LockGroupPromptMessage
);
} }
/* Ok is false if backpressure was built up, wait for drain */ /* Ok is false if backpressure was built up, wait for drain */

View File

@ -123,19 +123,25 @@ export class UserDescriptor {
} }
export class GroupDescriptor { export class GroupDescriptor {
private constructor(public readonly groupId: number, private groupSize: number, private position: PointMessage) {} private constructor(
public readonly groupId: number,
private groupSize: number,
private position: PointMessage,
private locked: boolean
) {}
public static createFromGroupUpdateZoneMessage(message: GroupUpdateZoneMessage): GroupDescriptor { public static createFromGroupUpdateZoneMessage(message: GroupUpdateZoneMessage): GroupDescriptor {
const position = message.getPosition(); const position = message.getPosition();
if (position === undefined) { if (position === undefined) {
throw new Error("Missing position"); throw new Error("Missing position");
} }
return new GroupDescriptor(message.getGroupid(), message.getGroupsize(), position); return new GroupDescriptor(message.getGroupid(), message.getGroupsize(), position, message.getLocked());
} }
public update(groupDescriptor: GroupDescriptor) { public update(groupDescriptor: GroupDescriptor) {
this.groupSize = groupDescriptor.groupSize; this.groupSize = groupDescriptor.groupSize;
this.position = groupDescriptor.position; this.position = groupDescriptor.position;
this.locked = groupDescriptor.locked;
} }
public toGroupUpdateMessage(): GroupUpdateMessage { public toGroupUpdateMessage(): GroupUpdateMessage {
@ -146,7 +152,7 @@ export class GroupDescriptor {
groupUpdateMessage.setGroupid(this.groupId); groupUpdateMessage.setGroupid(this.groupId);
groupUpdateMessage.setGroupsize(this.groupSize); groupUpdateMessage.setGroupsize(this.groupSize);
groupUpdateMessage.setPosition(this.position); groupUpdateMessage.setPosition(this.position);
groupUpdateMessage.setLocked(this.locked);
return groupUpdateMessage; return groupUpdateMessage;
} }
} }
@ -206,9 +212,7 @@ export class Zone {
this.notifyGroupMove(groupDescriptor); this.notifyGroupMove(groupDescriptor);
} else { } else {
this.groups.set(groupId, groupDescriptor); this.groups.set(groupId, groupDescriptor);
const fromZone = groupUpdateZoneMessage.getFromzone(); const fromZone = groupUpdateZoneMessage.getFromzone();
this.notifyGroupEnter(groupDescriptor, fromZone?.toObject()); this.notifyGroupEnter(groupDescriptor, fromZone?.toObject());
} }
} else if (message.hasUserleftzonemessage()) { } else if (message.hasUserleftzonemessage()) {

View File

@ -38,6 +38,7 @@ import {
ErrorMessage, ErrorMessage,
WorldFullMessage, WorldFullMessage,
PlayerDetailsUpdatedMessage, PlayerDetailsUpdatedMessage,
LockGroupPromptMessage,
InvalidTextureMessage, InvalidTextureMessage,
} from "../Messages/generated/messages_pb"; } from "../Messages/generated/messages_pb";
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils"; import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
@ -304,6 +305,12 @@ export class SocketManager implements ZoneEventListener {
client.backConnection.write(pusherToBackMessage); client.backConnection.write(pusherToBackMessage);
} }
handleLockGroup(client: ExSocketInterface, message: LockGroupPromptMessage): void {
const pusherToBackMessage = new PusherToBackMessage();
pusherToBackMessage.setLockgrouppromptmessage(message);
client.backConnection.write(pusherToBackMessage);
}
onEmote(emoteMessage: EmoteEventMessage, listener: ExSocketInterface): void { onEmote(emoteMessage: EmoteEventMessage, listener: ExSocketInterface): void {
const subMessage = new SubMessage(); const subMessage = new SubMessage();
subMessage.setEmoteeventmessage(emoteMessage); subMessage.setEmoteeventmessage(emoteMessage);