`;
},
render: function() {
if (!this.config) return;
// Display label (sXMR on stagenet) — distinct from cryptoType, which formatPrice
// still uses for decimals.
const currency = this.config.currencyLabel || this.config.cryptoType || 'WOW';
const hasCredits = this.userCredits >= (this.config.creditsPerGame || 1);
const creditPackages = this.config.creditPackages || [];
const cosmeticProducts = this.config.cosmeticProducts || [];
const $container = $('.game-modes');
$container.empty();
// Reset selected package
this.selectedPackageId = null;
// Explain the two leaderboards up front when both paid and free are available.
if (this.config.paymentsEnabled && this.config.freePlayEnabled) {
$container.append(
'
' +
'🏅 Pay (credits or single entry) to put your high scores in the Hall of Champions. ' +
'🪙 Play free — high scores go to the Pleb leaderboard instead.' +
'
'
);
}
// === SECTION 1: Use existing credits (if available) ===
if (hasCredits && this.config.creditsModeEnabled) {
const creditsPayoutsEnabled = this.config.creditsPayoutsEnabled;
const creditsMultipliers = this.config.payoutMultipliers?.credits || { escape: 2, escapeWithTreasure: 3 };
const creditsBaseValue = this.config.creditsPayoutBaseValue || this.config.singleGamePrice;
let payoutNote = '';
if (creditsPayoutsEnabled) {
const baseFormatted = this.formatPrice(creditsBaseValue);
const escapeWin = (parseFloat(baseFormatted) * creditsMultipliers.escape).toFixed(2);
const treasureWin = (parseFloat(baseFormatted) * creditsMultipliers.escapeWithTreasure).toFixed(2);
payoutNote = `💰 Win: ${creditsMultipliers.escape}x (${escapeWin} ${currency}) • Treasure: ${creditsMultipliers.escapeWithTreasure}x (${treasureWin} ${currency}) 🏅 High scores enter the Hall of Champions`;
} else {
payoutNote = '🏅 High scores enter the Hall of Champions';
}
$container.append(`
🎮 You have ${this.userCredits} credits
${payoutNote}
`);
}
// === SECTION 2: Direct payment (pay per game) ===
if (this.config.directModeEnabled) {
const price = this.config.singleGamePriceFormatted || this.formatPrice(this.config.singleGamePrice);
const multipliers = this.config.payoutMultipliers?.direct || { escape: 2, escapeWithTreasure: 3 };
const directPayoutsEnabled = this.config.directPayoutsEnabled !== false;
let payoutInfo = '';
if (directPayoutsEnabled) {
const escapeWin = (parseFloat(price) * multipliers.escape).toFixed(2);
const treasureWin = (parseFloat(price) * multipliers.escapeWithTreasure).toFixed(2);
payoutInfo = `Win: ${multipliers.escape}x (${escapeWin} ${currency}) • Treasure: ${multipliers.escapeWithTreasure}x (${treasureWin} ${currency}) 🏅 High scores enter the Hall of Champions`;
} else {
payoutInfo = '🏅 High scores enter the Hall of Champions';
}
$container.append(`
`);
}
// === Credits balance display (if has some but not enough) ===
if (this.config.creditsModeEnabled && this.userCredits > 0 && !hasCredits) {
$container.prepend(`
⚠️ You have ${this.userCredits} credits (need ${this.config.creditsPerGame || 1} to play)
`);
}
// === FREE play === shown when the instance is free-only OR offers free as a choice
// alongside paid (freePlayEnabled). Free games go to the Pleb leaderboard.
if (this.config.freePlayEnabled) {
const note = this.config.paymentsEnabled
? '🪙 Free — high scores go to the Pleb leaderboard'
: 'No payments required';
$container.append(`
`);
}
},
handleModeSelection: function(mode, action, packageId) {
console.log("Mode selected:", mode, "Action:", action, "Package:", packageId);
if (mode === 'FREE') {
$('#payment-ui').hide();
if (window.socket) {
// play_free records the game as FREE (Pleb leaderboard, no payout) even on
// an instance that also sells credits/entry.
window.socket.emit('play_free');
}
return;
}
// If using existing credits, just start the game
if (action === 'use_credit' && this.userCredits >= 1) {
$('#payment-ui').hide();
if (window.socket) {
window.socket.emit('enter_game');
}
return;
}
// Request payment with type and optional packageId
let paymentType = 'single_game';
if (action === 'buy_credits') {
paymentType = 'credits_package';
} else if (action === 'buy_product') {
paymentType = 'cosmetic_pack';
}
const requestData = { type: paymentType };
if (packageId) {
if (paymentType === 'cosmetic_pack') {
requestData.productId = packageId;
} else {
requestData.packageId = packageId;
}
}
if (window.socket) {
window.socket.emit('request_payment', requestData);
// Show loading state
$('.game-modes').hide();
$('#payment-details').show();
$('#payment-status').text('Requesting payment address...');
$('#payment-amount').text('...');
$('#payment-address').val('Loading...');
}
},
showPaymentRequest: function(data) {
$('#payment-ui').show();
$('#payment-details').show();
$('.game-modes').hide();
const isCreditsPurchase = data.paymentType === 'credits_package';
const isProductPurchase = data.paymentType === 'cosmetic_pack';
let headerText = '💳 Payment Required';
if (isCreditsPurchase && data.package) {
const bonus = data.package.bonus > 0 ? ` +${data.package.bonus} bonus` : '';
headerText = `🎫 Buy ${data.package.credits}${bonus} Credits`;
} else if (isProductPurchase && data.package) {
headerText = `Buy ${data.package.label || data.package.id || 'Pack'}`;
}
$('.payment-header strong').text(headerText);
const displayAmount = data.humanAmount || data.amountFormatted || data.amount;
$('#payment-amount').text(displayAmount + ' ' + (data.currency || ''));
$('#payment-address').val(data.address);
$('#check-payment').data('payment-id', data.paymentId || data.id);
if (data.reused) {
$('#payment-status').html('♻️ Using existing pending payment');
} else {
const statusText = isCreditsPurchase
? 'Send payment to receive credits...'
: (isProductPurchase ? 'Send payment to unlock this pack...' : 'Waiting for payment...');
$('#payment-status').text(statusText);
}
},
show: function() {
this.render(); // Ensure options are up-to-date with current config
$('#payment-ui').show();
$('.game-modes').show();
$('#payment-details').hide();
$('.payment-header strong').text('🎮 Choose How to Play');
}
};
// Expose globally
window.PaymentUI = PaymentUI;
// Initialize when DOM is ready
$(document).ready(function() {
PaymentUI.init();
});