Module:Get drop info

From Roat Pkz
Revision as of 01:37, 11 January 2024 by Wilderness>Riblet15 (rm trailblazer column)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Documentation for this module may be created at Module:Get drop info/doc

local p = {}

local onmain = require('Module:Mainonly').on_main
local yesno = require('Module:Yesno')
local purge = require('Module:Purge')._purge
local params = require('Module:Paramtest')

local images = {
    agility = '<span class="drops-agility" style="margin-left:0.3em;">[[File:Agility icon.png|link=Agility|frameless|20px]]</span>',
    combat = '<span class="drops-combat" style="margin-left:0.3em;">[[File:Multicombat.png|link=Combat level|frameless|20px]]</span>',
    hunter = '<span class="drops-hunter" style="margin-left:0.3em;">[[File:Hunter icon.png|link=Hunter|frameless|20px]]</span>',
    farming = '<span class="drops-farming" style="margin-left:0.3em;">[[File:Farming icon.png|link=Farming|frameless|20px]]</span>',
    firemaking = '<span class="drops-firemaking" style="margin-left:0.3em;">[[File:Firemaking icon.png|link=Firemaking|frameless|20px]]</span>',
    fishing = '<span class="drops-fishing" style="margin-left:0.3em;">[[File:Fishing icon.png|link=Fishing|frameless|20px]]</span>',
    mining = '<span class="drops-mining" style="margin-left:0.3em;">[[File:Mining icon.png|link=Mining|frameless|20px]]</span>',
    reward = '<span class="drops-reward" style="margin-left:0.3em;">[[File:Casket.png|link=Reward|frameless|20px]]</span>',
    woodcutting  = '<span class="drops-woodcutting" style="margin-left:0.3em;">[[File:Woodcutting icon.png|link=Woodcutting|frameless|20px]]</span>',
    smithing  = '<span class="drops-smithing" style="margin-left:0.3em;">[[File:Smithing icon.png|link=Smithing|frameless|20px]]</span>',
    thieving = '<span class="drops-thieving" style="margin-left:0.3em;">[[File:Thieving icon.png|link=Thieving|frameless|20px]]</span>'
}
--class, sort
local rarities = {
    always = { 'table-bg-blue', 1 },
    common = { 'table-bg-green', 16 },
    uncommon = { 'table-bg-yellow', 64 },
    rare = { 'table-bg-orange', 256 },
    ['very rare'] = { 'table-bg-red', 1024 },
    random = { 'table-bg-pink', 4096 },
    varies = { 'table-bg-pink', 4096 },
    once = { 'table-bg-pink', 65536 },
    conditional = { 'table-bg-pink', 65536 },
    _default = { 'table-bg-grey', 65536 }
}
-- arbitrary numbers
local rarities2 = {
    { 1, 'table-bg-blue' },
    { 1/25, 'table-bg-green' },
    { 1/99.99, 'table-bg-yellow' },
    { 1/999.99, 'table-bg-orange' },
    { 1/9999999, 'table-bg-red' }
}

local lang = mw.language.getContentLanguage()
local commas = function (n) return lang:formatNum(tonumber(n)) end

local _noted = '&nbsp;<span class="dropsline-noted">(noted)</span>'

-- supporting function for number => colour
function get_rarity_class(val)
    for i,v in ipairs(rarities2) do
        curr = v
        if val >= v[1] then
            break
        end
    end
    return curr[2]
end

function expr(t)
    t = t:gsub(',', '')
    local err, val = pcall(mw.ext.ParserFunctions.expr, t)
    if err then
        return tonumber(val)
    else
        return false
    end
end

function sigfig(n, f)
    f = math.floor(f-1)
    if n == 0 then return 0 end
    local m = math.floor(math.log10(n))
    f = math.max(m, f)
    local v = n / (10^(m-f))
    v = math.floor(v + 0.5) * 10^(m-f)
    return v
end

function p.main(frame)
    return p._main(frame:getParent().args)
end

function p._main(args)
    local item = args.item or args[1]
    local skipheader = yesno(params.default_to(args.skipheader,'no'))
    local smwitem
    if item then
        local cleanedName = item
        local dropVers = ''
        if item:match(' ?%(%d%)$') then
            cleanedName, dropVers = mw.ustring.match(item, '^(.-) ?(%(%d%))$')
        elseif item:match(' ?%(p%+*%)$') then
            cleanedName, dropVers = mw.ustring.match(item, '^(.-) ?(%(p%+*%))$')
        elseif item:match('%#') then
            cleanedName, dropVers = mw.ustring.match(item, '^(.-)%#([%w%s%(%)]+)$')
        end
        if dropVers ~= nil and dropVers ~= '' then
            smwitem = cleanedName .. '#' .. dropVers
        end
    else
        item = mw.title.getCurrentTitle().text
    end
    if not smwitem then
        smwitem = item
    end

    local q = {
        '[[Dropped item::'..smwitem..']]',
        '?Drop JSON',
        limit = args.limit or 500,
    }
    if args.incrdt == 'y' then
        q[1] = '[[Dropped item::'..smwitem..']] OR [[Dropped item from RDT::'..smwitem..']]'
    end

    local t1 = os.clock()
    local smwdata = mw.smw.ask(q)
    local t2 = os.clock()

    if not smwdata then
        return ":''No drop sources found. To force an update, click "
                ..purge('dml-'..mw.uri.anchorEncode(item), 'here', 'span')
                ..".''[[Category:Empty drop lists]]"
    end
    mw.log(string.format('SMW: entries: %d, time elapsed: %.3f ms.', #smwdata, (t2 - t1) * 1000))

    if params.has_content(args.sort) then
        assert(smwdata[1][args.sort], 'Invalid sorting key specified.')
        table.sort(smwdata, function(a, b) return a[args.sort] < b[args.sort] end)
    end

    local ret = {}
    if smwdata then
        for i,v in ipairs(smwdata) do
            local dropJSON = mw.text.jsonDecode(v['Drop JSON'] or '{}')
            table.insert(ret, makeLine(item, dropJSON))
        end
    end
    
    local t = mw.html.create('table')
    t   :addClass('wikitable sortable filterable item-drops align-center-2 align-center-3 align-center-4 autosort=4,a')
        :tag('tr')
            :tag('th'):addClass('drop-disp-btn btn-first'):wikitext('Source'):done()
            :tag('th'):wikitext('Level'):done()
            :tag('th'):wikitext('Quantity'):done()
            :tag('th'):wikitext('Rarity'):addClass('drops-rarity-header'):done()
    for i,v in ipairs(ret) do
        t:node(v)
    end
    
    if skipheader then
        return tostring(t)
    end
    
    local text = {
        "<div class=\"seealso\">For an exhaustive list of all known sources for this item, see <span class='plainlinks'>[", tostring(mw.uri.fullUrl('RuneScape:Autolists/full'))..'#'..mw.uri.buildQueryString({type='drops',page=smwitem}), " here]</span>.</div>\n",
        tostring(t)
    }
    local cat = ''
    if smwdata and onmain() then
        cat = '[[Category:Items dropped by monster]]'
        table.insert(text, cat)
    end
    return table.concat(text, '')
end

function makeLine(item, data)
    local dropType = data['Drop type'] or 'combat'
    local img = images[dropType]
    if img == nil then
        return nil
    end
    local level = data['Drop level'] or 'Not Available'
    local levelsort = data['Drop level'] or -1
    if dropType == 'reward' then
        level = 'N/A'
        levelsort = -1000
    end
    
    if string.find(level, ',') then
        levelsort = tonumber(mw.text.split(level, ',')[1])
        level = level:gsub(',', '; ')
    end
    local cleanSrc = data['Dropped from'] or ''
    local splitSrc = mw.text.split(data['Dropped from'] or '', '%#')
    if #splitSrc == 2 then
        splitSrc[2] = splitSrc[2]:gsub('_', ' ')
        cleanSrc = string.format('%s|%s <span class="beast-version">%s</span>', cleanSrc, splitSrc[1], splitSrc[2])
    end


    return line(cleanSrc, data['Name Notes'], level, img, levelsort, data['Drop Quantity'], '', data['Rarity'], data['Alt Rarity'], data['Alt Rarity Dash'], dropType, data['Rolls'])
end

function line(name,namenotes,
        combat,cbnotes,dtype,
        quantity,quantitynotes,
        rarity,altrarity,altraritydash,dtype,rolls)
    
    -- missing notes to empty string (and not nil)
    namenotes = namenotes or ''
    cbnotes = cbnotes or ''
    quantitynotes = quantitynotes or ''
    local rarity_value
    if rarities[string.lower(tostring(rarity))] then
        rarity = params.ucflc(rarity)
    else
        rarity_value = rarity:gsub(',','') --temp place to put this without overriding rarity
        local rv1, rv2 = string.match(rarity_value, '([%d%.]+)/([%d%.]+)')
        if rv1 and rv2 then
            rarity = commas(rv1) .. '/' .. commas(rv2)
            rarity_value = rv1/rv2
        else
            rarity_value = expr(rarity)
        end
    end

    local rare_class, rare_sort = '', nil
    if rarity_value == undefined then
        rare_class, rare_sort = unpack(rarities[string.lower(tostring(rarity))] or rarities._default)
    elseif rarity_value == false then
        rare_class, rare_sort = unpack(rarities._default)
    else
        rare_sort = 1/rarity_value
        rare_class = get_rarity_class(rarity_value)
    end
    local rollstext = ''
    if rolls ~= 1 and rolls then
        rollstext = rolls .. ' × '
        rare_sort = rare_sort / rolls
        rare_class = get_rarity_class(math.min(1/rare_sort,0.99))
    end
    
    -- Clean up the lists
    quantity = qty(quantity)
    local qtysort = mw.text.split(quantity, '[^%d,]')[1]
    if qtysort == '' then
        qtysort = 0
    end
    local cmbclass = ''
    local cmbsort = combat
    if combat == 'N/A' then
        cmbclass = 'table-na'
        cmbsort = 0
    else
        combat, cmbsort = cmb(combat)
    end

    -- Check if name is already formated
    if name:match('^%[%[') then
        name = name
    else
        name = '[['..name..']]'
    end
    if #namenotes > 5 then
        name = name..' '..namenotes
    end
    if #cbnotes > 5 then
        combat = combat..' '..cbnotes
    end
    if #quantitynotes > 5 then
        quantity = quantity..' '..quantitynotes
    end
    
    -- Table row creation
    local ret = mw.html.create('tr')
    ret     :tag('td')
                :wikitext(name)
            :done()
            :tag('td')
                :wikitext(combat)
                :addClass(cmbclass)
                :attr('data-sort-value', cmbsort)
            :done()
            :tag('td')
                :attr('data-sort-value', qtysort)
                :wikitext(quantity)
            :done()
    local rarity_cell = ret:tag('td')
    local rarity_span = rarity_cell:tag('span')
    rarity_span:wikitext(rollstext .. rarity)
    rarity_cell:attr('data-sort-value', rare_sort)
        :addClass(rare_class)
    :done()
    
    if type(rarity_value) == 'number' then
        rarity_span:attr({
            ['title'] = rollstext .. string.format('%.3g%%', 100 * rarity_value),
            ['data-drop-fraction'] = rollstext .. rarity,
            ['data-drop-oneover'] = rollstext .. '1/' .. commas(sigfig(1/rarity_value, 4)),
            ['data-drop-percent'] = rollstext .. sigfig(100 * rarity_value, 3),
            ['data-drop-permil'] = rollstext .. sigfig(1000 * rarity_value, 3),
            ['data-drop-permyriad'] = rollstext .. sigfig(10000 * rarity_value, 3),
        })
    end
    
    if altrarity ~= '' and altrarity ~= nil then
        local alt_rarity_value
        if rarities[string.lower(tostring(altrarity))] then
            altrarity = params.ucflc(altrarity)
        else
            alt_rarity_value = altrarity:gsub(',','') --temp place to put this without overriding rarity
            local rv1, rv2 = string.match(alt_rarity_value, '([%d%.]+)/([%d%.]+)')
            if rv1 and rv2 then
                altrarity = commas(rv1) .. '/' .. commas(rv2)
                alt_rarity_value = rv1/rv2
            else
                alt_rarity_value = expr(altrarity)
            end
        end
        
        if altraritydash  ~= '' and altraritydash ~= nil then
            rarity_cell:tag('span'):wikitext('–')
        else
            rarity_cell:tag('span'):wikitext('; ')
        end
        
        local altrarityspan = rarity_cell:tag('span')
        altrarityspan:wikitext(altrarity)
        if type(alt_rarity_value) == 'number' then
            altrarityspan:attr({
                ['data-drop-fraction'] = altrarity,
                ['data-drop-oneover'] = '1/' .. commas(sigfig(1/alt_rarity_value, 3)),
                ['data-drop-percent'] = sigfig(100 * alt_rarity_value, 3),
                ['data-drop-permil'] = sigfig(1000 * alt_rarity_value, 3),
                ['data-drop-permyriad'] = sigfig(10000 * alt_rarity_value, 3),
            })
        end
    end

    return ret:done()
end

function qty(quantity)
    -- if no quantity is given, return unknown
    if not quantity or quantity == 'unknown' then
        return 'Unknown'
    end
    -- en dashes are the proper dash for number ranges
    -- replace all hyphens and em dashes with en
    -- strip *all* whitespace
    -- change '(noted)' to '$n' for parsing
    quantity = mw.ustring.gsub(quantity,'[-—]','–')
        :gsub('%s','')
        :gsub('%(noted%)','$n')
    -- split list into table
    local vals = mw.text.split(quantity,'[,;]')
    -- recreate the quantity string to ensure consistent formatting
    local numstr = {}
    for i, v in ipairs(vals) do
        local clean = v:gsub('$n','')
        -- if list element contains an en dash (indicating range)
        -- Find the smaller/larger number (just in case)
        -- Compare them to the current min/max
        -- put them in order with desired format
        if mw.ustring.find(v,'–') then
            local splitvals = mw.text.split(clean,'–')
            -- assume a is smaller, b is larger
            local a = tonumber(splitvals[1])
            local b = tonumber(splitvals[2])
            -- Just in case
            if a and b then
                if a > b then
                    a,b = b,a
                end
                addx = commas(a)..'–'..commas(b)
            else
                addx = splitvals[1]..'–'..splitvals[2]
            end
            if v:find('$n') then
                addx = addx.._noted
            end
            table.insert(numstr,addx)
        else
            local addx = tonumber(clean) ~= nil and commas(tonumber(clean)) or clean
            if v:find('$n') then
                addx = addx.._noted
            end
            table.insert(numstr,addx)
        end
    end
    -- To prevent any possible confusion with formatted numbers
    -- elements should be separated with semicolons followed by a space
    numstr = table.concat(numstr,'; ')
    if numstr:find('%d') then
        return numstr
    else
        return 'Unknown'
    end
end

function cmb(levels)
    -- if no level is given, return unknown
    if not levels then
        return 'Unknown', 0
    end

    -- split list into table
    -- recreate the list string to ensure consistent formatting
    local numstr = {}
    for v in mw.text.gsplit(levels, '[,;]') do
        v = mw.text.trim(v)
        table.insert(numstr,tonumber(v))
    end

    table.sort(numstr)
    
    -- give a range if 5+ combat levels
    if #numstr > 4 then
        return numstr[1] .. '–' .. numstr[#numstr], numstr[1]
    end
    -- To prevent any possible confusion with formatted numbers
    -- elements should be separated with semicolons followed by a space
    return table.concat(numstr,'; '), numstr[1] or 0
end

--[[ DEBUG COPYPASTA
= p._main({item = 'Iron bar'})
--]]

return p