Module:Bestiary

From WIDEVERSE Wiki
Jump to navigation Jump to search

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

local p = {}
local filter = {}
local QUERY_SIZE = 8

local paramtest = require('Module:Paramtest')
local weakness_data = mw.loadData('Module:Weakness clickpic/data')
local hc = paramtest.has_content
local dt = paramtest.default_to
local yn = require('Module:Yesno')
local l = mw.getContentLanguage()

local membersMap = {
	-- key = { JSON name, SMW query }f
	all = { '*', '' },
	yes = { 'yes', '[[Is members only::true]]' },
	no = { 'no', '[[Is members only::false]]' },
}

local restrictionMap = {
	-- key = { JSON name, SMW query }
	all = { '*', '' },
	surface = { 'surface', '[[Location restriction::surface]]' },
	dg = { 'dungeoneering', '[[Location restriction::dungeoneering]]' },
	quest = { 'quest', '[[Location restriction::quest]]' },
	minigame = { 'minigame', '[[Location restriction::minigame]]' },
}

local styleMap = {
	-- key = { JSON name, SMW query }
	any = { '*', '' },
	melee = { 'melee', '[[NPC attack style::File:Attack-icon.png]]' },
	magic = { 'magic', '[[NPC attack style::File:Magic-icon.png]]' },
	ranged = { 'ranged', '[[NPC attack style::File:Ranged-icon.png]]' },
	dragonfire = { 'dragonfire', '[[NPC attack style::File:Dragonfire icon.png]]' },
	typeless = { 'zero', '[[NPC attack style::File:Zero weakness icon.png]]' },
}

local weaknessMap = {
	-- key = { JSON name, SMW query }
	any = { '*', '' },
	none = { 'zero', '[[Weakness::File:Zero weakness icon.png]]' },
	
	melee = { 'melee', '[[Weakness by class::melee]]' },
	stab = { 'stab', '[[Weakness::File:Stab weakness icon.png]]' },
	slash = { 'slash', '[[Weakness::File:Slash weakness icon.png]]' },
	crush = { 'crush', '[[Weakness::File:Crush weakness icon.png]]' },
	
	magic = { 'magic', '[[Weakness by class::magic]]' },
	air = { 'air', '[[Weakness::File:Air weakness icon.png]]' },
	water = { 'water', '[[Weakness::File:Water weakness icon.png]]' },
	earth = { 'earth', '[[Weakness::File:Earth weakness icon.png]]' },
	fire = { 'fire', '[[Weakness::File:Fire weakness icon.png]]' },
	
	ranged = { 'ranged', '[[Weakness by class::ranged]]' },
	arrows = { 'arrows', '[[Weakness::File:Arrow weakness icon.png]]' },
	bolts = { 'bolts', '[[Weakness::File:Bolt weakness icon.png]]' },
	thrown = { 'thrown', '[[Weakness::File:Thrown weakness icon.png]]' },
}

local extendedMap = {
	-- not passed to SMW so no query or JSON needed
	standard = false,
	extended = true
}

local otherFilterMap = {
	none = '',
	deflect = '[[NPC susceptible to deflect::true]]',
	drain = '[[NPC susceptible to drain::true]]',
	poison = '[[NPC susceptible to poison::true]]',
	stun = '[[NPC susceptible to stun::true]]',
	poisonous = '[[Is NPC poisonous::true]]',
	aggressive = '[[Is NPC aggressive::true]]',
	slayer = '[[Assigned by::+]]'
}

local slayerCatMap = {
	--TODO
	na = { '*', '' },
}
local slayerMasterMap = {
	na = { '*', '' },
	any = { '*', '[[Assigned by::+]]' },
	tureal = { 'turael', '[[Assigned by::File:Turael chathead.png]]' },
	mazchna = { 'mazchna', '[[Assigned by::File:Mazchna chathead.png]]' },
	chaeldar = { 'chaeldar', '[[Assigned by::File:Chaeldar chathead.png]]' },
	vannaka = { 'vannaka', '[[Assigned by::File:Vannaka chathead.png]]' },
	sumona = { 'sumona', '[[Assigned by::File:Sumona chathead.png]]' },
	duradel = { 'duradel', '[[Assigned by::File:Duradel chathead.png]]' },
	kuradal = { 'kuradal', '[[Assigned by::File:Kuradal chathead.png]]' },
	morvran = { 'morvran', '[[Assigned by::File:Morvran chathead.png]]' },
}

local image_map = {
	['Assigned by'] = {
		['File:Turael chathead.png'] = 'Turael',
		['File:Mazchna chathead.png'] = 'Mazchna',
		['File:Chaeldar chathead.png'] = 'Chaeldar',
		['File:Vannaka chathead.png'] = 'Vannaka',
		['File:Sumona chathead.png'] = 'Sumona',
		['File:Duradel chathead.png'] = 'Duradel',
		['File:Kuradal chathead.png'] = 'Kuradal',
		['File:Morvran chathead.png'] = 'Morvran',
	},
	['NPC attack style'] = {
		['File:Attack-icon.png'] = 'Melee',
		['File:Ranged-icon.png'] = 'Ranged',
		['File:Magic-icon.png'] = 'Magic',
		['File:Dragonfire icon.png'] = 'Dragonfire',
		['File:Zero weakness icon.png'] = 'Typeless',
	}
}
local _w = {}
for i,v in pairs(weakness_data) do
	_w['File:'..v.image] = v.link
end
image_map['Weakness'] = _w


local boolStrs = {
	['Is NPC aggressive'] = { 'Aggressive', 'Not aggressive', 'Both aggressive and not aggessive, see article' },
	['Is NPC poisonous'] = { 'Poisonous', 'Not poisonous', 'Both poisonous and not poisonous, see article' },
	['NPC susceptible to stun'] = { 'Susceptible to stuns', 'Immune to stuns', 'Both immune and susceptible to stuns, see article' },
	['NPC susceptible to poison'] = { 'Susceptible to poison', 'Immune to poison', 'Both immune and susceptible to poison, see article' },
	['NPC susceptible to deflect'] = { 'Susceptible to deflect damage', 'Immune to deflect damage', 'Both immune and susceptible to deflect damage, see article' },
	['NPC susceptible to drain'] = { 'Susceptible to stat drain', 'Immune to stat drain', 'Both immune and susceptible to stat drain, see article' }
}

local printouts = {
	-- use # or #- to specify printout to have no formatting (image/page properties)
	default = {
		'#-=page', -- first column, name
		'Is variant of#', 'Version anchor',
		'Combat level',
		'NPC attack style#',
		'NPC life points',
		'Weakness#',
		'Combat experience',
		'Slayer level',
		'Slayer experience',
		'Slayer category',
		'Assigned by#'
	},
	extended = {
		'NPC Attack level',
		'NPC Magic level',
		'NPC Ranged level',
		'NPC Defence level',
		'NPC melee max hit',
		'NPC magic max hit',
		'NPC ranged max hit',
		'NPC special max hit',
		'NPC melee accuracy',
		'NPC magic accuracy',
		'NPC ranged accuracy',
		'NPC armour',
		'NPC melee affinity',
		'NPC magic affinity',
		'NPC ranged affinity',
		'NPC weakness affinity',
		'Is NPC aggressive',
		'Is NPC poisonous',
		'NPC susceptible to deflect',
		'NPC susceptible to drain',
		'NPC susceptible to poison',
		'NPC susceptible to stun',
	}
}

function makeHeader()
	local t = mw.html.create('table')
	local r = t:tag('tr')
	
	t	:addClass('wikitable bestiary sortable sticky-header alternating-rows')
		:attr({
			['data-page'] = filter.info.page,
			['data-count'] = filter.info.count,
			['data-offset'] = filter.info.offset,
			['data-limit'] = filter.info.limit,
			['data-query'] = mw.text.nowiki(table.concat(filter.info.query,' '))
		})
	r	:tag('th'):wikitext('Monster'):done()
		:tag('th'):wikitext('Level'):done()
		:tag('th'):wikitext('Attack<br />style'):done()
		:tag('th'):wikitext('Life<br />points'):done()
		:tag('th'):wikitext('Weakness'):done()
		:tag('th'):wikitext('XP'):done()
		:tag('th'):wikitext('Slayer<br />level'):done()
		:tag('th'):wikitext('Slayer<br />XP'):done()
		:tag('th'):wikitext('Slayer<br />category'):done()
		:tag('th'):wikitext('Assigned<br />by'):done()
	
	if filter.extended then
		r	:tag('th'):wikitext('Attack<br />level'):done()
			:tag('th'):wikitext('Magic<br />level'):done()
			:tag('th'):wikitext('Ranged<br />level'):done()
			:tag('th'):wikitext('Defence<br />level'):done()
			:tag('th'):wikitext('Melee<br />damage'):done()
			:tag('th'):wikitext('Magic<br />damage'):done()
			:tag('th'):wikitext('Ranged<br />damage'):done()
			:tag('th'):wikitext('Special<br />damage'):done()
			:tag('th'):wikitext('Melee<br />accuracy'):done()
			:tag('th'):wikitext('Magic<br />accuracy'):done()
			:tag('th'):wikitext('Ranged<br />accuracy'):done()
			:tag('th'):wikitext('Armour'):done()
			:tag('th'):wikitext('Melee<br />affinity'):done()
			:tag('th'):wikitext('Magic<br />affinity'):done()
			:tag('th'):wikitext('Ranged<br />affinity'):done()
			:tag('th'):wikitext('Weakness<br />affinity'):done()
			:tag('th'):wikitext('Aggressive'):done()
			:tag('th'):wikitext('Poisonous'):done()
			:tag('th'):wikitext('Susceptible<br />to stun'):done()
			:tag('th'):wikitext('Susceptible<br />to poison'):done()
			:tag('th'):wikitext('Susceptible<br />to deflect'):done()
			:tag('th'):wikitext('Susceptible<br />to drain'):done()
	end
	
	return t
end

function p.query(frame)
	return p._query(frame:getParent().args)
end
p.main = p.query
function p._query(args)
	filter.members = membersMap[string.lower(args.members or 'all')] or membersMap.all
	filter.restriction = restrictionMap[string.lower(args.restriction or 'all')] or restrictionMap.all
	filter.style = styleMap[string.lower(args.style or 'any')] or styleMap.any
	filter.weakness = weaknessMap[string.lower(args.weakness or 'any')] or weaknessMap.any
	filter.enable_level_min = yn(args.enable_level_min, false)
	filter.enable_level_max = yn(args.enable_level_max, false)
	filter.level_min = tonumber(args.level_min or 0) or 0
	filter.level_max = tonumber(args.level_max or 100000) or 100000
	filter.other = otherFilterMap[string.lower(args.other or 'none')] or otherFilterMap.none
	filter.slayer_category = slayerCatMap[string.lower(args.slayer_category or 'na')] or slayerCatMap.na
	filter.slayer_master = slayerMasterMap[string.lower(args.slayer_master or 'na')] or slayerMasterMap.na
	filter.extended = extendedMap[string.lower(args.extended or 'any')] or extendedMap.standard
	
	
	local query = {'[[NPC ID::+]]'}
	table.insert(query, filter.members[2])
	table.insert(query, filter.restriction[2])
	table.insert(query, filter.style[2])
	table.insert(query, filter.weakness[2])
	if filter.enable_level_min and filter.level_min > 0 then
		table.insert(query, string.format('[[Combat level::≥%s]]', filter.level_min))
	end
	if filter.enable_level_max and filter.level_max < 100000 then
		table.insert(query, string.format('[[Combat level::≤%s]]', filter.level_max))
	end
	table.insert(query, filter.other)
	table.insert(query, filter.slayer_category[2])
	table.insert(query, filter.slayer_master[2])
	filter._filterCount = 0
	for i,v in ipairs(query) do
		if v and v ~= '' and string.match(v, '%[%[') then
			filter._filterCount = filter._filterCount + 1
		end
	end
	
	for i,v in ipairs(printouts.default) do
		table.insert(query, '?'..v)
	end
	if filter.extended then
		query.limit = 200
		for i,v in ipairs(printouts.extended) do
			table.insert(query, '?'..v)
		end
	else
		query.limit = 500
	end
	if args.__dump then
		query.limit = 10
	end
	local page = tonumber(args.page or 0) or 0
	query.offset = page * query.limit
	filter.info = {
		page = page,
		offset = query.offset,
		limit = query.limit,
		query = query
	}
	
	local ret = mw.smw.ask(query)
	if args.__dump__ then
		mw.logObject(ret)
		return
	end
	if ret then
		filter.info.count = #ret
		local t = makeHeader()
		
		local rows = {}
		local name
		for i,v in ipairs(ret) do
			t:node(p._make_row(v))
		end
		if filter._filterCount > QUERY_SIZE then
			return '<div id="bestiary-query-size-limit-reached" class="bestiary query-size-limit-reached table-bg-red">Warning: you have too many filters set and have exceeded the query size. The results provided below have ignored one or more of your filters. Set some filters to "Any"/"None"/disabled to reduce the size.</div>\n' .. tostring(t)
		end
		return t
	else
		return '<div id="bestiary-no-results-found" class="bestiary no-results-found table-bg-red">No results found for that filter set</div>'
	end
end

function p._make_row(data)
	function process(key, sep)
		sep = sep or ', '
		local info = data[key]
		if type(info) == 'table' then
			for i,v in ipairs(info) do
				if type(v) == 'number' then
					info[i] = l:formatNum(v)
				end
			end
			return table.concat(info, sep)
		elseif type(info) == 'number' then
			info = l:formatNum(info)
		end
		return info or ''
	end
	function file(key)
		local files = data[key]
		local links = image_map[key] or {}
		local ret = ''
		if type(files) == 'table' then
			ret = {}
			for i,v in ipairs(files) do
				table.insert(ret, mw.ustring.format('[[%s|25x25px|frameless|link=%s]]', v, links[v] or ''))
			end
			ret = table.concat(ret, ' ')
		elseif type(files) == 'string' then
			ret = mw.ustring.format('[[%s|25x25px|frameless|link=%s]]', files, links[files] or '')
		end
		return ret
	end
	function bool(key)
		local bools = data[key]
		local strs = boolStrs[key]
		if type(bools) == 'table' then
			return string.format('[[File:Yes check.svg|20x20px|link=|%s]][[File:X mark.svg|20x20px|link=|%s]]', strs[3], strs[3])
		end
		if bools == true then
			return string.format('[[File:Yes check.svg|20x20px|link=|%s]]', strs[1])
		elseif bools == false then
			return string.format('[[File:X mark.svg|20x20px|link=|%s]]', strs[2])
		end
		return '?'
	end
	
	local namestr = mw.ustring.format('[[%s]]',data.page)
	if data['Is variant of'] then
		namestr = mw.ustring.format('[[%s|%s <span class="beast-version">%s</span>]]',data.page, data['Is variant of'], data['Version anchor'])
	end
	local slayercat = process('Slayer category')
	if string.lower(slayercat) == 'n/a' then
		slayercat = ''
	else
		slayercat = l:ucfirst(slayercat)
	end
	
	local tr = mw.html.create('tr'):addClass('bestiaryrow')
	tr	:tag('td'):addClass('beast-name b_string')
			:wikitext(namestr)
		:done()
		:tag('td'):addClass('beast-level b_number b_level')
			:wikitext(process('Combat level'))
		:done()
		:tag('td'):addClass('beast-style b_image')
			:wikitext(file('NPC attack style'))
		:done()
		:tag('td'):addClass('beast-lifepoints b_number')
			:wikitext(process('NPC life points'))
		:done()
		:tag('td'):addClass('beast-weakness b_image')
			:wikitext(file('Weakness'))
		:done()
		:tag('td'):addClass('beast-experience b_number')
			:wikitext(process('Combat experience'))
		:done()
		:tag('td'):addClass('beast-slayer b_number b_level')
			:wikitext(process('Slayer level'))
		:done()
		:tag('td'):addClass('beast-slayerexperience b_number')
			:wikitext(process('Slayer experience'))
		:done()
		:tag('td'):addClass('beast-slayercategory b_string')
			:wikitext(slayercat)
		:done()
		:tag('td'):addClass('beast-assignedby b_image')
			:wikitext(file('Assigned by'))
		:done()
	
	-- TODO rework this to actually fit on a page
	if filter.extended then
		if not data['NPC weakness affinity'] then
			data['NPC weakness affinity'] = '-'
		end
		tr	:tag('td'):addClass('beast-attack b_number b_level')
				:wikitext(process('NPC Attack level'))
			:done()
			:tag('td'):addClass('beast-magic b_number b_level')
				:wikitext(process('NPC Magic level'))
			:done()
			:tag('td'):addClass('beast-ranged b_number b_level')
				:wikitext(process('NPC Ranged level'))
			:done()
			:tag('td'):addClass('beast-defence b_number b_level')
				:wikitext(process('NPC Defence level'))
			:done()
			:tag('td'):addClass('beast-maxmelee b_number')
				:wikitext(process('NPC melee max hit'))
			:done()
			:tag('td'):addClass('beast-maxmagic b_number')
				:wikitext(process('NPC magic max hit'))
			:done()
			:tag('td'):addClass('beast-maxranged b_number')
				:wikitext(process('NPC ranged max hit'))
			:done()
			:tag('td'):addClass('beast-maxspecial b_number')
				:wikitext(process('NPC special max hit'))
			:done()
			:tag('td'):addClass('beast-accmelee b_number')
				:wikitext(process('NPC melee accuracy'))
			:done()
			:tag('td'):addClass('beast-accmagic b_number')
				:wikitext(process('NPC magic accuracy'))
			:done()
			:tag('td'):addClass('beast-accranged b_number')
				:wikitext(process('NPC ranged accuracy'))
			:done()
			:tag('td'):addClass('beast-armour b_number')
				:wikitext(process('NPC armour'))
			:done()
			:tag('td'):addClass('beast-affmelee b_number')
				:wikitext(process('NPC melee affinity'))
			:done()
			:tag('td'):addClass('beast-affmagic b_number')
				:wikitext(process('NPC magic affinity'))
			:done()
			:tag('td'):addClass('beast-affranged b_number')
				:wikitext(process('NPC ranged affinity'))
			:done()
			:tag('td'):addClass('beast-affweakness b_number')
				:wikitext(process('NPC weakness affinity'))
			:done()
			:tag('td'):addClass('beast-aggressive b_boolean')
				:wikitext(bool('Is NPC aggressive'))
			:done()
			:tag('td'):addClass('beast-poisonous b_boolean')
				:wikitext(bool('Is NPC poisonous'))
			:done()
			:tag('td'):addClass('beast-stunsuscept b_boolean')
				:wikitext(bool('NPC susceptible to stun'))
			:done()
			:tag('td'):addClass('beast-poisonsuscept b_boolean')
				:wikitext(bool('NPC susceptible to poison'))
			:done()
			:tag('td'):addClass('beast-deflectsuscept b_boolean')
				:wikitext(bool('NPC susceptible to deflect'))
			:done()
			:tag('td'):addClass('beast-drainsuscept b_boolean')
				:wikitext(bool('NPC susceptible to drain'))
			:done()
	end
	
	return tr
end

return p