826233e10c
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
47 lines
1.6 KiB
TypeScript
47 lines
1.6 KiB
TypeScript
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));
|
|
}
|