import { type Item, type ActorType } from "../../core/types"; import { createMeleeWeapon, createArmour, createConsumable, createAmmo, MELEE_WEAPONS, ARMOUR } from "../../core/config/Items"; import { WEAPON_VARIANTS, ARMOUR_VARIANTS, type WeaponVariantId, type ArmourVariantId } from "../../core/config/ItemVariants"; import { UpgradeManager } from "../systems/UpgradeManager"; /** * Loot drop configuration. * Chances are cumulative (checked in order). */ export const LOOT_CONFIG = { // Base chance any item drops at all (per enemy) baseDropChance: 0.25, // Type weights (what kind of item drops) typeWeights: { weapon: 30, armour: 25, consumable: 35, ammo: 10, }, // Rarity chances (applied after type is chosen) rarityChances: { base: 0.60, // 60% just base item variant: 0.30, // 30% has a variant upgraded: 0.10, // 10% has upgrade applied }, // Per-enemy type drop chance modifiers enemyDropModifiers: { rat: 0.8, bat: 0.9, // Add more enemy types as needed } as Record, }; /** * Generate a random loot item based on the loot configuration. * Returns null if no item drops. */ export function generateLoot( random: () => number, enemyType?: ActorType, floorLevel: number = 1 ): Item | null { // Check base drop chance (modified by enemy type) let dropChance: number = LOOT_CONFIG.baseDropChance; if (enemyType && LOOT_CONFIG.enemyDropModifiers[enemyType]) { dropChance *= LOOT_CONFIG.enemyDropModifiers[enemyType]; } // Higher floor = slightly more drops dropChance += floorLevel * 0.02; dropChance = Math.min(dropChance, 0.6); // Cap at 60% if (random() > dropChance) { return null; } // Determine item type const itemType = pickWeightedRandom(LOOT_CONFIG.typeWeights, random); // Determine rarity tier const rarityRoll = random(); let hasVariant = false; let hasUpgrade = false; if (rarityRoll >= (1 - LOOT_CONFIG.rarityChances.upgraded)) { // Top 10%: upgraded (implies has variant too) hasVariant = true; hasUpgrade = true; } else if (rarityRoll >= (1 - LOOT_CONFIG.rarityChances.upgraded - LOOT_CONFIG.rarityChances.variant)) { // Next 30%: variant only hasVariant = true; } // Otherwise: base item (60%) // Generate the item let item: Item | null = null; switch (itemType) { case "weapon": { const weaponIds = Object.keys(MELEE_WEAPONS) as (keyof typeof MELEE_WEAPONS)[]; const weaponId = weaponIds[Math.floor(random() * weaponIds.length)]; let variant: WeaponVariantId | undefined; if (hasVariant) { const variantIds = Object.keys(WEAPON_VARIANTS) as WeaponVariantId[]; variant = variantIds[Math.floor(random() * variantIds.length)]; } item = createMeleeWeapon(weaponId, variant); break; } case "armour": { const armourIds = Object.keys(ARMOUR) as (keyof typeof ARMOUR)[]; const armourId = armourIds[Math.floor(random() * armourIds.length)]; let variant: ArmourVariantId | undefined; if (hasVariant) { const variantIds = Object.keys(ARMOUR_VARIANTS) as ArmourVariantId[]; variant = variantIds[Math.floor(random() * variantIds.length)]; } item = createArmour(armourId, variant); break; } case "consumable": { // Only drop health potions and throwing daggers, not upgrade scrolls const droppableConsumables = ["health_potion", "throwing_dagger"] as const; const consumableId = droppableConsumables[Math.floor(random() * droppableConsumables.length)]; const quantity = 1 + Math.floor(random() * 2); // 1-2 item = createConsumable(consumableId, quantity); break; } case "ammo": { const quantity = 5 + Math.floor(random() * 10); // 5-14 item = createAmmo("ammo_9mm", quantity); break; } } // Apply upgrade if rolled if (item && hasUpgrade) { UpgradeManager.applyUpgrade(item); } return item; } /** * Pick from weighted options. */ function pickWeightedRandom( weights: Record, random: () => number ): string { const entries = Object.entries(weights); const total = entries.reduce((sum, [, w]) => sum + w, 0); let roll = random() * total; for (const [key, weight] of entries) { roll -= weight; if (roll <= 0) { return key; } } return entries[entries.length - 1][0]; }