import { levelingService } from "@shared/modules/leveling/leveling.service"; import { economyService } from "@shared/modules/economy/economy.service"; import { userTimers } from "@db/schema"; import type { EffectHandler } from "./types"; import type { LootTableItem } from "@shared/lib/types"; import { inventoryService } from "@shared/modules/inventory/inventory.service"; import { inventory, items } from "@db/schema"; import { TimerType, TransactionType, LootType } from "@shared/lib/constants"; // Helper to extract duration in seconds const getDuration = (effect: any): number => { if (effect.durationHours) return effect.durationHours * 3600; if (effect.durationMinutes) return effect.durationMinutes * 60; return effect.durationSeconds || 60; // Default to 60s if nothing provided }; export const handleAddXp: EffectHandler = async (userId, effect, txFn) => { await levelingService.addXp(userId, BigInt(effect.amount), txFn); return `Gained ${effect.amount} XP`; }; export const handleAddBalance: EffectHandler = async (userId, effect, txFn) => { await economyService.modifyUserBalance(userId, BigInt(effect.amount), TransactionType.ITEM_USE, `Used Item`, null, txFn); return `Gained ${effect.amount} 🪙`; }; export const handleReplyMessage: EffectHandler = async (_userId, effect, _txFn) => { return effect.message; }; export const handleXpBoost: EffectHandler = async (userId, effect, txFn) => { const boostDuration = getDuration(effect); const expiresAt = new Date(Date.now() + boostDuration * 1000); await txFn.insert(userTimers).values({ userId: BigInt(userId), type: TimerType.EFFECT, key: 'xp_boost', expiresAt: expiresAt, metadata: { multiplier: effect.multiplier } }).onConflictDoUpdate({ target: [userTimers.userId, userTimers.type, userTimers.key], set: { expiresAt: expiresAt, metadata: { multiplier: effect.multiplier } } }); return `XP Boost (${effect.multiplier}x) active for ${Math.floor(boostDuration / 60)}m`; }; export const handleTempRole: EffectHandler = async (userId, effect, txFn) => { const roleDuration = getDuration(effect); const roleExpiresAt = new Date(Date.now() + roleDuration * 1000); await txFn.insert(userTimers).values({ userId: BigInt(userId), type: TimerType.ACCESS, key: `role_${effect.roleId}`, expiresAt: roleExpiresAt, metadata: { roleId: effect.roleId } }).onConflictDoUpdate({ target: [userTimers.userId, userTimers.type, userTimers.key], set: { expiresAt: roleExpiresAt } }); // Actual role assignment happens in the Command layer return `Temporary Role granted for ${Math.floor(roleDuration / 60)}m`; }; export const handleColorRole: EffectHandler = async (_userId, _effect, _txFn) => { return "Color Role Equipped"; }; export const handleLootbox: EffectHandler = async (userId, effect, txFn) => { const pool = effect.pool as LootTableItem[]; if (!pool || pool.length === 0) return "The box is empty..."; const totalWeight = pool.reduce((sum, item) => sum + item.weight, 0); let random = Math.random() * totalWeight; let winner: LootTableItem | null = null; for (const item of pool) { if (random < item.weight) { winner = item; break; } random -= item.weight; } if (!winner) return "The box is empty..."; // Should not happen // Process Winner if (winner.type === LootType.NOTHING) { return winner.message || "You found nothing inside."; } if (winner.type === LootType.CURRENCY) { let amount = winner.amount || 0; if (winner.minAmount && winner.maxAmount) { amount = Math.floor(Math.random() * (winner.maxAmount - winner.minAmount + 1)) + winner.minAmount; } if (amount > 0) { await economyService.modifyUserBalance(userId, BigInt(amount), TransactionType.LOOTBOX, 'Lootbox Reward', null, txFn); return winner.message || `You found ${amount} 🪙!`; } } if (winner.type === LootType.XP) { let amount = winner.amount || 0; if (winner.minAmount && winner.maxAmount) { amount = Math.floor(Math.random() * (winner.maxAmount - winner.minAmount + 1)) + winner.minAmount; } if (amount > 0) { await levelingService.addXp(userId, BigInt(amount), txFn); return winner.message || `You gained ${amount} XP!`; } } if (winner.type === LootType.ITEM) { if (winner.itemId) { const quantity = BigInt(winner.amount || 1); await inventoryService.addItem(userId, winner.itemId, quantity, txFn); // Try to fetch item name for the message try { const item = await txFn.query.items.findFirst({ where: (items: any, { eq }: any) => eq(items.id, winner.itemId!) }); if (item) { return winner.message || `You found ${quantity > 1 ? quantity + 'x ' : ''}**${item.name}**!`; } } catch (e) { console.error("Failed to fetch item name for lootbox message", e); } return winner.message || `You found an item! (ID: ${winner.itemId})`; } } return "You found nothing suitable inside."; };