From 951b9cc701b19ffb5639f8de56958f7eeab102f1 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 8 Mar 2026 04:44:16 +0000 Subject: [PATCH] feat: add superlatives subscribe/unsubscribe socket handlers Co-Authored-By: Claude Opus 4.6 --- server/src/network/SocketServer.ts | 47 +++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/server/src/network/SocketServer.ts b/server/src/network/SocketServer.ts index 1d1ca0d..ffc39eb 100644 --- a/server/src/network/SocketServer.ts +++ b/server/src/network/SocketServer.ts @@ -1,4 +1,4 @@ -import { Server } from 'socket.io'; +import { Server, Socket } from 'socket.io'; import type http from 'http'; import type { ClientEvents, ServerEvents, PlayerInput, Position, Movement, PlayerControlled, Appearance } from '@dflike/shared'; import { Direction, MAX_NPC_COUNT } from '@dflike/shared'; @@ -6,10 +6,14 @@ import { generateRandomAppearance } from '../spawner/appearanceGenerator.js'; import type { GameLoop } from '../game/GameLoop.js'; import { spawnNPC } from '../game/spawner.js'; import { serializeWorldState, serializeStateUpdate } from './stateSerializer.js'; +import { computeSuperlatives } from '../systems/superlativesComputer.js'; +import type { BondRegistry } from '../systems/bondRegistry.js'; export class SocketServer { private io: Server; private gameLoop: GameLoop; + private superlativesSubscribers: Set> = new Set(); + private superlativesTimer: ReturnType | null = null; constructor(httpServer: http.Server, gameLoop: GameLoop) { this.io = new Server(httpServer, { @@ -69,15 +73,56 @@ export class SocketServer { spawnNPC(world, map, target); }); + // Handle superlatives subscription + socket.on('superlatives-subscribe', () => { + this.superlativesSubscribers.add(socket); + // Send immediately + const registry = this.gameLoop.world.getSingleton('bondRegistry'); + if (registry) { + const data = computeSuperlatives(this.gameLoop.world, registry); + socket.emit('superlatives-update', data); + } + // Start timer if first subscriber + if (!this.superlativesTimer && this.superlativesSubscribers.size === 1) { + this.superlativesTimer = setInterval(() => { + this.broadcastSuperlatives(); + }, 10000); + } + }); + + socket.on('superlatives-unsubscribe', () => { + this.superlativesSubscribers.delete(socket); + this.stopSuperlativesTimerIfEmpty(); + }); + // Handle disconnect socket.on('disconnect', () => { console.log(`Player disconnected: ${playerId}`); + this.superlativesSubscribers.delete(socket); + this.stopSuperlativesTimerIfEmpty(); world.removeEntity(entity); this.io.emit('player-left', { playerId }); }); }); } + private broadcastSuperlatives(): void { + if (this.superlativesSubscribers.size === 0) return; + const registry = this.gameLoop.world.getSingleton('bondRegistry'); + if (!registry) return; + const data = computeSuperlatives(this.gameLoop.world, registry); + for (const socket of this.superlativesSubscribers) { + socket.emit('superlatives-update', data); + } + } + + private stopSuperlativesTimerIfEmpty(): void { + if (this.superlativesSubscribers.size === 0 && this.superlativesTimer) { + clearInterval(this.superlativesTimer); + this.superlativesTimer = null; + } + } + private handleInput(playerId: string, entityId: number, input: PlayerInput): void { const world = this.gameLoop.world; const pc = world.getComponent(entityId, 'playerControlled');