UNPKG

shoehive

Version:

WebSocket-based multiplayer game framework for real-time, event-driven gameplay

197 lines (196 loc) 7.39 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Lobby = void 0; const EventTypes_1 = require("../events/EventTypes"); /** * ✅ Attribute Support * * The Lobby class manages the lobby state and broadcasts updates to connected players. * It is responsible for tracking available games and tables, and notifying players * when these change. */ class Lobby { constructor(eventBus, gameManager, tableFactory) { this.eventBus = eventBus; this.gameManager = gameManager; this.tableFactory = tableFactory; this.setupEventListeners(); this.attributes = new Map(); } /** * Sets up event listeners for lobby-related events. * This listens for table creation, table emptying, and player attribute changes * that would affect lobby display. */ setupEventListeners() { this.eventBus.on({ event: EventTypes_1.TABLE_EVENTS.CREATED, listener: () => { this.broadcastLobbyUpdate(); }, }); this.eventBus.on({ event: EventTypes_1.TABLE_EVENTS.EMPTY, listener: () => { this.broadcastLobbyUpdate(); }, }); // Listen for player attribute changes that might affect lobby data this.eventBus.on({ event: EventTypes_1.PLAYER_EVENTS.ATTRIBUTE_CHANGED, listener: ({ player, key, valueNode: _value }) => { // If the player is at a table and the attribute might affect lobby display const table = player.getTable(); if (!table) return; // Get the game ID for this table const gameId = table.getAttribute({ key: 'gameId' }); if (!gameId) return; // Get the game definition const gameDefinition = this.gameManager.getGameDefinition({ gameId: gameId }); if (!gameDefinition) return; // Use the game-specific lobby relevant attributes, or fall back to defaults const lobbyRelevantAttributes = gameDefinition.lobbyRelevantPlayerAttributes || [ 'name', 'avatar', 'isReady', 'status', ]; // Only update the lobby if the attribute is relevant to lobby display if (lobbyRelevantAttributes.includes(key)) { this.broadcastLobbyUpdate(); } }, }); // Listen for table attribute changes that might affect lobby display this.eventBus.on({ event: EventTypes_1.TABLE_EVENTS.ATTRIBUTE_CHANGED, listener: ({ table, key, value }) => { const metadataAttributes = ['gameId', 'gameName', 'options']; if (metadataAttributes.includes(key)) { this.broadcastLobbyUpdate(); } }, }); } /** * Creates a new table for a game. * * @param gameId The ID of the game. * @param options Optional options for the table. * @returns The newly created table, or null if the game definition is not found. */ createTable({ gameId, options, }) { const gameDefinition = this.gameManager.getGameDefinition({ gameId: gameId }); if (!gameDefinition) { console.error(`Failed to create table: ${gameId} (game definition not found)`); return null; } const table = this.tableFactory.createTable({ totalSeats: gameDefinition.defaultSeats, maxSeatsPerPlayer: gameDefinition.maxSeatsPerPlayer, id: undefined, gameId: gameId, options: options, }); table.setAttribute({ key: 'gameName', value: gameDefinition.name }); // Call the setupTable function if provided in game definition options if (gameDefinition.options?.setupTable && typeof gameDefinition.options.setupTable === 'function') { gameDefinition.options.setupTable({ table }); } else { console.error(`${gameId} (setupTable function not found). Continuing...`); } return table; } /** * Broadcasts a lobby update to all players. */ broadcastLobbyUpdate() { const lobbyState = { games: this.gameManager.getAvailableGames(), tables: this.gameManager.getAllTables().map((table) => table.getTableMetadata()), }; this.eventBus.emit(EventTypes_1.LOBBY_EVENTS.UPDATED, { lobbyState }); } /** * Broadcasts a lobby update to all players. * This method can be called externally to force a lobby update. */ updateLobbyState() { this.broadcastLobbyUpdate(); } /** * Set a single attribute on the lobby and emit an event for the change. * * @param key The attribute name * @param value The attribute value * @param notify Whether to emit an event (defaults to true) */ setAttribute({ key, value, notify = true, }) { this.attributes.set(key, value); if (notify) { this.eventBus.emit(EventTypes_1.LOBBY_EVENTS.ATTRIBUTE_CHANGED, { table: this, key, value }); } } /** * Set multiple attributes at once and emit a single event. * This is more efficient than calling setAttribute multiple times. * * @param attributes Object containing attribute key-value pairs */ setAttributes({ attributes }) { const changedKeys = []; // Set all attributes first for (const [key, value] of Object.entries(attributes)) { this.attributes.set(key, value); changedKeys.push(key); } // Then emit a single event for all changes if (changedKeys.length > 0) { this.eventBus.emit(EventTypes_1.LOBBY_EVENTS.ATTRIBUTES_CHANGED, { table: this, changedKeys, attributes }); // Also emit individual events for backward compatibility for (const key of changedKeys) { this.eventBus.emit(EventTypes_1.LOBBY_EVENTS.ATTRIBUTE_CHANGED, { table: this, key, value: attributes[key], }); } } } /** * Get a single attribute from the lobby. * @param key - The key of the attribute to get * @returns The value of the attribute, or undefined if it doesn't exist */ getAttribute({ key }) { return this.attributes.get(key); } /** * Get all attributes from the lobby. * @returns An object containing all lobby attributes */ getAttributes() { return Object.fromEntries(this.attributes.entries()); } /** * Check if the lobby has an attribute. * @param key - The key of the attribute to check * @returns True if the attribute exists, false otherwise */ hasAttribute({ key }) { return this.attributes.has(key); } /** * Get the number of tables in the lobby. * @returns The number of tables in the lobby */ getTableCount() { return this.gameManager.getAllTables().length; } } exports.Lobby = Lobby;