Fix rewards, make tx hash clickable, add win payout notification, and add entry choice modal

This commit is contained in:
jwinterm
2026-02-20 12:34:21 -05:00
parent da6763886a
commit 27660d6948
7 changed files with 155 additions and 7 deletions
+14 -1
View File
@@ -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
+95 -3
View File
@@ -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($('<li style="color:#4ade80; font-weight:bold; padding:4px 0;">').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($('<li class="error" style="color:#f00;">').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 = $('<div id="entryChoiceOverlay">').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 = $('<div>').css({
background: '#0a1a0a', border: '2px solid #0f0', borderRadius: '8px',
padding: '24px', maxWidth: '420px', width: '90%', color: '#0f0',
fontFamily: 'monospace', textAlign: 'center'
});
$modal.html(
'<div style="font-size:18px; font-weight:bold; margin-bottom:16px;">How do you want to enter?</div>' +
'<div style="font-size:12px; color:#888; margin-bottom:20px;">Cost: ' + cost + ' credit per game</div>' +
'<button id="entryChoiceNow" style="' +
'display:block; width:100%; padding:12px; margin-bottom:10px; cursor:pointer; ' +
'background:linear-gradient(180deg,#662200,#441100); border:2px solid #ff6600; ' +
'color:#ffcc00; font-family:monospace; font-size:14px; font-weight:bold; border-radius:4px;' +
'">⚡ Start Now (risky)<br><span style="font-size:11px; font-weight:normal; color:#cc9966;">Game starts immediately — you die when next block arrives</span></button>' +
'<button id="entryChoiceQueue" style="' +
'display:block; width:100%; padding:12px; margin-bottom:10px; cursor:pointer; ' +
'background:linear-gradient(180deg,#003300,#001a00); border:2px solid #0f0; ' +
'color:#0f0; font-family:monospace; font-size:14px; font-weight:bold; border-radius:4px;' +
'">🛡️ Wait for Next Block (safe)<br><span style="font-size:11px; font-weight:normal; color:#6a6;">Queues you — full block window to escape</span></button>' +
'<button id="entryChoiceCancel" style="' +
'display:block; width:100%; padding:8px; cursor:pointer; ' +
'background:transparent; border:1px solid #555; color:#888; ' +
'font-family:monospace; font-size:12px; border-radius:4px;' +
'">Cancel</button>'
);
$overlay.append($modal);
$('body').append($overlay);
// Handlers
$('#entryChoiceNow').on('click', function() {
$('#entryChoiceOverlay').remove();
socket.emit('auto_start');
$('#messages').append($('<li style="color:#ff6600;">').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($('<li style="color:#0f0;">').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();
}
});
}
};
+5 -1
View File
@@ -143,7 +143,11 @@ const TransactionHistory = {
<div style="color:#888; font-size:0.85em; margin-top:4px;">
${payout.reason || 'Game reward'} • ${date}
</div>
${payout.txHash ? `<div style="color:#60a5fa; font-size:0.75em; margin-top:4px; word-break:break-all;">TX: ${payout.txHash.substring(0, 16)}...</div>` : ''}
${payout.txHash ? `<div style="font-size:0.75em; margin-top:4px; word-break:break-all;">${
typeof SocketHandlers !== 'undefined' && SocketHandlers._explorerTxUrl
? '<a href="' + SocketHandlers._explorerTxUrl + payout.txHash + '" target="_blank" rel="noopener" style="color:#60a5fa; text-decoration:underline;">TX: ' + payout.txHash.substring(0, 16) + '...</a>'
: '<span style="color:#60a5fa;">TX: ' + payout.txHash.substring(0, 16) + '...</span>'
} <button class="copy-hash-btn" data-hash="${payout.txHash}" style="font-size:10px; padding:1px 4px; cursor:pointer; background:#333; color:#fff; border:1px solid #555; border-radius:3px;">Copy</button></div>` : ''}
</div>
`);
+11
View File
@@ -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
# =============================================================================
+2 -1
View File
@@ -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
};
}
+1 -1
View File
@@ -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]
);
+27
View File
@@ -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)