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 .. "¤t=" .. 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