mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-17 21:59:20 +00:00
feat(livestats): per-player killedBy + deathPosition + winner for live standings (#4593)
Enriches the admin-bot live-stats snapshot (`/api/adminbot/game/:id/stats`) with the per-player fields a semi-live standings board needs, so it can score kills + placement live instead of waiting for the post-game record. Per player in `liveStats.players[]`: - **`killedBy`** — the eliminator's clientID (null while alive / non-client killer). Invert → live kill points. - **`deathPosition`** — finishing place at elimination = non-bot players still standing + 1 (frozen at "last of the then-standing"). Null while alive → live placement. The **full roster** is reported (dead players are retained in the client view), plus top-level **`winner`** (the decided winner's clientID, else null). Deterministic / consensus-safe: `killedBy` (stamped at `recordKill`) and `deathPosition` (stamped in `PlayerExecution`) are pure sim values, so in-sync clients agree on the majority vote; `winner` is added server-side in `GameServer.liveStats()` from the winner vote (like the existing publicID/username/connected enrichment). - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory
This commit is contained in:
@@ -18,6 +18,8 @@ function makePlayerState(overrides: Partial<PlayerState> = {}): PlayerState {
|
||||
smallID: 1,
|
||||
isAlive: true,
|
||||
isDisconnected: false,
|
||||
killedBy: null,
|
||||
deathPosition: null,
|
||||
tilesOwned: 0,
|
||||
gold: 0,
|
||||
troops: 100,
|
||||
@@ -68,6 +70,14 @@ describe("diffPlayerUpdate", () => {
|
||||
expect(diff.hasSpawned).toBeUndefined();
|
||||
});
|
||||
|
||||
it("emits killedBy + deathPosition when a player is eliminated", () => {
|
||||
const prev = makePlayerUpdate({ killedBy: null, deathPosition: null });
|
||||
const next = makePlayerUpdate({ killedBy: "client-b", deathPosition: 3 });
|
||||
const diff = diffPlayerUpdate(prev, next)!;
|
||||
expect(diff.killedBy).toBe("client-b");
|
||||
expect(diff.deathPosition).toBe(3);
|
||||
});
|
||||
|
||||
it("ignores tilesOwned/gold/troops — they travel via packedPlayerUpdates", () => {
|
||||
const prev = makePlayerUpdate({ gold: 100n, troops: 50, tilesOwned: 5 });
|
||||
const next = makePlayerUpdate({ gold: 200n, troops: 75, tilesOwned: 9 });
|
||||
@@ -241,6 +251,16 @@ describe("packAttackTroopDeltas", () => {
|
||||
});
|
||||
|
||||
describe("applyStateUpdate", () => {
|
||||
it("applies killedBy + deathPosition on elimination", () => {
|
||||
const target = makePlayerState();
|
||||
applyStateUpdate(
|
||||
target,
|
||||
makePlayerUpdate({ killedBy: "client-b", deathPosition: 3 }),
|
||||
);
|
||||
expect(target.killedBy).toBe("client-b");
|
||||
expect(target.deathPosition).toBe(3);
|
||||
});
|
||||
|
||||
it("applies every field from a full update", () => {
|
||||
const target = makePlayerState();
|
||||
const pu = makePlayerUpdate({
|
||||
|
||||
Reference in New Issue
Block a user