Module:Sandbox/User:Fjara/Sandbox/Shovel
Documentation for this module may be created at Module:Sandbox/User:Fjara/Sandbox/Shovel/doc
local p = {}
--0: csv "name (parenthetical)"-only
function createHeader(verbosity)
local ret = mw.html.create('table'):addClass('wikitable sortable')
:addClass('align-center-1 align-left-2 align-center-3 align-center-4')
local header = mw.html.create('tr')
:tag('th'):css('text-align', 'center'):attr('colspan', 2):wikitext('Monster'):done()
:tag('th'):wikitext('[[File:Member icon.png|link=|Members]]'):done()
:tag('th'):wikitext('[[File:Attack style icon.png|link=|Combat level]]'):done()
if(tonumber(verbosity) > 1) then
ret:addClass('align-center-5 align-center-6 align-center-7 align-center-8 align-center-9 align-center-10')
header:tag('th'):wikitext('[[File:Hitpoints icon.png|link=|Hitpoints]]'):done()
:tag('th'):wikitext('[[File:Attack icon.png|link=|Attack level]]'):done()
:tag('th'):wikitext('[[File:Strength icon.png|link=|Strength level]]'):done()
:tag('th'):wikitext('[[File:Defence icon.png|link=|Defence level]]'):done()
:tag('th'):wikitext('[[File:Magic icon.png|link=|Magic level]]'):done()
:tag('th'):wikitext('[[File:Ranged icon.png|link=|Ranged level]]'):done()
elseif(tonumber(verbosity) > 2) then
ret:addClass('align-center-11 align-center-12 align-center-13 align-center-14 align-center-15 align-center-16 align-center-17 align-center-18 align-center-19 align-center-20 align-center-21 align-center-22 align-center-23 align-center-24 align-center-25 align-center-26 align-center-27 align-center-28 align-center-29 align-center-30 align-center-31 align-center-32 align-center-33')
header:tag('th'):wikitext('[[File:Attack icon.png|link=|Attack bonus]]'):done()
:tag('th'):wikitext('[[File:Strength icon.png|link=|Strength bonus]]'):done()
:tag('th'):wikitext('[[File:Magic icon.png|link=|Magic bonus]]'):done()
:tag('th'):wikitext('[[File:Magic Damage icon.png|link=|Magic strength bonus]]'):done()
:tag('th'):wikitext('[[File:Ranged icon.png|link=|Ranged bonus]]'):done()
:tag('th'):wikitext('[[File:Ranged Strength icon.png|link=|Ranged Strength bonus]]'):done()
:tag('th'):wikitext('[[File:White dagger.png|link=|Defensive stab bonus]]'):done()
:tag('th'):wikitext('[[File:White scimitar.png|link=|Defensive slash bonus]]'):done()
:tag('th'):wikitext('[[File:White warhammer.png|link=|Defensive crush bonus]]'):done()
:tag('th'):wikitext('[[File:Magic icon.png|link=|Defensive Magic bonus]]'):done()
:tag('th'):wikitext('[[File:Ranged icon.png|link=|Defensive Ranged bonus]]'):done()
:tag('th'):wikitext('[[File:Poison hitsplat.png|link=|Poison immunity]]'):done()
:tag('th'):wikitext('[[File:Venom hitsplat.png|link=|Venom immunity]]'):done()
:tag('th'):wikitext('[[File:Slayer icon.png|link=|Slayer level]]'):done()
:tag('th'):wikitext('[[File:Slayer icon.png|link=|Slayer experience]]Experience'):done()
:tag('th'):css('text-align', 'left'):wikitext('Slayer categories'):done()
:tag('th'):css('text-align', 'left'):wikitext('Assigned by'):done()
:tag('th'):css('text-align', 'left'):wikitext('Experience bonus'):done()
:tag('th'):css('text-align', 'left'):wikitext('[[Combat Style]]'):done()
:tag('th'):css('text-align', 'center'):wikitext('[[Attack speed]]'):done()
:tag('th'):css('text-align', 'center'):wikitext('Inflicts [[poison]]'):done()
:tag('th'):css('text-align', 'center'):wikitext('Aggressiveness'):done()
:tag('th'):css('text-align', 'center'):wikitext('Size'):done()
end
ret:node(header):done()
return ret
end
-- Estimate the storage size of a serialized table of data
-- This function assumes somewhat consistent sizes of the elements
-- like for instance typical SMW data tables
function p.est_serial_size(tbl, datapoints)
assert(type(tbl) == 'table')
assert(type(datapoints) == 'number')
local dp = {}
for p = 1, datapoints do
local pos = math.floor(#tbl * p / datapoints)
local txt = mw.text.jsonEncode(tbl[pos])
table.insert(dp, txt:len())
end
local avgsize = math.floor(enum.sum(dp) / datapoints)
return #tbl * avgsize
end
-- Filter data with exclusion lists
function p.filterData(indata, disco, quest, dmm, cache)
-- Fetch exclusion list
local exlist = {}
if not disco then
table.insert(exlist, '[[Category:Monsters]] [[Category:Discontinued content]]')
end
if not quest then
table.insert(exlist, '[[Category:Quest monsters]]')
end
if not dmm then
table.insert(exlist, '[[Category:Monsters]] [[Category:Deadman Mode]]')
end
if not cache then
table.insert(exlist, '[[Category:Monsters]] [[Category:Pages using information from game APIs or cache]]')
end
local pages_excl = pageswithcats(exlist)
-- Post-process the data
local data = {}
for _, entry in ipairs(indata) do
local process = true
if enum.contains(pages_excl, entry['variantof']) or enum.contains(pages_excl, entry['name']) then
process = false
end
if process then
table.insert(data, entry)
else
--mw.log(string.format('Removed: %s', entry[1]))
end
end
-- Statistics
mw.log(string.format('Filter: exclusion list size: %i, start size: %i, end size: %i, removed %i.',
#pages_excl, #indata, #data, #indata - #data))
return data
end
local yesNo = require('Module:Yesno')
local paramTest = require('Module:Paramtest')
local hasContent = require('Module:Paramtest').has_content
local enum = require('Module:Array')
local pagelisttools = require('Module:PageListTools').pageswithcats
local SlayerMasters = { 'Turael', 'Spria', 'Krystilia', 'Mazchna', 'Vannaka', 'Chaeldar', 'Konar quo Maten', 'Nieve', 'Steve', 'Duradel' }
local SlayerTasks = { 'All', 'Aberrant Spectres', 'Abyssal Demons', 'Ankou', 'Aviansies', 'Bandits', 'Banshees', 'Basilisks', 'Bats', 'Bears', 'Birds', 'Black Demons', 'Black Dragons', 'Black Knights', 'Bloodveld', 'Blue Dragons', 'Bosses', 'Brine Rats', 'Bronze Dragons', 'Catablepon', 'Cave Bugs', 'Cave Crawlers', 'Cave Horrors', 'Cave Kraken', 'Cave Slimes', 'Chaos Druids', 'Cockatrice', 'Cows', 'Crawling Hands', 'Crocodiles', 'Dagannoths', 'Dark Beasts', 'Dark Warriors', 'Dogs', 'Drakes', 'Dust Devils', 'Dwarves', 'Earth Warriors', 'Elves', 'Ents', 'Fever Spiders', 'Fire Giants', 'Flesh Crawlers', 'Fossil Island Wyverns', 'Gargoyles', 'Ghosts', 'Ghouls', 'Goblins', 'Greater Demons', 'Green Dragons', 'Harpie Bug Swarms', 'Hellhounds', 'Hill Giants', 'Hobgoblins', 'Hydras', 'Ice Giants', 'Ice Warriors', 'Icefiends', 'Infernal Mages', 'Iron Dragons', 'Jellies', 'Jungle Horrors', 'Kalphite', 'Killerwatts', 'Kurask', 'Lava Dragons', 'Lesser Demons', 'Lizardmen', 'Lizards', 'Magic Axes', 'Mammoths', 'Minotaurs', 'Mithril Dragons', 'Mogres', 'Molanisks', 'Monkeys', 'Moss Giants', 'Mutated Zygomites', 'Nechryael', 'Ogres', 'Otherworldly Beings', 'Pirates', 'Pyrefiends', 'Rats', 'Red Dragons', 'Revenants', 'Rockslugs', 'Rogues', 'Rune Dragons', 'Scabarites', 'Scorpions', 'Sea Snakes', 'Shades', 'Shadow Warriors', 'Skeletal Wyverns', 'Skeletons', 'Smoke Devils', 'Sourhogs', 'Spiders', 'Spiritual Creatures', 'Steel Dragons', 'Suqahs', 'Terror Dogs', 'Trolls', 'Turoth', 'TzHaar', 'Vampyres', 'Wall Beasts', 'Waterfiends', 'Werewolves', 'Wolves', 'Wyrms', 'Zombies' }
local Attributes = { 'demon', 'draconic', 'fiery', 'golem', 'kalphite', 'leafy', 'penance', 'shade', 'spectral', 'undead', 'vampyre', 'vampyre1', 'vampyre2', 'vampyre3', 'xerician' }
local MemberOptions = {'members', 'f2p', 'all'}
local AlphaOnlyPattern = '^%a$'
function shouldExclude(property, exclusionList)
for _, exclusion in ipairs(exclusionList) do
if((paramTest.has_content(exclusion)) and (ustring.find(string.lower(untable(property) or ''), string.lower(trim(exclusion))))) then
return true
end
end
return false
end
function tableConcat(mainTable, newTable, exclusionList)
for i = 1, #newTable, 1 do
--
if(true) then
mainTable[#mainTable+1] = newTable[i]
end
end
return mainTable
end
function loadData(batchSize, limit, slayerOutput, members, assignedBy, slayerTask, attribute, fromLevel, toLevel, fromLetter, toLetter, cache, dmm, quest, discontinued)
local query = {
'[[Category:Monsters]]',
'[[Combat level::≥' .. fromLevel .. ']] [[Combat level::≤' .. toLevel .. ']]', -- How bad is it to add this even if it doesnt filter
'[[Combat level::≥' .. fromLevel .. ']] [[Combat level::≤' .. toLevel .. ']]', -- How bad is it to add this even if it doesnt filter
limit = batchSize,
offset = 0,
}
if(members == 'members') then
table.insert(query, ' [[Is members only::true]]')
elseif(members == 'f2p') then
table.insert(query, ' [[Is members only::false]]')
end
if(assignedBy) then
table.insert(query, ' [[Assigned by::' .. assignedBy .. ']]')
end
if(slayerTask) then
if(slayerTask == 'All') then
table.insert(query, ' [[Slayer category::+]]')
else
table.insert(query, ' [[Slayer category::' .. slayerTask .. ']]')
end
end
if(attribute) then
table.insert(query, ' [[Monster attribute::' .. attribute .. ']]')
end
if(cache) then
table.insert(query, ' [[Category:Pages using information from game APIs or cache]]')
end
if(dmm) then
table.insert(query, ' [[Category:Deadman Mode]]')
end
if(quest) then
table.insert(query, ' [[Category:Quest monsters]]')
end
if(discontinued) then
table.insert(query, ' [[Category:Discontinued content]]')
end
table.insert(query, '?=#-')
table.insert(query, '?Image#-=image')
table.insert(query, 'Is members only#-=members')
table.insert(query, 'Combat level#-=level')
table.insert(query, 'Hitpoints#-=hitpoints')
table.insert(query, 'Attack level#-=attack')
table.insert(query, 'Defence level#-=defence')
table.insert(query, 'Magic level#-=magic')
table.insert(query, 'Ranged level#-=ranged')
table.insert(query, 'Is variant of#-=variantof')
if(slayerOutput) then
table.insert(query, 'Slayer level#-=slayerLevel')
table.insert(query, 'Slayer experience#-=slayerXP')
table.insert(query, 'Slayer category#-=slayerCat')
table.insert(query, 'Assigned by#-=assignedBy')
end
local allData = {}
for i = 0, limit, batchSize do
query.limit = batchSize
query.offset = i
local t1 = os.clock()
local smwData = mw.smw.ask(query)
local t2 = os.clock()
if(smwData == nil) then break end
mw.log(string.format('SMW: entries %d, time elapsed: %.3f ms.', #smwData, (t2 - t1) * 1000))
tableConcat(allData, smwData, exclusionList)
end
assert(allData ~= nil and #allData > 0, 'SMW query failed')
for _, monster in ipairs(allData) do
monster['name'] = monster[1]
monster[1] = nil
if(type(monster['image']) == 'table') then
monster['image'] = monster['image'][1]
end
if(type(monster['slayerCat']) == 'table') then
monster['slayerCat'] = table.concat(monster['slayerCat'], ', ')
end
if(type(monster['assignedBy']) == 'string') then
monster['assignedBy'] = { monster['assignedBy'] }
elseif type(dataline['assignedby']) == 'nil' then
monster['assignedBy'] = { }
end
end
return allData
end
function p._main(args)
local batchSize = paramTest.default_to(tonumber(args.batchsize), 100)
local limit = paramTest.default_to(tonumber(args.limit), 5000) -- Acting as a real limiter
local csvOutput = yesNo(args.csv or '', true)
local slayerOutput = yesNo(args.slayer or '', true)
local members = contains(MemberOptions, args.members) and args.members or 'all'
local assignedBy = contains(SlayerMasters, args.assignedby) and args.assignedby or ''
local slayerTask = contains(SlayerTasks, args.slayertask) and args.slayertask or 'All' --force uppercase all words
local attribute = contains(Attributes, args.attribute) and args.attribute or ''
local levelSort = yesNo(args.levelsort or '', true) -- If false, alphabetical sort
local fromLevel = paramTest.default_to(tonumber(args.fromlevel), 0)-- should these not default
local toLevel = paramTest.default_to(tonumber(args.tolevel), 99999)-- should these not default
local fromLetter = args.fromletter:match(AlphaOnlyPattern) and args.fromletter:upper() or 'A'-- should these not default
local toLetter = args.toletter:match(AlphaOnlyPattern) and args.toletter:upper() or 'Z'-- should these not default
assert(fromLevel > toLevel, 'fromlevel needs to be less then or equal to tolevel')
assert(fromLetter > toLetter, 'fromletter needs to be the same or earlier than toletter')
local cache = yesNo(args.cache or '', false)
local dmm = yesNo(args.dmm or '', false)
local quest = yesNo(args.quest or '', true)
local discontinued = yesNo(args.discontinued or '', false)
local data = loadData(batchSize, limit, slayerOutput, members, assignedBy, slayerTask, attribute, fromLevel, toLevel, fromLetter, toLetter, cache, dmm, quest, discontinued)
data = filterData(data, ba['disco'], ba['quest'], ba['dmm'], ba['cache'])
if ba['levelsort'] then
table.sort(data, function(a, b) return a['level'] < b['level'] end)
end
-- Format the output page
local div = mw.html.create('div')
if #data == 0 then
div:wikitext('Search yielded no results.')
return div
elseif ba['showstats'] then
div:wikitext(string.format('Search yielded %i results.', #data))
if smwstats['found'] == smwstats['limit'] then
div:wikitext(string.format(' Your search might have been too large and have been truncated as a result.'))
end
end
local tbl = div:tag('table'):addClass('wikitable sortable')
:addClass('align-center-1 align-left-2 align-left-3 align-center-4 align-center-5')
:addClass('align-center-6 align-center-7 align-center-8 align-center-9')
if ba['verbose'] then
tbl:addClass('align-center-10 align-center-11 align-left-12 align-left-13')
end
p.header(tbl, ba['verbose'])
-- Render rows
for e, entry in ipairs(data) do
local name = mw.text.split(entry['name'], '#', true)
if name[2] then name[2] = name[2]:gsub('_', ' ') else name[2] = ' ' end
local tr = tbl:tag('tr')
:tag('td'):css('height', '64px'):wikitext(entry['image'] and string.format('[[%s|link=|64x64px|%s]]', entry['image'], entry['name']) or ''):done()
:tag('td'):wikitext(string.format('[[%s|%s]]<br/>\'\'%s\'\'', entry['name'], name[1], name[2])):done()
:tag('td'):wikitext(entry['members'] and '[[File:Member icon.png|link=|Members]]' or '[[File:Free-to-play icon.png|link=|Free-to-play]]'):done()
:tag('td'):wikitext(entry['level']):done()
:tag('td'):wikitext(entry['hitpoints']):done()
:tag('td'):wikitext(entry['attack']):done()
:tag('td'):wikitext(entry['defence']):done()
:tag('td'):wikitext(entry['magic']):done()
:tag('td'):wikitext(entry['ranged']):done()
if ba['verbose'] then
tr:tag('td'):wikitext(entry['slaylvl'] or '1'):done()
:tag('td'):wikitext(entry['slayxp']):done()
:tag('td'):wikitext(entry['slaycat']):done()
local td = tr:tag('td')
for a, ab in ipairs(entry['assignedby']) do
if enum.contains(slayer_masters, ab) then
td:wikitext(string.format('[[File:%s chathead.png|48x64px|link=%s]]', ab, ab))
else
mw.log(string.format('unknown slayer master: %s', ab))
end
end
end
end
return div
end
function p.main(frame)
local args = frame:getParent().args
return p._main(args)
end
--[[ DEBUG COPYPASTA
mw.logObject( p.loadData('1', '1', 'A', 'A', true, 'All', false, '', '', '', false) )
mw.logObject( p.loadData('1', '1', 'A', 'A', false, 'All', false, '', '', '', false) )
= p._main({fromlevel='1', tolevel='1', verbose='yes'})
= p._main({fromletter='A', toletter='A', levelselect='no', levelsort='no', verbose='yes'})
--]]
return p