const { getCollections, getUnixTime } = require("../utils/cookieEconomy"); const { activateBoss, failBoss, cancelBossAndRefund } = require("../utils/cookieBoss"); /* * How often we check persistent boss timers. * * Discord timestamps handle the visible countdown, * so checking once per minute is plenty. */ const CHECK_INTERVAL = 60 * 1000; /* * Your index.js automatically loads every file * inside /handlers and passes the Discord client: * * require(`./handlers/${handler}`)(client) */ module.exports = (client) => { /* * Run shortly after startup. * * This catches boss events whose timers expired * while ShaiBot was offline/restarting. */ setTimeout(() => { processBossEvents(client); }, 5000); /* * Persistent boss event watcher. */ setInterval(() => { processBossEvents(client); }, CHECK_INTERVAL); console.log( "[CookieBoss] Boss event watcher initialized" ); }; /* * Check all currently pending Cookie Boss events. */ async function processBossEvents(client) { try { const { db } = getCollections(); const events = db.collection( "items_events" ); const now = getUnixTime(); /* * ========================================== * JOINING PHASE * ========================================== * * Recruitment lasts for 30 minutes. * * If 5+ players joined: * start the battle automatically. * * If fewer than 5 joined: * cancel the boss and refund everyone. */ const joiningEvents = await events .find({ eventType: "CookieBoss", state: "Joining", joinUntil: { $lte: now } }) .toArray(); for ( const event of joiningEvents ) { const participantCount = Object.keys( event.participants ?? {} ).length; console.log( `[CookieBoss] Recruitment expired for ${event.eventId} ` + `(${participantCount}/${event.minimumParticipants} players)` ); /* * Enough players joined. * * Start the fight automatically. */ if ( participantCount >= event.minimumParticipants ) { const started = await activateBoss( client, event.eventId ); if (started) { console.log( `[CookieBoss] Battle ${event.eventId} started automatically` ); } continue; } /* * Not enough players. * * Nobody actually entered combat, * so refund all committed Cookies. */ await cancelBossAndRefund( client, event.eventId ); console.log( `[CookieBoss] Battle ${event.eventId} cancelled and refunded` ); } /* * ========================================== * ACTIVE BATTLES * ========================================== * * If the battle timer expires while the * boss still has HP remaining, the raid fails. * * All Cookies held in the encounter escrow * are transferred to the ShaiBot vault. */ const activeEvents = await events .find({ eventType: "CookieBoss", state: "Active", battleEndsAt: { $lte: now }, "boss.health": { $gt: 0 } }) .toArray(); for ( const event of activeEvents ) { console.log( `[CookieBoss] Battle ${event.eventId} expired ` + `with ${event.boss.health}/${event.boss.maxHealth} HP remaining` ); await failBoss( client, event.eventId ); console.log( `[CookieBoss] Battle ${event.eventId} failed` ); } } catch (error) { /* * Never let an exception kill the interval. * * The next watcher tick will simply try again. */ console.error( "[CookieBoss] Boss watcher error:", error ); } }