Module:Infobox Item

From the RuneScape Wiki, the wiki for all things RuneScape
Jump to: navigation, search
Module documentation
This documentation is transcluded from Module:Infobox Item/doc. [edit] [purge]
Module:Infobox Item is invoked by Template:Infobox Item.

Generates Infobox Item. Use Module:Infobox Item/sandbox for testing.


-- <nowiki>
-- Module for [[Template:Infobox Item]]
-- Test changes using [[Module:Infobox Item/sandbox]] [[Template:Infobox Item/sandbox]]
local p = {}

-- "imports"
local infobox = require('Module:Infobox')
local onmain = require('Module:Mainonly').on_main
local paramtest = require('Module:Paramtest')
local commas = require('Module:Addcommas')._add
local exchange = require('Module:Exchange')
local yesno = require('Module:Yesno')

local death_map = {
	allowed = {
		reclaimable = 'reclaimable',
		never = 'never',
		always = 'always',
		alwaysinclwild = 'alwaysinclwild',
		dropped = 'dropped',
		safe = 'safe',
		gemwdegrade = 'gemwdegrade'
	},
	strings = {
		reclaimable = 'reclaimable', --extra processing so no nice string
		never = 'Always lost',
		always = 'Always kept outside [[Wilderness|Wild]]',
		alwaysinclwild = 'Always kept',
		dropped = 'Dropped on death',
		safe = 'Always a safe death',
		gemwdegrade = nil --intentional nil
	}
}

-- location restriction
local restriction_map = {
	surface = 'surface',
	dungeoneering = 'dungeoneering',
	dg = 'dungeoneering',
	daemonheim = 'dungeoneering',
	quest = 'quest',
	minigame = 'minigame',
	activity = 'minigame',
	gone = 'removed',
	removed = 'removed',
	limited = 'limited',
	['time limited'] = 'limited',
	th = 'microtransaction',
	sof = 'microtransaction',
	['treasure hunter'] = 'microtransaction',
	['squeal of fortune'] = 'microtransaction',
	cache = 'cache'
}

-- Main function called with invokes
function p.main(frame)
	local args = frame:getParent().args
	local ret = infobox.new(args)

	-- Parameter definitions
	ret:defineParams{
		{ name = 'name', func = 'name' },
		{ name = 'aka', func = 'has_content' },
		{ name = 'image', func = 'image' },
		{ name = 'vanchor', func = { name = 'has_content', params = { 'version' }, flag = 'p' } },

		-- release and removal
		-- removal only shown if it exists
		{ name = 'release', func = 'release' },
		{ name = 'removal', func = 'removal' },
		{ name = 'removaldisp', func = { name = removaldisp, params = { 'removal' } }, dupes = true },

		{ name = 'members', func = 'has_content' },
		{ name = 'examine', func = 'has_content' },
		{ name = 'quest', func = 'has_content' },
		{ name = 'tradeable', func = tradeablearg },
		{ name = 'tradeablesmw', func = { name = yesno, params = { 'tradeable', false }, flag = { 'd', 'r' } } },
		{ name = 'equipable', func = 'has_content' },

		-- bankable; only show if "No"; default to "Yes"
		{ name = 'bankable', func = { name = 'has_content', params = { 'bankable', 'Yes'}, flag = { 'd', 'r' } } },
		{ name = 'bankabledisp', func = { name = yesnodisp, params = { 'bankable', 'no' }, flag = { 'd', 'r' } }, dupes = true },
		{ name = 'bankablesmw', func = { name = yesno, params = { 'bankable', false }, flag = { 'd', 'r' } }, dupes = true },

		-- stacksinbank; only show if "No"; default to "Yes"
		{ name = 'stacksinbank', func = { name = 'has_content', params = { 'stacksinbank', 'Yes'}, flag = { 'd', 'r' } } },
		{ name = 'stacksinbankdisp', func = { name = yesnodisp, params = { 'stacksinbank', 'no' }, flag = { 'd', 'r' } }, dupes = true },
		{ name = 'stacksinbanksmw', func = { name = yesno, params = { 'stacksinbank', false }, flag = { 'd', 'r' } }, dupes = true },

		-- lendable; only show if "Yes"; default to "No"
		{ name = 'lendable', func = { name = 'has_content', params = { 'lendable', 'No'}, flag = { 'd', 'r' } } },
		{ name = 'lendabledisp', func = { name = yesnodisp, params = { 'lendable' } }, dupes = true },
		{ name = 'lendablesmw', func = { name = yesno, params = { 'lendable', false }, flag = { 'd', 'r' } }, dupes = true },

		{ name = 'stackable', func = 'has_content' },
		{ name = 'stackablesmw', func = { name = yesno, params = { 'stackable', false }, flag = { 'd', 'r' } }, dupes = true },
		{ name = 'disassembly', func = disassemblyarg },
		{ name = 'disassemblysmw', func = { name = disassemblysmwarg, params = { 'disassembly' }, flag = 'p' } },

		-- edible; only show if "Yes"; default to "No"
		{ name = 'edible', func = { name = 'has_content', params = { 'edible', 'No'}, flag = { 'd', 'r' } } },
		{ name = 'edibledisp', func = { name = yesnodisp, params = { 'edible' } }, dupes = true },

		-- noteable; only show if "Yes"; default to "No"
		{ name = 'noteable', func = { name = 'has_content', params = { 'noteable', 'No'}, flag = { 'd', 'r' } } },
		{ name = 'noteabledisp', func = { name = yesnodisp, params = { 'noteable' } }, dupes = true },
		{ name = 'noteablesmw', func = { name = yesno, params = { 'noteable', true }, flag = { 'd', 'r' }, dupes = true } },
		{ name = 'destroy', func = 'has_content' },
		{ name = 'store', func = storearg },
		-- gemw prices
		-- only displayed if they exist
		--dupes must exist for individual prices to have them display properly
		{ name = 'gemw', func = { name = gemwarg, params = { 'exchange', 'tradeable' }, flag = {'p', 'd'} } },
		{ name = 'gemwname', func = { name = gemwnamearg, params = { 'name', 'gemwname' } } },
		{ name = 'gemwprice', func = { name = gemwpricearg, params = { 'gemw', 'gemwname' } }, dupes = true },
		{ name = 'gemwpage', func = { name = gemwpagearg, params = { 'gemwprice', 'gemwname' } } },
		{ name = 'exchange', func = { name = exchangearg, params = { 'gemwprice', 'gemwname' } }, dupes = true },
		{ name = 'graph', func = { name = gemwgrapharg, params = { 'gemwprice', 'gemwname' } }, dupes = true },
		{ name = 'buylimit', func = { name = buylimitarg, params = { 'gemwprice', 'gemwname' } }, dupes = true },
		{ name = 'buylimitsmw', func = { name = buylimitsmwarg, params = { 'buylimit' } }, dupes = true },
		{ name = 'exgvalue', func = { name = exgvaluearg, params = { 'gemwprice', 'gemwname' } }, dupes = true },
		{ name = 'val', func = { name = valraw, params = { 'value', 'exgvalue' }, flag = {'p', 'd'} }, dupes = true },
		{ name = 'value', func = { name = valuearg, params = { 'val', 'convert' }, flag = { 'd', 'p' } } },
		-- used for both exchange and graphs
		{ name = 'gemwdisp', func = { name = gemwdisp, params = { 'gemwprice' } }, dupes = true },
		{ name = 'deathstatus', func = { name = deathstatusarg, params = { 'kept' }, flag = 'p' } },
		{ name = 'ikodvalue', func = { name = ikodarg, params = { 'deathstatus', 'ikod', 'val', 'gemwprice' }, flag = { 'd', 'p', 'd', 'd' } } },
		{ name = 'reclaimvalue', func = { name = reclaimvaluearg, params = { 'ikodvalue', 'reclaim', }, flag = { 'd', 'p' } } },
		{ name = 'sacrificevalue', func = { name = sacrificevaluearg, params = { 'reclaimvalue', 'sacrifice' }, flag = { 'd', 'p' } } },
		{ name = 'kept', func = { name = keptondeatharg, params = { 'deathstatus', 'ikod', 'reclaimvalue', 'sacrificevalue' }, flag = {'d', 'p', 'd', 'd'} } },

		-- alchemy
		{ name = 'gemwalchable', func = { name = gemwalchablearg, params = { 'gemwprice', 'gemwname' } }, dupes = true },
		{ name = 'gemwalchmultiplier', func = { name = gemwalchmultiplierarg, params = { 'gemwprice', 'gemwname' } }, dupes = true },
		{ name = 'alchable', func = { name = alchablearg, params = { 'alchable', 'high', 'low', 'gemwalchable' }, flag = {'p', 'p', 'p', 'd' } }, dupes = true },
		{ name = 'alchmultiplier', func = { name = multiplierarg, params = { 'alchmultiplier', 'gemwalchmultiplier' }, flag = {'p','d'} } },
		{ name = 'highraw', func = { name = alchvaluesraw, params = { 'val', 'high', 'alchmultiplier', 1, 'alchable' }, flag = { 'd', 'p', 'd', 'r', 'd' } } },
		{ name = 'lowraw', func = { name = alchvaluesraw, params = { 'val', 'low', 'alchmultiplier', 2/3, 'alchable' }, flag = { 'd', 'p', 'd', 'r', 'd' } } },
		{ name = 'high', func = { name = alchvalues, params = { 'val', 'high', 'alchmultiplier', 1, 'alchable' }, flag = { 'd', 'p', 'd', 'r', 'd' } } },
		{ name = 'low', func = { name = alchvalues, params = { 'val', 'low', 'alchmultiplier', 2/3, 'alchable' }, flag = { 'd', 'p', 'd', 'r', 'd' } } },

		{ name = 'weight', func = weightarg },
		{ name = 'weightraw', func = { name = weightargraw, params = { 'weight' }, flag = 'p' } },

		{ name = 'restriction', func = restrictionarg },
		{ name = 'restrictionsurface', func = { name = restrsurfarg , params = { 'restriction', 'restriction', 'quest' }, flag = { 'd', 'p', 'd' } } },
		-- not used; only for categories
		{ name = 'id', func = 'numbers' },
		{ name = 'id_smw', func = { name = idsmw, params = { 'id' }, flag = 'p' } },
		{ name = 'rscid', func = 'numbers' },
		{ name = 'SMWarg', func = { name = SMWarg, params = { 
				'name', 'version', 'id', 'members', 'release', 'removal', 'examine', 'quest', 'destroy', 'restriction', -- strings
				'deathstatus', 'ikodvalue', 'reclaimvalue', 'sacrificevalue', -- death
				'tradeable', 'equipable', 'bankable', 'stacksinbank', 'lendable', 'stackable', 'disassembly', 'edible', 'noteable', --generally boolean or tri-lean
				'val', 'high', 'low', 'weightraw', -- numbers
				'gemw', 'gemwname', 'buylimit' -- gemw
			}, flag = 'd' } }
	}

	ret:setMaxButtons(7)
	ret:create()
	ret:cleanParams()
	ret:customButtonPlacement(true)

	-- parameter linkings for hidden rows
	ret:linkParams{
		{ 'removal', 'removaldisp' },
		{ 'exchange', 'gemwdisp' },
		{ 'graph', 'gemwdisp' },
		{ 'buylimit', 'gemwdisp' },
		{ 'bankable', 'bankabledisp' },
		{ 'stacksinbank', 'stacksinbankdisp' },
		{ 'lendable', 'lendabledisp' },
		{ 'noteable', 'noteabledisp' },
		{ 'edible', 'edibledisp' }
	}

	ret:defineLinks({ links = {{ 'Template:%s/FAQ', 'FAQ' },
			{ 'Template:%s/doc', 'doc' }}, colspan = 2 })

	ret:useSMW({
		id_smw = 'Item ID',
		members = 'Is members only'
	})

	ret:useSMWElement({
		buylimitsmw = 'Buy limit',
		SMWarg = 'Item JSON',
	})

	ret:useSMWSubobject({
		id_smw = 'Item ID',
		vanchor = 'Version anchor',
		name = 'Item name',
		val = 'Value',
		highraw = 'High Alchemy value',
		lowraw = 'Low Alchemy value',
		weightraw = 'Weight',
		members = 'Is members only',
		tradeablesmw = 'Tradeable',
		stackablesmw = 'Stackable',
		bankablesmw = 'Bankable',
		stacksinbanksmw = 'Stacksinbank',
		lendablesmw = 'Lendable',
		disassemblysmw = 'Disassembleable',
		noteablesmw = 'Noteable',
		gemwpage = 'Exchange page',
		buylimitsmw = 'Buy limit',
		restriction = 'Location restriction',
		deathstatus = 'Kept on death',
		release = 'Release date',
		update = 'Release update',
		removal = 'Removal date',
		removalupdate = 'Removal update',
	})
	if onmain() then
		local a2 = ret:categoryData()
		if not a2['restriction'].all_defined then
			ret:useSMWSubobject({
				restrictionsurface = 'Location restriction',
			})
		end
	end

	ret:addButtonsCaption()

	-- ret:caption()
	ret:defineName('Infobox Item')
	ret:addClass('infobox-item')

	-- PARAMETER: image
	ret:addRow{
		{ tag = 'argd', content = 'image', class = 'infobox-image inventory-image bordered-image', colspan = '2' } }

	-- PARAMETER: name
	ret:addRow{
		{ tag = 'argh', content = 'name', class='infobox-header', colspan = '2' } }
	
	-- PARAMETER: release
	-- (update included automatically by infobox)
		:addRow{ { tag = 'th', content = 'Release' },
				{ tag = 'argd', content = 'release' } }
 
	-- PARAMETER: removal
	if ret:paramDefined('removal') then
		ret:addRow{ { tag = 'th', content = 'Removal' },
				{ tag = 'argd', content = 'removal' } }
	end

	-- PARAMETER: aka
	-- add only if it exists
	if ret:paramDefined('aka') then
		ret:addRow{ { tag = 'th', content = '[[Slang dictionary|AKA]]' },
				{ tag = 'argd', content = 'aka' } }
	end

	-- PARAMETER: members
	ret:addRow{
		{ tag = 'th', content = '[[Members]]' },
		{ tag = 'argd', content = 'members' } }

	-- PARAMETER: quest
	:addRow{
		{ tag = 'th', content = '[[Quest items|Quest item]]' },
		{ tag = 'argd', content = 'quest' } }

	-- HEADER: Properties
	:addRow{
		{ tag = 'th', content = 'Properties', class = 'infobox-subheader', colspan = '2' } }

	-- PARAMETER: tradeable
	:addRow{
		{ tag = 'th', content = '[[Items#Tradeability|Tradeable]]' },
		{ tag = 'argd', content = 'tradeable' } }

	-- PARAMETER: bankable
	if ret:paramGrep('bankable','no') then
		ret:addRow{
			{ tag = 'th', content = '[[Bank]]able' },
			{ tag = 'argd', content = 'bankable' } }
	end

	-- PARAMETER: stacksinbank
	if ret:paramGrep('stacksinbank','no') then
		ret:addRow{
			{ tag = 'th', content = 'Stacks in bank' },
			{ tag = 'argd', content = 'stacksinbank' } }
	end

	-- PARAMETER: lendable
	if ret:paramGrep('lendable','yes') then
		ret:addRow{
			{ tag = 'th', content = '[[Item Lending|Lendable]]' },
			{ tag = 'argd', content = 'lendable' } }
	end

	-- PARAMETER: equipable
	ret:addRow{
		{ tag = 'th', content = '[[Equipment|Equipable]]' },
		{ tag = 'argd', content = 'equipable' } }

	-- PARAMETER: stackable
	:addRow{
		{ tag = 'th', content = '[[Stackable items|Stackable]]' },
		{ tag = 'argd', content = 'stackable' } }

	-- PARAMETER: disassembly
	:addRow{
		{ tag = 'th', content = '[[Disassemble|Disassembly]]' },
		{ tag = 'argd', content = 'disassembly' } }

	-- PARAMETER: noteable
	if ret:paramGrep('noteable','yes') then
		ret:addRow{
			{ tag = 'th', content = '[[Note|Noteable]]' },
			{ tag = 'argd', content = 'noteable' } }
	end

	-- PARAMETER: edible
	if ret:paramGrep('edible','yes') then
		ret:addRow{
			{ tag = 'th', content = '[[Food|Edible]]' },
			{ tag = 'argd', content = 'edible' } }
	end

	-- PARAMETER: destroy
	ret:addRow{
		{ tag = 'th', content = '[[Destroy (action)|Destroy]]' },
		{ tag = 'argd', content = 'destroy', css = { ['max-width'] = '200px' } } }

	-- HEADER: Values
	ret:addRow{
		{ tag = 'th', content = 'Values', class = 'infobox-subheader', colspan = '2' } }

	-- PARAMETER: value
	ret:addRow{
		{ tag = 'th', content = '[[Value]]' },
		{ tag = 'argd', content = 'value' } }

	-- PARAMETER: alchable | high | low
	-- find if any version is alchable
	local anyalchable = ret:paramGrep('alchable',true)

	-- if any are alchable, add both rows
	if anyalchable == true then
		ret:addRow{
			{ tag = 'th', content = '[[High Level Alchemy|High alch]]' },
			{ tag = 'argd', content = 'high' } }
		:addRow{ 
			{ tag = 'th', content = '[[Low Level Alchemy|Low alch]]' },
			{ tag = 'argd', content = 'low' } }
	else
	-- otherwise add a single "no alch" row
		ret:addRow{
			{ tag = 'th', content = '[[Alchemy]]' },
			{ tag = 'td', content = 'Not alchemisable' } }
	end

	-- PARAMETER: death
	ret:addRow{
		{ tag = 'th', content = '[[Items Kept on Death|On death]]' },
		{ tag = 'argd', content = 'kept' } }

	-- find if we have any exchange prices
	local anygemw = ret:paramGrep('gemwprice', function(_arg) return _arg > 0 end)

	-- if we have any on the ge, add the gemw row
	if anygemw == true then
		-- PARAMETER: exchange | gemwname
		ret:addRow{
			{ tag = 'th', content = '[[RuneScape:Grand Exchange Market Watch|Exchange]]' },
			{ tag = 'argd', content = 'exchange' } }
			:addRow{
			{ tag = 'th', content = '[[Grand Exchange#Trade restrictions|Buy limit]]' },
			{ tag = 'argd', content = 'buylimit' } }
	end

	-- PARAMETER: weight
	ret:addRow{
		{ tag = 'th', content = '[[Weight]]' },
		{ tag = 'argd', content = 'weight' } }

	-- PARAMETER: examine
	ret:addRow{
		{ tag = 'th', content = '[[Examine]]', class = 'infobox-subheader', colspan = '2' } }
	:addRow{
		{ tag = 'argd', content = 'examine', colspan = '2', css = { ['max-width'] = '300px', ['text-align'] = 'center' } } }

	-- PARAMETER: graph
	if anygemw == true then
		ret:addRow{
			{ tag = 'th', content = 'Exchange history', class = 'infobox-subheader', colspan = '2' } }
		:addRow{ { tag = 'argd', content = 'graph', colspan = '2', css = { ['text-align'] = 'center', ['padding'] = '0'} } }
	end

	if onmain() then
		local a1 = ret:param('all')
		local a2 = ret:categoryData()
		ret:wikitext(addcategories(ret, a1,a2))
	end

	return ret:tostring()
end

-- Store price
function storearg(store, currency, seller)
	-- remove any commas
	store = string.gsub(store or '',',','')

	-- no for not sold
	if string.lower(store) == 'no' then
		return 'Not sold'
	else
		store = tonumber(store,10)
	end

	if type(store) == 'number' then
		return store
	else
		return nil
	end
end

-- tradeable
-- tradeablearg(value)
function tradeablearg(v)
	v = string.lower(v or '')
	if v == 'yes' or v == 'no' then
		v = mw.text.split(v,'')
		v[1] = string.upper(v[1])
		return table.concat(v,'')
	elseif v == 'restricted' then
		return '[[Restricted trade items|Restricted]]'
	else
		return nil
	end
end

-- disassembly
-- disassemblyarg(yes/no)
local allowed_disassembly = {
	yes = 'true',
	no = 'false',
	restricted = 'restricted',
	na = 'N/A',
	['n/a'] = 'N/A',
	discontinued = 'N/A',
	irrelevant = 'N/A'
}
function disassemblyarg(d)
	d = string.lower(d or '')
	d = allowed_disassembly[d]
	if d == 'true' then
		return '[[#DisassemblyT|Yes]]' -- Unique anchor ID created by {{Disassembly}}
	elseif d == 'false' then
		return 'No'
	elseif d == 'restricted' then
		return '[[:Category:Location restricted disassembly|Restricted]]'
	elseif d == 'N/A' then
		return 'N/A'
	else
		return nil
	end
end
-- disassemblysmwarg(yes/no)
function disassemblysmwarg(d)
	d = string.lower(d or '')
	return allowed_disassembly[d]
end

-- value
-- separate number storage for operation
-- pulls the exchange value first, and prefers that if it exists
-- valraw(value, exchange value)
function valraw(v, ex)
	if type(ex) == 'number' then
		return ex
	else
		local _ v = v or ''
		_v = string.gsub(v,',','')
	
		return tonumber(v,10)
	end
end

-- value
-- valuearg(value, convert)
-- actual value already parsed
function valuearg(v,c)
	-- replace commas and turn into a number
	if paramtest.has_content(c) then
		c = string.gsub(c,',','')
		c = tonumber(c,10)
	else
		c = nil
	end

	v = tonumber(v,10)

	-- if both are defined, show both, value first
	if v and c then
		return string.format('%s<br/><b>Cash out:</b><br/>%s',plural('coin',v),plural('coin',c))
	-- if only value is defined, show just that
	elseif v and not c then
		return plural('coin',v)
	-- if only convert is defined, show just that
	-- may need to change this so that value is requested
	elseif c and not v then
		return string.format('<b>Cash out:</b><br/>%s',plural('coin',c))
	else
		return nil
	end
end

-- ge alchable boolean
-- gemwalchablearg(gemw price, name)
-- uses the already fetched ge price to operate
-- values less than 1 are used for parsing instructions
function gemwalchablearg(g,n)
	if type(g) ~= 'number' then
		g = tonumber(g,10) or 0
	end

	-- 0 for not sold an
	-- -1 for error
	-- call to hide the graph
	if g == 0 or g == -1 then
		return '-'
	-- all other numbers 
	elseif g > 0 then
		local ret = exchange._alchable(n)
		if ret == nil then
			return '-'
		elseif ret == false then
			return 'false' --not very good at handling false
		else
			return true
		end
	-- not a number = nil
	-- shouldn't be used, but it's a fallback
	else
		return '-'
	end
end

-- ge alch multiplier
-- gemwalchmultiplierarg(gemw price, name)
-- uses the already fetched ge price to operate
-- values less than 1 are used for parsing instructions
function gemwalchmultiplierarg(g,n)
	if type(g) ~= 'number' then
		g = tonumber(g,10) or 0
	end

	-- 0 for not sold an
	-- -1 for error
	-- call to hide the graph
	if g == 0 or g == -1 then
		return '-'
	-- all other numbers 
	elseif g > 0 then
		local ret = exchange._alchmultiplier(n)
		if ret == nil then
			return '-'
		else
			return ret
		end
	-- not a number = nil
	-- shouldn't be used, but it's a fallback
	else
		return '-'
	end
end

-- Alchables
-- alchablearg(alchable, high, low)
function alchablearg(a,h,l,ex)
	if ex == true then
		return true
	elseif ex == 'false' then
		return 'false'
	end
	-- not alchable if both are false, or if "alchable" is false
	if string.lower(a or '') == 'no' then
		return 'false'
	elseif string.lower(h or '') == 'no' and string.lower(l or '') == 'no' then
		return 'false'
	else
		return  true
	end
end

-- alch multiplier arg
-- only accepts numbers
-- defaults to .6
function multiplierarg(v, ex)
	if type(ex) == 'number' then
		return ex
	end
	return tonumber(v) or 0.6
end

-- high/low alch
-- alchvalues(value, override value, multiplier, alchable)
-- actual value already parsed
-- value, template param, alch multiplier, override multiplier, not alchable bool
function alchvalues(v,_p,a,m,n)
	-- if you can't alch it, return this
	-- used in the case of 1 version being alchable and the other not
	if not infobox.isDefined(n) or n == 'false' then
		return 'Not alchemisable'
	end
	-- remove commas and turn into a number
	v = tonumber(v,10)
	if paramtest.has_content(_p) then
		_p = string.gsub(_p,',','')
		_p = tonumber(_p,10)
	else
		_p = nil
	end

	-- return override always
	if type(_p) == 'number' then
		return plural('coin',_p)
	end

	-- otherwise try the value and multiply it
	if v then
		local r = math.floor(killRoundingError(v * m * a))
		return plural('coin',r)
	end

	return nil
end
-- alchvaluesraw(value, override value, multiplier, alchable)
-- actual value already parsed
-- value, template param, alch multiplier, override multiplier, not alchable bool
function alchvaluesraw(v,_p,a,m,n)
	-- if you can't alch it, return nil
	if not infobox.isDefined(n) or n == 'false' then
		return nil
	end
	-- remove commas and turn into a number
	v = tonumber(v,10)
	if paramtest.has_content(_p) then
		_p = string.gsub(_p,',','')
		_p = tonumber(_p,10)
	else
		_p = nil
	end

	-- return override always
	if type(_p) == 'number' then
		return _p
	end
	-- otherwise try the value and multiply it
	if v then
		local r = math.floor(killRoundingError(v * m * a))
		return r
	end

	return nil
end

-- weight
-- weightarg(weight)
function weightarg(w)
	if paramtest.has_content(w) then
		-- replace all "kg" and spaces here
		w = string.gsub(w or '','[kg ]','')

		-- replace hyphen with minus sign
		w = string.gsub(w,'-','&minus;')

		-- use non-breaking spaces and html entities for display
		-- still necessary to convert the "kg" to html?
		return string.format('%s&nbsp;&#107;&#103;',w)
	end
	return nil
end

-- weightargraw(weight)
function weightargraw(w)
	if paramtest.has_content(w) then
		-- replace all "kg" and spaces here
		w = string.gsub(w or '','[kg ]','')
		return tonumber(w)
	end
	return nil
end

-- on ge or not
-- only accepts "gemw"
-- gemwarg(exchange,tradeable)
function gemwarg(arg,arg2)
	g = string.lower(arg or '')
	return arg2 == 'Yes' and g == 'gemw'
end

-- gemw names
-- gemwnamearg(name, override name)
function gemwnamearg(n,a)
	-- return override
	if a and a:find('%S') then
		return string.gsub(a,'</?span>','')
	-- otherwise use the "name" parameter
	elseif n and n:find('%S') then
		return string.gsub(n,'</?span>','')
	-- default to page name
	else
		return mw.title.getCurrentTitle().fullText
	end
end

-- separate thing to hold all the prices as raw numbers
-- gemwpricearg(gemw, name)
function gemwpricearg(g,n)
	if g == true then
		-- return price if page is found
		-- -1 for errors
		if exchange._exists(n) then
			return tonumber(exchange._price(n),10) or -1
		else
			return -1
		end
	-- 0 for no price
	else
		return 0
	end
end

-- gemwpricearg(gemw price, name)
-- exchange page name for SMW
-- uses the already fetched ge price to operate
-- values less than 1 are used for parsing instructions
function gemwpagearg(g,n)
	if type(g) ~= 'number' then
		g = tonumber(g,10) or 0
	end
	if g < 1 then
		return nil
	else
		return 'Exchange:'..n
	end
end

-- exchange display
-- exchangearg(gemw price, name)
-- uses the already fetched ge price to operate
-- values less than 1 are used for parsing instructions
function exchangearg(g,n)
	if type(g) ~= 'number' then
		g = tonumber(g,10) or 0
	end
	-- 0 for not sold
	if g == 0 then
		return '<span class="infobox-quantity" data-val-each="0">Not sold</span>'
	-- -1 for no page found
	elseif g == -1 then 
		return badarg('exchange','was set to «gemw» but no page was found for «'..n..'».')
	-- all other numbers 
	elseif g > 0 then
		-- plural done in format because we need a span around the value
		return string.format('<span class="infobox-quantity" data-val-each="%s"><span class="infobox-quantity-replace">%s</span> coin%s ([[Exchange:%s|info]])</span>',g,commas(g),g>1 and 's' or '',n)
	-- not a number = nil
	-- shouldn't be used, but it's a fallback
	else
		return 0
	end
end

-- ge graphs
-- gemwgrapharg(gemw price, name)
-- uses the already fetched ge price to operate
-- values less than 1 are used for parsing instructions
function gemwgrapharg(g,n)
	if type(g) ~= 'number' then
		g = tonumber(g,10) or 0
	end

	-- 0 for not sold an
	-- -1 for error
	-- call to hide the graph
	if g == 0 or g == -1 then
		return 'No data to display'
	-- all other numbers 
	elseif g > 0 then
		-- TODO: Change Module:ExchangeData and give it a proper graph function
		-- remove reliance on frame
		return mw.getCurrentFrame():preprocess(string.format('{{GEChart|%s|size=small}}',n))
	-- not a number = nil
	-- shouldn't be used, but it's a fallback
	else
		return nil
	end
end

-- ge buy limits
-- buylimitarg(gemw price, name)
-- uses the already fetched ge price to operate
-- values less than 1 are used for parsing instructions
function buylimitarg(g,n)
	if type(g) ~= 'number' then
		g = tonumber(g,10) or 0
	end

	-- 0 for not sold an
	-- -1 for error
	-- call to hide the graph
	if g == 0 or g == -1 then
		return '-'
	-- all other numbers 
	elseif g > 0 then
		local ret = exchange._limit(n)
		if ret == nil then
			return '-'
		else
			return commas(ret)
		end
	-- not a number = nil
	-- shouldn't be used, but it's a fallback
	else
		return nil
	end
end

function buylimitsmwarg(b)
	if type(b) == 'string' then
		b = b:gsub(',', '')
	end
	if tonumber(b) then
		return tonumber(b)
	end
	return nil
end

-- ge value
-- exgvaluearg(gemw price, name)
-- uses the already fetched ge price to operate
-- values less than 1 are used for parsing instructions
function exgvaluearg(g,n)
	if type(g) ~= 'number' then
		g = tonumber(g,10) or 0
	end

	-- 0 for not sold an
	-- -1 for error
	-- call to hide the graph
	if g == 0 or g == -1 then
		return '-'
	-- all other numbers 
	elseif g > 0 then
		local ret = exchange._value(n)
		if ret == nil then
			return '-'
		else
			return ret
		end
	-- not a number = nil
	-- shouldn't be used, but it's a fallback
	else
		return '-'
	end
end

-- class names based on value
-- gemwdisp(price)
function gemwdisp(_p)
	if _p == 0 then
		return ''
	else
		return 'infobox-cell-shown'
	end
end

-- Shows if the param matches the alt
-- alt defaults to yes
-- Everything else = hide
function yesnodisp(arg, alt)
	arg = string.lower(arg or '')
	alt = string.lower(alt or 'yes')
	if arg == alt then
		return 'infobox-cell-shown'
	else
		return ''
	end
end

-- Shows if has a date
function removaldisp(arg)
	if string.find(arg or '','%[%[') then
		return 'infobox-cell-shown'
	else
		return ''
	end
end

-- death
-- deathstatusarg(death)
function deathstatusarg(d)
	d = string.lower(d or '')
	return death_map.allowed[d]
end

-- ikodarg()
function ikodarg(d, _i, v, g)
	if d == 'reclaimable' then
		v = tonumber(v)
		local i
		-- replace commas and convert to number
		if paramtest.has_content(_i) then
			i = string.gsub(_i,',','')
			i = tonumber(i,10)
		else
			i = nil
		end

		-- if i is defined, show that value
		if i then
			return i
		-- test geprice next
		elseif (tonumber(g,10) or 0) > 0 then
			i = tonumber(g,10)
		-- test value next
		elseif v then
			i = v
		end
		return i
	end
	return -1
end

-- reclaimvaluearg(ikodvalue, reclaim)
function reclaimvaluearg(i, _r)
	i = tonumber(i or -1) or -1
	if i > 0 then
		local r

		-- test for overridden reclaim
		if paramtest.has_content(_r) then
			_r = string.gsub(_r,',','')
			r = tonumber(_r)
		end

		-- if no number, use formula
		if not r then
			-- less than 10,000: .15x
			if i < 10000 then
				r = math.floor(i * .15)
				-- all items cost at least 1 coin, so round back up if 0
				if r == 0 then
					r = 1
				end

			--[[
				Death formula
			]]
			-- 10,000 to 49,999: 500 + 0.1x
			elseif i >= 10000 and i < 50000 then
				r = 500 + i * 0.1

			-- 50,000 to 249,999: 3000 + 0.05x
			elseif i >= 50000 and i < 250000 then
				r = 3000 + i * 0.05

			-- 250,000 to 999,999: 10500 + 0.02x
			elseif i >= 250000 and i < 1000000 then
				r = 10500 + i * 0.02

			-- 1,000,000 to 9,999,999: 20500 + 0.01x
			elseif i >= 1000000 and i < 10000000 then
				r = 20500 + i * 0.01

			-- 10,000,000 and greater: 70,500 + 0.005x
			elseif i >= 10000000 then
				r = 70500 + i * 0.005

			-- this shouldn't happen, but fallback to 1
			else
				r = 1
			end
		end

		r = math.floor(killRoundingError(r))
		return r
	end
	return -1
end

-- sacrificevaluearg(reclaimvalue, sacrifice)
function sacrificevaluearg(r, _s)
	if r > 0 then
		if paramtest.has_content(_s) then
			_s = string.gsub(_s,',','')
			_s = tonumber(_s,10)
		end

			-- if overridden, use _s for sacrifice
		if tonumber(_s,10) then
			s = _s
		else
			-- otherwise include sacrifice as 4r
			s = r * 4
		end
		return s
	end
	return -1
end

-- keptondeatharg(deathstatus, ikod, reclaimvalue, sacrificevalue)
function keptondeatharg(d,_i,r,s)
	if d == 'reclaimable' then
		local ret = { 'Reclaimable' }
		local i
		-- replace commas and convert to number
		if paramtest.has_content(_i) then
			i = string.gsub(_i,',','')
			i = tonumber(i,10)
		else
			i = nil
		end
		if i then
			table.insert(ret,string.format('<b>Value:</b> %s',commas(i)))
		end

		table.insert(ret,string.format('<b>Reclaim:</b> %s',commas(r)))
		table.insert(ret,string.format('<b>Sacrifice:</b> %s',commas(s)))

		return table.concat(ret,'<br/>')
	else
		return death_map.strings[d]
	end
end

-- red ERR span with title hover for explanation
function badarg(argname, argmessage)
	return '<span '..
			'title="The parameter «'..argname..'» '..argmessage..'" '..
			'style="color:red; font-weight:bold; cursor:help; border-bottom:1px dotted red;">'..
			'ERR</span>'
end

-- plural
-- returns number with the word
function plural(arg,n,alt)
	local _n = commas(tonumber(n,10) or 1)
	if tonumber(n,10) == 1 then
		return string.format('%s %s',_n,arg)
	elseif alt then
		return string.format('%s %s',_n,alt)
	else
		return string.format('%s %ss',_n,arg) 
	end
end

-- Does exactly what's on the tin
function killRoundingError(n)
	return math.floor(n*1000+0.0000099)/1000
end

function restrictionarg(arg)
	if paramtest.is_empty(arg) then
		return nil
	end
	return restriction_map[string.lower(arg)]
end
function restrsurfarg(cleaned, passed)
	if infobox.isDefined(cleaned) then
		return nil
	end
	return restriction_map.surface
end

function idsmw(id)
	if infobox.isDefined(id) then
		local r = string.gsub(id, ',', '&&SPLITPOINT&&')
		return r
	end
	return nil
end

-- SMW JSON
function SMWarg(name, version, id, members, release, removal, examine, quest, destroy, restriction, deathstatus, ikodvalue, reclaimvalue, sacrificevalue, tradeable, equipable, bankable, stacksinbank, lendable, stackable, disassembly, edible, noteable, value, high, low, weight, gemw, gemwname, buylimit)
	local toJSON = {
		name = name,
		version = version,
		id = id,
		members = string.lower(tostring(members) or ''),
		examine = examine,
		death = deathstatus,
		destroy = destroy,
		tradeable = string.lower(tostring(tradeable) or ''),
		equipable = string.lower(tostring(equipable) or ''),
		bankable = string.lower(tostring(bankable) or ''),
		stacksinbank = string.lower(tostring(stacksinbank) or ''),
		lendable = string.lower(tostring(lendable) or ''),
		stackable = string.lower(tostring(stackable) or ''),
		edible = string.lower(tostring(edible) or ''),
		noteable = string.lower(tostring(noteable) or ''),
		value = val,
		weight = weight,
	}

	local w
	if high == 'Not alchemisable' then
		toJSON.highalch = false
	else
		w = high:gsub(',',''):gsub('coins?','')
		toJSON.highalch = tonumber(w) or false
	end
	if low == 'Not alchemisable' then
		toJSON.lowalch = false
	else
		w = low:gsub(',',''):gsub('coins?','')
		toJSON.highalch = tonumber(w) or false
	end

	if toJSON.tradeable == 'yes' and gemw then
		toJSON.gemw = { name = gemwname }
		if buylimit ~= '-' then
			w = buylimit:gsub(',','')
			toJSON.gemw.limit = tonumber(w)
		end
	else
		toJSON.gemw = false
	end

	if disassembly:find('[Yy]es') then
		toJSON.disassembly = 'yes'
	elseif disassembly:find('restricted') then
		toJSON.disassembly = 'restricted'
	else 
		toJSON.disassembly = string.lower(tostring(disassembly) or '')
	end

	if paramtest.is_empty(restriction) and tostring(restriction):find('action=edit') then
		toJSON.restriction = restriction_map.surface
	else
		toJSON.restriction = restriction
	end

	local rel, upd, rem, updr
	rel, upd = release:match('(.-) %(%[%[Update:(.-)|Update%]%]%)')
	if rel == nil then
		rel = release:match('(.-) %(Update unknown%)')
	end
	if rel then
		toJSON.release_date = rel:gsub('%[',''):gsub('%]','')
		if upd then
			toJSON.release_update_post = upd
		end
	end

	rem, updr = removal:match('(.-) %(%[%[Update:(.-)|Update%]%]%)')
	if rem == nil then
		rem = removal:match('(.-) %(Update unknown%)')
	end
	if rem then
		toJSON.removal_date = rem:gsub('%[',''):gsub('%]','')
		if updr then
			toJSON.removal_update_post = updr
		end
	end

	for k,v in pairs(toJSON) do
		if v == '' or (type(v) == 'string' and string.find(v,'action=edit')) then
			toJSON[k] = nil
		end
	end

	return mw.text.nowiki(mw.text.jsonEncode(toJSON))
end

-- Categories
-- oman this is still blatant copy pasta
function addcategories(ibox, args, catargs)
	local ret = { 'Items' }
	local cat_map = {
		-- Added if the parameter has content
		defined = {
			aka = 'Pages with AKA',
			alchmultiplier = 'Has Alchemy Multiplier'
			},
		-- Added if the parameter has no content
		notdefined = {
			image = 'Needs image',
			members = 'Needs members status',
			release = 'Needs release date',
			examine = 'Needs examine added',
			level = 'Needs combat level',
			weight = 'Needs weight added',
			value = 'Items missing value',
			quest = 'Items missing quest',
			destroy = 'Missing destroy text',
			kept = 'Items missing death info',
			disassembly = 'Items missing disassembly info'
			},
		-- Parameters that have text
		-- map a category to a value
		grep = {
			members = { yes = 'Members\' items', no = 'Free-to-play items' },
			stackable = { yes = 'Stackable items' },
			lendable = { yes = 'Lendable items' },
			equipable = { yes = 'Equipable items' },
			gemw = { ['true'] = 'Grand Exchange items' },
			tradeable = { yes = 'Tradeable items', no = 'Untradeable items', restricted = 'Restricted trade items' },
			disassembly = { yes = 'Items that can be disassembled', no = 'Items that cannot be disassembled', restricted = 'Location restricted disassembly', ['n/a'] = 'Items with N/A disassembly' },
			kept = { ['always lost'] = 'Items that are never kept on death', ['always kept outside'] = 'Items that are always kept outside the Wilderness on death', reclaimable = 'Items that are reclaimable on death' }
		}
	}

	-- Run and add mapped categories

	-- defined categories
	for n, v in pairs(cat_map.defined) do
		if catargs[n] and catargs[n].one_defined then
			table.insert(ret,v)
		end
	end

	-- undefined categories
	for n, v in pairs(cat_map.notdefined) do
		if catargs[n] and catargs[n].all_defined == false then
			table.insert(ret,v)
		end
	end

	-- searches
	for n, v in pairs(cat_map.grep) do
		for m, w in pairs(v) do
			if args[n] then
				if string.find(string.lower(tostring(args[n].d) or ''),m) then
					table.insert(ret,w)
				end
				if args[n].switches then
					for _, x in ipairs(args[n].switches) do
						if string.find(string.lower(tostring(x)),m) then
							table.insert(ret,w)
						end
					end
				end
			end
		end
	end

	-- quest items
	-- just look for a link
	if args.quest.d:find('%[%[') then
		table.insert(ret,'Quest items')
	elseif args.quest.switches then
		for _, v in ipairs(args.quest.switches) do
			if v:find('%[%[') then
				table.insert(ret,'Quest items')
				break
			end
		end
	end

	-- extra func for death
	-- searching for 'always kept' would match more than what we want
	if args.kept.d == 'Always kept' then
		table.insert(ret,'Items that are always kept on death')
	elseif args.kept.switches then
		for _, v in ipairs(args.kept.switches) do
			if v == 'Always kept' then
				table.insert(ret,'Items that are always kept on death')
				break
			end
		end
	end
	-- ids
	if not catargs.id.all_defined then
		-- rsc ids have no id
		if catargs.rscid.all_defined then
			-- do nothing
		else
			table.insert(ret,'Needs ID')
		end
	end

	-- store
	if string.lower(args.store.d or '') ~= 'not sold' and not string.lower(args.store.d or 'edit'):find('edit') then
		table.insert(ret,'Pages that use Store')
	elseif args.store.switches then
		for _, v in ipairs(args.store.switches) do
			if string.lower(v or '') ~= 'not sold' and string.lower(v or '') ~= infobox.nil_param() and not string.lower(v or 'edit'):find('edit') then
				table.insert(ret,'Pages that use Store')
			end
		end
	end

	-- alchemy
	-- non alchable
	if args.alchable.d ~= true then
		table.insert(ret,'Items that cannot be alchemised')
	elseif args.alchable.switches then
		for _, v in ipairs(args.alchable.switches) do
			if v ~= true then
				table.insert(ret,'Items that cannot be alchemised')
				break
			end
		end
	end

	-- gemw
	-- if item is both (not untradeable) and (not GEMW) then add Non-GE items
	if not args.gemw.d and string.lower(args.tradeable.d) ~= 'no' then
		table.insert(ret, 'Non-GE items')
	end
	-- switches; if tradeable switches exist, if gemwX and tradeableX are as above, add Non-GE items
	--						if no switches, gemwX and tradeable (default)
	if args.gemw.switches then
		if args.tradeable.switches then
			for i, v in ipairs(args.gemw.switches) do
				if not v and string.lower(args.tradeable.switches[i] or args.tradeable.d) ~= 'no' then
					table.insert(ret, 'Non-GE items')
				end
			end
		else
			for i, v in ipairs(args.gemw.switches) do
				if not v and string.lower(args.tradeable.d) ~= 'no' then
					table.insert(ret, 'Non-GE items')
				end
			end
		end
	end

	local limit = string.gsub(tostring(args.buylimit.d), ',', '')
	if tonumber(limit) then
		if tonumber(limit) < 1000 then
			table.insert(ret, 'Low buy limit')
		end
	end
	if args.buylimit.switches then
		for i,v in ipairs(args.buylimit.switches) do
			limit = string.gsub(tostring(args.buylimit.switches[i] or args.buylimit.d), ',', '')
			if tonumber(limit) then
				if tonumber(limit) < 1000 then
					table.insert(ret, 'Low buy limit')
				end
			end
		end
	end
	
	-- special addition for location restriction
	-- if everything is the same then add to the base page as well
	-- only matters if is a switchfo
	if ibox.switchfo then
		if not catargs.restriction.one_defined then
			-- no restriction set == all of them are surface
			mw.smw.set({['Location restriction'] = 'surface'})
		else
			if args.restriction.switches then
				local val = args.restriction.switches[1] or args.restriction.d or restriction_map.surface
				local all_the_same = true
				for i,v in ipairs(args.restriction.switches) do
					local curr = v or args.restriction.d or restriction_map.surface
					if val ~= curr then
						all_the_same = false
						break
					end
				end
				if all_the_same then
					mw.smw.set({['Location restriction'] = val})
				else
					table.insert(ret, 'Items with mixed restrictions')
				end
			else
				-- not a switchable param
				-- set to d
				mw.smw.set({['Location restriction'] = args.restriction.d or restriction_map.surface})
			end
		end
	end

	-- combine table and format category wikicode
	for i, v in ipairs(ret) do
		ret[i] = string.format('[[Category:%s]]',v)
	end

	return table.concat(ret,'')
end

return p
--</nowiki>