Module:Infobox Bonuses new

Revision as of 09:48, 28 September 2021 by en>Gaz Lloyd
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Documentation for this module may be created at Module:Infobox Bonuses new/doc

-- <nowiki>
--[=[
-- Implements [[Template:Infobox Bonuses]]
--]=]

local p = {}
local infobox = require('Module:Infobox')
local onmain = require('Module:Mainonly').on_main
local paramtest = require('Module:Paramtest')
local yesno = require('Module:Yesno')
local skillpic = require('Module:Skill clickpic')._main
local editbutton = require('Module:Edit button')
local commas = require('Module:Addcommas')._add
local chargedrain = require('Module:Augmented degrade')
local attack_speed_bar = require('Module:Attack speed bar').weapon

-- Accepted slot names
local slots = {
	head = 'head',
	neck = 'neck',
	back = 'back',
	cape = 'back',
	torso = 'torso',
	body = 'torso',
	legs = 'legs',
	hands = 'hands',
	feet = 'feet',
	ammo = 'ammo',
	ring = 'ring',
	aura = 'aura',
	pocket = 'pocket',
	sigil = 'sigil',
	main = 'main hand weapon',
	['main hand'] = 'main hand weapon',
	['main-hand'] = 'main hand weapon',
	mainhand = 'main hand weapon',
	weapon = 'main hand weapon',
	['2h'] = '2h weapon',
	['off-hand'] = 'off-hand',
	offhand = 'off-hand',
	shield = 'off-hand',
	['off-hand weapon'] = 'off-hand weapon',
	['offhand weapon'] = 'off-hand weapon',
	ohw = 'off-hand weapon',
	set = 'e',
	none = 'e'
}

-- Categories for slots
local slot_cats = {
	head = 'Head slot items',
	neck = 'Neck slot items',
	back = 'Back slot items',
	torso = 'Torso slot items',
	legs = 'Legs slot items',
	hands = 'Hand slot items',
	feet = 'Feet slot items',
	ammo = 'Ammunition slot items',
	ring = 'Rings',
	aura = 'Auras',
	pocket = 'Pocket slot items',
	sigil = 'Sigil slot items',
	['main hand weapon'] = 'Main hand slot items',
	['2h weapon'] = 'Two-handed slot items',
	['off-hand'] = 'Off-hand slot items',
	['off-hand weapon'] = 'Off-hand slot weapon',
	e = ''
}

-- Images used for slot display
local slot_images = {
	head = '[[File:Head slot.png|link=Head slot]]',
	ammo = '[[File:Ammo slot.png|link=Ammunition slot]]',
	neck = '[[File:Neck slot.png|link=Neck slot]]',
	back = '[[File:Back slot.png|link=Back slot]]',
	['main hand weapon'] = '[[File:Main hand slot.png|link=Main hand slot]]',
	['2h weapon'] = '[[File:2h slot.png|link=Two-handed slot]]',
	torso = '[[File:Torso slot.png|link=Torso slot]]',
	['off-hand'] = '[[File:Off-hand slot.png|link=Off-hand slot]]',
	['off-hand weapon'] = '[[File:Off-hand slot.png|link=Off-hand slot]]',
	legs = '[[File:Legs slot.png|link=Legs slot]]',
	hands = '[[File:Gloves slot.png|link=Hands slot]]',
	feet = '[[File:Feet slot.png|link=Feet slot]]',
	ring = '[[File:Ring slot.png|link=Ring slot]]',
	aura = '[[File:Aura slot.png|link=Aura slot]]',
	pocket = '[[File:Pocket slot.png|link=Pocket slot]]',
	sigil = '[[File:Sigil slot.png|link=Sigil slot]]',
	e = 'None'
}

-- 'invention slots'
local inv_slots = {
	['main hand weapon'] = 'mh',
	['2h weapon'] = '2h',
	['off-hand'] = 'oh',
	['off-hand weapon'] = 'oh',
	shield = 'shield',
	torso = 'body',
	legs = 'legs',
	tool = 'tool',
	t = 'tool'
}

-- Accepted class names
local classes = {
	melee = 'melee',
	ranged = 'ranged',
	ranging = 'ranged',
	range = 'ranged',
	magic = 'magic',
	mage = 'magic',
	all = 'all',
	hybrid = 'hybrid',
	none = 'none',
	['n/a'] = 'none'
}

-- Classes with images
local class_img = {
	melee = '[[File:Attack.png|x24px|link=Melee]]',
	ranged = '[[File:Ranged.png|x24px|link=Ranged]]',
	magic = '[[File:Magic.png|x24px|link=Magic]]',
	hybrid = '[[File:CombatSwords.png|x24px|link=Armour#Hybrid]]',
	all = '[[File:CombatSwords.png|x24px|link=Armour#All]]',
	none = ''
}

local class_cats = {
	melee = 'Melee',
	magic = 'Magic',
	ranged = 'Ranged',
	hybrid = 'Hybrid',
	all = 'Hybrid',
	none = 'Typeless',
}

-- Accepted style names
local styles = {
	stab = 'stab',
	stabbing = 'stab',
	slash = 'slash',
	slashing = 'slash',
	crush = 'crush',
	crushing = 'crush',
	arrow = 'arrows',
	arrows = 'arrows',
	bolt = 'bolts',
	bolts = 'bolts',
	thrown = 'thrown',
	throwing = 'thrown',
	magic = 'spell-casting',
	spell = 'spell-casting',
	spells = 'spell-casting',
	none = '-',
	['n/a'] = '-',
	no = '-'
}

-- Categories for styles
local style_cats = {
	stab = 'Stab weapons',
	slash = 'Slash weapons',
	crush = 'Crush weapons',
	thrown = 'Thrown weapons'
}

local types = {
	power = 'Power armour',
	['power armour'] = 'Power armour',
	tank = 'Tank armour',
	['tank armour'] = 'Tank armour',
	pvp = 'PvP armour',
	['pvp armour'] = 'PvP armour',
	shieldbow = 'Shieldbow',
	shortbow = 'Shortbow',
	defender = 'Defender',
	repriser = 'Repriser',
	rebounder = 'Rebounder',
	halberd = 'Halberd',
	shield = 'Shield',
	chargebow = 'Chargebow',
	cosmetic = 'Cosmetic',
	['prevents attack'] = 'Prevents attack',
	-- weapon diversity
	dagger = 'Dagger',
	spear = 'Spear',
	scimitar = 'Scimitar',
	['2h sword'] = '2h sword',
	['two handed sword'] = '2h sword',
	mace = 'Mace',
	maul = 'Maul',
	['1h crossbow'] = 'Crossbow',
	['one handed crossbow'] = 'Crossbow',
	['crossbow'] = 'Crossbow',
	['2h crossbow'] = '2h crossbow',
	['two handed crossbow'] = '2h crossbow',
	['throwing knife'] = 'Throwing knife',
	['throwing axe'] = 'Throwing axe',
}

local type_cats = {
	['cosmetic'] = { '[[Cosmetic]]' },
	['power armour'] = { '[[Power armour]]', 'Power armour' },
	['tank armour'] = { '[[Tank armour]]', 'Tank armour' },
	['pvp armour'] = { '[[PvP armour]]', 'PvP armour' },
	shieldbow = { '[[Shieldbow (bow type)|Shieldbow]]', 'Shieldbows' },
	shortbow = { '[[Shortbow (bow type)|Shortbow]]','Shortbows' },
	defender = { '[[Defender]]', 'Defenders' },
	repriser = { '[[Repriser]]', 'Defenders' },
	rebounder = { '[[Rebounder]]', 'Defenders' },
	halberd = { '[[Halberd]]', 'Halberds' },
	shield = { '[[Shield]]', 'Shields' },
	chargebow = { '[[Chargebow (bow type)|Chargebow]]', 'Chargebows' },
	['prevents attack'] = { 'Prevents attack', 'Items which prevent attack' },
	dagger = { '[[Dagger]]', 'Daggers' },
	spear = { '[[Spear]]', 'Spears' },
	scimitar = { '[[Scimitar]]', 'Scimitars' },
	['2h sword'] = { '[[Two-handed sword|2h sword]]', 'Two-handed swords'},
	mace = { '[[Mace]]', 'Maces' },
	maul = { '[[Maul]]', 'Mauls' },
	['crossbow'] = { '[[Crossbow (weapon type)|Crossbow]]', 'One-handed crossbows' },
	['2h crossbow'] = { '[[Two-handed crossbow|2h crossbow]]', 'Two-handed crossbows' },
	['throwing knife'] = { '[[Throwing knife]]', 'Throwing knives' },
	['throwing axe'] = { '[[Throwing axes|Throwing axe]]', 'Throwing axes' },
}

local reduction_types = { -- accepts type or class
	['tank armour'] = 'tank',
	['power armour'] = 'other',
	['pvp armour'] = 'pvp',
	['shield'] = 'shield',
	['shieldbow'] = 'shield',
	['hybrid'] = 'other',
	['all'] = 'other'
}

local reductions = { -- type -> slot -> pvm, pvp multipliers
	tank = {
		head = {pvm = 0.02, pvp = 0.0375},
		torso = {pvm = 0.02, pvp = 0.06},
		legs = {pvm = 0.02, pvp = 0.0525},
		hands = {pvm = 0.02, pvp = 0},
		feet = {pvm = 0.02, pvp = 0}
	},
	pvp = {
		head = {pvm = 0, pvp = 0.0375},
		torso = {pvm = 0, pvp = 0.06},
		legs = {pvm = 0, pvp = 0.0525}
	},
	other = { --power, hybrid, all
		head = {pvm = 0, pvp = 0.01875},
		torso = {pvm = 0, pvp = 0.03},
		legs = {pvm = 0, pvp = 0.02625}
	},
	shield = { --because hybrid shields are a thing
		 -- for consistency to continue allowing reduction.type.slot.pvx
		['off-hand'] = {pvm = 0.1, pvp = 0},
		['2h weapon'] = {pvm = 0.1, pvp = 0}, --shieldbows pls
	}
}


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

	ret:defineParams{
		{ name = 'image', func = { name = imagearg, params = { 'image' }, flag = 'p' } },
		{ name = 'altimage', func = { name = imagearg, params = { 'altimage' }, flag = 'p' } },
		{ name = 'noimgcat', func = { name = noimgcatarg, params = { 'noimgcat', 'image' }, flag='p' } },
		{ name = 'requirements', func = requirementsarg },
		{ name = 'class', func = { name = lookuparg, params = { classes, 'class' }, flag = { 'r', 'd' } } },
		{ name = 'classimg', func = { name = lookuparg, params = { class_img, 'class' }, flag = { 'r', 'd' } } },
		{ name = 'classstr', func = { name = classstrarg, params = {'class', 'classimg' }, flag = 'd' } },
		{ name = 'class_smw', func = { name = classsmwarg, params = { 'class' }, flag = { 'd' } } },
		{ name = 'tier', func = tierarg },
		{ name = 'type', func = { name = lookupmultiarg, params = { types, 'type' }, flag = { 'r', 'd' } } },
		{ name = 'type_disp', func = { name = type_display, params = { 'type' }, flag = { 'd' } } },
		{ name = 'slot', func = { name = lookuparg, params = { slots, 'slot' }, flag = { 'r', 'd' } } },
		{ name = 'slotimg', func = { name = lookuparg, params = { slot_images, 'slot' }, flag = { 'r', 'd' } } },
		{ name = 'isweapon', func = { name = isweaponarg, params = { 'slot', 'type' }, flag = 'd' } },
		{ name = 'invdegrade', func = { name = invdegradearg, params = { 'invtier', 'invslot', 'slot' }, flag = 'd' } },
		{ name = 'invdegrade_span', func = { name = invdegradetooltipspanarg, params = { 'invtier', 'invslot', 'slot' }, flag = 'd' } },
		{ name = 'invdegrade_div', func = { name = invdegradetooltipdivarg, params = { 'invtier', 'invslot', 'slot' }, flag = 'd' } },
		{ name = 'normdegrades', func = { name = normdegradesarg, params = { 'degrades' }, flag = 'p' } },
		{ name = 'degradetype', func = { name = degradestypearg, params = { 'normdegrades', 'invdegrade' }, flag = 'd' } },
		{ name = 'degradestr', func = { name = degradesstrarg, params = { 'degradetype', 'normdegrades', 'invdegrade', 'invdegrade_span' }, flag = 'd' } },
		{ name = 'degradeheader', func = { name = degradeheaderarg, params = { 'degradetype' }, flag = 'd' } },
		{ name = 'hasstyle', func = { name = hasstylearg, params = { 'slot', 'type' }, flag = 'd' } },
		{ name = 'style', func = { name = stylearg, params = { 'style', 'hasstyle' }, flag = 'd' } },
		{ name = 'attackrange', func = { name = attackrangearg, params = { 'isweapon', 'attack_range', 'attack range', 'attackrange' }, flag = { 'd', 'p', 'p', 'p' } } },
		{ name = 'attackrangesmw', func = { name = attackrangesmwarg, params = { 'attackrange' }, flag = 'd' } },
		{ name = 'damage', func = fnumbers },
		{ name = 'accuracy', func = fnumbers },
		{ name = 'maindamage', func = { name = mainoffdamarg, params = { 'damage', 'slot', 'damage', {'main hand weapon', '2h weapon', 'ammo'} }, flag = { 'd', 'd', 'p', 'r' } } },
		{ name = 'mainaccuracy', func = { name = mainoffaccarg, params = { 'accuracy', 'slot', {'main hand weapon', '2h weapon'} }, flag = { 'd', 'd', 'r' } } },
		{ name = 'offdamage', func = { name = mainoffdamarg, params = { 'damage', 'slot', 'damage', {'off-hand weapon'} }, flag = { 'd', 'd', 'p', 'r' } } },
		{ name = 'offaccuracy', func = { name = mainoffaccarg, params = { 'accuracy', 'slot', {'off-hand weapon'} }, flag = { 'd', 'd', 'r' } } },
		{ name = 'armour', func = armourarg },
		{ name = 'life', func = fnumbers2 },
		{ name = 'prayer', func = fnumbers2 },
		{ name = 'magic', func = fnumbers },
		{ name = 'strength', func = fnumbers },
		{ name = 'ranged', func = fnumbers },
		{ name = 'magicstr', func = { name = stylebonusesarg, params = { 'magic', 'Magic', 'magic' }, flag = { 'd', 'r', 'p' } } },
		{ name = 'strengthstr', func = { name = stylebonusesarg, params = { 'strength', 'Strength', 'strength' }, flag = { 'd', 'r', 'p' } } },
		{ name = 'rangedstr', func = { name = stylebonusesarg, params = { 'ranged', 'Ranged', 'ranged' }, flag = { 'd', 'r', 'p' } } },
		{ name = 'reductionlevel', func = { name = reductionlevelarg, params = { 'reductionlevel', 'tier', 'requirements' }, flag = { 'p', 'd', 'd' } } },
		{ name = 'pvmreduction', func = { name = reductionarg, params = { 'reductionlevel', 'reductionlevel', 'pvmReduction', 'type', 'slot', 'class', 'pvm' }, flag = { 'p', 'd', 'p', 'd', 'd', 'd', 'r' } } },
		{ name = 'pvpreduction', func = { name = reductionarg, params = { 'reductionlevel', 'reductionlevel', 'pvpReduction', 'type', 'slot', 'class', 'pvp' }, flag = { 'p', 'd', 'p', 'd', 'd', 'd', 'r' } } },
		{ name = 'pvmreductionstr', func = { name = reductionstrarg, params = { 'pvmreduction', "'''PvM: '''" }, flag = { 'd', 'r' } } },
		{ name = 'pvpreductionstr', func = { name = reductionstrarg, params = { 'pvpreduction',  "'''PvP: '''" }, flag = { 'd', 'r' } } },
		{ name = 'speedraw', func = { name = speedrawarg, params = { 'isweapon', 'speed', 'aspeed' }, flag = { 'd', 'p', 'p' } } },
		{ name = 'speed', func = { name = speedarg, params = { 'speedraw' }, flag = 'd' } },
		{ name = 'layouttype', func = { name = layoutarg, params = { 'image', 'altimage' }, flag ='d' } },
		{ name = 'isrecolour', func = { name = recolourarg, params = { 'isrecolour' }, flag = 'd' } },
		
		{ name = 'intbonus', func = { name = intbonusarg, params = { 'armour', 'damage', 'strength', 'ranged', 'magic' }, flag ='p' } },
		
		{ name = 'tier_smw', func = { name = smwtierarg, params = { 'tier' }, flag = 'd' } },
		{ name = 'style_smw', func = { name = smwstylearg, params = { 'style' }, flag = 'd' } },
		{ name = 'damage_smw', func = { name = smwnumbers, params = { 'damage' }, flag = 'd' } },
		{ name = 'accuracy_smw', func = { name = smwnumbers, params = { 'accuracy' }, flag = 'd' } },
		{ name = 'armour_smw', func = { name = smwnumbers2, params = { 'armour' }, flag = 'd' } },
		{ name = 'life_smw', func = { name = smwnumbers2, params = { 'life' }, flag = 'd' } },
		{ name = 'prayer_smw', func = { name = smwnumbers2, params = { 'prayer' }, flag = 'd' } },
		{ name = 'magic_smw', func = { name = smwnumbers, params = { 'magic' }, flag = 'd' } },
		{ name = 'strength_smw', func = { name = smwnumbers, params = { 'strength' }, flag = 'd' } },
		{ name = 'ranged_smw', func = { name = smwnumbers, params = { 'ranged' }, flag = 'd' } },
		{ name = 'charges', func = { name = chargesarg, params = { 'degradetype', 'normdegrades' }, flag = 'd' } },
		{ name = 'invtier', func = { name = invtierarg, params = { 'degradetype', 'invtier' }, flag = { 'd', 'p' } } },
		{ name = 'smwJSON', func = { name = smwjsonarg, params = { 'class', 'slot', 'style_smw', 'type', 'damage_smw', 'accuracy_smw', 'attackrangesmw', 'armour_smw', 'life_smw', 'speedraw', 'prayer_smw', 'strength_smw', 'ranged_smw', 'magic_smw', 'tier_smw', 'charges', 'invtier' }, flag = 'd' } }
	}

	ret:setMaxButtons(7)
	ret:setAddRSWInfoboxClass(false)
	ret:create()
	ret:cleanParams()
	ret:customButtonPlacement(true)
	
	ret:defineLinks({
		colspan = 12,
		links = { 
			{ 'Template:Infobox Bonuses/FAQ', 'FAQ' },
			{ 'Template:Infobox Bonuses', 'docs' }
		}
	})
	ret:css({
		width = '450px',
		float = 'none',
		margin = '0.8em 0'
	})
	
	--smw here later
	ret:useSMWSubobject({
		smwJSON = 'Equipment JSON',
		class_smw = 'Combat class',
		slot = 'Equipment slot',
		type = 'Equipment type',
		damage_smw = 'Weapon damage',
		accuracy_smw = 'Weapon accuracy',
		style_smw = 'Attack style',
		attackrangesmw = 'Attack range',
		armour_smw = 'Equipment armour',
		life_smw = 'Equipment life points',
		speedraw = 'Weapon attack speed',
		prayer_smw = 'Prayer bonus',
		strength_smw = 'Strength bonus',
		ranged_smw = 'Ranged bonus',
		magic_smw = 'Magic bonus',
		tier_smw = 'Equipment tier',
		charges = 'Degradation charges',
		invtier = 'Invention tier',
		isrecolour = 'Is cosmetic recolour',
		intbonus = 'Has integer bonuses',
		pvmreduction = 'PvM damage reduction',
		pvpreduction = 'PvP damage reduction'
	})
	
	ret:addButtonsCaption()

	ret:defineName('Infobox Bonuses')
	ret:addClass('infobox-bonuses wikitable')
	
	local layout = ret:param('layouttype', 'r')
	p['_main'..layout](ret)

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

	return ret:tostring()

end

-- 1 image
function p._mainA(ret)
	local row1 = {
		{ tag = 'th', content = 'Requirements', colspan = 6, class = 'combat-requirements' },
		{ tag = 'argd', content = 'image', colspan = 6, rowspan = 11, class = 'infobox-image bordered-image' }
	}
	local row2 = {
		{ tag = 'argd', content = 'requirements', colspan = 6, css = { ['max-width'] = '240px' } } -- stop skill reqs getting too wide
	}
	if ret:param('speed', 'r') ~= 'no' then
		row1[2].rowspan = 12
	end
	
	if infobox.isDefined(ret:param('degradetype', 'r')) then
		row1[1].colspan = 3
		row2[1].colspan = 3
		table.insert(row1, 2, { tag = 'argh', content = 'degradeheader', colspan = 3 })
		table.insert(row2, 2, { tag = 'argd', content = 'degradestr', colspan = 3 })
		if ret:param('degradetype', 'r') == 'invention' then
			ret:append(tostring(ret:param('invdegrade_div', 'r')))
		end
	end
	ret:addRow(row1):addRow(row2)

	-- class, slot
	ret:addRow{
		{ tag = 'th', content = 'Class', colspan = 3 },
		{ tag = 'th', content = 'Slot', colspan = 3 }
	}
	:addRow{
		{ tag = 'argd', content = 'classstr', colspan = 3 },
		{ tag = 'argd', content = 'slotimg', colspan = 3 }
	}

	if ret:paramDefined('type') then
		-- tier, type
		ret:addRow{
			{ tag = 'th', content = '[[Equipment tier|Tier]]', colspan = 3 },
			{ tag = 'th', content = 'Type', colspan = 3 }
		}
		:addRow{
			{ tag = 'argd', content = 'tier', colspan = 3 },
			{ tag = 'argd', content = 'type_disp', colspan = 3 }
		}
	else
		ret:addRow{
			{ tag = 'th', content = '[[Equipment tier|Tier]]', colspan = 6 }
		}
		:addRow{
			{ tag = 'argd', content = 'tier', colspan = 6 }
		}
	end

	-- weapons section
	ret:addRow{ --headers
		{ tag = 'th', content = 'Weapons', colspan = 2, css = { width = '33%' } },
		{ tag = 'th', content = 'Main', colspan = 2, css = { width = '33%' } },
		{ tag = 'th', content = 'Off', colspan = 2, css = { width = '33%' } }
	}
	:addRow{ -- damage
		{ tag = 'th', content = '[[Ability damage|Damage]]', colspan = 2 },
		{ tag = 'argd', content = 'maindamage', colspan = 2 },
		{ tag = 'argd', content = 'offdamage', colspan = 2 }
	}
	:addRow{ -- accuracy
		{ tag = 'th', content = '[[Hit chance|Accuracy]]', colspan = 2 },
		{ tag = 'argd', content = 'mainaccuracy', colspan = 2 },
		{ tag = 'argd', content = 'offaccuracy', colspan = 2 }
	}
	:addRow{ -- style
		{ tag = 'th', content = '[[Attack types|Style]]', colspan = 2 },
		{ tag = 'argd', content = 'style', colspan = 4 }
	}
	:addRow{ -- range
		{ tag = 'th', content = '[[Attack range|Range]]', colspan = 2 },
		{ tag = 'argd', content = 'attackrange', colspan = 4 }
	}
	
	if ret:param('speed', 'r') ~= 'no' then
		ret:addRow{ -- rate (formely speed)
			{ tag = 'th', content = '[[Attack rate|Speed]]', colspan = 2 },
			{ tag = 'argd', content = 'speed', colspan = 4, css = { padding = '0.2em' } }
		}
	end

	-- attributes / bottom section
	ret:addRow{ -- attribute & reduction headers
		{ tag = 'th', content = 'Attributes', colspan = 6 },
		{ tag = 'th', content = '[[Damage reduction]]', colspan = 6, css = { ['min-width'] = '225px' } }, -- force as wide as other side (3x th = 210 from css, + paddings and borders = 225)
	}:addRow{ -- armour, reductions
		{ tag = 'th', content = skillpic('Defence') .. ' Armour', colspan = 4, class = 'combat-attributes' },
		{ tag = 'argd', content = 'armour', colspan = 2, css = { ['text-align'] = 'right' } },
		{ tag = 'argd', content = 'pvmreductionstr', colspan = 3 },
		{ tag = 'argd', content = 'pvpreductionstr', colspan = 3 }
	}:addRow{ -- life points, style header
		{ tag = 'th', content = skillpic('Constitution') .. ' Life points', colspan = 4, class = 'combat-attributes' },
		{ tag = 'argd', content = 'life', colspan = 2, css = { ['text-align'] = 'right' } },
		{ tag = 'th', content = '[[Damage bonus|Style bonuses]]', colspan = 6 },
	}:addRow{ -- prayer, style bonuses
		{ tag = 'th', content = skillpic('Prayer') .. ' Prayer', colspan = 4, class = 'combat-attributes' },
		{ tag = 'argd', content = 'prayer', colspan = 2, css = { ['text-align'] = 'right' } },
		{ tag = 'argd', content = 'strengthstr', colspan = 2 },
		{ tag = 'argd', content = 'rangedstr', colspan = 2 },
		{ tag = 'argd', content = 'magicstr', colspan = 2 },
	}

	-- bottom row
	-- kinda special to accomodate the compare button
	--[=[ret	:tag('tr')
			:addClass('infobox-bonuses-bottomrow')
			:css('border', 'none')
			:tag('td')
				:attr('colspan', 12)
				:css({
					background = 'transparent !important',
					border = 'none !important',
					['text-align'] = 'left',
					['font-size'] = 'smaller'
				})
				:wikitext('[[Template:Infobox Bonuses/FAQ|&#91;FAQ&#93;]] &bull; [[Template:Infobox Bonuses|&#91;doc&#93;]]')
	--]=]

end

-- 2 images or no images
function p._mainB(ret, hasImages)
	if hasImages then
		ret:addRow{
			{ tag = 'argd', content = 'image', colspan = 6 },
			{ tag = 'argd', content = 'altimage', colspan = 6 }
		}
	end

	local row1 = {
		{ tag = 'th', content = 'Requirements', colspan = 6, class = 'combat-requirements' },
		{ tag = 'th', content = '[[Equipment tier|Tier]]', colspan = 6 }
	}
	local row2 = {
		{ tag = 'argd', content = 'requirements', colspan = 6, css = { ['max-width'] = '240px' } }, -- stop skill reqs getting too wide
		{ tag = 'argd', content = 'tier', colspan = 6 }
	}
	local row1tier = row1[2]
	local row2tier = row2[2]

	if infobox.isDefined(ret:param('degradetype', 'r')) then
		row1[1].colspan = 3
		row2[1].colspan = 3
		table.insert(row1, 2, { tag = 'argh', content = 'degradeheader', colspan = 3 })
		table.insert(row2, 2, { tag = 'argd', content = 'degradestr', colspan = 3 })
		if ret:param('degradetype', 'r') == 'invention' then
			ret:append(tostring(ret:param('invdegrade_div', 'r')))
		end
	end
	
	if ret:paramDefined('type') then
		row1tier.colspan = 3
		row2tier.colspan = 3
		table.insert(row1, { tag = 'th', content = 'Type', colspan = 3 })
		table.insert(row2, { tag = 'argd', content = 'type_disp', colspan = 3 })
	end
	ret:addRow(row1):addRow(row2)
	local strSpan = 1
	if infobox.isDefined(ret:param('speed', 'r')) and ret:param('speed', 'r') ~= 'no' then
		strSpan = 2
	end
	ret:addRow{
		-- class, slot, attr headers
		{ tag = 'th', content = 'Class', colspan = 3, css = { width = '50%' } },
		{ tag = 'th', content = 'Slot', colspan = 3, css = { width = '50%' } },
		{ tag = 'th', content = 'Attributes', colspan = 6 },
	}
	:addRow{
		-- class, slot, armour
		{ tag = 'argd', content = 'classstr', rowspan = 2, colspan = 3 },
		{ tag = 'argd', content = 'slotimg', rowspan = 2, colspan = 3 },
		{ tag = 'th', content = skillpic('Defence') .. ' Armour', colspan = 4, class = 'combat-attributes' },
		{ tag = 'argd', content = 'armour', colspan = 2, css = { ['text-align'] = 'right' } }
	}
	:addRow{
		-- [class second row from rowspan]
		-- [slot second row from rowspan]
		-- life points
		{ tag = 'th', content = skillpic('Constitution') .. ' Life points', colspan = 4, class = 'combat-attributes' },
		{ tag = 'argd', content = 'life', colspan = 2, css = { ['text-align'] = 'right' } }
	}
	:addRow{
		-- weapons header, prayer
		{ tag = 'th', content = 'Weapons', colspan = 2, css = { width = '33%' } },
		{ tag = 'th', content = 'Main', colspan = 2, css = { width = '33%' } },
		{ tag = 'th', content = 'Off', colspan = 2, css = { width = '33%' } },
		{ tag = 'th', content = skillpic('Prayer') .. ' Prayer', colspan = 4, class = 'combat-attributes' },
		{ tag = 'argd', content = 'prayer', colspan = 2, css = { ['text-align'] = 'right' } }
	}
	:addRow{
		-- damage, reduction header
		{ tag = 'th', content = '[[Ability damage|Damage]]', colspan = 2 },
		{ tag = 'argd', content = 'maindamage', colspan = 2 },
		{ tag = 'argd', content = 'offdamage', colspan = 2 },
		{ tag = 'th', content = '[[Damage reduction]]', colspan = 6 }
	}
	:addRow{
		-- accuracy, reductions
		{ tag = 'th', content = '[[Hit chance|Accuracy]]', colspan = 2 },
		{ tag = 'argd', content = 'mainaccuracy', colspan = 2 },
		{ tag = 'argd', content = 'offaccuracy', colspan = 2 },
		{ tag = 'argd', content = 'pvmreductionstr', colspan = 3 },
		{ tag = 'argd', content = 'pvpreductionstr', colspan = 3 }
	}
	:addRow{
		-- style, style bonuses header
		{ tag = 'th', content = '[[Attack types|Style]]', colspan = 2 },
		{ tag = 'argd', content = 'style', colspan = 4 },
		{ tag = 'th', content = '[[Damage bonus|Style bonuses]]', colspan = 6 }
	}
	:addRow{
		-- range
		{ tag = 'th', content = '[[Attack range|Range]]', colspan = 2 },
		{ tag = 'argd', content = 'attackrange', colspan = 4 },
		{ tag = 'argd', content = 'strengthstr', colspan = 2, rowspan = strSpan },
		{ tag = 'argd', content = 'rangedstr', colspan = 2, rowspan = strSpan },
		{ tag = 'argd', content = 'magicstr', colspan = 2, rowspan = strSpan }
	}
	if ret:param('speed', 'r') ~= 'no' then
		ret:addRow{ --(formely speed)
			{ tag = 'th', content = '[[Attack rate|Speed]]', colspan = 2 },
			{ tag = 'argd', content = 'speed', colspan = 4, css = { padding = '0.2em' } }
		}
	end

end
function p._mainB0(ret)
	return p._mainB(ret, false)
end
function p._mainB2(ret)
	return p._mainB(ret, true)
end

-- images
function imagearg(f)
	local height, width = 280, 220
	if paramtest.is_empty(f) then
		return nil
	end
	f = tostring(f)
	if string.lower(f) == 'no' then
		return nil
	end
	f = f:gsub('[Ff]ile:',''):gsub('{{!}}','|')
	f = mw.text.split(f, '|')
	f = f[1]

	return mw.ustring.format('[[File:%s|%sx%spx|frameless|alt=%s: %s equipped by a player]]', f, width, height, f, mw.title.getCurrentTitle().text)
end
function noimgcatarg(nocat, img)
	if infobox.isDefined(nocat) then
		return 'true'
	end
	if infobox.isDefined(img) then
		if string.lower(img) == 'no' then
			return 'true'
		end
	end
	return nil
end

--requirements
function requirementsarg(r)
	if infobox.isDefined(r) then
		return mw.getCurrentFrame():preprocess(r)
	end
	return nil
end

-- class style type slot
function lookuparg(t, c)
	return t[string.lower(c or '')]
end
function lookupmultiarg(t, c)
	local out = {}
	if not infobox.isDefined(c) then
		return nil
	end
	c = string.lower(c)
	for i in mw.text.gsplit(c, ',') do
		table.insert(out, t[mw.text.trim(i)])
	end
	if #out == 0 then
		return nil
	end
	return table.concat(out, infobox.splitpoint)
end
function type_display(a)
	if a == nil then
		return nil
	end
	local out = {}
	for i in mw.text.gsplit(a, infobox.splitpoint) do
		i = i:lower()
		if type_cats[i] then
			table.insert(out, type_cats[i][1])
		end
	end
	return table.concat(out, ', ')
end
function stylearg(s, b)
	if b == 'true' then
		s = styles[string.lower(s or '')]
		if s then
			return paramtest.ucfirst(s)
		end
		return s
	end
	return '-'
end
function smwstylearg(s)
	if s ~= nil and s ~= '-' then
		return string.lower(tostring(s))
	end
end
function classstrarg(c, ci)
	if c == 'none' then
		return 'None'
	end
	if infobox.isDefined(c) and infobox.isDefined(ci) then
		return ci .. ' ' .. paramtest.ucfirst(c)
	end
	return nil
end
function classsmwarg(c)
	if c == 'all' then
		return 'hybrid'
	end
	return c
end

-- tier
function tierarg(t)
	t = tostring(t)
	t = t:lower()
	if t == 'n/a' or t == 'no' or t == 'none' then
		t = 0
	end
	t = clean(t)
	if t then
		if t == 0 then
			return "''None''"
		end
		return t
	end
	return nil
end
function smwtierarg(t)
	if t == "''None''" then
		return 0
	end
	if type(t) == 'number' then
		return t
	end
	return nil
end

-- numerical args
function fnumbers(x)
	x = clean(x)
	if x then
		return x
	end
	return '-'
end
function fnumbers2(x)
	x = clean(x)
	if x then
		return x
	end
	return 0
end
function smwnumbers(x)
	if tonumber(x) then
		return x
	end
	return 0
end
function smwnumbers2(x)
	if tonumber(x) then
		return x
	end
	return 0
end
function armourarg(pas)
	local x = fnumbers(pas)
	if x == '-' then --nil
		return '0.0'
	elseif pas:find('%d%.%d') then --has decimal
		return string.format('%.1f', x)
	end
	return x --no decimal
end
function mainoffaccarg(x, s, slots)
	for i,v in ipairs(slots) do
		if s == v then
			return tonumber(x)
		end
	end
	return '-'
end
function mainoffdamarg(x, s, pas, slots)
	local r = nil
	local found = false
	for i,v in ipairs(slots) do
		if s == v then
			r = tonumber(x)
			found = true
			break
		end
	end
	if r then
		if pas:find('%d%.%d') then
			r = string.format('%.1f', r)
		end
		return r
	end
	if found then
		return nil
	end
	return '-'
end

function stylebonusesarg(x, t, passed)
	local s = tostring(x)
	if x == '-' then
		s = '0.0'
	elseif passed:find('%d%.%d') then
		s  = string.format('%.1f', x)
	end
	return mw.ustring.format('<span style="float:left;">%s</span><span style="float:right;">%s</span>', skillpic(t), s)
end

-- isWeapon boolean
function isweaponarg(s,t)
	local weapon_slots = {
		['main hand weapon'] = 'true',
		['2h weapon'] = 'true',
		['off-hand weapon'] = 'true'

	}
	local non_weapon_types = {
		['Prevents attack'] = 'true'
	}
	if non_weapon_types[t] then
		return 'false'
	end
	return weapon_slots[s] or 'false'
end

-- degradation
-- invention charge drain
function invdegradearg(invtier,invslot,slot)
	invtier = tonumber(invtier)
	local ret = nil
	if invtier then
		invslot = slots[invslot] or invslot
		invslot = inv_slots[invslot]
		if not invslot then
			invslot = inv_slots[slot]
		end
		ret = chargedrain.get_base(invtier,invslot)
	end
	return ret
end
function invdegradetooltipspanarg(invtier,invslot,slot)
	invtier = tonumber(invtier)
	local ret = nil
	if invtier then
		invslot = slots[invslot] or invslot
		invslot = inv_slots[invslot]
		if not invslot then
			invslot = inv_slots[slot]
		end
		ret, _ = chargedrain.get_tooltip(invtier,invslot)
		ret = tostring(ret)
	end
	return ret
end
function invdegradetooltipdivarg(invtier,invslot,slot)
	invtier = tonumber(invtier)
	local ret = nil
	if invtier then
		invslot = slots[invslot] or invslot
		invslot = inv_slots[invslot]
		if not invslot then
			invslot = inv_slots[slot]
		end
		_, ret = chargedrain.get_tooltip(invtier,invslot)
		ret = tostring(ret)
	end
	return ret
end
-- normal degradation
function normdegradesarg(deg)
	local charges = clean(deg)
	if charges then
		return charges
	end
	if yesno(deg, false) then
		return 'Yes'
	end
	return nil
end
function degradestypearg(deg,inv)
	if infobox.isDefined(inv) and infobox.isDefined(deg) then
		return 'both'
	end
	if infobox.isDefined(inv) then
		return 'invention'
	end
	if infobox.isDefined(deg) then
		return 'normal'
	end
	return nil
end
-- string representation
function degradesstrarg(dtype, deg, inv, invspan)
	if dtype == 'invention' then
		return mw.ustring.format('%s/s %s', inv, invspan)
	end
	if dtype == 'normal' or dtype == 'both' then
		if type(deg) == 'number' then
			return commas(deg) .. ' charges'
		elseif deg == true then
			return 'Yes'
		else
			return deg
		end
	end
	return ''
end
-- header
function degradeheaderarg(dtype)
	if dtype == 'invention' then
		return '[[Charge pack|Charge drain]]'
	end
	if dtype == 'normal' or dtype == 'both' then
		return '[[Equipment degradation|Degrades]]'
	end
	return ''
end
--smw
function chargesarg(dt, c)
	if dt == 'normal' then
		if type(c) == 'number' then
			return c
		end
	end
	return nil
end
function invtierarg(dt, t)
	if dt == 'invention' then
		t = clean(t)
		if t then
			return t
		end
	end
	return nil
end

-- hasStyle boolean
function hasstylearg(s,t)
	return (isweaponarg(s,t) == 'true' or (s == 'ammo')) and 'true' or 'false'
end

-- attack range
function attackrangearg(w,r1,r2,r3)
	if w == 'true' then
		return clean(r1) or clean(r2) or clean(r3)
	end
	return '-'
end
function attackrangesmwarg(a)
	if a == '-' then
		return nil
	end
	return a
end

-- reductions
function reductionlevelarg(r, tier, reqs)
	if r then
		r = clean(r)
		if r then
			return r
		end
	end
	local ret = tonumber(tier) or 0
	if infobox.isDefined(reqs) then
		local maxR = 0
		for v in string.gmatch(reqs, '(%d+) %[%[') do
			if tonumber(v) and tonumber(v) > maxR then
				maxR = tonumber(v)
			end
		end
		if maxR > 0 then
			ret = maxR
		end
	end
	return ret
end
function reductionarg(rlr, rl, over, t, s, c, pvx)
	over = clean(over)
	if over then
		--overridden
		return over
	end
	if not infobox.isDefined(rl) then
		--tier/rl not defined
		return 0
	end
	local reduction_table, reduction_type = nil,nil
	if infobox.isDefined(t) then
		-- check if we have a reduction for this type+slot
		reduction_type = reduction_types[t:lower()]
		if reduction_type then
			reduction_table = reductions[reduction_type][s]
		end
	end
	if not reduction_table and infobox.isDefined(c) then
		-- couldn't find type+slot, so class+slot
		reduction_type = reduction_types[c:lower()]
		if reduction_type then
			reduction_table = reductions[reduction_type][s]
		end
	end
	if infobox.isDefined(rlr) then
		reduction_table = reductions.tank[s]
	end
	if reduction_table then
		return (reduction_table[pvx] or 0) * rl
	end
	-- didn't find anything
	return 0
end
function reductionstrarg(r, ty)
	return mw.ustring.format('<span style="float:left;">%s</span><span style="float:right;">%s%%</span>', ty, r)
end

-- speed
function speedrawarg(iswep, s1, s2)
	if iswep =='true' then
		local s
		if infobox.isDefined(s1) then
			s = string.lower(s1)
		elseif infobox.isDefined(s2) then
			s = string.lower(s2)
		end
		return s or ''
	else
		return 'no'
	end
end
function speedarg(s)
	if infobox.isDefined(s) then
		if s == 'no' then
			return 'no'
		else
			return tostring(attack_speed_bar(s))
		end
	end
	return nil
end

-- layout
function layoutarg(img, alt)
	if infobox.isDefined(img) then
		if infobox.isDefined(alt) then
			-- 2 images
			return 'B2'
		end
		-- 1 image
		return 'A'
	end
	-- no images
	return 'B0'
end

function intbonusarg(...)
	for _,v in ipairs({...}) do
		if paramtest.has_content(v) then
			if not v:match('%d%.%d') then
				return 'true'
			end
		end
	end
	return nil
end

function recolourarg(arg)
	-- string to ensure it correctly passes through infobox
	return tostring(infobox.isDefined(arg) and yesno(arg, false))
end

-- Removes all plus signs, commas, and percent signs
function clean(number)
	if not number then
		return nil
	else
		number = tostring(number)
		number = number:gsub('[+,%%]','')
		return tonumber(number,10)
	end
end

-- legacy SMW JSON
function smwjsonarg(class, slot, style, itype, damage, accuracy, attack_range, armour, life, speed, prayer, strength, ranged, magic, tier, charges, invtier)
	local json = {
		class = class,
		slot = slot,
		style = style,
		type = itype,
		damage = damage,
		accuracy = accuracy,
		style = style,
		attack_range = attack_range,
		armour = armour,
		lp = life,
		speed = speed,
		prayer = prayer,
		strength = strength,
		ranged = ranged,
		magic = magic,
		tier = tier,
		charges = charges,
		invention = invtier
	}
	for k,v in pairs(json) do
		if not infobox.isDefined(v) then
			json[k] = nil
		end
	end
	local jsonGood, encoded = pcall(mw.text.jsonEncode,json)
	if jsonGood then
		return encoded
	end
	return nil
end

-- categories
function addcategories(ibox, args, catargs)
	local cats = {'Equipment'}
	local versions = ibox.versions
	function forAllSwitches(func, param1, param2, param3)
		local r = {}
		param2 = param2 or {}
		param3 = param3 or {}
		local p1,p2,p3
		p1 = param1.d
		p2 = param2.d
		p3 = param3.d
		if versions == 0 or not (param1.switches or param2.switches or param3.switches) then
			table.insert(r, func(p1,p2,p3))
		else
			local p1s,p2s,p3s
			for i = 1,versions,1 do
				p1s = p1
				if param1.switches then
					p1s = param1.switches[i]
					if p1s == infobox.nil_param then
						p1s = p1
					end
				end
				p2s = p2
				if param2.switches then
					p2s = param2.switches[i]
					if p2s == infobox.nil_param then
						p2s = p2
					end
				end
				p3s = p3
				if param3.switches then
					p3s = param3.switches[i]
					if p3s == infobox.nil_param then
						p3s = p3
					end
				end
				local ret = func(p1s, p2s, p3s)
				if type(ret) == 'table' then
					for ir,vr in ipairs(ret) do
						table.insert(r, vr)
					end
				else
					table.insert(r, ret)
				end
			end
		end
		return r
	end
	function append(a)
		if type(a) == 'table' then
			for i,v in ipairs(a) do
				table.insert(cats, v)
			end
		else
			table.insert(cats,a)
		end
	end
	
	-- slot missing
	if not catargs.slot.all_defined then
		append('Missing slot information')
	end
	
	-- tiers
	append(forAllSwitches(function(v)
		if type(v) == 'number' then
			if v == 0 then
				return 'Tierless equipment'
			elseif v > 0 then
				return 'Tier '..v..' equipment'
			end
		end
		return 'Missing equipment tier'
	end, args.tier_smw))
	
	-- type
	append(forAllSwitches(function(v)
		local out = {}
		if type(v) == 'string' then
			for i in mw.text.gsplit(v, infobox.splitpoint) do
				i = i:lower()
				if type_cats[i] then
					table.insert(out, type_cats[i][2])
				end
			end
		end
		return out
	end, args.type))
	
	-- slots
	append(forAllSwitches(function(v)
		v = string.lower(tostring(v))
		if v == 'off-hand weapon' then
			local k = mw.title.getCurrentTitle().fullText:gsub('[Oo]ff[%- ]?hand ?', '')
			return 'Off-hand slot weapons|'..k
		elseif slot_cats[v] then
			return slot_cats[v]
		end
		return 'Missing slot information'
	end, args.slot))
	
	-- style
	append(forAllSwitches(function(v)
		return style_cats[v]
	end, args.style))
	
	-- degrades
	if catargs.normdegrades.one_defined then
		append('Degrading equipment')
	end
	
	-- class
	append(forAllSwitches(function(c,w,s)
		local cat = class_cats[c]
		if cat then
			if s == 'ammo' then
				return nil
			end
			if w == 'true' then
				if cat == 'Hybrid' then
					cat = 'Typeless'
				end
				return cat .. ' weapons'
			end
			return cat .. ' armour'
		end
		return nil
	end, args.class, args.isweapon, args.slot))
	
	-- prayer
	append(forAllSwitches(function(p)
		if tonumber(p) then
			if tonumber(p) > 0 then
				return 'Items with a prayer bonus'
			end
		end
		return nil
	end, args.prayer_smw))
	
	-- range
	if not catargs.attackrange.all_defined then
		append('Missing attack range')
	end
	
	-- speed
	append(forAllSwitches(function(s)
		if s == '' then
			return 'Missing attack rate'
		end
		return nil
	end, args.speedraw))
	
	-- integer equipment bonuses
	if catargs.intbonus.one_defined then
		append('Integer equipment bonus')
	end
	
	-- equipped image
	append(forAllSwitches(function(i, s, n)
		if infobox.isDefined(n) then
			return nil
		end
		if s == 'ammo' or s == 'pocket' or s == 'ring' or s == 'sigil' then
			return nil
		end
		if not infobox.isDefined(i) then
			return 'Needs equipped image'
		end
		return nil
	end, args.image, args.slot, args.noimgcat))
	
	local _cats = {}
	for i,v in ipairs(cats) do
		if type(v) == 'table' then
			for j,u in ipairs(v) do
				if paramtest.has_content(u) then
					table.insert(_cats, string.format('[[Category:%s]]', u))
				end
			end
		elseif paramtest.has_content(v) then
			table.insert(_cats, string.format('[[Category:%s]]', v))
		end
	end
	return table.concat(_cats, '')
end

return p
-- </nowiki>