mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-19 07:08:33 +02:00
- FOG: sceneFromGameState only emits exit/entrance/treasure/items once their cell is EXPLORED, and the monster only while IN VIEW — no more exit stairs leaking through the fog. - DESCEND FEEDBACK: reaching a non-final exit descends (multi-level) and used to silently dump the player at a new entrance (read as a bug). Server now sends depth/maxDepth on game_event; the client shows "Stairs down! Level N of M — keep going". Also lowered level counts (WOW 4->2, BTC 8->3, XMR 2->1) — clearing 4-8 levels in one block was unwinnable; kept modest until proper multi-level calibration + UX. - CAMERA: the host filled the very-tall #game-display so centering put the player low; it's now a bounded viewport (min(100%,78vh)) centered in #game-display, so the player stays on-screen-centered. Suite 62/435 green.
101 lines
3.3 KiB
JavaScript
101 lines
3.3 KiB
JavaScript
/**
|
|
* MovementManager
|
|
* Handles rate limiting and applying player movement to games.
|
|
*/
|
|
class MovementManager {
|
|
constructor({ activeGames, io, debugManager, moveCooldown = 100, postMoveHook = null, spectatorManager = null }) {
|
|
this.activeGames = activeGames; // Map socketId -> Game
|
|
this.io = io;
|
|
this.debugManager = debugManager;
|
|
this.moveCooldown = moveCooldown;
|
|
this._lastMove = new Map(); // socketId -> timestamp
|
|
this.postMoveHook = typeof postMoveHook === 'function' ? postMoveHook : null;
|
|
this.spectatorManager = spectatorManager; // For broadcasting to spectators
|
|
}
|
|
|
|
handleMove(socketId, moveData) {
|
|
if (!moveData || typeof moveData.direction !== 'string') return;
|
|
const now = Date.now();
|
|
const last = this._lastMove.get(socketId) || 0;
|
|
if (now - last < this.moveCooldown) return; // rate limit
|
|
this._lastMove.set(socketId, now);
|
|
|
|
const game = this.activeGames.get(socketId);
|
|
if (!game) return; // not in a game
|
|
|
|
const dir = moveData.direction;
|
|
let dx = 0, dy = 0;
|
|
switch (dir) {
|
|
case 'up': dy = -1; break;
|
|
case 'down': dy = 1; break;
|
|
case 'left': dx = -1; break;
|
|
case 'right': dx = 1; break;
|
|
default: return;
|
|
}
|
|
|
|
let moveResult = null;
|
|
if (typeof game.movePlayer === 'function') {
|
|
moveResult = game.movePlayer(dx, dy);
|
|
}
|
|
|
|
// Allow hook to perform monster movement or other side-effects BEFORE we snapshot state
|
|
if (moveResult && moveResult.status === 'moved' && this.postMoveHook) {
|
|
try {
|
|
this.postMoveHook({ socketId, game, moveResult });
|
|
} catch (e) {
|
|
console.error('postMoveHook error:', e);
|
|
}
|
|
}
|
|
|
|
// Build state after player + potential monster move
|
|
let state;
|
|
if (typeof game.getState === 'function') {
|
|
state = game.getState();
|
|
} else {
|
|
state = { player: game.player };
|
|
}
|
|
if (this.debugManager && typeof this.debugManager.getCurrentBlockHeight === 'function') {
|
|
state.blockHeight = this.debugManager.getCurrentBlockHeight();
|
|
}
|
|
|
|
this.io.to(socketId).emit('game_update', state);
|
|
|
|
// Broadcast to spectators
|
|
if (this.spectatorManager && game.id) {
|
|
this.spectatorManager.broadcastToSpectators(game.id, state);
|
|
}
|
|
|
|
// Handle special events from moveResult (escape / treasure / descend). Include depth for the
|
|
// multi-level descend so the client can tell the player they took the stairs down (not a bug).
|
|
if (moveResult && moveResult.event) {
|
|
this.io.to(socketId).emit('game_event', {
|
|
event: moveResult.event, depth: moveResult.depth, maxDepth: moveResult.maxDepth
|
|
});
|
|
}
|
|
}
|
|
|
|
emitGameUpdate(socketId) {
|
|
const game = this.activeGames.get(socketId);
|
|
if (!game) {
|
|
return;
|
|
}
|
|
let state;
|
|
if (typeof game.getState === 'function') {
|
|
state = game.getState();
|
|
} else {
|
|
state = { player: game.player };
|
|
}
|
|
if (this.debugManager && typeof this.debugManager.getCurrentBlockHeight === 'function') {
|
|
state.blockHeight = this.debugManager.getCurrentBlockHeight();
|
|
}
|
|
this.io.to(socketId).emit('game_update', state);
|
|
|
|
// Broadcast to spectators
|
|
if (this.spectatorManager && game.id) {
|
|
this.spectatorManager.broadcastToSpectators(game.id, state);
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = MovementManager;
|