RuneScape Wiki
Advertisement

Documentation for this module may be created at Module:Skill guide/doc

-- <nowiki>
-- Make sorting by time actually work
-- confirm all data
-- display table titles
-- input
local p = {}

local commas = require('Module:Addcommas')._add
local tables = require('Module:Tables')

function p.eff(frame)
    local args = frame:getParent().args
    
    local dataret = require('Module:Skill guide/' .. args.skill .. '/data')
    local data = dataret(args)
    local prices = require('Module:Skill guide/' .. args.skill .. '/prices')
    
    local ret = mw.html.create('table'):addClass('wikitable sortable')
    local practicality = tonumber(args.practicality) or 0
    for _, v in ipairs(data) do
        if v.practicality >= practicality then
            v.profit = getprofit(v, prices)
            v.efficiency = (v.profit * v.numhr - args.money) / (v.xp * v.numhr)
        else
            v.efficiency = -math.huge
        end
    end
    table.sort(data, function(a,b) return a.efficiency > b.efficiency end)
    local elts = {'', 'Item', 'Level', 'XP', 'Profit', 'Coins/XP', '#/Hr', 'XP/Hr', 'Profit/Hr', '[[Efficiency]]'}
    tables._row(ret:tag('tr'), elts, true)
    
    for _, v in ipairs(data) do
        if v.practicality >= practicality then
            elts = {'[[File:' .. mw.text.split(v.name, "<")[1] .. '.png]]',
                (v.numaction and (v.numaction .. ' x ') or '') .. '[[' .. mw.text.split(v.name, "<")[1] .. ']]',
                v.level,
                v.xp,
                commas(v.profit),
                string.format("%.2f", v.profit / v.xp),
                commas(v.numhr),
                commas(v.numhr * v.xp),
                commas(v.numhr * v.profit),
                string.format("%.2f", v.efficiency)}  
            tables._row(ret:tag('tr'), elts, false)
        end
    end
    
    return ret
end

function p.skillcalc(frame)
    local args = frame:getParent().args
    
    local level = require('Module:Experience').level_at_xp
    local current = args.current
    local goal = args.goal
    local currentlevel = level({args = {current}})
    local goallevel = level({args = {goal}})
    
    local dataret = require('Module:Skill guide/' .. args.skill .. '/data')
    local data = dataret(args)
    local prices = require('Module:Skill guide/' .. args.skill .. '/prices')
    
    table.sort(data, function(a,b) return a.level < b.level end)
    
    local ret = mw.html.create('table'):addClass('wikitable sortable')
    
    local elts = {'', 'Item', 'Level', 'XP', 'Coins/XP', 'XP/Hr', '# Needed', 'Est. Profit', 'Est. Time'}
    tables._row(ret:tag('tr'), elts, true)
    
    for _, v in ipairs(data) do
        local profit = getprofit(v, prices)
        local needed = math.ceil((goal - current) / v.xp)
        local totaltime = math.ceil(3600 * needed / v.numhr)
        local elts = {'[[File:' .. mw.text.split(v.name, "<")[1] .. '.png]]',
                (v.numaction and (v.numaction .. ' x ') or '') .. '[[' .. mw.text.split(v.name, "<")[1] .. ']]',
                v.level,
                v.xp,
                string.format("%.2f", profit / v.xp),
                commas(v.numhr * v.xp),
                commas(needed),
                commas(needed * profit),
                {['text'] = formattime(totaltime), attr = {['data-sort-value'] = totaltime}}}
        local class = 'sg-yellow'
        if v.level > goallevel then
            class = 'sg-red'
        elseif v.level <= currentlevel then
            class = 'sg-green'
        end
        tables._row(ret:tag('tr'):addClass(class), elts, false)
    end
    return ret
end

function p.guide(frame)
    local args = frame:getParent().args
    local level = require('Module:Experience')
    local current = args.current
    local goal = args.goal
    if tonumber(current) >= tonumber(goal) then
        return "You've already reached this goal!"
    end
    local currentlevel = level.level_at_xp({args = {current}})
    local goallevel = level.level_at_xp({args = {goal}})
    
    local dataret = require('Module:Skill guide/' .. args.skill .. '/data')
    local data = dataret(args)
    local prices = require('Module:Skill guide/' .. args.skill .. '/prices')
    
    local loremipsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
    
    local ignored = mw.text.split(args.ignored or '', "!")
    local excludedtable = ""
    if #ignored > 1 then
        excludedtable = mw.html.create('table'):addClass('wikitable mw-collapsible mw-collapsed')
        excludedlist = ""
        for _, v in ipairs(ignored) do
            if v ~= '' then
                excludedlist = excludedlist .. '\n' .. tostring(mw.html.create('span'):wikitext('[[File:Yes check.svg|20px|link=]] '):attr('lang', v):addClass('wiki-skillguide-excludedx'):css('cursor', 'pointer')) .. v .. '<br />'
            end
        end
        local tabledata = {{{tag = 'th', text = 'Excluded methods'}},
            {{tag = 'td', text = excludedlist}}}
        tables._table(excludedtable, tabledata)
    end
    
    local practicality = tonumber(args.practicality) or 0
    for _, v in ipairs(data) do
        if v.practicality >= practicality then
            v.profit = getprofit(v, prices)
            v.efficiency = (v.profit * v.numhr - args.money) / (v.xp * v.numhr)
        else
            v.efficiency = -math.huge
        end
    end
    table.sort(data, function(a,b) if a.efficiency == b.efficiency then return a.level < b.level else return a.efficiency > b.efficiency end end)
    local best = {}
    local finished = false
    for _, v in ipairs(data) do
        if (not inarray(ignored, v.name)) and v.practicality >= practicality and v.level <= goallevel and (#best == 0 or v.level < best[#best].level) then
            table.insert(best, v)
            if v.level <= currentlevel then
                finished = true
                break
            end
        end
    end
    
    if not finished then
        return 'Could not find a suitable training guide given your constraints. Try decreasing minimum practicality, or ignore fewer methods.' .. tostring(excludedtable)
    end
	return outputguide(best, current, goal, currentlevel, goallevel, excludedtable, args.skill, args.money, prices, true)
end

function p.staticguide(frame)
	local args = frame:getParent().args
    local level = require('Module:Experience')
    local current = args.current
    local goal = args.goal
    local currentlevel = level.level_at_xp({args = {current}})
    local goallevel = level.level_at_xp({args = {goal}})
    
    for _, v in ipairs({"money", "current", "goal"}) do
        if not args[v] then
            return "Missing " .. v .. " parameter."
        end
    end
    local dataret = require('Module:Skill guide/' .. args.skill .. '/data')
    local data = dataret(args)
    local prices = require('Module:Skill guide/' .. args.skill .. '/prices')
	
	local methods = mw.text.split(args.methods or '', "!")
	
	local _data = {}
	for _, v in ipairs(data) do
        for _, w in ipairs(methods) do
            if w == v.name then
                table.insert(_data, v)
            end
        end
    end

    for _, v in ipairs(_data) do
        v.profit = getprofit(v, prices)
        v.efficiency = (v.profit * v.numhr - args.money) / (v.xp * v.numhr)
    end
    table.sort(_data, function(a,b) if a.efficiency == b.efficiency then return a.level < b.level else return a.efficiency > b.efficiency end end)
    local best = {}
    for _, v in ipairs(_data) do
        if v.level <= goallevel and (#best == 0 or v.level < best[#best].level) then
            table.insert(best, v)
            if v.level <= currentlevel then
                finished = true
                break
            end
        end
    end
    
    if not finished then
        return "There's no way to reach your goal with these methods."
    end
    local notice = ""
    if args.date then
        notice = "''This guide was generated on " .. args.date .. " and may no longer be ideal for your goal. Visit '''[http://runescape.wikia.com/wiki/Calculator:Skilltest?money=" .. args.money .. "&current=" .. current .. "&goal=" .. goal .. " here]''' for the most recent guide.''\n"
    end
    return notice .. outputguide(best, current, goal, currentlevel, goallevel, "", args.skill, args.money, prices, false)
end

function outputguide(best, current, goal, currentlevel, goallevel, excludedtable, skill, money, prices, interactive)
    local coins = require('Module:Coins')._amount
    local level = require('Module:Experience')
    local ret = mw.html.create('table'):addClass('wikitable')
    ret:tag('caption'):wikitext(skill .. ' training guide for level ' .. currentlevel .. ' to ' .. goallevel .. ' at ' .. commas(money) .. ' coins per hour')
    local elts = {'', '', 'Item', 'Level', 'XP', 'Profit', 'Coins/XP', '#/Hr', 'XP/Hr', 'Profit/Hr', '[[Efficiency]]', '#', 'Total Profit', 'Time'}
    if not interactive then
        table.remove(elts, 1)
    end
    tables._row(ret:tag('tr'), elts, true)
    local curxp = current
    local totaltime = 0
    local totalprofit = 0
    local totalxp = 0
    local guidetext = ""
    for j = #best, 1, -1 do
        local v = best[j]
        if best[j - 1] then
            local levelxp = level.xp_at_level({args = {best[j - 1].level}})
            v.num = math.ceil((levelxp - curxp) / v.xp)
            curxp = curxp + v.xp * v.num
            v.low = v.level
            v.high = best[j -1].level
        else
            v.num = math.ceil((goal - curxp) / v.xp)
            v.low = v.level
            v.high = "end"
        end
        best[#best].low = "start"
        totaltime = totaltime + 3600 * v.num / v.numhr
        totalprofit = totalprofit + v.num * v.profit
        totalxp = totalxp + v.xp * v.num
        local elts = {{text = '[[File:X mark.svg|20px|link=]]', attr = {abbr = v.name}, class = "wiki-skillguide-x", css = {cursor = 'pointer'}},
                '[[File:' .. mw.text.split(v.name, "<")[1] .. '.png]]',
                (v.numaction and (v.numaction .. ' x ') or '') .. '[[' .. mw.text.split(v.name, "<")[1] .. ']]',
                v.level,
                v.xp,
                commas(v.profit),
                string.format("%.2f", v.profit / v.xp),
                commas(v.numhr),
                commas(v.numhr * v.xp),
                commas(v.numhr * v.profit),
                string.format("%.2f", v.efficiency),
                commas(v.num),
                commas(v.num * v.profit),
                formattime(3600 * v.num / v.numhr)}
        if not interactive then
            table.remove(elts, 1)
        end
        tables._row(ret:tag('tr'), elts, false)
        subtext = "\n==" .. mw.text.split(v.name, "<")[1] .. " (" .. v.low .. "-" .. v.high .. ")==\n"
        local subtable = mw.html.create('table'):addClass('wikitable'):css('float', 'right')
        local header = (v.numaction and (v.numaction .. ' x ') or '') .. "''" .. mw.text.split(v.name, "<")[1] .. "'' " .. "[[File:" .. (v.members and "P" or "F") .. "2P icon.png|20px]] — " .. "[[File:" .. skill .. ".png|20px]] " .. v.level
        local input = ""
        for _, w in ipairs(v.input) do
            cost = ""
            if prices[w[1]] then
                cost = "<br/>(" .. commas(v.num * prices[w[1]] * (w[2] or 1)) .. ")"
            end
            input = input .. ("\n*" .. commas(v.num * (w[2] or 1)) .. " x [[" .. w[1] .. "]]" .. cost)
        end
        local output = ""
        for _, w in ipairs(v.output) do
            cost = ""
            if prices[w[1]] then
                cost = "<br/>(" .. commas(v.num * prices[w[1]] * (w[2] or 1)) .. ")"
            end
            output = output .. ("\n*" .. commas(v.num * (w[2] or 1)) .. " x [[" .. w[1] .. "]]" .. cost)
        end
        output = tostring(mw.html.create('ul'):wikitext(output))
        local tabledata = {{{tag = 'th', text = header, attr = {colspan = 4}}},
            {{tag = 'th', text = "Input", attr = {colspan = 2}}, {tag = 'th', text = "Output", attr = {colspan = 2}}},
            {{tag = 'td', text = input, attr = {colspan = 2}}, {tag = 'td', text = output, attr = {colspan = 2}}},
            {{tag = 'th', text = 'Practicality ' .. explain("An estimate of how well this method actually works, compared to how it looks on the guide. 0 is bad, 10 is good.")}, {tag = 'td', text = v.practicality}, {tag = 'th', text = 'Coins/XP'}, {tag = 'td', text = coins(v.profit / v.xp)}},
            {{tag = 'th', text = 'XP/Hour'}, {tag = 'td', text = commas(v.numhr * v.xp)}, {tag = 'th', text = 'Profit/Hour'}, {tag = 'td', text = coins(v.profit * v.numhr)}},
            {{tag = 'th', text = 'Number made'}, {tag = 'td', text = commas(v.num)}, {tag = 'th', text = 'Training time ' .. explain("How much time you will spend actually gaining experience with this training method to reach your goal")}, {tag = 'td', text = formattime(3600 * v.num / v.numhr)}},
            {{tag = 'th', text = 'Profit'}, {tag = 'td', text = coins(v.num * v.profit)}, {tag = 'th', text = '"Money" time ' .. explain("How much time you will spend making money to pay for this training method, given your time value. A negative number means you make money")}, {tag = 'td', text = formattime(-3600 * v.num * v.profit / money)}},
            {{tag = 'th', text = '[[Efficiency|Adjusted Coins/XP]]'}, {tag = 'td', text = coins((v.numhr * v.profit - money) / (v.xp * v.numhr))}, {tag = 'th', text = '"Total" time ' .. explain("The total time you will spend on this method, including the time it takes to make money to pay for it")}, {tag = 'td', text = formattime(3600 * (v.num / v.numhr - v.num * v.profit / money))}}}
        tables._table(subtable, tabledata)
        subtext = subtext .. tostring(subtable) .. "\n[[File:" .. mw.text.split(v.name, "<")[1] .. " detail.png|left|80x80px]]\n" .. v.details.. tostring(mw.html.create('div'):css('clear', 'both'))
        guidetext = guidetext .. subtext
    end
    local elts = {'', '', '', '', '', '', commas(string.format("%.2f", totalprofit / totalxp)), '', commas(math.floor(totalxp / (totaltime / 3600))), commas(math.floor(totalprofit / (totaltime / 3600))), '' , '', commas(totalprofit), formattime(totaltime)}
    if not interactive then
        table.remove(elts, 1)
    end
    tables._row(ret:tag('tr'), elts, true)
    return tostring(ret) .. tostring(excludedtable) .. guidetext
end

function formattime(arg)
    local minus = ""
    if arg < 0 then
        minus = "-"
        arg = -arg
    end
    return minus .. math.floor(arg / 3600) .. ':' .. string.format("%02d", math.floor((arg % 3600) / 60)) .. ':' .. string.format("%02d", arg % 60)
end

-- please tell me there's a better way to do this
function inarray(array, elt)
    for _, v in ipairs(array) do
        if v == elt then return true end
    end
    return false
end

function getprofit(v, prices)
    local profit = 0
    for _, w in ipairs(v.input) do
        if prices[w[1]] then
            profit = profit - (w[2] or 1) * prices[w[1]]
        elseif type(w) == 'number' then
            profit = profit - w
        end
    end
    if not v.output then
        v.output = {{mw.text.split(v.name, "<")[1], v.numaction or 1}}
    end
    for _, w in ipairs(v.output) do
        if prices[w[1]] then
            profit = profit + (w[2] or 1) * prices[w[1]]
        elseif type(w) == 'number' then
            profit = profit + w
        end
    end
    return profit
end

function explain(message)
    local ret = mw.html.create('span'):css({['color'] = "#15f", ['border-bottom'] = "dotted 1px black", ['cursor'] = 'help'}):attr("title", message):wikitext("?")
    return "[" .. tostring(ret) .. "]"
end
return p
Advertisement