diff --git a/html/js/input/inputHandler.js b/html/js/input/inputHandler.js index 85281e5..fccbca5 100644 --- a/html/js/input/inputHandler.js +++ b/html/js/input/inputHandler.js @@ -201,7 +201,20 @@ const InputHandler = { setupClickHandlers: function() { // Add click handling for the HTML START button $('#startButton').click(function(e) { - // Attempt immediate start + // If early entry is available and user has credits, show choice modal + if (typeof SocketHandlers !== 'undefined') { + var isPaidCredits = SocketHandlers._gameMode === 'PAID_CREDITS'; + var hasCredits = SocketHandlers._creditsBalance >= (SocketHandlers._creditsPerGame || 1); + var earlyAllowed = SocketHandlers._earlyEntryConfig && + SocketHandlers._earlyEntryConfig.enabled && + SocketHandlers._earlyEntryConfig.allowInCreditsMode; + if (isPaidCredits && hasCredits && earlyAllowed) { + SocketHandlers.showEntryChoiceModal(); + return; + } + } + + // Default: attempt immediate start socket.emit('auto_start'); // Check if we should show waiting screen diff --git a/html/js/network/socketHandlers.js b/html/js/network/socketHandlers.js index 657bcf8..ddf1e50 100644 --- a/html/js/network/socketHandlers.js +++ b/html/js/network/socketHandlers.js @@ -506,8 +506,21 @@ const SocketHandlers = { } } - UI.scrollChat(); const won = data && (data.status === 'won' || data.reason === 'escaped'); + + // Show payout notification for wins + if (won && data.payout && data.payout.amount) { + var decimals = (SocketHandlers._cryptoType === 'WOW') ? 11 : 12; + var amountFormatted = (data.payout.amount / Math.pow(10, decimals)).toFixed(4); + var currency = SocketHandlers._cryptoType || 'WOW'; + var multiplierText = data.payout.multiplier ? ' (' + data.payout.multiplier + 'x)' : ''; + var treasureText = data.payout.treasure ? ' + Treasure bonus!' : ''; + $('#messages').append($('
  • ').html( + '💰 Payout queued: ' + amountFormatted + ' ' + currency + multiplierText + treasureText + )); + } + + UI.scrollChat(); if (won) { SocketHandlers._setBannerStatus('Won', '#0f0'); } else { @@ -591,6 +604,8 @@ const SocketHandlers = { SocketHandlers._directPayoutsEnabled = !!data.directPayoutsEnabled; SocketHandlers._paymentsEnabled = !!data.paymentsEnabled; SocketHandlers._smirkEnabled = data.smirkEnabled !== false; // Default to true if not specified + SocketHandlers._cryptoType = data.cryptoType || 'WOW'; + SocketHandlers._explorerTxUrl = data.explorerTxUrl || null; // Initialize Smirk auth if enabled and not already initialized if (SocketHandlers._smirkEnabled && typeof SmirkAuth !== 'undefined' && !SmirkAuth._initialized) { @@ -1433,14 +1448,91 @@ const SocketHandlers = { */ onEarlyEntryError: function(data) { console.error('Early entry error:', data); - + // Re-enable button const $btn = $('#earlyEntryButton'); $btn.prop('disabled', false).text('⚡ ENTER NOW (RISKY!) ⚡'); - + const message = data?.message || 'Early entry not available'; $('#messages').append($('
  • ').text('Early entry failed: ' + message)); UI.scrollChat(); + }, + + // ===================== + // Entry Choice Modal + // ===================== + + showEntryChoiceModal: function() { + // Remove any existing modal + $('#entryChoiceOverlay').remove(); + + var cost = this._creditsPerGame || 1; + var $overlay = $('
    ').css({ + position: 'fixed', top: 0, left: 0, width: '100%', height: '100%', + background: 'rgba(0,0,0,0.7)', zIndex: 3000, + display: 'flex', alignItems: 'center', justifyContent: 'center' + }); + + var $modal = $('
    ').css({ + background: '#0a1a0a', border: '2px solid #0f0', borderRadius: '8px', + padding: '24px', maxWidth: '420px', width: '90%', color: '#0f0', + fontFamily: 'monospace', textAlign: 'center' + }); + + $modal.html( + '
    How do you want to enter?
    ' + + '
    Cost: ' + cost + ' credit per game
    ' + + '' + + '' + + '' + ); + + $overlay.append($modal); + $('body').append($overlay); + + // Handlers + $('#entryChoiceNow').on('click', function() { + $('#entryChoiceOverlay').remove(); + socket.emit('auto_start'); + $('#messages').append($('
  • ').text('⚡ Starting game immediately...')); + UI.scrollChat(); + if (typeof ScreenManager !== 'undefined' && ScreenManager.drawWaitingScreen) { + ScreenManager.drawWaitingScreen(); + } + }); + + $('#entryChoiceQueue').on('click', function() { + $('#entryChoiceOverlay').remove(); + socket.emit('join_queue'); + $('#messages').append($('
  • ').text('🛡️ Joining queue — waiting for next block...')); + UI.scrollChat(); + if (typeof ScreenManager !== 'undefined' && ScreenManager.drawWaitingScreen) { + ScreenManager.drawWaitingScreen(); + } + }); + + $('#entryChoiceCancel').on('click', function() { + $('#entryChoiceOverlay').remove(); + }); + + // Close on overlay click (outside modal) + $overlay.on('click', function(e) { + if (e.target === this) { + $('#entryChoiceOverlay').remove(); + } + }); } }; diff --git a/html/js/ui/transactionHistory.js b/html/js/ui/transactionHistory.js index 78e8918..cfcc151 100644 --- a/html/js/ui/transactionHistory.js +++ b/html/js/ui/transactionHistory.js @@ -143,7 +143,11 @@ const TransactionHistory = {
    ${payout.reason || 'Game reward'} • ${date}
    - ${payout.txHash ? `
    TX: ${payout.txHash.substring(0, 16)}...
    ` : ''} + ${payout.txHash ? `
    ${ + typeof SocketHandlers !== 'undefined' && SocketHandlers._explorerTxUrl + ? 'TX: ' + payout.txHash.substring(0, 16) + '...' + : 'TX: ' + payout.txHash.substring(0, 16) + '...' + }
    ` : ''} `); diff --git a/src/.env.example b/src/.env.example index f23bd34..e4505d4 100644 --- a/src/.env.example +++ b/src/.env.example @@ -391,6 +391,17 @@ ALERT_COOLDOWN_MS=3600000 # Set to false for Monero stagenet deployments SMIRK_ENABLED=true +# ============================================================================= +# SECTION 14b: BLOCK EXPLORER +# ============================================================================= +# URL prefix for linking transaction hashes to a block explorer. +# Include the trailing slash. TX hash will be appended directly. +# Leave unset to disable clickable TX links in transaction history. +# +# Wownero: https://explore.wowne.ro/tx/ +# Monero: https://xmrchain.net/tx/ +EXPLORER_TX_URL=https://explore.wowne.ro/tx/ + # ============================================================================= # SECTION 15: DEVELOPMENT/DEBUG OPTIONS # ============================================================================= diff --git a/src/game/gameModeManager.js b/src/game/gameModeManager.js index d795093..3142a76 100644 --- a/src/game/gameModeManager.js +++ b/src/game/gameModeManager.js @@ -1071,7 +1071,8 @@ class GameModeManager { allowInCreditsMode: this.configSnapshot?.earlyEntry?.allowInCreditsMode ?? false }, // Smirk wallet integration (disabled for Monero stagenet) - smirkEnabled: process.env.SMIRK_ENABLED !== 'false' + smirkEnabled: process.env.SMIRK_ENABLED !== 'false', + explorerTxUrl: process.env.EXPLORER_TX_URL || null }; } diff --git a/src/index.js b/src/index.js index eab09b6..b9d82ce 100644 --- a/src/index.js +++ b/src/index.js @@ -424,7 +424,7 @@ app.get('/api/user/:socketId/payouts', asyncHandler(async (req, res) => { // Calculate total received (confirmed payouts) const totalResult = await db.query( - `SELECT COALESCE(SUM(amount), 0) as total_received FROM payouts WHERE user_id = $1 AND status = 'confirmed'`, + `SELECT COALESCE(SUM(amount), 0) as total_received FROM payouts WHERE user_id = $1 AND status = 'completed'`, [user.id] ); diff --git a/src/network/socketHandlers.js b/src/network/socketHandlers.js index a1e8164..aa4ff00 100644 --- a/src/network/socketHandlers.js +++ b/src/network/socketHandlers.js @@ -463,6 +463,7 @@ class SocketHandlers { } }); socket.on('auto_start', () => this.handleAutoStart(socket)); // New handler for start button + socket.on('join_queue', () => this.handleJoinQueue(socket)); // Queue instead of auto-start socket.on('early_entry', () => this.handleEarlyEntry(socket)); // Early entry without waiting for block socket.on('address:prompt', () => this.handleAddressPrompt(socket)); @@ -581,6 +582,32 @@ class SocketHandlers { } } + /** + * Handle join_queue request — adds player to block queue instead of starting immediately + */ + async handleJoinQueue(socket) { + try { + await this.queueHandler.handleGameQueue( + socket, + this.connectionHandler.getUserBySocket.bind(this.connectionHandler) + ); + // queueHandler already emits queue status and adds to queue + // Client receives queue_joined event from broadcastManager + const pos = this.queueManager.getPlayerIndex(socket.id); + if (pos !== -1) { + const currentBlock = this.debugManager.getCurrentBlockHeight ? this.debugManager.getCurrentBlockHeight() : 0; + socket.emit('queue_joined', { + position: pos + 1, + currentBlock: currentBlock, + nextBlock: currentBlock + 1 + }); + } + } catch (err) { + console.error('handleJoinQueue error:', err); + this.broadcastManager.sendStatusUpdate(socket.id, 'error', 'Failed to join queue.'); + } + } + /** * Handle early entry request - start game immediately without waiting for block * Only available for free mode and credits mode (not direct payment mode)