Module:Slottable
Module documentation
This documentation is transcluded from Template:No documentation/doc. [edit] [history] [purge]
This module does not have any documentation. Please consider adding documentation at Module:Slottable/doc. [edit]
Module:Slottable's function main is invoked by Template:Slottable.
Module:Slottable requires
.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
if item.speed == nil or (tonumber(item.speed) and tonumber(item.speed) < 0) then
-- proceed with your existing code
row:tag('td'):addClass('table-na nohighlight'):css('text-align', 'center'):wikitext('<small>N/A</small>')
else
if not tonumber(item.speed) then
error('Invalid speed format for item: ' .. item.name .. ' (Speed: ' .. tostring(item.speed) .. ')')
end
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