mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-26 17:48:32 +01:00
fix monster bug and let spectators join pending games
This commit is contained in:
@@ -5,13 +5,32 @@ var ScreenManager = {
|
||||
_blockTimer: null,
|
||||
_updateInterval: null, // Track the update interval separately
|
||||
_waitingForBlock: false,
|
||||
_isShowingWaitingScreen: false, // Track if waiting screen is active
|
||||
_cryptoType: 'WOW', // Default to WOW, updated on game mode info
|
||||
|
||||
// Get the game title based on crypto type
|
||||
getGameTitle: function() {
|
||||
return this._cryptoType === 'XMR' ? 'MONEROGUE' : 'WOWNEROGUE';
|
||||
},
|
||||
|
||||
// Set crypto type (called from socket handlers)
|
||||
setCryptoType: function(cryptoType) {
|
||||
this._cryptoType = cryptoType || 'WOW';
|
||||
},
|
||||
|
||||
// Check if waiting screen is currently being shown
|
||||
isShowingWaitingScreen: function() {
|
||||
return this._isShowingWaitingScreen;
|
||||
},
|
||||
|
||||
init: function(screenWidth, screenHeight) {
|
||||
this._screenWidth = screenWidth;
|
||||
this._screenHeight = screenHeight;
|
||||
this._currentBlockHeight = 0;
|
||||
this._waitingForBlock = false;
|
||||
this._isShowingWaitingScreen = false;
|
||||
this._updateInterval = null; // Initialize update interval tracker
|
||||
this._cryptoType = 'WOW';
|
||||
|
||||
// Initialize the waiting screen animator
|
||||
if (typeof WaitingScreenAnimator !== 'undefined') {
|
||||
@@ -60,15 +79,19 @@ var ScreenManager = {
|
||||
|
||||
drawWelcomeScreen: function() {
|
||||
if (!DisplayManager.ensureDisplay()) return;
|
||||
|
||||
// Mark that we're NOT showing the waiting screen
|
||||
this._isShowingWaitingScreen = false;
|
||||
|
||||
DisplayManager.clearDisplay();
|
||||
|
||||
// Draw the border
|
||||
this.drawBorder();
|
||||
|
||||
// Draw content
|
||||
// Draw content - use dynamic title based on crypto type
|
||||
let y = 3;
|
||||
this.drawCenteredText(y, "WOWGUE");
|
||||
y += 4;
|
||||
this.drawCenteredText(y, this.getGameTitle());
|
||||
y += 3;
|
||||
|
||||
// Draw instructions
|
||||
this.drawCenteredText(y, "Type enter to queue!");
|
||||
@@ -102,9 +125,11 @@ var ScreenManager = {
|
||||
y++;
|
||||
}
|
||||
|
||||
// Draw current block info
|
||||
// Draw current block info on two lines
|
||||
y += 2;
|
||||
this.drawCenteredText(y, `Current Block: ${this._currentBlockHeight}`);
|
||||
this.drawCenteredText(y, "Current Block:");
|
||||
y += 1;
|
||||
this.drawCenteredText(y, `${this._currentBlockHeight}`);
|
||||
|
||||
// Control HTML button visibility
|
||||
const startButton = document.getElementById('startButton');
|
||||
@@ -228,6 +253,10 @@ var ScreenManager = {
|
||||
|
||||
drawWaitingScreen: function(_internalLoopCall = false) {
|
||||
if (!DisplayManager.ensureDisplay()) return;
|
||||
|
||||
// Mark that we're showing the waiting screen
|
||||
this._isShowingWaitingScreen = true;
|
||||
|
||||
DisplayManager.clearDisplay();
|
||||
|
||||
const animationEnabled = (typeof WaitingScreenAnimator !== 'undefined') ?
|
||||
|
||||
@@ -442,6 +442,11 @@ const SocketHandlers = {
|
||||
UI.updateGameTitle(data.cryptoType);
|
||||
}
|
||||
|
||||
// Update ScreenManager with crypto type for welcome screen title
|
||||
if (typeof ScreenManager !== 'undefined' && ScreenManager.setCryptoType) {
|
||||
ScreenManager.setCryptoType(data.cryptoType);
|
||||
}
|
||||
|
||||
// Display testnet/stagenet warning if applicable
|
||||
if (data.testnetWarning) {
|
||||
SocketHandlers._showNetworkWarning(data.testnetWarning, data.network);
|
||||
@@ -653,6 +658,12 @@ const SocketHandlers = {
|
||||
// Sync to ScreenManager for welcome screen display
|
||||
if (typeof ScreenManager !== 'undefined' && data.blockHeight) {
|
||||
ScreenManager._currentBlockHeight = data.blockHeight;
|
||||
// Redraw welcome screen if not in a game, not spectating, and not showing waiting screen
|
||||
if (typeof GameState !== 'undefined' && !GameState.isGameActive() &&
|
||||
typeof Game !== 'undefined' && !Game._isSpectating &&
|
||||
!ScreenManager.isShowingWaitingScreen()) {
|
||||
ScreenManager.drawWelcomeScreen();
|
||||
}
|
||||
}
|
||||
// Fallback: if status banner still says Connecting..., upgrade it
|
||||
const current = $('#statusValue').text();
|
||||
@@ -783,6 +794,7 @@ const SocketHandlers = {
|
||||
<strong>👁️ Live Games</strong>
|
||||
<button id="gamesListClose" style="background: none; border: 1px solid #0f0; color: #0f0; cursor: pointer; padding: 2px 6px;">×</button>
|
||||
</div>
|
||||
<div id="pendingGamesContent"></div>
|
||||
<div id="gamesListContent"></div>
|
||||
<div id="gamesListPagination" style="border-top: 1px solid #0f0; padding-top: 5px; margin-top: 10px; text-align: center;"></div>
|
||||
</div>
|
||||
@@ -803,14 +815,59 @@ const SocketHandlers = {
|
||||
});
|
||||
}
|
||||
|
||||
// Render pending games section
|
||||
const $pendingContent = $('#pendingGamesContent');
|
||||
$pendingContent.empty();
|
||||
|
||||
if (data.pendingGames && data.pendingGames.length > 0) {
|
||||
$pendingContent.append(`
|
||||
<div style="color: #fa0; font-size: 11px; margin-bottom: 5px; border-bottom: 1px solid #550;">
|
||||
⏳ Pending (${data.pendingGames.length})
|
||||
</div>
|
||||
`);
|
||||
|
||||
data.pendingGames.forEach(function(pending) {
|
||||
const waitTime = Math.floor((Date.now() - pending.queuedAt) / 1000);
|
||||
const status = pending.waitingForConfirmation ? '⏳ Confirming' : '✅ Ready';
|
||||
|
||||
$pendingContent.append(`
|
||||
<div style="
|
||||
padding: 6px;
|
||||
margin-bottom: 5px;
|
||||
background: rgba(60, 40, 0, 0.5);
|
||||
border: 1px solid #550;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
">
|
||||
<div style="display: flex; justify-content: space-between;">
|
||||
<span style="color: #fa0;">Player ${pending.playerId}</span>
|
||||
<span style="color: #888;">${waitTime}s ago</span>
|
||||
</div>
|
||||
<div style="color: #888; margin-top: 2px;">
|
||||
${status} - waiting for next block
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
});
|
||||
}
|
||||
|
||||
// Render active games section
|
||||
const $content = $('#gamesListContent');
|
||||
$content.empty();
|
||||
|
||||
if (!data.games || data.games.length === 0) {
|
||||
$content.html('<div style="color: #888; text-align: center; padding: 20px;">No active games</div>');
|
||||
if (!data.pendingGames || data.pendingGames.length === 0) {
|
||||
$content.html('<div style="color: #888; text-align: center; padding: 20px;">No active games</div>');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$content.append(`
|
||||
<div style="color: #0f0; font-size: 11px; margin-bottom: 5px; margin-top: 10px; border-bottom: 1px solid #050;">
|
||||
🎮 Live (${data.games.length})
|
||||
</div>
|
||||
`);
|
||||
|
||||
// Render each game
|
||||
data.games.forEach(function(game) {
|
||||
const duration = game.durationSeconds || 0;
|
||||
|
||||
+7
-2
@@ -52,8 +52,8 @@ const UI = {
|
||||
|
||||
updateGameTitle: function(cryptoType) {
|
||||
if (cryptoType === 'XMR') {
|
||||
document.title = "XMROGUE - A Monero Roguelike";
|
||||
this._gameTitleFull = "XMROGUE - A Monero Roguelike";
|
||||
document.title = "Monerogue - A Monero Roguelike";
|
||||
this._gameTitleFull = "MONEROGUE - A Monero Roguelike";
|
||||
} else {
|
||||
document.title = "Wownerogue - A Wownero Roguelike";
|
||||
this._gameTitleFull = "WOWNEROGUE - A Wownero Roguelike";
|
||||
@@ -63,6 +63,11 @@ const UI = {
|
||||
if ($('#staticbanner').is(':visible') && !$('#banner').is(':visible')) {
|
||||
$('#staticbanner').text(this._gameTitleFull);
|
||||
}
|
||||
|
||||
// Also update ScreenManager for welcome screen display
|
||||
if (typeof ScreenManager !== 'undefined' && ScreenManager.setCryptoType) {
|
||||
ScreenManager.setCryptoType(cryptoType);
|
||||
}
|
||||
},
|
||||
|
||||
// Initialize browser detection
|
||||
|
||||
+18
-2
@@ -168,8 +168,8 @@ class Game {
|
||||
this.dungeon.map[newY][newX] !== undefined &&
|
||||
(this.dungeon.map[newY][newX] === primaryFloor || this.dungeon.map[newY][newX] === secondaryFloor)) {
|
||||
|
||||
this.player.moveTo(newX, newY);
|
||||
this.moveCount += 1;
|
||||
this.player.moveTo(newX, newY);
|
||||
this.moveCount += 1;
|
||||
if (CONSOLE_LOGGING) {
|
||||
console.log(`Player moved to ${newX},${newY} in game for socket ${this.socketId}`);
|
||||
}
|
||||
@@ -177,6 +177,22 @@ class Game {
|
||||
// Update FOV after moving
|
||||
this.updateFOV();
|
||||
|
||||
// Check if player walked into monster (death by collision)
|
||||
if (this.monster && this.monster.x === newX && this.monster.y === newY) {
|
||||
this.gameState = 'lost';
|
||||
if (CONSOLE_LOGGING) {
|
||||
console.log(`💀 Player walked into monster at (${newX},${newY}) - GAME OVER`);
|
||||
}
|
||||
return {
|
||||
status: 'moved',
|
||||
event: 'monster_caught',
|
||||
player: this.player.getState(),
|
||||
monster: this.monster.getState(),
|
||||
visibleTiles: this.visibleTiles,
|
||||
moves: this.moveCount
|
||||
};
|
||||
}
|
||||
|
||||
// Check for game events like finding treasure or exit
|
||||
if (this.dungeon.exit && this.player.isAt(this.dungeon.exit[0], this.dungeon.exit[1])) {
|
||||
this.gameState = 'won';
|
||||
|
||||
@@ -112,7 +112,14 @@ class GameManager {
|
||||
proof: proofReveal
|
||||
});
|
||||
|
||||
// Notify spectators that the game has ended
|
||||
// Persist completion details if DB available
|
||||
await this._updateGameRecord(game, socketId, status, reason, moves, durationSeconds);
|
||||
|
||||
// Clean up game from active games BEFORE notifying spectators
|
||||
// This ensures the game list broadcast won't include this game
|
||||
this.activeGames.delete(socketId);
|
||||
|
||||
// Notify spectators that the game has ended and broadcast updated list
|
||||
if (this.spectatorManager) {
|
||||
this.spectatorManager.notifyGameEnded(game.id, {
|
||||
status,
|
||||
@@ -124,12 +131,6 @@ class GameManager {
|
||||
treasure: game.player.hasTreasure || false
|
||||
});
|
||||
}
|
||||
|
||||
// Persist completion details if DB available
|
||||
await this._updateGameRecord(game, socketId, status, reason, moves, durationSeconds);
|
||||
|
||||
// Clean up game from active games
|
||||
this.activeGames.delete(socketId);
|
||||
|
||||
if (this.debugManager.CONSOLE_LOGGING) {
|
||||
console.log(`🎮 Game ${game.id} ended for ${socketId}: ${status} (${reason})`);
|
||||
|
||||
@@ -154,6 +154,20 @@ class QueueManager {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of pending games (players waiting in queue)
|
||||
* Used by spectator system to show upcoming games
|
||||
* @returns {Array} List of pending game entries
|
||||
*/
|
||||
getPendingGamesList() {
|
||||
return this._waitingPlayers.map(entry => ({
|
||||
playerId: entry.serverId.substring(0, 6), // Anonymized
|
||||
queuedAt: entry.entryTime,
|
||||
isConfirmed: entry.confirmed,
|
||||
waitingForConfirmation: entry.requiresConfirmation && !entry.confirmed
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = QueueManager;
|
||||
|
||||
@@ -155,7 +155,8 @@ class SocketHandlers {
|
||||
io: this.io,
|
||||
activeGames: this.activeGames,
|
||||
broadcastManager: this.broadcastManager,
|
||||
debugManager: this.debugManager
|
||||
debugManager: this.debugManager,
|
||||
queueManager: this.queueManager // For pending games list
|
||||
});
|
||||
this.spectatorManager.initialize();
|
||||
|
||||
@@ -295,6 +296,20 @@ class SocketHandlers {
|
||||
* Handles monster chasing logic and immediate game over if monster catches player.
|
||||
*/
|
||||
afterPlayerMove(socketId, game, moveResult) {
|
||||
// Check if player walked into monster (death by walking into monster)
|
||||
if (moveResult && moveResult.event === 'monster_caught') {
|
||||
const fakeSocket = { id: socketId };
|
||||
this.handleGameOver(fakeSocket, game, 'lost', 'monster', 'You walked into the monster!', 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// If player escaped, handle via game manager
|
||||
if (moveResult && moveResult.event === 'escaped') {
|
||||
const fakeSocket = { id: socketId };
|
||||
this.handleGameOver(fakeSocket, game, 'won', 'escaped', 'You escaped the dungeon!', 0);
|
||||
return;
|
||||
}
|
||||
|
||||
let monsterResult = null;
|
||||
|
||||
// Move monster one step toward player each player action
|
||||
@@ -306,20 +321,13 @@ class SocketHandlers {
|
||||
}
|
||||
}
|
||||
|
||||
// If the monster caught the player, end the game immediately
|
||||
// If the monster caught the player after moving, end the game immediately
|
||||
if (monsterResult && monsterResult.event === 'monster_caught') {
|
||||
const fakeSocket = { id: socketId };
|
||||
this.handleGameOver(fakeSocket, game, 'lost', 'monster', 'You were caught by the monster!', 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// If player escaped or triggered another event, handle via game manager
|
||||
if (moveResult && moveResult.event === 'escaped') {
|
||||
const fakeSocket = { id: socketId };
|
||||
this.handleGameOver(fakeSocket, game, 'won', 'escaped', 'You escaped the dungeon!', 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Re-emit state if treasure collected without auto game_update (fallback)
|
||||
if (moveResult && moveResult.event === 'treasure_found') {
|
||||
this.movementManager.emitGameUpdate(socketId);
|
||||
|
||||
@@ -10,11 +10,12 @@
|
||||
*/
|
||||
|
||||
class SpectatorManager {
|
||||
constructor({ io, activeGames, broadcastManager, debugManager }) {
|
||||
constructor({ io, activeGames, broadcastManager, debugManager, queueManager = null }) {
|
||||
this.io = io;
|
||||
this.activeGames = activeGames;
|
||||
this.broadcastManager = broadcastManager;
|
||||
this.debugManager = debugManager;
|
||||
this.queueManager = queueManager; // For pending games list
|
||||
|
||||
// Map of gameId -> Set of spectator socket IDs
|
||||
this._spectatorsByGame = new Map();
|
||||
@@ -22,6 +23,10 @@ class SpectatorManager {
|
||||
// Map of socketId -> gameId (for quick lookup of what a spectator is watching)
|
||||
this._spectatorWatching = new Map();
|
||||
|
||||
// Pending game subscriptions: Map of playerId -> Set of spectator socket IDs
|
||||
// Spectators waiting for a pending game to start
|
||||
this._pendingSubscriptions = new Map();
|
||||
|
||||
// Cached game list for efficient broadcasting
|
||||
this._cachedGameList = [];
|
||||
this._gameListLastUpdate = 0;
|
||||
@@ -32,6 +37,13 @@ class SpectatorManager {
|
||||
this._broadcastIntervalMs = 3000; // Broadcast every 3 seconds
|
||||
}
|
||||
|
||||
/**
|
||||
* Set queue manager (for late binding)
|
||||
*/
|
||||
setQueueManager(queueManager) {
|
||||
this.queueManager = queueManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the manager - start periodic broadcasts
|
||||
*/
|
||||
@@ -49,7 +61,7 @@ class SpectatorManager {
|
||||
/**
|
||||
* Get list of active games with spectator-safe info
|
||||
* @param {Object} options - Filtering/pagination options
|
||||
* @returns {Array} List of active games with metadata
|
||||
* @returns {Object} Object containing active games and pending games
|
||||
*/
|
||||
getActiveGamesList(options = {}) {
|
||||
const { page = 1, pageSize = 20, sortBy = 'newest' } = options;
|
||||
@@ -60,7 +72,7 @@ class SpectatorManager {
|
||||
return this._applyPagination(this._cachedGameList, page, pageSize);
|
||||
}
|
||||
|
||||
// Build fresh list
|
||||
// Build fresh list of active games
|
||||
const games = [];
|
||||
for (const [socketId, game] of this.activeGames.entries()) {
|
||||
if (game && game.gameState === 'active') {
|
||||
@@ -78,6 +90,17 @@ class SpectatorManager {
|
||||
return this._applyPagination(games, page, pageSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of pending games (players waiting in queue)
|
||||
* @returns {Array} List of pending games
|
||||
*/
|
||||
getPendingGamesList() {
|
||||
if (!this.queueManager || typeof this.queueManager.getPendingGamesList !== 'function') {
|
||||
return [];
|
||||
}
|
||||
return this.queueManager.getPendingGamesList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a spectator-safe game summary
|
||||
* @private
|
||||
@@ -302,25 +325,27 @@ class SpectatorManager {
|
||||
*/
|
||||
notifyGameEnded(gameId, gameOverData) {
|
||||
const spectators = this._spectatorsByGame.get(gameId);
|
||||
if (!spectators || spectators.size === 0) return;
|
||||
|
||||
// Emit game over to spectators
|
||||
this.io.to(`spectate:${gameId}`).emit('spectate_ended', {
|
||||
gameId: gameId,
|
||||
reason: 'game_over',
|
||||
gameOverData: gameOverData
|
||||
});
|
||||
|
||||
// Remove all spectators from this game's tracking
|
||||
for (const spectatorId of spectators) {
|
||||
this._spectatorWatching.delete(spectatorId);
|
||||
const sock = this._getSocket(spectatorId);
|
||||
if (sock) sock.leave(`spectate:${gameId}`);
|
||||
// Emit game over to spectators if any
|
||||
if (spectators && spectators.size > 0) {
|
||||
this.io.to(`spectate:${gameId}`).emit('spectate_ended', {
|
||||
gameId: gameId,
|
||||
reason: 'game_over',
|
||||
gameOverData: gameOverData
|
||||
});
|
||||
|
||||
// Remove all spectators from this game's tracking
|
||||
for (const spectatorId of spectators) {
|
||||
this._spectatorWatching.delete(spectatorId);
|
||||
const sock = this._getSocket(spectatorId);
|
||||
if (sock) sock.leave(`spectate:${gameId}`);
|
||||
}
|
||||
this._spectatorsByGame.delete(gameId);
|
||||
}
|
||||
this._spectatorsByGame.delete(gameId);
|
||||
|
||||
// Invalidate cache
|
||||
// Invalidate cache and immediately broadcast updated game list
|
||||
this._gameListLastUpdate = 0;
|
||||
this._broadcastGameListToLobby();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -365,8 +390,14 @@ class SpectatorManager {
|
||||
// Get fresh game list
|
||||
const gameListData = this.getActiveGamesList({ page: 1, pageSize: 50 });
|
||||
|
||||
// Only broadcast if there are active games
|
||||
if (gameListData.pagination.totalGames === 0) return;
|
||||
// Get pending games
|
||||
const pendingGames = this.getPendingGamesList();
|
||||
|
||||
// Add pending games to the response
|
||||
gameListData.pendingGames = pendingGames;
|
||||
|
||||
// Only broadcast if there are active or pending games
|
||||
if (gameListData.pagination.totalGames === 0 && pendingGames.length === 0) return;
|
||||
|
||||
// Broadcast to a "lobby" room - users join this room when not in game
|
||||
this.io.to('lobby').emit('active_games', gameListData);
|
||||
|
||||
Reference in New Issue
Block a user