var DropView = (function() { var _cache = {}; var _timers = {}; var _seq = 0; var DEBOUNCE_MS = 200; function fetchTable(id) { if (!id) return Promise.resolve(null); if (id in _cache) return Promise.resolve(_cache[id]); return API.get('/api/drops/' + encodeURIComponent(id)).then(function(data) { _cache[id] = data; return data; }).catch(function() { _cache[id] = null; return null; }); } function invalidate(id) { if (id !== undefined && id !== null) { delete _cache[id]; } else { _cache = {}; } } function entryWeight(e) { var w = parseInt(e.weight) || 0; if (w <= 0) return 0; if (!e.item_id && !e.table) return 0; return w; } function mapAdd(map, key, prob) { map.set(key, (map.get(key) || 0) + prob); } function computeNestedProbMap(tableID, visited) { return fetchTable(tableID).then(function(data) { var map = new Map(); if (!data || !Array.isArray(data.drops) || data.drops.length === 0) return map; if (visited.has(tableID)) return map; if (visited.size >= 32) return map; visited.add(tableID); return _computeEntriesProbMap(data.drops, visited).then(function(result) { visited.delete(tableID); return result; }).catch(function() { visited.delete(tableID); return new Map(); }); }).catch(function() { return new Map(); }); } function _computeEntriesProbMap(entries, visited) { var total = 0; for (var i = 0; i < entries.length; i++) { total += entryWeight(entries[i]); } if (total === 0) return Promise.resolve(new Map()); var map = new Map(); var innerCache = {}; var promises = []; for (var i = 0; i < entries.length; i++) { (function(e) { var w = entryWeight(e); if (w === 0) return; var pEntry = w / total; if (e.table) { var rolls = parseInt(e.quantity) || 1; if (!innerCache[e.table]) { innerCache[e.table] = computeNestedProbMap(e.table, new Set(visited)); } promises.push( innerCache[e.table].then(function(innerMap) { innerMap.forEach(function(innerP, key) { var atLeastOnce = 1 - Math.pow(1 - innerP, rolls); mapAdd(map, key, pEntry * atLeastOnce); }); }) ); } else if (e.item_id) { var qty = parseInt(e.quantity) || 1; var key = e.item_id + '\x00' + qty; mapAdd(map, key, pEntry); } })(entries[i]); } return Promise.all(promises).then(function() { return map; }); } function computeProbMap(getEntries) { var entries = getEntries(); if (!entries || !entries.length) return Promise.resolve(new Map()); var visited = new Set(); return _computeEntriesProbMap(entries, visited); } function snapUnitFraction(p) { if (p <= 0) return '0'; if (p <= 0.0000001) return 'Very rare'; if (p >= 0.9999) return '1'; var n = Math.round(1 / p); if (n > 10000000) return 'Very rare'; if (n <= 1) return '1'; return '1/' + n; } function renderCompact(probMap) { if (probMap.size === 0) return '
No drops
'; var items = []; probMap.forEach(function(p, key) { var parts = key.split('\x00'); items.push({ itemID: parts[0], qty: parseInt(parts[1]) || 1, prob: p }); }); items.sort(function(a, b) { if (b.prob !== a.prob) return b.prob - a.prob; var nameCmp = a.itemID.localeCompare(b.itemID); if (nameCmp !== 0) return nameCmp; return a.qty - b.qty; }); var h = '
'; for (var i = 0; i < items.length; i++) { var snap = snapUnitFraction(items[i].prob); var qtyStr = items[i].qty > 1 ? items[i].qty + '\u00d7 ' : ''; h += '
'; h += '' + esc(qtyStr + items[i].itemID) + ''; h += '' + esc(snap) + ''; h += '
'; } h += '
'; return h; } function refreshViewEl(view, getEntries) { if (!document.contains(view)) return; var body = view.querySelector('.obj-drop-view-body'); if (!body) return; body.innerHTML = 'Computing...'; computeProbMap(getEntries).then(function(probMap) { if (!document.contains(view)) return; body.innerHTML = renderCompact(probMap); }).catch(function(e) { if (!document.contains(view)) return; body.innerHTML = 'Error: ' + esc(e.message) + ''; }); } function scheduleRefresh(view, getEntries) { var key = view._dvKey || (view._dvKey = ++_seq); if (_timers[key]) clearTimeout(_timers[key]); _timers[key] = setTimeout(function() { delete _timers[key]; refreshViewEl(view, getEntries); }, DEBOUNCE_MS); } function mount(container, getEntries) { if (typeof container === 'string') container = document.querySelector(container); if (!container) return null; var viewEl = container.querySelector('.obj-drop-view'); if (!viewEl) { viewEl = document.createElement('div'); viewEl.className = 'obj-drop-view'; container.appendChild(viewEl); } if (!viewEl.querySelector('.obj-drop-view-refresh')) { viewEl.innerHTML = '
' + 'Drop chance' + '' + '
' + '
Computing...
'; } viewEl._getEntries = getEntries; viewEl.querySelector('.obj-drop-view-refresh').onclick = function(e) { e.stopPropagation(); invalidate(); scheduleRefresh(viewEl, getEntries); }; if (!container._dvSetup) { container._dvSetup = true; container.addEventListener('input', function(e) { if (e.target.tagName === 'INPUT') { scheduleRefresh(viewEl, getEntries); } }); container.addEventListener('change', function(e) { scheduleRefresh(viewEl, getEntries); }); } scheduleRefresh(viewEl, getEntries); return viewEl; } function refreshView(btn) { var view = (btn && btn.closest) ? btn.closest('.obj-drop-view') : null; if (!view || !view._getEntries) return; invalidate(); scheduleRefresh(view, view._getEntries); } function setupAllInter() { document.querySelectorAll('.ie-act-kv[data-ie-act-key="drop_table"]').forEach(function(kv) { if (kv.querySelector('.obj-drop-view')) { mount(kv, function() { var dt = kv.querySelector('.ie-drop-table'); if (!dt) return []; var collected = ie_collectDropTableBlock(dt); if (!collected) return []; return collected.map(function(e) { return { item_id: e.item_id || '', table: e.table || '', weight: e.weight || 0, quantity: e.quantity || 0 }; }); }); } }); } return { mount: mount, invalidate: invalidate, refreshView: refreshView, computeProbMap: computeProbMap, snapUnitFraction: snapUnitFraction, setupAllInter: setupAllInter }; })();