Making groups part of zones

This commit is contained in:
David Négrier 2020-09-16 16:06:43 +02:00
parent 3ca2a5bf68
commit f5f9dcac04
12 changed files with 245 additions and 188 deletions

View file

@ -7,7 +7,7 @@ import Jwt, {JsonWebTokenError} from "jsonwebtoken";
import {SECRET_KEY, MINIMUM_DISTANCE, GROUP_RADIUS, ALLOW_ARTILLERY} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..." import {SECRET_KEY, MINIMUM_DISTANCE, GROUP_RADIUS, ALLOW_ARTILLERY} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..."
import {World} from "../Model/World"; import {World} from "../Model/World";
import {Group} from "../Model/Group"; import {Group} from "../Model/Group";
import {UserInterface} from "../Model/UserInterface"; import {User} from "../Model/User";
import {isSetPlayerDetailsMessage,} from "../Model/Websocket/SetPlayerDetailsMessage"; import {isSetPlayerDetailsMessage,} from "../Model/Websocket/SetPlayerDetailsMessage";
import {MessageUserJoined} from "../Model/Websocket/MessageUserJoined"; import {MessageUserJoined} from "../Model/Websocket/MessageUserJoined";
import {MessageUserMoved} from "../Model/Websocket/MessageUserMoved"; import {MessageUserMoved} from "../Model/Websocket/MessageUserMoved";
@ -22,6 +22,8 @@ import {isItemEventMessageInterface} from "../Model/Websocket/ItemEventMessage";
import {uuid} from 'uuidv4'; import {uuid} from 'uuidv4';
import {isUserMovesInterface} from "../Model/Websocket/UserMovesMessage"; import {isUserMovesInterface} from "../Model/Websocket/UserMovesMessage";
import {isViewport} from "../Model/Websocket/ViewportMessage"; import {isViewport} from "../Model/Websocket/ViewportMessage";
import {GroupUpdateInterface} from "_Model/Websocket/GroupUpdateInterface";
import {Movable} from "../Model/Movable";
enum SockerIoEvent { enum SockerIoEvent {
CONNECTION = "connection", CONNECTION = "connection",
@ -159,7 +161,7 @@ export class IoSocketController {
}); });
} }
private sendDeleteGroupEvent(uuid: string, lastUser: UserInterface): void { private sendDeleteGroupEvent(uuid: string, lastUser: User): void {
// Let's get the room of the group. To do this, let's get anyone in the group and find its room. // Let's get the room of the group. To do this, let's get anyone in the group and find its room.
const userId = lastUser.id; const userId = lastUser.id;
const client: ExSocketInterface = this.searchClientByIdOrFail(userId); const client: ExSocketInterface = this.searchClientByIdOrFail(userId);
@ -215,15 +217,29 @@ export class IoSocketController {
//join new previous room //join new previous room
const world = this.joinRoom(Client, roomId, message.position); const world = this.joinRoom(Client, roomId, message.position);
const users = world.setViewport(Client, message.viewport); const things = world.setViewport(Client, message.viewport);
const listOfUsers = users.map((user: UserInterface) => {
const player: ExSocketInterface|undefined = this.sockets.get(user.id); const listOfUsers: Array<MessageUserPosition> = [];
if (player === undefined) { const listOfGroups: Array<GroupUpdateInterface> = [];
console.warn('Something went wrong. The World contains a user "'+user.id+"' but this user does not exist in the sockets list!");
return null; for (const thing of things) {
if (thing instanceof User) {
const player: ExSocketInterface|undefined = this.sockets.get(thing.id);
if (player === undefined) {
console.warn('Something went wrong. The World contains a user "'+thing.id+"' but this user does not exist in the sockets list!");
continue;
}
listOfUsers.push(new MessageUserPosition(thing.id, player.name, player.characterLayers, player.position));
} else if (thing instanceof Group) {
listOfGroups.push({
groupId: thing.getId(),
position: thing.getPosition(),
});
} else {
console.error("Unexpected type for Movable returned by setViewport");
} }
return new MessageUserPosition(user.id, player.name, player.characterLayers, player.position); }
}, users);
const listOfItems: {[itemId: string]: unknown} = {}; const listOfItems: {[itemId: string]: unknown} = {};
for (const [itemId, item] of world.getItemsState().entries()) { for (const [itemId, item] of world.getItemsState().entries()) {
@ -232,15 +248,13 @@ export class IoSocketController {
//console.warn('ANSWER PLAYER POSITIONS', listOfUsers); //console.warn('ANSWER PLAYER POSITIONS', listOfUsers);
if (answerFn === undefined && ALLOW_ARTILLERY === true) { if (answerFn === undefined && ALLOW_ARTILLERY === true) {
/*console.error("TYPEOF answerFn", typeof(answerFn));
console.error("answerFn", answerFn);
process.exit(1)*/
// For some reason, answerFn can be undefined if we use Artillery (?) // For some reason, answerFn can be undefined if we use Artillery (?)
return; return;
} }
answerFn({ answerFn({
users: listOfUsers, users: listOfUsers,
groups: listOfGroups,
items: listOfItems items: listOfItems
}); });
} catch (e) { } catch (e) {
@ -485,28 +499,51 @@ export class IoSocketController {
}, (user1: string, group: Group) => { }, (user1: string, group: Group) => {
this.disConnectedUser(user1, group); this.disConnectedUser(user1, group);
}, MINIMUM_DISTANCE, GROUP_RADIUS, (group: Group) => { }, MINIMUM_DISTANCE, GROUP_RADIUS, (group: Group) => {
this.sendUpdateGroupEvent(group); //this.sendUpdateGroupEvent(group);
}, (groupUuid: string, lastUser: UserInterface) => { }, (groupUuid: string, lastUser: User) => {
this.sendDeleteGroupEvent(groupUuid, lastUser); //this.sendDeleteGroupEvent(groupUuid, lastUser);
}, (user, listener) => { }, (thing: Movable, listener: User) => {
const clientUser = this.searchClientByIdOrFail(user.id);
const clientListener = this.searchClientByIdOrFail(listener.id); const clientListener = this.searchClientByIdOrFail(listener.id);
const messageUserJoined = new MessageUserJoined(clientUser.userId, clientUser.name, clientUser.characterLayers, clientUser.position); if (thing instanceof User) {
const clientUser = this.searchClientByIdOrFail(thing.id);
const messageUserJoined = new MessageUserJoined(clientUser.userId, clientUser.name, clientUser.characterLayers, clientUser.position);
clientListener.emit(SockerIoEvent.JOIN_ROOM, messageUserJoined); clientListener.emit(SockerIoEvent.JOIN_ROOM, messageUserJoined);
//console.log("Sending JOIN_ROOM event"); } else if (thing instanceof Group) {
}, (user, position, listener) => { clientListener.emit(SockerIoEvent.GROUP_CREATE_UPDATE, {
const clientUser = this.searchClientByIdOrFail(user.id); position: thing.getPosition(),
groupId: thing.getId()
} as GroupUpdateInterface);
} else {
console.error('Unexpected type for Movable.');
}
}, (thing: Movable, position, listener) => {
const clientListener = this.searchClientByIdOrFail(listener.id); const clientListener = this.searchClientByIdOrFail(listener.id);
if (thing instanceof User) {
const clientUser = this.searchClientByIdOrFail(thing.id);
clientListener.emitInBatch(SockerIoEvent.USER_MOVED, new MessageUserMoved(clientUser.userId, clientUser.position)); clientListener.emitInBatch(SockerIoEvent.USER_MOVED, new MessageUserMoved(clientUser.userId, clientUser.position));
//console.log("Sending USER_MOVED event"); //console.log("Sending USER_MOVED event");
}, (user, listener) => { } else if (thing instanceof Group) {
const clientUser = this.searchClientByIdOrFail(user.id); clientListener.emit(SockerIoEvent.GROUP_CREATE_UPDATE, {
position: thing.getPosition(),
groupId: thing.getId()
} as GroupUpdateInterface);
} else {
console.error('Unexpected type for Movable.');
}
}, (thing: Movable, listener) => {
const clientListener = this.searchClientByIdOrFail(listener.id); const clientListener = this.searchClientByIdOrFail(listener.id);
if (thing instanceof User) {
const clientUser = this.searchClientByIdOrFail(thing.id);
clientListener.emit(SockerIoEvent.USER_LEFT, clientUser.userId);
//console.log("Sending USER_LEFT event");
} else if (thing instanceof Group) {
clientListener.emit(SockerIoEvent.GROUP_DELETE, thing.getId());
} else {
console.error('Unexpected type for Movable.');
}
clientListener.emit(SockerIoEvent.USER_LEFT, clientUser.userId);
//console.log("Sending USER_LEFT event");
}); });
this.Worlds.set(roomId, world); this.Worlds.set(roomId, world);
} }
@ -516,7 +553,7 @@ export class IoSocketController {
Client.emit(SockerIoEvent.GROUP_CREATE_UPDATE, { Client.emit(SockerIoEvent.GROUP_CREATE_UPDATE, {
position: group.getPosition(), position: group.getPosition(),
groupId: group.getId() groupId: group.getId()
}); } as GroupUpdateInterface);
}); });
//join world //join world
world.join(Client, Client.position); world.join(Client, Client.position);

View file

@ -1,29 +1,30 @@
import { World, ConnectCallback, DisconnectCallback } from "./World"; import { World, ConnectCallback, DisconnectCallback } from "./World";
import { UserInterface } from "./UserInterface"; import { User } from "./User";
import {PositionInterface} from "_Model/PositionInterface"; import {PositionInterface} from "_Model/PositionInterface";
import {uuid} from "uuidv4"; import {uuid} from "uuidv4";
import {Movable} from "_Model/Movable";
export class Group { export class Group implements Movable {
static readonly MAX_PER_GROUP = 4; static readonly MAX_PER_GROUP = 4;
private id: string; private id: string;
private users: Set<UserInterface>; private users: Set<User>;
private connectCallback: ConnectCallback; private connectCallback: ConnectCallback;
private disconnectCallback: DisconnectCallback; private disconnectCallback: DisconnectCallback;
constructor(users: UserInterface[], connectCallback: ConnectCallback, disconnectCallback: DisconnectCallback) { constructor(users: User[], connectCallback: ConnectCallback, disconnectCallback: DisconnectCallback) {
this.users = new Set<UserInterface>(); this.users = new Set<User>();
this.connectCallback = connectCallback; this.connectCallback = connectCallback;
this.disconnectCallback = disconnectCallback; this.disconnectCallback = disconnectCallback;
this.id = uuid(); this.id = uuid();
users.forEach((user: UserInterface) => { users.forEach((user: User) => {
this.join(user); this.join(user);
}); });
} }
getUsers(): UserInterface[] { getUsers(): User[] {
return Array.from(this.users.values()); return Array.from(this.users.values());
} }
@ -38,7 +39,7 @@ export class Group {
let x = 0; let x = 0;
let y = 0; let y = 0;
// Let's compute the barycenter of all users. // Let's compute the barycenter of all users.
this.users.forEach((user: UserInterface) => { this.users.forEach((user: User) => {
x += user.position.x; x += user.position.x;
y += user.position.y; y += user.position.y;
}); });
@ -58,7 +59,7 @@ export class Group {
return this.users.size <= 1; return this.users.size <= 1;
} }
join(user: UserInterface): void join(user: User): void
{ {
// Broadcast on the right event // Broadcast on the right event
this.connectCallback(user.id, this); this.connectCallback(user.id, this);
@ -66,7 +67,7 @@ export class Group {
user.group = this; user.group = this;
} }
leave(user: UserInterface): void leave(user: User): void
{ {
const success = this.users.delete(user); const success = this.users.delete(user);
if (success === false) { if (success === false) {

View file

@ -0,0 +1,8 @@
import {PositionInterface} from "_Model/PositionInterface";
/**
* A physical object that can be placed into a Zone
*/
export interface Movable {
getPosition(): PositionInterface
}

View file

@ -8,10 +8,12 @@
* The PositionNotifier is important for performance. It allows us to send the position of players only to a restricted * The PositionNotifier is important for performance. It allows us to send the position of players only to a restricted
* number of players around the current player. * number of players around the current player.
*/ */
import {UserEntersCallback, UserLeavesCallback, UserMovesCallback, Zone} from "./Zone"; import {EntersCallback, LeavesCallback, MovesCallback, Zone} from "./Zone";
import {PointInterface} from "_Model/Websocket/PointInterface"; import {PointInterface} from "_Model/Websocket/PointInterface";
import {UserInterface} from "_Model/UserInterface"; import {User} from "_Model/User";
import {ViewportInterface} from "_Model/Websocket/ViewportMessage"; import {ViewportInterface} from "_Model/Websocket/ViewportMessage";
import {Movable} from "_Model/Movable";
import {PositionInterface} from "_Model/PositionInterface";
interface ZoneDescriptor { interface ZoneDescriptor {
i: number; i: number;
@ -24,7 +26,7 @@ export class PositionNotifier {
private zones: Zone[][] = []; private zones: Zone[][] = [];
constructor(private zoneWidth: number, private zoneHeight: number, private onUserEnters: UserEntersCallback, private onUserMoves: UserMovesCallback, private onUserLeaves: UserLeavesCallback) { constructor(private zoneWidth: number, private zoneHeight: number, private onUserEnters: EntersCallback, private onUserMoves: MovesCallback, private onUserLeaves: LeavesCallback) {
} }
private getZoneDescriptorFromCoordinates(x: number, y: number): ZoneDescriptor { private getZoneDescriptorFromCoordinates(x: number, y: number): ZoneDescriptor {
@ -38,7 +40,7 @@ export class PositionNotifier {
* Sets the viewport coordinates. * Sets the viewport coordinates.
* Returns the list of new users to add * Returns the list of new users to add
*/ */
public setViewport(user: UserInterface, viewport: ViewportInterface): UserInterface[] { public setViewport(user: User, viewport: ViewportInterface): Movable[] {
if (viewport.left > viewport.right || viewport.top > viewport.bottom) { if (viewport.left > viewport.right || viewport.top > viewport.bottom) {
console.warn('Invalid viewport received: ', viewport); console.warn('Invalid viewport received: ', viewport);
return []; return [];
@ -60,43 +62,46 @@ export class PositionNotifier {
const removedZones = [...oldZones].filter(x => !newZones.has(x)); const removedZones = [...oldZones].filter(x => !newZones.has(x));
let users: UserInterface[] = []; let things: Movable[] = [];
for (const zone of addedZones) { for (const zone of addedZones) {
zone.startListening(user); zone.startListening(user);
users = users.concat(Array.from(zone.getPlayers())) things = things.concat(Array.from(zone.getThings()))
} }
for (const zone of removedZones) { for (const zone of removedZones) {
zone.stopListening(user); zone.stopListening(user);
} }
return users; return things;
} }
public updatePosition(user: UserInterface, userPosition: PointInterface): void { public updatePosition(thing: Movable, newPosition: PositionInterface, oldPosition: PositionInterface): void {
// Did we change zone? // Did we change zone?
const oldZoneDesc = this.getZoneDescriptorFromCoordinates(user.position.x, user.position.y); const oldZoneDesc = this.getZoneDescriptorFromCoordinates(oldPosition.x, oldPosition.y);
const newZoneDesc = this.getZoneDescriptorFromCoordinates(userPosition.x, userPosition.y); const newZoneDesc = this.getZoneDescriptorFromCoordinates(newPosition.x, newPosition.y);
if (oldZoneDesc.i != newZoneDesc.i || oldZoneDesc.j != newZoneDesc.j) { if (oldZoneDesc.i != newZoneDesc.i || oldZoneDesc.j != newZoneDesc.j) {
const oldZone = this.getZone(oldZoneDesc.i, oldZoneDesc.j); const oldZone = this.getZone(oldZoneDesc.i, oldZoneDesc.j);
const newZone = this.getZone(newZoneDesc.i, newZoneDesc.j); const newZone = this.getZone(newZoneDesc.i, newZoneDesc.j);
// Leave old zone // Leave old zone
oldZone.leave(user, newZone); oldZone.leave(thing, newZone);
// Enter new zone // Enter new zone
newZone.enter(user, oldZone, userPosition); newZone.enter(thing, oldZone, newPosition);
} else { } else {
const zone = this.getZone(oldZoneDesc.i, oldZoneDesc.j); const zone = this.getZone(oldZoneDesc.i, oldZoneDesc.j);
zone.move(user, userPosition); zone.move(thing, newPosition);
} }
} }
public leave(user: UserInterface): void { public leave(thing: Movable): void {
const oldZoneDesc = this.getZoneDescriptorFromCoordinates(user.position.x, user.position.y); const oldPosition = thing.getPosition();
const oldZoneDesc = this.getZoneDescriptorFromCoordinates(oldPosition.x, oldPosition.y);
const oldZone = this.getZone(oldZoneDesc.i, oldZoneDesc.j); const oldZone = this.getZone(oldZoneDesc.i, oldZoneDesc.j);
oldZone.leave(user, null); oldZone.leave(thing, null);
}
public removeViewport(user: User): void {
// Also, let's stop listening on viewports // Also, let's stop listening on viewports
for (const zone of user.listenedZones) { for (const zone of user.listenedZones) {
zone.stopListening(user); zone.stopListening(user);

23
back/src/Model/User.ts Normal file
View file

@ -0,0 +1,23 @@
import { Group } from "./Group";
import { PointInterface } from "./Websocket/PointInterface";
import {Zone} from "_Model/Zone";
import {Movable} from "_Model/Movable";
import {PositionInterface} from "_Model/PositionInterface";
export class User implements Movable {
public listenedZones: Set<Zone>;
public group?: Group;
public constructor(
public id: string,
public position: PointInterface,
public silent: boolean,
) {
this.listenedZones = new Set<Zone>();
}
public getPosition(): PositionInterface {
return this.position;
}
}

View file

@ -1,11 +0,0 @@
import { Group } from "./Group";
import { PointInterface } from "./Websocket/PointInterface";
import {Zone} from "_Model/Zone";
export interface UserInterface {
id: string,
group?: Group,
position: PointInterface,
silent: boolean,
listenedZones: Set<Zone>
}

View file

@ -0,0 +1,6 @@
import {PositionInterface} from "_Model/PositionInterface";
export interface GroupUpdateInterface {
position: PositionInterface,
groupId: string,
}

View file

@ -2,27 +2,28 @@ import {MessageUserPosition, Point} from "./Websocket/MessageUserPosition";
import {PointInterface} from "./Websocket/PointInterface"; import {PointInterface} from "./Websocket/PointInterface";
import {Group} from "./Group"; import {Group} from "./Group";
import {Distance} from "./Distance"; import {Distance} from "./Distance";
import {UserInterface} from "./UserInterface"; import {User} from "./User";
import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface"; import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface";
import {PositionInterface} from "_Model/PositionInterface"; import {PositionInterface} from "_Model/PositionInterface";
import {Identificable} from "_Model/Websocket/Identificable"; import {Identificable} from "_Model/Websocket/Identificable";
import {UserEntersCallback, UserLeavesCallback, UserMovesCallback, Zone} from "_Model/Zone"; import {EntersCallback, LeavesCallback, MovesCallback, Zone} from "_Model/Zone";
import {PositionNotifier} from "./PositionNotifier"; import {PositionNotifier} from "./PositionNotifier";
import {ViewportInterface} from "_Model/Websocket/ViewportMessage"; import {ViewportInterface} from "_Model/Websocket/ViewportMessage";
import {Movable} from "_Model/Movable";
export type ConnectCallback = (user: string, group: Group) => void; export type ConnectCallback = (user: string, group: Group) => void;
export type DisconnectCallback = (user: string, group: Group) => void; export type DisconnectCallback = (user: string, group: Group) => void;
// callback called when a group is created or moved or changes users // callback called when a group is created or moved or changes users
export type GroupUpdatedCallback = (group: Group) => void; export type GroupUpdatedCallback = (group: Group) => void;
export type GroupDeletedCallback = (uuid: string, lastUser: UserInterface) => void; export type GroupDeletedCallback = (uuid: string, lastUser: User) => void;
export class World { export class World {
private readonly minDistance: number; private readonly minDistance: number;
private readonly groupRadius: number; private readonly groupRadius: number;
// Users, sorted by ID // Users, sorted by ID
private readonly users: Map<string, UserInterface>; private readonly users: Map<string, User>;
private readonly groups: Set<Group>; private readonly groups: Set<Group>;
private readonly connectCallback: ConnectCallback; private readonly connectCallback: ConnectCallback;
@ -40,11 +41,11 @@ export class World {
groupRadius: number, groupRadius: number,
groupUpdatedCallback: GroupUpdatedCallback, groupUpdatedCallback: GroupUpdatedCallback,
groupDeletedCallback: GroupDeletedCallback, groupDeletedCallback: GroupDeletedCallback,
onUserEnters: UserEntersCallback, onEnters: EntersCallback,
onUserMoves: UserMovesCallback, onMoves: MovesCallback,
onUserLeaves: UserLeavesCallback) onLeaves: LeavesCallback)
{ {
this.users = new Map<string, UserInterface>(); this.users = new Map<string, User>();
this.groups = new Set<Group>(); this.groups = new Set<Group>();
this.connectCallback = connectCallback; this.connectCallback = connectCallback;
this.disconnectCallback = disconnectCallback; this.disconnectCallback = disconnectCallback;
@ -53,24 +54,19 @@ export class World {
this.groupUpdatedCallback = groupUpdatedCallback; this.groupUpdatedCallback = groupUpdatedCallback;
this.groupDeletedCallback = groupDeletedCallback; this.groupDeletedCallback = groupDeletedCallback;
// A zone is 10 sprites wide. // A zone is 10 sprites wide.
this.positionNotifier = new PositionNotifier(320, 320, onUserEnters, onUserMoves, onUserLeaves); this.positionNotifier = new PositionNotifier(320, 320, onEnters, onMoves, onLeaves);
} }
public getGroups(): Group[] { public getGroups(): Group[] {
return Array.from(this.groups.values()); return Array.from(this.groups.values());
} }
public getUsers(): Map<string, UserInterface> { public getUsers(): Map<string, User> {
return this.users; return this.users;
} }
public join(socket : Identificable, userPosition: PointInterface): void { public join(socket : Identificable, userPosition: PointInterface): void {
this.users.set(socket.userId, { this.users.set(socket.userId, new User(socket.userId, userPosition, false));
id: socket.userId,
position: userPosition,
silent: false, // FIXME: silent should be set at the correct value when joining a room.
listenedZones: new Set<Zone>()
});
// Let's call update position to trigger the join / leave room // Let's call update position to trigger the join / leave room
this.updatePosition(socket, userPosition); this.updatePosition(socket, userPosition);
} }
@ -87,6 +83,7 @@ export class World {
if (userObj !== undefined) { if (userObj !== undefined) {
this.positionNotifier.leave(userObj); this.positionNotifier.leave(userObj);
this.positionNotifier.removeViewport(userObj);
} }
} }
@ -100,7 +97,9 @@ export class World {
return; return;
} }
this.positionNotifier.updatePosition(user, userPosition); this.positionNotifier.updatePosition(user, userPosition, user.position);
const oldGroupPosition = user.group?.getPosition();
user.position = userPosition; user.position = userPosition;
@ -108,17 +107,17 @@ export class World {
return; return;
} }
if (typeof user.group === 'undefined') { if (user.group === undefined) {
// If the user is not part of a group: // If the user is not part of a group:
// should he join a group? // should he join a group?
const closestItem: UserInterface|Group|null = this.searchClosestAvailableUserOrGroup(user); const closestItem: User|Group|null = this.searchClosestAvailableUserOrGroup(user);
if (closestItem !== null) { if (closestItem !== null) {
if (closestItem instanceof Group) { if (closestItem instanceof Group) {
// Let's join the group! // Let's join the group!
closestItem.join(user); closestItem.join(user);
} else { } else {
const closestUser : UserInterface = closestItem; const closestUser : User = closestItem;
const group: Group = new Group([ const group: Group = new Group([
user, user,
closestUser closestUser
@ -138,7 +137,8 @@ export class World {
// At the very end, if the user is part of a group, let's call the callback to update group position // At the very end, if the user is part of a group, let's call the callback to update group position
if (typeof user.group !== 'undefined') { if (typeof user.group !== 'undefined') {
this.groupUpdatedCallback(user.group); this.positionNotifier.updatePosition(user.group, user.group.getPosition(), oldGroupPosition ? oldGroupPosition : user.group.getPosition());
//this.groupUpdatedCallback(user.group);
} }
} }
@ -167,21 +167,23 @@ export class World {
* *
* @param user * @param user
*/ */
private leaveGroup(user: UserInterface): void { private leaveGroup(user: User): void {
const group = user.group; const group = user.group;
if (typeof group === 'undefined') { if (typeof group === 'undefined') {
throw new Error("The user is part of no group"); throw new Error("The user is part of no group");
} }
group.leave(user); group.leave(user);
if (group.isEmpty()) { if (group.isEmpty()) {
this.groupDeletedCallback(group.getId(), user); //this.groupDeletedCallback(group.getId(), user);
this.positionNotifier.leave(group);
group.destroy(); group.destroy();
if (!this.groups.has(group)) { if (!this.groups.has(group)) {
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);
} else { } else {
this.groupUpdatedCallback(group); this.positionNotifier.updatePosition(group, group.getPosition(), group.getPosition());
//this.groupUpdatedCallback(group);
} }
} }
@ -193,10 +195,10 @@ export class World {
* OR * OR
* - close enough to a group (distance <= groupRadius) * - close enough to a group (distance <= groupRadius)
*/ */
private searchClosestAvailableUserOrGroup(user: UserInterface): UserInterface|Group|null private searchClosestAvailableUserOrGroup(user: User): User|Group|null
{ {
let minimumDistanceFound: number = Math.max(this.minDistance, this.groupRadius); let minimumDistanceFound: number = Math.max(this.minDistance, this.groupRadius);
let matchingItem: UserInterface | Group | null = null; let matchingItem: User | Group | null = null;
this.users.forEach((currentUser, userId) => { this.users.forEach((currentUser, userId) => {
// Let's only check users that are not part of a group // Let's only check users that are not part of a group
if (typeof currentUser.group !== 'undefined') { if (typeof currentUser.group !== 'undefined') {
@ -265,7 +267,7 @@ export class World {
return matchingItem; return matchingItem;
} }
public static computeDistance(user1: UserInterface, user2: UserInterface): number public static computeDistance(user1: User, user2: User): number
{ {
return Math.sqrt(Math.pow(user2.position.x - user1.position.x, 2) + Math.pow(user2.position.y - user1.position.y, 2)); return Math.sqrt(Math.pow(user2.position.x - user1.position.x, 2) + Math.pow(user2.position.y - user1.position.y, 2));
} }
@ -343,7 +345,7 @@ export class World {
} }
return 0; return 0;
}*/ }*/
setViewport(socket : Identificable, viewport: ViewportInterface): UserInterface[] { setViewport(socket : Identificable, viewport: ViewportInterface): Movable[] {
const user = this.users.get(socket.userId); const user = this.users.get(socket.userId);
if(typeof user === 'undefined') { if(typeof user === 'undefined') {
console.warn('In setViewport, could not find user with ID "'+socket.userId+'" in world.'); console.warn('In setViewport, could not find user with ID "'+socket.userId+'" in world.');

View file

@ -1,77 +1,77 @@
import {UserInterface} from "./UserInterface"; import {User} from "./User";
import {PointInterface} from "_Model/Websocket/PointInterface";
import {PositionInterface} from "_Model/PositionInterface"; import {PositionInterface} from "_Model/PositionInterface";
import {Movable} from "_Model/Movable";
export type UserEntersCallback = (user: UserInterface, listener: UserInterface) => void; export type EntersCallback = (thing: Movable, listener: User) => void;
export type UserMovesCallback = (user: UserInterface, position: PointInterface, listener: UserInterface) => void; export type MovesCallback = (thing: Movable, position: PositionInterface, listener: User) => void;
export type UserLeavesCallback = (user: UserInterface, listener: UserInterface) => void; export type LeavesCallback = (thing: Movable, listener: User) => void;
export class Zone { export class Zone {
private players: Set<UserInterface> = new Set<UserInterface>(); private things: Set<Movable> = new Set<Movable>();
private listeners: Set<UserInterface> = new Set<UserInterface>(); private listeners: Set<User> = new Set<User>();
constructor(private onUserEnters: UserEntersCallback, private onUserMoves: UserMovesCallback, private onUserLeaves: UserLeavesCallback) { constructor(private onEnters: EntersCallback, private onMoves: MovesCallback, private onLeaves: LeavesCallback) {
} }
/** /**
* A user leaves the zone * A user/thing leaves the zone
*/ */
public leave(user: UserInterface, newZone: Zone|null) { public leave(thing: Movable, newZone: Zone|null) {
this.players.delete(user); this.things.delete(thing);
this.notifyUserLeft(user, newZone); this.notifyLeft(thing, newZone);
} }
/** /**
* Notify listeners of this zone that this user left * Notify listeners of this zone that this user/thing left
*/ */
private notifyUserLeft(user: UserInterface, newZone: Zone|null) { private notifyLeft(thing: Movable, newZone: Zone|null) {
for (const listener of this.listeners) { for (const listener of this.listeners) {
if (listener !== user && (newZone === null || !listener.listenedZones.has(newZone))) { if (listener !== thing && (newZone === null || !listener.listenedZones.has(newZone))) {
this.onUserLeaves(user, listener); this.onLeaves(thing, listener);
} }
} }
} }
public enter(user: UserInterface, oldZone: Zone|null, position: PointInterface) { public enter(thing: Movable, oldZone: Zone|null, position: PositionInterface) {
this.players.add(user); this.things.add(thing);
this.notifyUserEnter(user, oldZone, position); this.notifyUserEnter(thing, oldZone, position);
} }
/** /**
* Notify listeners of this zone that this user entered * Notify listeners of this zone that this user entered
*/ */
private notifyUserEnter(user: UserInterface, oldZone: Zone|null, position: PointInterface) { private notifyUserEnter(thing: Movable, oldZone: Zone|null, position: PositionInterface) {
for (const listener of this.listeners) { for (const listener of this.listeners) {
if (listener === user) { if (listener === thing) {
continue; continue;
} }
if (oldZone === null || !listener.listenedZones.has(oldZone)) { if (oldZone === null || !listener.listenedZones.has(oldZone)) {
this.onUserEnters(user, listener); this.onEnters(thing, listener);
} else { } else {
this.onUserMoves(user, position, listener); this.onMoves(thing, position, listener);
} }
} }
} }
public move(user: UserInterface, position: PointInterface) { public move(thing: Movable, position: PositionInterface) {
if (!this.players.has(user)) { if (!this.things.has(thing)) {
this.players.add(user); this.things.add(thing);
const foo = this.players; const foo = this.things;
this.notifyUserEnter(user, null, position); this.notifyUserEnter(thing, null, position);
return; return;
} }
for (const listener of this.listeners) { for (const listener of this.listeners) {
if (listener !== user) { if (listener !== thing) {
this.onUserMoves(user,position, listener); this.onMoves(thing,position, listener);
} }
} }
} }
public startListening(listener: UserInterface): void { public startListening(listener: User): void {
for (const player of this.players) { for (const thing of this.things) {
if (player !== listener) { if (thing !== listener) {
this.onUserEnters(player, listener); this.onEnters(thing, listener);
} }
} }
@ -79,10 +79,10 @@ export class Zone {
listener.listenedZones.add(this); listener.listenedZones.add(this);
} }
public stopListening(listener: UserInterface): void { public stopListening(listener: User): void {
for (const player of this.players) { for (const thing of this.things) {
if (player !== listener) { if (thing !== listener) {
this.onUserLeaves(player, listener); this.onLeaves(thing, listener);
} }
} }
@ -90,7 +90,7 @@ export class Zone {
listener.listenedZones.delete(this); listener.listenedZones.delete(this);
} }
public getPlayers(): Set<UserInterface> { public getThings(): Set<Movable> {
return this.players; return this.things;
} }
} }

View file

@ -3,17 +3,16 @@ import {World, ConnectCallback, DisconnectCallback } from "../src/Model/World";
import {Point} from "../src/Model/Websocket/MessageUserPosition"; import {Point} from "../src/Model/Websocket/MessageUserPosition";
import { Group } from "../src/Model/Group"; import { Group } from "../src/Model/Group";
import {PositionNotifier} from "../src/Model/PositionNotifier"; import {PositionNotifier} from "../src/Model/PositionNotifier";
import {UserInterface} from "../src/Model/UserInterface"; import {User} from "../src/Model/User";
import {PointInterface} from "../src/Model/Websocket/PointInterface"; import {PointInterface} from "../src/Model/Websocket/PointInterface";
import {Zone} from "_Model/Zone"; import {Zone} from "_Model/Zone";
import {Movable} from "_Model/Movable";
function move(user: UserInterface, x: number, y: number, positionNotifier: PositionNotifier): void { function move(user: User, x: number, y: number, positionNotifier: PositionNotifier): void {
positionNotifier.updatePosition(user, { positionNotifier.updatePosition(user, {
x, x,
y, y
moving: false, }, user.position);
direction: 'down'
});
user.position.x = x; user.position.x = x;
user.position.y = y; user.position.y = y;
} }
@ -24,35 +23,27 @@ describe("PositionNotifier", () => {
let moveTriggered = false; let moveTriggered = false;
let leaveTriggered = false; let leaveTriggered = false;
const positionNotifier = new PositionNotifier(300, 300, (user: UserInterface) => { const positionNotifier = new PositionNotifier(300, 300, (thing: Movable) => {
enterTriggered = true; enterTriggered = true;
}, (user: UserInterface, position: PointInterface) => { }, (thing: Movable, position: PointInterface) => {
moveTriggered = true; moveTriggered = true;
}, (user: UserInterface) => { }, (thing: Movable) => {
leaveTriggered = true; leaveTriggered = true;
}); });
const user1 = { const user1 = new User("1", {
id: "1", x: 500,
position: { y: 500,
x: 500, moving: false,
y: 500, direction: 'down'
moving: false, }, false);
direction: 'down'
},
listenedZones: new Set<Zone>(),
} as UserInterface;
const user2 = { const user2 = new User("2", {
id: "2", x: -9999,
position: { y: -9999,
x: -9999, moving: false,
y: -9999, direction: 'down'
moving: false, }, false);
direction: 'down'
},
listenedZones: new Set<Zone>(),
} as UserInterface;
positionNotifier.setViewport(user1, { positionNotifier.setViewport(user1, {
left: 200, left: 200,
@ -98,6 +89,7 @@ describe("PositionNotifier", () => {
// Leave the room // Leave the room
positionNotifier.leave(user2); positionNotifier.leave(user2);
positionNotifier.removeViewport(user2);
expect(enterTriggered).toBe(false); expect(enterTriggered).toBe(false);
expect(moveTriggered).toBe(false); expect(moveTriggered).toBe(false);
expect(leaveTriggered).toBe(true); expect(leaveTriggered).toBe(true);
@ -109,35 +101,27 @@ describe("PositionNotifier", () => {
let moveTriggered = false; let moveTriggered = false;
let leaveTriggered = false; let leaveTriggered = false;
const positionNotifier = new PositionNotifier(300, 300, (user: UserInterface) => { const positionNotifier = new PositionNotifier(300, 300, (thing: Movable) => {
enterTriggered = true; enterTriggered = true;
}, (user: UserInterface, position: PointInterface) => { }, (thing: Movable, position: PointInterface) => {
moveTriggered = true; moveTriggered = true;
}, (user: UserInterface) => { }, (thing: Movable) => {
leaveTriggered = true; leaveTriggered = true;
}); });
const user1 = { const user1 = new User("1", {
id: "1", x: 500,
position: { y: 500,
x: 500, moving: false,
y: 500, direction: 'down'
moving: false, }, false);
direction: 'down'
},
listenedZones: new Set<Zone>(),
} as UserInterface;
const user2 = { const user2 = new User("2", {
id: "2", x: -9999,
position: { y: -9999,
x: -9999, moving: false,
y: -9999, direction: 'down'
moving: false, }, false);
direction: 'down'
},
listenedZones: new Set<Zone>(),
} as UserInterface;
let newUsers = positionNotifier.setViewport(user1, { let newUsers = positionNotifier.setViewport(user1, {
left: 200, left: 200,

View file

@ -123,7 +123,8 @@ export interface ItemEventMessageInterface {
} }
export interface RoomJoinedMessageInterface { export interface RoomJoinedMessageInterface {
users: MessageUserPositionInterface[] users: MessageUserPositionInterface[],
groups: GroupCreatedUpdatedMessageInterface[],
items: { [itemId: number] : unknown } items: { [itemId: number] : unknown }
} }

View file

@ -780,6 +780,7 @@ export class GameScene extends Phaser.Scene implements CenterListener {
this.initUsersPosition(roomJoinedMessage.users); this.initUsersPosition(roomJoinedMessage.users);
this.connectionAnswerPromiseResolve(roomJoinedMessage); this.connectionAnswerPromiseResolve(roomJoinedMessage);
}); });
// FIXME: weirdly enough we don't use the result of joinARoom !!!!!!
//listen event to share position of user //listen event to share position of user
this.CurrentPlayer.on(hasMovedEventName, this.pushPlayerPosition.bind(this)) this.CurrentPlayer.on(hasMovedEventName, this.pushPlayerPosition.bind(this))