/* ============================================================ * Diablo2D — loot.js : drops, gold, pickups * ============================================================ */ 'use strict'; window.D2 = window.D2 || {}; (function (D2) { const rnd = Math.random; function goldFindMult(player) { return 1 + ((player.stats && player.stats.goldFind) || 0) / 100; } function magicFindOf(player) { return (player.stats && player.stats.magicFind) || 0; } /** main drop routine when a monster dies */ function dropFor(game, m) { const p = game.player; const DC = D2.BAL.dropChances; const x = m.x, y = m.y; /* --- gold --- */ let goldChance = DC.gold + (m.isElite ? 0.4 : 0) + (m.isBoss ? 1 : 0); if (m.isBoss) goldChance = 1; if (rnd() < goldChance) { let amount = D2.BAL.goldDropBase(m.level) * goldFindMult(p); if (m.isElite) amount *= 2.6; if (m.isBoss) amount *= 7; amount = Math.max(1, Math.round(amount)); const piles = m.isBoss ? 4 : amount > 40 ? 2 : 1; for (let i = 0; i < piles; i++) { game.pickups.push(new D2.entities.Pickup('gold', x + (rnd() - .5) * 0.8, y + (rnd() - .5) * 0.8, { amount: Math.max(1, Math.round(amount / piles)) })); } } /* --- potions --- */ const potChance = (DC.potionHp + DC.potionMp) * (m.isElite ? 1.8 : 1); if (rnd() < potChance || m.isBoss) { const type = rnd() < 0.62 ? 'hp' : 'mp'; game.pickups.push(new D2.entities.Pickup('potion', x + (rnd() - .5) * .6, y + (rnd() - .5) * .6, { potionType: type })); } if (m.isBoss) { game.pickups.push(new D2.entities.Pickup('potion', x + .5, y, { potionType: 'hp' })); game.pickups.push(new D2.entities.Pickup('potion', x - .5, y, { potionType: 'mp' })); } /* --- gear --- */ let gearChance = DC.gear + (m.isElite ? DC.gearEliteBonus : 0); if (m.isBoss) gearChance = DC.gearBoss; const rolls = m.isBoss ? 4 : 1; for (let i = 0; i < rolls; i++) { if (rnd() >= gearChance && !(m.isBoss && i === 0)) continue; const opts = { magicFind: magicFindOf(p), elite: m.isElite, boss: m.isBoss, slot: rnd() < 0.55 ? undefined : D2.Items.SLOTS[(rnd() * D2.Items.SLOTS.length) | 0], }; if (m.isBoss && i === 0) { /* guaranteed flagship drop: legendary 40%, else rare */ opts.rarity = rnd() < 0.4 ? 'legendary' : 'rare'; } const item = D2.Items.rollItem(Math.max(1, m.level), opts); if (item) { game.pickups.push(new D2.entities.Pickup('item', x + (rnd() - .5) * 1.2, y + (rnd() - .5) * 1.2, { item })); if (item.rarity === 'legendary') { game.sfx('legendary'); game.toast('✦ ' + item.name, 'gold'); game.addLight(x, y, 3, '#ef8f34', 0.9, 2.5); } else if (item.rarity === 'rare') { game.sfx('pickup'); } } } } /** barrels / urns / chests */ function rollPropLoot(game, x, y, propType) { if (propType === 'shrine' || String(propType).startsWith('npc_')) return; if (propType === 'chest') { /* chests are generous */ const n = 1 + (rnd() < 0.4 ? 1 : 0); for (let i = 0; i < n; i++) { const item = D2.Items.rollItem(Math.max(1, game.monsterLevel()), { magicFind: magicFindOf(game.player) + 15 }); if (item) game.pickups.push(new D2.entities.Pickup('item', x + (rnd() - .5), y + (rnd() - .5) * .6, { item })); } const g = Math.round(D2.BAL.goldDropBase(game.monsterLevel()) * 2 * goldFindMult(game.player)); game.pickups.push(new D2.entities.Pickup('gold', x, y + .3, { amount: g })); game.sfx('coin'); return; } /* barrels & urns */ if (rnd() < 0.45) { const amount = Math.max(1, Math.round(D2.BAL.goldDropBase(Math.max(1, game.monsterLevel() - 2)) * 0.5 * goldFindMult(game.player))); game.pickups.push(new D2.entities.Pickup('gold', x, y, { amount })); } else if (rnd() < 0.14) { game.pickups.push(new D2.entities.Pickup('potion', x, y, { potionType: rnd() < 0.65 ? 'hp' : 'mp' })); } else if (rnd() < 0.05) { const item = D2.Items.rollItem(Math.max(1, game.monsterLevel() - 1)); if (item) game.pickups.push(new D2.entities.Pickup('item', x, y, { item })); } } /** collect a pickup; returns true if consumed */ function collect(game, pk) { const p = game.player; switch (pk.kind) { case 'gold': { p.gold += pk.amount; game.sfx('coin', { vol: .35 }); return true; } case 'potion': { if (p.potions[pk.potionType] >= D2.BAL.potionStackMax) return false; p.potions[pk.potionType]++; game.sfx('pickup', { vol: .3 }); return true; } case 'item': { if (p.inventory.length >= 40) { game.toast(D2.i18n.t('msg.inventory_full'), 'bad'); return false; } p.inventory.push(pk.item); game.sfx(pk.item.rarity === 'legendary' ? 'legendary' : 'pickup', { vol: .45 }); if (pk.item.rarity === 'magic' || pk.item.rarity === 'rare' || pk.item.rarity === 'legendary') { game.toast(pk.item.name, pk.item.rarity === 'legendary' ? 'gold' : ''); } if (D2.ui) D2.ui.refreshInventory(); return true; } } return false; } D2.loot = { dropFor, rollPropLoot, collect }; })(window.D2);