Module:Slottable: Difference between revisions

From RuneRealm Wiki
Jump to navigation Jump to search
Content added Content deleted
No edit summary
No edit summary
 
(4 intermediate revisions by the same user not shown)
Line 45: Line 45:
-- end
-- end
if item.speed == nil or (tonumber(item.speed) and tonumber(item.speed) < 0) then
if item.speed == nil or (tonumber(item.speed) and tonumber(item.speed) < 0) then
row:tag('td'):addClass('table-na nohighlight'):css('text-align', 'center'):wikitext('<small>N/A</small>')
-- proceed with your existing code
row:tag('td'):addClass('table-na nohighlight'):css('text-align', 'center'):wikitext('<small>N/A</small>')
else
else
if not tonumber(item.speed) then
row:tag('td'):wikitext(item.speed):done()
error('Invalid speed format for item: ' .. item.name .. ' (Speed: ' .. tostring(item.speed) .. ')')
end
row:tag('td'):wikitext(item.speed):done()
end
end
end
end

Latest revision as of 16:24, 5 November 2024

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.

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
			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