// plugins/pigsmp.js - Pass the Pigs Multiplayer game plugin const fs = require('fs'); const path = require('path'); module.exports = { init(bot) { console.log('Pass the Pigs Multiplayer plugin initialized'); this.bot = bot; // Persistent player statistics (saved to file) this.playerStats = new Map(); // nick -> { bestScore, totalGames, totalRolls, jackpots, name } // Current game scores (reset each game) this.gameScores = new Map(); // nick -> { currentScore, rollCount, eliminated } // Game state with multiplayer support this.gameState = { phase: 'idle', // 'idle', 'joining', 'active' players: [], joinTimer: null, startTime: null, currentPlayer: 0, turnScore: 0, channel: null, joinTimeLimit: 60000, // 60 seconds warningTime: 50000 // Warning at 50 seconds (10 sec left sluts) }; // Set up score file path this.scoresFile = path.join(__dirname, 'pigsmp_scores.json'); // Load existing scores this.loadScores(); }, cleanup(bot) { console.log('Pass the Pigs Multiplayer plugin cleaned up'); // Clear any timers if (this.gameState.joinTimer) { clearTimeout(this.gameState.joinTimer); } // Save scores before cleanup this.saveScores(); // Clear any active games on cleanup this.resetGameState(); // Clear game scores if (this.gameScores) { this.gameScores.clear(); } }, // Reset game state to idle resetGameState() { if (this.gameState.joinTimer) { clearTimeout(this.gameState.joinTimer); } this.gameState = { phase: 'idle', players: [], joinTimer: null, startTime: null, currentPlayer: 0, turnScore: 0, channel: null, joinTimeLimit: 30000, warningTime: 25000 }; }, // Load scores from file loadScores() { try { if (fs.existsSync(this.scoresFile)) { const data = fs.readFileSync(this.scoresFile, 'utf8'); const scoresObject = JSON.parse(data); // Convert back to Map this.playerStats = new Map(Object.entries(scoresObject)); console.log(`🐷 Loaded ${this.playerStats.size} pig multiplayer player stats from ${this.scoresFile}`); // Log top 3 players if any exist if (this.playerStats.size > 0) { const topPlayers = Array.from(this.playerStats.values()) .sort((a, b) => b.bestScore - a.bestScore) .slice(0, 3); console.log('🏆 Top players:', topPlayers.map(p => `${p.name}(${p.bestScore})`).join(', ')); } } else { console.log(`🐷 No existing multiplayer scores file found, starting fresh`); } } catch (error) { console.error(`❌ Error loading pig multiplayer scores:`, error); this.playerStats = new Map(); // Reset to empty if load fails } }, // Save scores to file saveScores() { try { // Convert Map to plain object for JSON serialization const scoresObject = Object.fromEntries(this.playerStats); const data = JSON.stringify(scoresObject, null, 2); fs.writeFileSync(this.scoresFile, data, 'utf8'); console.log(`💾 Saved ${this.playerStats.size} pig multiplayer player stats to ${this.scoresFile}`); } catch (error) { console.error(`❌ Error saving pig multiplayer scores:`, error); } }, // Update a player's persistent stats and save to file updatePlayerStats(nick, updates) { // Ensure playerStats Map exists if (!this.playerStats) { this.playerStats = new Map(); } if (!this.playerStats.has(nick)) { this.playerStats.set(nick, { bestScore: 0, totalGames: 0, totalRolls: 0, jackpots: 0, name: nick }); } const player = this.playerStats.get(nick); Object.assign(player, updates); // Save to file after each update this.saveScores(); }, // Initialize player for current game initGamePlayer(nick) { // Ensure gameScores Map exists if (!this.gameScores) { this.gameScores = new Map(); } if (!this.gameScores.has(nick)) { this.gameScores.set(nick, { currentScore: 0, rollCount: 0, eliminated: false }); } else { // Reset for new game this.gameScores.set(nick, { currentScore: 0, rollCount: 0, eliminated: false }); } // Initialize persistent stats if new player if (!this.playerStats.has(nick)) { this.updatePlayerStats(nick, { bestScore: 0, totalGames: 0, totalRolls: 0, jackpots: 0, name: nick }); } }, // Start the join timer startJoinTimer(target) { this.gameState.startTime = Date.now(); // Set warning timer (25 seconds) setTimeout(() => { if (this.gameState.phase === 'joining') { this.bot.say(target, '⏰ 10 seconds left to join the pig game! Join you sluts!'); } }, this.gameState.warningTime); // Set game start timer (30 seconds) this.gameState.joinTimer = setTimeout(() => { this.startGame(target); }, this.gameState.joinTimeLimit); }, // Start the actual game startGame(target) { if (this.gameState.players.length < 2) { this.bot.say(target, '😞 Nobody else joined the pig game. Game cancelled.'); this.resetGameState(); this.gameScores.clear(); return; } // Initialize all players this.gameState.players.forEach(nick => { this.initGamePlayer(nick); }); // Start the game this.gameState.phase = 'active'; this.gameState.currentPlayer = 0; this.gameState.turnScore = 0; const playerList = this.gameState.players.join(' vs '); this.bot.say(target, `🎮 Game starting! ${playerList} - ${this.gameState.players[0]} goes first!`); }, // Pass the Pigs game simulation with reduced difficulty rollPigs() { // Actual probabilities from 11,954 sample study const positions = [ { name: 'Side (no dot)', points: 0, weight: 34.9 }, { name: 'Side (dot)', points: 0, weight: 30.2 }, { name: 'Razorback', points: 5, weight: 22.4 }, { name: 'Trotter', points: 5, weight: 8.8 }, { name: 'Snouter', points: 10, weight: 3.0 }, { name: 'Leaning Jowler', points: 15, weight: 0.61 } ]; const rollPig = () => { const totalWeight = positions.reduce((sum, pos) => sum + pos.weight, 0); let random = Math.random() * totalWeight; for (const position of positions) { random -= position.weight; if (random <= 0) { return position; } } return positions[0]; // fallback }; const pig1 = rollPig(); const pig2 = rollPig(); // Check for touching conditions first (increased probability for more difficulty) if (Math.random() < 0.035) { // 3.5% chance of touching (was 1.5%) if (Math.random() < 0.25) { // 25% of touches are Piggyback return { result: 'Piggyback', points: -9999, description: '🐷📚 Piggyback! ELIMINATED!' }; } else { // 75% are Makin' Bacon/Oinker return { result: 'Makin\' Bacon', points: -999, description: '🥓 Makin\' Bacon! Lose ALL points!' }; } } // Handle side positions (Sider vs Pig Out) const pig1IsSide = pig1.name.startsWith('Side'); const pig2IsSide = pig2.name.startsWith('Side'); if (pig1IsSide && pig2IsSide) { // Both on sides - reduced chance of opposite sides (Pig Out) const pig1Dot = pig1.name.includes('dot'); const pig2Dot = pig2.name.includes('dot'); // Reduced bias toward opposite sides for better gameplay const oppositeRoll = Math.random(); if (oppositeRoll < 0.35) { // 35% chance of opposite sides when both are siders return { result: 'Pig Out', points: 0, description: '💥 Pig Out! Turn ends!' }; } else { return { result: 'Sider', points: 1, description: '🐷 Sider' }; } } // If only one pig is on its side, that pig scores nothing if (pig1IsSide && !pig2IsSide) { return { result: pig2.name, points: pig2.points, description: `🐷 ${pig2.name} (${pig2.points})` }; } if (pig2IsSide && !pig1IsSide) { return { result: pig1.name, points: pig1.points, description: `🐷 ${pig1.name} (${pig1.points})` }; } // Neither pig is on its side - normal scoring if (pig1.name === pig2.name) { // Double combinations - sum doubled (quadruple individual) const doublePoints = (pig1.points + pig2.points) * 2; switch (pig1.name) { case 'Razorback': return { result: 'Double Razorback', points: doublePoints, description: '🐷🐷 Double Razorback (20)' }; case 'Trotter': return { result: 'Double Trotter', points: doublePoints, description: '🐷🐷 Double Trotter (20)' }; case 'Snouter': return { result: 'Double Snouter', points: doublePoints, description: '🐷🐷 Double Snouter (40)' }; case 'Leaning Jowler': return { result: 'Double Leaning Jowler', points: doublePoints, description: '🐷🐷 Double Leaning Jowler (60)' }; } } // Mixed combination - sum of individual scores const totalPoints = pig1.points + pig2.points; return { result: 'Mixed Combo', points: totalPoints, description: `🐷 ${pig1.name} + ${pig2.name} (${totalPoints})` }; }, // Turn management for multiplayer pig game nextTurn(target) { // Find next non-eliminated player let attempts = 0; const maxAttempts = this.gameState.players.length; do { this.gameState.currentPlayer = (this.gameState.currentPlayer + 1) % this.gameState.players.length; attempts++; if (attempts >= maxAttempts) { // All players eliminated somehow - shouldn't happen but safety check this.bot.say(target, '🚨 All players eliminated! Game ending.'); this.endGame(target); return; } } while (this.gameScores.get(this.gameState.players[this.gameState.currentPlayer])?.eliminated); const nextPlayer = this.gameState.players[this.gameState.currentPlayer]; this.bot.say(target, `🎲 ${nextPlayer}'s turn!`); }, endGame(target) { // Ensure gameScores exists before processing if (!this.gameScores) { this.gameScores = new Map(); } // Save final scores to persistent stats and update records this.gameState.players.forEach(playerName => { const gameScore = this.gameScores.get(playerName); const playerStats = this.playerStats.get(playerName); if (gameScore && playerStats) { const updates = { totalGames: playerStats.totalGames + 1, totalRolls: playerStats.totalRolls + gameScore.rollCount }; // Update best score if this game was better if (gameScore.currentScore > playerStats.bestScore) { updates.bestScore = gameScore.currentScore; this.bot.say(target, `🏆 ${playerName} set a new personal best: ${gameScore.currentScore} points!`); } this.updatePlayerStats(playerName, updates); console.log(`🐷 Saved game stats for ${playerName}: score=${gameScore.currentScore}, rolls=${gameScore.rollCount}`); } }); // Clear current game data this.gameScores.clear(); this.bot.say(target, '🏁 Game ended! Use !pigs to start new game.'); this.resetGameState(); }, commands: [ { name: 'pigs', description: 'Start or join a multiplayer Pass the Pigs game', execute: function(context, bot) { const plugin = module.exports; const target = context.replyTo; const from = context.nick; const to = context.channel || context.replyTo; // Only allow channel play for multiplayer games if (!context.channel) { bot.say(target, 'Multiplayer pig games can only be played in channels!'); return; } // Handle different game phases switch (plugin.gameState.phase) { case 'idle': // Start new game and join timer plugin.gameState.phase = 'joining'; plugin.gameState.players = [from]; plugin.gameState.channel = to; bot.say(target, `🐷 ${from} started a pig game! Others have 60 seconds to join with !pigs`); plugin.startJoinTimer(target); break; case 'joining': // Player wants to join during countdown if (plugin.gameState.players.includes(from)) { bot.say(target, `${from}: You're already in the game!`); return; } if (plugin.gameState.players.length >= 6) { // Max 6 players bot.say(target, `${from}: Game is full! (Max 6 players)`); return; } plugin.gameState.players.push(from); bot.say(target, `🐷 ${from} joined! (${plugin.gameState.players.length} players)`); break; case 'active': // Game is running - handle roll if (!plugin.gameState.players.includes(from)) { bot.say(target, `${from}: You're not in the current game!`); return; } const currentPlayerName = plugin.gameState.players[plugin.gameState.currentPlayer]; if (from !== currentPlayerName) { bot.say(target, `${from}: It's ${currentPlayerName}'s turn!`); return; } // Check if current player is eliminated const playerGame = plugin.gameScores.get(from); if (playerGame.eliminated) { bot.say(target, `${from}: You are eliminated!`); plugin.endGame(target); return; } // Roll the pigs! const roll = plugin.rollPigs(); // Update roll count playerGame.rollCount++; let endTurn = false; let endGame = false; if (roll.points === -9999) { // Piggyback - eliminate player playerGame.eliminated = true; bot.say(target, `${from}: ${roll.description}`); // Check if only one player left const activePlayers = plugin.gameState.players.filter(p => !plugin.gameScores.get(p)?.eliminated ); if (activePlayers.length <= 1) { if (activePlayers.length === 1) { bot.say(target, `🎉 ${activePlayers[0]} wins by elimination! 🎉`); } endGame = true; } else { endTurn = true; } } else if (roll.points === -999) { // Makin' Bacon - lose all points and end turn const lostPoints = playerGame.currentScore; playerGame.currentScore = 0; plugin.gameState.turnScore = 0; bot.say(target, `${from}: ${roll.description} Lost ${lostPoints} game points!`); endTurn = true; } else if (roll.points === 0) { // Pig Out - lose turn score and end turn const lostTurn = plugin.gameState.turnScore; plugin.gameState.turnScore = 0; bot.say(target, `${from}: ${roll.description} Lost ${lostTurn} turn points!`); endTurn = true; } else { // Normal scoring - add to turn score plugin.gameState.turnScore += roll.points; const potentialTotal = playerGame.currentScore + plugin.gameState.turnScore; // Send detailed roll info as notice to the player (NO public message for normal rolls) let detailMsg = `Your roll: ${roll.description} | Turn: ${plugin.gameState.turnScore} | Game Total: ${playerGame.currentScore}`; if (potentialTotal >= 100) { detailMsg += ` | Can win!`; } bot.notice(from, detailMsg); console.log(`Sending notice to ${from}: ${detailMsg}`); } if (endGame) { plugin.endGame(target); } else if (endTurn) { plugin.nextTurn(target); } break; } } }, { name: 'bank', description: 'Bank your turn points in multiplayer Pass the Pigs', execute: function(context, bot) { const plugin = module.exports; const target = context.replyTo; const from = context.nick; if (plugin.gameState.phase !== 'active' || !plugin.gameState.players.includes(from)) { bot.say(target, `${from}: You're not in an active pig game!`); return; } const currentPlayerName = plugin.gameState.players[plugin.gameState.currentPlayer]; if (from !== currentPlayerName) { bot.say(target, `${from}: It's ${currentPlayerName}'s turn!`); return; } if (plugin.gameState.turnScore === 0) { bot.say(target, `${from}: No points to bank!`); return; } // Bank the points const playerGame = plugin.gameScores.get(from); const newTotal = playerGame.currentScore + plugin.gameState.turnScore; playerGame.currentScore = newTotal; bot.say(target, `${from}: Banked ${plugin.gameState.turnScore} points! Game Total: ${newTotal}`); // Check for win if (newTotal >= 100) { bot.say(target, `🎉 ${from} WINS with ${newTotal} points! 🎉`); plugin.endGame(target); return; } plugin.gameState.turnScore = 0; plugin.nextTurn(target); } }, { name: 'quitpigs', description: 'Quit the current multiplayer pig game', execute: function(context, bot) { const plugin = module.exports; const target = context.replyTo; const from = context.nick; if (plugin.gameState.phase === 'idle') { bot.say(target, `${from}: No pig game to quit!`); return; } if (!plugin.gameState.players.includes(from)) { bot.say(target, `${from}: You're not in the current game!`); return; } // Remove player from game plugin.gameState.players = plugin.gameState.players.filter(p => p !== from); bot.say(target, `${from} quit the pig game!`); // Check if game should continue if (plugin.gameState.phase === 'joining') { if (plugin.gameState.players.length === 0) { bot.say(target, 'Game cancelled - no players left.'); plugin.resetGameState(); } } else if (plugin.gameState.phase === 'active') { if (plugin.gameState.players.length <= 1) { if (plugin.gameState.players.length === 1) { bot.say(target, `${plugin.gameState.players[0]} wins by forfeit!`); } plugin.endGame(target); } else { // Adjust current player if needed if (plugin.gameState.currentPlayer >= plugin.gameState.players.length) { plugin.gameState.currentPlayer = 0; } plugin.nextTurn(target); } } } }, { name: 'toppigs', description: 'Show top multiplayer pig players', execute: function(context, bot) { const plugin = module.exports; const target = context.replyTo; if (plugin.playerStats.size === 0) { bot.say(target, 'No multiplayer pig scores recorded yet! Use !pigs to start playing.'); return; } // Convert to array and sort by best score const sortedScores = Array.from(plugin.playerStats.values()) .sort((a, b) => b.bestScore - a.bestScore) .slice(0, 5); // Top 5 bot.say(target, '🏆 Top Multiplayer Pig Players:'); sortedScores.forEach((player, index) => { const rank = index + 1; const trophy = rank === 1 ? '🥇' : rank === 2 ? '🥈' : rank === 3 ? '🥉' : `${rank}.`; bot.say(target, `${trophy} ${player.name}: Best ${player.bestScore} pts (${player.totalGames} games, ${player.totalRolls} rolls)`); }); } }, { name: 'pigstatus', description: 'Show current multiplayer pig game status', execute: function(context, bot) { const plugin = module.exports; const target = context.replyTo; if (plugin.gameState.phase === 'idle') { bot.say(target, 'No active multiplayer pig game. Use !pigs to start one!'); return; } if (plugin.gameState.phase === 'joining') { const timeLeft = Math.ceil((plugin.gameState.joinTimeLimit - (Date.now() - plugin.gameState.startTime)) / 1000); bot.say(target, `🐷 Joining phase: ${plugin.gameState.players.join(', ')} (${timeLeft}s left)`); return; } if (plugin.gameState.phase === 'active') { const currentPlayer = plugin.gameState.players[plugin.gameState.currentPlayer]; let statusMsg = `🎮 Active game: `; plugin.gameState.players.forEach((player, i) => { const gameScore = plugin.gameScores.get(player); if (i > 0) statusMsg += ' vs '; statusMsg += `${player}(${gameScore.currentScore})`; if (gameScore.eliminated) statusMsg += '[OUT]'; }); bot.say(target, statusMsg); bot.say(target, `🎲 ${currentPlayer}'s turn | Turn score: ${plugin.gameState.turnScore}`); } } }, { name: 'resetpigs', description: 'Reset all multiplayer pig scores (admin command)', execute: function(context, bot) { const plugin = module.exports; const target = context.replyTo; const from = context.nick; // Simple admin check - you can customize this list const adminNicks = ['admin', 'owner', 'cancerbot', 'megasconed']; // Add your admin nicks here if (!adminNicks.includes(from)) { bot.say(target, `${from}: Access denied - admin only command`); return; } const playerCount = plugin.playerStats.size; plugin.playerStats.clear(); plugin.saveScores(); bot.say(target, `🗑️ ${from}: Reset ${playerCount} multiplayer pig player stats`); } } ] };