Module:Skill info

Documentation for this module may be created at Module:Skill info/doc

--------------------------
-- Module for [[Template:Skill info]]
--------------------------
local p = {}

local onmain = require('Module:Mainonly').on_main
local paramtest = require('Module:Paramtest')
local infobox = require('Module:Infobox')
local yesno = require('Module:Yesno')
local scp = require('Module:Skill clickpic')._main
local qty = require('Module:Quantity box')._main
local editbutton = require('Module:Edit button')
local edit = editbutton("'''?''' (edit)")

local presets = require('Module:Skill info/presets')

local MAXSKILL = 5 -- maximum amount of skills to iterate over; when changing this also change the `skillnames` param definition.

local SKILLS = {
	'Attack',			'Constitution',	'Mining',
	'Strength',			'Agility',		'Smithing',
	'Defence',			'Herblore',		'Fishing',
	'Ranged',			'Thieving',		'Cooking',
	'Prayer',			'Crafting',		'Firemaking',
	'Magic',			'Fletching',	'Woodcutting',
	'Runecrafting',		'Slayer',		'Farming',
	'Construction',		'Hunter',		'Summoning',
	'Dungeoneering',	'Divination',	'Invention',
	'Archaeology',
}

-- array contains val
function contains(tbl, val)
	for _, item in ipairs(tbl) do
		if item == val then
			return true
		end
	end
	return false
end

function p.main(frame)
	local args = frame:getParent().args
	preset = frame.args[1] and string.lower(frame.args[1]) or args.preset
	mw.log(preset)
	local mand = {}
	if preset and presets[preset] then
		args, mand = presets[preset](args)
	end
	return p._main(args, mand)
end

function p._main(args, mand)
	table.insert(mand, 'skill1exp') -- Experience is a mandatory parameter
	local ret = infobox.new(args)
	ret:defineParams{
		{ name = 'name', func = 'name' },
		{ name = 'itemname', func = { name = paramtest.default_to, params = { 'itemname', "Uses item" }, flag = { 'p', 'r' } } },
		{ name = 'item', func = 'hasContent' },
		{ name = 'item_smw', func = { name = csv_to_multi, params = { 'item', true }, flag = { 'p', 'r' } } },
		{ name = 'locname', func = { name = paramtest.default_to, params = { 'locname', "Location" }, flag = { 'p', 'r' } } },
		{ name = 'loc', func = 'hasContent' },
		{ name = 'loc_smw', func = { name = csv_to_multi, params = { 'loc', true }, flag = { 'p', 'r' } } },
		{ name = 'facilityname', func = { name = paramtest.default_to, params = { 'facilityname', "Facility" }, flag = { 'p', 'r' } } },
		{ name = 'facility', func = 'hasContent' },
		{ name = 'facility_smw', func = { name = csv_to_multi, params = { 'facility', true }, flag = { 'p', 'r' } } },
		{ name = 'typename', func = { name = paramtest.default_to, params = { 'typename', "Type" }, flag = { 'p', 'r' } } },
		{ name = 'type', func = 'hasContent' },
		{ name = 'type_smw', func = { name = csv_to_multi, params = { 'type', true }, flag = { 'p', 'r' } } },
		{ name = 'toolname', func = { name = paramtest.default_to, params = { 'toolname', "Required tool" }, flag = { 'p', 'r' } } },
		{ name = 'tool', func = 'hasContent' },
		{ name = 'tool_smw', func = { name = csv_to_multi, params = { 'tool', true }, flag = { 'p', 'r' } } },
		{ name = 'timename', func = { name = paramtest.default_to, params = { 'timename', "Respawn time" }, flag = { 'p', 'r' } } },
		{ name = 'time', func = 'hasContent' },
		{ name = 'time_smw', func = { name = csv_to_multi, params = { 'time', true }, flag = { 'p', 'r' } } },
		{ name = 'members', func = yesno },
		{ name = 'dropversion', func = 'has_content' },
	}

	-- Generate skill-related arguments for each skill, similar to [[Template:Recipe]]
	for i = 1, MAXSKILL do
		local s = 'skill'..tostring(i)
		ret:defineParams{
			{ name = s..'name', func = 'hasContent' },
			{ name = s..'boostable', func = yesno },
			{ name = s..'lvl', func = { name = numericarg, params = { s..'lvl' }, flag = 'd' } },
			{ name = s..'disp', func = { name = expdisp, params = { s..'exp' }, flag = 'p' }, dupes = true },
			{ name = s..'exp', func = { name = exparg, params = { s..'exp' }, flag = 'd' }, dupes = true },
			{ name = s..'exp_def', func = { name = 'hasContent', params = { s..'exp' }, flag = 'p' } }, -- used for category, to check if the experience is actually defined
			{ name = s..'exp_smw', func = { name = tonumber, params = { s..'exp' }, flag = 'p' } },
			{ name = s..'label', func = { name = labelarg, params = { s..'label', s..'name', s..'note', s..'exp_smw' }, flag = 'd' } },
		}
	end
	ret:defineParams{
		{ name = 'skillnames', func = { name = tolist, params = { 'skill1name', 'skill2name', 'skill3name', 'skill4name', 'skill5name' }, flag = 'p' } },
		{ name = 'skillnames_str', func = { name = table.concat, params = { 'skillnames', ',' }, flag = { 'd', 'r' } } },
		{ name = 'skillnames_smw', func = { name = csv_to_multi, params = { 'skillnames_str', false }, flag = { 'd', 'r' } } },
		{ name = 'levels', func = { name = tolist, params = { 'skill1lvl', 'skill2lvl', 'skill3lvl', 'skill4lvl', 'skill5lvl' }, flag = 'p' } },
		{ name = 'clickpics', func = { name = clickpicsarg, params = { 'skillnames', 'levels' }, flag = 'd' } },
	}

	local smw_mapping = {
		name = 'Skilling node name',
		item_smw = 'Uses material',
		facility_smw = 'Uses facility',
		tool_smw = 'Uses tool',
		members = 'Is members only',
		skillnames_smw = 'Uses skill',
	}

	for _, s in ipairs(SKILLS) do
		local lcs = string.lower(s)
		-- These functions take in all lvl/xp info and poop out only the lvl/xp for the skill `lcs`.
		ret:defineParams{
			{ name = lcs..'lvl', func = { name = lvlsmw, params = { 'skillnames', 'levels', lcs }, flag = { 'd', 'd', 'r' } } },
			{ name = lcs..'exp', func = {
				name = expsmw,
				params = { 'skillnames', 'skill1exp_smw', 'skill2exp_smw', 'skill3exp_smw', 'skill4exp_smw', 'skill5exp_smw', lcs },
				flag = { 'd', 'd', 'd', 'd', 'd', 'd', 'r' }
			} },
		}
		smw_mapping[lcs..'lvl'] = s..' level'
		smw_mapping[lcs..'exp'] = s..' experience'
	end

	ret:create()
	ret:cleanParams()

	for i = 1, MAXSKILL do
		local s = 'skill'..tostring(i)
		-- link exp box to the respective class stored in disp
		ret:linkParams{
			{ s..'exp', s..'disp' },
		}
	end
	
	ret:customButtonPlacement(true)
	ret:setDefaultVersionSMW(true)
	ret:addButtonsCaption()

	ret:defineLinks({ hide = true })
	if not yesno(args.nosmw, false) then
		-- Apply SMW properties
		local smw_all_mapping = {}
		for param, property_name in pairs(smw_mapping) do
			smw_all_mapping[param] = 'All '..property_name
		end
		ret:useSMWSubobject(smw_mapping)
		ret:useSMWOne(smw_all_mapping)
	end

	ret:defineName('Skill info')
	ret:addClass('skill-info')
	
	local tblwidth = 10

	ret:addRow{
		{ tag = 'argh', content = 'name', class='infobox-header', colspan = tblwidth }
	}

	:pad(tblwidth)

	:addRow{
		{ tag = 'th', content = 'Level required', colspan = math.ceil(tblwidth/2) },
		{ tag = 'argd', content = 'clickpics', colspan = math.floor(tblwidth/2) }
	}
	
	for i = 1, MAXSKILL do
		local s = 'skill'..tostring(i)
		addNamedRow(ret, s..'exp', s..'label', tblwidth, mand)
	end

	addNamedRow(ret, 'item', 'itemname', tblwidth, mand)
	addNamedRow(ret, 'loc', 'locname', tblwidth, mand)
	addNamedRow(ret, 'facility', 'facilityname', tblwidth, mand)
	addNamedRow(ret, 'type', 'typename', tblwidth, mand)
	addNamedRow(ret, 'tool', 'toolname', tblwidth, mand)
	addNamedRow(ret, 'time', 'timename', tblwidth, mand)

	ret:pad(tblwidth)
	
	for i = 1, MAXSKILL do
		local s = 'skill'..tostring(i)
		local par = ret:param(s..'name', 'd')
		if infobox.isDefined(par) then
			ret:addDropLevelVars(par:lower(), s..'lvl')
		end
	end

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

function addNamedRow(ret, name, label, tblwidth, mand)
	if ret:paramDefined(name) or contains(mand, name) then
		ret:addRow{
			{ tag = 'argh', content = label, colspan = math.ceil(tblwidth/2) },
			{ tag = 'argd', content = name, colspan = math.floor(tblwidth/2) }
		}
	end
end

function labelarg(lbl, skill, note, xp)
	local ref = ''
	if paramtest.has_content(note) then
		ref = mw.getCurrentFrame():extensionTag{ name = 'ref', content = note, args = { group = 'i' } }
	end
	if paramtest.has_content(lbl) then
		return lbl .. ref
	elseif not infobox.isDefined(skill) then
		if (tonumber(xp) or 0) < 0 then
			return "[PLACEHOLDER]"
		else
			return nil
		end
	else
		return string.format('[[%s|%s XP]]', skill, skill) .. ref
	end
end

function exparg(xp)
	if xp ~= '' then
		return qty(xp)
	else
		return qty(0) .. edit
	end
end

function expdisp(xp)
	-- default: show (needs explicit -1 to be hidden)
	if (tonumber(xp) or 1) < 0 then
		return 'infobox-cell-hidden'
	else
		return 'infobox-cell-shown'
	end
end

function tolist(...)
	return arg
end

-- Make list of clickpics for output
function clickpicsarg(names, lvls)
	local ret = ''
	for i, lvl in ipairs(lvls) do
		local name = names[i]
		if paramtest.has_content(name) and paramtest.has_content(lvl) then
			ret = ret .. scp(name, lvl)
		end
	end
	if ret ~= '' then
		return ret
	else
		return edit
	end
end

function numericarg(arg)
	if not infobox.isDefined(arg) then
		return edit
	end
	return arg
end

function lvlsmw(skills, lvls, name)
	for i, s in ipairs(skills) do
		if string.lower(s) == name then
			return lvls[i]
		end
	end
	return nil
end

function expsmw(skills, xp1, xp2, xp3, xp4, xp5, name)
	local xp = {xp1, xp2, xp3, xp4, xp5}
	for i, s in ipairs(skills) do
		if string.lower(s) == name then
			return xp[i]
		end
	end
	return nil
end

-- Take the link targets from any list of links and turn it into a plain csv
function linkcsv(str)
	if type(str) ~= 'string' then return str end
	str = string.gsub(str, '%[%[File:[^%]]+%]%]', '')
	local ret = {}
	for m in string.gmatch(str, '%[%[([^%]|]+)') do
		table.insert(ret, m)
	end
	for m in string.gmatch(str, '%{%{[Pp]link|([^%}|]+)') do
		table.insert(ret, m)
	end

	return table.concat(ret, '&&SPLITPOINT&&')
end

function csv_to_multi(raw, links)
    assert(type(links) == 'boolean')

    local r = string.gsub(raw, "'\"`UNIQ[^`]*QINU`\"'", '') -- UNIQ QINU typically means unparsed content, such as <ref></ref>. Remove this from SMW
	if infobox.isDefined(raw) then
		if links then
			r = linkcsv(r)
		else
			r = string.gsub(r, '%s*,%s*', '&&SPLITPOINT&&')
		end
		return r
    end
	return nil
end

function addcategories(args, catargs, mand)
	local ret = {}

	-- mandatory arguments missing
	for _, v in ipairs(mand) do
		if catargs[v] and not catargs[v].all_defined then
			if v ~= 'skill1exp' then -- skill1exp is handled individually for a more specific category
				table.insert(ret, 'Missing skill info values')
				mw.log(v .. " is not defined (missing skill info values)")
			end
		end
	end

	-- Experience missing for one of the versions, or for any skill1 version
	for i = 1, 5 do
		local stri = tostring(i)
		-- Any skill# that has experience defined at least once, should specify it for every version. Also skill1 must be filled out fully.
		if (catargs['skill'..stri..'exp_def'].one_defined or i == 1) then
			-- No experience rate given for at least one version
			if not catargs['skill'..stri..'exp_def'].all_defined then
				table.insert(ret, 'Needs experience info')
			elseif not catargs['skill'..stri..'exp_smw'].all_defined then
				table.insert(ret, 'Pages with non-numeric experience quantity')
			end
			-- No label specified for at least one version
			if not catargs['skill'..stri..'label'].all_defined then
				table.insert(ret, 'Missing skill info values')
			end
		end
	end

	-- Determine skill-related categories to add
	local skills = {}
	-- Get list of all `skillnames` params, which in turn consist of a full list of skill names for each switch version
	local skillstr = args['skillnames_str']
	local skillnames = skillstr.switches
	-- for non-switched boxes, just use the default; otherwise, append the default values
	if not skillnames then
		skillnames = {skillstr.d}
	else
		table.insert(skillnames, skillstr.d)
	end
	-- iterate over all skills and toggle associative array for listed skills
	for _, ver in ipairs(skillnames) do
		for s in mw.text.gsplit(ver, ',') do
			if paramtest.has_content(s) then
				skills[s] = true
			end
		end
	end
	-- add actual skill categories
	for skill, _ in pairs(skills) do
		table.insert(ret, skill)
	end

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

	return table.concat(ret, '')
end

return p