Add player position tracking relay + rendering (Phase 7b)
Relays the mod's throttled player_positions roster over a new /ws/players/:serverId gateway (Redis pub/sub + snapshot key so a tab connecting between mod flushes isn't empty), gated per-server by a new playerPositionsVisible admin toggle independent of the mod's own tracking config. Frontend renders the roster as map markers with a show/hide toggle and online count. Covered by unit tests (players.test.ts, ws-gateway.test.ts, admin.test.ts) and a new e2e spec that plays the real mod WS protocol from inside a browser context. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015tKdPZt78zbPUZMXWzKEKt
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "./db/client";
|
||||
import { servers } from "./db/schema";
|
||||
import { redis } from "./redis";
|
||||
|
||||
export interface PlayerPosition {
|
||||
uuid: string;
|
||||
username: string;
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
}
|
||||
|
||||
export function playerChannel(serverId: string): string {
|
||||
return `mcmapper:players:${serverId}`;
|
||||
}
|
||||
|
||||
function snapshotKey(serverId: string): string {
|
||||
return `mcmapper:players:snapshot:${serverId}`;
|
||||
}
|
||||
|
||||
export async function arePlayerPositionsVisible(serverId: string): Promise<boolean> {
|
||||
const [row] = await db
|
||||
.select({ playerPositionsVisible: servers.playerPositionsVisible })
|
||||
.from(servers)
|
||||
.where(eq(servers.id, serverId))
|
||||
.limit(1);
|
||||
return row?.playerPositionsVisible ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fans a player roster out to every browser subscribed to this server's channel, and stashes it
|
||||
* in Redis so a browser tab that connects between mod flushes gets the current roster immediately
|
||||
* instead of waiting up to `playerPositionIntervalTicks` for the next one — see
|
||||
* players-gateway.ts's `open()`. No history/persistence beyond the latest snapshot: unlike chat,
|
||||
* player positions have no meaningful "history" to keep.
|
||||
*/
|
||||
export async function publishPlayerPositions(serverId: string, dimension: number, players: PlayerPosition[]) {
|
||||
const payload = JSON.stringify({ dimension, players });
|
||||
await redis.set(snapshotKey(serverId), payload);
|
||||
await redis.publish(playerChannel(serverId), payload);
|
||||
}
|
||||
|
||||
export async function getPlayerSnapshot(serverId: string): Promise<string | null> {
|
||||
return redis.get(snapshotKey(serverId));
|
||||
}
|
||||
Reference in New Issue
Block a user