- 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>
597 lines
No EOL
24 KiB
JavaScript
597 lines
No EOL
24 KiB
JavaScript
// 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);
|
|
}
|
|
}
|
|
}; |