mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-22 00:28:34 +02:00
add early entry and help screens for new users
This commit is contained in:
@@ -13,6 +13,13 @@ const DEFAULT_CONFIG = Object.freeze({
|
||||
minPayment: 1000000000n, // 0.01 WOW in atomic units
|
||||
maxPayment: 100000000000000n // 1,000 WOW in atomic units
|
||||
},
|
||||
// Early entry allows free/credits mode players to start immediately without waiting for next block
|
||||
// Risk: If next block is found before they escape, they lose (timeout)
|
||||
earlyEntry: {
|
||||
enabled: true, // Master toggle for early entry feature
|
||||
allowInFreeMode: true, // Allow early entry in free mode
|
||||
allowInCreditsMode: true // Allow early entry when using credits
|
||||
},
|
||||
modes: {
|
||||
direct: {
|
||||
enabled: true,
|
||||
|
||||
@@ -12,6 +12,7 @@ const {
|
||||
getDecimalDivisor
|
||||
} = require('./helpers/gameModeUtils');
|
||||
const { ValidationError, normalizeError } = require('../utils/errors');
|
||||
const paymentConfig = require('../config/paymentConfig');
|
||||
|
||||
const DEFAULT_SINGLE_GAME_PRICE = 5000000000; // 0.005 XMR or 0.05 WOW depending on currency decimals
|
||||
const DEFAULT_CREDITS_PACKAGE_PRICE = 50000000000;
|
||||
@@ -1066,6 +1067,11 @@ class GameModeManager {
|
||||
paymentRequired: this.paymentsEnabled,
|
||||
creditsSystem: this.creditsModeEnabled,
|
||||
payouts: this.directPayoutMultipliers.escape > 0 || this.creditPayoutMultipliers.escape > 0
|
||||
},
|
||||
earlyEntry: {
|
||||
enabled: paymentConfig.earlyEntry?.enabled ?? false,
|
||||
allowInFreeMode: paymentConfig.earlyEntry?.allowInFreeMode ?? false,
|
||||
allowInCreditsMode: paymentConfig.earlyEntry?.allowInCreditsMode ?? false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -120,6 +120,147 @@ class QueueHandler {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if early entry is allowed for the current mode
|
||||
* @returns {Object} { allowed: boolean, reason: string }
|
||||
*/
|
||||
isEarlyEntryAllowed() {
|
||||
if (!this.gameModeManager) {
|
||||
return { allowed: true, reason: 'No game mode manager' };
|
||||
}
|
||||
|
||||
const config = this.gameModeManager.configSnapshot;
|
||||
const earlyEntry = config?.earlyEntry;
|
||||
|
||||
// Check master toggle
|
||||
if (!earlyEntry?.enabled) {
|
||||
return { allowed: false, reason: 'Early entry is disabled' };
|
||||
}
|
||||
|
||||
const mode = this.gameModeManager.gameMode;
|
||||
|
||||
// Check mode-specific toggles
|
||||
if (mode === 'FREE' && !earlyEntry.allowInFreeMode) {
|
||||
return { allowed: false, reason: 'Early entry not allowed in free mode' };
|
||||
}
|
||||
|
||||
if (mode === 'PAID_CREDITS' && !earlyEntry.allowInCreditsMode) {
|
||||
return { allowed: false, reason: 'Early entry not allowed in credits mode' };
|
||||
}
|
||||
|
||||
// PAID_SINGLE (direct payment) should NOT allow early entry - players paid for a full block
|
||||
if (mode === 'PAID_SINGLE') {
|
||||
return { allowed: false, reason: 'Early entry not allowed for direct payment games' };
|
||||
}
|
||||
|
||||
return { allowed: true, reason: `Early entry allowed for ${mode}` };
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle early entry request - start game immediately without waiting for next block
|
||||
* Risk: Player will die if next block is found before they escape
|
||||
*/
|
||||
async handleEarlyEntry(socket, getUserBySocket) {
|
||||
try {
|
||||
// Rate limiting
|
||||
const rateLimitResult = await this.rateLimiter.checkLimit(socket.id, 'game:queue');
|
||||
if (!rateLimitResult.allowed) {
|
||||
const msg = `Please wait ${Math.ceil(rateLimitResult.retryAfter / 1000)} seconds before trying again.`;
|
||||
socket.emit('early_entry_error', { message: msg });
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'warning', msg);
|
||||
return { success: false, reason: 'rate_limited' };
|
||||
}
|
||||
|
||||
if (this.debugManager.CONSOLE_LOGGING) {
|
||||
console.log(`⚡ Player ${socket.id} requested early entry`);
|
||||
}
|
||||
|
||||
// Check if early entry is allowed
|
||||
const earlyEntryCheck = this.isEarlyEntryAllowed();
|
||||
if (!earlyEntryCheck.allowed) {
|
||||
socket.emit('early_entry_error', { message: earlyEntryCheck.reason });
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'error', earlyEntryCheck.reason);
|
||||
return { success: false, reason: earlyEntryCheck.reason };
|
||||
}
|
||||
|
||||
const currentUser = getUserBySocket(socket.id);
|
||||
if (!currentUser) {
|
||||
const msg = 'Error: Could not start game. Please try again.';
|
||||
socket.emit('early_entry_error', { message: msg });
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'error', msg);
|
||||
return { success: false, reason: 'user_not_found' };
|
||||
}
|
||||
|
||||
// Check if already in queue
|
||||
if (this.queueManager.isPlayerQueued(socket.id)) {
|
||||
const msg = 'You are already in the queue! Use early entry only when not queued.';
|
||||
socket.emit('early_entry_error', { message: msg });
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'info', msg);
|
||||
return { success: false, reason: 'already_queued' };
|
||||
}
|
||||
|
||||
// Check if already in a game
|
||||
if (this.activeGames.has(socket.id)) {
|
||||
const msg = 'You are already in a game!';
|
||||
socket.emit('early_entry_error', { message: msg });
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'error', msg);
|
||||
return { success: false, reason: 'already_in_game' };
|
||||
}
|
||||
|
||||
// Check payment eligibility
|
||||
const paymentCheckResult = await this._checkPaymentEligibility(socket.id);
|
||||
if (!paymentCheckResult.allowed) {
|
||||
switch (paymentCheckResult.action) {
|
||||
case 'set_address':
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'payment', '⚠️ Paste your payout address first, then type confirm.');
|
||||
break;
|
||||
case 'make_payment':
|
||||
await this.paymentHandlers.createAndShowPaymentRequest(socket);
|
||||
break;
|
||||
default:
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'error',
|
||||
paymentCheckResult.reason || 'Not allowed to start game');
|
||||
}
|
||||
return { success: false, reason: paymentCheckResult.reason };
|
||||
}
|
||||
|
||||
// Record the attempt
|
||||
await this.rateLimiter.recordAttempt(socket.id, 'game:queue');
|
||||
|
||||
// Start the game immediately
|
||||
const currentBlock = this.debugManager.getCurrentBlockHeight();
|
||||
|
||||
// For early entry, the player's blockRec is set to current block
|
||||
// This means they will die when the NEXT block is found (currentBlock + 1)
|
||||
const result = await this.queueManager.startEarlyGame(socket.id, currentUser, currentBlock);
|
||||
|
||||
if (result.success) {
|
||||
// Emit early entry success event
|
||||
socket.emit('early_entry_success', { blockHeight: currentBlock });
|
||||
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'info',
|
||||
`⚡ Early entry! Game started on block ${currentBlock}. Escape before block ${currentBlock + 1}!`);
|
||||
if (this.debugManager.CONSOLE_LOGGING) {
|
||||
console.log(`⚡ EARLY ENTRY: Player ${socket.id} started on block ${currentBlock}`);
|
||||
}
|
||||
return { success: true, blockHeight: currentBlock };
|
||||
} else {
|
||||
// Emit early entry error event
|
||||
socket.emit('early_entry_error', { message: result.reason || 'Failed to start early game' });
|
||||
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'error', result.reason || 'Failed to start early game');
|
||||
return { success: false, reason: result.reason };
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
const normalized = normalizeError(error, 'Failed to start early game');
|
||||
console.error('handleEarlyEntry error:', normalized.message);
|
||||
socket.emit('early_entry_error', { message: normalized.message });
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'error', normalized.message);
|
||||
return { success: false, reason: normalized.message };
|
||||
}
|
||||
}
|
||||
|
||||
// Private helper methods
|
||||
|
||||
/**
|
||||
|
||||
@@ -155,6 +155,55 @@ class QueueManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a game immediately for early entry (not from queue)
|
||||
* Used when player opts for early entry without waiting for block
|
||||
* @param {string} serverId - Socket ID
|
||||
* @param {Object} currentUser - User object
|
||||
* @param {number} blockHeight - Current block height
|
||||
* @returns {Object} { success: boolean, reason?: string }
|
||||
*/
|
||||
async startEarlyGame(serverId, currentUser, blockHeight) {
|
||||
if (!currentUser) {
|
||||
return { success: false, reason: 'User not found' };
|
||||
}
|
||||
|
||||
// Check if already in a game
|
||||
if (this.activeGames && this.activeGames.has(serverId)) {
|
||||
return { success: false, reason: 'Already in a game' };
|
||||
}
|
||||
|
||||
// Set blockRec to current block - player dies when next block (currentBlock + 1) is found
|
||||
currentUser.blockRec = blockHeight;
|
||||
currentUser.isEarlyEntry = true; // Mark as early entry for potential special handling
|
||||
|
||||
try {
|
||||
const game = this.createGameForUser(currentUser, 'standard', { earlyEntry: true });
|
||||
const gameState = game.getState();
|
||||
gameState.blockHeight = blockHeight;
|
||||
gameState.isEarlyEntry = true;
|
||||
gameState.deathBlock = blockHeight + 1; // Explicit death block for client display
|
||||
|
||||
// Include provably fair commitment
|
||||
if (game.getProofCommitment) {
|
||||
gameState.proof = game.getProofCommitment();
|
||||
}
|
||||
|
||||
this.io.to(serverId).emit('game_start', gameState);
|
||||
|
||||
if (this.CONSOLE_LOGGING) {
|
||||
console.log(`[QueueManager] ⚡ Early entry game started for ${serverId} at block ${blockHeight} (dies at ${blockHeight + 1})`);
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
const normalized = normalizeError(err, 'Failed to start early game');
|
||||
console.error('[QueueManager] Error starting early game:', normalized.message);
|
||||
this.io.to(serverId).emit('message', 'Error starting game: ' + normalized.message);
|
||||
return { success: false, reason: normalized.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of pending games (players waiting in queue)
|
||||
* Used by spectator system to show upcoming games
|
||||
|
||||
@@ -378,6 +378,7 @@ class SocketHandlers {
|
||||
}
|
||||
});
|
||||
socket.on('auto_start', () => this.handleAutoStart(socket)); // New handler for start button
|
||||
socket.on('early_entry', () => this.handleEarlyEntry(socket)); // Early entry without waiting for block
|
||||
socket.on('address:prompt', () => this.handleAddressPrompt(socket));
|
||||
|
||||
// Payment system handlers
|
||||
@@ -490,6 +491,28 @@ class SocketHandlers {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle early entry request - start game immediately without waiting for block
|
||||
* Only available for free mode and credits mode (not direct payment mode)
|
||||
*/
|
||||
async handleEarlyEntry(socket) {
|
||||
try {
|
||||
const result = await this.queueHandler.handleEarlyEntry(
|
||||
socket,
|
||||
this.connectionHandler.getUserBySocket.bind(this.connectionHandler)
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
if (this.debugManager.CONSOLE_LOGGING) {
|
||||
console.log(`⚡ Early entry successful for ${socket.id}`);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('handleEarlyEntry error:', err);
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'error', 'Unexpected error with early entry.');
|
||||
}
|
||||
}
|
||||
|
||||
async handleAddressPrompt(socket) {
|
||||
try {
|
||||
if (this.chatHandler && typeof this.chatHandler.promptAddress === 'function') {
|
||||
|
||||
Reference in New Issue
Block a user