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
597
plugins/duck_hunt_plugin.js
Normal file
597
plugins/duck_hunt_plugin.js
Normal file
|
|
@ -0,0 +1,597 @@
|
|||
// plugins/duck-hunt.js - Duck Hunt Game Plugin
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
gameState: {
|
||||
activeDucks: new Map(), // channel -> duck data
|
||||
lastActivity: new Map(), // channel -> timestamp
|
||||
duckTimers: new Map(), // channel -> timer
|
||||
scores: new Map(), // nick -> score
|
||||
channelActivity: new Map(), // channel -> activity count
|
||||
enabledChannels: new Map() // channel -> enabled status (default true)
|
||||
},
|
||||
|
||||
duckTypes: [
|
||||
{ emoji: '🦆', name: 'Mallard Duck', points: 1, rarity: 'common', sound: 'Quack!' },
|
||||
{ emoji: '🦢', name: 'Swan', points: 2, rarity: 'uncommon', sound: 'Honk!' },
|
||||
{ emoji: '🐧', name: 'Penguin', points: 3, rarity: 'rare', sound: 'Squawk!' },
|
||||
{ emoji: '🦅', name: 'Eagle', points: 5, rarity: 'epic', sound: 'SCREECH!' },
|
||||
{ emoji: '🦉', name: 'Owl', points: 4, rarity: 'rare', sound: 'Hoot!' },
|
||||
{ emoji: '🪿', name: 'Goose', points: 2, rarity: 'uncommon', sound: 'HONK HONK!' },
|
||||
{ emoji: '🦜', name: 'Parrot', points: 3, rarity: 'rare', sound: 'Polly wants a cracker!' }
|
||||
],
|
||||
|
||||
rareDucks: [
|
||||
{ emoji: '🏆', name: 'Golden Duck', points: 10, rarity: 'legendary', sound: '*shimmers*' },
|
||||
{ emoji: '💎', name: 'Diamond Duck', points: 15, rarity: 'mythical', sound: '*sparkles*' },
|
||||
{ emoji: '🌟', name: 'Cosmic Duck', points: 20, rarity: 'cosmic', sound: '*otherworldly quack*' }
|
||||
],
|
||||
|
||||
init(bot) {
|
||||
console.log('Duck Hunt plugin initialized - Get ready to hunt! 🦆🔫');
|
||||
|
||||
// Set up score file path
|
||||
this.scoresFile = path.join(__dirname, 'duck_hunt_scores.json');
|
||||
|
||||
// Load existing scores
|
||||
this.loadScores();
|
||||
|
||||
this.startDuckSpawning(bot);
|
||||
},
|
||||
|
||||
cleanup(bot) {
|
||||
console.log('Duck Hunt plugin cleaned up - Ducks are safe... for now');
|
||||
// Save scores before cleanup
|
||||
this.saveScores();
|
||||
// Clear all timers
|
||||
this.gameState.duckTimers.forEach(timer => clearTimeout(timer));
|
||||
this.gameState.duckTimers.clear();
|
||||
},
|
||||
|
||||
commands: [
|
||||
{
|
||||
name: 'shoot',
|
||||
description: 'Shoot a duck if one is present',
|
||||
execute(context, bot) {
|
||||
module.exports.shootDuck(context, bot);
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'bang',
|
||||
description: 'Alternative shoot command',
|
||||
execute(context, bot) {
|
||||
module.exports.shootDuck(context, bot);
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'duckscore',
|
||||
description: 'Check your duck hunting score',
|
||||
execute(context, bot) {
|
||||
const score = module.exports.gameState.scores.get(context.nick) || 0;
|
||||
bot.say(context.replyTo, `🎯 ${context.nick} has shot ${score} ducks!`);
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'topducks',
|
||||
description: 'Show top duck hunters',
|
||||
execute(context, bot) {
|
||||
module.exports.showLeaderboard(context, bot);
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'duckstats',
|
||||
description: 'Show duck hunting statistics',
|
||||
execute(context, bot) {
|
||||
module.exports.showStats(context, bot);
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'stopducks',
|
||||
description: 'Stop duck spawning in this channel (bakedbeans only)',
|
||||
execute(context, bot) {
|
||||
module.exports.stopDucks(context, bot);
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'startducks',
|
||||
description: 'Resume duck spawning in this channel (bakedbeans only)',
|
||||
execute(context, bot) {
|
||||
module.exports.startDucks(context, bot);
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'feed',
|
||||
description: 'Feed a duck if one is present',
|
||||
execute(context, bot) {
|
||||
module.exports.feedDuck(context, bot);
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
// Handle all channel messages to track activity
|
||||
onMessage(data, bot) {
|
||||
if (data.isChannel) {
|
||||
this.trackActivity(data.target);
|
||||
this.scheduleNextDuck(data.target, bot);
|
||||
}
|
||||
},
|
||||
|
||||
trackActivity(channel) {
|
||||
const now = Date.now();
|
||||
this.gameState.lastActivity.set(channel, now);
|
||||
|
||||
// Increment activity counter
|
||||
const currentActivity = this.gameState.channelActivity.get(channel) || 0;
|
||||
this.gameState.channelActivity.set(channel, currentActivity + 1);
|
||||
},
|
||||
|
||||
scheduleNextDuck(channel, bot) {
|
||||
// Don't schedule if there's already a timer or active duck
|
||||
if (this.gameState.duckTimers.has(channel) || this.gameState.activeDucks.has(channel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if ducks are enabled for this channel
|
||||
if (!this.isDuckHuntEnabled(channel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Random delay between 6-20 minutes, but weighted towards longer times
|
||||
const minDelay = 6 * 60 * 1000; // 6 minutes
|
||||
const maxDelay = 20 * 60 * 1000; // 20 minutes
|
||||
|
||||
// Use exponential distribution to favor longer waits
|
||||
const randomFactor = Math.random() * Math.random(); // 0-1, skewed towards 0
|
||||
const delay = minDelay + (randomFactor * (maxDelay - minDelay));
|
||||
|
||||
console.log(`🦆 Scheduling duck for ${channel} in ${Math.round(delay/1000)} seconds`);
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
this.spawnDuck(channel, bot);
|
||||
}, delay);
|
||||
|
||||
this.gameState.duckTimers.set(channel, timer);
|
||||
},
|
||||
|
||||
spawnDuck(channel, bot) {
|
||||
// Clear the timer
|
||||
this.gameState.duckTimers.delete(channel);
|
||||
|
||||
// Check if channel has been active recently (within last 30 minutes)
|
||||
const lastActivity = this.gameState.lastActivity.get(channel) || 0;
|
||||
const thirtyMinutesAgo = Date.now() - (30 * 60 * 1000);
|
||||
|
||||
if (lastActivity < thirtyMinutesAgo) {
|
||||
console.log(`🦆 Channel ${channel} inactive, duck going back to sleep`);
|
||||
return; // Channel is quiet, don't spawn duck
|
||||
}
|
||||
|
||||
// Don't spawn if there's already an active duck
|
||||
if (this.gameState.activeDucks.has(channel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const duck = this.getRandomDuck();
|
||||
const spawnTime = Date.now();
|
||||
|
||||
// Randomly determine if this is a hunt duck (80%) or feed duck (20%)
|
||||
const isHuntDuck = Math.random() < 0.8;
|
||||
|
||||
// Duck flies away after 45-90 seconds if not shot/fed
|
||||
const flyAwayTime = 45000 + (Math.random() * 45000);
|
||||
|
||||
this.gameState.activeDucks.set(channel, {
|
||||
duck: duck,
|
||||
spawnTime: spawnTime,
|
||||
isHuntDuck: isHuntDuck,
|
||||
timer: setTimeout(() => {
|
||||
this.duckFliesAway(channel, bot, duck);
|
||||
}, flyAwayTime)
|
||||
});
|
||||
|
||||
// Duck spawn messages based on type
|
||||
let spawnMessages;
|
||||
if (isHuntDuck) {
|
||||
spawnMessages = [
|
||||
`${duck.emoji} *${duck.sound}* A ${duck.name} flies into the channel! Quick, !shoot it!`,
|
||||
`${duck.emoji} *WHOOSH* A ${duck.name} appears! Someone !shoot it before it escapes!`,
|
||||
`${duck.emoji} *${duck.sound}* Look! A wild ${duck.name}! Type !shoot to hunt it!`,
|
||||
`${duck.emoji} *flutters* A ${duck.name} lands nearby! !shoot to bag it!`,
|
||||
`${duck.emoji} *${duck.sound}* A ${duck.name} swoops in! !bang or !shoot to take it down!`
|
||||
];
|
||||
} else {
|
||||
spawnMessages = [
|
||||
`${duck.emoji} *${duck.sound}* A hungry ${duck.name} waddles into the channel! !feed it some bread!`,
|
||||
`${duck.emoji} *chirp chirp* A ${duck.name} looks around for food! Quick, !feed it!`,
|
||||
`${duck.emoji} *${duck.sound}* A friendly ${duck.name} approaches peacefully! !feed it something tasty!`,
|
||||
`${duck.emoji} *peep peep* A ${duck.name} seems hungry! !feed it before it leaves!`,
|
||||
`${duck.emoji} *${duck.sound}* A gentle ${duck.name} lands nearby looking for food! !feed it!`
|
||||
];
|
||||
}
|
||||
|
||||
const message = this.randomChoice(spawnMessages);
|
||||
bot.say(channel, message);
|
||||
|
||||
console.log(`🦆 Spawned ${duck.name} in ${channel}`);
|
||||
},
|
||||
|
||||
duckFliesAway(channel, bot, duck) {
|
||||
this.gameState.activeDucks.delete(channel);
|
||||
|
||||
const escapeMessages = [
|
||||
`${duck.emoji} *${duck.sound}* The ${duck.name} got away! Better luck next time!`,
|
||||
`${duck.emoji} *whoosh* The ${duck.name} flies away safely! Too slow, hunters!`,
|
||||
`${duck.emoji} *${duck.sound}* Nobody shot the ${duck.name}! It escapes into the sunset!`,
|
||||
`${duck.emoji} The ${duck.name} laughs at your slow reflexes and flies away!`
|
||||
];
|
||||
|
||||
bot.say(channel, this.randomChoice(escapeMessages));
|
||||
console.log(`🦆 Duck escaped from ${channel}`);
|
||||
|
||||
// Schedule next duck
|
||||
this.scheduleNextDuck(channel, bot);
|
||||
},
|
||||
|
||||
shootDuck(context, bot) {
|
||||
const channel = context.channel;
|
||||
if (!channel) {
|
||||
bot.say(context.replyTo, '🦆 You can only hunt ducks in channels!');
|
||||
return;
|
||||
}
|
||||
|
||||
const activeDuck = this.gameState.activeDucks.get(channel);
|
||||
if (!activeDuck) {
|
||||
const failMessages = [
|
||||
'🎯 *BANG!* You shoot at empty air! No ducks here!',
|
||||
'🔫 *click* Nothing to shoot! Wait for a duck to appear!',
|
||||
'🦆 The only thing you hit was your own ego! No ducks present!',
|
||||
'💨 *WHOOSH* Your bullet flies into the void! No targets!',
|
||||
'🎪 *BANG!* You\'ve shot a circus balloon! Still not a duck!'
|
||||
];
|
||||
bot.say(context.replyTo, this.randomChoice(failMessages));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is a feed duck (wrong action)
|
||||
if (!activeDuck.isHuntDuck) {
|
||||
const duck = activeDuck.duck;
|
||||
const wrongActionMessages = [
|
||||
`😱 *BANG!* You shot at the friendly ${duck.name}! It flies away terrified! You monster!`,
|
||||
`🚫 *BOOM!* The peaceful ${duck.name} was just hungry! Now it's gone because of you!`,
|
||||
`💔 *BLAM!* You scared away the gentle ${duck.name}! It just wanted food!`,
|
||||
`😰 *BANG!* The ${duck.name} flees in terror! Wrong action, hunter!`,
|
||||
`🦆💨 *BOOM!* You frightened the hungry ${duck.name}! It escapes without eating!`
|
||||
];
|
||||
|
||||
// Clear the duck
|
||||
clearTimeout(activeDuck.timer);
|
||||
this.gameState.activeDucks.delete(channel);
|
||||
|
||||
bot.say(context.replyTo, this.randomChoice(wrongActionMessages));
|
||||
console.log(`🦆 ${context.nick} tried to shoot a feed duck in ${channel}`);
|
||||
|
||||
// Schedule next duck
|
||||
this.scheduleNextDuck(channel, bot);
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear the fly-away timer
|
||||
clearTimeout(activeDuck.timer);
|
||||
|
||||
// Calculate reaction time
|
||||
const reactionTime = Date.now() - activeDuck.spawnTime;
|
||||
const reactionSeconds = (reactionTime / 1000).toFixed(2);
|
||||
|
||||
// Remove duck from active ducks
|
||||
this.gameState.activeDucks.delete(channel);
|
||||
|
||||
// Award points
|
||||
const duck = activeDuck.duck;
|
||||
const currentScore = this.gameState.scores.get(context.nick) || 0;
|
||||
this.gameState.scores.set(context.nick, currentScore + duck.points);
|
||||
|
||||
// Save scores to file after each duck shot
|
||||
this.saveScores();
|
||||
|
||||
// Success messages with variety
|
||||
const successMessages = [
|
||||
`🎯 *BANG!* ${context.nick} shot the ${duck.name}! +${duck.points} points! (${reactionSeconds}s reaction time)`,
|
||||
`🔫 *BOOM!* ${context.nick} bagged a ${duck.name}! +${duck.points} points in ${reactionSeconds} seconds!`,
|
||||
`🏆 Nice shot, ${context.nick}! You got the ${duck.name} worth ${duck.points} points! (${reactionSeconds}s)`,
|
||||
`💥 *BLAM!* ${context.nick} is a crack shot! ${duck.name} down for ${duck.points} points! Reaction: ${reactionSeconds}s`,
|
||||
`🎪 ${context.nick} with the quick draw! ${duck.name} eliminated! +${duck.points} points (${reactionSeconds}s)`
|
||||
];
|
||||
|
||||
// Add special messages for rare ducks
|
||||
if (duck.rarity === 'legendary' || duck.rarity === 'mythical' || duck.rarity === 'cosmic') {
|
||||
bot.say(channel, `🌟 ✨ RARE DUCK ALERT! ✨ 🌟`);
|
||||
}
|
||||
|
||||
bot.say(channel, this.randomChoice(successMessages));
|
||||
|
||||
// Extra flair for special achievements
|
||||
if (reactionTime < 1000) {
|
||||
bot.say(channel, '⚡ LIGHTNING FAST! That was under 1 second!');
|
||||
} else if (duck.points >= 10) {
|
||||
bot.say(channel, `💎 LEGENDARY SHOT! You got a ${duck.rarity} duck!`);
|
||||
}
|
||||
|
||||
console.log(`🎯 ${context.nick} shot ${duck.name} in ${channel} (${reactionSeconds}s)`);
|
||||
|
||||
// Schedule next duck
|
||||
this.scheduleNextDuck(channel, bot);
|
||||
},
|
||||
|
||||
feedDuck(context, bot) {
|
||||
const channel = context.channel;
|
||||
if (!channel) {
|
||||
bot.say(context.replyTo, '🦆 You can only feed ducks in channels!');
|
||||
return;
|
||||
}
|
||||
|
||||
const activeDuck = this.gameState.activeDucks.get(channel);
|
||||
if (!activeDuck) {
|
||||
const failMessages = [
|
||||
'🍞 You scatter breadcrumbs in the empty air! No ducks here!',
|
||||
'🌾 You hold out seeds but there\'s nothing to feed!',
|
||||
'🦆 You make feeding noises but no ducks are around!',
|
||||
'🥖 Your bread goes stale waiting for a duck to appear!',
|
||||
'🍀 You offer lettuce to the void! Where are the ducks?'
|
||||
];
|
||||
bot.say(context.replyTo, this.randomChoice(failMessages));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is a hunt duck (wrong action)
|
||||
if (activeDuck.isHuntDuck) {
|
||||
const duck = activeDuck.duck;
|
||||
const wrongActionMessages = [
|
||||
`🔫 *WHOOSH* The wild ${duck.name} ignores your food and flies away! You should have shot it!`,
|
||||
`🏹 *FLAP FLAP* The ${duck.name} doesn't want food, it's a wild hunter! Wrong approach!`,
|
||||
`🎯 *SWOOSH* The aggressive ${duck.name} flies off! It needed to be hunted, not fed!`,
|
||||
`💨 *FLUTTER* The ${duck.name} wasn't hungry for food! You missed your shot!`,
|
||||
`🦆 *ESCAPE* The wild ${duck.name} laughs at your bread and flies away! Should have used !shoot!`
|
||||
];
|
||||
|
||||
// Clear the duck
|
||||
clearTimeout(activeDuck.timer);
|
||||
this.gameState.activeDucks.delete(channel);
|
||||
|
||||
bot.say(context.replyTo, this.randomChoice(wrongActionMessages));
|
||||
console.log(`🦆 ${context.nick} tried to feed a hunt duck in ${channel}`);
|
||||
|
||||
// Schedule next duck
|
||||
this.scheduleNextDuck(channel, bot);
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear the fly-away timer
|
||||
clearTimeout(activeDuck.timer);
|
||||
|
||||
// Calculate reaction time
|
||||
const reactionTime = Date.now() - activeDuck.spawnTime;
|
||||
const reactionSeconds = (reactionTime / 1000).toFixed(2);
|
||||
|
||||
// Remove duck from active ducks
|
||||
this.gameState.activeDucks.delete(channel);
|
||||
|
||||
// Award points (same as hunting)
|
||||
const duck = activeDuck.duck;
|
||||
const currentScore = this.gameState.scores.get(context.nick) || 0;
|
||||
this.gameState.scores.set(context.nick, currentScore + duck.points);
|
||||
|
||||
// Save scores to file after each duck fed
|
||||
this.saveScores();
|
||||
|
||||
// Success messages for feeding
|
||||
const successMessages = [
|
||||
`🍞 *nom nom* ${context.nick} fed the ${duck.name}! +${duck.points} points! It waddles away happily! (${reactionSeconds}s reaction time)`,
|
||||
`🌾 *chirp* ${context.nick} gave seeds to the ${duck.name}! +${duck.points} points in ${reactionSeconds} seconds! So wholesome!`,
|
||||
`🥖 *happy quack* ${context.nick} shared bread with the ${duck.name}! +${duck.points} points! (${reactionSeconds}s) What a kind soul!`,
|
||||
`🍀 *munch munch* ${context.nick} fed the ${duck.name} some greens! +${duck.points} points! Reaction: ${reactionSeconds}s`,
|
||||
`🎪 ${context.nick} the duck whisperer! Fed the ${duck.name} perfectly! +${duck.points} points (${reactionSeconds}s)`
|
||||
];
|
||||
|
||||
// Add special messages for rare ducks
|
||||
if (duck.rarity === 'legendary' || duck.rarity === 'mythical' || duck.rarity === 'cosmic') {
|
||||
bot.say(channel, `🌟 ✨ RARE DUCK FEEDING! ✨ 🌟`);
|
||||
}
|
||||
|
||||
bot.say(channel, this.randomChoice(successMessages));
|
||||
|
||||
// Extra flair for special achievements
|
||||
if (reactionTime < 1000) {
|
||||
bot.say(channel, '⚡ LIGHTNING FAST FEEDING! That was under 1 second!');
|
||||
} else if (duck.points >= 10) {
|
||||
bot.say(channel, `💎 LEGENDARY FEEDING! You fed a ${duck.rarity} duck!`);
|
||||
}
|
||||
|
||||
console.log(`🦆 ${context.nick} fed ${duck.name} in ${channel} (${reactionSeconds}s)`);
|
||||
|
||||
// Schedule next duck
|
||||
this.scheduleNextDuck(channel, bot);
|
||||
},
|
||||
|
||||
getRandomDuck() {
|
||||
const rand = Math.random();
|
||||
|
||||
// Rare duck chances (very low)
|
||||
if (rand < 0.005) { // 0.5% chance for cosmic
|
||||
return this.rareDucks[2]; // Cosmic Duck
|
||||
} else if (rand < 0.015) { // 1% more for mythical
|
||||
return this.rareDucks[1]; // Diamond Duck
|
||||
} else if (rand < 0.04) { // 2.5% more for legendary
|
||||
return this.rareDucks[0]; // Golden Duck
|
||||
}
|
||||
|
||||
// Regular ducks with weighted probabilities
|
||||
const weights = [40, 25, 15, 8, 10, 25, 15]; // Common to rare
|
||||
const totalWeight = weights.reduce((a, b) => a + b, 0);
|
||||
const randomWeight = Math.random() * totalWeight;
|
||||
|
||||
let currentWeight = 0;
|
||||
for (let i = 0; i < this.duckTypes.length; i++) {
|
||||
currentWeight += weights[i];
|
||||
if (randomWeight <= currentWeight) {
|
||||
return this.duckTypes[i];
|
||||
}
|
||||
}
|
||||
|
||||
return this.duckTypes[0]; // Fallback to mallard
|
||||
},
|
||||
|
||||
showLeaderboard(context, bot) {
|
||||
const scores = Array.from(this.gameState.scores.entries())
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, 10);
|
||||
|
||||
if (scores.length === 0) {
|
||||
bot.say(context.replyTo, '🦆 No one has shot any ducks yet! Get hunting!');
|
||||
return;
|
||||
}
|
||||
|
||||
bot.say(context.replyTo, '🏆 TOP DUCK HUNTERS 🏆');
|
||||
scores.forEach((score, index) => {
|
||||
const medal = index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : '🎯';
|
||||
bot.say(context.replyTo, `${medal} ${index + 1}. ${score[0]}: ${score[1]} ducks`);
|
||||
});
|
||||
},
|
||||
|
||||
showStats(context, bot) {
|
||||
const totalDucks = Array.from(this.gameState.scores.values()).reduce((a, b) => a + b, 0);
|
||||
const totalHunters = this.gameState.scores.size;
|
||||
const activeDucks = this.gameState.activeDucks.size;
|
||||
|
||||
bot.say(context.replyTo, `🦆 DUCK HUNT STATS 🦆`);
|
||||
bot.say(context.replyTo, `🎯 Total ducks shot: ${totalDucks}`);
|
||||
bot.say(context.replyTo, `🏹 Active hunters: ${totalHunters}`);
|
||||
bot.say(context.replyTo, `🦆 Ducks currently in channels: ${activeDucks}`);
|
||||
},
|
||||
|
||||
startDuckSpawning(bot) {
|
||||
// Check for inactive channels every 5 minutes and clean up
|
||||
setInterval(() => {
|
||||
const now = Date.now();
|
||||
const thirtyMinutesAgo = now - (30 * 60 * 1000);
|
||||
|
||||
this.gameState.lastActivity.forEach((lastActivity, channel) => {
|
||||
if (lastActivity < thirtyMinutesAgo) {
|
||||
// Channel is inactive, clear any timers
|
||||
const timer = this.gameState.duckTimers.get(channel);
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
this.gameState.duckTimers.delete(channel);
|
||||
console.log(`🦆 Cleared inactive duck timer for ${channel}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 5 * 60 * 1000); // Every 5 minutes
|
||||
},
|
||||
|
||||
randomChoice(array) {
|
||||
return array[Math.floor(Math.random() * array.length)];
|
||||
},
|
||||
|
||||
isDuckHuntEnabled(channel) {
|
||||
// Default to enabled if not explicitly set
|
||||
return this.gameState.enabledChannels.get(channel) !== false;
|
||||
},
|
||||
|
||||
stopDucks(context, bot) {
|
||||
// Only allow this command from #bakedbeans channel
|
||||
if (context.channel !== '#bakedbeans') {
|
||||
bot.say(context.replyTo, '🦆 This command can only be used in #bakedbeans!');
|
||||
return;
|
||||
}
|
||||
|
||||
const channel = context.channel;
|
||||
|
||||
// Set channel as disabled
|
||||
this.gameState.enabledChannels.set(channel, false);
|
||||
|
||||
// Clear any existing timer
|
||||
const timer = this.gameState.duckTimers.get(channel);
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
this.gameState.duckTimers.delete(channel);
|
||||
}
|
||||
|
||||
// Remove any active duck
|
||||
const activeDuck = this.gameState.activeDucks.get(channel);
|
||||
if (activeDuck) {
|
||||
clearTimeout(activeDuck.timer);
|
||||
this.gameState.activeDucks.delete(channel);
|
||||
}
|
||||
|
||||
bot.say(context.replyTo, '🦆 Duck hunt disabled in this channel! Use !startducks to resume.');
|
||||
console.log(`🦆 Duck hunt disabled in ${channel} by ${context.nick}`);
|
||||
},
|
||||
|
||||
startDucks(context, bot) {
|
||||
// Only allow this command from #bakedbeans channel
|
||||
if (context.channel !== '#bakedbeans') {
|
||||
bot.say(context.replyTo, '🦆 This command can only be used in #bakedbeans!');
|
||||
return;
|
||||
}
|
||||
|
||||
const channel = context.channel;
|
||||
|
||||
// Set channel as enabled
|
||||
this.gameState.enabledChannels.set(channel, true);
|
||||
|
||||
bot.say(context.replyTo, '🦆 Duck hunt resumed in this channel! Ducks will start spawning again.');
|
||||
console.log(`🦆 Duck hunt enabled in ${channel} by ${context.nick}`);
|
||||
|
||||
// Schedule the next duck
|
||||
this.scheduleNextDuck(channel, bot);
|
||||
},
|
||||
|
||||
// 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.gameState.scores = new Map(Object.entries(scoresObject));
|
||||
console.log(`🦆 Loaded ${this.gameState.scores.size} duck hunter scores from ${this.scoresFile}`);
|
||||
|
||||
// Log top 3 hunters if any exist
|
||||
if (this.gameState.scores.size > 0) {
|
||||
const topHunters = Array.from(this.gameState.scores.entries())
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, 3);
|
||||
console.log('🏆 Top duck hunters:', topHunters.map(([name, score]) => `${name}(${score})`).join(', '));
|
||||
}
|
||||
} else {
|
||||
console.log(`🦆 No existing duck hunt scores file found, starting fresh`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ Error loading duck hunt scores:`, error);
|
||||
this.gameState.scores = 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.gameState.scores);
|
||||
const data = JSON.stringify(scoresObject, null, 2);
|
||||
|
||||
fs.writeFileSync(this.scoresFile, data, 'utf8');
|
||||
console.log(`💾 Saved ${this.gameState.scores.size} duck hunter scores to ${this.scoresFile}`);
|
||||
} catch (error) {
|
||||
console.error(`❌ Error saving duck hunt scores:`, error);
|
||||
}
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue