Module:Infobox Bonuses new

From the RuneScape Wiki, the wiki for all things RuneScape
Jump to navigation Jump to search
Module documentation
This documentation is transcluded from Module:Infobox Bonuses new/doc. [edit] [history] [purge]
Module:Infobox Bonuses new's function main is invoked by Template:Infobox Bonuses.
Function list
L 14 — getChargeDrainModule
L 296 — p.main
L 530 — imagearg
L 548 — noimgcatarg
L 561 — requirementsarg
L 569 — lookuparg
L 572 — lookupmultiarg
L 586 — type_display
L 599 — stylearg
L 612 — bucketstylearg
L 617 — classstrarg
L 626 — classbucketarg
L 634 — tierarg
L 649 — armourtierarg
L 664 — armourdamagetierarg
L 676 — buckettierarg
L 687 — fnumbers
L 694 — fnumbers2
L 701 — bucketnumbers
L 707 — bucketnumbers2
L 713 — armourarg
L 722 — mainoffaccarg
L 730 — mainoffdamarg
L 751 — abilityDamage
L 764 — fnum
L 838 — abilityDamage_raw
L 841 — abilityDamage_disp
L 845 — stylebonusesarg
L 856 — accuracybonusarg
L 886 — accuracybonusstrarg
L 902 — isweaponarg
L 920 — invdegradearg
L 933 — invdegradetooltipspanarg
L 947 — invdegradetooltipdivarg
L 962 — normdegradesarg
L 972 — degradestypearg
L 985 — degradesstrarg
L 1001 — degradeheaderarg
L 1011 — chargesarg
L 1019 — invtierarg
L 1030 — hasstylearg
L 1035 — attackrangearg
L 1041 — attackrangebucketarg
L 1049 — reductionlevelarg
L 1071 — reductionarg
L 1105 — reductionstrarg
L 1110 — speedrawarg
L 1123 — speedarg
L 1134 — intbonusarg
L 1145 — recolourarg
L 1151 — clean
L 1162 — bucketjsonarg
L 1207 — addcategories
L 1210 — forAllSwitches
L 1256 — append

--[=[
-- 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 commas = require('Module:Addcommas')._add
local chargedrain = nil
local function getChargeDrainModule()
	if chargedrain == nil then
		chargedrain = require('Module:Augmented degrade')
	end
	return chargedrain
end
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',
	necro = 'necromancy',
	necromancy = 'necromancy',
	all = 'all',
	hybrid = 'hybrid',
	none = 'none',
	['n/a'] = 'none'
}

-- Classes with images
local class_img = {
	melee = '[[File:Melee style bonus.png|x24px|link=Melee]]',
	ranged = '[[File:Ranged style bonus.png|x24px|link=Ranged]]',
	magic = '[[File:Magic style bonus.png|x24px|link=Magic]]',
	necromancy = '[[File:Necromancy style bonus.png|x24px|link=Necromancy]]',
	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',
	necromancy = 'Necromancy',
	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',
	necromancy = 'necromancy',
	necro = 'necromancy',
	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',
	hybrid = 'Hybrid armour',
	['hybrid armour'] = 'Hybrid armour',
	['power hybrid'] = 'Power Hybrid armour',
	['hybrid power'] = 'Power Hybrid 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',
	['death guard'] = 'Death guard'
}

local type_cats = {
	['cosmetic'] = { '[[Cosmetic]]' },
	['power armour'] = { '[[Armour#Power|Power armour]]', 'Power armour' },
	['tank armour'] = { '[[Armour#Tank|Tank armour]]', 'Tank armour' },
	['pvp armour'] = { '[[Armour#PvP|PvP armour]]', 'PvP armour' },
	['hybrid armour'] = { '[[Armour#Hybrid|Hybrid armour]]', 'Hybrid armour' },
	['power hybrid armour'] = { '[[Armour#Hybrid power|Power Hybrid armour]]', 'Power armour' },
	shieldbow = { '[[Shieldbow (bow type)|Shieldbow]]', 'Shieldbows' },
	shortbow = { '[[Shortbow (bow type)|Shortbow]]','Shortbows' },
	defender = { '[[Defenders|Defender]]', 'Defenders' },
	repriser = { '[[Repriser]]', 'Defenders' },
	rebounder = { '[[Rebounder]]', 'Defenders' },
	halberd = { '[[Halberds|Halberd]]', 'Halberds' },
	shield = { '[[Shield]]', 'Shields' },
	chargebow = { '[[Chargebow (bow type)|Chargebow]]', 'Chargebows' },
	['prevents attack'] = { 'Prevents attack', 'Items which prevent attack' },
	dagger = { '[[Daggers|Dagger]]', 'Daggers' },
	spear = { '[[Spears|Spear]]', 'Spears' },
	scimitar = { '[[Scimitars|Scimitar]]', 'Scimitars' },
	['2h sword'] = { '[[Two-handed sword|2h sword]]', 'Two-handed swords'},
	mace = { '[[Maces|Mace]]', 'Maces' },
	maul = { '[[Mauls|Maul]]', 'Mauls' },
	['crossbow'] = { '[[Crossbows|Crossbow]]', 'One-handed crossbows' },
	['2h crossbow'] = { '[[Two-handed crossbow|2h crossbow]]', 'Two-handed crossbows' },
	['throwing knife'] = { '[[Throwing knife]]', 'Throwing knives' },
	['throwing axe'] = { '[[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},
		hands = {pvm = 0, pvp = 0},
		feet = {pvm = 0, pvp = 0}
	},
	other = { --power, hybrid, all
		head = {pvm = 0, pvp = 0.01875},
		torso = {pvm = 0, pvp = 0.03},
		legs = {pvm = 0, pvp = 0.02625},
		hands = {pvm = 0, pvp = 0},
		feet = {pvm = 0, pvp = 0}
	},
	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
	}
}

local imagearg, noimgcatarg, requirementsarg, lookuparg, lookupmultiarg,
	type_display, stylearg, bucketstylearg, classstrarg, classbucketarg, tierarg, armourtierarg, armourdamagetierarg,
	buckettierarg, fnumbers, fnumbers2, bucketnumbers, bucketnumbers2, armourarg,
	mainoffaccarg, mainoffdamarg, abilityDamage, abilityDamage_raw,
	abilityDamage_disp, stylebonusesarg, isweaponarg, invdegradearg,
	invdegradetooltipspanarg, invdegradetooltipdivarg, normdegradesarg,
	degradestypearg, degradesstrarg, degradeheaderarg, chargesarg, invtierarg,
	hasstylearg, attackrangearg, attackrangebucketarg, reductionlevelarg,
	reductionarg, reductionstrarg, speedrawarg, speedarg,
	accuracybonusarg, accuracybonusstrarg,
	intbonusarg, recolourarg, clean, bucketjsonarg, addcategories

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_bucket', func = { name = classbucketarg, params = { 'class' }, flag = { 'd' } } },
		{ name = 'tier', func = tierarg },
		{ name = 'damageTier', func = tierarg },
		{ name = 'accuracyTier', func = tierarg },
		{ name = 'armourTier', func = { name = armourtierarg, params = { 'armourTier', 'type', 'tier' } } },
		{ name = 'armourDamageTier', func = { name = armourdamagetierarg, params = { 'armourDamageTier', 'type', 'tier' } } },
		{ 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', 'class' }, flag = 'd' } },
		{ name = 'attackrange', func = { name = attackrangearg, params = { 'isweapon', 'attack_range', 'attack range', 'attackrange' }, flag = { 'd', 'p', 'p', 'p' } } },
		{ name = 'attackrangebucket', func = { name = attackrangebucketarg, params = { 'attackrange' }, flag = 'd' } },
		{ name = 'damage', func = {name = fnumbers, params={'damage'}, flag='p' } },
		{ name = 'accuracy', func = {name = fnumbers, params={'accuracy'}, flag='p' } },
		{ name = 'damage_disp', func = { name = mainoffdamarg, params = { 'damage', 'slot', 'damage', {'main hand weapon', '2h weapon', 'ammo', 'off-hand weapon'} }, flag = { 'd', 'd', 'p', 'r' } } },
		{ name = 'accuracy_disp', func = { name = mainoffaccarg, params = { 'accuracy', 'slot', {'main hand weapon', '2h weapon', 'off-hand weapon'} }, flag = { 'd', 'd', 'r' } } },
		{ name = 'armour', func = {name=armourarg, params={'armour'}, flag=p} },
		{ name = 'life', func = {name = fnumbers2, params={'life'}, flag='p' } },
		{ name = 'prayer', func = {name = fnumbers2, params={'prayer'}, flag='p' } },
		{ name = 'magic', func = {name = fnumbers, params={'magic'}, flag='p' } },
		{ name = 'strength', func = {name = fnumbers, params={'strength'}, flag='p' } },
		{ name = 'ranged', func = {name = fnumbers, params={'ranged'}, flag='p' } },
		{ name = 'necromancy', func = {name = fnumbers, params={'necromancy'}, flag='p' } },
		{ name = 'magicstr', func = { name = stylebonusesarg, params = { 'magic', 'Magic', 'magic' }, flag = { 'd', 'r', 'p' } } },
		{ name = 'necromancystr', func = { name = stylebonusesarg, params = { 'necromancy', 'Necromancy', 'necromancy' }, flag = { 'd', 'r', 'p' } } },
		{ name = 'strengthstr', func = { name = stylebonusesarg, params = { 'strength', 'Attack', 'strength' }, flag = { 'd', 'r', 'p' } } },
		{ name = 'rangedstr', func = { name = stylebonusesarg, params = { 'ranged', 'Ranged', 'ranged' }, flag = { 'd', 'r', 'p' } } },
		{ name = 'rangedaccuracy', func = {name = accuracybonusarg, params = {'rangedaccuracy', 'ranged', 'slot', 'class', 'armour'}, flag = {'d', 'r', 'd', 'd'} } },
		{ name = 'meleeaccuracy', func = {name = accuracybonusarg, params = {'meleeaccuracy', 'melee', 'slot', 'class', 'armour'}, flag = {'d', 'r', 'd', 'd'} } },
		{ name = 'magicaccuracy', func = {name = accuracybonusarg, params = {'magicaccuracy', 'magic', 'slot', 'class', 'armour'}, flag = {'d', 'r', 'd', 'd'} } },
		{ name = 'necromancyaccuracy', func = {name = accuracybonusarg, params = {'necromancyaccuracy', 'necromancy', 'slot', 'class', 'armour'}, flag = {'d', 'r', 'd', 'd'} } },
		{ name = 'rangedaccuracystr', func = { name = accuracybonusstrarg, params={'rangedaccuracy'}} },
		{ name = 'meleeaccuracystr', func = { name = accuracybonusstrarg, params={'meleeaccuracy'}} },
		{ name = 'magicaccuracystr', func = { name = accuracybonusstrarg, params={'magicaccuracy'}} },
		{ name = 'necromancyaccuracystr', func = { name = accuracybonusstrarg, params={'necromancyaccuracy'}} },
		{ 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 = 'ability_damage', func = { name = abilityDamage, params = { 'isweapon', 'tier', 'damageTier', 'damage', 'speedraw', 'slot', 'class', 'style', 'type' }, flag = 'd' } },
		{ name = 'ability_damage_raw', func = { name = abilityDamage_raw, params = { 'ability_damage' }, flag = 'd' } },
		{ name = 'ability_damage_disp', func = { name = abilityDamage_disp, params = { 'ability_damage' }, 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_bucket', func = { name = buckettierarg, params = { 'tier' }, flag = 'd' } },
		{ name = 'style_bucket', func = { name = bucketstylearg, params = { 'style' }, flag = 'd' } },
		{ name = 'damage_bucket', func = { name = bucketnumbers, params = { 'damage' }, flag = 'd' } },
		{ name = 'accuracy_bucket', func = { name = bucketnumbers, params = { 'accuracy' }, flag = 'd' } },
		{ name = 'armour_bucket', func = { name = bucketnumbers2, params = { 'armour' }, flag = 'd' } },
		{ name = 'life_bucket', func = { name = bucketnumbers2, params = { 'life' }, flag = 'd' } },
		{ name = 'prayer_bucket', func = { name = bucketnumbers2, params = { 'prayer' }, flag = 'd' } },
		{ name = 'magic_bucket', func = { name = bucketnumbers, params = { 'magic' }, flag = 'd' } },
		{ name = 'strength_bucket', func = { name = bucketnumbers, params = { 'strength' }, flag = 'd' } },
		{ name = 'ranged_bucket', func = { name = bucketnumbers, params = { 'ranged' }, flag = 'd' } },
		{ name = 'necromancy_bucket', func = { name = bucketnumbers, params = { 'necromancy' }, flag = 'd' } },
		{ name = 'charges', func = { name = chargesarg, params = { 'degradetype', 'normdegrades' }, flag = 'd' } },
		{ name = 'invtier', func = { name = invtierarg, params = { 'degradetype', 'invtier' }, flag = { 'd', 'p' } } },
		{ name = 'bucketJSON', func = { name = bucketjsonarg, params = { 'class', 'slot', 'style_bucket', 'type', 'damage_bucket', 'accuracy_bucket', 'attackrangebucket',
			'armour_bucket', 'life_bucket', 'speedraw', 'prayer_bucket', 'strength_bucket', 'ranged_bucket', 'magic_bucket', 'necromancy_bucket', 'tier_bucket', 'charges', 'invtier',
			'ability_damage_raw', 'ability_damage_disp', 'requirements', 'meleeaccuracy', 'rangedaccuracy', 'magicaccuracy', 'necromancyaccuracy' }, flag = 'd' } }
	}

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

	ret:defineLinks({ hide = true })

	ret:useBucket('infobox_bonuses', {
		bucketJSON = 'json',
		class_bucket = 'combat_class',
		slot = 'equipment_slot',
		type = 'equipment_type',
		damage_bucket = 'weapon_damage',
		accuracy_bucket = 'weapon_accuracy',
		style_bucket = 'attack_style',
		attackrangebucket = 'attack_range',
		armour_bucket = 'equipment_armour',
		life_bucket = 'equipment_life_points',
		speedraw = 'weapon_attack_speed',
		prayer_bucket = 'prayer_bonus',
		strength_bucket = 'strength_bonus',
		ranged_bucket = 'ranged_bonus',
		magic_bucket = 'magic_bonus',
		necromancy_bucket = 'necromancy_bonus',
		tier_bucket = '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')

	-- lets just really mess around with the infobox
	ret.rtable:addClass('infobox-outer')
	local original_rtable = ret.rtable
	local maincell = ret.rtable:tag('tr'):tag('td'):addClass('infobox-grid-container')
	
	-- panel 1 - info
	ret.rtable = maincell:tag('table'):addClass('wikitable infobox-panel infobox-bonuses-panel-general')
	local r1 = {{tag='th', content='Requirements', colspan=3}}
	local r2 = {{tag='argd', content='requirements', colspan=3}}
	if infobox.isDefined(ret:param('degradetype', 'r')) then
		r1[1].colspan=2
		r2[1].colspan=2
		r1[2] = {tag='argh', content='degradeheader'}
		r2[2] = {tag='argd', content='degradestr'}
		if ret:param('degradetype', 'r') == 'invention' then
			ret:append(tostring(ret:param('invdegrade_div', 'r')))
		end
	end
	ret:addRow(r1):addRow(r2)
		:addRow{ {tag='th', content='[[Combat triangle|Class]]', colspan=3} }
		:addRow{ {tag='argd', content='classstr', colspan=3} }
		:addRow{ {tag='th', content='Slot', colspan=3} }
		:addRow{ {tag='argd', content='slotimg', colspan=3} }
	if ret:paramDefined('type') then
		ret:addRow{ {tag='th', content='[[Equipment tier|Tier]]', colspan=2}, {tag='th', content='Type'} }
		if ret:paramDefined('damageTier') or ret:paramDefined('accuracyTier') then
			ret:addRow{ {tag = 'th', content = 'Level'}, {tag = 'argd', content = 'tier'}, {tag = 'argd', content = 'type_disp', rowspan = 3} }
			:addRow{ {tag = 'th', content = '[[Ability damage|Damage]]'}, {tag = 'argd', content = 'damageTier'} }
			:addRow{ {tag = 'th', content = '[[Hit chance|Accuracy]]'}, {tag = 'argd', content = 'accuracyTier'} }
		elseif ret:paramDefined('armourTier','all') or ret:paramDefined('armourDamageTier','all') then
			ret:addRow{ {tag = 'th', content = 'Level'}, { tag = 'argd', content = 'tier'}, {tag = 'argd', content = 'type_disp', rowspan = 3} }
			:addRow{ {tag = 'th', content = '[[Armour#Attributes|Armour]]'}, {tag = 'argd', content = 'armourTier'} }
			:addRow{ {tag = 'th', content = '[[Armour#Damage bonus|Damage]]'}, {tag = 'argd', content = 'armourDamageTier'} }
		else
			ret:addRow{ {tag = 'argd', content = 'tier', colspan = 2}, {tag = 'argd', content = 'type_disp'} }
		end
	else
		ret:addRow{ {tag='th', content='[[Equipment tier|Tier]]', colspan=3} }
		if ret:paramDefined('damageTier') or ret:paramDefined('accuracyTier') then
			ret:addRow{ {tag = 'th', content = 'Level'}, {tag = 'argd', content = 'tier'} }
			:addRow{ {tag = 'th', content = '[[Ability damage|Damage]]'}, {tag = 'argd', content = 'damageTier'} }
			:addRow{ {tag = 'th', content = '[[Hit chance|Accuracy]]'}, {tag = 'argd', content = 'accuracyTier'} }
		elseif ret:paramDefined('armourTier') or ret:paramDefined('armourDamageTier') then
			ret:addRow{ {tag = 'th', content = 'Level'}, { tag = 'argd', content = 'tier'} }
			:addRow{ {tag = 'th', content = '[[Armour#Attributes|Armour]]'}, {tag = 'argd', content = 'armourTier'} }
			:addRow{ {tag = 'th', content = '[[Armour#Damage bonus|Damage]]'}, {tag = 'argd', content = 'armourDamageTier'} }
		else
			ret:addRow{ {tag = 'argd', content = 'tier', colspan = 3} }
		end
	end
	
	-- panel 2 - images
	if ret:paramDefined('image') and ret:param('image', 'r') ~= 'no' then
		ret.rtable = maincell:tag('table'):addClass('wikitable infobox-panel infobox-bonuses-panel-image')
		local rimg = { {tag='argd', content='image'} }
		if ret:paramDefined('altimage') then
			rimg[2] = {tag='argd', content='altimage', css={width='50%'}}
		end
		ret:addRow(rimg)
	end
	
	-- panel 3 - offensive stats
	if ret:param('hasstyle', 'r') == 'true' then
		ret.rtable = maincell:tag('table'):addClass('wikitable infobox-panel infobox-bonuses-panel-offensive')
		ret:addRow{ {tag='th', content='Offensive attributes', colspan=3, class='offensive-header'} }
			:addRow{ {tag='th', content='&nbsp;'}, {tag='th', content='<span style="display:block;" class="hover-text" title="The damage value shown in the item\'s tooltip, which is used to calculate ability damage. See link for more information."><span>[[Template:Infobox_Bonuses/FAQ#What_is_\'Tooltip\'_damage_and_\'Speed\'?|Tooltip]]</span></span>'}, {tag='th', content='[[Abilities|Ability]]'} }
			:addRow{ {tag='th', content='Damage' }, {tag='argd', content='damage_disp'}, {tag='argd', content='ability_damage_disp'} }
			:addRow{ {tag='th', content='[[Hit chance#Accuracy|Accuracy]]' }, {tag='argd', content='accuracy_disp', colspan=2} }
			:addRow{ {tag='th', content='[[Attack types|Style]]' }, {tag='argd', content='style', colspan=2} }
			:addRow{ {tag='th', content='[[Attack range|Range]]' }, {tag='argd', content='attackrange', colspan=2} }
		-- Hide speed param after 2026 Combat Style Modernisation
		-- As the patch notes says, it does not affect 'basic attack' speed but it still exists
		if ret:param('speed', 'r') ~= 'N/A' then
			ret:addRow{ {tag='th', content='<span style="display:block;" class="hover-text" title="The attack speed is only used to calculate ability damage and is not used by basic attacks, and is not visible in-game. See link for more information."><span>[[Template:Infobox_Bonuses/FAQ#What_is_\'Tooltip\'_damage_and_\'Speed\'?|Speed]]</span></span>' }, {tag='argd', content='speed', colspan=2, css = { padding = '0.2em' }} }
		end
	end
	
	-- panel 4 - style stats
	ret.rtable = maincell:tag('table'):addClass('wikitable infobox-panel infobox-panel-small infobox-bonuses-panel-style')
	ret:addRow{ {tag='th', content='Style attributes', colspan=5, class='style-header'} }
		:addRow{ {tag='th', content='&nbsp;'}, {tag='th', content='[[File:Melee style bonus.png|link=Melee|21x21px]]'}, {tag='th', content='[[File:Ranged style bonus.png|link=Ranged|21x21px]]'}, {tag='th', content='[[File:Magic style bonus.png|link=Magic|21x21px]]'}, {tag='th', content='[[File:Necromancy style bonus.png|link=Necromancy|21x21px]]'} }
		:addRow{ {tag='th', content='[[Damage bonus|Damage]]'}, {tag='argd', content='strengthstr'}, {tag='argd', content='rangedstr'}, {tag='argd', content='magicstr'}, {tag='argd', content='necromancystr'} }
		:addRow{ {tag='th', content='[[Hit chance#Accuracy|Accuracy]]'}, {tag='argd', content='meleeaccuracystr'}, {tag='argd', content='rangedaccuracystr'}, {tag='argd', content='magicaccuracystr'}, {tag='argd', content='necromancyaccuracystr'} }
		
	-- panel 5 - defensive stats
	ret.rtable = maincell:tag('table'):addClass('wikitable infobox-panel infobox-panel-small infobox-bonuses-panel-defensive')
	ret:addRow{ {tag='th', content='Defensive attributes', class='defensive-header', colspan=4} }
		:addRow{ {tag='th', content='[[File:Shield icon.png|21x21px|link=Armour#Attributes]]&nbsp;[[Armour#Attributes|Armour]]'}, {tag='argd', content='armour'}, {tag='th', content='[[Armour#Damage_reduction|Reduction]]', colspan=2 }}
		:addRow{ {tag='th', content='[[File:Tooltip Life Points.png|21x21px|link=Life points]]&nbsp;[[Life points|LP]]'}, {tag='argd', content='life'}, {tag='th', content='PvM'}, {tag='argd', content='pvmreductionstr'} }
		:addRow{ {tag='th', content='[[File:Prayer points.png|21x21px|link=Prayer#Prayer bonus]]&nbsp;[[Prayer#Prayer bonus|Prayer]]'}, {tag='argd', content='prayer'}, {tag='th', content='PvP'}, {tag='argd', content='pvpreductionstr'} }
	
	ret.rtable = original_rtable
	ret.rtable:tag('tr'):tag('td'):addClass('cioCompareLinkCell cioCompareLinkCellEmpty') -- for compare
	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

-- 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 'no'
	end
	f = f:gsub('[Ff]ile:',''):gsub('{{!}}','|')
	f = mw.text.split(f, '|')[1]
	if mw.title.new(f, 'File').exists == true then
		return string.format('[[File:%s|%sx%spx|frameless|alt=%s: %s equipped by a player]]', f, width, height, f, mw.title.getCurrentTitle().text)
	else
		local g = f:gsub('%.png',''):gsub(' equipped','')
		return string.format('[[File:%s|%sx%spx|frameless|alt=Missing image for %s – help by uploading one!]][[Category:Needs equipped image]]', f, width, height, g)
	end
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, c)
	if b == 'true' then
		s = styles[string.lower(s or '')]
		if s then
			return paramtest.ucfirst(s)
		end
		if (c == "necromancy") then
			return paramtest.ucfirst(styles.necromancy);
		end
		return s
	end
	return '-'
end
function bucketstylearg(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 classbucketarg(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 armourtierarg(t,typ,basetier)
	local t = tierarg(t)
	local typ = string.lower(typ)
	local basetier = tierarg(basetier)
	if t~=nil then 
		return t
	elseif (typ=='tank' or typ=='tank armour') and type(basetier)=='number' then
		return basetier
	elseif (typ=='power' or typ=='power armour') and type(basetier)=='number' then
		return basetier - 5
	elseif (typ=='hybrid' or typ=='hybrid armour') and type(basetier)=='number' then
		return basetier - 15
	end
	return nil
end
function armourdamagetierarg(t,typ,basetier)
	local t = tierarg(t)
	local typ = string.lower(typ)
	if t~=nil then 
		return t
	elseif (typ=='power' or typ=='power armour') and type(basetier)=='number' then
		return basetier
	elseif (typ=='hybrid' or typ=='hybrid armour' or typ=='tank' or typ=='tank armour') and type(basetier)=='number' then
		return "''None''"
	end
	return nil
end
function buckettierarg(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 bucketnumbers(x)
	if tonumber(x) then
		return x
	end
	return 0
end
function bucketnumbers2(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 abilityDamage(isweapon, tier, damageTier, damage, speed, slot, class, style, weaponType)
	local weaponTier = tier
	if infobox.isDefined(damageTier) then
		weaponTier = damageTier
	end
	local errVal = { nil, '-' }
	local abilDamage = nil
	local msg = '-'

	if (isweapon == 'false' and slot ~= 'ammo') or not infobox.isDefined(weaponTier) then
		return errVal
	end

	local function fnum(num)
		return string.format('%.1f', num):gsub('%.0', '')
	end

	local speedMultiplier = {
		average = 96/149,
		fast = 192/245,
		fastest = 1,
		['6'] = 96/149,
		['5'] = 192/245,
		['4'] = 1,
		['3'] = 1,
		['0'] = 1
	}

	local slotMultiplier = {
		['main hand weapon'] = 9.6,
		['off-hand weapon'] = 4.8,
		['2h weapon'] = 14.4,
	}

	if class == 'melee' or class == 'necromancy' then
		if type(damage) ~= 'number' or speedMultiplier[speed] == nil then
			return errVal
		end

		abilDamage = fnum(damage * speedMultiplier[speed])
		msg = abilDamage
	elseif class == 'ranged' then
		if type(damage) ~= 'number' then
			return errVal
		end

		if slot == 'ammo' then
			abilDamage = fnum(damage * 1.5)
			if style == 'Bolts' then
				msg = string.format('<span class="hover-text" title="Assuming tier %d or higher dual wield/2h crossbow is used">%s</span>', weaponTier, abilDamage)
			else
				msg = string.format('<span class="hover-text" title="Assuming a tier %d or higher bow is used">%s</span>', weaponTier, abilDamage)
			end
		elseif weaponType == 'Chargebow' or style == 'Thrown' then
			abilDamage = fnum(damage * speedMultiplier[speed])
			msg = abilDamage
		else
			if weaponType == 'Shieldbow' then
				slot = 'main hand weapon'
			end

			if weaponType == 'Repriser' then
				weaponTier = weaponTier / 2
			end

			abilDamage = fnum(weaponTier * slotMultiplier[slot])
			msg = string.format('<span class="hover-text" title="Assuming tier %d or higher ammunition is used">%s</span>', weaponTier, abilDamage)
		end
	elseif class == 'magic' then
		if type(weaponTier) ~= 'number' then
			return errVal
		end

		if slot == 'ammo' then
			msg = "-"
		else
			if weaponType == 'Rebounder' then
				weaponTier = weaponTier / 2
			end

			abilDamage = fnum(weaponTier * slotMultiplier[slot])
			msg = string.format('<span class="hover-text" title="Assuming a tier %d or higher spell is used">%s</span>', weaponTier, fnum(abilDamage))
		end
	end

	return { abilDamage, msg }
end
function abilityDamage_raw(t)
	return t[1]
end
function abilityDamage_disp(t)
	return t[2]
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 s
end

-- accuracy bonus (non-weapons)
function accuracybonusarg(mainarg, accclass, slot, class, armour)
	if infobox.isDefined(mainarg) then
		-- if arg set, use that unquestioned
		return mainarg
	end
	if class == 'hybrid' or class == 'all' or class == accclass or not infobox.isDefined(armour) then
		-- hybrid/all skips the calc
		return 0
	end
	-- otherwise calc the accuracy penalty for off-style armour
	-- this is 1.5x the armour value for armour of the style you are strong against (i.e., ranged armour when using melee), or non-necromancy armour when using necromancy
	--   and 0.8x for the other two styles (necromancy armour or magic armour when using melee)
	-- only applies to offhand + 5 main armour slots
	if slot == 'off-hand' or slot == 'head' or slot == 'torso' or slot == 'legs' or slot == 'hands' or slot == 'feet' then
		local mult = 0.8
		if (accclass == 'necromancy') or 
			(accclass == 'melee' and class == 'ranged') or 
			(accclass == 'ranged' and class == 'magic') or
			(accclass == 'magic' and class == 'melee')
		then
			mult = 1.5
		end
		local val = math.floor(armour * mult)
		if val > 0 then
			return -1 * val
		end
	end
	return 0
end

function accuracybonusstrarg(arg)
	local cls = 'acc-unk'
	local a = tonumber(arg)
	if a then
		if a > 0 then
			cls = 'acc-pos'
		elseif a < 0 then
			cls = 'acc-neg'
		else
			cls = 'acc-zero'
		end
	end
	return '<span class="'..cls..'">'..arg..'</span>'
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 = getChargeDrainModule().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, _ = getChargeDrainModule().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 = getChargeDrainModule().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
--bucket
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 attackrangebucketarg(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
			local numV = tonumber(v)
			if numV and numV > maxR then
				maxR = numV
			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) and not reduction_type 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 tostring(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 'N/A'
		else
			return tostring(attack_speed_bar(s):css("margin", "0 auto"))
		end
	end
	return nil
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 bucket JSON
function bucketjsonarg(class, slot, style, itype, damage, accuracy, attack_range,
					armour, life, speed, prayer, strength, ranged, magic, necromancy, tier,
					charges, invtier, abilDmg, abilDmgNote, requirements, meleeaccuracy, rangedaccuracy, magicaccuracy, necromancyaccuracy)
	local json = {
		class = class,
		slot = slot,
		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,
		necromancy = necromancy,
		meleeaccuracy = meleeaccuracy,
		rangedaccuracy = rangedaccuracy,
		magicaccuracy = magicaccuracy,
		necromancyaccuracy = necromancyaccuracy,
		tier = tier,
		charges = charges,
		invention = invtier,
		ability_damage = abilDmg,
		ability_damage_note = abilDmgNote,
		requirements = requirements
	}
	for k,v in pairs(json) do
		if not infobox.isDefined(v) then
			json[k] = nil
		elseif type(v) == "string" then
			json[k] = v:gsub(infobox.splitpoint, ", ")
		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
	local 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
	local 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_bucket))

	-- 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_bucket))

	-- 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