From f8241dd309865719f81ba8e260f32956619fd462 Mon Sep 17 00:00:00 2001 From: jwinterm Date: Sat, 27 Sep 2025 11:21:11 -0400 Subject: [PATCH] Continue working on payment logic --- html/audio/Nowescapethedunge.m4a | Bin 0 -> 21661 bytes html/audio/Paymentconfirmed.m4a | Bin 0 -> 18348 bytes html/audio/Paymentdetected.m4a | Bin 0 -> 18330 bytes html/audio/Pleasesendcoin.m4a | Bin 0 -> 17891 bytes html/index.html | 13 +- html/js/display/screenManager.js | 5 +- html/js/display/waitingScreenAnimator.js | 21 ++- html/js/input/inputHandler.js | 22 ++- html/js/network/socketHandlers.js | 67 ++++++++ html/js/ui/audioAlerts.js | 41 ++++- src/.env.example | 5 + src/game/gameModeManager.js | 37 ++-- src/index.js | 2 + src/migrations/003_add_anon_token.sql | 6 + src/network/addressManager.js | 86 ++++++++++ src/network/paymentHandlers.js | 28 ++- src/network/sessionManager.js | 50 ++++++ src/network/socketHandlers.js | 209 ++++++++++++++++++++--- 18 files changed, 523 insertions(+), 69 deletions(-) create mode 100644 html/audio/Nowescapethedunge.m4a create mode 100644 html/audio/Paymentconfirmed.m4a create mode 100644 html/audio/Paymentdetected.m4a create mode 100644 html/audio/Pleasesendcoin.m4a create mode 100644 src/migrations/003_add_anon_token.sql create mode 100644 src/network/addressManager.js create mode 100644 src/network/sessionManager.js diff --git a/html/audio/Nowescapethedunge.m4a b/html/audio/Nowescapethedunge.m4a new file mode 100644 index 0000000000000000000000000000000000000000..16d529a8e2dfc9328d2c1207ae2763623c65e13b GIT binary patch literal 21661 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
  • eL8`iX6a{S-Bqvnk|0gH?X&H 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

    -eJxF_P^Q=?u$7H$Y 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 C4u6OM literal 0 HcmV?d00001 diff --git a/html/audio/Paymentdetected.m4a b/html/audio/Paymentdetected.m4a new file mode 100644 index 0000000000000000000000000000000000000000..4af0004010757788ef05b00acbacd5ef8cb20e88 GIT binary patch literal 18330 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%0DQuQ3iQ;RLF16m-oDoE%dZ?;_A_uL=1UEPh&@6!G z7$uSkTefJr^SbN&kWqCEM+It(i95?l%tSR+S7v5+EySaFi!dd9MlUYkYK%>?g(vL1Tsha-yIdtB{kq^!0o z5?9tp$C(gEX)RRF48TZi2XV}$G17>@Q8-r)M>90Sgv9VF2twi&02VOF$7d6F8(Sa^ zG_y2Wv}axsV@JGt`SSAq7@X*4$_vpj903?Ntknj@1OPbZ_08oqNhw;HO;i^$AGo%q zBv5Q3yvY*5p(11S5h5#*Wa3oA(bVwRtm3_Hm`WNVjYmtgRkMoI$%B>~hP6!E+EILR zxRte_$tCfmRJfF!p`(=+r3Ke%Wtxuu0@@_@)H?gPwRLfy2`t7(W@;`BO|P?KfR#66 zV74$RizW4|wQ>W|Rh7Lw>aAK|C_z^1m1^UC*F8-ag_K0*0Ybc`dootDrMsUM#-|Cj z)(nzkMKfQF?{F@fC}Baqi}$oOzzyZD$xRTNYv-FZpyfLddhHjgfOk}8w>sGO+U_k~ zQf)^!m}-fqUGmQ0021LlUoCLP#ob(qSZ)dC-_>6}039d@9bAMZQn(<;Ku#T#f=UWJlYgYNOz@|xTDD6$B_x^m3?fJBlfpLN@rPW(kGf9XWr_T-3 zDb_~4y}$tuH?0lwpr9`J=JQRQW~hwOS0-zE$tq+VZI|#cVSG6epSdd`e-6bB#nn~{ zYz~u16W$cE`Ox)Jp4;!wx{#e|d+$r4p z;yJU=0hUL6?_NKR&VR5{bYbM3l`6ZgKbj9LL-l`Tur1mmw*Fw;sVeEtiCw*;`?{b@z(_IS! zwl{dJP}wfzvGL9({kR`VP9hmvvsTT~3iWR}X>#GrMZcxn=9IQhRtbFZ-7qLig(+^o z$&V1xpVlb?ItC&KJm1gI{9L(l#+-`>*=Ba$J+R?Z{r^oHHzq1JcPl?sRxk>0V|w?) za${n{Mhnp&k9!XK&vt+Q|0ggE9^QTODby!n&D4j%Zd?aa-o4X_2<Q+T6LijAf0o8xkd~I_kd{`XB7X4oy92(A6Ixy#7g0VgmY{BvmQr4BvYuZ(qrupK z!+IarSrcw!;ob9$nSrB}iWbJg4;2`MJ0F`CSzmAIV~pd=Il*gc@#u)!k!L49Sr{fN zZ4zi;?lorIyxYj4A=n`JdN)J!&P~jno60M#V{XieoG|ym+#`$=&zg20xp2odbIF1= zxBsj=^7!rV35<6Ce*A9R`-4$JfgOZ_yKAd+^YhDqwQNpqSw;$wMgq()7#LVJ7#JBq z2mw$yKw&h#141P>789^dwzQ-uF&iiuQ<4p~lNm`N)HI;I2*3yyM6-v0y(+0GCB;A^ zWKwgAOQ1#pO#qq80(CQ!Y;HJNk-0qfMwNXaRJn!!+OGC*pgatlmA4g~=a9|D*d1V9XsQ-Q!NwKxUjVFu=gh6YujG8P~$?jQ;h zVlrxAU~IX`3KU@#<_LSrzy@S70;7UKp|}JbOkh<&LLMAEU|X0E0QoXNmn4HEfdFJP z2rEG8DL}a@AR7i0fEJkpm4pD@;09y^!wtJY5Kjc+xlk+v z)OQq!uLJq7fS47?z5%59fLIZTwIF6P0_XG@`+yjjWEp#b80cfhD4+v?c7d@-aY=GM z)IlJ(>Hx7+ae6@th*OlG0}5S`zZw7k2a+ - + diff --git a/html/js/display/screenManager.js b/html/js/display/screenManager.js index 3a32040..d3c4e3a 100644 --- a/html/js/display/screenManager.js +++ b/html/js/display/screenManager.js @@ -226,7 +226,7 @@ var ScreenManager = { this.drawCenteredText(y + 3, "Better luck next time."); }, - drawWaitingScreen: function() { + drawWaitingScreen: function(_internalLoopCall = false) { if (!DisplayManager.ensureDisplay()) return; DisplayManager.clearDisplay(); @@ -259,7 +259,8 @@ var ScreenManager = { this.showAnimationButton(); // Start animation loop if not already running - if (animationEnabled && typeof WaitingScreenAnimator !== 'undefined') { + // Only start the animator if this wasn't invoked from inside the animator loop + if (! _internalLoopCall && animationEnabled && typeof WaitingScreenAnimator !== 'undefined') { WaitingScreenAnimator.startAnimation(); } }, diff --git a/html/js/display/waitingScreenAnimator.js b/html/js/display/waitingScreenAnimator.js index 5884e27..8a2792d 100644 --- a/html/js/display/waitingScreenAnimator.js +++ b/html/js/display/waitingScreenAnimator.js @@ -2,6 +2,7 @@ var WaitingScreenAnimator = { _animationEnabled: true, _waitingAnimationInterval: null, + _animating: false, _waitingAnimation: { frame: 0, playerX: 8, @@ -354,11 +355,12 @@ var WaitingScreenAnimator = { break; case 'exit': - // Brief pause then reset - if (anim.frameCount > anim.frameCount + 20) { + if (anim.exitStartFrame === undefined) { + anim.exitStartFrame = anim.frameCount; + } + if (anim.frameCount - anim.exitStartFrame > 40) { // ~4s at 10fps this.resetAnimation(); } else { - // Monster exits if (anim.monsterX < 48) { anim.monsterX++; } @@ -432,22 +434,22 @@ var WaitingScreenAnimator = { }, startAnimation: function() { - // Clear any existing interval + if (this._animating) return; // already running + // Clear any existing interval defensively if (this._waitingAnimationInterval) { clearInterval(this._waitingAnimationInterval); } - - // Start new animation loop + this._animating = true; this._waitingAnimationInterval = setInterval(() => { if (this._animationEnabled && !GameState.isGameActive()) { - // Trigger a redraw in the main screen manager + // Draw only the animated frame WITHOUT restarting the loop (avoid recursion) if (typeof ScreenManager !== 'undefined' && ScreenManager.drawWaitingScreen) { - ScreenManager.drawWaitingScreen(); + ScreenManager.drawWaitingScreen(true); // pass flag to skip auto-start } } else { this.stopAnimation(); } - }, 100); // 10 FPS animation + }, 100); // 10 FPS }, stopAnimation: function() { @@ -455,6 +457,7 @@ var WaitingScreenAnimator = { clearInterval(this._waitingAnimationInterval); this._waitingAnimationInterval = null; } + this._animating = false; this.resetAnimation(); }, diff --git a/html/js/input/inputHandler.js b/html/js/input/inputHandler.js index 4a95711..9d9fe67 100644 --- a/html/js/input/inputHandler.js +++ b/html/js/input/inputHandler.js @@ -72,26 +72,27 @@ const InputHandler = { let dy = 0; let moved = false; + let direction = null; switch(e.key) { - case 'w': case 'ArrowUp': dy = -1; moved = true; break; - case 's': case 'ArrowDown': dy = 1; moved = true; break; - case 'a': case 'ArrowLeft': dx = -1; moved = true; break; - case 'd': case 'ArrowRight': dx = 1; moved = true; break; + case 'w': case 'ArrowUp': direction = 'up'; dy = -1; moved = true; break; + case 's': case 'ArrowDown': direction = 'down'; dy = 1; moved = true; break; + case 'a': case 'ArrowLeft': direction = 'left'; dx = -1; moved = true; break; + case 'd': case 'ArrowRight': direction = 'right'; dx = 1; moved = true; break; } - if (moved) { + if (moved && direction) { e.preventDefault(); // Prevent page scrolling // Implement movement throttling to prevent rapid-fire movement const now = Date.now(); if (now - self._lastMoveTime >= self._moveCooldown) { // Send move immediately if enough time has passed - socket.emit('player_move', { dx: dx, dy: dy }); + socket.emit('player_move', { direction }); self._lastMoveTime = now; self._pendingMove = null; } else { // Queue the move to be sent after cooldown - self._pendingMove = { dx: dx, dy: dy }; + self._pendingMove = { direction }; const timeToWait = self._moveCooldown - (now - self._lastMoveTime); setTimeout(() => { if (self._pendingMove) { @@ -149,15 +150,12 @@ const InputHandler = { setupClickHandlers: function() { // Add click handling for the HTML START button $('#startButton').click(function(e) { - - // Use the new auto_start event for immediate entry + // Attempt immediate start socket.emit('auto_start'); - + // We optimistically show waiting screen; if server later responds with payment requirement, audio handler will fire on payment_created if (typeof ScreenManager !== 'undefined' && ScreenManager.drawWaitingScreen) { ScreenManager.drawWaitingScreen(); } - - // Add visual feedback to chat $('#messages').append($('

  • ').text("šŸ–±ļø Game start requested...")); UI.scrollChat(); }); diff --git a/html/js/network/socketHandlers.js b/html/js/network/socketHandlers.js index 39df673..7fcf9eb 100644 --- a/html/js/network/socketHandlers.js +++ b/html/js/network/socketHandlers.js @@ -34,6 +34,8 @@ const SocketHandlers = { // Connection handlers socket.on('connect', this.onConnect); socket.on('welcome', this.onWelcome); + socket.on('session_token', this.onSessionToken); + socket.on('session_resumed', this.onSessionResumed); socket.on('message', this.onMessage); socket.on('status_update', this.onStatusUpdate); socket.on('chat_broadcast', this.onChatBroadcast); @@ -52,6 +54,7 @@ const SocketHandlers = { socket.on('payment_created', this.onPaymentCreated); socket.on('payment_confirmed', this.onPaymentConfirmed); socket.on('payment_detected', this.onPaymentDetected); + socket.on('credits_update', this.onCreditsUpdate); // Block height handler socket.on('blockheight', this.onBlockHeight); @@ -60,6 +63,15 @@ const SocketHandlers = { onConnect: function() { if (SocketHandlers._didConnect) return; // prevent duplicate registration emission SocketHandlers._didConnect = true; + // Include stored session token in a lightweight resume emit (if server did not get it via handshake) + try { + const existing = localStorage.getItem('wowngeon_token'); + if (existing) { + // If we later decide to rely solely on query param at io() creation, this is harmless redundancy. + socket.io.opts.query = socket.io.opts.query || {}; + socket.io.opts.query.resumeToken = existing; + } + } catch (e) {} socket.emit('register_client', { clientId: socket.id, userAgent: navigator.userAgent @@ -68,6 +80,49 @@ const SocketHandlers = { SocketHandlers._setBannerStatus('Connected', '#0f0'); }, + onSessionToken: function(data) { + if (data && data.token) { + try { localStorage.setItem('wowngeon_token', data.token); } catch(e) {} + $('#messages').append($('
  • ').text('New session established. Token stored.')); + UI.scrollChat(); + } + }, + + onSessionResumed: function(data) { + if (data && data.token) { + try { localStorage.setItem('wowngeon_token', data.token); } catch(e) {} + $('#messages').append($('
  • ').text('Session resumed.')); + UI.scrollChat(); + } + if (data && typeof data.credits === 'number') { + SocketHandlers._updateCreditsDisplay(data.credits); + } + if (data && data.payoutAddress) { + $('#messages').append($('
  • ').text('Payout address restored.')); + UI.scrollChat(); + } + }, + + onCreditsUpdate: function(data) { + if (!data) return; + if (typeof data.balance === 'number') { + SocketHandlers._updateCreditsDisplay(data.balance); + } + }, + + _updateCreditsDisplay: function(balance) { + let el = document.getElementById('creditsDisplay'); + if (!el) { + // Create a small unobtrusive badge in header if not present + const header = document.getElementById('header') || document.body; + el = document.createElement('div'); + el.id = 'creditsDisplay'; + el.style.cssText = 'position:absolute;top:4px;right:8px;font-size:12px;font-family:monospace;color:#0af;background:#111;padding:2px 6px;border:1px solid #044;border-radius:4px;'; + header.appendChild(el); + } + el.textContent = 'Credits: ' + balance; + }, + onWelcome: function(msg) { $('#messages').append($('
  • ').text("Connected to server!")); UI.scrollChat(); @@ -96,6 +151,7 @@ const SocketHandlers = { } else if (data.type === 'payment') { // Generic payment message (more granular handlers override later) SocketHandlers._setBannerStatus('Payment', '#0af'); + if (typeof AudioAlerts !== 'undefined') { AudioAlerts.playRequestCoin(); } } else if (data.type === 'error') { SocketHandlers._setBannerStatus('Error', '#f00'); } else if (data.type === 'success') { @@ -180,6 +236,9 @@ const SocketHandlers = { UI.scrollChat(); SocketHandlers._setBannerStatus('In Game', '#0f0'); + if (typeof AudioAlerts !== 'undefined' && AudioAlerts._enabled) { + try { AudioAlerts.playFile('game_start'); } catch(_) {} + } }, onGameUpdate: function(data) { @@ -243,6 +302,7 @@ const SocketHandlers = { onPaymentCreated: function(data) { console.log('Payment created:', data); + if (typeof AudioAlerts !== 'undefined') { AudioAlerts.playRequestCoin(); } const parts = []; parts.push('šŸ’³ Payment Required'); parts.push('Amount: ' + data.humanAmount + ' ' + data.cryptoType); @@ -349,6 +409,9 @@ const SocketHandlers = { )); UI.scrollChat(); SocketHandlers._setBannerStatus('Confirmed', '#0f0'); + if (typeof AudioAlerts !== 'undefined' && AudioAlerts._enabled) { + try { AudioAlerts.playFile('payment_confirmed'); } catch(_) {} + } }, _mempoolShownForPayment: new Set(), @@ -369,6 +432,10 @@ const SocketHandlers = { )); UI.scrollChat(); SocketHandlers._setBannerStatus('Mempool', '#0af'); + // Fallback audio trigger if AudioAlerts patched earlier failed to wrap or user enabled after patch + if (typeof AudioAlerts !== 'undefined' && AudioAlerts._enabled) { + try { AudioAlerts.playFile('payment_detected'); } catch(_) {} + } }, // Internal: periodic cleanup of client-side payment marker sets (invoked opportunistically) diff --git a/html/js/ui/audioAlerts.js b/html/js/ui/audioAlerts.js index 3ffe7fb..7edf073 100644 --- a/html/js/ui/audioAlerts.js +++ b/html/js/ui/audioAlerts.js @@ -12,6 +12,13 @@ const AudioAlerts = { _volume: 0.4, _lastPlay: 0, _cooldownMs: 1500, + _audioTags: {}, // cache HTMLAudioElements + _fileMap: { + request_coin: 'audio/Pleasesendcoin.m4a', + payment_detected: 'audio/Paymentdetected.m4a', + payment_confirmed: 'audio/Paymentconfirmed.m4a', + game_start: 'audio/Nowescapethedunge.m4a' + }, init() { if (this._initialized) return; @@ -41,15 +48,22 @@ const AudioAlerts = { if (SocketHandlers._audioAlertsPatched) return; // idempotent SocketHandlers._audioAlertsPatched = true; const origPaymentConfirmed = SocketHandlers.onPaymentConfirmed; + const origPaymentDetected = SocketHandlers.onPaymentDetected; const origGameStart = SocketHandlers.onGameStart; + const origQueueJoined = SocketHandlers.onQueueJoined; SocketHandlers.onPaymentConfirmed = function(data) { if (origPaymentConfirmed) origPaymentConfirmed.call(SocketHandlers, data); - AudioAlerts.playPattern('payment'); + AudioAlerts.playFile('payment_confirmed'); + }; + SocketHandlers.onPaymentDetected = function(data) { + if (origPaymentDetected) origPaymentDetected.call(SocketHandlers, data); + AudioAlerts.playFile('payment_detected'); }; SocketHandlers.onGameStart = function(data) { if (origGameStart) origGameStart.call(SocketHandlers, data); - AudioAlerts.playPattern('start'); + AudioAlerts.playFile('game_start'); }; + // We'll trigger request_coin when user attempts start but is blocked for payment/credits; handled externally via public method }, _lazyInitContext() { @@ -97,6 +111,29 @@ const AudioAlerts = { t += dur * 0.5; // slight overlap for chord-like effect }); } + , + _getAudio(tag) { + if (!this._enabled) return null; + if (!this._audioTags[tag]) { + const src = this._fileMap[tag]; + if (!src) return null; + const el = new Audio(src); + el.preload = 'auto'; + this._audioTags[tag] = el; + } + return this._audioTags[tag]; + }, + playFile(tag) { + if (!this._enabled) return; + const now = Date.now(); + if (now - this._lastPlay < this._cooldownMs) return; + this._lastPlay = now; + const el = this._getAudio(tag); + if (!el) return; + try { el.currentTime = 0; el.play().catch(()=>{}); } catch(_) {} + }, + // Public helper to be invoked when server denies start due to payment/credits + playRequestCoin() { this.playFile('request_coin'); } }; if (typeof module !== 'undefined' && module.exports) { diff --git a/src/.env.example b/src/.env.example index fb47546..bb92361 100644 --- a/src/.env.example +++ b/src/.env.example @@ -59,3 +59,8 @@ BLOCK_SOURCE=daemon # Payout Configuration PAYOUT_BATCH_INTERVAL=300000 MAX_PAYOUT_BATCH_SIZE=50 + +# Credits Mode Optional Payouts +# Set to true to enable payouts (same multipliers as single mode) when using PAID_CREDITS +# Default is false meaning credits mode is purely consumptive / no rewards. +CREDITS_PAYOUT_ENABLED=false diff --git a/src/game/gameModeManager.js b/src/game/gameModeManager.js index 54d92b3..0f5b306 100644 --- a/src/game/gameModeManager.js +++ b/src/game/gameModeManager.js @@ -21,14 +21,18 @@ class GameModeManager { return Math.trunc(num); }; - this.singleGamePrice = parseAtomic(process.env.SINGLE_GAME_PRICE, 5000000000); // default 0.005 XMR - this.creditsPackagePrice = parseAtomic(process.env.CREDITS_PACKAGE_PRICE, 50000000000); // default 0.05 XMR + this.singleGamePrice = parseAtomic(process.env.SINGLE_GAME_PRICE, 5000000000); // default 0.005 XMR + this.creditsPackagePrice = parseAtomic(process.env.CREDITS_PACKAGE_PRICE, 50000000000); // default 0.05 XMR + this.creditsPayoutEnabled = /^true$/i.test(process.env.CREDITS_PAYOUT_ENABLED || 'false'); console.log(`šŸŽ® Game Mode Manager initialized: ${this.gameMode} mode`); console.log(`šŸ’° Currency: ${this.cryptoType}`); const toXMR = (atomic) => (atomic / 1e12).toFixed(6).replace(/0+$/,'').replace(/\.$/,''); console.log(`šŸ’µ Single game price: ${this.singleGamePrice} atomic units (~${toXMR(this.singleGamePrice)} ${this.cryptoType})`); console.log(`šŸŽ« Credits package price: ${this.creditsPackagePrice} atomic units (~${toXMR(this.creditsPackagePrice)} ${this.cryptoType})`); + if (this.creditsPayoutEnabled) { + console.log('šŸŽ Credits payout mode ENABLED (will pay rewards in PAID_CREDITS).'); + } } /** @@ -103,16 +107,19 @@ class GameModeManager { case 'PAID_CREDITS': // Deduct one credit - await this.db.query(` + const updateRes = await this.db.query(` UPDATE users SET credits = credits - 1, total_games_played = total_games_played + 1, updated_at = NOW() WHERE id = $1 + RETURNING credits `, [user.id]); - - console.log(`šŸŽ« Deducted 1 credit from user ${user.id}, ${user.credits - 1} remaining`); - return { success: true, creditsRemaining: user.credits - 1 }; + const remainingCredits = updateRes.rows[0] ? updateRes.rows[0].credits : (user.credits - 1); + // Emit credits update asynchronously if an io ref was injected later (pattern: attach externally) + try { this.io && this.io.to(socketId).emit('credits_update', { balance: remainingCredits }); } catch(_) {} + console.log(`šŸŽ« Deducted 1 credit from user ${user.id}, ${remainingCredits} remaining`); + return { success: true, creditsRemaining: remainingCredits }; case 'PAID_SINGLE': // Link game to payment @@ -186,15 +193,12 @@ class GameModeManager { WHERE id = $4 `, [outcome === 'escaped' ? 'won' : 'lost', outcome, treasureFound, gameId]); - // Handle payouts for PAID_SINGLE mode - if (this.gameMode === 'PAID_SINGLE' && outcome === 'escaped' && game.payout_address) { + // Handle payouts for PAID_SINGLE or (optionally) PAID_CREDITS mode + const payoutEligibleMode = (this.gameMode === 'PAID_SINGLE') || (this.gameMode === 'PAID_CREDITS' && this.creditsPayoutEnabled); + if (payoutEligibleMode && outcome === 'escaped' && game.payout_address) { let payoutAmount; - - if (treasureFound) { - payoutAmount = this.singleGamePrice * 3; // 3x for treasure - } else { - payoutAmount = this.singleGamePrice * 2; // 2x for escape - } + const base = (this.gameMode === 'PAID_CREDITS') ? this.singleGamePrice : this.singleGamePrice; // could make distinct; reuse singleGamePrice as base + payoutAmount = treasureFound ? base * 3 : base * 2; // Create payout record await this.walletService.processPayout( @@ -221,7 +225,7 @@ class GameModeManager { success: true, outcome, treasureFound, - payoutCreated: this.gameMode === 'PAID_SINGLE' && outcome === 'escaped' + payoutCreated: payoutEligibleMode && outcome === 'escaped' }; } catch (error) { @@ -423,7 +427,8 @@ class GameModeManager { } // Handle payouts only in PAID_SINGLE mode and only on win (escaped) - if (this.gameMode === 'PAID_SINGLE' && won) { + const payoutEligibleStartMode = (this.gameMode === 'PAID_SINGLE') || (this.gameMode === 'PAID_CREDITS' && this.creditsPayoutEnabled); + if (payoutEligibleStartMode && won) { // Derive payout multiplier const multiplier = treasureFound ? 3 : 2; const payoutAmount = this.singleGamePrice * multiplier; diff --git a/src/index.js b/src/index.js index 8d91a6c..12141f4 100644 --- a/src/index.js +++ b/src/index.js @@ -28,6 +28,8 @@ const databaseManager = new DatabaseManager(); const rpcService = new RpcService(); const walletRPCService = new WalletRPCService(debugManager); const gameModeManager = new GameModeManager(databaseManager, walletRPCService, debugManager); +// Provide io reference so GameModeManager can emit events (e.g., credits_update) +gameModeManager.io = io; // Initialize remaining components const activeGames = new Map(); // Maps socketId to Game objects diff --git a/src/migrations/003_add_anon_token.sql b/src/migrations/003_add_anon_token.sql new file mode 100644 index 0000000..1bcc773 --- /dev/null +++ b/src/migrations/003_add_anon_token.sql @@ -0,0 +1,6 @@ +-- Migration 003: Add anonymous session token + last_seen +ALTER TABLE users + ADD COLUMN IF NOT EXISTS anon_token VARCHAR(64) UNIQUE, + ADD COLUMN IF NOT EXISTS last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP; + +CREATE INDEX IF NOT EXISTS idx_users_anon_token ON users(anon_token); diff --git a/src/network/addressManager.js b/src/network/addressManager.js new file mode 100644 index 0000000..373a65d --- /dev/null +++ b/src/network/addressManager.js @@ -0,0 +1,86 @@ +/** + * AddressManager + * Handles detection, pending confirmation, validation, and persistence of payout addresses. + * Keeps socketHandlers slimmer by encapsulating this concern. + */ +class AddressManager { + constructor({ gameModeManager, broadcastManager, io, debugManager, onConfirmed = null }) { + this.gameModeManager = gameModeManager; + this.broadcastManager = broadcastManager; + this.io = io; + this.debugManager = debugManager; + this.pending = new Map(); // socketId -> address awaiting confirmation + this.onConfirmed = onConfirmed; // optional callback(socketId, accepted, address) + } + + detectInText(text) { + if (!text || typeof text !== 'string') return null; + const re = /((?:4|8)[0-9A-Za-z]{90,110}|Wo[0-9A-Za-z]{88,110})/; // Monero / Wownero style + const m = text.match(re); + if (!m) return null; + if (m[1].length < 80) return null; + return m[1]; + } + + async handleDetection(socketId, address) { + try { + if (this.gameModeManager) { + const userRow = await this.gameModeManager.getOrCreateUser(socketId); + if (userRow && userRow.payout_address === address) { + this.io.to(socketId).emit('address_confirmed', { address, message: 'Payout address already set.' }); + return; + } + } + } catch (e) { + if (this.debugManager?.CONSOLE_LOGGING) console.warn('Address detection user lookup failed:', e.message); + } + this.pending.set(socketId, address); + const shortAddr = address.slice(0, 10) + '…' + address.slice(-6); + this.io.to(socketId).emit('address_detected', { address: shortAddr, fullAddress: address, message: `Detected payout address: ${shortAddr}\nType 'confirm' to save or 'cancel' to discard.` }); + this.broadcastManager?.sendStatusUpdate(socketId, 'info', 'Address detected. Type confirm or cancel.'); + } + + async confirm(socketId, accept) { + const pending = this.pending.get(socketId); + if (!pending) { + this.broadcastManager?.sendStatusUpdate(socketId, 'warning', 'No address pending. Paste an address first.'); + return; + } + if (!accept) { + this.pending.delete(socketId); + this.broadcastManager?.sendStatusUpdate(socketId, 'info', 'Address entry cancelled.'); + this.io.to(socketId).emit('address_confirmed', { cancelled: true, message: 'Address entry cancelled.' }); + if (this.onConfirmed) { + try { this.onConfirmed(socketId, false, pending); } catch(_) {} + } + return; + } + if (this.gameModeManager) { + try { + await this.gameModeManager.setUserPayoutAddress(socketId, pending); + this.broadcastManager?.sendStatusUpdate(socketId, 'success', 'Payout address saved.'); + this.io.to(socketId).emit('address_confirmed', { address: pending, message: 'Payout address saved.' }); + } catch (e) { + this.broadcastManager?.sendStatusUpdate(socketId, 'error', 'Failed to save address. Try again.'); + return; + } finally { + this.pending.delete(socketId); + } + } else { + this.pending.delete(socketId); + this.broadcastManager?.sendStatusUpdate(socketId, 'success', 'Payout address accepted (session only).'); + this.io.to(socketId).emit('address_confirmed', { address: pending, message: 'Payout address accepted (session only).' }); + } + if (this.onConfirmed) { + try { this.onConfirmed(socketId, true, pending); } catch(_) {} + } + } + + requiresPayoutAddress() { + if (!this.gameModeManager) return false; // free mode => no requirement + const mode = this.gameModeManager.gameMode; + return (mode === 'PAID_SINGLE') || (mode === 'PAID_CREDITS' && this.gameModeManager.creditsPayoutEnabled); + } +} + +module.exports = AddressManager; \ No newline at end of file diff --git a/src/network/paymentHandlers.js b/src/network/paymentHandlers.js index a9d102f..631769c 100644 --- a/src/network/paymentHandlers.js +++ b/src/network/paymentHandlers.js @@ -62,6 +62,21 @@ class PaymentHandlers { async createAndShowPaymentRequest(socket) { if (!this.gameModeManager) return; try { + // Ensure payout address exists for modes that may payout (PAID_SINGLE or PAID_CREDITS with payouts) + const mode = this.gameModeManager.gameMode; + const needsAddress = (mode === 'PAID_SINGLE') || (mode === 'PAID_CREDITS' && this.gameModeManager.creditsPayoutEnabled); + if (needsAddress) { + try { + const userRow = await this.gameModeManager.getOrCreateUser(socket.id); + if (!userRow.payout_address) { + this.broadcastManager.sendStatusUpdate(socket.id, 'payment', 'šŸ’³ Before paying, paste your payout address (XMR/WOW) then type confirm.'); + // Do not proceed until address is set + return; + } + } catch(e) { + console.error('Address pre-check failed:', e.message); + } + } const currentUser = socket.id && this.queueManager.getUserBySocket ? this.queueManager.getUserBySocket(socket.id) : null; // caller (SocketHandlers) will handle user existence; keep method generic const gameMode = this.gameModeManager.gameMode; @@ -145,10 +160,15 @@ class PaymentHandlers { } else if (paymentStatus.confirmed) { this.broadcastManager.sendStatusUpdate(socketId, 'success', `šŸ’° PAYMENT CONFIRMED IN BLOCK\n\nāœ… Added to queue.\nšŸ•’ Starts at block ${nextBlock}.\nšŸ“¦ Current block: ${currentBlock}`); } - if (!this.confirmedPayments.has(paymentRequest.id)) { - this.confirmedPayments.add(paymentRequest.id); - this.io.to(socketId).emit('payment_confirmed', { paymentId: paymentRequest.id, status: paymentStatus, nextBlock, currentBlock }); - this._confirmedTimestamps.set(paymentRequest.id, Date.now()); + const newBalRes = await this.db.query('SELECT credits FROM users WHERE id = $1', [currentUser.id]); + const remaining = newBalRes.rows[0] ? newBalRes.rows[0].credits : currentUser.credits; + if (this.io) { + this.io.to(socketId).emit('credits_update', { balance: remaining }); + } + if (!this.confirmedPayments.has(paymentRequest.id)) { + this.confirmedPayments.add(paymentRequest.id); + this.io.to(socketId).emit('payment_confirmed', { paymentId: paymentRequest.id, status: paymentStatus, nextBlock, currentBlock }); + this._confirmedTimestamps.set(paymentRequest.id, Date.now()); } } diff --git a/src/network/sessionManager.js b/src/network/sessionManager.js new file mode 100644 index 0000000..d681079 --- /dev/null +++ b/src/network/sessionManager.js @@ -0,0 +1,50 @@ +/** + * SessionManager + * Issues and resumes anonymous session tokens to persist credits & payout address across reconnects. + */ +const { randomUUID } = require('crypto'); + +class SessionManager { + constructor({ db, debugManager, gameModeManager }) { + this.db = db; + this.debugManager = debugManager; + this.gameModeManager = gameModeManager; + } + + async resumeOrCreate({ socketId, ipAddress, resumeToken }) { + try { + if (resumeToken) { + const existing = await this.db.query(`SELECT * FROM users WHERE anon_token = $1 LIMIT 1`, [resumeToken]); + if (existing.rows.length) { + const row = existing.rows[0]; + await this.db.query(`UPDATE users SET socket_id = $1, last_seen = NOW() WHERE id = $2`, [socketId, row.id]); + if (this.debugManager.CONSOLE_LOGGING) console.log(`[SessionManager] Resumed session ${row.id} via token.`); + return { resumed: true, token: resumeToken, user: { ...row, socket_id: socketId } }; + } + } + // Create + const token = randomUUID(); + const inserted = await this.db.query(`INSERT INTO users (socket_id, ip_address, anon_token) VALUES ($1,$2,$3) RETURNING *`, [socketId, ipAddress, token]); + const row = inserted.rows[0]; + if (this.debugManager.CONSOLE_LOGGING) console.log(`[SessionManager] Created new anonymous user ${row.id}`); + return { resumed: false, token, user: row }; + } catch (e) { + console.error('SessionManager resumeOrCreate error:', e.message); + throw e; + } + } + + async getBySocket(socketId) { + const res = await this.db.query(`SELECT * FROM users WHERE socket_id = $1 LIMIT 1`, [socketId]); + return res.rows[0] || null; + } + + async rotateToken(socketId) { + const token = randomUUID(); + const res = await this.db.query(`UPDATE users SET anon_token = $1, last_seen = NOW() WHERE socket_id = $2 RETURNING anon_token`, [token, socketId]); + if (res.rows.length) return res.rows[0].anon_token; + return null; + } +} + +module.exports = SessionManager; diff --git a/src/network/socketHandlers.js b/src/network/socketHandlers.js index f62b750..7dccdf6 100644 --- a/src/network/socketHandlers.js +++ b/src/network/socketHandlers.js @@ -8,6 +8,8 @@ const Game = require('../game/game'); const MovementManager = require('../game/movementManager'); const QueueManager = require('./queueManager'); const PaymentHandlers = require('./paymentHandlers'); +const AddressManager = require('./addressManager'); +const SessionManager = require('./sessionManager'); class SocketHandlers { constructor(io, activeGames, broadcastManager, debugManager, gameModeManager = null, walletService = null) { @@ -48,6 +50,31 @@ class SocketHandlers { moveCooldown: this.MOVE_COOLDOWN, postMoveHook: ({ socketId, game }) => this.afterPlayerMove(socketId, game) }); + // Address / payout handling encapsulated in AddressManager + this.addressManager = new AddressManager({ + gameModeManager: this.gameModeManager, + broadcastManager: this.broadcastManager, + io: this.io, + debugManager: this.debugManager, + onConfirmed: (socketId, accepted) => { + if (accepted && this._awaitingAddressFor && this._awaitingAddressFor.has(socketId)) { + // Automatically proceed to payment creation after address confirmation + const sock = this._getLiveSocket(socketId); + if (sock) { + this.paymentHandlers.createAndShowPaymentRequest(sock); + } + this._awaitingAddressFor.delete(socketId); + } + } + }); + // Anonymous session management + if (this.gameModeManager && this.gameModeManager.db) { + this.sessionManager = new SessionManager({ + db: this.gameModeManager.db, + debugManager: this.debugManager, + gameModeManager: this.gameModeManager + }); + } } /** @@ -137,25 +164,57 @@ class SocketHandlers { /** * Initialize socket event handlers for a new connection */ - handleConnection(socket) { + async handleConnection(socket) { if (this.debugManager.CONSOLE_LOGGING) { console.log('A user connected'); console.log(socket.client.id); console.log(socket.handshake.address); } - // Create and register user - new user.User(socket.id, socket.handshake.address); - const newUser = this.getUserBySocket(socket.id); - if (newUser) { - newUser.clientId = socket.client.id; - if (this.debugManager.CONSOLE_LOGGING) { - console.log(`User created with both socket.id (${socket.id}) and socket.client.id (${socket.client.id})`); + // Session resume / create (DB authoritative) + ephemeral in-memory user object for legacy game logic + let resumeToken = null; + try { resumeToken = socket.handshake.query?.resumeToken || null; } catch(_) {} + let sessionInfo = null; + if (this.sessionManager) { + try { + sessionInfo = await this.sessionManager.resumeOrCreate({ + socketId: socket.id, + ipAddress: socket.handshake.address, + resumeToken + }); + } catch(e) { + console.error('Session initialization failed:', e.message); } } + + // Always create an in-memory user (legacy systems rely on it) but map to socket.id + new user.User(socket.id, socket.handshake.address); + const memUser = this.getUserBySocket(socket.id); + if (memUser) memUser.clientId = socket.client.id; - // Send welcome and current status - this.io.to(socket.client.id).emit('welcome', socket.client.id); + // Emit session / welcome events + if (sessionInfo) { + if (sessionInfo.resumed) { + this.io.to(socket.id).emit('session_resumed', { + token: sessionInfo.token, + payoutAddress: sessionInfo.user.payout_address || null, + credits: sessionInfo.user.credits || 0 + }); + } else { + this.io.to(socket.id).emit('session_token', { token: sessionInfo.token }); + } + } else { + // fallback legacy welcome + this.io.to(socket.client.id).emit('welcome', socket.client.id); + } + // If payout address already set, echo confirmation for UI convenience + if (sessionInfo && sessionInfo.user.payout_address) { + this.io.to(socket.id).emit('address_confirmed', { address: sessionInfo.user.payout_address, message: 'Payout address restored.' }); + } + // Credits convenience push + if (sessionInfo) { + this.io.to(socket.id).emit('credits_update', { balance: sessionInfo.user.credits || 0 }); + } // Send current block height const currentBlock = this.debugManager.getCurrentBlockHeight(); @@ -181,6 +240,97 @@ class SocketHandlers { socket.on('get_user_credits', () => this.handleGetUserCredits(socket)); } + /** + * Handle immediate start button (auto_start) + * Applies payment eligibility + payout address gating (for payout-eligible modes) + * If eligible, starts game immediately (bypassing block queue) and processes game start (credit deduction / payment linkage) + */ + async handleAutoStart(socket) { + try { + const existingGame = this.activeGames.get(socket.id); + if (existingGame) { + this.broadcastManager.sendStatusUpdate(socket.id, 'info', 'You are already in a game!'); + return; + } + + const memUser = this.getUserBySocket(socket.id); + if (!memUser) { + this.broadcastManager.sendStatusUpdate(socket.id, 'error', 'Could not start game (user not found). Please reconnect.'); + return; + } + + let canStart = { allowed: true, reason: 'Free mode' }; + if (this.gameModeManager) { + // Payout address gating for modes that can payout + const payoutEligible = (this.gameModeManager.gameMode === 'PAID_SINGLE') || (this.gameModeManager.gameMode === 'PAID_CREDITS' && this.gameModeManager.creditsPayoutEnabled); + if (payoutEligible) { + try { + const dbUser = await this.gameModeManager.getOrCreateUser(socket.id); + if (!dbUser.payout_address) { + this.broadcastManager.sendStatusUpdate(socket.id, 'payment', 'āš ļø Paste your payout address first, then type confirm. Payment request will appear automatically.'); + if (!this._awaitingAddressFor) this._awaitingAddressFor = new Set(); + this._awaitingAddressFor.add(socket.id); + return; + } + } catch (e) { + console.error('Payout address pre-check failed:', e.message); + this.broadcastManager.sendStatusUpdate(socket.id, 'error', 'Address check failed, try again.'); + return; + } + } + + try { + canStart = await this.gameModeManager.canUserStartGame(socket.id); + } catch (e) { + console.error('Eligibility check failed:', e.message); + this.broadcastManager.sendStatusUpdate(socket.id, 'error', 'Eligibility check failed.'); + return; + } + + if (!canStart.allowed) { + // Trigger payment request automatically + if (canStart.action === 'make_payment' || canStart.action === 'purchase_credits') { + await this.paymentHandlers.createAndShowPaymentRequest(socket); + return; + } + this.broadcastManager.sendStatusUpdate(socket.id, 'error', canStart.reason || 'Not allowed to start'); + return; + } + } + + // Create game immediately + const blockHeight = this.debugManager.getCurrentBlockHeight ? this.debugManager.getCurrentBlockHeight() : null; + memUser.blockRec = blockHeight; // keep legacy timeout logic consistent + const game = this.createGameForUser(memUser, 'standard'); + const state = game.getState(); + state.blockHeight = blockHeight; + + // Process start (credits deduction / payment link) + if (this.gameModeManager) { + const startRes = await this.gameModeManager.processGameStart(socket.id, game.id); + if (!startRes.success) { + // Abort game + this.activeGames.delete(socket.id); + this.broadcastManager.sendStatusUpdate(socket.id, 'error', startRes.reason || 'Failed to start game.'); + return; + } + } + + this.io.to(socket.id).emit('game_start', state); + this.broadcastManager.sendStatusUpdate(socket.id, 'success', 'Game started! Escape before the next block!'); + } catch (err) { + console.error('handleAutoStart error:', err); + this.broadcastManager.sendStatusUpdate(socket.id, 'error', 'Unexpected error starting game.'); + } + } + + /** Return the active socket instance by id (io.sockets.sockets Map) */ + _getLiveSocket(socketId) { + try { + return this.io.sockets.sockets.get(socketId) || null; + } catch(_) { return null; } + } + /** * Handle chat messages and game commands */ @@ -191,10 +341,10 @@ class SocketHandlers { const command = msg.toLowerCase(); - // Check for XMR/WOW address in the message - const xmrAddressMatch = this.detectXMRAddress(msg); - if (xmrAddressMatch) { - this.handleAddressDetection(socket, xmrAddressMatch); + // Check for XMR/WOW payout address in the message using AddressManager + const detected = this.addressManager.detectInText(msg); + if (detected) { + this.addressManager.handleDetection(socket.id, detected); return; } @@ -211,17 +361,19 @@ class SocketHandlers { return; case 'cancel': - // Check if this is for address confirmation - if (this.pendingAddresses && this.pendingAddresses.has(socket.id)) { - this.handleAddressConfirmation(socket, false); + if (this.addressManager.pending.has(socket.id)) { + this.addressManager.confirm(socket.id, false); return; } - // Otherwise handle queue cancellation - this.handleCancelEntry(socket); + this.handleCancelEntry(socket); // queue cancellation path return; - + case 'confirm': - this.handleAddressConfirmation(socket, true); + if (this.addressManager.pending.has(socket.id)) { + this.addressManager.confirm(socket.id, true); + return; + } + this.broadcastManager.sendStatusUpdate(socket.id, 'info', 'Nothing pending confirmation.'); return; case 'address': @@ -246,6 +398,7 @@ class SocketHandlers { } } + /** * Broadcast a generic chat message to all clients. Performs minimal sanitization * and rate limiting (simple cooldown per socket) to avoid spam / abuse. @@ -334,6 +487,15 @@ class SocketHandlers { if (this.gameModeManager) { try { const eligibility = await this.gameModeManager.canUserStartGame(socket.id); + // If in payout-eligible mode and user missing payout address, require address first + const payoutEligible = (this.gameModeManager.gameMode === 'PAID_SINGLE') || (this.gameModeManager.gameMode === 'PAID_CREDITS' && this.gameModeManager.creditsPayoutEnabled); + if (payoutEligible) { + const u = await this.gameModeManager.getOrCreateUser(socket.id); + if (!u.payout_address) { + this.broadcastManager.sendStatusUpdate(socket.id, 'payment', 'āš ļø Paste your payout address first, then type confirm.'); + return; + } + } if (!eligibility.allowed) { if (this.debugManager.CONSOLE_LOGGING) { @@ -443,7 +605,10 @@ class SocketHandlers { } // Clean up payment monitoring - this.stopPaymentMonitoring(socket.id); + // Payment monitoring cleanup (refactored into paymentHandlers) + if (this.paymentHandlers && typeof this.paymentHandlers.stopMonitoringForSocket === 'function') { + this.paymentHandlers.stopMonitoringForSocket(socket.id); + } // Clean up movement timestamps this.playerMoveTimestamps.delete(socket.id);