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>
This commit is contained in:
commit
a3ed25f8dd
39 changed files with 12360 additions and 0 deletions
339
plugins/combo_plugin.js
Normal file
339
plugins/combo_plugin.js
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
// 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'}`);
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue