Module:EmberFantasy/sandbox/Slottable: Difference between revisions

From RuneRealm Wiki
Jump to navigation Jump to search
Content added Content deleted
(Created page with "-- <pre> require('Module:Mw.html extension') local p = {} local format = require('Module:Format equipment stat').format --for formatting the stats with + and - symbols on output local p2pIcon = 'frameless|link=Pay-to-play' --these icons are for the later members/f2p stars local f2pIcon = 'frameless|link=Free-to-play' local stats = {'astab', 'aslash', 'acrush', 'amagic', 'arange', 'dstab', 'dslash', 'dcrush', 'dm...")
 
No edit summary
Tag: Reverted
Line 1: Line 1:
-- <pre>
require('Module:Mw.html extension')

local p = {}
local p = {}


require('Module:Mw.html extension')
local format = require('Module:Format equipment stat').format --for formatting the stats with + and - symbols on output


local yesNo = require('Module:Yesno')
local p2pIcon = '[[File:Member icon.png|frameless|link=Pay-to-play]]' --these icons are for the later members/f2p stars
local paramTest = require('Module:Paramtest')
local f2pIcon = '[[File:Free-to-play icon.png|frameless|link=Free-to-play]]'
local contains = require('Module:Array').contains
local minimum = require('Module:Array').min
local pagesWithCats = require('Module:PageListTools').pageswithcats
local pagesWithConditions = require('Module:PageListTools').pageswithconditions
local editbtn = require('Module:Edit button')


local memberOptions = {'members', 'f2p', 'all'}
local stats = {'astab', 'aslash', 'acrush', 'amagic', 'arange', 'dstab', 'dslash', 'dcrush', 'dmagic', 'drange', 'str', 'rstr', 'mdmg', 'prayer'}
local slotOptions = {'head', 'cape', 'neck', 'ammo', 'weapon', 'shield', 'body', 'legs', 'hands', 'feet', 'ring', '2h'}
local statOptions = {'astab', 'aslash', 'acrush', 'amagic', 'arange', 'dstab', 'dslash', 'dcrush', 'dmagic', 'drange', 'str', 'rstr', 'mdmg', 'prayer'}


function p.main(frame)
function statFormat(_arg, default)
local arg = tonumber(_arg)
local args = frame:getParent().args --get the input from the template
if(not arg) then return (default or _arg) end
local slot = string.lower(tostring(args['slot'])) --take the slot input as the slot we want the table for
if(arg < 0) then return tostring(arg) end
local mems = args['mems']
return '+' .. arg
local dmm = args['dmm']
end
local beta = args['beta']
local data = getData(slot,mems,dmm,beta) --this takes the slot and sends it to our getData function (seen below) which gives us back a data table


function buildRow(item, attackSpeedColumn, uim)
local restbl = mw.html.create('table') --start making our results table, beginninig with the header
local row = mw.html.create('tr')
restbl:addClass('wikitable align-center-1 sortable align-center')
:tag('td'):wikitext(item.image and '[[' .. item.image .. '|link=|' .. item.name .. ']]' or ''):done()
:tag('tr')
:tag('th'):addClass('unsortable'):wikitext(''):done()
:tag('td'):wikitext('[[' .. item.name .. ']]'):done()
:tag('th'):wikitext('Name'):done()
:tag('td'):wikitext(item.members and '[[File:Member icon.png|link=|Members]]' or '[[File:Free-to-play icon.png|link=|Free-to-play]]'):done()

:tag('th'):wikitext('Members'):done()
for _, stat in ipairs(statOptions) do
:tag('th'):wikitext('[[File:White dagger.png|Stab attack]]') :done()
local statNum = tonumber(item[stat])
:tag('th'):wikitext('[[File:White scimitar.png|Slash attack]]') :done()
row:tag('td'):wikitext((statFormat(item[stat]) or editbtn("'''?''' (edit)", item.name)) .. (stat == 'mdmg' and '%' or ''))
:tag('th'):wikitext('[[File:White warhammer.png|Crush attack]]') :done()
:addClassIf(statNum and (statNum > 0), 'table-positive')
:tag('th'):wikitext('[[File:Magic icon.png|Magic attack]]') :done()
:addClassIf(statNum and (statNum < 0), 'table-negative')
:tag('th'):wikitext('[[File:Ranged icon.png|Ranged attack]]') :done()
end
:tag('th'):wikitext('[[File:White dagger.png|Stab defence]]<sup>[[File:Defence icon.png]]</sup>') :done()
:tag('th'):wikitext('[[File:White scimitar.png|Slash defence]]<sup>[[File:Defence icon.png]]</sup>') :done()
local weightNum = tonumber(item.weight)
:tag('th'):wikitext('[[File:White warhammer.png|Crush defence]]<sup>[[File:Defence icon.png]]</sup>') :done()
row:tag('td'):wikitext(item.weight)
:tag('th'):wikitext('[[File:Magic icon.png|Magic defence]]<sup>[[File:Defence icon.png]]</sup>') :done()
:tag('th'):wikitext('[[File:Ranged icon.png|Ranged defence]]<sup>[[File:Defence icon.png]]</sup>') :done()
:tag('th'):wikitext('[[File:Strength icon.png|Strength]]') :done()
:tag('th'):wikitext('[[File:Ranged Strength icon.png|Ranged Strength]]') :done()
:tag('th'):wikitext('[[File:Magic Damage icon.png|Magic Damage]]') :done()
:tag('th'):wikitext('[[File:Prayer icon.png|Prayer]]') :done()
:tag('th'):wikitext('Weight') :done()
:done()


if(attackSpeedColumn) then
-- Create the rows for the output table
if((item.speed == nil) or (item.speed < 0)) then
for _, item in ipairs(data) do --for each row of data, we take it and split it up, then put it into our table row. We also format them with + and - symbols here
row:tag('td'):addClass('table-na nohighlight'):css('text-align', 'center'):wikitext('<small>N/A</small>')
local row = restbl:tag('tr')
else
:tag('td'):wikitext(item.image):done()
:tag('td'):wikitext(item.name):done()
row:tag('td'):wikitext(item.speed):done()
:tag('td'):wikitext(item.members):done()
for _, stat in ipairs(stats) do
local value = item[stat]
local value_num = tonumber(value)
row:tag('td'):wikitext(format(value))
:addClassIf(value_num and (value_num > 0), 'table-positive')
:addClassIf(value_num and (value_num < 0), 'table-negative')
end
end
end
row:tag('td'):wikitext(item.weight):done()
if(uim) then
local storeability = 'Shop'
if(item.emote) then
storeability = 'STASH'
elseif(item.costume) then
storeability = 'POH'
end
row:tag('td'):wikitext(storeability):done()
end
end


return row
return tostring(restbl) --returns the table back to the template to be put onto the page
end
end


function createHeader(slotName, attackSpeedColumn, uim)
function getData(slotName,mems,dmm,beta) --so this is the function that takes the slot name and gets all the data for the items
local tabl = mw.html.create('table'):addClass('wikitable lighttable sortable sticky-header align-center-1 align-left-2 align-center-3 align-right-4 align-right-5 align-right-6 align-right-7 align-right-8 align-right-9 align-right-10 align-right-11 align-right-12 align-right-13 align-right-14 align-right-15 align-right-16 align-right-17 align-center-18'):done()
--first we set up our SMW query
local q = {
local header = tabl:tag('tr')
header:tag('th'):attr('colspan', '2'):wikitext('Name'):done()
'[[Equipment slot::'..slotName..']]', --we want everything that matches the slot we called, and then all the attached data below
:tag('th'):wikitext('[[File:Member icon.png|link=|Members]]'):done()
'?Is members only',
:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:White dagger.png|link=|Stab attack]]'):done()
'?Stab attack bonus',
:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:White scimitar.png|link=|Slash attack]]'):done()
'?Slash attack bonus',
:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:White warhammer.png|link=|Crush attack]]'):done()
'?Crush attack bonus',
:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:Magic icon.png|link=|Magic attack]]'):done()
'?Magic attack bonus',
:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:Ranged icon.png|link=|Ranged attack]]'):done()
'?Range attack bonus',
:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:White dagger.png|link=|Stab defence]]<sup>[[File:Defence icon.png|link=]]</sup>'):done()
'?Stab defence bonus',
:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:White scimitar.png|link=|Slash defence]]<sup>[[File:Defence icon.png|link=]]</sup>'):done()
'?Slash defence bonus',
:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:White warhammer.png|link=|Crush defence]]<sup>[[File:Defence icon.png|link=]]</sup>'):done()
'?Crush defence bonus',
:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:Magic icon.png|link=|Magic defence]]<sup>[[File:Defence icon.png|link=]]</sup>'):done()
'?Magic defence bonus',
:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:Ranged icon.png|link=|Ranged defence]]<sup>[[File:Defence icon.png|link=]]</sup>'):done()
'?Range defence bonus',
:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:Strength icon.png|link=|Strength]]'):done()
'?Strength bonus',
:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:Ranged Strength icon.png|link=|Ranged Strength]]'):done()
'?Ranged Strength bonus',
:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:Magic Damage icon.png|link=|Magic Damage]]'):done()
'?Magic Damage bonus',
:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:Prayer icon.png|link=|Prayer]]'):done()
'?Prayer bonus',
:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:Weight icon.png|link=|Weight]]'):done()
'?Category',
'?Image',
if(attackSpeedColumn) then
'?Weight',
header:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:Watch.png|link=|Speed]]'):done()
limit = 1000,
end
order = 'ascending'
if(uim) then
header:tag('th'):wikitext('[[File:Marble magic wardrobe icon.png|link=]]Stored/Buy'):done()
end
return tabl
end

function filterData(data, slotName, options)

if(slotName == 'ammo') then slotName = 'Ammunition' end -- hacky shit until ammo/ammunition is resolved
slotCat = '[[Category:' .. paramTest.ucfirst(slotName) .. ' slot items]]'
if slotName == '2h' then
slotCat = '[[Category:Two-handed slot items]]'
end
local exclusionListCategories = {
{ options.beta, slotCat .. '[[Category:Beta items]]' },
{ options.discontinued, slotCat .. '[[Category:Discontinued content]]' },
{ options.dmm, slotCat .. '[[Category:Deadman seasonal items]]' },
{ options.emir, slotCat .. '[[Category:Emir\'s Arena]]' },
{ options.failedPoll, slotCat .. '[[Category:Pages containing information from failed polls]]' },
{ options.gauntlet, slotCat .. '[[Category:The Gauntlet]]' },
{ options.lms, slotCat .. '[[Category:Last Man Standing]]' },
{ options.quest, slotCat .. '[[Category:Quest items]]' },
}
}
local smwdata = mw.smw.ask(q) --this now asks the smw for all the data, and saves all of it in smwdata
local data = {} --setting this table up to insert processed data into


local exclusionList = { }
for _, item in ipairs(smwdata) do --for each item we found with our smw query
for _, excl in ipairs(exclusionListCategories) do
local process = true
if(not excl[1]) then
local dmmcat = false
table.insert(exclusionList, excl[2])
local betacat = false
if type(item['Category']) == 'table' then
for _, category in ipairs(item['Category']) do
if category == "[[:Category:Deadman seasonal items|Deadman seasonal items]]" then
dmmcat = true
elseif category == "[[:Category:Beta items|Beta items]]" then
betacat = true
end
end
end
end
end
if mems == 'Members' then
local pagesToExclude = #exclusionList > 0 and pagesWithCats(exclusionList) or {}
if not item['Is members only'] then
local emotePagesToInclude, costumePagesToInclude
process = false
if(options.uim) then
end
emotePagesToInclude = pagesWithCats({ slotCat .. '[[Category:Items needed for an emote clue]]' }) or {}
costumePagesToInclude = pagesWithCats({ slotCat .. '[[Category:Items storable in the costume room]]' }) or {}
end

-- Filter the data
local retData = {}

for _, item in ipairs(data) do
local keep = true

if(((options.members == 'members') and (item['members'] == false)) or
((options.members == 'f2p') and (item['members'] == true)) or
((contains(pagesToExclude, item['variantof'])) or (contains(pagesToExclude, item['name'])))) then
keep = false
end
end

if mems == 'F2P' then
if item['Is members only'] then
if(options.uim) then
if((not (contains(emotePagesToInclude, item['variantof']) or (contains(emotePagesToInclude, item['name'])))) and
process = false
(not (contains(costumePagesToInclude, item['variantof']) or (contains(costumePagesToInclude, item['name'])))) and
(next(pagesWithConditions('[[Sells item::' .. item.name .. ']]')) == nil)) then
keep = false
elseif(contains(costumePagesToInclude, item['variantof']) or (contains(costumePagesToInclude, item['name']))) then
item.costume = true
elseif(contains(emotePagesToInclude, item['variantof']) or (contains(emotePagesToInclude, item['name']))) then
item.emote = true
end
end
end
end

if dmm == 'No' then
if dmmcat == true then
if(keep) then
table.insert(retData, item)
process = false
end
end
end
if beta == 'No' then
end
if betacat == true then
process = false
mw.log(string.format('Filter: exclusion list size: %i, start size: %i, end size: %i, removed %i.', #pagesToExclude, #data, #retData, #data - #retData))

return retData
end

function loadData(slotName, attackSpeed, members)
local query = {
'[[Equipment slot::' .. slotName .. ']]',
'?=#-',
'?Stab attack bonus#-=astab',
'?Slash attack bonus#-=aslash',
'?Crush attack bonus#-=acrush',
'?Magic attack bonus#-=amagic',
'?Range attack bonus#-=arange',
'?Stab defence bonus#-=dstab',
'?Slash defence bonus#-=dslash',
'?Crush defence bonus#-=dcrush',
'?Magic defence bonus#-=dmagic',
'?Range defence bonus#-=drange',
'?Strength bonus#-=str',
'?Magic Damage bonus#-=mdmg',
'?Ranged Strength bonus#-=rstr',
'?Prayer bonus#-=prayer',
'?Weight#-=weight',
'?Is members only#-=members',
'?Image#-=image',
'?Is variant of#-=variantof',
offset = 0,
limit = 1000,
}
if(attackSpeed) then
table.insert(query, '?Weapon attack speed#-=speed')
end

local t1 = os.clock()
local smwData = mw.smw.ask(query)
local t2 = os.clock()
assert(smwData ~= nil and #smwData > 0, 'SMW query failed')

for _, item in ipairs(smwData) do
-- Rename the first parameter to name for clarity and ease of use
item['name'] = item[1]
item[1] = nil
if(item['image'] == nil) then
local hasDefaultFile = mw.title.new(item['name'] .. '.png', 'File'):getContent()
item['image'] = hasDefaultFile and 'File:' .. item['name'] .. '.png' or ''
elseif(type(item['image']) == 'table') then
item['image'] = item['image'][1]
end
-- Fix members values by defaulting when running into issue
if(type(item.members) == 'boolean') then
-- Short circuit
elseif(item.members == nil) then
item.members = false
elseif(type(item.members) == 'table') then
for _, mems in ipairs(item.members) do
if(mems == false) then
item.members = false
break
end
end
end
end
end
-- Fix weights with multiple values (Max cape), this may do nothing and is precautionary
if process then
if(item.weight == nil) then
local dataline = processData(item) --we process it (seen below) and then save it as a line
item.weight = 0
table.insert(data, dataline) --and then we insert that line into our data table to be returned
elseif(type(item.weight) == 'table') then
item.weight, _ = minimum(item.weight)
end
end
end
end


mw.log(string.format('SMW: entries %d, time elapsed: %.3f ms.', #smwData, (t2 - t1) * 1000))
return data --and once we've processed all the data we send the data table back up to main for the formatting

return smwData
end
end


-- JSON data dump entry-point
function processData(item) --this breaks up the smwdata bit into manageable little bites
function p.dumpData(frame)
local name = item[1] or '' --this gets the item name, which by default is in the first position of the query results (lua starts at 1, not 0)
local _name = string.match(name, '|(.-)%]')
local args = frame:getParent().args
return p._dumpData(args)
end
local members = item['Is members only'] --members we have to do a bit extra for

if members == true then --if it is members, we use the members star
function p._dumpData(args)
members = p2pIcon
local slot = args.slot
elseif members == false then --if not, use f2p star
assert(contains(slotOptions, slot), 'Invalid slot specified')
members = f2pIcon

else
local data = loadData(slot, true)
members = ''

-- Tests indicate that JSON pretty-printing increases the size by a factor of 1.78.
-- Removing indenting results in an increase in size by a factor of 1.15.
-- The latter is a very reasonable trade-off to make the files more wiki-friendly.
local prefix = string.format('-- Data for item slot \'%s\' @ %s.\n-- Generated by Module:Slottable, function dumpData()\nreturn mw.text.jsonDecode([=[\n', slot, os.date('%F %T', os.time()))
local rawjson = mw.text.jsonEncode(data, mw.text.JSON_PRETTY)
local jsondata, subst = rawjson:gsub('\n%s+', '\n')
local postfix = '\n]=])\n'

mw.log(string.format('Dumping JSON data for item slot \'%s\'. Raw size: %d bytes, formatted size: %d bytes (factor: %.2f).', slot, rawjson:len(), jsondata:len(), jsondata:len() / rawjson:len()))

return prefix .. jsondata .. postfix
end


-- Turtle data dump entry-point
function p.dumpDataTTL(frame)
local args = frame:getParent().args
return p._dumpDataTTL(args)
end

function p._dumpDataTTL(args)
local slot = args.slot
assert(contains(slotOptions, slot), 'Invalid slot specified')

local data = loadData(slot, true)

local slot_
if(slot == "2h") then
slot_ = "zweihander"
else
slot_ = slot
end

local prefix = string.format('# Data for item slot \'%s\' @ %s.\n# Generated by Module:Slottable, function dumpDataTTL()\n\n', slot, os.date('%F %T', os.time()))
prefix = prefix .. "@prefix " .. slot_ .. ": <http://oldschool.runescape.wiki/rdf/" .. slot_ .. "/> .\n"
prefix = prefix .. "@prefix prop: <http://oldschool.runescape.wiki/rdf/prop/> .\n\n"

local ttlData = ""
for _, i in ipairs(data) do
ttlData = ttlData .. slot_ .. ":" .. _ .. " " .. "prop:slot \"" .. slot_ .. "\"; "
local outl = {}
for k, v in pairs(i) do
if((k == "image") or (k == "name") or (k == "variantof")) then
table.insert(outl, "prop:" .. k .. " \"" .. tostring(v) .. "\"")
else
table.insert(outl, "prop:" .. k .. " " .. tostring(v))
end
end
ttlData = ttlData .. table.concat(outl, "; ") .. " .\n"
end
end

local image = item['Image'] or ''
local postfix = '\n\n'
mw.log(string.format('Dumping Turtle data for item slot \'%s\'. Size: %d bytes.', slot, ttlData:len()))
if type(image) == 'table' then

image = image[1] -- take the first image available
return prefix .. ttlData .. postfix
end
end
image = string.match(image, '[Ff]ile:.-%.png') or ''

if image ~= '' then
function p._main(args)
image = string.format('[[%s|link=%s]]', image, _name)
local slot = string.lower(paramTest.default_to(args.slot, ''))
local members = string.lower(paramTest.default_to(args.members, 'all'))

assert(contains(slotOptions, slot), 'Invalid slot specified')
assert(contains(memberOptions, members), 'Invalid members status specified')

local beta = yesNo(paramTest.default_to(args.beta, false), false)
local discontinued = yesNo(paramTest.default_to(args.discontinued, false), false)
local dmm = yesNo(paramTest.default_to(args.dmm, false), false)
local emir = yesNo(paramTest.default_to(args.emir, false), false)
local failedPoll = yesNo(paramTest.default_to(args.failedpoll, false), false)
local gauntlet = yesNo(paramTest.default_to(args.beta, false), false)
local lms = yesNo(paramTest.default_to(args.lms, false), false)
local quest = yesNo(paramTest.default_to(args.quest, true), true)

-- UIM specific tables, that only display items that can be:
-- purchased, stored in the POH, or stored in STASH untsi
local uim = yesNo(paramTest.default_to(args.uim, false), false)

local attackSpeed = false
if((slot == '2h') or (slot == 'weapon')) then
attackSpeed = true
end
end

local data = loadData(slot, attackSpeed, members, uim)
data = filterData(data, slot, {members = members, uim = uim, beta = beta, discontinued = discontinued, dmm = dmm, emir = emir, failedPoll = failedPoll, gauntlet = gauntlet, lms = lms, quest = quest })
local ret = createHeader(slot, attackSpeed, uim)
local keyset=""
for _, item in ipairs(data) do
local n=0
ret:node(buildRow(item, attackSpeed, uim))
for k,v in pairs(item) do
n=n+1
keyset = keyset .. k .. " (" .. tostring(v) ..")" .. ":"
end
end


return ret
return { --now we return the processed data for this item back to the getData function, which sends us another item and repeats until we've done them all
name = name,
image = image,
members = members,
astab = item['Stab attack bonus'] or '',
aslash = item['Slash attack bonus'] or '',
acrush = item['Crush attack bonus'] or '',
amagic = item['Magic attack bonus'] or '',
arange = item['Range attack bonus'] or '',
dstab = item['Stab defence bonus'] or '',
dslash = item['Slash defence bonus'] or '',
dcrush = item['Crush defence bonus'] or '',
dmagic = item['Magic defence bonus'] or '',
drange = item['Range defence bonus'] or '',
str = item['Strength bonus'] or '',
rstr = item['Ranged Strength bonus'] or '',
mdmg = item['Magic Damage bonus'] or keyset,
prayer = item['Prayer bonus'] or '',
weight = item['Weight'] or '',
}
end
end

p.getData = getData
function p.main(frame)
local args = frame:getParent().args
return p._main(args)
end

--[[ DEBUG
mw.logObject(p.getData('weapon'))
p._main({slot='weapon', members='all'})
p._dumpData({slot='weapon'})
p._dumpDataTTL({slot='weapon'})
--]]

return p
return p

Revision as of 00:13, 17 October 2024

Documentation for this module may be created at Module:EmberFantasy/sandbox/Slottable/doc

local p = {}

require('Module:Mw.html extension')

local yesNo = require('Module:Yesno')
local paramTest = require('Module:Paramtest')
local contains = require('Module:Array').contains
local minimum = require('Module:Array').min
local pagesWithCats = require('Module:PageListTools').pageswithcats
local pagesWithConditions = require('Module:PageListTools').pageswithconditions
local editbtn = require('Module:Edit button')

local memberOptions = {'members', 'f2p', 'all'}
local slotOptions = {'head', 'cape', 'neck', 'ammo', 'weapon', 'shield', 'body', 'legs', 'hands', 'feet', 'ring', '2h'}
local statOptions = {'astab', 'aslash', 'acrush', 'amagic', 'arange', 'dstab', 'dslash', 'dcrush', 'dmagic', 'drange', 'str', 'rstr', 'mdmg', 'prayer'}

function statFormat(_arg, default)
	local arg = tonumber(_arg)
	if(not arg) then return (default or _arg) end
	if(arg < 0) then return tostring(arg) end
	return '+' .. arg
end

function buildRow(item, attackSpeedColumn, uim)
	local row = mw.html.create('tr')
		:tag('td'):wikitext(item.image and '[[' .. item.image .. '|link=|' .. item.name .. ']]' or ''):done()
		:tag('td'):wikitext('[[' .. item.name .. ']]'):done()
		:tag('td'):wikitext(item.members and '[[File:Member icon.png|link=|Members]]' or '[[File:Free-to-play icon.png|link=|Free-to-play]]'):done()

	for _, stat in ipairs(statOptions) do
		local statNum = tonumber(item[stat])
		row:tag('td'):wikitext((statFormat(item[stat]) or editbtn("'''?''' (edit)", item.name)) .. (stat == 'mdmg' and '%' or ''))
			:addClassIf(statNum and (statNum > 0), 'table-positive')
			:addClassIf(statNum and (statNum < 0), 'table-negative')
	end
	
	local weightNum = tonumber(item.weight)
	row:tag('td'):wikitext(item.weight)

	if(attackSpeedColumn) then
		if((item.speed == nil) or (item.speed < 0)) then
			row:tag('td'):addClass('table-na nohighlight'):css('text-align', 'center'):wikitext('<small>N/A</small>')
		else
			row:tag('td'):wikitext(item.speed):done()
		end
	end
	
	if(uim) then
		local storeability = 'Shop'
		if(item.emote) then
			storeability = 'STASH'
		elseif(item.costume) then
			storeability = 'POH'
		end
		row:tag('td'):wikitext(storeability):done()
	end

	return row
end

function createHeader(slotName, attackSpeedColumn, uim)
	local tabl = mw.html.create('table'):addClass('wikitable lighttable sortable sticky-header align-center-1 align-left-2 align-center-3 align-right-4 align-right-5 align-right-6 align-right-7 align-right-8 align-right-9 align-right-10 align-right-11 align-right-12 align-right-13 align-right-14 align-right-15 align-right-16 align-right-17 align-center-18'):done()
	local header = tabl:tag('tr')
	header:tag('th'):attr('colspan', '2'):wikitext('Name'):done()
		:tag('th'):wikitext('[[File:Member icon.png|link=|Members]]'):done()
		:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:White dagger.png|link=|Stab attack]]'):done()
		:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:White scimitar.png|link=|Slash attack]]'):done()
		:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:White warhammer.png|link=|Crush attack]]'):done()
		:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:Magic icon.png|link=|Magic attack]]'):done()
		:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:Ranged icon.png|link=|Ranged attack]]'):done()
		:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:White dagger.png|link=|Stab defence]]<sup>[[File:Defence icon.png|link=]]</sup>'):done()
		:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:White scimitar.png|link=|Slash defence]]<sup>[[File:Defence icon.png|link=]]</sup>'):done()
		:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:White warhammer.png|link=|Crush defence]]<sup>[[File:Defence icon.png|link=]]</sup>'):done()
		:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:Magic icon.png|link=|Magic defence]]<sup>[[File:Defence icon.png|link=]]</sup>'):done()
		:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:Ranged icon.png|link=|Ranged defence]]<sup>[[File:Defence icon.png|link=]]</sup>'):done()
		:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:Strength icon.png|link=|Strength]]'):done()
		:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:Ranged Strength icon.png|link=|Ranged Strength]]'):done()
		:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:Magic Damage icon.png|link=|Magic Damage]]'):done()
		:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:Prayer icon.png|link=|Prayer]]'):done()
		:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:Weight icon.png|link=|Weight]]'):done()
		
	if(attackSpeedColumn) then
		header:tag('th'):attr('data-sort-type', 'number'):wikitext('[[File:Watch.png|link=|Speed]]'):done()
	end
	
	if(uim) then
		header:tag('th'):wikitext('[[File:Marble magic wardrobe icon.png|link=]]Stored/Buy'):done()
	end
	
	return tabl
end

function filterData(data, slotName, options)

	if(slotName == 'ammo') then slotName = 'Ammunition' end -- hacky shit until ammo/ammunition is resolved
	slotCat = '[[Category:' .. paramTest.ucfirst(slotName) .. ' slot items]]'
	if slotName == '2h' then
		slotCat = '[[Category:Two-handed slot items]]'	
	end
	local exclusionListCategories = {
		{ options.beta,			slotCat .. '[[Category:Beta items]]' },
		{ options.discontinued,	slotCat .. '[[Category:Discontinued content]]' },
		{ options.dmm,			slotCat .. '[[Category:Deadman seasonal items]]' },
		{ options.emir,			slotCat .. '[[Category:Emir\'s Arena]]' },
		{ options.failedPoll,	slotCat .. '[[Category:Pages containing information from failed polls]]' },
		{ options.gauntlet,		slotCat .. '[[Category:The Gauntlet]]' },
		{ options.lms,			slotCat .. '[[Category:Last Man Standing]]' },
		{ options.quest,		slotCat .. '[[Category:Quest items]]' },
	}

	local exclusionList = { }
	for _, excl in ipairs(exclusionListCategories) do
		if(not excl[1]) then
			table.insert(exclusionList, excl[2])
		end
	end
	local pagesToExclude = #exclusionList > 0 and pagesWithCats(exclusionList) or {}
	local emotePagesToInclude, costumePagesToInclude
	if(options.uim) then
		emotePagesToInclude = pagesWithCats({ slotCat .. '[[Category:Items needed for an emote clue]]' }) or {}
		costumePagesToInclude = pagesWithCats({ slotCat .. '[[Category:Items storable in the costume room]]' }) or {}
	end

	-- Filter the data
	local retData = {} 

	for _, item in ipairs(data) do 
		local keep = true

		if(((options.members == 'members') and (item['members'] == false)) or
			((options.members == 'f2p') and (item['members'] == true)) or
			((contains(pagesToExclude, item['variantof'])) or (contains(pagesToExclude, item['name'])))) then
			keep = false
		end

		if(options.uim) then
			if((not (contains(emotePagesToInclude, item['variantof']) or (contains(emotePagesToInclude, item['name'])))) and
				(not (contains(costumePagesToInclude, item['variantof']) or (contains(costumePagesToInclude, item['name'])))) and
				(next(pagesWithConditions('[[Sells item::' .. item.name .. ']]')) == nil)) then
				keep = false
			elseif(contains(costumePagesToInclude, item['variantof']) or (contains(costumePagesToInclude, item['name']))) then
				item.costume = true
			elseif(contains(emotePagesToInclude, item['variantof']) or (contains(emotePagesToInclude, item['name']))) then
				item.emote = true
			end
		end

		if(keep) then
			table.insert(retData, item)
		end
		
	end
	
	mw.log(string.format('Filter: exclusion list size: %i, start size: %i, end size: %i, removed %i.', #pagesToExclude, #data, #retData, #data - #retData))

	return retData
end

function loadData(slotName, attackSpeed, members)
	local query = {
		'[[Equipment slot::' .. slotName .. ']]',
		'?=#-',
		'?Stab attack bonus#-=astab',
		'?Slash attack bonus#-=aslash',
		'?Crush attack bonus#-=acrush',
		'?Magic attack bonus#-=amagic',
		'?Range attack bonus#-=arange',
		'?Stab defence bonus#-=dstab',
		'?Slash defence bonus#-=dslash',
		'?Crush defence bonus#-=dcrush',
		'?Magic defence bonus#-=dmagic',
		'?Range defence bonus#-=drange',
		'?Strength bonus#-=str',
		'?Magic Damage bonus#-=mdmg',
		'?Ranged Strength bonus#-=rstr',
		'?Prayer bonus#-=prayer',
		'?Weight#-=weight',
		'?Is members only#-=members',
		'?Image#-=image',
		'?Is variant of#-=variantof',
		offset = 0,
		limit = 1000,
	}
	
	if(attackSpeed) then
		table.insert(query, '?Weapon attack speed#-=speed')
	end

	local t1 = os.clock()
	local smwData = mw.smw.ask(query)
	local t2 = os.clock()
	assert(smwData ~= nil and #smwData > 0, 'SMW query failed')

	for _, item in ipairs(smwData) do 
		-- Rename the first parameter to name for clarity and ease of use
		item['name'] = item[1]
		item[1] = nil
		
		if(item['image'] == nil) then
			local hasDefaultFile = mw.title.new(item['name'] .. '.png', 'File'):getContent()
			item['image'] = hasDefaultFile and 'File:' .. item['name'] .. '.png' or ''
		elseif(type(item['image']) == 'table') then
			item['image'] = item['image'][1]
		end
		-- Fix members values by defaulting when running into issue
		if(type(item.members) == 'boolean') then
			-- Short circuit
		elseif(item.members == nil) then
			item.members = false
		elseif(type(item.members) == 'table') then
			for _, mems in ipairs(item.members) do
				if(mems == false) then
					item.members = false
					break
				end
			end
		end
		-- Fix weights with multiple values (Max cape), this may do nothing and is precautionary
		if(item.weight == nil) then
			item.weight = 0
		elseif(type(item.weight) == 'table') then
			item.weight, _ = minimum(item.weight)
		end
	end

	mw.log(string.format('SMW: entries %d, time elapsed: %.3f ms.', #smwData, (t2 - t1) * 1000))

	return smwData
end

-- JSON data dump entry-point
function p.dumpData(frame)
	local args = frame:getParent().args
	return p._dumpData(args)
end

function p._dumpData(args)
	local slot = args.slot
	assert(contains(slotOptions, slot), 'Invalid slot specified')

	local data = loadData(slot, true)

	-- Tests indicate that JSON pretty-printing increases the size by a factor of 1.78.
	-- Removing indenting results in an increase in size by a factor of 1.15.
	-- The latter is a very reasonable trade-off to make the files more wiki-friendly.
	local prefix = string.format('-- Data for item slot \'%s\' @ %s.\n-- Generated by Module:Slottable, function dumpData()\nreturn mw.text.jsonDecode([=[\n', slot, os.date('%F %T', os.time()))
	local rawjson = mw.text.jsonEncode(data, mw.text.JSON_PRETTY)
	local jsondata, subst = rawjson:gsub('\n%s+', '\n')
	local postfix = '\n]=])\n'

	mw.log(string.format('Dumping JSON data for item slot \'%s\'. Raw size: %d bytes, formatted size: %d bytes (factor: %.2f).', slot, rawjson:len(), jsondata:len(), jsondata:len() / rawjson:len()))

	return prefix .. jsondata .. postfix
end


-- Turtle data dump entry-point
function p.dumpDataTTL(frame)
	local args = frame:getParent().args
	return p._dumpDataTTL(args)
end

function p._dumpDataTTL(args)
	local slot = args.slot
	assert(contains(slotOptions, slot), 'Invalid slot specified')

	local data = loadData(slot, true)

    local slot_
    if(slot == "2h") then
    	slot_ = "zweihander"
    else
    	slot_ = slot
    end

	local prefix = string.format('# Data for item slot \'%s\' @ %s.\n# Generated by Module:Slottable, function dumpDataTTL()\n\n', slot, os.date('%F %T', os.time()))
	prefix = prefix .. "@prefix " .. slot_ .. ": <http://oldschool.runescape.wiki/rdf/" .. slot_ .. "/> .\n"
	prefix = prefix .. "@prefix prop: <http://oldschool.runescape.wiki/rdf/prop/> .\n\n"

	local ttlData = ""
	for _, i in ipairs(data) do
		ttlData = ttlData .. slot_ .. ":" .. _ .. " " .. "prop:slot \"" .. slot_ .. "\"; "
		local outl = {}
		for k, v in pairs(i) do
			if((k == "image") or (k == "name") or (k == "variantof")) then
				table.insert(outl, "prop:" .. k .. " \"" .. tostring(v) .. "\"")
			else
				table.insert(outl, "prop:" .. k .. " " .. tostring(v))
			end
		end
		ttlData = ttlData .. table.concat(outl, "; ") .. " .\n"
	end

	local postfix = '\n\n'
	mw.log(string.format('Dumping Turtle data for item slot \'%s\'. Size: %d bytes.', slot, ttlData:len()))

	return prefix .. ttlData .. postfix
end

function p._main(args)
	local slot = string.lower(paramTest.default_to(args.slot, ''))
	local members = string.lower(paramTest.default_to(args.members, 'all'))

	assert(contains(slotOptions, slot), 'Invalid slot specified')
	assert(contains(memberOptions, members), 'Invalid members status specified')

	local beta = yesNo(paramTest.default_to(args.beta, false), false)
	local discontinued = yesNo(paramTest.default_to(args.discontinued, false), false)
	local dmm = yesNo(paramTest.default_to(args.dmm, false), false)
	local emir = yesNo(paramTest.default_to(args.emir, false), false)
	local failedPoll = yesNo(paramTest.default_to(args.failedpoll, false), false)
	local gauntlet = yesNo(paramTest.default_to(args.beta, false), false)
	local lms = yesNo(paramTest.default_to(args.lms, false), false)
	local quest = yesNo(paramTest.default_to(args.quest, true), true)

	-- UIM specific tables, that only display items that can be:
	--  purchased, stored in the POH, or stored in STASH untsi
	local uim = yesNo(paramTest.default_to(args.uim, false), false)

	local attackSpeed = false
	if((slot == '2h') or (slot == 'weapon')) then
		attackSpeed = true
	end

	local data = loadData(slot, attackSpeed, members, uim)
	data = filterData(data, slot, {members = members, uim = uim, beta = beta, discontinued = discontinued, dmm = dmm, emir = emir, failedPoll = failedPoll, gauntlet = gauntlet, lms = lms,  quest = quest })
	
	local ret = createHeader(slot, attackSpeed, uim)
	for _, item in ipairs(data) do
		ret:node(buildRow(item, attackSpeed, uim))
	end

	return ret
end

function p.main(frame)
	local args = frame:getParent().args
	return p._main(args)
end

--[[ DEBUG
mw.logObject(p.getData('weapon'))
p._main({slot='weapon', members='all'})
p._dumpData({slot='weapon'})
p._dumpDataTTL({slot='weapon'})
--]]

return p