Share player stats with master server

Send the per player stats with the player event updates to the master
server and expose it to the Web UI and ctl interfaces.
This commit is contained in:
Hornwitser committed 2022-07-12 23:03:50 +02:00
1 parent deb6842a4d
commit 0245ccd16c
12 files changed
+400 -34

No files matched your search

+30
View File
@@ -85,6 +85,7 @@ describe("lib/users", function() {
test_roundtrip({ name: "user", roles: [2], token_valid_after: 12345 });
test_roundtrip({ name: "user", is_admin: true, is_whitelisted: true });
test_roundtrip({ name: "user", is_banned: true, ban_reason: "Bad user" });
test_roundtrip({ name: "user", instance_stats: [[1, { join_count: 1 }]]});
});
it("should ignore invalid roles", function() {
let user = new libUsers.User({ name: "test", roles: [1, 4, 55] }, roles);
@@ -115,6 +116,35 @@ describe("lib/users", function() {
assert(!libUsers.User.onlineUsers.has(user));
assert.deepEqual(user.instances, new Set());
});
it("should calculate playerStats", function() {
let user = new libUsers.User({ name: "test", roles: [1], instance_stats: [
[1, {
join_count: 1,
online_time_ms: 60e3,
last_join_at: new Date("2020-05T12:00Z").getTime(),
last_leave_at: new Date("2020-05T12:01Z").getTime(),
last_leave_reason: "quit",
}],
[2, {
join_count: 1,
online_time_ms: 0,
last_join_at: new Date("2020-05T12:02Z").getTime(),
}],
[3, {
join_count: 2,
online_time_ms: 120e3,
last_join_at: new Date("2020-05T11:00Z").getTime(),
last_leave_at: new Date("2020-05T11:02Z").getTime(),
last_leave_reason: "afk",
}],
]}, roles);
assert.equal(user.playerStats.onlineTimeMs, 180e3);
assert.equal(user.playerStats.joinCount, 4);
assert.deepEqual(user.playerStats.lastJoinAt, new Date("2020-05T12:02Z"));
assert.deepEqual(user.playerStats.lastLeaveAt, new Date("2020-05T12:01Z"));
assert.equal(user.playerStats.lastLeaveReason, "quit");
});
describe(".checkPermission()", function() {
it("should correctly resolve permissions", function() {
+58 -8
View File
@@ -54,18 +54,28 @@ describe("class Instance", function() {
it("should send player_event", function() {
instance._recordPlayerJoin("player");
let stats = instance.playerStats.get("player");
assert.deepEqual(
connector.sentMessages[0],
{
seq: 1,
type: "player_event_event",
data: { type: "join", instance_id: instance.id, name: "player" },
data: {
type: "join",
instance_id: instance.id,
name: "player",
stats: {
join_count: 1,
last_join_at: stats.lastJoinAt.getTime(),
},
},
},
);
});
it("should be idempotent", function() {
it("should be idempotent", async function() {
instance._recordPlayerJoin("player");
await wait(10);
instance._recordPlayerJoin("player");
assert(instance.playersOnline.has("player"), "player was not added");
assert.equal(connector.sentMessages.length, 1);
@@ -73,8 +83,9 @@ describe("class Instance", function() {
});
describe("._recordPlayerLeave()", function() {
it("should remove player to playersOnline", function() {
it("should remove player to playersOnline", async function() {
instance._recordPlayerJoin("player");
await wait(10);
instance._recordPlayerLeave("player");
assert(!instance.playersOnline.has("player"), "player was not removed");
});
@@ -87,22 +98,38 @@ describe("class Instance", function() {
assert(instance.playerStats.get("player").onlineTimeMs > 0, "no onlineTimeMs recorded");
});
it("should send player_event", function() {
it("should send player_event", async function() {
instance._recordPlayerJoin("player");
await wait(10);
instance._recordPlayerLeave("player", "quit");
let stats = instance.playerStats.get("player");
assert.deepEqual(
connector.sentMessages[1],
{
seq: 2,
type: "player_event_event",
data: { type: "leave", instance_id: instance.id, name: "player", reason: "quit" },
data: {
type: "leave",
instance_id: instance.id,
name: "player",
reason: "quit",
stats: {
join_count: 1,
online_time_ms: stats.onlineTimeMs,
last_join_at: stats.lastJoinAt.getTime(),
last_leave_at: stats.lastLeaveAt.getTime(),
last_leave_reason: "quit",
},
},
},
);
});
it("should be idempotent", function() {
it("should be idempotent", async function() {
instance._recordPlayerJoin("player");
await wait(10);
instance._recordPlayerLeave("player", "quit");
await wait(10);
instance._recordPlayerLeave("player", "quit");
assert(!instance.playersOnline.has("player"), "player was not removed");
assert.equal(connector.sentMessages.length, 2);
@@ -129,12 +156,21 @@ describe("class Instance", function() {
);
instance._recordPlayerJoin("player");
await instance._checkOnlinePlayers();
let stats = instance.playerStats.get("foo");
assert.deepEqual(
connector.sentMessages[1],
{
seq: 2,
type: "player_event_event",
data: { type: "join", instance_id: instance.id, name: "foo" },
data: {
type: "join",
instance_id: instance.id,
name: "foo",
stats: {
join_count: 1,
last_join_at: stats.lastJoinAt.getTime(),
},
},
},
);
});
@@ -142,13 +178,27 @@ describe("class Instance", function() {
it("should remove extra players", async function() {
instance.server.rconCommandResults.set("/players online", "Online Players (0):\n");
instance._recordPlayerJoin("player");
await wait(10);
await instance._checkOnlinePlayers();
let stats = instance.playerStats.get("player");
assert.deepEqual(
connector.sentMessages[1],
{
seq: 2,
type: "player_event_event",
data: { type: "leave", instance_id: instance.id, name: "player", reason: "quit" },
data: {
type: "leave",
instance_id: instance.id,
name: "player",
reason: "quit",
stats: {
join_count: 1,
online_time_ms: stats.onlineTimeMs,
last_join_at: stats.lastJoinAt.getTime(),
last_leave_at: stats.lastLeaveAt.getTime(),
last_leave_reason: "quit",
},
},
},
);
});