diff options
| author | historia <[not public]> | 2026-07-13 17:25:14 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-07-13 17:25:14 -0400 |
| commit | 8952653be9bfeeb9aadf59f845de507630d65dc4 (patch) | |
| tree | 64babb2c89568734cc5ff59b386845f1707f7ecf /internal/admin/static/dropview.js | |
| parent | 709917f657eda63f10ea244546e8bb7fd4d839e8 (diff) | |
| download | thehouseoficarus-8952653be9bfeeb9aadf59f845de507630d65dc4.tar.gz | |
feat(admin): add drop tables to gui showing 1/n drop chances
Diffstat (limited to 'internal/admin/static/dropview.js')
| -rw-r--r-- | internal/admin/static/dropview.js | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/internal/admin/static/dropview.js b/internal/admin/static/dropview.js new file mode 100644 index 0000000..9f4ea15 --- /dev/null +++ b/internal/admin/static/dropview.js @@ -0,0 +1,239 @@ +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 '<div class="obj-drop-view-empty">No drops</div>'; + 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 = '<div class="obj-drop-view-table">'; + 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 += '<div class="obj-drop-view-row">'; + h += '<span class="obj-drop-view-item">' + esc(qtyStr + items[i].itemID) + '</span>'; + h += '<span class="obj-drop-view-chance">' + esc(snap) + '</span>'; + h += '</div>'; + } + h += '</div>'; + return h; + } + + function refreshViewEl(view, getEntries) { + if (!document.contains(view)) return; + var body = view.querySelector('.obj-drop-view-body'); + if (!body) return; + body.innerHTML = '<span class="obj-drop-view-loading">Computing...</span>'; + computeProbMap(getEntries).then(function(probMap) { + if (!document.contains(view)) return; + body.innerHTML = renderCompact(probMap); + }).catch(function(e) { + if (!document.contains(view)) return; + body.innerHTML = '<span class="obj-drop-view-error">Error: ' + esc(e.message) + '</span>'; + }); + } + + 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 = + '<div class="obj-drop-view-header">' + + '<span>Drop chance</span>' + + '<button class="btn btn-sm obj-drop-view-refresh" title="Refresh">\u21bb</button>' + + '</div>' + + '<div class="obj-drop-view-body">Computing...</div>'; + } + + 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 + }; +})(); |
