Module:Infobox NPC

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

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

--------------------------
-- Module for [[Template:Infobox NPC]]
------------------------
local p = {}

-- "imports"
local onmain = require('Module:Mainonly').on_main
local yesno = require('Module:Yesno')
local paramtest = require('Module:Paramtest')
local infobox = require('Module:Infobox')
local cleanimg = require('Module:Clean image').clean
local npcrace = mw.loadData('Module:NPC races')

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

	ret:defineParams{
		{ name = 'name', func = 'name' },
		{ name = 'aka', func = 'has_content' },
		{ name = 'image', func = imgarg },
		{ name = 'imagebackground', func = { name = imgback, params = { 'imagebackground' }, flag = { 'd' } }, dupes = true },
		{ name = 'vanchor', func = { name = 'has_content', params = { 'version' }, flag = 'p' } },
		{ name = 'release', func = 'release' },
		{ name = 'removal', func = 'removal' },
		{ name = 'members', func = 'has_content' },
		{ name = 'quest', func = { name = questarg, params = { 'quest' }, flag = { 'd' } } },
		{ name = 'location', func = { name = txtarg, params = { 'location' }, flag = 'd'  } },
		{ name = 'level', func = 'has_content' },
		{ name = 'actions', func = { name = actionarg, params = { 'actions' }, flag = { 'd' } } },
		{ name = 'actions_smw', func = { name = actionsmw, params = { 'actions' }, flag = { 'p' } }, dupes = true },
		{ name = 'thievelvl', func = { name = thievarg, params = { 'thievelvl' }, flag = { 'd' } } },
		{ name = 'thievelvl_smw', func = { name = thievarg, params = { 'thievelvl' }, flag = { 'd' } }, dupes = true },
		{ name = 'examine', func = { name = txtarg, params = { 'examine' }, flag = { 'd' } } },
		{ name = 'map', func = { name = maparg, params = { 'map' }, flag = { 'd' } } },
		{ name = 'intmap', func = { name = intmaparg, params = { 'intmap' }, flag = { 'd' } } },
		{ name = 'mapdisp', func = { name = mapdisp, params = { 'map', 'intmap' }, flag = { 'd', 'd' } }, dupes = true },
		{ name = 'race', func = racearg },
		{ name = 'id', func = { name = iddisp,  params = { 'id' }, flag = 'p' } },
		{ name = 'objectid', func = { name = iddisp,  params = { 'objectid' }, flag = 'p' } },
		{ name = 'id_smw', func = { name = idsmw, params = { 'id' }, flag = 'p' }, dupes = true },
		{ name = 'objectid_smw', func = { name = idsmw, params = { 'objectid' }, flag = 'p' }, dupes = true },
		{ name = 'chisel_links', func = { name = 'make_chisel_links', params = { 'name', '<br>', 'mrnd', 'id', 'mrod', 'objectid' }, flag = { 'p', 'r', 'r', 'p', 'r', 'p' } } },
		{ name = 'shop', func = 'has_content' },
		{ name = 'gender', func = 'has_content' },
		{ name = 'f2pvisible', func ='has_content' },
		{ name = 'voice', func ='has_content' },
	}

	ret:useSMW({
		members = 'Is members only',
		thievelvl_smw = 'Thieving level'
	})

	ret:useSMWOne({
		id_smw = 'All NPC ID',
		objectid_smw = 'All Object ID'
	})

	ret:useSMWSubobject({
		id_smw = 'NPC ID',
		objectid_smw = 'Object ID',
--		name = 'NPC name',
		members = 'Is members only',
--		location = 'NPC location',
		actions_smw = 'Actions',
		release = 'Release date',
		update = 'Release update',
		--removal = 'Removal date',
		removalupdate = 'Removal update',
		thievelvl_smw = 'Thieving level',
		examine = 'Examine'
	})

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

	ret:linkParams{
		image = 'imagebackground'
	}
	
	ret:defineLinks({ links = {{ 'Template:%s', 'Infobox' },
		{ 'Template_talk:%s', 'Talk page' }}, colspan = '2' })

	ret:defineName('Infobox NPC')
	ret:addClass('rsw-infobox plainlinks')

	ret:addButtonsCaption()

	-- PARAMETER: image
	ret:addRow{
		{ tag = 'argd', content = 'image', class = 'infobox-image bordered-image', colspan = '2' },
		meta = { addClass = ret:param('imagebackground', 'r') }
	}

	-- PARAMETER: name
	ret:addRow{
		{ tag = 'argh', content = 'name', class='infobox-header', colspan = '2' }
	}
	
	-- PARAMETER: release
	-- (update included automatically by infobox)
	:addRow{
		{ tag = 'th', content = 'Release date', css = { ['white-space'] = 'nowrap' } },
		{ tag = 'argd', content = 'release' }
	}

	-- PARAMETER: removal
	if ret:paramDefined('removal') then
		ret:addRow{
			{ tag = 'th', content = 'Removal' },
			{ tag = 'argd', content = 'removal' }
		}
	end

	-- PARAMETER: aka
	if ret:paramDefined('aka') then
		ret:addRow{
			{ tag = 'th', content = 'Also called' },
			{ tag = 'argd', content = 'aka' }
		}
	end

	-- PARAMETER: level
	if ret:paramDefined('level') then
		ret:addRow{
			{ tag = 'th', content = '[[Combat level]]', css = { ['white-space'] = 'nowrap' } },
			{ tag = 'argd', content = 'level' }
		}
	end

	-- PARAMETER: race
	ret:addRow{
		{ tag = 'th', content = '[[Races|Race]]' },
		{ tag = 'argd', content = 'race' }
	}
	
	-- PARAMETER: members
	:addRow{
		{ tag = 'th', content = '[[Members]]' },
		{ tag = 'argd', content = 'members' }
	}
	
	-- PARAMETER: quest
	ret:addRow{
		{ tag = 'th', content = '[[:Category:Quest NPCs|Quest NPC]]' },
		{ tag = 'argd', content = 'quest' }
	}

	-- PARAMETER: location
	ret:addRow{
		{ tag = 'th', content = '[[Locations|Location(s)]]' },
		{ tag = 'argd', content = 'location' }
	}
	
	-- PARAMETER: shop
	ret:addRow{
		{ tag = 'th', content = '[[Shop|Sells items]]' },
		{ tag = 'argd', content = 'shop' }
	}

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

	-- PARAMETER: actions
	ret:addRow{
		{ tag = 'th', content = 'Actions' },
		{ tag = 'argd', content = 'actions' }
	}
	
	-- PARAMETER: thievelvl
	if ret:paramDefined('thievelvl') then
		ret:addRow{
			{ tag = 'th', content = '[[File:Thieving-icon.png|link=Thieving|frameless|20px|alt=Thieving level|Thieving level]] [[Thieving|Level]]' },
			{ tag = 'argd', content = 'thievelvl' } }
	end

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

	-- PARAMETER: map
	local map_defined  = ret:paramGrep('mapdisp', function(x) return (x or 'false') ~= 'false' end)
	if map_defined  == true then
		if ret:paramDefined('map', 'all') then
		ret:addRow{
			{ tag = 'argd', content = 'map', class = 'infobox-map', colspan = '2' } }
		elseif ret:paramDefined('intmap', 'all') then
		ret:addRow{
			{ tag = 'argd', content = 'intmap', class = 'infobox-map', colspan = '2' } }
		end
	end

	-- PARAMETER: Advanced data
	ret:addRow{
		{ tag = 'th', content = 'Advanced data', class = 'infobox-subheader', colspan = '2' },
		meta = {addClass = 'advanced-data'}
	}
	:addRow{
		{ tag = 'th', content = 'NPC ID' },
		{ tag = 'argd', content = 'id' },
		meta = {addClass = 'advanced-data'}
	}
	if ret:paramDefined('objectid', 'all') then
	ret:addRow{
		{ tag = 'th', content = 'Object ID' },
		{ tag = 'argd', content = 'objectid' },
		meta = {addClass = 'advanced-data'}
	}
	end
	ret:addRow{
		{ tag = 'th', content = 'Links' },
		{ tag = 'argd', content = 'chisel_links' },
		meta = {addClass = 'advanced-data'} }

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

function imgarg(arg)
	if infobox.isDefined(arg) then
		return cleanimg{ file = arg, width = 300, height = 300 }
	end
	return nil
end

function imgback(arg)
	if infobox.isDefined(arg) then
		if yesno(arg) then
			return 'infobox-imgbg'
		end
	end
	return ''
end

function txtarg(txt)
	if infobox.isDefined(txt) then
		txt = mw.text.trim(txt)
		if string.sub(txt, 1,1) == '*' then
			local ret = mw.html.create('ul')
			for x in mw.text.gsplit(txt, '\n%*%s*') do
				local _x = string.gsub(x, '^%*%s*', '')
				ret:tag('li'):wikitext(_x)
			end
			return tostring(ret)
			-- txt = '\n'..txt..'\n'
		end
		return txt
	end
	return nil
end

function thievarg(arg)
	arg = string.gsub(arg or '',',','')
	return tonumber(arg, 10)
end

function maparg(arg)
	if infobox.isDefined(arg) then
		local low = string.lower(arg)
		if yesno(low) == false or low == 'none' then
			return "''no map to display''"
		elseif low == 'name' then
			return string.format('[[File:%s location.png]]', mw.title.getCurrentTitle().text)
		elseif string.find(low, '.png') then
			return cleanimg{ file = arg, width = 250 }
		else
			return arg
		end
	else
		return nil
	end
end

function racearg(arg)
	local ret = { '' }
	if infobox.isDefined(arg) then
		for _, v in ipairs(npcrace) do
			if v.race == string.lower(arg) then
			 	table.insert(ret, v.racelink)
		    end
		end
		for i, v in ipairs(ret) do
			if (v ~= '') then
				ret[i] = string.format('[[%s]]', v)
			end
		end
		
		r =  table.concat(ret, '')
		if r ~= '' then
			return r
		else
			--Do not return invalid categories
			--return arg
		end
	end

	return nil
end

function intmaparg(arg)
	if infobox.isDefined(arg) then
		if yesno(arg) == false or string.lower(arg) == 'none' then
			return "''no map to display''"
		else
			return mw.getCurrentFrame():preprocess( arg )
		end
	else
		return nil
	end
end

function mapdisp(map, intmap)
	if infobox.isDefined(intmap) or infobox.isDefined(map) then
		local yn_map = map == "''no map to display''"
		local yn_intmap = intmap == "''no map to display''"
		if yn_map or yn_intmap then
			return 'false'
		else
			return 'true'
		end
	else
		return 'false'
	end
end

function iddisp(id)
	if infobox.isDefined(id) then
		if id:lower() ~= 'no' and id:lower() ~= 'none' then
			local r = string.gsub(id, ', *', ', ')
			return r
		elseif id:lower() == 'no' or id:lower() == 'none' then
			return 'None'
		end
	end
	return nil
end

function idsmw(id)
	if infobox.isDefined(id) and id:lower() ~= 'no' and id:lower() ~= 'none' then
		local r = string.gsub(id, ', *', infobox.splitpoint)
		return r
	end
	return nil
end

function questarg(arg)
	if infobox.isDefined(arg) then
		if string.lower(arg) == 'no'  then
			return 'No'
		else
			local ret = mw.html.create('ul')
			local r = mw.ustring.gsub(arg, '%[*Missing, Presumed Death%]*', '!foo!')
			for x in mw.text.gsplit(r, ',') do
				ret:tag('li'):wikitext(mw.text.trim(x))
			end
			ret = tostring(ret)
			ret = mw.ustring.gsub(ret, '!foo!', '[[Missing, Presumed Death]]')
			return ret
		end
	end
	return nil
end

function actionarg(arg)
	if infobox.isDefined(arg) then
		if string.lower(arg) == 'none' or string.lower(arg) == 'examine' then
			return '<ul><li>Examine</li></ul>'
		end
		local has_examine = false
		local ret = mw.html.create('ul')
		for x in mw.text.gsplit(arg, ',') do
			ret:tag('li'):wikitext(mw.text.trim(x))
			has_examine = has_examine or (string.find(x, 'Examine') ~= nil)
		end
		if not has_examine then
			ret:tag('li'):wikitext('Examine')
		end
		ret = tostring(ret)
		ret = mw.ustring.gsub(ret, '%[*[Ww]alk [Hh]ere%]*', '[[Walk here|Walk here]]')
		ret = mw.ustring.gsub(ret, '%[*[Ee]xamine%]*', 'Examine')
		return ret
	end
	return nil
end

function actionsmw(arg)
	if infobox.isDefined(arg) then
		if string.lower(arg) == 'none' or string.lower(arg) == 'examine' then
			return 'Examine'
		end
		local r = string.gsub(arg, ',%s*', infobox.splitpoint)
		r = mw.ustring.gsub(r, '%[*[Ww]alk [Hh]ere%]*', 'Walk here')
		r = mw.ustring.gsub(r, '%[*[Ee]xamine%]*', 'Examine')
		if not r:find('Examine') then
			r = r .. infobox.splitpoint .. 'Examine'
		end
		return r
	end
	return nil
end

function addcategories(args, catargs)
	function is_def(x, y)
		y = y or 'd'
		if infobox.isDefined(x) then
			return infobox.isDefined(x[y])
		end
		return false
	end
	
	local ret = { 'Non-player characters' }
	
	-- ID based categories
	local misID = {}
	if args.id.switches then
		for i,v in ipairs(args.id.switches) do
			if infobox.isDefined(v) and v ~= 'None' then
				table.insert(ret, 'Cache NPCs')
			else
				misID[i] = true
			end
		end
	elseif args.id.d and args.id.d ~= 'None' then
		table.insert(ret, 'Cache NPCs')
	end
	
	
	if args.objectid.switches then
		for i,v in ipairs(args.objectid.switches) do
			if infobox.isDefined(v) and v ~= 'None' then
				table.insert(ret, 'Cache scenery')
				if misID[i] then
					misID[i] = false
				end
			end
		end
	elseif infobox.isDefined(args.objectid.d) and args.objectid.d ~= 'None' then
		table.insert(ret, 'Cache scenery')
		misID = {}
	end
	for _,v in pairs(misID) do
		if v then
			table.insert(ret, 'Needs ID')
		end
	end
	
	local cat_map = {
		-- Added if the parameter has content
		defined = {
			aka = 'Pages with AKA',
			thievelvl = 'Thievable entities',
		},
		-- Added if the parameter has no content
		notdefined = {
			image = 'Needs non-player character image',
			members = 'Needs members status',
			release = 'Needs release date',
			examine = 'Needs examine added',
			actions = 'Needs actions added',
			race = 'Needs race',
			gender = 'Needs gender',
		},
	}
	
	-- Run and add mapped categories
	for n, v in pairs(cat_map.defined) do
		if catargs[n] and catargs[n].one_defined then
			table.insert(ret,v)
		end
	end
	for n, v in pairs(cat_map.notdefined) do
		if catargs[n] and catargs[n].all_defined == false then
			table.insert(ret,v)
		end
	end
	
	-- Add the associated race category if matched
	local norace = true
	if args['race'] and args['race'].d then
		local racestr = string.gsub(string.gsub(args['race'].d, '%]%]', ''), '%[%[', '')
		for _, v in ipairs(npcrace) do
			if (v.racelink == racestr) then
		    	table.insert(ret, v.racecat)
		    	if v.addcat then
		    		for _,k in ipairs(v.addcat) do
		    			table.insert(ret, k)
		    		end
		    	end
		    	norace = false
		    end
		end
	end
	if norace then
		table.insert(ret, 'Needs race')
	end
	
	-- Add the associated gender category if matched
	if args['gender'] then
		if string.lower(args['gender'].d or '') == 'male' then
			table.insert(ret, 'Male characters')
		elseif string.lower(args['gender'].d or '') == 'female' then
			table.insert(ret, 'Female characters')
		elseif string.lower(args['gender'].d or '') == 'no' then
			table.insert(ret, 'Genderless characters')
		end
	end

	-- Add the associated category if the parameter is valued at yes
	if args['voice'] then
		if string.lower(args['voice'].d or '') == 'yes' then
			table.insert(ret, 'Voice acted NPCs')
		end
	end
	
	if args['f2pvisible'] then
		if string.lower(args['f2pvisible'].d or '') == 'yes' then
			table.insert(ret, 'Non-player characters that are visible but not interactive in free-to-play')
		end
	end
	
	-- Add the associated category if the parameter is not valued at no
	if args['shop'] then
		if string.lower(args['shop'].d or '') ~= 'no' then
			table.insert(ret, 'Merchants')
		end
	end

	if args['quest'] then
		if string.lower(args['quest'].d or '') ~= 'no' then
			table.insert(ret, 'Quest NPCs')
		end
	end
	
	-- Map category
	if catargs.mapdisp and catargs.mapdisp.all_defined == false then
		if args.map then
			if args.map.d ~= 'no' then
				if args.intmap then
					if args.intmap.d ~= 'no' then
						table.insert(ret, 'Needs map')
					end
				end
			end
		else
			if args.intmap then
				if args.intmap.d ~= 'no' then
					table.insert(ret, 'Needs map')
				end
			else
				table.insert(ret, 'Needs map')
			end
		end
	end
	
	mw.logObject(args['actions'])
	mw.logObject(args['actions_smw'])
	mw.log(infobox.isDefined(args['actions']))
	mw.log(infobox.isDefined(args['actions_smw']))
	mw.log(is_def(args['actions']))
	mw.log(is_def(args['actions_smw']))
	mw.log(string.lower(args['actions_smw'].d or '') == 'examine')
	
	-- Action based categories
	if args.actions_smw.switches then
		for _,v in ipairs(args.actions_smw.switches) do
			if infobox.isDefined(v) then
				if v == 'Examine' then
					table.insert(ret, 'Non-interactive characters')
				else
					table.insert(ret, 'Interactive characters')
				end
			end
		end
	elseif infobox.isDefined(args.actions_smw.d) then
		if args.actions_smw.d == 'Examine' then
			table.insert(ret, 'Non-interactive characters')
		else
			table.insert(ret, 'Interactive characters')
		end	
	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