Module:Sandbox/User:Riblet15/Disassemble

Revision as of 21:23, 4 November 2021 by Admin (talk | contribs) (1 revision imported)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Documentation for this module may be created at Module:Sandbox/User:Riblet15/Disassemble/doc

-- <nowiki>
--------------------------
-- Module for [[Template:Disassembly]]
-- Please test changes to this module at [[Module:Disassembly]] first
--------------------------
local p = {}
local infobox = require('Module:Infobox')
local onmain = require('Module:Mainonly').on_main
local disdata = mw.loadData('Module:Disassemble/data')
local materials = mw.loadData('Module:Disassemble/mats')
local matchances = mw.loadData('Module:Disassembly material calculator/data')
local tooltips = require('Module:Tooltip')
local yesno = require('Module:Yesno')
local divineChargePrice = require('Module:ExchangeLite').price('Divine charge')

local junkpast75 = {
	[75] = 4.2, [76] = 3.8, [77] = 3.4, [78] = 3.0, [79] = 2.7,
	[80] = 2.3, [81] = 2.0, [82] = 1.7, [83] = 1.4, [84] = 1.2,
	[85] = 1.0, [86] = 0.8, [87] = 0.6, [88] = 0.4, [89] = 0.3
}

local junkreduction = {
	{34, 0.99}, {49, 0.97}, {64, 0.95}, {69, 0.93}, {78, 0.91}, {83, 0.88}, {91, 0.86}, {95, 0.83}, {105, 0.8}
}

function sigfig(n, f)
	f = math.floor(f-1)
	if n == 0 then return 0 end
	local m = math.floor(math.log10(n))
	local v = n / (10^(m-f))
	v = math.floor(v) * 10^(m-f)
	return v
end

function p.main(frame)
	local args = frame:getParent().args

	local ret = infobox.new(args)

	ret:defineParams{
		{ name = 'name', func = 'name' },
		{ name = 'category', func = getCategory, dupes = true },
		{ name = 'catnames', func = { name = getCategoryNames, params = { 'category' } }, dupes = true },
		{ name = 'catlink', func = { name = getCategoryLink, params = { 'category' } }, dupes = true },
		{ name = 'often_mats', func = { name = getMats, params = { 'category', 'often', 'often' }, flag = { 'd', 'd', 'r' } } },
		{ name = 'sometimes_mats', func = { name = getMats, params = { 'category', 'sometimes', 'sometimes' }, flag = { 'd', 'd', 'r' } } },
		{ name = 'rarely_mats', func = { name = getMats, params = { 'category', 'rarely', 'rarely' }, flag = { 'd', 'd', 'r' } } },
		{ name = 'mat_chances', func = { name = getMatChances, params = { 'category', 'often_mats', 'sometimes_mats', 'rarely_mats' } } },
		{ name = 'level', func = 'numbers', dupes = true },
		{ name = 'x10', func = { name = getX10, params = { 'category', 'x10' } }, dupes = true },
		{ name = 'xp', func = { name = getXP, params = { 'level', 'x10' } } },
		{ name = 'junkraw', func = { name = getRawJunk, params = { 'level' } } },
		{ name = 'junk', func = { name = getJunk, params = { 'junkraw' } } },
		{ name = 'compqty', func = { name = getCompQty, params = { 'category', 'compqty' } } },
		{ name = 'itemqty', func = { name = getItemQty, params = { 'category', 'itemqty' } } },
		{ name = 'special_mats', func = { name = getSpecialMats, params = { 'special' } } },
		{ name = 'specialchance', func = { name = getSpecialChance, params = { 'specialchance' }, flag = { 'd' } }, dupes = true },
		{ name = 'calcvalue', func = 'hascontent' },
		{ name = 'calccomp', func = 'hascontent' },
		{ name = 'augmented', func = 'hascontent' },
		{ name = 'allspecmats', func = allspecsarg },
		{ name = 'returnsitems', func = getReturnedItems },
		{ name = 'smwJSON', func = { name = smwarg, params = { 'category', 'often_mats', 'sometimes_mats', 'rarely_mats', 'mat_chances', 'level', 'xp', 'junkraw', 'itemqty', 'compqty', 'special_mats', 'specialchance' }, flag = 'd' } }
	}

	ret:setMaxButtons('8')

	ret:create()
	ret:cleanParams()
	ret:customButtonPlacement(true)
	
	ret:defineLinks({hide=true})

	-- Unique anchor for linking from Infobox Item
	ret:addClass('rsw-infobox-disassembly infobox-disassembly-migration')
	ret:attr({ id = 'DisassemblyT' })
	ret:float('none')
	ret:css({ width = '300px',
		    float = 'none',
			margin = '.8em 0' })

	ret:useSMW({
		junkraw = 'Junk chance',
	})
	ret:useSMWSubobject({
		smwJSON = 'Disassembly JSON',
	})
	ret:defineName('Disassembly')
	
	ret:addButtonsCaption()

	ret:addRow {
		{ tag = 'argh', class = 'infobox-header', content = 'name', colspan = '2' },
	}
	:addRow{
		{ tag = 'th', content = '[[Template:Disassembly/FAQ#What is disassembly category?|Disassembly category]]', css = { ['text-align'] = 'left', width = '70%' }, title = 'Disassembly group this item belongs to' },
		{ tag = 'argd', content = 'catlink', css = { ['text-align'] = 'right' } }
	}
	--[=[ not adding this row as it would probably just add confusion with a required level
	:addRow{
		{ tag = 'th', content = '[[Disassemble#Item level|Disassembly level]]', css = { ['text-align'] = 'left', width = '70%' }, title = 'Internal item level for disassembly (this is not a requirement)' },
		{ tag = 'argd', content = 'level', css = { ['text-align'] = 'right' } }
	}
	--]=]
	:addRow{
		{ tag = 'th', content = '[[Disassemble#Experience|Disassembly XP]]', css = { ['text-align'] = 'left' }, title = 'Experience received for disassembling' },
		{ tag = 'argd', content = 'xp', css = { ['text-align'] = 'right' }, attr = {['data-discalc-xp'] = ret:param('xp', 'r')} }
	}

	:addRow{
		{ tag = 'th', content = 'Item quantity required', css = { ['text-align'] = 'left' }, title = 'Amount disassembled per action' },
		{ tag = 'argd', content = 'itemqty', css = { ['text-align'] = 'right' }, attr = {['data-discalc-iqty'] = ret:param('itemqty', 'r')} }
	}

	:addRow{
		{ tag = 'th', content = 'Material count', css = { ['text-align'] = 'left' }, title = 'The number of materials received normally (not including specials); shown in chat window' },
		{ tag = 'argd', content = 'compqty', css = { ['text-align'] = 'right' }, attr = {['data-discalc-cqty'] = ret:param('compqty', 'r')} }
	}

	:addRow{
		{ tag = 'th', content = 'Base [[junk]] chance', css = { ['text-align'] = 'left' }, title = 'Base chance of receiving junk' },
		{ tag = 'argd', content = 'junk', css = { ['text-align'] = 'right' }, attr = {['data-discalc-junk'] = ret:param('junkraw', 'r')} }
	}

	-- plink function for materials
	local function matRow(builder, matname, chance)
		local th = builder:tag('th')
		builder:addClass('disassembly-material-row')
		
		th:wikitext(string.format('[[File:%s.png|25px|link=%s]] [[%s]]',matname,matname,matname))
			:attr('data-discalc-mat', matname)
			:css({['text-align'] = 'left', ['font-weight'] = 'normal'})
		
		if chance[1] then
			local frac = chance[1]..'/'..chance[2]
			local oneover = '1/'..sigfig(chance[2] / chance[1], 4)
			local percent = sigfig(100 * chance[1] / chance[2], 4)
			local permil = sigfig(1000 * chance[1] / chance[2], 5)
			local permyriad = sigfig(10000 * chance[1] / chance[2], 6)
			builder:tag('td')
						:wikitext(frac)
						:css('text-align', 'right')
						:attr({
							['title'] = 'These are the chances of getting this material from one of the rolls for this item, after junk. See FAQ for more information.',
							['data-discalc-chance'] = percent,
							['data-discalc-chance-percent'] = percent,
							['data-discalc-chance-permil'] = permil,
							['data-discalc-chance-permyriad'] = permyriad,
							['data-discalc-chance-fraction'] = frac,
							['data-discalc-chance-oneover'] = oneover,
							
						})
					:done()
		else
			td:attr('colspan', '2')
		end
		
	end

	local returneditems = ret:param('returnsitems', 'd')
	if  returneditems[1] and returneditems[1]:find('%S') then
		ret:addRow{
			{ tag = 'th', content = 'Returned items', colspan = '2', class = 'infobox-header', css = { ['border-bottom'] = 'none' } },
		}
		for _,v in ipairs(returneditems) do
			ret:tag('tr')
				:tag('td')
					:attr('colspan', '2')
					:wikitext(string.format('[[File:%s.png|link=%s]] [[%s]]',v,v,v))
				:done()
			:done()
		end
	end
	
	local chances = ret:param('mat_chances', 'd')
	local calcval = ret:param('calcvalue','d')
	local calccomp = ret:param('calccomp','d')
	if not infobox.isDefined(calcval) then
		calcval = nil
	end
	if not infobox.isDefined(calccomp) then
		calccomp = nil
	end

	-- get materials in table
	local spec = ret:param('special_mats', 'd')
	local schance = ret:param('specialchance','d')
	local oftn = ret:param('often_mats', 'd')
	local smts = ret:param('sometimes_mats', 'd')
	local rrly = ret:param('rarely_mats', 'd')

	-- only add row if specials are there
	if spec[1] then
		local allspec = ret:param('allspecmats','d')
		ret:addRow{
				{ tag = 'th', content = 'Special materials', colspan = '2', class = 'infobox-subheader', title = 'The number of special materials received (if any are received); note that the number of special materials received is independent of the total materials and junk chance listed above. See FAQ for more information.' }
			}
		local speccount = table.maxn(spec)
		local _tr, _th
		for i, v in ipairs(spec) do
			_tr = ret:tag('tr')
			_tr:addClass('disassembly-material-row-special')
			_th = _tr:tag('th')
			_th:wikitext(mw.ustring.format('%s × [[File:%s.png|25px|link=%s]] [[%s]]', v.q,v.n,v.n,v.n))
				:attr({
					['data-discalc-special-qty'] = v.q,
					['data-discalc-special-name'] = v.n
				})
				:css({['text-align'] = 'left', ['font-weight'] = 'normal'})
			local _chance = v.c
			if speccount == 1 and schance == true then
				_chance = { 1, 1}
			end
			
			local spcell, sptitle
			local percent, permil, permyriad, frac, oneover
			if _chance and _chance[1] then
				frac = _chance[1] .. '/' .. _chance[2]
				percent = sigfig(100 * _chance[1] / _chance[2], 4)
				permil = sigfig(1000 * _chance[1] / _chance[2], 5)
				permyriad = sigfig(10000 * _chance[1] / _chance[2], 6)
				oneover = '1/'..  sigfig(_chance[2] / _chance[1], 4)
				spcell = frac
				sptitle = 'The chance of getting this material by disassembling this item - as a special material, this ignores junk chance. See FAQ for more information.'
			elseif schance == false then
				spcell = 'Not 100%'
				sptitle = 'This special material is not guaranteed, and the actual chance of getting it is not known. See FAQ for more information.'
			else
				spcell = 'Unknown'
				sptitle = 'The chance of receiving this special material is not known, including whether it is guaranteed or not. See FAQ for more information.'
			end
			if spcell == '1/1' then
				spcell = 'Always'
				frac = 'Always'
				oneover = 'Always'
			end
			
			_tr:tag('td')
						:wikitext(spcell)
						:css({['text-align'] = 'right', ['vertical-align'] = 'middle'})
						:attr({
							['title'] = sptitle,
							['data-discalc-special-chance'] = percent,
							['data-discalc-chance-percent'] = percent,
							['data-discalc-chance-permil'] = permil,
							['data-discalc-chance-permyriad'] = permyriad,
							['data-discalc-chance-fraction'] = frac,
							['data-discalc-chance-oneover'] = oneover,
						})
					:done()
			
			
			
			if i < speccount and allspec ~= 'yes' then
				ret	:tag('tr')
						:tag('td')
							:attr('colspan','2')
							:css({ ['text-align'] = 'center' })
							:wikitext("''OR''")
						:done()
					:done()
			end
		end
	end

	-- only add row if often mats are there
	if oftn[1] then
		ret:addRow{
				{ tag = 'th', content = 'Common materials', colspan = '2', class = 'infobox-subheader' }
			}
		for _, v in ipairs(oftn) do
			matRow(ret:tag('tr'), v, { chances[v], chances._total })
		end
	end

	-- only add row if sometimes mats are there
	if smts[1] then
		ret:addRow{
				{ tag = 'th', content = 'Uncommon materials', colspan = '2', class = 'infobox-subheader' }
			}
		for _, v in ipairs(smts) do
			matRow(ret:tag('tr'), v, { chances[v], chances._total })
		end
	end

	-- only add row if rarely mats are there
	if rrly[1] then
		ret:addRow{
				{ tag = 'th', content = 'Rare materials', colspan = '2', class = 'infobox-subheader' }
			}
		for _, v in ipairs(rrly) do
			matRow(ret:tag('tr'), v, { chances[v], chances._total })
		end
	end

	ret:addRow{ {
		tag = 'td',
		content = '',
		colspan = '2',
		class = 'disassembly-materials-header',
		css = { ['text-align'] = 'center' },
		attr = { 
			['data-discalc-mastermod'] = chances['_master_modifier'],
			['data-discalc-isaugmented'] = tostring(infobox.isDefined(ret:param('augmented','d'))),
			['data-discalc-divchprice'] = divineChargePrice,
			['data-discalc-calcval'] = calcval,
			['data-discalc-calccomp'] = calccomp
		}
	} }

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

	ret:finish()
	return ret:tostring()
end

-- param parsing

-- gets the category table
function getCategory(arg)
	local _arg = string.lower(arg or '')

	if _arg == 'no' or _arg == 'custom' then
		return 'custom'
	elseif disdata[_arg] then
		_arg = _arg
	else
		_arg = nil
	end

	return _arg
end

-- category name, for categorising article
function getCategoryNames(arg)
	local _arg = string.lower(arg or '')

	if _arg == 'custom' then
		return 'custom'
	elseif disdata[_arg] then
		local cat = disdata[_arg] or {}
		_arg = cat.cat or 'custom'
	else
		_arg = nil
	end

	return _arg
end

-- category link
function getCategoryLink(arg)
	local _arg = string.lower(arg or '')
	local noneRet = 'None'
	local text
	local link

	if _arg == 'custom' then
		return noneRet
	elseif disdata[_arg] then
		local cat = disdata[_arg] or {}
		text = cat.name
		link = cat.cat
		if not link or not text then
			return noneRet
		end
		_arg = string.format('[[:Category:Disassemble category/%s|%s]]', link, text)
	else
		return nil
	end

	return _arg
end

-- table of mats
-- only accepts materials that exist in the /mats page
-- arg is value passed to template, unused unless custom
function getMats(_cat,arg,mattype)
	local mat_list

	if _cat == 'custom' then
		mat_list = string.lower(arg or '')
	else
		local cat = disdata[_cat] or {}
		mat_list = cat[mattype]
	end

	if not mat_list then
		return {}
	end

	local ret = {}

	for _, v in ipairs(mw.text.split(mat_list,',')) do
		local _v = string.lower(v)
				:gsub('components?','')
				:gsub('parts?','')

		_v = mw.text.trim(_v)

		local _name = materials[_v]
		if _name then
			table.insert(ret,_name)
		end
	end

	return ret
end

-- material chances
function getMatChances(_cat, often_mats, sometimes_mats, rarely_mats)
	local ret = {}
	if _cat ~= 'custom' and disdata[_cat] then
		local cat = disdata[_cat].cat
		local chances = matchances[cat]
		if chances then
			for i,v in pairs(chances) do
				if string.sub(i, 1, 1) == '_' then
					ret[i] = v
				else
					ret[materials[i]] = v
				end
			end
		end
	end
	return ret
end


-- special materials
-- handled differently from normal mats
function getSpecialMats(arg)
	local mat_list = string.lower(arg or '')

	if not mat_list then
		return {}
	end

	local ret = {}

	for _, v in ipairs(mw.text.split(mat_list,',')) do
		local _v = string.lower(v)
				:gsub(' ?components?','')
				:gsub(' ?parts?','')

		_v = mw.text.trim(_v)
		local _m,_q,_c = _v:match('([%w -]+)%s*%[?(%d*)%]?%s*%{?(%d*%.?%d*/?%d*%.?%d*)%}?')
		local _name = materials[_m]
		local _chance = nil
		if _c and _c ~= '' then
			if _c:find('/') then
				_c = mw.text.split(_c, '/')
				_chance = { tonumber(_c[1]), tonumber(_c[2]) }
			else
				_c = tonumber(_c)
				if math.floor(100/_c) == 100/_c then
					_chance = { 1, 100/_c }
				else
					_chance = { _c, 100 }
				end
			end
		end
		
		if _name then
			table.insert(ret,{ n = _name, q = tonumber(_q) or '?', c = _chance })
		end
	end

	return ret
end

function getSpecialChance(arg)
	if arg == '' or arg == nil then
		return nil --unknown
	else
		return yesno(arg)
	end
end


-- raw value of junk for parsing and properties
function getRawJunk(_l)
	local l = tonumber(_l)

	if not l then
		return nil
	end

	if l >= 90 then
		return 0
	end

	if l >= 75 then
		return junkpast75[l]
	end

	local junk = 1000 - 11 * l

	junk = math.floor(junk)/10

	return junk
end

-- parses junk to a string
-- adds '%'
-- adds '.0' to integers
function getJunk(_j)
	local junknum = tonumber(_j)

	if not junknum then
		return nil
	end

	-- function to add '.0' to integers
	-- needed for full table in tool tip as well
	local function point0(j)
		local _j = math.floor(10 * j)/10
		return string.format('%.1f', _j)
	end
	local function junkRound(x)
		return string.format('%.1f', math.ceil(10*x) / 10)
	end

	local _junk = point0(junknum)
	local junk = _junk .. '%'
	local junk_out = '<span class="rsw-discalc-junknum">'.._junk..'</span>%'

	local tooltip_span = ''
	local tooltip_div = nil
	
	-- add tool tip if not 0 junk
	if (tonumber(junknum) or -1) > 0 then
	
		local junktstr = 'Your actual junk chance depends on your junk chance reduction researched.<br />See the table below for all values, and [[Junk]] for more information.'
		local junkttable = mw.html.create('table')
		junkttable	:addClass('wikitable')
					:css({
						['text-align'] = 'right',
						margin = '0 auto',
					})
					:tag('tr')
						:tag('th')
							:wikitext('Reduction')
						:done()
						:tag('th')
							:wikitext('[[File:Invention-icon.png|link=Invention]]')
						:done()
						:tag('th')
							:wikitext('Junk chance')
						:done()
					:done()
					:tag('tr')
						:tag('td')
							:wikitext('None')
						:done()
						:tag('td')
							:wikitext(1)
						:done()
						:tag('td')
							:wikitext(junk)
						:done()
					:done()
		
		for i,v in ipairs(junkreduction) do
			junkttable	:tag('tr')
							:tag('td')
								:wikitext(i)
							:done()
							:tag('td')
								:wikitext(v[1])
							:done()
							:tag('td')
								-- kill rounding errors
								:wikitext(junkRound(junknum * v[2]))
							:done()
						:done()
		end
		
	
		tooltip_span = tostring(tooltips._span({'junkchance' .. junknum})) .. '&nbsp;&nbsp;'
		tooltip_div = tooltips._div({
			name = 'junkchance' .. junknum,
			content = junktstr .. '\n' .. tostring(junkttable),
		})
	end

	-- end o' tool tip

	return tooltip_span..junk_out..tostring(tooltip_div or '')
end


-- experience multiplier
-- taken from category
-- uses value passed to template if custom
function getX10(_cat,x10)
	local xx10
	if _cat == 'custom' then
		xx10 = string.lower(x10 or 'no')
	else
		local cat = disdata[_cat] or {}
		xx10 = tostring(cat.x10)
	end

	return yesno(xx10,false)
end

-- get xp from level
function getXP(_l,x10)
	local l = tonumber(_l)

	if not l then
		l = 1
	end

	local mult = yesno(tostring(x10)) and 10 or 1

	local xp = math.max(math.floor(l * 0.03 * mult * 1000 + 0.009)/1000,0.1)

	xp = math.floor(xp * 10 + 0.05)/10

	if xp % 1 == 0 then
		xp = xp..'.0'
	end

	return xp
end

-- get number of materials received
function getCompQty(_cat,qty)
	local compqty
	if _cat == 'custom' then
		compqty = string.lower(qty or 'no')
	else
		local cat = disdata[_cat] or {}
		compqty = cat.compqty
	end

	return tonumber(compqty)
end

-- get number of items disassembled per action
function getItemQty(_cat,qty)
	local itemqty
	if _cat == 'custom' then
		itemqty = string.lower(qty or 'no')
	else
		local cat = disdata[_cat] or {}
		itemqty = cat.itemqty
	end

	return tonumber(itemqty) or 1
end

-- spec amounts
function allspecsarg(arg)
	arg = arg or 'no'
	return string.lower(arg)
end

-- returned items eg refined anima core
function getReturnedItems(arg)
	local ret = {}
	if arg then
		ret = mw.text.split(arg, '%s?;%s?')
	end
	return ret
end

-- SMW JSON
function smwarg(cat, often, sometimes, rarely, chances, level, xp, junk, iqty, cqty, special, spchance)
	local smwJSON = {
		category = cat,
		item_quantity = iqty,
		mat_quantity = cqty,
		level = level,
		xp = xp,
		often = {},
		sometimes = {},
		rarely = {},
		special = {},
	}
	if type(junk) == 'number' then
		smwJSON.junk = { junk }
		for i,v in ipairs(junkreduction) do
			table.insert(smwJSON.junk, junk * v[2])
		end
	end
	for i,v in ipairs(often) do
		table.insert(smwJSON.often, { name = v, chance = chances[v] })
	end
	if #smwJSON.often == 0 then
		smwJSON.often = nil
	end
	for i,v in ipairs(sometimes) do
		table.insert(smwJSON.sometimes, { name = v, chance = chances[v] })
	end	
	if #smwJSON.sometimes == 0 then
		smwJSON.sometimes = nil
	end
	for i,v in ipairs(rarely) do
		table.insert(smwJSON.rarely, { name = v, chance = chances[v] })
	end	
	if #smwJSON.rarely == 0 then
		smwJSON.rarely = nil
	end
	for i,v in ipairs(special) do
		local _c = v.c
		if table.maxn(special) == 1 and ( (type(v.q) == 'number' and v.q > 1) or spchance == true) then
			_c = 100
		end
		table.insert(smwJSON.special, { name = v.n, quantity = v.q, chance = _c })
	end
	if #smwJSON.special == 0 then
		smwJSON.special = nil
	end
	
	local jsg, json = pcall(mw.text.jsonEncode, smwJSON)
	if jsg then
		return mw.text.nowiki(json)
	end
	return nil
end

-- categories
function addCategories(args,cdata)
	local ret = {}

	-- new table for special materials' names only
	local spec_mats = {}
	local has_spec = false
	for _, v in ipairs(args.special_mats.d) do
		table.insert(spec_mats,v.n)
		has_spec = true
	end

	-- iterate over all materials and add categories
	for _, v in ipairs({
				args.often_mats.d,
				args.sometimes_mats.d,
				args.rarely_mats.d,
				spec_mats
			}) do
		for _, w in ipairs(v) do
			table.insert(ret,string.format('Items that disassemble into %s',w))
		end
	end

	-- add category based on disassembly category
	-- custom = nothing
	-- not defined = tracking category
	if args.catnames.d == 'custom' then
		table.insert(ret, 'Custom disassembly category')
	elseif infobox.isDefined(args.catnames.d) then
		table.insert(ret,string.format('Disassemble category/%s',args.catnames.d))
	else
		table.insert(ret,'Missing disassembly category')
	end

	-- same deal, but with the other categories
	-- commenting out for now since only the default category affects materials
	-- switched categories can only affect comp/mat qty
	--[=[
	local catswitches = args.catnames.switches
	if catswitches then
		for _, v in ipairs(catswitches) do
			if infobox.isDefined(v) then
				if v == 'custom' then
					-- nothing
				else
					table.insert(ret,string.format('Disassemble category/%s',v))
				end
			else
				table.insert(ret,'Missing disassembly category')
			end
		end
	end
	--]=]
	-- special mats
	if has_spec then
		table.insert(ret, 'Items that can disassemble into special materials')
		if not cdata.specialchance.all_defined then
			table.insert(ret,'Missing special chance')
		end
		-- check that all materials have a quantity
		local check_spec = #args.special_mats.d > 1
		for _, v in ipairs(args.special_mats.d) do
			if type(v.q) ~= 'number' then
				table.insert(ret,'Missing special material quantity')
			else
				if ((v.q == 1 and args.specialchance.d ~= true) or check_spec) and not v.c then
					table.insert(ret, 'Missing special material chance')
				end
			end
		end
	end

	if args.junkraw.d == 0 then
		table.insert(ret,'Items that cannot disassemble into Junk')
	elseif args.junkraw.switches then
		for _, v in ipairs(args.junkraw.switches) do
			if v == 0 then
				table.insert(ret,'Items that cannot disassemble into Junk')
				break
			end
		end
	end

	-- if default level isn't defined
	-- see if switches exist
	-- if any switch isn't defined and default isn't, add maintenance cat
	-- if switches don't exist, and default isn't defined, add maintenance cat
	if not infobox.isDefined(args.level.d) then
		if args.level.switches then
			for _, v in ipairs(args.level.switches) do
				if not infobox.isDefined(v) then
					table.insert(ret,'Missing Invention disassembly level')
				end
			end
		else
			table.insert(ret,'Missing Invention disassembly level')
		end
	end

	if infobox.isDefined(args.calcvalue.d) then
		table.insert(ret,'Disassembly calculator override')
	end
	
	-- add to the disassembly calculator for junk if the base junk is more than 20000 per hour
	local dis_per_hour = 3000
	local calc_cat_limit = 19999
	if type(args.junkraw.d) == 'number' then
		if args.junkraw.switches then
			for _,v in ipairs(args.junkraw.switches) do
				if type(args.compqty.d) == 'number' then
					if not args.compqty.switches and v/100 * args.compqty.d * dis_per_hour > calc_cat_limit then
						table.insert(ret,'Disassembly junk calculator')
					end
				end
			end
		else
			if type(args.compqty.d) == 'number' then
				if args.compqty.switches then
					for _,v in ipairs(args.compqty.switches) do
							-- ignore if both have switches
						v = tonumber(v)
						if v then
							if not args.junkraw.switches and v * args.junkraw.d/100 * dis_per_hour > calc_cat_limit then
								table.insert(ret,'Disassembly junk calculator')
							end
						end
					
					end
				else
					if args.junkraw.d/100 * args.compqty.d * dis_per_hour > calc_cat_limit then
						table.insert(ret,'Disassembly junk calculator')
					end
				end
			end
		end
	end

	-- clean return string
	local cats = {}

	for _, v in ipairs(ret) do
		table.insert(cats,string.format('[[Category:%s]]',v))
	end

	return table.concat(cats,'\n')
end

return p
--</nowiki>