Add new IRC games and enhance bot functionality
- Add Rock Paper Scissors game with PvP and bot modes - Fixed syntax errors and improved game mechanics - PvP moves now require PM for secrecy - Add Word Scramble game with difficulty levels - Multiple word categories and persistent scoring - Enhance duck hunt with better statistics tracking - Separate points vs duck count tracking - Fixed migration logic issues - Add core rate limiting system (5 commands/30s) - Admin whitelist for megasconed - Automatic cleanup and unblocking - Improve reload functionality for hot-reloading plugins - Add channel-specific commands (\!stopducks/\!startducks) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
a3ed25f8dd
commit
8552887b6c
9 changed files with 1536 additions and 30 deletions
|
|
@ -70,22 +70,43 @@ module.exports = {
|
|||
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!`);
|
||||
const playerData = module.exports.gameState.scores.get(context.nick);
|
||||
if (!playerData) {
|
||||
bot.say(context.replyTo, `🎯 ${context.nick} hasn't shot any ducks yet!`);
|
||||
return;
|
||||
}
|
||||
|
||||
const points = module.exports.getTotalPoints(playerData);
|
||||
const ducks = module.exports.getTotalDucks(playerData);
|
||||
const isMigrated = playerData.migrated;
|
||||
|
||||
if (isMigrated && ducks === 0) {
|
||||
bot.say(context.replyTo, `🎯 ${context.nick}: ${points} points (duck count tracked from next shot)`);
|
||||
} else {
|
||||
bot.say(context.replyTo, `🎯 ${context.nick}: ${points} points from ${ducks} ducks!`);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'topducks',
|
||||
description: 'Show top duck hunters',
|
||||
description: 'Show top duck hunters by points',
|
||||
execute(context, bot) {
|
||||
module.exports.showLeaderboard(context, bot);
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'duckcount',
|
||||
description: 'Show top duck hunters by quantity',
|
||||
execute(context, bot) {
|
||||
module.exports.showDuckCount(context, bot);
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
name: 'duckstats',
|
||||
description: 'Show duck hunting statistics',
|
||||
description: 'Show duck hunting statistics and most shot birds',
|
||||
execute(context, bot) {
|
||||
module.exports.showStats(context, bot);
|
||||
}
|
||||
|
|
@ -293,10 +314,9 @@ module.exports = {
|
|||
// Remove duck from active ducks
|
||||
this.gameState.activeDucks.delete(channel);
|
||||
|
||||
// Award points
|
||||
// Award points and track duck type
|
||||
const duck = activeDuck.duck;
|
||||
const currentScore = this.gameState.scores.get(context.nick) || 0;
|
||||
this.gameState.scores.set(context.nick, currentScore + duck.points);
|
||||
this.updatePlayerScore(context.nick, duck.name, duck.points);
|
||||
|
||||
// Save scores to file after each duck shot
|
||||
this.saveScores();
|
||||
|
|
@ -383,10 +403,9 @@ module.exports = {
|
|||
// Remove duck from active ducks
|
||||
this.gameState.activeDucks.delete(channel);
|
||||
|
||||
// Award points (same as hunting)
|
||||
// Award points and track duck type (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);
|
||||
this.updatePlayerScore(context.nick, duck.name, duck.points);
|
||||
|
||||
// Save scores to file after each duck fed
|
||||
this.saveScores();
|
||||
|
|
@ -450,7 +469,7 @@ module.exports = {
|
|||
|
||||
showLeaderboard(context, bot) {
|
||||
const scores = Array.from(this.gameState.scores.entries())
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.sort((a, b) => this.getTotalPoints(b[1]) - this.getTotalPoints(a[1]))
|
||||
.slice(0, 10);
|
||||
|
||||
if (scores.length === 0) {
|
||||
|
|
@ -458,22 +477,70 @@ module.exports = {
|
|||
return;
|
||||
}
|
||||
|
||||
bot.say(context.replyTo, '🏆 TOP DUCK HUNTERS 🏆');
|
||||
bot.say(context.replyTo, '🏆 TOP DUCK HUNTERS (by points) 🏆');
|
||||
scores.forEach((score, index) => {
|
||||
const medal = index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : '🎯';
|
||||
bot.say(context.replyTo, `${medal} ${index + 1}. ${score[0]}: ${score[1]} ducks`);
|
||||
const points = this.getTotalPoints(score[1]);
|
||||
bot.say(context.replyTo, `${medal} ${index + 1}. ${score[0]}: ${points} points`);
|
||||
});
|
||||
},
|
||||
|
||||
showDuckCount(context, bot) {
|
||||
// Filter out migrated users with 0 duck count for this leaderboard
|
||||
const scores = Array.from(this.gameState.scores.entries())
|
||||
.filter(([nick, data]) => {
|
||||
const ducks = this.getTotalDucks(data);
|
||||
return ducks > 0 || !data.migrated; // Include non-migrated users even with 0 ducks
|
||||
})
|
||||
.sort((a, b) => this.getTotalDucks(b[1]) - this.getTotalDucks(a[1]))
|
||||
.slice(0, 10);
|
||||
|
||||
if (scores.length === 0) {
|
||||
bot.say(context.replyTo, '🦆 No one has shot any tracked ducks yet! Get hunting!');
|
||||
bot.say(context.replyTo, '📝 Note: Duck counts are tracked from new shots only (not migrated data)');
|
||||
return;
|
||||
}
|
||||
|
||||
bot.say(context.replyTo, '🦆 TOP DUCK COUNTERS (by quantity) 🦆');
|
||||
scores.forEach((score, index) => {
|
||||
const medal = index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : '🎯';
|
||||
const ducks = this.getTotalDucks(score[1]);
|
||||
bot.say(context.replyTo, `${medal} ${index + 1}. ${score[0]}: ${ducks} ducks`);
|
||||
});
|
||||
},
|
||||
|
||||
showStats(context, bot) {
|
||||
const totalDucks = Array.from(this.gameState.scores.values()).reduce((a, b) => a + b, 0);
|
||||
const totalDucks = Array.from(this.gameState.scores.values()).reduce((a, b) => a + this.getTotalDucks(b), 0);
|
||||
const totalPoints = Array.from(this.gameState.scores.values()).reduce((a, b) => a + this.getTotalPoints(b), 0);
|
||||
const totalHunters = this.gameState.scores.size;
|
||||
const activeDucks = this.gameState.activeDucks.size;
|
||||
|
||||
bot.say(context.replyTo, `🦆 DUCK HUNT STATS 🦆`);
|
||||
// Calculate most shot duck types
|
||||
const duckTypeStats = {};
|
||||
this.gameState.scores.forEach(playerData => {
|
||||
if (typeof playerData === 'object' && playerData.duckTypes) {
|
||||
Object.entries(playerData.duckTypes).forEach(([duckType, count]) => {
|
||||
duckTypeStats[duckType] = (duckTypeStats[duckType] || 0) + count;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const sortedDuckTypes = Object.entries(duckTypeStats)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, 5);
|
||||
|
||||
bot.say(context.replyTo, `🦆 DUCK HUNT STATISTICS 🦆`);
|
||||
bot.say(context.replyTo, `🎯 Total ducks shot: ${totalDucks}`);
|
||||
bot.say(context.replyTo, `🏆 Total points earned: ${totalPoints}`);
|
||||
bot.say(context.replyTo, `🏹 Active hunters: ${totalHunters}`);
|
||||
bot.say(context.replyTo, `🦆 Ducks currently in channels: ${activeDucks}`);
|
||||
|
||||
if (sortedDuckTypes.length > 0) {
|
||||
bot.say(context.replyTo, `📊 Most hunted: ${sortedDuckTypes[0][0]} (${sortedDuckTypes[0][1]} times)`);
|
||||
if (sortedDuckTypes.length > 1) {
|
||||
bot.say(context.replyTo, `🥈 Second most: ${sortedDuckTypes[1][0]} (${sortedDuckTypes[1][1]} times)`);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
startDuckSpawning(bot) {
|
||||
|
|
@ -561,16 +628,40 @@ module.exports = {
|
|||
const data = fs.readFileSync(this.scoresFile, 'utf8');
|
||||
const scoresObject = JSON.parse(data);
|
||||
|
||||
// Convert back to Map
|
||||
this.gameState.scores = new Map(Object.entries(scoresObject));
|
||||
// Convert back to Map and migrate old format if needed
|
||||
this.gameState.scores = new Map();
|
||||
let migrationNeeded = false;
|
||||
|
||||
for (const [nick, scoreData] of Object.entries(scoresObject)) {
|
||||
// Check if this is old format (just a number) or new format (object)
|
||||
if (typeof scoreData === 'number') {
|
||||
// Old format: migrate to new structure
|
||||
this.gameState.scores.set(nick, {
|
||||
totalPoints: scoreData,
|
||||
totalDucks: 0, // We don't know the real duck count, start fresh
|
||||
duckTypes: {},
|
||||
migrated: true // Flag to indicate this is migrated data
|
||||
});
|
||||
migrationNeeded = true;
|
||||
} else {
|
||||
// New format: use as-is
|
||||
this.gameState.scores.set(nick, scoreData);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`🦆 Loaded ${this.gameState.scores.size} duck hunter scores from ${this.scoresFile}`);
|
||||
|
||||
if (migrationNeeded) {
|
||||
console.log('📈 Migrated old score format to new detailed tracking');
|
||||
this.saveScores(); // Save migrated data
|
||||
}
|
||||
|
||||
// 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])
|
||||
.sort((a, b) => this.getTotalPoints(b[1]) - this.getTotalPoints(a[1]))
|
||||
.slice(0, 3);
|
||||
console.log('🏆 Top duck hunters:', topHunters.map(([name, score]) => `${name}(${score})`).join(', '));
|
||||
console.log('🏆 Top duck hunters:', topHunters.map(([name, data]) => `${name}(${this.getTotalPoints(data)}pts)`).join(', '));
|
||||
}
|
||||
} else {
|
||||
console.log(`🦆 No existing duck hunt scores file found, starting fresh`);
|
||||
|
|
@ -593,5 +684,48 @@ module.exports = {
|
|||
} catch (error) {
|
||||
console.error(`❌ Error saving duck hunt scores:`, error);
|
||||
}
|
||||
},
|
||||
|
||||
// Helper functions for new scoring system
|
||||
getTotalPoints(playerData) {
|
||||
if (typeof playerData === 'number') {
|
||||
return playerData; // Old format compatibility
|
||||
}
|
||||
return playerData.totalPoints || 0;
|
||||
},
|
||||
|
||||
getTotalDucks(playerData) {
|
||||
if (typeof playerData === 'number') {
|
||||
return playerData; // Old format estimate
|
||||
}
|
||||
return playerData.totalDucks || 0;
|
||||
},
|
||||
|
||||
initializePlayerData(nick) {
|
||||
if (!this.gameState.scores.has(nick)) {
|
||||
this.gameState.scores.set(nick, {
|
||||
totalPoints: 0,
|
||||
totalDucks: 0,
|
||||
duckTypes: {},
|
||||
migrated: false // New players have accurate data from the start
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
updatePlayerScore(nick, duckName, points) {
|
||||
this.initializePlayerData(nick);
|
||||
const playerData = this.gameState.scores.get(nick);
|
||||
|
||||
// Update totals
|
||||
playerData.totalPoints += points;
|
||||
playerData.totalDucks += 1;
|
||||
|
||||
// Update duck type count
|
||||
if (!playerData.duckTypes[duckName]) {
|
||||
playerData.duckTypes[duckName] = 0;
|
||||
}
|
||||
playerData.duckTypes[duckName] += 1;
|
||||
|
||||
this.gameState.scores.set(nick, playerData);
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue