Module:Sandbox/User:F-Lambda/Infotable Bonuses

From Illerai

This is the current revision of this page, as edited by Mark (Sọ̀rọ̀ | contribs) at 21:59, 4 November 2024 (1 revision imported). The present address (URL) is a permanent link to this version.

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Documentation for this module may be created at Module:Sandbox/User:F-Lambda/Infotable Bonuses/doc

local p = {}

local yesno = require('Module:Yesno')
local pt = require('Module:Paramtest')
local enum = require('Module:Array')
local pagelisttools = require('Module:PageListTools')
local exchange = require('Module:Exchange')
local itemstats = require('Module:FetchItemStats')
local pagelistchecks = pagelisttools.pagelistchecks

-- Sorting keys
local skey = {
    'astab', 'aslash', 'acrush', 'amagic', 'arange',
    'dstab', 'dslash', 'dcrush', 'dmagic', 'drange',
    'str',   'mdmg',   'rstr',   'prayer', 'weight',
    'geprice',
}

-- Sorting orders
local sorder = {
    'ascending', 'asc',
    'descending', 'desc', 'reverse',
    'random', 'rand'
}

-- Construct table header
function p.header(tbl, useprices, usecomments)
    local tr = tbl:tag('tr')
        :node('<th colspan="2" rowspan="2">Item</th>')
        :node('<th colspan="5">Attack Bonuses</th>')
        :node('<th colspan="5">Defence Bonuses</th>')
    	:node('<th colspan="5">Other</th>')
    	
    if useprices then
    	tr:node('<th rowspan="2">GE Price</th>')
    end
    if usecomments then
    	tr:node('<th rowspan="2">Comment</th>')
	end

    tr = tbl:tag('tr')
        :tag('th'):wikitext('[[File:White dagger.png|link=|Stab attack]]'):done()
        :tag('th'):wikitext('[[File:White scimitar.png|link=|Slash attack]]'):done()
        :tag('th'):wikitext('[[File:White warhammer.png|link=|Crush attack]]'):done()
        :tag('th'):wikitext('[[File:Magic icon.png|link=|Magic attack]]'):done()
        :tag('th'):wikitext('[[File:Ranged icon.png|link=|Ranged attack]]'):done()
        :tag('th'):wikitext('[[File:White dagger.png|link=|Stab defence]]'):done()
        :tag('th'):wikitext('[[File:White scimitar.png|link=|Slash defence]]'):done()
        :tag('th'):wikitext('[[File:White warhammer.png|link=|Crush defence]]'):done()
        :tag('th'):wikitext('[[File:Magic icon.png|link=|Magic defence]]'):done()
        :tag('th'):wikitext('[[File:Ranged icon.png|link=|Ranged defence]]'):done()
        :tag('th'):wikitext('[[File:Strength icon.png|link=|Melee strength]]'):done()
        :tag('th'):wikitext('[[File:Magic Damage icon.png|link=|Magic damage]]'):done()
        :tag('th'):wikitext('[[File:Ranged Strength icon.png|link=|Ranged strength]]'):done()
        :tag('th'):wikitext('[[File:Prayer icon.png|link=|Prayer bonus]]'):done()
		:tag('th'):wikitext('[[File:Weight icon.png|link=|Weight]]'):done()
end

-- Soft errors
function p.softerr(txt, cat)
    local div = mw.html.create('div')
        :tag('b'):css('color', 'red'):node('Error:'):done()
        :node(' '):wikitext(txt)
        :wikitext(cat and string.format('[[Category:%s]]', cat) or '')
        :wikitext('[[Category:Pages with script errors]]')

    return div
end

-- Main entry-point
function p.main(frame)
    local args = frame:getParent().args
    return p._main(args)
end

function p._main(args)
    -- Fetch and validate input parameters
    local pages = {}
    for _, page in ipairs(args) do
        table.insert(pages, mw.text.trim(page))
    end
    assert(#pages > 0, 'You must specify at least one item')

    local keys = {}
    if pt.has_content(args['sort']) then
        for key in mw.text.gsplit(args['sort'], ',', true) do
            table.insert(keys, mw.text.trim(key))
        end
    end

	if yesno(keys[1], true) then
	    for _, o in ipairs(keys) do
	        assert(enum.contains(skey, o), 'Invalid sorting key:"' .. o .. '"' .. tostring(yesno(keys[1])))
	    end
	end

    local orders = {}
    if pt.has_content(args['order']) then
        for order in mw.text.gsplit(args['order'], ',', true) do
            table.insert(orders, mw.text.trim(order))
        end
    end
    
    local useprices = false
    if args['prices'] == 'yes' then
    	useprices = true
    end
    
    local comments = {}
    local usecomments = false
    for i=1,#pages do
    	cmt = args['comment'..tostring(i)]
    	if pt.has_content(cmt) then
    		comments[i] = cmt
    		usecomments = true
    	end
    end

    for _, o in ipairs(orders) do
        assert(enum.contains(sorder, o), 'Invalid sorting order:' .. o)
    end

    assert(#orders == #keys or #orders == 0 or #keys == 0, 'The number of sort orders must match the number of sort keys, or either can be zero')

    local ba = {
        noheader  = false,
        nototals  = false,
        expensive = false
    }

    for k, b in pairs(ba) do
        if pt.has_content(args[k]) then
            ba[k] = yesno(args[k])
        end
    end

    local curtitle = mw.title.getCurrentTitle()

    -- As the name suggests, these tests are expensive so you should only
    -- enable them temporary and site-wide for the purpose of maintenance.
    if ba['expensive'] then
        local czech = pagelistchecks(pages)

        if #czech.invalid > 0 or #czech.redirect > 0 or #czech.duplicate > 0 then
            local msg = string.format('Of the %d pages requested %d are non-existent (%s), %d are redirects (%s) and %d are duplicates (%s).',
                #pages,
                #czech.invalid, (#czech.invalid > 0) and mw.text.listToText(czech.invalid, ', ', ' and ') or '',
                #czech.redirect, (#czech.redirect > 0) and mw.text.listToText(czech.redirect, ', ', ' and ') or '',
                #czech.duplicate, (#czech.duplicate > 0) and mw.text.listToText(czech.duplicate, ', ', ' and ') or '')
            local cat = 'Infotable Bonuses with multi-variant items'
    
            return p.softerr(msg, curtitle:inNamespace('') and cat or nil)
        end
    end

    -- Fetch the data
    local smw = itemstats.equipmentStats(pages, keys, orders)

    -- Check for missing pages. Sorting in SMW is arguably broken since it can often
    -- lead to pages being removed from the results, due to either the page not having
    -- the property that's being sorted on, or that the property is set to a nil value.
    if #smw < #pages then
        local missing = #pages - #smw

        local msg = string.format('Of the %i pages requested %i are missing.%s', 
            #pages, missing, 
            (#keys > 0) and ' Try temporarily disabling sorting to see which items might have multiple variants.' or '')
        local cat = 'Infotable Bonuses with multi-variant items'

        return p.softerr(msg, curtitle:inNamespace('') and cat or nil)
    end

    -- Check for items with multiple variants
    for _, entry in ipairs(smw) do
        if entry['subobj'] then
            local msg = string.format('Item \'[[%s]]\' have multiple variants; please specify one of them: %s',
                entry['name'], mw.text.listToText(entry['subobj'], ', ', ' or '))
            local cat = 'Infotable Bonuses with multi-variant items'

            return p.softerr(msg, curtitle:inNamespace('') and cat or nil)
        end
    end

    -- Render the page
    local tbl = mw.html.create('table')
        :addClass('wikitable sortable infotable-bonuses')
        :addClass('align-center-1 align-left-2 align-right-3 align-right-4 align-right-5')
        :addClass('align-right-6 align-right-7 align-right-8 align-right-9 align-right-10')
        :addClass('align-right-11 align-right-12 align-right-13 align-right-14 align-right-15')
        :addClass('align-right-16 align-right-17 align-right-18')

    -- Header
    if not ba['noheader'] then
        p.header(tbl, useprices, usecomments)
    end

    local totals = {}
    for pr = 1, #skey do table.insert(totals, 0) end

    -- Render the rows
    for i, entry in ipairs(smw) do
        local tr = tbl:tag('tr')
            :tag('td')
            :cssText((args['cwidth'] and (args['cwidth']):len() > 0) and 'width:' .. args['cwidth'] or nil)
            :wikitext(entry['image'] and string.format('[[%s|link=|%s]]', entry['image'], mw.text.split(entry['name'], '#', true)[1]) or '')
            :done()
            :tag('td')
            :cssText((args['iwidth'] and (args['iwidth']):len() > 0) and 'width:' .. args['iwidth'] or nil)
            :wikitext(string.format('[[%s]]', mw.text.split(entry['name'], '#', true)[1]))
            :done()

        for pr = 1, #skey do
			local attr = entry[skey[pr]]
			
			if useprices then
				if skey[pr] == 'geprice' then
					if exchange._exists(entry['name']) == true then
						if entry['name']:match("#") then
							attr = "NA"
						else
							attr = exchange._price(entry['name'])
						end
					else
						attr = "NA"
					end
				else
					attr = "skip"
				end
			end
			
            local td = tr:tag('td')
                :cssText((args['cwidth'] and (args['cwidth']):len() > 0) and 'width:' .. args['cwidth'] or nil)

            if not attr then
                td:addClass('table-no'):addClass('nohighlight'):attr('data-sort-value', 0)
                    :node('?')
            else
            	if skey[pr] == 'mdmg' then
                    td:node(string.format('%d%%', attr))
            	elseif skey[pr] == 'weight' then
                    td:node(string.format('<span title="%.3f">%.1f</span>', attr, attr))
            	elseif skey[pr] == 'geprice' then
            		if useprices then
	                    if attr == "NA" then
	                    	td:addClass('table-na nohighlight')
	                    	td.style = "text-align:center"
	                    	td:node("N/A")
	                    else
	                    	td:node(tostring(attr))
	                    end
                    end
            	else
                    td:node(tostring(attr))
            	end
            	if attr == "NA" then
            		
            	else
                	totals[pr] = totals[pr] + attr
                end
            end
        end
        
        if usecomments then
        	local td = tr:tag('td')
        		:wikitext(comments[i])
        end
    end

    -- Footer
    if not ba['nototals'] then
        local tr = tbl:tag('tr'):addClass('sortbottom')
            :node('<th colspan="2">Totals</th>')
    
        for i = 1, #skey do
            local td = tr:tag('td')
            :css('text-align', 'right')
        	if skey[i] == 'mdmg' then
                td:node(string.format('%d%%', totals[i]))
        	elseif skey[i] == 'weight' then
                td:node(string.format('<span title="%.3f">%.1f</span>', totals[i], totals[i]))
        	else
                td:node(tostring(totals[i]))
        	end
        end
        if usecomments then
        	tr:tag('td')
        end
    end

    return tbl
end

--[[ DEBUG COPYPASTA
mw.logObject( p.loadData({'Beach boxing gloves#Yellow', 'Boxing gloves#Red'}, {}, {}) )
mw.logObject( p.loadData({'Iron pickaxe', 'Steel pickaxe'}, {'arange', 'drange'}, {'desc', 'desc'}) )
= p._main({'Verac\'s brassard#Undamaged', 'Verac\'s flail#Undamaged', 'Verac\'s helm#Undamaged', 'Verac\'s plateskirt#Undamaged'})
= p._main({'3rd age full helmet', '3rd age platebody', '3rd age platelegs', '3rd age kiteshield', '3rd age longsword', sort='dstab,str', order='asc,asc'})
--]]

return p