megabot/plugins/combo_plugin.js
megaproxy a3ed25f8dd Initial commit: Enhanced IRC bot with duck hunt game
- Added hunt/feed duck mechanics (80% hunt, 20% feed)
- Implemented persistent scoring system
- Added channel control commands (\!stopducks/\!startducks)
- Enhanced duck hunt with wrong action penalties
- Organized bot structure with botmain.js as main file
- Added comprehensive documentation (README.md)
- Included 17 plugins with various games and utilities

🦆 Duck Hunt Features:
- Hunt ducks with \!shoot/\!bang (80% of spawns)
- Feed ducks with \!feed (20% of spawns)
- Persistent scores saved to JSON
- Channel-specific controls for #bakedbeans
- Reaction time tracking and special achievements

🎮 Other Games:
- Casino games (slots, coinflip, hi-lo, scratch cards)
- Multiplayer games (pigs, zombie dice, quiplash)
- Text generation (babble, conspiracy, drunk historian)
- Interactive features (story writing, emojify, combos)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-17 19:03:45 +00:00

339 lines
No EOL
13 KiB
JavaScript

// plugins/combo.js - Track consecutive message combos
module.exports = {
init(bot) {
console.log('🔥 Combo plugin initialized');
this.bot = bot;
// ================================
// EASY CONFIG - EDIT THIS SECTION
// ================================
this.config = {
minComboLength: 3, // Minimum messages for a combo announcement
maxComboLength: 50, // Maximum combo before forcing break
comboBreakTime: 300000, // 5 minutes - auto break combo if idle this long
ignoreShortMessages: true, // Ignore messages under 3 characters
ignoreBots: ['services', 'chanserv', 'nickserv'], // Bot nicks to ignore
comboEmojis: ['🔥', '💥', '⚡', '🎯', '🚀', '💫', '⭐', '✨'], // Random emojis for announcements
enableChannelStats: true // Track per-channel combo records
};
// Current combo tracking
this.currentCombos = new Map(); // channel -> { nick, count, lastMessageTime, broken }
// Combo records and stats
this.comboRecords = new Map(); // channel -> { topCombo: {nick, count, date}, allTimeStats: Map(nick -> {best, total, current}) }
// Last message tracking for combo breaking
this.lastMessages = new Map(); // channel -> { nick, time }
},
cleanup(bot) {
console.log('🔥 Combo plugin cleaned up');
},
// Check if we should ignore this message
shouldIgnoreMessage(nick, message) {
// Ignore bots
if (this.config.ignoreBots.includes(nick.toLowerCase())) {
return true;
}
// Ignore very short messages if configured
if (this.config.ignoreShortMessages && message.trim().length < 3) {
return true;
}
// Ignore commands (starting with !)
if (message.trim().startsWith('!')) {
return true;
}
return false;
},
// Get or create channel stats
getChannelStats(channel) {
if (!this.comboRecords.has(channel)) {
this.comboRecords.set(channel, {
topCombo: { nick: null, count: 0, date: null },
allTimeStats: new Map()
});
}
return this.comboRecords.get(channel);
},
// Get or create user stats for channel
getUserStats(channel, nick) {
const channelStats = this.getChannelStats(channel);
if (!channelStats.allTimeStats.has(nick)) {
channelStats.allTimeStats.set(nick, {
best: 0,
total: 0,
current: 0
});
}
return channelStats.allTimeStats.get(nick);
},
// Check if combo should be broken due to time
checkComboTimeout(channel) {
const combo = this.currentCombos.get(channel);
if (!combo || combo.broken) return;
const timeSinceLastMessage = Date.now() - combo.lastMessageTime;
if (timeSinceLastMessage > this.config.comboBreakTime) {
this.breakCombo(channel, 'timeout');
}
},
// Break the current combo
breakCombo(channel, reason = 'interrupted') {
const combo = this.currentCombos.get(channel);
if (!combo || combo.broken) return;
combo.broken = true;
// Update user stats
if (this.config.enableChannelStats) {
const userStats = this.getUserStats(channel, combo.nick);
userStats.current = 0; // Reset current combo
// Check if this was a personal best
if (combo.count > userStats.best) {
userStats.best = combo.count;
}
// Check if this was a channel record (but don't announce)
const channelStats = this.getChannelStats(channel);
if (combo.count > channelStats.topCombo.count) {
channelStats.topCombo = {
nick: combo.nick,
count: combo.count,
date: new Date().toISOString()
};
}
}
// No automatic announcements - only show when !combo is used
},
// Get random emoji from config
getRandomEmoji() {
return this.config.comboEmojis[Math.floor(Math.random() * this.config.comboEmojis.length)];
},
// Process a message for combo tracking
processMessage(channel, nick, message) {
// Skip if we should ignore this message
if (this.shouldIgnoreMessage(nick, message)) {
return;
}
// Check for combo timeouts first
this.checkComboTimeout(channel);
const lastMessage = this.lastMessages.get(channel);
const currentCombo = this.currentCombos.get(channel);
const now = Date.now();
// Update last message tracker
this.lastMessages.set(channel, { nick, time: now });
// Check if this continues an existing combo
if (currentCombo && !currentCombo.broken && currentCombo.nick === nick) {
// Continue the combo
currentCombo.count++;
currentCombo.lastMessageTime = now;
// Update user stats
if (this.config.enableChannelStats) {
const userStats = this.getUserStats(channel, nick);
userStats.current = currentCombo.count;
userStats.total++;
}
// No automatic announcements - combos are silent until !combo is used
// Check for forced break at max length
if (currentCombo.count >= this.config.maxComboLength) {
this.breakCombo(channel, 'forced');
}
} else {
// Break any existing combo (different user spoke)
if (currentCombo && !currentCombo.broken && currentCombo.nick !== nick) {
this.breakCombo(channel, 'interrupted');
}
// Start new combo
this.currentCombos.set(channel, {
nick: nick,
count: 1,
lastMessageTime: now,
broken: false
});
// Update user stats
if (this.config.enableChannelStats) {
const userStats = this.getUserStats(channel, nick);
userStats.current = 1;
userStats.total++;
}
}
},
// Message event handler
onMessage(data, bot) {
if (data.isChannel) {
this.processMessage(data.target, data.nick, data.message);
}
},
commands: [
{
name: 'combo',
description: 'Show current combo status',
execute: function(context, bot) {
const plugin = module.exports;
const target = context.replyTo;
const channel = context.channel || target;
// Check for timeouts first
plugin.checkComboTimeout(channel);
const currentCombo = plugin.currentCombos.get(channel);
if (!currentCombo || currentCombo.broken || currentCombo.count < 2) {
bot.say(target, '🔥 No active combo right now.');
} else {
const emoji = plugin.getRandomEmoji();
const timeAgo = Math.floor((Date.now() - currentCombo.lastMessageTime) / 1000);
bot.say(target, `${emoji} ${currentCombo.nick} is on a ${currentCombo.count}-message combo! (${timeAgo}s ago)`);
}
}
},
{
name: 'combostats',
description: 'Show combo statistics for this channel',
execute: function(context, bot) {
const plugin = module.exports;
const target = context.replyTo;
const channel = context.channel || target;
if (!plugin.config.enableChannelStats) {
bot.say(target, 'Channel stats are disabled.');
return;
}
const channelStats = plugin.getChannelStats(channel);
// Show channel record
if (channelStats.topCombo.nick) {
const date = new Date(channelStats.topCombo.date).toLocaleDateString();
bot.say(target, `🏆 Channel Record: ${channelStats.topCombo.nick} with ${channelStats.topCombo.count} messages (${date})`);
} else {
bot.say(target, '🏆 No channel record set yet.');
}
// Show top 5 personal bests
const topUsers = Array.from(channelStats.allTimeStats.entries())
.sort((a, b) => b[1].best - a[1].best)
.slice(0, 5)
.filter(([nick, stats]) => stats.best > 0);
if (topUsers.length > 0) {
bot.say(target, '📊 Top Personal Bests:');
topUsers.forEach(([nick, stats], index) => {
const rank = index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : `${index + 1}.`;
bot.say(target, `${rank} ${nick}: ${stats.best} messages (${stats.total} total messages)`);
});
} else {
bot.say(target, '📊 No stats recorded yet.');
}
}
},
{
name: 'combome',
description: 'Show your personal combo stats',
execute: function(context, bot) {
const plugin = module.exports;
const target = context.replyTo;
const from = context.nick;
const channel = context.channel || target;
if (!plugin.config.enableChannelStats) {
bot.say(target, 'Personal stats are disabled.');
return;
}
const userStats = plugin.getUserStats(channel, from);
const currentCombo = plugin.currentCombos.get(channel);
let message = `📈 ${from}'s stats: Best ${userStats.best}, Total messages ${userStats.total}`;
if (currentCombo && !currentCombo.broken && currentCombo.nick === from && currentCombo.count > 1) {
message += `, Current combo: ${currentCombo.count} 🔥`;
}
bot.say(target, message);
}
},
{
name: 'combobreak',
description: 'Break your own combo (admin can break any combo)',
execute: function(context, bot) {
const plugin = module.exports;
const target = context.replyTo;
const from = context.nick;
const channel = context.channel || target;
const args = context.args;
const currentCombo = plugin.currentCombos.get(channel);
if (!currentCombo || currentCombo.broken) {
bot.say(target, `${from}: No active combo to break.`);
return;
}
// Admin check - can break anyone's combo
const adminNicks = ['admin', 'owner', 'cancerbot', 'megasconed'];
const isAdmin = adminNicks.includes(from);
if (isAdmin && args.length > 0) {
// Admin breaking specific user's combo - no announcement
const targetNick = args[0];
if (currentCombo.nick === targetNick) {
plugin.breakCombo(channel, 'forced');
} else {
bot.say(target, `${from}: ${targetNick} doesn't have an active combo.`);
}
} else if (currentCombo.nick === from) {
// User breaking their own combo - no announcement
plugin.breakCombo(channel, 'voluntary');
} else {
// Not their combo and not admin
bot.say(target, `${from}: You can only break your own combo! (${currentCombo.nick} has the current combo)`);
}
}
},
{
name: 'combosettings',
description: 'Show current combo plugin settings',
execute: function(context, bot) {
const plugin = module.exports;
const target = context.replyTo;
bot.say(target, `⚙️ Combo Settings:`);
bot.say(target, `• Min combo for tracking: ${plugin.config.minComboLength} messages`);
bot.say(target, `• Max combo length: ${plugin.config.maxComboLength} messages`);
bot.say(target, `• Combo timeout: ${plugin.config.comboBreakTime / 60000} minutes`);
bot.say(target, `• Ignore short messages: ${plugin.config.ignoreShortMessages ? 'Yes' : 'No'}`);
bot.say(target, `• Channel stats: ${plugin.config.enableChannelStats ? 'Enabled' : 'Disabled'}`);
}
}
]
};