Merge branch 'develop' of github.com:thecodingmachine/workadventure
This commit is contained in:
+87
-25
@@ -213,13 +213,13 @@ export class GameRoom {
|
||||
}
|
||||
|
||||
private updateUserGroup(user: User): void {
|
||||
user.group?.updatePosition();
|
||||
user.group?.searchForNearbyUsers();
|
||||
|
||||
if (user.silent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const group = user.group;
|
||||
const closestItem: User | Group | null = this.searchClosestAvailableUserOrGroup(user);
|
||||
|
||||
if (group === undefined) {
|
||||
// If the user is not part of a group:
|
||||
// should he join a group?
|
||||
@@ -229,12 +229,11 @@ export class GameRoom {
|
||||
return;
|
||||
}
|
||||
|
||||
const closestItem: User | Group | null = this.searchClosestAvailableUserOrGroup(user);
|
||||
|
||||
if (closestItem !== null) {
|
||||
if (closestItem instanceof Group) {
|
||||
// Let's join the group!
|
||||
closestItem.join(user);
|
||||
closestItem.setOutOfBounds(false);
|
||||
} else {
|
||||
const closestUser: User = closestItem;
|
||||
const group: Group = new Group(
|
||||
@@ -249,32 +248,95 @@ export class GameRoom {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If the user is part of a group:
|
||||
// should he leave the group?
|
||||
let noOneOutOfBounds = true;
|
||||
group.getUsers().forEach((foreignUser: User) => {
|
||||
if (foreignUser.group === undefined) {
|
||||
return;
|
||||
let hasKickOutSomeone = false;
|
||||
let followingMembers: User[] = [];
|
||||
|
||||
const previewNewGroupPosition = group.previewGroupPosition();
|
||||
|
||||
if (!previewNewGroupPosition) {
|
||||
this.leaveGroup(user);
|
||||
return;
|
||||
}
|
||||
|
||||
if (user.hasFollowers() || user.following) {
|
||||
followingMembers = user.hasFollowers()
|
||||
? group.getUsers().filter((currentUser) => currentUser.following === user)
|
||||
: group.getUsers().filter((currentUser) => currentUser.following === user.following);
|
||||
|
||||
// If all group members are part of the same follow group
|
||||
if (group.getUsers().length - 1 === followingMembers.length) {
|
||||
let isOutOfBounds = false;
|
||||
|
||||
// If a follower is far away from the leader, "outOfBounds" is set to true
|
||||
for (const member of followingMembers) {
|
||||
const distance = GameRoom.computeDistanceBetweenPositions(
|
||||
member.getPosition(),
|
||||
previewNewGroupPosition
|
||||
);
|
||||
|
||||
if (distance > this.groupRadius) {
|
||||
isOutOfBounds = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
group.setOutOfBounds(isOutOfBounds);
|
||||
}
|
||||
const usrPos = foreignUser.getPosition();
|
||||
const grpPos = foreignUser.group.getPosition();
|
||||
const distance = GameRoom.computeDistanceBetweenPositions(usrPos, grpPos);
|
||||
}
|
||||
|
||||
// Check if the moving user has kicked out another user
|
||||
for (const headMember of group.getGroupHeads()) {
|
||||
if (!headMember.group) {
|
||||
this.leaveGroup(headMember);
|
||||
continue;
|
||||
}
|
||||
|
||||
const headPosition = headMember.getPosition();
|
||||
const distance = GameRoom.computeDistanceBetweenPositions(headPosition, previewNewGroupPosition);
|
||||
|
||||
if (distance > this.groupRadius) {
|
||||
if (foreignUser.hasFollowers() || foreignUser.following) {
|
||||
// If one user is out of the group bounds BUT following, the group still exists... but should be hidden.
|
||||
// We put it in 'outOfBounds' mode
|
||||
group.setOutOfBounds(true);
|
||||
noOneOutOfBounds = false;
|
||||
} else {
|
||||
this.leaveGroup(foreignUser);
|
||||
}
|
||||
hasKickOutSomeone = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the current moving user has kicked another user from the radius,
|
||||
* the moving user leaves the group because he is too far away.
|
||||
*/
|
||||
const userDistance = GameRoom.computeDistanceBetweenPositions(user.getPosition(), previewNewGroupPosition);
|
||||
|
||||
if (hasKickOutSomeone && userDistance > this.groupRadius) {
|
||||
if (user.hasFollowers() && group.getUsers().length === 3 && followingMembers.length === 1) {
|
||||
const other = group
|
||||
.getUsers()
|
||||
.find((currentUser) => !currentUser.hasFollowers() && !currentUser.following);
|
||||
if (other) {
|
||||
this.leaveGroup(other);
|
||||
}
|
||||
} else if (user.hasFollowers()) {
|
||||
this.leaveGroup(user);
|
||||
for (const member of followingMembers) {
|
||||
this.leaveGroup(member);
|
||||
}
|
||||
|
||||
// Re-create a group with the followers
|
||||
const newGroup: Group = new Group(
|
||||
this.roomUrl,
|
||||
[user, ...followingMembers],
|
||||
this.groupRadius,
|
||||
this.connectCallback,
|
||||
this.disconnectCallback,
|
||||
this.positionNotifier
|
||||
);
|
||||
this.groups.add(newGroup);
|
||||
} else {
|
||||
this.leaveGroup(user);
|
||||
}
|
||||
});
|
||||
if (noOneOutOfBounds && !user.group?.isEmpty()) {
|
||||
group.setOutOfBounds(false);
|
||||
}
|
||||
}
|
||||
|
||||
user.group?.updatePosition();
|
||||
user.group?.searchForNearbyUsers();
|
||||
}
|
||||
|
||||
public sendToOthersInGroupIncludingUser(user: User, message: ServerToClientMessage): void {
|
||||
|
||||
+46
-11
@@ -59,6 +59,39 @@ export class Group implements Movable {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of users of the group, ignoring any "followers".
|
||||
* Useful to compute the position of the group if a follower is "trapped" far away from the the leader.
|
||||
*/
|
||||
getGroupHeads(): User[] {
|
||||
return Array.from(this.users).filter((user) => user.group?.leader === user || !user.following);
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview the position of the group but don't update it
|
||||
*/
|
||||
previewGroupPosition(): { x: number; y: number } | undefined {
|
||||
const users = this.getGroupHeads();
|
||||
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
|
||||
if (users.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
users.forEach((user: User) => {
|
||||
const position = user.getPosition();
|
||||
x += position.x;
|
||||
y += position.y;
|
||||
});
|
||||
|
||||
x /= users.length;
|
||||
y /= users.length;
|
||||
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the barycenter of all users (i.e. the center of the group)
|
||||
*/
|
||||
@@ -66,19 +99,15 @@ export class Group implements Movable {
|
||||
const oldX = this.x;
|
||||
const oldY = this.y;
|
||||
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
// Let's compute the barycenter of all users.
|
||||
this.users.forEach((user: User) => {
|
||||
const position = user.getPosition();
|
||||
x += position.x;
|
||||
y += position.y;
|
||||
});
|
||||
x /= this.users.size;
|
||||
y /= this.users.size;
|
||||
if (this.users.size === 0) {
|
||||
throw new Error("EMPTY GROUP FOUND!!!");
|
||||
const newPosition = this.previewGroupPosition();
|
||||
|
||||
if (!newPosition) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { x, y } = newPosition;
|
||||
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
|
||||
@@ -97,10 +126,12 @@ export class Group implements Movable {
|
||||
if (!this.currentZone) return;
|
||||
|
||||
for (const user of this.positionNotifier.getAllUsersInSquareAroundZone(this.currentZone)) {
|
||||
// Todo: Merge two groups with a leader
|
||||
if (user.group || this.isFull()) return; //we ignore users that are already in a group.
|
||||
const distance = GameRoom.computeDistanceBetweenPositions(user.getPosition(), this.getPosition());
|
||||
if (distance < this.groupRadius) {
|
||||
this.join(user);
|
||||
this.setOutOfBounds(false);
|
||||
this.updatePosition();
|
||||
}
|
||||
}
|
||||
@@ -176,4 +207,8 @@ export class Group implements Movable {
|
||||
this.outOfBounds = true;
|
||||
}
|
||||
}
|
||||
|
||||
get getOutOfBounds() {
|
||||
return this.outOfBounds;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import "jasmine";
|
||||
import {PositionNotifier} from "../src/Model/PositionNotifier";
|
||||
import {User, UserSocket} from "../src/Model/User";
|
||||
import {Zone} from "_Model/Zone";
|
||||
import {Movable} from "_Model/Movable";
|
||||
import {PositionInterface} from "_Model/PositionInterface";
|
||||
import {ZoneSocket} from "../src/RoomManager";
|
||||
|
||||
import { PositionNotifier } from "../src/Model/PositionNotifier";
|
||||
import { User, UserSocket } from "../src/Model/User";
|
||||
import { Zone } from "_Model/Zone";
|
||||
import { Movable } from "_Model/Movable";
|
||||
import { PositionInterface } from "_Model/PositionInterface";
|
||||
import { ZoneSocket } from "../src/RoomManager";
|
||||
|
||||
describe("PositionNotifier", () => {
|
||||
it("should receive notifications when player moves", () => {
|
||||
@@ -13,28 +12,59 @@ describe("PositionNotifier", () => {
|
||||
let moveTriggered = false;
|
||||
let leaveTriggered = false;
|
||||
|
||||
const positionNotifier = new PositionNotifier(300, 300, (thing: Movable) => {
|
||||
enterTriggered = true;
|
||||
}, (thing: Movable, position: PositionInterface) => {
|
||||
moveTriggered = true;
|
||||
}, (thing: Movable) => {
|
||||
leaveTriggered = true;
|
||||
}, () => {},
|
||||
() => {});
|
||||
const positionNotifier = new PositionNotifier(
|
||||
300,
|
||||
300,
|
||||
(thing: Movable) => {
|
||||
enterTriggered = true;
|
||||
},
|
||||
(thing: Movable, position: PositionInterface) => {
|
||||
moveTriggered = true;
|
||||
},
|
||||
(thing: Movable) => {
|
||||
leaveTriggered = true;
|
||||
},
|
||||
() => {},
|
||||
() => {}
|
||||
);
|
||||
|
||||
const user1 = new User(1, 'test', '10.0.0.2', {
|
||||
x: 500,
|
||||
y: 500,
|
||||
moving: false,
|
||||
direction: 'down'
|
||||
}, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
|
||||
const user1 = new User(
|
||||
1,
|
||||
"test",
|
||||
"10.0.0.2",
|
||||
{
|
||||
x: 500,
|
||||
y: 500,
|
||||
moving: false,
|
||||
direction: "down",
|
||||
},
|
||||
false,
|
||||
positionNotifier,
|
||||
{} as UserSocket,
|
||||
[],
|
||||
null,
|
||||
"foo",
|
||||
[]
|
||||
);
|
||||
|
||||
const user2 = new User(2, 'test', '10.0.0.2', {
|
||||
x: -9999,
|
||||
y: -9999,
|
||||
moving: false,
|
||||
direction: 'down'
|
||||
}, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
|
||||
const user2 = new User(
|
||||
2,
|
||||
"test",
|
||||
"10.0.0.2",
|
||||
{
|
||||
x: -9999,
|
||||
y: -9999,
|
||||
moving: false,
|
||||
direction: "down",
|
||||
},
|
||||
false,
|
||||
positionNotifier,
|
||||
{} as UserSocket,
|
||||
[],
|
||||
null,
|
||||
"foo",
|
||||
[]
|
||||
);
|
||||
|
||||
positionNotifier.addZoneListener({} as ZoneSocket, 0, 0);
|
||||
positionNotifier.addZoneListener({} as ZoneSocket, 0, 1);
|
||||
@@ -47,21 +77,21 @@ describe("PositionNotifier", () => {
|
||||
bottom: 500
|
||||
});*/
|
||||
|
||||
user2.setPosition({x: 500, y: 500, direction: 'down', moving: false});
|
||||
user2.setPosition({ x: 500, y: 500, direction: "down", moving: false });
|
||||
|
||||
expect(enterTriggered).toBe(true);
|
||||
expect(moveTriggered).toBe(false);
|
||||
enterTriggered = false;
|
||||
|
||||
// Move inside the zone
|
||||
user2.setPosition({x:501, y:500, direction: 'down', moving: false});
|
||||
user2.setPosition({ x: 501, y: 500, direction: "down", moving: false });
|
||||
|
||||
expect(enterTriggered).toBe(false);
|
||||
expect(moveTriggered).toBe(true);
|
||||
moveTriggered = false;
|
||||
|
||||
// Move out of the zone in a zone that we don't track
|
||||
user2.setPosition({x: 901, y: 500, direction: 'down', moving: false});
|
||||
user2.setPosition({ x: 901, y: 500, direction: "down", moving: false });
|
||||
|
||||
expect(enterTriggered).toBe(false);
|
||||
expect(moveTriggered).toBe(false);
|
||||
@@ -69,7 +99,7 @@ describe("PositionNotifier", () => {
|
||||
leaveTriggered = false;
|
||||
|
||||
// Move back in
|
||||
user2.setPosition({x: 500, y: 500, direction: 'down', moving: false});
|
||||
user2.setPosition({ x: 500, y: 500, direction: "down", moving: false });
|
||||
expect(enterTriggered).toBe(true);
|
||||
expect(moveTriggered).toBe(false);
|
||||
expect(leaveTriggered).toBe(false);
|
||||
@@ -89,28 +119,59 @@ describe("PositionNotifier", () => {
|
||||
let moveTriggered = false;
|
||||
let leaveTriggered = false;
|
||||
|
||||
const positionNotifier = new PositionNotifier(300, 300, (thing: Movable, fromZone: Zone|null ) => {
|
||||
enterTriggered = true;
|
||||
}, (thing: Movable, position: PositionInterface) => {
|
||||
moveTriggered = true;
|
||||
}, (thing: Movable) => {
|
||||
leaveTriggered = true;
|
||||
}, () => {},
|
||||
() => {});
|
||||
const positionNotifier = new PositionNotifier(
|
||||
300,
|
||||
300,
|
||||
(thing: Movable, fromZone: Zone | null) => {
|
||||
enterTriggered = true;
|
||||
},
|
||||
(thing: Movable, position: PositionInterface) => {
|
||||
moveTriggered = true;
|
||||
},
|
||||
(thing: Movable) => {
|
||||
leaveTriggered = true;
|
||||
},
|
||||
() => {},
|
||||
() => {}
|
||||
);
|
||||
|
||||
const user1 = new User(1, 'test', '10.0.0.2', {
|
||||
x: 500,
|
||||
y: 500,
|
||||
moving: false,
|
||||
direction: 'down'
|
||||
}, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
|
||||
const user1 = new User(
|
||||
1,
|
||||
"test",
|
||||
"10.0.0.2",
|
||||
{
|
||||
x: 500,
|
||||
y: 500,
|
||||
moving: false,
|
||||
direction: "down",
|
||||
},
|
||||
false,
|
||||
positionNotifier,
|
||||
{} as UserSocket,
|
||||
[],
|
||||
null,
|
||||
"foo",
|
||||
[]
|
||||
);
|
||||
|
||||
const user2 = new User(2, 'test', '10.0.0.2', {
|
||||
x: 0,
|
||||
y: 0,
|
||||
moving: false,
|
||||
direction: 'down'
|
||||
}, false, positionNotifier, {} as UserSocket, [], null, 'foo', []);
|
||||
const user2 = new User(
|
||||
2,
|
||||
"test",
|
||||
"10.0.0.2",
|
||||
{
|
||||
x: 0,
|
||||
y: 0,
|
||||
moving: false,
|
||||
direction: "down",
|
||||
},
|
||||
false,
|
||||
positionNotifier,
|
||||
{} as UserSocket,
|
||||
[],
|
||||
null,
|
||||
"foo",
|
||||
[]
|
||||
);
|
||||
|
||||
const listener = {} as ZoneSocket;
|
||||
positionNotifier.addZoneListener(listener, 0, 0);
|
||||
@@ -126,14 +187,12 @@ describe("PositionNotifier", () => {
|
||||
positionNotifier.enter(user1);
|
||||
positionNotifier.enter(user2);
|
||||
|
||||
|
||||
//expect(newUsers.length).toBe(2);
|
||||
expect(enterTriggered).toBe(true);
|
||||
enterTriggered = false;
|
||||
|
||||
|
||||
//positionNotifier.updatePosition(user2, {x:500, y:500}, {x:0, y: 0})
|
||||
user2.setPosition({x: 500, y: 500, direction: 'down', moving: false});
|
||||
user2.setPosition({ x: 500, y: 500, direction: "down", moving: false });
|
||||
|
||||
expect(enterTriggered).toBe(true);
|
||||
expect(moveTriggered).toBe(false);
|
||||
@@ -184,4 +243,4 @@ describe("PositionNotifier", () => {
|
||||
enterTriggered = false;
|
||||
//expect(newUsers.length).toBe(2);
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user