Now, when a user moves, only his/her position is sent back to the other users. The position of all users is not sent each time. The messages sent to the browser are now: - the list of all users as a return to the join_room event (you can send responses to events in socket.io) - a "join_room" event sent when a new user joins the room - a "user_moved" event when a user moved - a "user_left" event when a user left the room The GameScene tracks all these events and reacts accordingly. Also, I made a number of refactoring in the classes and removed the GameSceneInterface that was useless (it was implemented by the LogincScene for no reason at all)
288 lines
10 KiB
TypeScript
288 lines
10 KiB
TypeScript
import {MessageUserPosition, Point} from "./Websocket/MessageUserPosition";
|
|
import {PointInterface} from "./Websocket/PointInterface";
|
|
import {Group} from "./Group";
|
|
import {Distance} from "./Distance";
|
|
import {UserInterface} from "./UserInterface";
|
|
import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface";
|
|
import {PositionInterface} from "_Model/PositionInterface";
|
|
import {Identificable} from "_Model/Websocket/Identificable";
|
|
|
|
export type ConnectCallback = (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
|
|
export type GroupUpdatedCallback = (group: Group) => void;
|
|
export type GroupDeletedCallback = (uuid: string, lastUser: UserInterface) => void;
|
|
|
|
export class World {
|
|
private readonly minDistance: number;
|
|
private readonly groupRadius: number;
|
|
|
|
// Users, sorted by ID
|
|
private readonly users: Map<string, UserInterface>;
|
|
private readonly groups: Group[];
|
|
|
|
private readonly connectCallback: ConnectCallback;
|
|
private readonly disconnectCallback: DisconnectCallback;
|
|
private readonly groupUpdatedCallback: GroupUpdatedCallback;
|
|
private readonly groupDeletedCallback: GroupDeletedCallback;
|
|
|
|
constructor(connectCallback: ConnectCallback,
|
|
disconnectCallback: DisconnectCallback,
|
|
minDistance: number,
|
|
groupRadius: number,
|
|
groupUpdatedCallback: GroupUpdatedCallback,
|
|
groupDeletedCallback: GroupDeletedCallback)
|
|
{
|
|
this.users = new Map<string, UserInterface>();
|
|
this.groups = [];
|
|
this.connectCallback = connectCallback;
|
|
this.disconnectCallback = disconnectCallback;
|
|
this.minDistance = minDistance;
|
|
this.groupRadius = groupRadius;
|
|
this.groupUpdatedCallback = groupUpdatedCallback;
|
|
this.groupDeletedCallback = groupDeletedCallback;
|
|
}
|
|
|
|
public getGroups(): Group[] {
|
|
return this.groups;
|
|
}
|
|
|
|
public getUsers(): Map<string, UserInterface> {
|
|
return this.users;
|
|
}
|
|
|
|
public join(socket : Identificable, userPosition: PointInterface): void {
|
|
this.users.set(socket.id, {
|
|
id: socket.id,
|
|
position: userPosition
|
|
});
|
|
// Let's call update position to trigger the join / leave room
|
|
this.updatePosition(socket, userPosition);
|
|
}
|
|
|
|
public leave(user : Identificable){
|
|
let userObj = this.users.get(user.id);
|
|
if (userObj === undefined) {
|
|
console.warn('User ', user.id, 'does not belong to world! It should!');
|
|
}
|
|
if (userObj !== undefined && typeof userObj.group !== 'undefined') {
|
|
this.leaveGroup(userObj);
|
|
}
|
|
this.users.delete(user.id);
|
|
}
|
|
|
|
public updatePosition(socket : Identificable, userPosition: PointInterface): void {
|
|
let user = this.users.get(socket.id);
|
|
if(typeof user === 'undefined') {
|
|
return;
|
|
}
|
|
|
|
user.position = userPosition;
|
|
|
|
if (typeof user.group === 'undefined') {
|
|
// If the user is not part of a group:
|
|
// should he join a group?
|
|
let closestItem: UserInterface|Group|null = this.searchClosestAvailableUserOrGroup(user);
|
|
|
|
if (closestItem !== null) {
|
|
if (closestItem instanceof Group) {
|
|
// Let's join the group!
|
|
closestItem.join(user);
|
|
} else {
|
|
let closestUser : UserInterface = closestItem;
|
|
let group: Group = new Group([
|
|
user,
|
|
closestUser
|
|
], this.connectCallback, this.disconnectCallback);
|
|
this.groups.push(group);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
// If the user is part of a group:
|
|
// should he leave the group?
|
|
let distance = World.computeDistanceBetweenPositions(user.position, user.group.getPosition());
|
|
if (distance > this.groupRadius) {
|
|
this.leaveGroup(user);
|
|
}
|
|
}
|
|
|
|
// 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') {
|
|
this.groupUpdatedCallback(user.group);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Makes a user leave a group and closes and destroy the group if the group contains only one remaining person.
|
|
*
|
|
* @param user
|
|
*/
|
|
private leaveGroup(user: UserInterface): void {
|
|
let group = user.group;
|
|
if (typeof group === 'undefined') {
|
|
throw new Error("The user is part of no group");
|
|
}
|
|
group.leave(user);
|
|
if (group.isEmpty()) {
|
|
this.groupDeletedCallback(group.getId(), user);
|
|
group.destroy();
|
|
const index = this.groups.indexOf(group, 0);
|
|
if (index === -1) {
|
|
throw new Error("Could not find group");
|
|
}
|
|
this.groups.splice(index, 1);
|
|
} else {
|
|
this.groupUpdatedCallback(group);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Looks for the closest user that is:
|
|
* - close enough (distance <= minDistance)
|
|
* - not in a group
|
|
* OR
|
|
* - close enough to a group (distance <= groupRadius)
|
|
*/
|
|
private searchClosestAvailableUserOrGroup(user: UserInterface): UserInterface|Group|null
|
|
{
|
|
let minimumDistanceFound: number = Math.max(this.minDistance, this.groupRadius);
|
|
let matchingItem: UserInterface | Group | null = null;
|
|
this.users.forEach((currentUser, userId) => {
|
|
// Let's only check users that are not part of a group
|
|
if (typeof currentUser.group !== 'undefined') {
|
|
return;
|
|
}
|
|
if(currentUser === user) {
|
|
return;
|
|
}
|
|
|
|
let distance = World.computeDistance(user, currentUser); // compute distance between peers.
|
|
|
|
if(distance <= minimumDistanceFound && distance <= this.minDistance) {
|
|
minimumDistanceFound = distance;
|
|
matchingItem = currentUser;
|
|
}
|
|
/*if (typeof currentUser.group === 'undefined' || !currentUser.group.isFull()) {
|
|
// We found a user we can bind to.
|
|
return;
|
|
}*/
|
|
/*
|
|
if(context.groups.length > 0) {
|
|
|
|
context.groups.forEach(group => {
|
|
if(group.isPartOfGroup(userPosition)) { // Is the user in a group ?
|
|
if(group.isStillIn(userPosition)) { // Is the user leaving the group ? (is the user at more than max distance of each player)
|
|
|
|
// Should we split the group? (is each player reachable from the current player?)
|
|
// This is needed if
|
|
// A <==> B <==> C <===> D
|
|
// becomes A <==> B <=====> C <> D
|
|
// If C moves right, the distance between B and C is too great and we must form 2 groups
|
|
|
|
}
|
|
} else {
|
|
// If the user is in no group
|
|
// Is there someone in a group close enough and with room in the group ?
|
|
}
|
|
});
|
|
|
|
} else {
|
|
// Aucun groupe n'existe donc je stock les users assez proches de moi
|
|
let dist: Distance = {
|
|
distance: distance,
|
|
first: userPosition,
|
|
second: user // TODO: convertir en messageUserPosition
|
|
}
|
|
usersToBeGroupedWith.push(dist);
|
|
}
|
|
*/
|
|
});
|
|
|
|
this.groups.forEach((group: Group) => {
|
|
if (group.isFull()) {
|
|
return;
|
|
}
|
|
let distance = World.computeDistanceBetweenPositions(user.position, group.getPosition());
|
|
if(distance <= minimumDistanceFound && distance <= this.groupRadius) {
|
|
minimumDistanceFound = distance;
|
|
matchingItem = group;
|
|
}
|
|
});
|
|
|
|
return matchingItem;
|
|
}
|
|
|
|
public static computeDistance(user1: UserInterface, user2: UserInterface): number
|
|
{
|
|
return Math.sqrt(Math.pow(user2.position.x - user1.position.x, 2) + Math.pow(user2.position.y - user1.position.y, 2));
|
|
}
|
|
|
|
public static computeDistanceBetweenPositions(position1: PositionInterface, position2: PositionInterface): number
|
|
{
|
|
return Math.sqrt(Math.pow(position2.x - position1.x, 2) + Math.pow(position2.y - position1.y, 2));
|
|
}
|
|
|
|
/*getDistancesBetweenGroupUsers(group: Group): Distance[]
|
|
{
|
|
let i = 0;
|
|
let users = group.getUsers();
|
|
let distances: Distance[] = [];
|
|
users.forEach(function(user1, key1) {
|
|
users.forEach(function(user2, key2) {
|
|
if(key1 < key2) {
|
|
distances[i] = {
|
|
distance: World.computeDistance(user1, user2),
|
|
first: user1,
|
|
second: user2
|
|
};
|
|
i++;
|
|
}
|
|
});
|
|
});
|
|
|
|
distances.sort(World.compareDistances);
|
|
|
|
return distances;
|
|
}
|
|
|
|
filterGroup(distances: Distance[], group: Group): void
|
|
{
|
|
let users = group.getUsers();
|
|
let usersToRemove = false;
|
|
let groupTmp: MessageUserPosition[] = [];
|
|
distances.forEach(dist => {
|
|
if(dist.distance <= World.MIN_DISTANCE) {
|
|
let users = [dist.first];
|
|
let usersbis = [dist.second]
|
|
groupTmp.push(dist.first);
|
|
groupTmp.push(dist.second);
|
|
} else {
|
|
usersToRemove = true;
|
|
}
|
|
});
|
|
|
|
if(usersToRemove) {
|
|
// Detecte le ou les users qui se sont fait sortir du groupe
|
|
let difference = users.filter(x => !groupTmp.includes(x));
|
|
|
|
// TODO : Notify users un difference that they have left the group
|
|
}
|
|
|
|
let newgroup = new Group(groupTmp);
|
|
this.groups.push(newgroup);
|
|
}
|
|
|
|
private static compareDistances(distA: Distance, distB: Distance): number
|
|
{
|
|
if (distA.distance < distB.distance) {
|
|
return -1;
|
|
}
|
|
if (distA.distance > distB.distance) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}*/
|
|
}
|