Module:Mmgtable/displaysandbox: Difference between revisions

From RuneRealm Wiki
Jump to navigation Jump to search
Content added Content deleted
(Created page with "-- <nowiki> local timefunc = require('Module:Time') local exg = require('Module:Exchange')._price local p = {} local lang = mw.getContentLanguage() function gep(x) return exg(x, 1, nil, nil, 0) end local MEMBERS_ICON = { [false] = "20px|center|link=Free-to-play", [true] = "20px|center|link=Members" } function round1k(x, f) if not tonumber(x) then return x end local _x = math.abs(x) _x = 1000 * mat...")
 
No edit summary
Tag: Reverted
Line 1: Line 1:
-- <nowiki>
local timefunc = require('Module:Time')
local exg = require('Module:Exchange')._price
local p = {}
local p = {}

--imports
local gePrice = require('Module:Exchange')._price
local yn = require('Module:Yesno')
local round = require('Module:Number')._round
local _coins = require('Module:Currency')._amount
local vdf = mw.ext.VariablesLua.vardefine
local lang = mw.getContentLanguage()
local lang = mw.getContentLanguage()
local title = mw.title.getCurrentTitle()

local onmain = require('Module:Mainonly').on_main
function gep(x)
function expr(x)
return exg(x, 1, nil, nil, 0)
local e_g, e = pcall(mw.ext.ParserFunctions.expr, x)
if e_g then
return e
end
return nil
end
end
function sigfig(x, p)

local MEMBERS_ICON = {
local x_sign = x < 0 and '-1' or '1'
local x = math.abs(x)
[false] = "[[File:Free-to-play icon.png|20px|center|link=Free-to-play]]",
if x == 0 then
[true] = "[[File:Member icon.png|20px|center|link=Members]]"
return 0
}

function round1k(x, f)
if not tonumber(x) then
return x
end
end
local _x = math.abs(x)
local n = math.floor(math.log10(x)) + 1 - p
_x = 1000 * math.floor(_x / 1000 + 0.5)
return tonumber(x_sign) * math.pow(10,n) * round(x/math.pow(10, n), 0)
if x < 0 then
_x = _x * -1
end
if f then
return lang:formatNum(_x)
end
return _x
end
end
function round1dp(x, f)
function autoround(x, f)
if not tonumber(x) then
x = tonumber(x) or 0
local _x
return x
if x < 0.1 and x > -0.1 then
end
local _x = math.abs(x)
_x = sigfig(x,2)
elseif x >= 100 or x <= -100 then
_x = math.floor(_x * 10 + 0.5) / 10
_x = round(x, 0)
if x < 0 then
else
_x = _x * -1
_x = round(x, 2)
end
end
if f then
if f then
Line 42: Line 41:
return _x
return _x
end
end
-- Config constants, change as needed
MAX_INPUTS = 75
MAX_OUTPUTS = 75
MAX_XP = 75


function calc_value(args, kph)
function p.testmmgtable(args)
return p._mmgtable(mw.getCurrentFrame(), args)
local total = 0
for i,v in ipairs(args) do
local val
if v.pricetype == 'value' then
val = v.qty * v.value
elseif v.pricetype == 'gemw' then
val = gep(v.name) * v.qty
end
if kph>0 and not v.isph then
val = val * kph
end
total = total + val
end
return total
end
end


function make_row(fullpagename, raw_data, ismulti)
function p.mmgtable(frame)
local data = mw.text.jsonDecode(mw.text.decode(raw_data))
local args = frame:getParent().args
return p._mmgtable(frame, args)
local tr = mw.html.create('tr')
local pagename, pagelink
pagelink = fullpagename..'|'..string.match(fullpagename, '/(.*)')
if ismulti and data.version then
pagename = '[['..pagelink..' ('..data.version..')]]'
else
pagename = '[['..pagelink..']]'
end
local rowcat = data.category
if data.skillcategory then
rowcat = rowcat .. '/' .. data.skillcategory
end
local val, c_class
local inputval = calc_value(data.inputs, data.prices.default_kph or 0)
local outputval = calc_value(data.outputs, data.prices.default_kph or 0)
val = outputval - inputval
if val > 0 then
c_class = 'coins-pos'
elseif val < 0 then
c_class = 'coins-neg'
else
c_class = ''
end
local intensity = (data.intensity == 'High') and 3 or (data.intensity == 'Moderate') and 2 or (data.intensity == 'Low') and 1 or 0

tr :addClass('mmg-list-table-row')
:tag('td')
:wikitext(pagename)
:css('max-width', '300px')
:css('word-wrap', 'break-word')
:done()
:tag('td')
:addClass('mmg-list-table-profit-cell')
:tag('span')
:addClass('coins')
:addClass(c_class)
:wikitext(round1k(val, true))
:done()
:done()
:tag('td')
:addClass('mmg-list-table-kph-cell')
:wikitext(data.prices.default_kph)
:css('max-width', '125px')
:css('min-width', '125px')
:done()
:tag('td')
:addClass('plainlist')
:newline()
:wikitext(data.skill)
:css('max-width', '400px')
:css('word-wrap', 'break-word')
:done()
:tag('td')
:wikitext(rowcat)
:done()
:tag('td')
:wikitext(data.intensity)
:attr('data-sort-value', intensity)
:done()
:tag('td')
:wikitext(MEMBERS_ICON[data.members])
:done()
return val, data.category, tr
end
end


-- Create an MMG table.
function p.main(frame)
-- Frame is the frame the module was invoked from.
local args = frame:getParent().args
-- Args are the template arguments used when creating the table.
local query = {'[[MMG JSON::+]]', '?MMG JSON', '?=#', limit=10000}
function p._mmgtable(frame, args)
if args[1] then
local parsedInput = handleInputs(args)
table.insert(query, 2, '[[Category:'..args[1]..']]')
local parsedOutput = handleOutputs(args)
end
local data = mw.smw.ask(query)
local parsedXP = handleXP(args)
local is_per_kill = yn(args.isperkill)
local ret = mw.html.create('')
mw.logObject(parsedInput)
local t = mw.html.create('table')
mw.logObject(parsedOutput)
mw.logObject(parsedXP)
t :addClass('wikitable sortable sticky-header align-right-2 align-center-5 align-center-6 align-center-7 mmg-list-table')
local tbl = ret:tag('table'):addClass('wikitable mmg-table')
:cssText('width:100%;text-align:center;')
:tag('tr')
:tag('th')
:tag('caption')
:wikitext('Method')
:wikitext(args['Activity'])
:css('word-wrap', 'break-word')
:css('max-width', '300px')
:done()
:done()
:tag('th')
:tag('tr')
:wikitext('Hourly Profit')
:tag('th')
:css('max-width', '100px')
:wikitext('Requirements')
:done()
:tag('td')
:attr('rowspan', '9')
:wikitext(args['Image'] or '{{{Image}}}')
:done()
:done()
:done()
:tag('th')
:tag('tr')
:wikitext('Actions per hour')
:tag('th')
:addClass('unsortable')
:wikitext('Skills')
:done()
:done()
:done()
:tag('th')
:tag('tr')
:wikitext('Skills')
:tag('td')
:css('word-wrap', 'break-word')
:addClass('plainlist')
:newline()
:css('max-width', '400px')
:wikitext(args['Skill'] or 'None') -- Can leave blank if no reqs.
:addClass('unsortable')
:done()
:done()
:done()
:tag('th')
:tag('tr')
:wikitext('Category')
:tag('th')
:wikitext('Items')
:done()
:done()
:done()
:tag('th')
:tag('tr')
:wikitext('Intensity')
:tag('td')
:css('width', '65px')
:addClass('plainlist')
:newline()
:wikitext(args['Item'] or 'None') -- Can leave blank if no reqs.
:done()
:done()
:done()
:tag('th')
:tag('tr')
:wikitext('Members')
:tag('th')
:css('width', '65px')
:wikitext('Quest')
:done()
:done()
:tag('tr')
:tag('td')
:addClass('plainlist')
:newline()
:wikitext(args['Quest'] or 'None') -- Can leave blank if no reqs
:done()
:done()
:tag('tr')
:tag('th')
:wikitext('Other')
:done()
:done()
:tag('tr')
:tag('td')
:addClass('plainlist')
:newline()
:wikitext(args['Other'] or 'None') -- Can leave blank if no reqs
:done()
:done()
:tag('tr')
:tag('th')
:attr('colspan', '2')
:wikitext('Results')
:done()
:done()
:tag('tr')
:tag('th')
:wikitext('Profit')
:done()
:tag('th')
:wikitext('Experience gained')
:done()
:done()
:done()
local methods = {}
local xpTR = tbl:tag('tr')
local profitTD = xpTR:tag('td')
for i,v in ipairs(data) do
profitTD:addClass('mmg-varieswithkph')
if(string.find(v[1], 'Money making guide/')) then
:attr({['data-mmg-cost-ph'] = parsedOutput.valueph-parsedInput.valueph, ['data-mmg-cost-pk'] = parsedOutput.valuepk-parsedInput.valuepk})
if type(v['MMG JSON']) == 'table' then
:wikitext(_coins(autoround(parsedOutput.value - parsedInput.value), 'coins'))
for j,u in ipairs(v['MMG JSON']) do
table.insert(methods, { make_row(v[1], u, true) })
local xpTD = xpTR:tag('td')
end
if args['Other Benefits'] then
else
xpTD:newline():wikitext(args['Other Benefits']):addClass('plainlist')
table.insert(methods, { make_row(v[1], v['MMG JSON'], false) })
elseif #parsedXP.spans > 0 then
end
for i,v in ipairs(parsedXP.spans) do
xpTD:node(v)
end
end
else
xpTD = tbl:wikitext('None')
end
end
table.sort(methods, function(a,b) return a[1]>b[1] end)
local putsTRH = tbl:tag('tr')
for i,v in ipairs(methods) do
local inputTH = putsTRH:tag('th')
if v[1] > 0 then
inputTH:wikitext('Inputs')
t:newline():node(v[3])
if parsedInput.value ~= 0 then
inputTH:wikitext(' (')
:tag('span')
:addClass('mmg-varieswithkph')
:attr({['data-mmg-cost-ph'] = parsedInput.valueph, ['data-mmg-cost-pk'] = parsedInput.valuepk})
:wikitext(_coins(autoround(parsedInput.value), 'coins'))
:done()
:wikitext(')')
end
local outputTH = putsTRH:tag('th')
outputTH:wikitext('Outputs')
if parsedOutput.value ~= 0 then
outputTH:wikitext(' (')
:tag('span')
:addClass('mmg-varieswithkph')
:attr({['data-mmg-cost-ph'] = parsedOutput.valueph, ['data-mmg-cost-pk'] = parsedOutput.valuepk})
:wikitext(_coins(autoround(parsedOutput.value), 'coins'))
:done()
:wikitext(')')
end
local inputTR = tbl:tag('tr')
local inputTD = inputTR:tag('td')
local outputTD = inputTR:tag('td')
for i,v in ipairs(parsedInput.spans) do
inputTD:node(v)
end
for i,v in ipairs(parsedOutput.spans) do
outputTD:node(v)
end
local kph_text = args['kph name'] or 'Kills per hour'
if is_per_kill then
tbl:addClass('mmg-isperkill')
:attr('data-default-kph', args.kph)
:attr('data-default-kph-name', kph_text)
end
if not(yn(args.noexports)) then
if is_per_kill then
vdf('kph', string.format('<span class="mmg-variable mmg-kph">%s</span>', args.kph))
vdf('default_kph', args.kph)
vdf('inputPH', string.format('<span class="mmg-variable mmg-input-ph" data-mmg-cost-ph="%s">%s</span>', parsedInput.valueph, _coins(autoround(parsedInput.valueph), 'nocoins')))
vdf('inputPK', string.format('<span class="mmg-variable mmg-input-pk" data-mmg-cost-pk="%s">%s</span>', parsedInput.valuepk, _coins(autoround(parsedInput.valuepk), 'nocoins')))
vdf('input', string.format('<span class="mmg-variable mmg-input" data-mmg-cost-ph="%s", data-mmg-cost-pk="%s">%s</span>', parsedInput.valueph, parsedInput.valuepk, _coins(autoround(parsedInput.value), 'nocoins')))
vdf('outputPH', string.format('<span class="mmg-variable mmg-output-ph" data-mmg-cost-ph="%s">%s</span>', parsedOutput.valueph, _coins(autoround(parsedOutput.valueph), 'nocoins')))
vdf('outputPK', string.format('<span class="mmg-variable mmg-output-pk" data-mmg-cost-pk="%s">%s</span>', parsedOutput.valuepk, _coins(autoround(parsedOutput.valuepk), 'nocoins')))
vdf('output', string.format('<span class="mmg-variable mmg-varieswithkph mmg-output" data-mmg-cost-ph="%s", data-mmg-cost-pk="%s">%s</span>', parsedOutput.valueph, parsedOutput.valuepk, _coins(autoround(parsedOutput.value), 'nocoins')))
vdf('profitPH', string.format('<span class="mmg-variable mmg-profit-ph" data-mmg-cost-ph="%s">%s</span>', parsedOutput.valueph-parsedInput.valueph, _coins(autoround(parsedOutput.valueph-parsedInput.valueph), 'nocoins')))
vdf('profitPK', string.format('<span class="mmg-variable mmg-profit-pk" data-mmg-cost-pk="%s">%s</span>', parsedOutput.valuepk-parsedInput.valuepk, _coins(autoround(parsedOutput.valuepk-parsedInput.valuepk), 'nocoins')))
vdf('profit', string.format('<span class="mmg-variable mmg-varieswithkph mmg-profit" data-mmg-cost-ph="%s", data-mmg-cost-pk="%s">%s</span>', parsedOutput.valueph-parsedInput.valueph, parsedOutput.valuepk-parsedInput.valuepk, _coins(autoround(parsedOutput.value-parsedInput.value), 'nocoins')))
else
vdf('input', string.format('<span class="mmg-input">%s</span>', parsedInput.value, _coins(autoround(parsedInput.value), 'nocoins')))
vdf('output', string.format('<span class="mmg-input">%s</span>', parsedOutput.value, _coins(autoround(parsedOutput.value), 'nocoins')))
vdf('profit', string.format('<span class="mmg-input">%s</span>', parsedOutput.value-parsedInput.value, _coins(autoround(parsedOutput.value-parsedInput.value), 'nocoins')))
vdf('input_raw', parsedInput.value)
vdf('output_raw', parsedOutput.value)
vdf('profit_raw', parsedOutput.value-parsedInput.value)
end
end
end
end
return t
frame:callParserFunction('DISPLAYTITLE', title.subpageText)
frame:callParserFunction('DEFAULTSORT', title.subpageText)
local cats = '[[Category:Money making guides]]'
if args['Members'] == nil then
cats = cats .. '[[Category:Money making guides without a membership status]]'
elseif not(yn(args['Members'])) then
cats = cats .. '[[Category:MMG/F2P]]'
end
local final_profit = parsedOutput.value - parsedInput.value
local set_smw = true
if final_profit <= 100000 and yn(args['Members']) then
set_smw = false
cats = cats .. '[[Category:Obsolete money making guides]]'
elseif ((final_profit <= 20000) and (parsedInput.value>0) ) then
set_smw = false
cats = cats .. '[[Category:Obsolete money making guides]]'
elseif final_profit <= 15000 then
set_smw = false
cats = cats .. '[[Category:Obsolete money making guides]]'
elseif yn(args.Exclude) then
set_smw = false
cats = cats .. '[[Category:Obsolete money making guides]]'
end
if args["Category"] then
local mmgcat = ({
["Combat/Low"] = "[[Category:MMG/Combat]]",
["Combat/Mid"] = "[[Category:MMG/Combat]]",
["Combat/High"] = "[[Category:MMG/Combat]]",
["Combat"] = "[[Category:MMG/Combat]]",
["Skilling"] = "[[Category:MMG/Skilling]]",
["Processing"] = "[[Category:MMG/Processing]]",
["Recurring"] = "[[Category:MMG/Recurring]]",
["Collecting"] = "[[Category:MMG/Collecting]]"
})[args["Category"]]
if mmgcat == nil then
cats = cats .. '[[Category:Money making guides with an invalid category]]'
else
cats = cats .. mmgcat
end
else
cats = cats .. '[[Category:Money making guides without a category]]'
end
if not onmain() then
cats = ''
end
if set_smw then
local smw_data = {
members = yn(args.Members or 'yes', true),
skill = args.Skill,
activity = args.Activity,
category = args.Category,
skillcategory = args.SkillCategory,
intensity = args.Intensity,
isperkill = is_per_kill,
version = args.Version,
inputs = parsedInput.list,
outputs = parsedOutput.list
}
if is_per_kill then
smw_data.prices = {
input_perhour=parsedInput.valueph,
input_perkill=parsedInput.valuepk,
output_perhour=parsedOutput.valueph,
output_perkill=parsedOutput.valuepk,
default_kph=tonumber(args.kph) or 1,
kph_text=kph_text,
default_value=parsedOutput.value - parsedInput.value,
}
else
smw_data.prices = {
input=parsedInput.value,
output=parsedOutput.value,
value=parsedOutput.value - parsedInput.value
}
end
smw_data = mw.text.killMarkers(mw.text.nowiki(mw.text.jsonEncode(smw_data)))
mw.smw.set({
['MMG value']=parsedOutput.value - parsedInput.value,
['MMG JSON']=smw_data
})
end
return ret, cats
end
end


-- Calculate the profit and do nothing else.
function p.profit(frame)
local frame = frame or mw.getCurrentFrame()
local args = frame:getParent().args -- Template args, NOT #invoke args
return handleOutputs(args).value - handleInputs(args).value
end


-- Implements handleInputs and handleOutputs
-- See those functions for further details
function handleIteratedArgs(args, prefix, max_iters)
local frame = mw.getCurrentFrame()
local items = {}
local total_item_value = 0
local textlines = {}
local is_per_kill = yn(args.isperkill)
local defaultKPH = tonumber(args.kph) or 1
local value_per_kill = 0
local value_per_hour = 0
for i=1,max_iters,1 do
if not args[prefix..i] then break end
local pri = prefix..i
local span = mw.html.create('span')
span:addClass('mmg-itemline mmg-'..prefix:lower())
local name = args[pri]
local qty_param = args[pri..'num']
local actual_qty = nil
local value_param = args[pri..'value']
local actual_value = nil
local is_per_hour = not is_per_kill
if is_per_kill and yn(args[pri..'isph']) then
is_per_hour = true
end
-- Keep track of sanity check states - we want to handle them gracefully later.
local invalid_qty_present = false
local invalid_value_present = false
local failed_ge_lookup = false
local pricetype = ''
if qty_param then
actual_qty = tonumber(qty_param) or expr(qty_param)
invalid_qty_present = not actual_qty
actual_qty = actual_qty or 1
-- If the given quantity doesn't look like a number, we'll default to 1
-- but we should probably alert the user
-- since they might want to fix that
else
-- Default value of 1
actual_qty = 1
end
if value_param then
-- Again, if it was specified, it should be a number
-- If it isn't, we pretend it wasn't specified
-- but we alert the user because it's probably not what they want
actual_value = tonumber(value_param) or expr(value_param)
invalid_value_present = not actual_value
pricetype = 'value'
end
-- If we got the value earlier, skip this part
if not actual_value then
-- Here we try to find an exchange price
-- If we get here, and we can't get an exchange price
-- we default to 0.
-- This is almost certainly not what the user wants,
-- so we warn them about it.
local success, price = pcall(gePrice, name)
actual_value = success and tonumber(price) -- This is awful but still pleasant somehow
failed_ge_lookup = not actual_value
actual_value = actual_value or 0
pricetype = 'gemw'
end
local this_item_value, this_item_qty, attrName
local attrVal = actual_qty * actual_value
if is_per_kill and not is_per_hour then
span:addClass('mmg-varieswithkph')
this_item_qty = actual_qty * defaultKPH
this_item_value = attrVal * defaultKPH
value_per_kill = value_per_kill + attrVal
attrName = 'data-mmg-cost-pk'
else
this_item_qty = actual_qty
this_item_value = attrVal
value_per_hour = value_per_hour + attrVal
attrName = 'data-mmg-cost-ph'
end
total_item_value = total_item_value + this_item_value
span:tag('span'):addClass('mmg-quantity'):attr('data-mmg-qty', actual_qty):wikitext(autoround(this_item_qty, true))
if invalid_qty_present then
span:node(warning('Could not interpret \''..qty_param..'\' as a number, defaulting to 1'))
end
span:wikitext(string.format(' × [[File:%s.png|link=%s]] [[%s]] (', name, name, name))
span:tag('span'):addClass('mmg-cost'):attr(attrName, attrVal):wikitext(_coins(autoround(this_item_value), 'nocoins'))
span:wikitext(')')
if invalid_value_present then
span:node(warning('Could not interpret \''..value_param..'\' as a number, ignoring.'))
end
if failed_ge_lookup then
span:node(warning('Could not find exchange price for item \''..name..'\', please double-check the spelling'))
span:wikitext('[[Category:Money making guides with a failed GE lookup]]')
end
if args[pri..'note'] then
span:tag('span'):addClass('mmg-note'):wikitext(' ',args[pri..'note'])
end
table.insert(textlines, span)
table.insert(items, {name = name, qty = actual_qty, value = actual_value, isph = is_per_hour, pricetype=pricetype})
end
return {value = total_item_value, valuepk = value_per_kill, valueph = value_per_hour, spans = textlines, list = items}
end


-- args are the args supplied to the template, (or a subset of them contining all input arguments)
function make_rec_row(fullpagename, raw_data, ismulti)
-- Returns a table. The table has three keys: 'value', 'text', and 'as_table'
local data = mw.text.jsonDecode(mw.text.decode(raw_data))
---- 'value' contains the total value of the inputs specified by args
local tr = mw.html.create('tr')
---- 'text' contains a formatted string based on the inputs specified by args. This can be directly plugged into the HTML table.
local pagename, pagelink
---- 'list' contains all the inputs as a Lua list. Each input is represented by a table with the following keys
pagelink = fullpagename..'|'..string.match(fullpagename, '/(.*)')
------ 'name' being the name of the item
if ismulti and data.version then
------ 'value' being the value of the item in question
pagename = '[['..pagelink..' ('..data.version..')]]'
------ 'qty' being the quantity specified for the item
else
function handleInputs(args)
pagename = '[['..pagelink..']]'
return handleIteratedArgs(args, 'Input', MAX_INPUTS)
end

-- args are the args supplied to the template, (or a subset of them contining all output arguments)
-- Returns a table. The table has two keys: 'value', and 'text'
---- 'value' contains the total value of the outputs specified by args
---- 'text' contains a formatted string based on the outputs specified by args. This can be directly plugged into the HTML table.
---- 'list' contains all the outputs as a Lua list. Each output is represented by a table with the following keys
------ 'name' being the name of the item
------ 'value' being the value of the item in question
------ 'qty' being the quantity specified for the item
function handleOutputs(args)
return handleIteratedArgs(args, 'Output', MAX_OUTPUTS)
end


function handleXP(args)
local frame = mw.getCurrentFrame()
local items = {}
local textlines = {}
local is_per_kill = yn(args.isperkill)
local defaultKPH = tonumber(args.kph) or 1
for i=1,MAX_XP,1 do
if not args['Experience'..i] then break end
local pri = 'Experience'..i
local span = mw.html.create('span')
span:addClass('mmg-xpline')
local skill = args[pri]
local qty_param = args[pri..'num']
local actual_qty = tonumber(qty_param) or expr(qty_param) or 0
local is_per_hour = not is_per_kill
if is_per_kill and yn(args[pri..'isph']) then
is_per_hour = true
end
local this_item_value, attrName
if is_per_kill and not is_per_hour then
span:addClass('mmg-varieswithkph')
this_item_value = actual_qty * defaultKPH
attrName = 'data-mmg-xp-pk'
else
this_item_value = actual_qty
attrName = 'data-mmg-xp-ph'
end
-- TODO modulise SCP
span:attr(attrName, actual_qty)
:wikitext(frame:expandTemplate{title='SCP', args = { skill, autoround(this_item_value, true) }})
table.insert(textlines, span)
table.insert(items, { skill = skill, xp = actual_qty, isph = is_per_hour })
end
end
return { spans = textlines, items = items }
local val = calc_value(data.outputs, 0) - calc_value(data.inputs, 0)
end
local c_class

if val > 0 then
-- Creates a neat little warning message
c_class = 'coins-pos'
function warning(msg)
elseif val < 0 then
return mw.html.create('span')
c_class = 'coins-neg'
:addClass('mmg-warning')
:css({
['border-bottom'] = '1px red dotted',
color = 'red',
cursor = 'help'
})
:attr('title', msg)
:wikitext('*')
end

function p.recurringTable(frame)
return p._recurringTable(frame, frame:getParent().args)
end

function p._recurringTable(frame, args)
local parsedInput = handleInputs(args)
local parsedOutput = handleOutputs(args)
local timeAsString = nil
local num_good, numMinutes = pcall(mw.ext.ParserFunctions.expr, args['Activity Time'])
numMinutes = tonumber(numMinutes)
if not num_good or not numMinutes then
numMinutes = 1
end
if numMinutes < 1 then
local seconds = numMinutes * 60
timeAsString = seconds..' '..lang:plural(seconds, 'second', 'seconds')
else
else
timeAsString = numMinutes..' '..lang:plural(numMinutes, 'minute', 'minutes')
c_class = ''
end
end
local tbl = mw.html.create('table')
tr :tag('td')
local inputTR = mw.html.create('tr')
:wikitext(pagename)
local inputTD = inputTR:tag('td')
local outputTD = inputTR:tag('td')
for i,v in ipairs(parsedInput.spans) do
inputTD:node(v)
end
for i,v in ipairs(parsedOutput.spans) do
outputTD:node(v)
end

-- This is one statement, defining a single variable. It goes on for 120 lines. I don't know how I feel about this tbh.
-- I mean how the heck would you even document this, for a start?
tbl:addClass('wikitable') -- I guess it's pretty self-documenting if you know HTML
:cssText('width:100%;text-align:center;') -- But like...
:tag('tr')
:tag('caption')
:wikitext(args['Activity'])
:done()
:done()
:done()
:tag('td')
:tag('tr')
:attr('data-sort-value', val)
:tag('th')
:tag('span')
:wikitext('Profit per instance')
:addClass('coins')
:done()
:addClass(c_class)
:tag('td')
:wikitext(round1k(val, true))
:attr('rowspan', '8')
:wikitext(args['Image'])
:done()
:done()
:done()
:done()
:tag('td')
:tag('tr')
:tag('td')
:wikitext(timefunc._m_to_c(tostring(data.time)))
:wikitext(_coins(round(parsedOutput.value - parsedInput.value, 0), 'coins'), ' per instance')
:done()
:done()
:done()
:tag('td')
:tag('tr')
:tag('th')
:attr('data-sort-value', val*60/data.time)
:tag('span')
:wikitext('Activity time')
:addClass('coins')
:addClass(c_class)
:wikitext(round1k(val*60/data.time, true))
:done()
:done()
:done()
:done()
:tag('td')
:tag('tr')
:tag('td')
:attr('data-sort-value', timefunc._w_to_c(tostring(data.recurrence)))
:wikitext(timefunc._w_to_c(tostring(data.recurrence)))
:wikitext(timeAsString)
:done()
:done()
:done()
:tag('td')
:tag('tr')
:addClass('plainlist')
:tag('th')
:wikitext('Minimum recurrence time')
:newline()
:wikitext(data.skill)
:done()
:done()
:done()
:tag('td')
:tag('tr')
:wikitext(data.category)
:tag('td')
:wikitext(args['Recurrence Time'])
:done()
:done()
:done()
:tag('td')
:tag('tr')
:tag('th')
:wikitext(MEMBERS_ICON[data.members])
:wikitext('Effective profit')
:done()
:done()
:done()
:tag('tr')
return val, tr
:tag('td')
end
:wikitext(_coins(tostring(parsedOutput.value - parsedInput.value)..' * 60 / ('..args['Activity Time']..') round 0', 'coins') .. ' per hour') -- A bit messy but it should do the job. Assuming Activity Time is a well-behaved number, of course.

:done()
function p.rec(frame)
local data = mw.smw.ask({'[[MMG recurring JSON::+]]', '[[MMG value::+]]', '?MMG recurring JSON', '?=#', limit=10000})
local t = mw.html.create('table')
t :addClass('wikitable sortable sticky-header align-right-2 align-right-3 align-right-4 align-right-5 align-center-7 align-center-8')
:tag('caption')
:wikitext('[[Money making guide|All guides]] &bull; [[Money making guide/Collecting|Collecting]] &bull; [[Money making guide/Combat|Combat]] &bull; [[Money making guide/Processing|Processing]] &bull; [[Money making guide/Skilling|Skilling]] &bull; [[Money making guide/Recurring|Recurring]] &bull; [[Money making guide/Free-to-play|Free-to-play]]')
:done()
:done()
:tag('tr')
:tag('tr')
:tag('th')
:tag('th')
:wikitext('Method')
:wikitext('Skill requirements')
:done()
:done()
:tag('th')
:tag('th')
:wikitext('Profit')
:wikitext('Quest requirements')
:done()
:done()
:done()
:tag('tr')
:tag('td')
:addClass('plainlist')
:newline()
:wikitext(args['Skill'] or 'None')
:done()
:tag('td')
:addClass('plainlist')
:newline()
:wikitext(args['Quest'] or 'None')
:done()
:done()
:tag('tr')
:tag('th')
:tag('th')
:wikitext('Time')
:wikitext('Item requirements')
:done()
:done()
:tag('th')
:tag('th')
:wikitext('Effective<br>profit')
:wikitext('Other requirements')
:done()
:done()
:done()
:tag('tr')
:tag('td')
:addClass('plainlist')
:newline()
:wikitext(args['Item'] or 'None')
:done()
:tag('td')
:addClass('plainlist')
:newline()
:wikitext(args['Other'] or 'None')
:done()
:done()
:tag('tr')
:tag('th')
:tag('th')
:wikitext('Recurrence<br>time')
:wikitext('Experience gained')
:done()
:done()
:tag('th')
:tag('th')
:wikitext('Skills')
:wikitext('Location')
:done()
:done()
:done()
:tag('tr')
:tag('td')
:wikitext(args['Other Benefits'] or 'None')
:done()
:tag('td')
:wikitext(args['Location'] or 'Anywhere') -- Sensible enough as a default
:done()
:done()
:tag('tr')
:tag('th')
:tag('th')
:wikitext('Category')
:wikitext('Inputs')
:wikitext(' (', _coins(round(parsedInput.value, 0), 'coins'), ')')
:done()
:done()
:tag('th')
:tag('th')
:wikitext('Members')
:wikitext('Outputs')
:wikitext(' (', _coins(round(parsedOutput.value, 0), 'coins'), ')')
:css('width', '65px')
:done()
:done()
:done()
:node(inputTR)
local methods = {}
:done()
for i,v in ipairs(data) do
if type(v['MMG recurring JSON']) == 'table' then
frame:callParserFunction('DISPLAYTITLE', title.subpageText)
for j,u in ipairs(v['MMG recurring JSON']) do
frame:callParserFunction('DEFAULTSORT', title.subpageText)
table.insert(methods, { make_rec_row(v[1], u, true) })
end
local cats = '[[Category:Money making guides]][[Category:MMG/Recurring]]'
else
table.insert(methods, { make_rec_row(v[1], v['MMG recurring JSON'], false) })
local final_profit = parsedOutput.value - parsedInput.value
end
local set_smw = true
if final_profit <= 0 then
set_smw = false
cats = cats .. '[[Category:Obsolete money making guides]]'
end
end
table.sort(methods, function(a,b) return a[1]>b[1] end)
if set_smw then
for i,v in ipairs(methods) do
local smw_data = {
t:newline()
members = yn(args.Members or 'yes', true),
t:node(v[2])
skill = args.Skill,
activity = args.Activity,
category = args.Category,
version = args.Version,
time = numMinutes,
recurrence = args['Recurrence Time'],
prices = {
input=parsedInput.value,
output=parsedOutput.value,
value=parsedOutput.value - parsedInput.value
},
inputs = parsedInput.list,
outputs = parsedOutput.list
}
smw_data = mw.text.killMarkers(mw.text.nowiki(mw.text.jsonEncode(smw_data)))
mw.smw.set({
['MMG value']=parsedOutput.value - parsedInput.value,
['MMG recurring JSON']=smw_data
})
end
end
t:newline()
return t
return tbl, cats
end
end




return p
return p
-- </nowiki>

Revision as of 00:12, 17 October 2024

Documentation for this module may be created at Module:Mmgtable/displaysandbox/doc

local p = {}

--imports
local gePrice = require('Module:Exchange')._price
local yn = require('Module:Yesno')
local round = require('Module:Number')._round
local _coins = require('Module:Currency')._amount
local vdf = mw.ext.VariablesLua.vardefine
local lang = mw.getContentLanguage()
local title = mw.title.getCurrentTitle()
local onmain = require('Module:Mainonly').on_main
function expr(x)
	local e_g, e = pcall(mw.ext.ParserFunctions.expr, x)
	if e_g then
		return e
	end
	return nil
end
function sigfig(x, p)
	local x_sign = x < 0 and '-1' or '1'
	local x = math.abs(x)
	if x == 0 then
		return 0
	end
	local n = math.floor(math.log10(x)) + 1 - p
	return tonumber(x_sign) * math.pow(10,n) * round(x/math.pow(10, n), 0)
end
function autoround(x, f)
	x = tonumber(x) or 0
	local _x
	if x < 0.1 and x > -0.1 then
		_x = sigfig(x,2)
	elseif x >= 100 or x <= -100 then
		_x = round(x, 0)
	else
		_x = round(x, 2)
	end
	if f then
		return lang:formatNum(_x)
	end
	return _x
end
-- Config constants, change as needed
MAX_INPUTS = 75
MAX_OUTPUTS = 75
MAX_XP = 75

function p.testmmgtable(args)
	return p._mmgtable(mw.getCurrentFrame(), args)
end

function p.mmgtable(frame)
	local args = frame:getParent().args
	return p._mmgtable(frame, args)
end

-- Create an MMG table.
-- Frame is the frame the module was invoked from.
-- Args are the template arguments used when creating the table.
function p._mmgtable(frame, args)
	local parsedInput = handleInputs(args)
	local parsedOutput = handleOutputs(args)
	local parsedXP = handleXP(args)
	local is_per_kill = yn(args.isperkill)
	local ret = mw.html.create('')
	
	mw.logObject(parsedInput)
	mw.logObject(parsedOutput)
	mw.logObject(parsedXP)
	
	local tbl = ret:tag('table'):addClass('wikitable mmg-table')
			:cssText('width:100%;text-align:center;')
			:tag('caption')
				:wikitext(args['Activity'])
			:done()
			:tag('tr')
				:tag('th')
					:wikitext('Requirements')
				:done()
				:tag('td')
					:attr('rowspan', '9')
					:wikitext(args['Image'] or '{{{Image}}}')
				:done()
			:done()
			:tag('tr')
				:tag('th')
					:wikitext('Skills')
				:done()
			:done()
			:tag('tr')
				:tag('td')
					:addClass('plainlist')
					:newline()
					:wikitext(args['Skill'] or 'None') -- Can leave blank if no reqs.
				:done()
			:done()
			:tag('tr')
				:tag('th')
					:wikitext('Items')
				:done()
			:done()
			:tag('tr')
				:tag('td')
					:addClass('plainlist')
					:newline()
					:wikitext(args['Item'] or 'None') -- Can leave blank if no reqs.
				:done()
			:done()
			:tag('tr')
				:tag('th')
					:wikitext('Quest')
				:done()
			:done()
			:tag('tr')
				:tag('td')
					:addClass('plainlist')
					:newline()
					:wikitext(args['Quest'] or 'None') -- Can leave blank if no reqs
				:done()
			:done()
			:tag('tr')
				:tag('th')
					:wikitext('Other')
				:done()
			:done()
			:tag('tr')
				:tag('td')
					:addClass('plainlist')
					:newline()
					:wikitext(args['Other'] or 'None') -- Can leave blank if no reqs
				:done()
			:done()
			:tag('tr')
				:tag('th')
					:attr('colspan', '2')
					:wikitext('Results')
				:done()
			:done()
			:tag('tr')
				:tag('th')
					:wikitext('Profit')
				:done()
				:tag('th')
					:wikitext('Experience gained')
				:done()
			:done()
			
	local xpTR = tbl:tag('tr')
	local profitTD = xpTR:tag('td')
	profitTD:addClass('mmg-varieswithkph')
		:attr({['data-mmg-cost-ph'] = parsedOutput.valueph-parsedInput.valueph, ['data-mmg-cost-pk'] = parsedOutput.valuepk-parsedInput.valuepk})
		:wikitext(_coins(autoround(parsedOutput.value - parsedInput.value), 'coins'))
	
	local xpTD = xpTR:tag('td')
	if args['Other Benefits'] then
		xpTD:newline():wikitext(args['Other Benefits']):addClass('plainlist')
	elseif #parsedXP.spans > 0 then
		for i,v in ipairs(parsedXP.spans) do
			xpTD:node(v)
		end
	else
		xpTD = tbl:wikitext('None')
	end
	
	local putsTRH = tbl:tag('tr')
	local inputTH = putsTRH:tag('th')
	inputTH:wikitext('Inputs')
	if parsedInput.value ~= 0 then
		inputTH:wikitext(' (')
			:tag('span')
				:addClass('mmg-varieswithkph')
				:attr({['data-mmg-cost-ph'] = parsedInput.valueph, ['data-mmg-cost-pk'] = parsedInput.valuepk})
				:wikitext(_coins(autoround(parsedInput.value), 'coins'))
			:done()
			:wikitext(')')
	end
	
	local outputTH = putsTRH:tag('th')
	outputTH:wikitext('Outputs')
	if parsedOutput.value ~= 0 then
		outputTH:wikitext(' (')
			:tag('span')
				:addClass('mmg-varieswithkph')
				:attr({['data-mmg-cost-ph'] = parsedOutput.valueph, ['data-mmg-cost-pk'] = parsedOutput.valuepk})
				:wikitext(_coins(autoround(parsedOutput.value), 'coins'))
			:done()
			:wikitext(')')
	end
	
	local inputTR = tbl:tag('tr')
	local inputTD = inputTR:tag('td')
	local outputTD = inputTR:tag('td')
	for i,v in ipairs(parsedInput.spans) do
		inputTD:node(v)
	end
	for i,v in ipairs(parsedOutput.spans) do
		outputTD:node(v)
	end
	
	local kph_text = args['kph name'] or 'Kills per hour'
	if is_per_kill then
		tbl:addClass('mmg-isperkill')
			:attr('data-default-kph', args.kph)
			:attr('data-default-kph-name', kph_text)
	end
	
	if not(yn(args.noexports)) then
		if is_per_kill then
			vdf('kph', string.format('<span class="mmg-variable mmg-kph">%s</span>', args.kph))
			vdf('default_kph', args.kph)
			vdf('inputPH', string.format('<span class="mmg-variable mmg-input-ph" data-mmg-cost-ph="%s">%s</span>', parsedInput.valueph, _coins(autoround(parsedInput.valueph), 'nocoins')))
			vdf('inputPK', string.format('<span class="mmg-variable mmg-input-pk" data-mmg-cost-pk="%s">%s</span>', parsedInput.valuepk, _coins(autoround(parsedInput.valuepk), 'nocoins')))
			vdf('input', string.format('<span class="mmg-variable mmg-input" data-mmg-cost-ph="%s", data-mmg-cost-pk="%s">%s</span>', parsedInput.valueph, parsedInput.valuepk, _coins(autoround(parsedInput.value), 'nocoins')))
			vdf('outputPH', string.format('<span class="mmg-variable mmg-output-ph" data-mmg-cost-ph="%s">%s</span>', parsedOutput.valueph, _coins(autoround(parsedOutput.valueph), 'nocoins')))
			vdf('outputPK', string.format('<span class="mmg-variable mmg-output-pk" data-mmg-cost-pk="%s">%s</span>', parsedOutput.valuepk, _coins(autoround(parsedOutput.valuepk), 'nocoins')))
			vdf('output', string.format('<span class="mmg-variable mmg-varieswithkph mmg-output" data-mmg-cost-ph="%s", data-mmg-cost-pk="%s">%s</span>', parsedOutput.valueph, parsedOutput.valuepk, _coins(autoround(parsedOutput.value), 'nocoins')))
			vdf('profitPH', string.format('<span class="mmg-variable mmg-profit-ph" data-mmg-cost-ph="%s">%s</span>', parsedOutput.valueph-parsedInput.valueph, _coins(autoround(parsedOutput.valueph-parsedInput.valueph), 'nocoins')))
			vdf('profitPK', string.format('<span class="mmg-variable mmg-profit-pk" data-mmg-cost-pk="%s">%s</span>', parsedOutput.valuepk-parsedInput.valuepk, _coins(autoround(parsedOutput.valuepk-parsedInput.valuepk), 'nocoins')))
			vdf('profit', string.format('<span class="mmg-variable mmg-varieswithkph mmg-profit" data-mmg-cost-ph="%s", data-mmg-cost-pk="%s">%s</span>', parsedOutput.valueph-parsedInput.valueph, parsedOutput.valuepk-parsedInput.valuepk, _coins(autoround(parsedOutput.value-parsedInput.value), 'nocoins')))
		else
			vdf('input', string.format('<span class="mmg-input">%s</span>', parsedInput.value, _coins(autoround(parsedInput.value), 'nocoins')))
			vdf('output', string.format('<span class="mmg-input">%s</span>', parsedOutput.value, _coins(autoround(parsedOutput.value), 'nocoins')))
			vdf('profit', string.format('<span class="mmg-input">%s</span>', parsedOutput.value-parsedInput.value, _coins(autoround(parsedOutput.value-parsedInput.value), 'nocoins')))
			vdf('input_raw', parsedInput.value)
			vdf('output_raw', parsedOutput.value)
			vdf('profit_raw', parsedOutput.value-parsedInput.value)
		end
	end
	
	frame:callParserFunction('DISPLAYTITLE', title.subpageText)
	frame:callParserFunction('DEFAULTSORT', title.subpageText)
	
	local cats = '[[Category:Money making guides]]'
	
	if args['Members'] == nil then
		cats = cats .. '[[Category:Money making guides without a membership status]]'
	elseif not(yn(args['Members'])) then
		cats = cats .. '[[Category:MMG/F2P]]'
	end
	
	local final_profit = parsedOutput.value - parsedInput.value
	
	local set_smw = true
	if final_profit <= 100000 and yn(args['Members']) then
		set_smw = false
		cats = cats .. '[[Category:Obsolete money making guides]]'
	elseif ((final_profit <= 20000) and (parsedInput.value>0) ) then
		set_smw = false
		cats = cats .. '[[Category:Obsolete money making guides]]'
	elseif final_profit <= 15000 then
		set_smw = false
		cats = cats .. '[[Category:Obsolete money making guides]]'
	elseif yn(args.Exclude) then
		set_smw = false
		cats = cats .. '[[Category:Obsolete money making guides]]'
	end
	
	if args["Category"] then
		local mmgcat = ({
			["Combat/Low"] = "[[Category:MMG/Combat]]",
			["Combat/Mid"] = "[[Category:MMG/Combat]]",
			["Combat/High"] = "[[Category:MMG/Combat]]",
			["Combat"] = "[[Category:MMG/Combat]]",
			["Skilling"] = "[[Category:MMG/Skilling]]",
			["Processing"] = "[[Category:MMG/Processing]]",
			["Recurring"] = "[[Category:MMG/Recurring]]",
			["Collecting"] = "[[Category:MMG/Collecting]]"
		})[args["Category"]]
		if mmgcat == nil then
			cats = cats .. '[[Category:Money making guides with an invalid category]]'
		else
			cats = cats .. mmgcat
		end
	else
		cats = cats .. '[[Category:Money making guides without a category]]'
	end
	
	if not onmain() then
		cats = ''
	end
	
	if set_smw then
		local smw_data = {
			members = yn(args.Members or 'yes', true),
			skill = args.Skill,
			activity = args.Activity,
			category = args.Category,
			skillcategory = args.SkillCategory,
			intensity = args.Intensity,
			isperkill = is_per_kill,
			version = args.Version,
			inputs = parsedInput.list,
			outputs = parsedOutput.list
		}
		if is_per_kill then
			smw_data.prices = {
				input_perhour=parsedInput.valueph,
				input_perkill=parsedInput.valuepk,
				output_perhour=parsedOutput.valueph,
				output_perkill=parsedOutput.valuepk,
				default_kph=tonumber(args.kph) or 1,
				kph_text=kph_text,
				default_value=parsedOutput.value - parsedInput.value,
			}
		else
			smw_data.prices = {
				input=parsedInput.value,
				output=parsedOutput.value,
				value=parsedOutput.value - parsedInput.value
			}
		end
		smw_data = mw.text.killMarkers(mw.text.nowiki(mw.text.jsonEncode(smw_data)))
		mw.smw.set({
			['MMG value']=parsedOutput.value - parsedInput.value,
			['MMG JSON']=smw_data
		})
	end
	
	return ret, cats
end

-- Calculate the profit and do nothing else.
function p.profit(frame)
	local frame = frame or mw.getCurrentFrame()
	local args = frame:getParent().args -- Template args, NOT #invoke args
	
	return handleOutputs(args).value - handleInputs(args).value
end

-- Implements handleInputs and handleOutputs
-- See those functions for further details
function handleIteratedArgs(args, prefix, max_iters)
	local frame = mw.getCurrentFrame()
	local items = {}
	local total_item_value = 0
	local textlines = {}
	local is_per_kill = yn(args.isperkill)
	local defaultKPH = tonumber(args.kph) or 1
	local value_per_kill = 0
	local value_per_hour = 0
	
	for i=1,max_iters,1 do
		if not args[prefix..i] then break end
		local pri = prefix..i
		local span = mw.html.create('span')
		span:addClass('mmg-itemline mmg-'..prefix:lower())
		
		local name = args[pri]
		local qty_param = args[pri..'num']
		local actual_qty = nil
		local value_param = args[pri..'value']
		local actual_value = nil
		local is_per_hour = not is_per_kill
		if is_per_kill and yn(args[pri..'isph']) then
			is_per_hour = true
		end
		
		-- Keep track of sanity check states - we want to handle them gracefully later.
		local invalid_qty_present = false
		local invalid_value_present = false
		local failed_ge_lookup = false
		local pricetype = ''
		
		if qty_param then
			actual_qty = tonumber(qty_param) or expr(qty_param)
			invalid_qty_present = not actual_qty
			actual_qty = actual_qty or 1
			-- If the given quantity doesn't look like a number, we'll default to 1
			--   but we should probably alert the user
			--   since they might want to fix that
		else
			-- Default value of 1
			actual_qty = 1
		end
		
		if value_param then
			-- Again, if it was specified, it should be a number
			-- If it isn't, we pretend it wasn't specified
			--   but we alert the user because it's probably not what they want
			actual_value = tonumber(value_param) or expr(value_param)
			invalid_value_present = not actual_value
			pricetype = 'value'
		end
		
		-- If we got the value earlier, skip this part
		if not actual_value then
			-- Here we try to find an exchange price
			-- If we get here, and we can't get an exchange price
			-- we default to 0.
			-- This is almost certainly not what the user wants,
			-- so we warn them about it.
			local success, price = pcall(gePrice, name)
			actual_value = success and tonumber(price) -- This is awful but still pleasant somehow
			failed_ge_lookup = not actual_value
			actual_value = actual_value or 0
			pricetype = 'gemw'
		end
		local this_item_value, this_item_qty, attrName
		local attrVal = actual_qty * actual_value
		if is_per_kill and not is_per_hour then
			span:addClass('mmg-varieswithkph')
			this_item_qty = actual_qty * defaultKPH
			this_item_value = attrVal * defaultKPH
			value_per_kill = value_per_kill + attrVal
			attrName = 'data-mmg-cost-pk'
		else
			this_item_qty = actual_qty
			this_item_value = attrVal
			value_per_hour = value_per_hour + attrVal
			attrName = 'data-mmg-cost-ph'
		end
		total_item_value = total_item_value + this_item_value
		
		span:tag('span'):addClass('mmg-quantity'):attr('data-mmg-qty', actual_qty):wikitext(autoround(this_item_qty, true))
		if invalid_qty_present then
			span:node(warning('Could not interpret \''..qty_param..'\' as a number, defaulting to 1'))
		end
		span:wikitext(string.format(' × [[File:%s.png|link=%s]] [[%s]] (', name, name, name))
		span:tag('span'):addClass('mmg-cost'):attr(attrName, attrVal):wikitext(_coins(autoround(this_item_value), 'nocoins'))
		span:wikitext(')')
		if invalid_value_present then
			span:node(warning('Could not interpret \''..value_param..'\' as a number, ignoring.'))
		end
		if failed_ge_lookup then
			span:node(warning('Could not find exchange price for item \''..name..'\', please double-check the spelling'))
			span:wikitext('[[Category:Money making guides with a failed GE lookup]]')
		end
		if args[pri..'note'] then
			span:tag('span'):addClass('mmg-note'):wikitext(' ',args[pri..'note'])
		end
		
		table.insert(textlines, span)
			
		table.insert(items, {name = name, qty = actual_qty, value = actual_value, isph = is_per_hour, pricetype=pricetype})
	end
	
	return {value = total_item_value, valuepk = value_per_kill, valueph = value_per_hour, spans = textlines, list = items}
end

-- args are the args supplied to the template, (or a subset of them contining all input arguments)
-- Returns a table. The table has three keys: 'value', 'text', and 'as_table'
---- 'value' contains the total value of the inputs specified by args
---- 'text' contains a formatted string based on the inputs specified by args. This can be directly plugged into the HTML table.
---- 'list' contains all the inputs as a Lua list. Each input is represented by a table with the following keys
------ 'name' being the name of the item
------ 'value' being the value of the item in question
------ 'qty' being the quantity specified for the item
function handleInputs(args)
	return handleIteratedArgs(args, 'Input', MAX_INPUTS)
end

-- args are the args supplied to the template, (or a subset of them contining all output arguments)
-- Returns a table. The table has two keys: 'value', and 'text'
---- 'value' contains the total value of the outputs specified by args
---- 'text' contains a formatted string based on the outputs specified by args. This can be directly plugged into the HTML table.
---- 'list' contains all the outputs as a Lua list. Each output is represented by a table with the following keys
------ 'name' being the name of the item
------ 'value' being the value of the item in question
------ 'qty' being the quantity specified for the item
function handleOutputs(args)
	return handleIteratedArgs(args, 'Output', MAX_OUTPUTS)
end


function handleXP(args)
	local frame = mw.getCurrentFrame()
	local items = {}
	local textlines = {}
	local is_per_kill = yn(args.isperkill)
	local defaultKPH = tonumber(args.kph) or 1
	
	for i=1,MAX_XP,1 do
		if not args['Experience'..i] then break end
		local pri = 'Experience'..i
		local span = mw.html.create('span')
		span:addClass('mmg-xpline')
		
		local skill = args[pri]
		local qty_param = args[pri..'num']
		local actual_qty = tonumber(qty_param) or expr(qty_param) or 0
		local is_per_hour = not is_per_kill
		if is_per_kill and yn(args[pri..'isph']) then
			is_per_hour = true
		end
		local this_item_value, attrName
		if is_per_kill and not is_per_hour then
			span:addClass('mmg-varieswithkph')
			this_item_value = actual_qty * defaultKPH
			attrName = 'data-mmg-xp-pk'
		else
			this_item_value = actual_qty
			attrName = 'data-mmg-xp-ph'
		end
		
		-- TODO modulise SCP
		span:attr(attrName, actual_qty)
			:wikitext(frame:expandTemplate{title='SCP', args = { skill, autoround(this_item_value, true) }})
		
		table.insert(textlines, span)
		table.insert(items, { skill = skill, xp = actual_qty, isph = is_per_hour })
	end
	
	return { spans = textlines, items = items }
end

-- Creates a neat little warning message
function warning(msg)
	return mw.html.create('span')
				:addClass('mmg-warning')
				:css({
					['border-bottom'] = '1px red dotted',
					color = 'red',
					cursor = 'help'
				})
				:attr('title', msg)
				:wikitext('*')
end

function p.recurringTable(frame)
	return p._recurringTable(frame, frame:getParent().args)	
end

function p._recurringTable(frame, args)
	local parsedInput = handleInputs(args)
	local parsedOutput = handleOutputs(args)
	local timeAsString = nil
	local num_good, numMinutes = pcall(mw.ext.ParserFunctions.expr, args['Activity Time'])
	numMinutes = tonumber(numMinutes)
	if not num_good or not numMinutes then
		numMinutes = 1
	end
	if numMinutes < 1 then
		local seconds = numMinutes * 60
		timeAsString = seconds..' '..lang:plural(seconds, 'second', 'seconds')
	else
		timeAsString = numMinutes..' '..lang:plural(numMinutes, 'minute', 'minutes')
	end
	local tbl = mw.html.create('table')
	local inputTR = mw.html.create('tr')
	local inputTD = inputTR:tag('td')
	local outputTD = inputTR:tag('td')
	for i,v in ipairs(parsedInput.spans) do
		inputTD:node(v)
	end
	for i,v in ipairs(parsedOutput.spans) do
		outputTD:node(v)
	end

	
	-- This is one statement, defining a single variable. It goes on for 120 lines. I don't know how I feel about this tbh.
	 -- I mean how the heck would you even document this, for a start?
	tbl:addClass('wikitable') -- I guess it's pretty self-documenting if you know HTML
		:cssText('width:100%;text-align:center;') -- But like...
		:tag('tr')
			:tag('caption')
				:wikitext(args['Activity'])
			:done()
		:done()
		:tag('tr')
			:tag('th')
				:wikitext('Profit per instance')
			:done()
			:tag('td')
				:attr('rowspan', '8')
				:wikitext(args['Image'])
			:done()
		:done()
		:tag('tr')
			:tag('td')
				:wikitext(_coins(round(parsedOutput.value - parsedInput.value, 0), 'coins'), ' per instance')
			:done()
		:done()
		:tag('tr')
			:tag('th')
				:wikitext('Activity time')
			:done()
		:done()
		:tag('tr')
			:tag('td')
				:wikitext(timeAsString)
			:done()
		:done()
		:tag('tr')
			:tag('th')
				:wikitext('Minimum recurrence time')
			:done()
		:done()
		:tag('tr')
			:tag('td')
				:wikitext(args['Recurrence Time'])
			:done()
		:done()
		:tag('tr')
			:tag('th')
				:wikitext('Effective profit')
			:done()
		:done()
		:tag('tr')
			:tag('td')
				:wikitext(_coins(tostring(parsedOutput.value - parsedInput.value)..' * 60 / ('..args['Activity Time']..') round 0', 'coins') .. ' per hour') -- A bit messy but it should do the job. Assuming Activity Time is a well-behaved number, of course.
			:done()
		:done()
		:tag('tr')
			:tag('th')
				:wikitext('Skill requirements')
			:done()
			:tag('th')
				:wikitext('Quest requirements')
			:done()
		:done()
		:tag('tr')
			:tag('td')
				:addClass('plainlist')
				:newline()
				:wikitext(args['Skill'] or 'None')
			:done()
			:tag('td')
				:addClass('plainlist')
				:newline()
				:wikitext(args['Quest'] or 'None')
			:done()
		:done()
		:tag('tr')
			:tag('th')
				:wikitext('Item requirements')
			:done()
			:tag('th')
				:wikitext('Other requirements')
			:done()
		:done()
		:tag('tr')
			:tag('td')
				:addClass('plainlist')
				:newline()
				:wikitext(args['Item'] or 'None')
			:done()
			:tag('td')
				:addClass('plainlist')
				:newline()
				:wikitext(args['Other'] or 'None')
			:done()
		:done()
		:tag('tr')
			:tag('th')
				:wikitext('Experience gained')
			:done()
			:tag('th')
				:wikitext('Location')
			:done()
		:done()
		:tag('tr')
			:tag('td')
				:wikitext(args['Other Benefits'] or 'None')
			:done()
			:tag('td')
				:wikitext(args['Location'] or 'Anywhere') -- Sensible enough as a default
			:done()
		:done()
		:tag('tr')
			:tag('th')
				:wikitext('Inputs')
				:wikitext(' (', _coins(round(parsedInput.value, 0), 'coins'), ')')
			:done()
			:tag('th')
				:wikitext('Outputs')
				:wikitext(' (', _coins(round(parsedOutput.value, 0), 'coins'), ')')
			:done()
		:done()
		:node(inputTR)
	:done()
	
	frame:callParserFunction('DISPLAYTITLE', title.subpageText)
	frame:callParserFunction('DEFAULTSORT', title.subpageText)
	
	local cats = '[[Category:Money making guides]][[Category:MMG/Recurring]]'
	
	local final_profit = parsedOutput.value - parsedInput.value
	
	local set_smw = true
	if final_profit <= 0 then
		set_smw = false
		cats = cats .. '[[Category:Obsolete money making guides]]'
	end
	
	if set_smw then
		local smw_data = {
			members = yn(args.Members or 'yes', true),
			skill = args.Skill,
			activity = args.Activity,
			category = args.Category,
			version = args.Version,
			time = numMinutes,
			recurrence = args['Recurrence Time'],
			prices = {
				input=parsedInput.value,
				output=parsedOutput.value,
				value=parsedOutput.value - parsedInput.value
			},
			inputs = parsedInput.list,
			outputs = parsedOutput.list
		}
		smw_data = mw.text.killMarkers(mw.text.nowiki(mw.text.jsonEncode(smw_data)))
		mw.smw.set({
			['MMG value']=parsedOutput.value - parsedInput.value,
			['MMG recurring JSON']=smw_data
		})
	end
	
	return tbl, cats
end

return p