Module:Mmgtable/displaysandbox: Difference between revisions
		
		
		
		
		
		Jump to navigation
		Jump to search
		
				
		
		
	
Content added Content deleted
No edit summary Tag: Reverted  | 
				No edit summary Tag: Manual revert  | 
				||
| 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()  | 
  |||
function gep(x)  | 
|||
local onmain = require('Module:Mainonly').on_main  | 
  |||
	return exg(x, 1, nil, nil, 0)  | 
|||
function expr(x)  | 
  |||
	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 = {  | 
|||
    [false] = "[[File:Free-to-play icon.png|20px|center|link=Free-to-play]]",  | 
|||
	local x = math.abs(x)  | 
  |||
    [true]  = "[[File:Member icon.png|20px|center|link=Members]]"  | 
|||
	if x == 0 then  | 
  |||
}  | 
|||
		return 0  | 
  |||
function round1k(x, f)  | 
|||
	if not tonumber(x) then  | 
|||
		return x  | 
|||
	end  | 
  	end  | 
||
	local   | 
  	local _x = math.abs(x)  | 
||
	_x = 1000 * math.floor(_x / 1000 + 0.5)  | 
|||
	if x < 0 then  | 
|||
		_x = _x * -1  | 
|||
	end  | 
|||
	if f then  | 
|||
		return lang:formatNum(_x)  | 
|||
	end  | 
|||
	return _x  | 
|||
end  | 
  end  | 
||
function   | 
  function round1dp(x, f)  | 
||
	if not tonumber(x) then  | 
|||
		return x  | 
|||
	local _x  | 
  |||
	end  | 
|||
	if x < 0.1 and x > -0.1 then  | 
  |||
	local _x = math.abs(x)  | 
|||
	_x = math.floor(_x * 10 + 0.5) / 10  | 
|||
	elseif x >= 100 or x <= -100 then  | 
  |||
	if x < 0 then  | 
|||
		_x = round(x, 0)  | 
  |||
		_x = _x * -1  | 
|||
	else  | 
  |||
		_x = round(x, 2)  | 
  |||
	end  | 
  	end  | 
||
	if f then  | 
  	if f then  | 
||
| Line 41: | Line 42: | ||
	return _x  | 
  	return _x  | 
||
end  | 
  end  | 
||
-- Config constants, change as needed  | 
  |||
MAX_INPUTS = 75  | 
  |||
MAX_OUTPUTS = 75  | 
  |||
MAX_XP = 75  | 
  |||
function   | 
  function calc_value(args, kph)  | 
||
	local total = 0  | 
|||
	return p._mmgtable(mw.getCurrentFrame(), args)  | 
  |||
	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   | 
  function make_row(fullpagename, raw_data, ismulti)  | 
||
	local   | 
  	local data = mw.text.jsonDecode(mw.text.decode(raw_data))  | 
||
	local tr = mw.html.create('tr')  | 
|||
	return p._mmgtable(frame, args)  | 
  |||
	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  | 
||
function p.main(frame)  | 
|||
-- Create an MMG table.  | 
  |||
	local args = frame:getParent().args  | 
|||
-- Frame is the frame the module was invoked from.  | 
  |||
	local query = {'[[MMG JSON::+]]', '?MMG JSON', '?=#', limit=10000}  | 
|||
-- Args are the template arguments used when creating the table.  | 
  |||
	if args[1] then  | 
|||
function p._mmgtable(frame, args)  | 
  |||
		table.insert(query, 2, '[[Category:'..args[1]..']]')  | 
|||
	local parsedInput = handleInputs(args)  | 
  |||
	end  | 
|||
	local parsedOutput = handleOutputs(args)  | 
  |||
	local   | 
  	local data = mw.smw.ask(query)  | 
||
	local is_per_kill = yn(args.isperkill)  | 
  |||
	local ret = mw.html.create('')  | 
  |||
	local t = mw.html.create('table')  | 
|||
	mw.logObject(parsedInput)  | 
  |||
	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')  | 
|||
		:tag('tr')  | 
|||
			:cssText('width:100%;text-align:center;')  | 
  |||
			:tag('  | 
  			:tag('th')  | 
||
				:wikitext(  | 
  				:wikitext('Method')  | 
||
				:css('word-wrap', 'break-word')  | 
|||
				:css('max-width', '300px')  | 
|||
			:done()  | 
  			:done()  | 
||
			:tag('  | 
  			:tag('th')  | 
||
				:  | 
  				:wikitext('Hourly Profit')  | 
||
				:css('max-width', '100px')  | 
|||
				:done()  | 
  |||
				:tag('td')  | 
  |||
					:attr('rowspan', '9')  | 
  |||
					:wikitext(args['Image'] or '{{{Image}}}')  | 
  |||
				:done()  | 
  |||
			:done()  | 
  			:done()  | 
||
			:tag('  | 
  			:tag('th')  | 
||
				:  | 
  				:wikitext('Actions per hour')  | 
||
				:addClass('unsortable')  | 
|||
				:done()  | 
  |||
			:done()  | 
  			:done()  | 
||
			:tag('  | 
  			:tag('th')  | 
||
				:  | 
  				:wikitext('Skills')  | 
||
				:css('word-wrap', 'break-word')  | 
|||
				:css('max-width', '400px')  | 
|||
					:newline()  | 
  |||
				:addClass('unsortable')  | 
|||
					:wikitext(args['Skill'] or 'None') -- Can leave blank if no reqs.  | 
  |||
				:done()  | 
  |||
			:done()  | 
  			:done()  | 
||
			:tag('  | 
  			:tag('th')  | 
||
				:  | 
  				:wikitext('Category')  | 
||
					:wikitext('Items')  | 
  |||
				:done()  | 
  |||
			:done()  | 
  			:done()  | 
||
			:tag('  | 
  			:tag('th')  | 
||
				:  | 
  				:wikitext('Intensity')  | 
||
				:css('width', '65px')  | 
|||
					:newline()  | 
  |||
					:wikitext(args['Item'] or 'None') -- Can leave blank if no reqs.  | 
  |||
				:done()  | 
  |||
			:done()  | 
  			:done()  | 
||
			:tag('  | 
  			:tag('th')  | 
||
				:  | 
  				:wikitext('Members')  | 
||
				:css('width', '65px')  | 
|||
				: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   | 
  	local methods = {}  | 
||
	for i,v in ipairs(data) do  | 
|||
	local profitTD = xpTR:tag('td')  | 
  |||
		if(string.find(v[1], 'Money making guide/')) then  | 
|||
	profitTD:addClass('mmg-varieswithkph')  | 
  |||
			if type(v['MMG JSON']) == 'table' then  | 
|||
		:attr({['data-mmg-cost-ph'] = parsedOutput.valueph-parsedInput.valueph, ['data-mmg-cost-pk'] = parsedOutput.valuepk-parsedInput.valuepk})  | 
  |||
				for j,u in ipairs(v['MMG JSON']) do  | 
|||
		:wikitext(_coins(autoround(parsedOutput.value - parsedInput.value), 'coins'))  | 
  |||
					table.insert(methods, { make_row(v[1], u, true) })  | 
|||
				end  | 
|||
	local xpTD = xpTR:tag('td')  | 
  |||
			else  | 
|||
	if args['Other Benefits'] then  | 
  |||
				table.insert(methods, { make_row(v[1], v['MMG JSON'], false) })  | 
|||
		xpTD:newline():wikitext(args['Other Benefits']):addClass('plainlist')  | 
  |||
			end  | 
|||
	elseif #parsedXP.spans > 0 then  | 
  |||
		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)  | 
|||
	for i,v in ipairs(methods) do  | 
|||
	local putsTRH = tbl:tag('tr')  | 
  |||
		if v[1] > 0 then  | 
|||
	local inputTH = putsTRH:tag('th')  | 
  |||
			t:newline():node(v[3])  | 
|||
	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  | 
||
	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  | 
  |||
function make_rec_row(fullpagename, raw_data, ismulti)  | 
|||
-- args are the args supplied to the template, (or a subset of them contining all input arguments)  | 
  |||
	local data = mw.text.jsonDecode(mw.text.decode(raw_data))  | 
|||
-- Returns a table. The table has three keys: 'value', 'text', and 'as_table'  | 
  |||
	local tr = mw.html.create('tr')  | 
|||
---- 'value' contains the total value of the inputs specified by args  | 
  |||
	local pagename, pagelink  | 
|||
---- 'text' contains a formatted string based on the inputs specified by args. This can be directly plugged into the HTML table.  | 
  |||
	pagelink = fullpagename..'|'..string.match(fullpagename, '/(.*)')  | 
|||
---- 'list' contains all the inputs as a Lua list. Each input is represented by a table with the following keys  | 
  |||
	if ismulti and data.version then  | 
|||
------ 'name' being the name of the item  | 
  |||
		pagename = '[['..pagelink..' ('..data.version..')]]'  | 
|||
------ 'value' being the value of the item in question  | 
  |||
	else  | 
|||
------ 'qty' being the quantity specified for the item  | 
  |||
		pagename = '[['..pagelink..']]'  | 
|||
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  | 
  	end  | 
||
	local val = calc_value(data.outputs, 0) - calc_value(data.inputs, 0)  | 
|||
	return { spans = textlines, items = items }  | 
  |||
	local c_class  | 
|||
end  | 
  |||
	if val > 0 then  | 
|||
		c_class = 'coins-pos'  | 
|||
-- Creates a neat little warning message  | 
  |||
	elseif val < 0 then  | 
|||
function warning(msg)  | 
  |||
		c_class = 'coins-neg'  | 
|||
	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  | 
  	else  | 
||
		c_class = ''  | 
|||
		timeAsString = numMinutes..' '..lang:plural(numMinutes, 'minute', 'minutes')  | 
  |||
	end  | 
  	end  | 
||
	tr	:tag('td')  | 
|||
	local tbl = mw.html.create('table')  | 
  |||
			:wikitext(pagename)  | 
|||
	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()  | 
  		:done()  | 
||
		:tag('  | 
  		:tag('td')  | 
||
			:  | 
  			:attr('data-sort-value', val)  | 
||
			:tag('span')  | 
|||
			:  | 
  				:addClass('coins')  | 
||
			:  | 
  				:addClass(c_class)  | 
||
				:  | 
  				:wikitext(round1k(val, true))  | 
||
				:wikitext(args['Image'])  | 
  |||
			:done()  | 
  			:done()  | 
||
		:done()  | 
  		:done()  | 
||
		:tag('  | 
  		:tag('td')  | 
||
			:wikitext(timefunc._m_to_c(tostring(data.time)))  | 
|||
			:tag('td')  | 
  |||
				:wikitext(_coins(round(parsedOutput.value - parsedInput.value, 0), 'coins'), ' per instance')  | 
  |||
			:done()  | 
  |||
		:done()  | 
  		:done()  | 
||
		:tag('  | 
  		:tag('td')  | 
||
			:attr('data-sort-value', val*60/data.time)  | 
|||
			:tag('th')  | 
  |||
			:tag('span')  | 
|||
				:addClass('coins')  | 
|||
				:addClass(c_class)  | 
|||
				:wikitext(round1k(val*60/data.time, true))  | 
|||
			:done()  | 
  			:done()  | 
||
		:done()  | 
  		:done()  | 
||
		:tag('  | 
  		:tag('td')  | 
||
			:attr('data-sort-value', timefunc._w_to_c(tostring(data.recurrence)))  | 
|||
			:tag('td')  | 
  |||
			:wikitext(timefunc._w_to_c(tostring(data.recurrence)))  | 
|||
			:done()  | 
  |||
		:done()  | 
  		:done()  | 
||
		:tag('  | 
  		:tag('td')  | 
||
			:  | 
  			:addClass('plainlist')  | 
||
			:newline()  | 
|||
				:wikitext('Minimum recurrence time')  | 
  |||
			:  | 
  			:wikitext(data.skill)  | 
||
		:done()  | 
  		:done()  | 
||
		:tag('  | 
  		:tag('td')  | 
||
			:  | 
  			:wikitext(data.category)  | 
||
				:wikitext(args['Recurrence Time'])  | 
  |||
			:done()  | 
  |||
		:done()  | 
  		:done()  | 
||
		:tag('  | 
  		:tag('td')  | 
||
			:wikitext(MEMBERS_ICON[data.members])  | 
|||
			:tag('th')  | 
  |||
				:wikitext('Effective profit')  | 
  |||
			:done()  | 
  |||
		:done()  | 
  		:done()  | 
||
	return val, tr  | 
|||
		:tag('tr')  | 
  |||
end  | 
|||
			: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.  | 
  |||
function p.rec(frame)  | 
|||
			:done()  | 
  |||
	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]] • [[Money making guide/Collecting|Collecting]] • [[Money making guide/Combat|Combat]] • [[Money making guide/Processing|Processing]] • [[Money making guide/Skilling|Skilling]] • [[Money making guide/Recurring|Recurring]] • [[Money making guide/Free-to-play|Free-to-play]]')  | 
|||
		:done()  | 
  		:done()  | 
||
		:tag('tr')  | 
  		:tag('tr')  | 
||
			:tag('th')  | 
  			:tag('th')  | 
||
				:wikitext('  | 
  				:wikitext('Method')  | 
||
			:done()  | 
  			:done()  | 
||
			:tag('th')  | 
  			:tag('th')  | 
||
				:wikitext('  | 
  				:wikitext('Profit')  | 
||
			: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('  | 
  				:wikitext('Time')  | 
||
			:done()  | 
  			:done()  | 
||
			:tag('th')  | 
  			:tag('th')  | 
||
				:wikitext('  | 
  				:wikitext('Effective<br>profit')  | 
||
			: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('  | 
  				:wikitext('Recurrence<br>time')  | 
||
			:done()  | 
  			:done()  | 
||
			:tag('th')  | 
  			:tag('th')  | 
||
				:wikitext('  | 
  				:wikitext('Skills')  | 
||
			: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('  | 
  				:wikitext('Category')  | 
||
				:wikitext(' (', _coins(round(parsedInput.value, 0), 'coins'), ')')  | 
  |||
			:done()  | 
  			:done()  | 
||
			:tag('th')  | 
  			:tag('th')  | 
||
				:wikitext('  | 
  				:wikitext('Members')  | 
||
				:css('width', '65px')  | 
|||
				:wikitext(' (', _coins(round(parsedOutput.value, 0), 'coins'), ')')  | 
  |||
			:done()  | 
  			:done()  | 
||
	local methods = {}  | 
|||
		:node(inputTR)  | 
  |||
	for i,v in ipairs(data) do  | 
|||
	:done()  | 
  |||
		if type(v['MMG recurring JSON']) == 'table' then  | 
|||
			for j,u in ipairs(v['MMG recurring JSON']) do  | 
|||
	frame:callParserFunction('DISPLAYTITLE', title.subpageText)  | 
  |||
				table.insert(methods, { make_rec_row(v[1], u, true) })  | 
|||
	frame:callParserFunction('DEFAULTSORT', title.subpageText)  | 
  |||
			end  | 
|||
		else  | 
|||
	local cats = '[[Category:Money making guides]][[Category:MMG/Recurring]]'  | 
  |||
			table.insert(methods, { make_rec_row(v[1], v['MMG recurring JSON'], false) })  | 
|||
		end  | 
|||
	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  | 
  	end  | 
||
	table.sort(methods, function(a,b) return a[1]>b[1] end)  | 
|||
	for i,v in ipairs(methods) do  | 
|||
	if set_smw then  | 
  |||
		t:newline()  | 
|||
		local smw_data = {  | 
  |||
		t:node(v[2])  | 
|||
			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  | 
  	end  | 
||
	t:newline()  | 
|||
	return   | 
  	return t  | 
||
end  | 
  end  | 
||
return p  | 
  return p  | 
||
-- </nowiki>  | 
|||
Latest revision as of 11:23, 17 October 2024
Documentation for this module may be created at Module:Mmgtable/displaysandbox/doc
-- <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] = "[[File:Free-to-play icon.png|20px|center|link=Free-to-play]]",
    [true]  = "[[File:Member icon.png|20px|center|link=Members]]"
}
function round1k(x, f)
	if not tonumber(x) then
		return x
	end
	local _x = math.abs(x)
	_x = 1000 * math.floor(_x / 1000 + 0.5)
	if x < 0 then
		_x = _x * -1
	end
	if f then
		return lang:formatNum(_x)
	end
	return _x
end
function round1dp(x, f)
	if not tonumber(x) then
		return x
	end
	local _x = math.abs(x)
	_x = math.floor(_x * 10 + 0.5) / 10
	if x < 0 then
		_x = _x * -1
	end
	if f then
		return lang:formatNum(_x)
	end
	return _x
end
function calc_value(args, kph)
	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
function make_row(fullpagename, raw_data, ismulti)
	local data = mw.text.jsonDecode(mw.text.decode(raw_data))
	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
function p.main(frame)
	local args = frame:getParent().args
	local query = {'[[MMG JSON::+]]', '?MMG JSON', '?=#', limit=10000}
	if args[1] then
		table.insert(query, 2, '[[Category:'..args[1]..']]')
	end
	local data = mw.smw.ask(query)
	
	local t = mw.html.create('table')
	
	t	:addClass('wikitable sortable sticky-header align-right-2 align-center-5 align-center-6 align-center-7 mmg-list-table')
		:tag('tr')
			:tag('th')
				:wikitext('Method')
				:css('word-wrap', 'break-word')
				:css('max-width', '300px')
			:done()
			:tag('th')
				:wikitext('Hourly Profit')
				:css('max-width', '100px')
			:done()
			:tag('th')
				:wikitext('Actions per hour')
				:addClass('unsortable')
			:done()
			:tag('th')
				:wikitext('Skills')
				:css('word-wrap', 'break-word')
				:css('max-width', '400px')
				:addClass('unsortable')
			:done()
			:tag('th')
				:wikitext('Category')
			:done()
			:tag('th')
				:wikitext('Intensity')
				:css('width', '65px')
			:done()
			:tag('th')
				:wikitext('Members')
				:css('width', '65px')
			:done()
			
	local methods = {}
	for i,v in ipairs(data) do
		if(string.find(v[1], 'Money making guide/')) then
			if type(v['MMG JSON']) == 'table' then
				for j,u in ipairs(v['MMG JSON']) do
					table.insert(methods, { make_row(v[1], u, true) })
				end
			else
				table.insert(methods, { make_row(v[1], v['MMG JSON'], false) })
			end
		end
	end
	table.sort(methods, function(a,b) return a[1]>b[1] end)
	for i,v in ipairs(methods) do
		if v[1] > 0 then
			t:newline():node(v[3])
		end
	end
	return t
end
function make_rec_row(fullpagename, raw_data, ismulti)
	local data = mw.text.jsonDecode(mw.text.decode(raw_data))
	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 val = calc_value(data.outputs, 0) - calc_value(data.inputs, 0)
	local c_class
	if val > 0 then
		c_class = 'coins-pos'
	elseif val < 0 then
		c_class = 'coins-neg'
	else
		c_class = ''
	end
	tr	:tag('td')
			:wikitext(pagename)
		:done()
		:tag('td')
			:attr('data-sort-value', val)
			:tag('span')
				:addClass('coins')
				:addClass(c_class)
				:wikitext(round1k(val, true))
			:done()
		:done()
		:tag('td')
			:wikitext(timefunc._m_to_c(tostring(data.time)))
		:done()
		:tag('td')
			:attr('data-sort-value', val*60/data.time)
			:tag('span')
				:addClass('coins')
				:addClass(c_class)
				:wikitext(round1k(val*60/data.time, true))
			:done()
		:done()
		:tag('td')
			:attr('data-sort-value', timefunc._w_to_c(tostring(data.recurrence)))
			:wikitext(timefunc._w_to_c(tostring(data.recurrence)))
		:done()
		:tag('td')
			:addClass('plainlist')
			:newline()
			:wikitext(data.skill)
		:done()
		:tag('td')
			:wikitext(data.category)
		:done()
		:tag('td')
			:wikitext(MEMBERS_ICON[data.members])
		:done()
	return val, tr
end
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]] • [[Money making guide/Collecting|Collecting]] • [[Money making guide/Combat|Combat]] • [[Money making guide/Processing|Processing]] • [[Money making guide/Skilling|Skilling]] • [[Money making guide/Recurring|Recurring]] • [[Money making guide/Free-to-play|Free-to-play]]')
		:done()
		:tag('tr')
			:tag('th')
				:wikitext('Method')
			:done()
			:tag('th')
				:wikitext('Profit')
			:done()
			:tag('th')
				:wikitext('Time')
			:done()
			:tag('th')
				:wikitext('Effective<br>profit')
			:done()
			:tag('th')
				:wikitext('Recurrence<br>time')
			:done()
			:tag('th')
				:wikitext('Skills')
			:done()
			:tag('th')
				:wikitext('Category')
			:done()
			:tag('th')
				:wikitext('Members')
				:css('width', '65px')
			:done()
			
	local methods = {}
	for i,v in ipairs(data) do
		if type(v['MMG recurring JSON']) == 'table' then
			for j,u in ipairs(v['MMG recurring JSON']) do
				table.insert(methods, { make_rec_row(v[1], u, true) })
			end
		else
			table.insert(methods, { make_rec_row(v[1], v['MMG recurring JSON'], false) })
		end
	end
	table.sort(methods, function(a,b) return a[1]>b[1] end)
	for i,v in ipairs(methods) do
		t:newline()
		t:node(v[2])
	end
	t:newline()
	return t
end
return p
-- </nowiki>