From 8952653be9bfeeb9aadf59f845de507630d65dc4 Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Mon, 13 Jul 2026 17:25:14 -0400 Subject: feat(admin): add drop tables to gui showing 1/n drop chances --- internal/admin/static/dropview.js | 239 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 internal/admin/static/dropview.js (limited to 'internal/admin/static/dropview.js') 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 '