Module:Bestiary
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