sending info about group lock state. wip
This commit is contained in:
parent
724dbc8efa
commit
d43c8d181a
@ -418,7 +418,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());
|
||||||
|
@ -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,6 +142,10 @@ 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;
|
||||||
}
|
}
|
||||||
@ -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.
|
||||||
|
@ -29,6 +29,7 @@ import {
|
|||||||
WebRtcSignalToServerMessage,
|
WebRtcSignalToServerMessage,
|
||||||
WorldFullWarningToRoomMessage,
|
WorldFullWarningToRoomMessage,
|
||||||
ZoneMessage,
|
ZoneMessage,
|
||||||
|
LockGroupMessage,
|
||||||
} 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,11 @@ const roomManager: IRoomManagerServer = {
|
|||||||
user,
|
user,
|
||||||
message.getFollowabortmessage() as FollowAbortMessage
|
message.getFollowabortmessage() as FollowAbortMessage
|
||||||
);
|
);
|
||||||
|
} else if (message.hasLockgroupmessage()) {
|
||||||
|
socketManager.handleLockGroupMessage(
|
||||||
|
user,
|
||||||
|
message.getLockgroupmessage() as LockGroupMessage
|
||||||
|
);
|
||||||
} 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);
|
||||||
@ -148,6 +154,8 @@ const roomManager: IRoomManagerServer = {
|
|||||||
user,
|
user,
|
||||||
setPlayerDetailsMessage as SetPlayerDetailsMessage
|
setPlayerDetailsMessage as SetPlayerDetailsMessage
|
||||||
);
|
);
|
||||||
|
} else if (message.hasLockgroupmessage()) {
|
||||||
|
console.log("===== GOT LOCK GROUP MESSAGE FROM CLIENT =====");
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Unhandled message type");
|
throw new Error("Unhandled message type");
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,7 @@ import {
|
|||||||
SetPlayerDetailsMessage,
|
SetPlayerDetailsMessage,
|
||||||
PlayerDetailsUpdatedMessage,
|
PlayerDetailsUpdatedMessage,
|
||||||
GroupUsersUpdateMessage,
|
GroupUsersUpdateMessage,
|
||||||
|
LockGroupMessage,
|
||||||
} 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";
|
||||||
@ -404,6 +405,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);
|
||||||
@ -889,6 +891,11 @@ export class SocketManager {
|
|||||||
leader?.delFollower(user);
|
leader?.delFollower(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleLockGroupMessage(user: User, message: LockGroupMessage) {
|
||||||
|
console.log(`lock group: ${message.getLock()}`);
|
||||||
|
user.group?.lock(message.getLock());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const socketManager = new SocketManager();
|
export const socketManager = new SocketManager();
|
||||||
|
@ -45,6 +45,7 @@ export interface GroupCreatedUpdatedMessageInterface {
|
|||||||
position: PositionInterface;
|
position: PositionInterface;
|
||||||
groupId: number;
|
groupId: number;
|
||||||
groupSize: number;
|
groupSize: number;
|
||||||
|
locked: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GroupUsersUpdateMessageInterface {
|
export interface GroupUsersUpdateMessageInterface {
|
||||||
|
@ -636,6 +636,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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -851,6 +852,20 @@ export class RoomConnection implements RoomConnection {
|
|||||||
this.socket.send(bytes);
|
this.socket.send(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public emitLockGroup(groupId: number, lock: boolean = true): void {
|
||||||
|
const bytes = ClientToServerMessageTsProto.encode({
|
||||||
|
message: {
|
||||||
|
$case: "lockGroupMessage",
|
||||||
|
lockGroupMessage: {
|
||||||
|
groupId,
|
||||||
|
lock,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).finish();
|
||||||
|
|
||||||
|
this.socket.send(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
public getAllTags(): string[] {
|
public getAllTags(): string[] {
|
||||||
return this.tags;
|
return this.tags;
|
||||||
}
|
}
|
||||||
|
@ -718,6 +718,16 @@ export class GameScene extends DirtyScene {
|
|||||||
e
|
e
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.input.keyboard.on("keydown-L", (event: Event) => {
|
||||||
|
console.log("group locked");
|
||||||
|
this.connection?.emitLockGroup(1, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.input.keyboard.on("keydown-U", (event: Event) => {
|
||||||
|
console.log("group unlocked");
|
||||||
|
this.connection?.emitLockGroup(1, false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -782,6 +792,7 @@ export class GameScene extends DirtyScene {
|
|||||||
|
|
||||||
this.connection.groupUpdateMessageStream.subscribe(
|
this.connection.groupUpdateMessageStream.subscribe(
|
||||||
(groupPositionMessage: GroupCreatedUpdatedMessageInterface) => {
|
(groupPositionMessage: GroupCreatedUpdatedMessageInterface) => {
|
||||||
|
console.log(groupPositionMessage);
|
||||||
this.shareGroupPosition(groupPositionMessage);
|
this.shareGroupPosition(groupPositionMessage);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -1809,8 +1820,13 @@ ${escapedMessage}
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "GroupCreatedUpdatedEvent":
|
case "GroupCreatedUpdatedEvent":
|
||||||
|
console.log("CREATE OR UPDATE GROUP");
|
||||||
this.doShareGroupPosition(event.event);
|
this.doShareGroupPosition(event.event);
|
||||||
break;
|
break;
|
||||||
|
// TODO: CALL THIS ON GROUP LOCK CHANGE
|
||||||
|
// case "GroupCreatedUpdatedEvent":
|
||||||
|
// this.doShareGroupPosition(event.event);
|
||||||
|
// break;
|
||||||
case "DeleteGroupEvent":
|
case "DeleteGroupEvent":
|
||||||
this.doDeleteGroup(event.groupId);
|
this.doDeleteGroup(event.groupId);
|
||||||
break;
|
break;
|
||||||
@ -1985,7 +2001,9 @@ ${escapedMessage}
|
|||||||
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);
|
||||||
|
@ -98,6 +98,11 @@ message FollowAbortMessage {
|
|||||||
int32 follower = 2;
|
int32 follower = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message LockGroupMessage {
|
||||||
|
int32 groupId = 1;
|
||||||
|
bool lock = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message ClientToServerMessage {
|
message ClientToServerMessage {
|
||||||
oneof message {
|
oneof message {
|
||||||
UserMovesMessage userMovesMessage = 2;
|
UserMovesMessage userMovesMessage = 2;
|
||||||
@ -116,6 +121,7 @@ message ClientToServerMessage {
|
|||||||
FollowRequestMessage followRequestMessage = 15;
|
FollowRequestMessage followRequestMessage = 15;
|
||||||
FollowConfirmationMessage followConfirmationMessage = 16;
|
FollowConfirmationMessage followConfirmationMessage = 16;
|
||||||
FollowAbortMessage followAbortMessage = 17;
|
FollowAbortMessage followAbortMessage = 17;
|
||||||
|
LockGroupMessage lockGroupMessage = 18;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,6 +178,7 @@ message SubMessage {
|
|||||||
VariableMessage variableMessage = 8;
|
VariableMessage variableMessage = 8;
|
||||||
ErrorMessage errorMessage = 9;
|
ErrorMessage errorMessage = 9;
|
||||||
PlayerDetailsUpdatedMessage playerDetailsUpdatedMessage = 10;
|
PlayerDetailsUpdatedMessage playerDetailsUpdatedMessage = 10;
|
||||||
|
LockGroupMessage lockGroupMessage = 11;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,6 +191,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 {
|
||||||
@ -316,6 +324,7 @@ message ServerToClientMessage {
|
|||||||
FollowConfirmationMessage followConfirmationMessage = 22;
|
FollowConfirmationMessage followConfirmationMessage = 22;
|
||||||
FollowAbortMessage followAbortMessage = 23;
|
FollowAbortMessage followAbortMessage = 23;
|
||||||
GroupUsersUpdateMessage groupUsersUpdateMessage = 24;
|
GroupUsersUpdateMessage groupUsersUpdateMessage = 24;
|
||||||
|
LockGroupMessage lockGroupMessage = 25;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,6 +367,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 +413,7 @@ message PusherToBackMessage {
|
|||||||
FollowRequestMessage followRequestMessage = 16;
|
FollowRequestMessage followRequestMessage = 16;
|
||||||
FollowConfirmationMessage followConfirmationMessage = 17;
|
FollowConfirmationMessage followConfirmationMessage = 17;
|
||||||
FollowAbortMessage followAbortMessage = 18;
|
FollowAbortMessage followAbortMessage = 18;
|
||||||
|
LockGroupMessage lockGroupMessage = 19;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ import {
|
|||||||
FollowConfirmationMessage,
|
FollowConfirmationMessage,
|
||||||
FollowAbortMessage,
|
FollowAbortMessage,
|
||||||
VariableMessage,
|
VariableMessage,
|
||||||
|
LockGroupMessage,
|
||||||
} 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 { TemplatedApp } from "uWebSockets.js";
|
import { TemplatedApp } from "uWebSockets.js";
|
||||||
@ -494,6 +495,8 @@ 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.hasLockgroupmessage()) {
|
||||||
|
socketManager.handleLockGroup(client, message.getLockgroupmessage() as LockGroupMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ok is false if backpressure was built up, wait for drain */
|
/* Ok is false if backpressure was built up, wait for drain */
|
||||||
|
@ -32,6 +32,7 @@ export interface ZoneEventListener {
|
|||||||
onGroupEnters(group: GroupDescriptor, listener: ExSocketInterface): void;
|
onGroupEnters(group: GroupDescriptor, listener: ExSocketInterface): void;
|
||||||
onGroupMoves(group: GroupDescriptor, listener: ExSocketInterface): void;
|
onGroupMoves(group: GroupDescriptor, listener: ExSocketInterface): void;
|
||||||
onGroupLeaves(groupId: number, listener: ExSocketInterface): void;
|
onGroupLeaves(groupId: number, listener: ExSocketInterface): void;
|
||||||
|
// onGroupLock(groupId: number, listener: ExSocketInterface): void;
|
||||||
onEmote(emoteMessage: EmoteEventMessage, listener: ExSocketInterface): void;
|
onEmote(emoteMessage: EmoteEventMessage, listener: ExSocketInterface): void;
|
||||||
onError(errorMessage: ErrorMessage, listener: ExSocketInterface): void;
|
onError(errorMessage: ErrorMessage, listener: ExSocketInterface): void;
|
||||||
onPlayerDetailsUpdated(playerDetailsUpdatedMessage: PlayerDetailsUpdatedMessage, listener: ExSocketInterface): void;
|
onPlayerDetailsUpdated(playerDetailsUpdatedMessage: PlayerDetailsUpdatedMessage, listener: ExSocketInterface): void;
|
||||||
@ -123,19 +124,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,6 +153,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;
|
||||||
}
|
}
|
||||||
@ -238,6 +246,9 @@ export class Zone {
|
|||||||
} else if (message.hasEmoteeventmessage()) {
|
} else if (message.hasEmoteeventmessage()) {
|
||||||
const emoteEventMessage = message.getEmoteeventmessage() as EmoteEventMessage;
|
const emoteEventMessage = message.getEmoteeventmessage() as EmoteEventMessage;
|
||||||
this.notifyEmote(emoteEventMessage);
|
this.notifyEmote(emoteEventMessage);
|
||||||
|
} else if (message.hasEmoteeventmessage()) {
|
||||||
|
const emoteEventMessage = message.getEmoteeventmessage() as EmoteEventMessage;
|
||||||
|
this.notifyEmote(emoteEventMessage);
|
||||||
} else if (message.hasPlayerdetailsupdatedmessage()) {
|
} else if (message.hasPlayerdetailsupdatedmessage()) {
|
||||||
const playerDetailsUpdatedMessage =
|
const playerDetailsUpdatedMessage =
|
||||||
message.getPlayerdetailsupdatedmessage() as PlayerDetailsUpdatedMessage;
|
message.getPlayerdetailsupdatedmessage() as PlayerDetailsUpdatedMessage;
|
||||||
|
@ -38,6 +38,7 @@ import {
|
|||||||
ErrorMessage,
|
ErrorMessage,
|
||||||
WorldFullMessage,
|
WorldFullMessage,
|
||||||
PlayerDetailsUpdatedMessage,
|
PlayerDetailsUpdatedMessage,
|
||||||
|
LockGroupMessage,
|
||||||
} from "../Messages/generated/messages_pb";
|
} from "../Messages/generated/messages_pb";
|
||||||
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
|
import { ProtobufUtils } from "../Model/Websocket/ProtobufUtils";
|
||||||
import { ADMIN_API_URL, JITSI_ISS, JITSI_URL, SECRET_JITSI_KEY } from "../Enum/EnvironmentVariable";
|
import { ADMIN_API_URL, JITSI_ISS, JITSI_URL, SECRET_JITSI_KEY } from "../Enum/EnvironmentVariable";
|
||||||
@ -292,6 +293,12 @@ export class SocketManager implements ZoneEventListener {
|
|||||||
client.backConnection.write(pusherToBackMessage);
|
client.backConnection.write(pusherToBackMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleLockGroup(client: ExSocketInterface, message: LockGroupMessage): void {
|
||||||
|
const pusherToBackMessage = new PusherToBackMessage();
|
||||||
|
pusherToBackMessage.setLockgroupmessage(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);
|
||||||
|
Loading…
Reference in New Issue
Block a user