add early entry and help screens for new users

This commit is contained in:
jwinterm
2025-12-26 10:21:25 -05:00
parent f1c86a084b
commit aec1c71a8c
12 changed files with 743 additions and 3 deletions
+53 -1
View File
@@ -71,6 +71,7 @@
<script src="js/ui/audioAlerts.js"></script>
<script src="js/ui/addressModal.js"></script>
<script src="js/ui/transactionHistory.js"></script>
<script src="js/ui/helpModal.js"></script>
<script src="js/network/socketHandlers.js"></script>
<script src="js/input/inputHandler.js"></script>
</head>
@@ -103,7 +104,8 @@
<label style="cursor:pointer; display:flex; align-items:center; gap:4px;">
<input type="checkbox" id="audioAlertsToggle" style="cursor:pointer;"> Audio Alerts
</label>
<button id="historyButton" style="margin-left:auto; background:#1a1a4d; color:#c4b5fd; border:1px solid #8b5cf6; padding:4px 10px; font-size:11px; cursor:pointer; border-radius:3px;" title="View your payment and payout history">📜 History</button>
<button id="helpButton" style="margin-left:auto; background:#2d2d00; color:#ffd700; border:1px solid #ffd700; padding:4px 10px; font-size:11px; cursor:pointer; border-radius:3px;" title="How to play and payment information">❓ Help</button>
<button id="historyButton" style="background:#1a1a4d; color:#c4b5fd; border:1px solid #8b5cf6; padding:4px 10px; font-size:11px; cursor:pointer; border-radius:3px;" title="View your payment and payout history">📜 History</button>
<button id="manageAddressButton" style="background:#053655; color:#0ff; border:1px solid #0ff; padding:4px 10px; font-size:11px; cursor:pointer; border-radius:3px;" title="Your payout address and credits are tied to a browser session token. Clearing cookies/localStorage or using incognito mode will lose your session.">⚙️ Manage Payout Address</button>
<span id="sessionWarning" style="display:none; color:#f59e0b; font-size:10px; margin-left:6px;" title="Private browsing or localStorage blocked - your session won't persist">⚠️ Session not saved</span>
</div>
@@ -233,6 +235,56 @@
</div>
</div>
<!-- Help/Instructions Modal -->
<div id="helpModal" class="modal-overlay hidden" role="dialog" aria-modal="true" aria-labelledby="helpModalTitle">
<div class="modal-content" style="max-width: 600px; max-height: 80vh; overflow-y: auto;">
<button type="button" class="modal-close" id="helpModalClose" aria-label="Close">✕</button>
<h2 id="helpModalTitle" style="color:#ffd700; margin-bottom:15px;">❓ How to Play</h2>
<div style="margin-bottom:20px;">
<h3 style="color:#4ade80; font-size:1em; margin-bottom:8px;">🎮 Game Objective</h3>
<p style="color:#ccc; font-size:0.9em; line-height:1.4;">Navigate through the dungeon, collect the treasure bag, and escape through the exit before the monster catches you or time runs out!</p>
</div>
<div style="margin-bottom:20px;">
<h3 style="color:#4ade80; font-size:1em; margin-bottom:8px;">⌨️ Controls</h3>
<ul style="color:#ccc; font-size:0.9em; line-height:1.6; padding-left:20px;">
<li><strong>WASD</strong> or <strong>Arrow Keys</strong> - Move your character</li>
<li><strong>Enter</strong> - Join the queue to play</li>
</ul>
</div>
<div style="margin-bottom:20px;">
<h3 style="color:#4ade80; font-size:1em; margin-bottom:8px;">🕐 Block Timing</h3>
<p style="color:#ccc; font-size:0.9em; line-height:1.4;">Games are synchronized to blockchain blocks. <strong>You may need to wait a few minutes</strong> for the next block before your game starts. The block height is shown at the top of the screen.</p>
</div>
<div id="helpPaymentModes" style="margin-bottom:20px;">
<h3 style="color:#4ade80; font-size:1em; margin-bottom:8px;">💰 Payment Modes</h3>
<div id="helpModesList" style="color:#ccc; font-size:0.9em; line-height:1.4;">
<!-- Dynamically populated based on active modes -->
</div>
</div>
<div style="margin-bottom:20px;">
<h3 style="color:#4ade80; font-size:1em; margin-bottom:8px;">🎁 Payouts</h3>
<p style="color:#ccc; font-size:0.9em; line-height:1.4;">Set your payout address using the "Manage Payout Address" button. When you escape the dungeon, your winnings will be sent automatically. Escaping with the treasure bag earns a higher multiplier!</p>
</div>
<div style="margin-bottom:15px;">
<h3 style="color:#4ade80; font-size:1em; margin-bottom:8px;">📜 Legend</h3>
<div style="display:grid; grid-template-columns: 30px 1fr; gap:8px; color:#ccc; font-size:0.9em;">
<span style="color:#60a5fa;">@</span><span>You (the player)</span>
<span style="color:#4ade80;">></span><span>Exit - escape here to win</span>
<span style="color:#ef4444;">~</span><span>Monster - avoid at all costs</span>
<span style="color:#fbbf24;">$</span><span>Treasure - collect for bonus payout</span>
</div>
</div>
<button id="helpModalCloseBtn" style="width:100%; padding:10px; background:#4c1d95; color:#fff; border:none; cursor:pointer; border-radius:4px; font-weight:bold;">Got it!</button>
</div>
</div>
<!-- Browser info panel -->
<div id="browser-info" style="position: fixed; right: 10px; top: 40px; background: rgba(0,0,0,0.7); color: #0f0; padding: 5px; font-family: monospace; font-size: 12px; z-index: 1000; display: none;">
<div>Browser: <span id="browser-name">checking...</span></div>
+6
View File
@@ -155,6 +155,12 @@ window.GameTiles = {
},
getTreasureTile: function() {
// Dynamically return treasure tile based on crypto type
// Check ScreenManager for the current crypto type
if (typeof ScreenManager !== 'undefined' && ScreenManager._cryptoType) {
return ScreenManager._cryptoType === 'XMR' ? '$M' : '$W';
}
// Fallback to static config
return options.gameConfig.treasureType;
},
+5 -1
View File
@@ -100,11 +100,15 @@ var ScreenManager = {
// Draw legend with special tile handling
const display = DisplayManager.getDisplay();
// Use dynamic treasure tile based on crypto type
const treasureTile = (typeof GameTiles !== 'undefined' && GameTiles.getTreasureTile)
? GameTiles.getTreasureTile()
: (this._cryptoType === 'XMR' ? '$M' : '$W');
const legendItems = [
{ tile: "@2", text: "This is you" },
{ tile: ">", text: " Escape the dungeon" },
{ tile: "~", text: " Avoid the monster" },
{ tile: "$M", text: "Secure the bag" }
{ tile: treasureTile, text: "Secure the bag" }
];
const centerX = Math.floor(this._screenWidth / 2) - 10;
+188
View File
@@ -10,6 +10,29 @@ const SocketHandlers = {
el.text(text);
if (color) el.css('color', color);
},
_updateAddressButtonStatus: function(hasAddress) {
const $btn = $('#manageAddressButton');
if (!$btn.length) return;
if (hasAddress) {
// Address is set - show green indicator
$btn.css({
'background': '#053655',
'color': '#0ff',
'border-color': '#0ff'
});
$btn.html('✅ Payout Address Set');
} else {
// No address - show warning indicator (yellow/orange)
$btn.css({
'background': '#553300',
'color': '#ffa500',
'border-color': '#ffa500'
});
$btn.html('⚠️ Set Payout Address');
}
},
init: function() {
if (this._initialized) {
@@ -99,6 +122,10 @@ const SocketHandlers = {
// Block height handler
socket.on('blockheight', this.onBlockHeight);
// Early entry handlers
socket.on('early_entry_success', this.onEarlyEntrySuccess);
socket.on('early_entry_error', this.onEarlyEntryError);
// Spectator handlers
socket.on('active_games', this.onActiveGames);
socket.on('spectate_start', this.onSpectateStart);
@@ -131,6 +158,8 @@ const SocketHandlers = {
try { localStorage.setItem('wownerogue_token', data.token); } catch(e) {}
$('#messages').append($('<li class="status">').text('New session established. Token stored.'));
UI.scrollChat();
// New session = no address set yet
SocketHandlers._updateAddressButtonStatus(false);
}
},
@@ -149,6 +178,9 @@ const SocketHandlers = {
if (typeof AddressModal !== 'undefined') {
AddressModal.setCurrentAddress(data.payoutAddress);
}
SocketHandlers._updateAddressButtonStatus(true);
} else {
SocketHandlers._updateAddressButtonStatus(false);
}
},
@@ -273,6 +305,11 @@ const SocketHandlers = {
onQueueJoined: function(data) {
console.log('Queue joined:', data);
// Mark as queued and update early entry button
SocketHandlers._isQueued = true;
SocketHandlers._updateEarlyEntryButton();
const currentBlockFallback = (typeof data.currentBlock === 'number' && data.currentBlock > 0)
? data.currentBlock
: (typeof UI !== 'undefined' && typeof UI._currentBlockHeight === 'number' && UI._currentBlockHeight > 0
@@ -295,6 +332,10 @@ const SocketHandlers = {
},
onGameStart: function(data) {
// Clear queued state
SocketHandlers._isQueued = false;
SocketHandlers._updateEarlyEntryButton();
$('#messages').append($('<li class="game-start">').text("Starting game..."));
// Display provably fair commitment if present
@@ -403,6 +444,10 @@ const SocketHandlers = {
},
onQueueCancelled: function(data) {
// Clear queued state
SocketHandlers._isQueued = false;
SocketHandlers._updateEarlyEntryButton();
$('#messages').append($('<li style="color: #ff0;">').text("Queue entry cancelled."));
if (typeof Game !== 'undefined' && Game._drawWelcomeScreen) {
@@ -438,6 +483,10 @@ const SocketHandlers = {
if (typeof AddressModal !== 'undefined') {
AddressModal.onConfirmed(data);
}
// Update button to show address is set (unless cancelled)
if (!data.cancelled && data.address) {
SocketHandlers._updateAddressButtonStatus(true);
}
},
onAddressPrompt: function(data) {
@@ -463,6 +512,10 @@ const SocketHandlers = {
SocketHandlers._creditsPerGame = data.creditsPerGame;
}
// Store game mode and early entry config
SocketHandlers._gameMode = data.mode;
SocketHandlers._earlyEntryConfig = data.earlyEntry || { enabled: false };
if (typeof UI !== 'undefined' && UI.updateGameTitle) {
UI.updateGameTitle(data.cryptoType);
}
@@ -488,6 +541,14 @@ const SocketHandlers = {
$('#shopButton').hide();
}
}
// Update HelpModal with config
if (typeof HelpModal !== 'undefined') {
HelpModal.updateConfig(data);
}
// Update early entry button visibility
SocketHandlers._updateEarlyEntryButton();
},
_showNetworkWarning: function(message, network) {
@@ -1033,6 +1094,133 @@ const SocketHandlers = {
_hideSpectatorControls: function() {
$('#spectatorControls').hide();
},
// =====================
// Early Entry Functions
// =====================
_gameMode: null,
_earlyEntryConfig: { enabled: false },
_isQueued: false,
/**
* Check if early entry is currently allowed based on mode and config
*/
isEarlyEntryAllowed: function() {
const config = this._earlyEntryConfig;
if (!config || !config.enabled) return false;
const mode = this._gameMode;
if (mode === 'FREE' && config.allowInFreeMode) return true;
if (mode === 'PAID_CREDITS' && config.allowInCreditsMode) return true;
return false;
},
/**
* Update early entry button visibility based on current state
*/
_updateEarlyEntryButton: function() {
let $btn = $('#earlyEntryButton');
// Create button if it doesn't exist
if (!$btn.length) {
$btn = $(`
<button id="earlyEntryButton" style="
display: none;
background: linear-gradient(180deg, #662200, #441100);
border: 2px solid #ff6600;
color: #ffcc00;
padding: 8px 16px;
font-family: monospace;
font-size: 14px;
cursor: pointer;
margin: 5px;
border-radius: 4px;
text-shadow: 0 0 5px #ff3300;
animation: earlyEntryPulse 2s infinite;
">⚡ ENTER NOW (RISKY!) ⚡</button>
`);
// Add CSS animation
if (!$('#earlyEntryStyles').length) {
$('head').append(`
<style id="earlyEntryStyles">
@keyframes earlyEntryPulse {
0%, 100% { box-shadow: 0 0 5px #ff6600; }
50% { box-shadow: 0 0 15px #ff6600, 0 0 25px #ff3300; }
}
#earlyEntryButton:hover {
background: linear-gradient(180deg, #883300, #551100) !important;
transform: scale(1.05);
}
</style>
`);
}
// Insert near animation toggle button
const $animBtn = $('#animationToggleButton');
if ($animBtn.length) {
$animBtn.after($btn);
} else {
// Fallback - add to header area
$('#header').append($btn);
}
// Click handler
$btn.on('click', function() {
SocketHandlers.requestEarlyEntry();
});
}
// Show/hide based on whether we're queued and early entry is allowed
if (this._isQueued && this.isEarlyEntryAllowed()) {
$btn.show();
} else {
$btn.hide();
}
},
/**
* Request early entry from the server
*/
requestEarlyEntry: function() {
if (!window.socket) return;
// Disable button to prevent double-clicks
const $btn = $('#earlyEntryButton');
$btn.prop('disabled', true).text('⏳ Entering...');
socket.emit('early_entry');
},
/**
* Handle early entry success
*/
onEarlyEntrySuccess: function(data) {
console.log('Early entry success:', data);
SocketHandlers._isQueued = false;
SocketHandlers._updateEarlyEntryButton();
// The game_start event will follow - just show feedback
$('#messages').append($('<li class="status" style="color:#ff6600;">').text('⚡ Early entry! Race to escape before the next block!'));
UI.scrollChat();
},
/**
* Handle early entry error
*/
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();
}
};
+121
View File
@@ -0,0 +1,121 @@
/**
* Help Modal
* Displays game instructions and payment mode information
*/
const HelpModal = {
_config: null,
init: function() {
// Bind close buttons
$('#helpModalClose, #helpModalCloseBtn').on('click', function() {
HelpModal.hide();
});
// Bind help button
$('#helpButton').on('click', function() {
HelpModal.show();
});
// Close on overlay click
$('#helpModal').on('click', function(e) {
if (e.target === this) {
HelpModal.hide();
}
});
// Close on Escape key
$(document).on('keydown', function(e) {
if (e.key === 'Escape' && !$('#helpModal').hasClass('hidden')) {
HelpModal.hide();
}
});
},
updateConfig: function(config) {
this._config = config;
},
show: function() {
this._renderPaymentModes();
$('#helpModal').removeClass('hidden');
},
hide: function() {
$('#helpModal').addClass('hidden');
},
_renderPaymentModes: function() {
const $container = $('#helpModesList');
$container.empty();
const config = this._config || {};
const currency = config.cryptoType || 'WOW';
// Check if payments are disabled (free mode)
if (!config.paymentsEnabled) {
$container.html(`
<div style="padding:8px; background:#0d3320; border:1px solid #4ade80; border-radius:4px; margin-bottom:8px;">
<strong style="color:#4ade80;">🆓 Free Mode</strong><br>
<span>Play for free! No payment required. Payouts may or may not be enabled.</span>
</div>
`);
return;
}
// Direct payment mode
if (config.directModeEnabled) {
const price = config.singleGamePriceFormatted || '1';
const multipliers = config.payoutMultipliers?.direct || {};
const escapeMulti = multipliers.escape || 2;
const treasureMulti = multipliers.treasure || 3;
$container.append(`
<div style="padding:8px; background:#1a1a2e; border:1px solid #3730a3; border-radius:4px; margin-bottom:8px;">
<strong style="color:#818cf8;">⚡ Direct Payment</strong><br>
<span>Pay <strong>${price} ${currency}</strong> per game.</span><br>
<span style="font-size:0.85em; color:#aaa;">Escape: ${escapeMulti}× payout | With treasure: ${treasureMulti}× payout</span>
</div>
`);
}
// Credits mode
if (config.creditsModeEnabled) {
const packages = config.creditPackages || [];
const creditsPayoutEnabled = config.creditsPayoutsEnabled;
let packagesInfo = '';
if (packages.length > 0) {
packagesInfo = packages.map(pkg => {
const bonus = pkg.bonus > 0 ? ` +${pkg.bonus} bonus` : '';
return `${pkg.credits}${bonus} credits for ${pkg.priceFormatted} ${currency}`;
}).join(', ');
}
$container.append(`
<div style="padding:8px; background:#1a1a2e; border:1px solid #7c3aed; border-radius:4px; margin-bottom:8px;">
<strong style="color:#a78bfa;">🎫 Credits Mode</strong><br>
<span>Buy credits in bulk and use them to play.</span><br>
${packagesInfo ? `<span style="font-size:0.85em; color:#aaa;">Packages: ${packagesInfo}</span><br>` : ''}
${creditsPayoutEnabled
? '<span style="font-size:0.85em; color:#4ade80;">✓ Payouts enabled for credit games</span>'
: '<span style="font-size:0.85em; color:#888;">Payouts not available for credit games</span>'}
</div>
`);
}
// If no paid modes are shown but payments enabled
if (!config.directModeEnabled && !config.creditsModeEnabled && config.paymentsEnabled) {
$container.html(`
<p style="color:#888;">Payment configuration is loading...</p>
`);
}
}
};
// Expose globally
window.HelpModal = HelpModal;
// Initialize when DOM is ready
$(document).ready(function() {
HelpModal.init();
});