Module:Sandbox/User:4madness/Questreq table
Documentation for this module may be created at Module:Sandbox/User:4madness/Questreq table/doc
--
-- Variation of [[Module:Questreq]] that outputs a table instead.
-- <nowiki>
--
local p = {}
-- Load data from quest list
local quests = mw.loadData('Module:Questreq/data')
local yesno = require( 'Module:Yesno' )
-- Main function
function p.main(frame)
local args = frame:getParent().args
-- named args for JSCalculator support
local quest = args[1] or args.Quest or args.quest or ''
local limit = tonumber(args[2]) or tonumber(args.Limit) or tonumber(args.limit) or 9
return p._main(quest,limit)
end
function p._main(quest,limit)
if (quest == nil or trim(quest) == '') then
return '\'\'Error: Missing quest name!\'\'\n\n'
end
-- Handle butchered page names gracefully if necessary
quest = quest:gsub(''','\'')
local rettable = mw.html.create('table')
:addClass('wikitable')
:addClass('sortable')
:tag('tr')
:tag('th')
:wikitext('[[File:Quest.png|21px|link=]] Quest Name')
:done()
:tag('th')
:addClass('unsortable')
:wikitext('Skill requirements')
:done()
:tag('th')
:wikitext('Quest stage')
:done()
:done() -- end header row
local quest_list = get_quest_list(quest,1,limit)
local hasData = false
for q,qdata in pairs(quest_list) do
hasData = true
break
end
if hasData then
return rettable:node(assemble_quest_rows(quest_list))
else
return rettable:tag('tr')
:tag('td')
:attr('colspan', 3)
:wikitext('\'\'Quest not found.\'\'')
:done()
:done()
end
end
function assemble_quest_rows(list)
local chain = mw.html.create(nil)
local refundefined = true
-- for each quest, assemble a table row with three columns.
-- quest name, skill requirments, and stage of completion
for qname, qdata in pairs(list) do
chain = chain:tag('tr')
:tag('td') -- start quest name cell
:wikitext(qdata.link or qname)
if (qdata.incomplete) then
if (refundefined) then
chain = chain:tag('ref')
:attr('name', 'Incomplete')
:attr('group', 'questreqtable')
:wikitext('This quest has requirements that were omitted.')
:done()
else
chain = chain:tag('ref', {selfClosing=true})
:attr('name', 'Incomplete')
:attr('group', 'questreqtable')
:done()
end
end
chain = chain:done() -- end quest name cell
:tag('td')
:wikitext(mw.getCurrentFrame():expandTemplate({title = 'User:4madness/Calculators/Complete quest requirements/Extract quest skills', args = {qname}}))
:done()
:tag('td')
:wikitext(qdata.stage or '')
:done()
:done() -- end row
end
return chain
end
-- Function to flatten the table into one entry
function p.summary(frame)
local args = frame:getParent().args
-- named args for JSCalculator support
local quest = args[1] or args.Quest or args.quest or ''
local limit = tonumber(args[2]) or tonumber(args.Limit) or tonumber(args.limit) or 15
local hideext = yesno( args.ext ) == false
return p._summary(quest,limit,hideext)
end
function p._summary(quest,limit,hideext)
if (quest == nil or trim(quest) == '') then
return '\'\'Error: Missing quest name!\'\'\n\n'
end
-- Handle butchered page names gracefully if necessary
quest = quest:gsub(''','\'')
-- cache frame reference
local frame = mw.getCurrentFrame()
-- get formatted quest link + validate name
local qlink = quest
local quest_list = get_quest_list(quest,1,0)
local hasData = false
for q, qdata in pairs(quest_list) do
hasData = qdata.stage ~= nil
qlink = qdata.link or q
end
if (hasData == false) then
-- check for redirect to catch typos
redirectedPage = mw.title.makeTitle(0, quest).redirectTarget
if (redirectedPage) then
return p._summary(tostring(redirectedPage),limit,hideext)
end
end
-- get complete quest list up to limit
quest_list = get_quest_list(quest,1,limit)
local max_stats = {}
for qname,qdata in pairs(quest_list) do
local skill, level, value = '', 0, ''
allRawSkillReqArgs = frame:expandTemplate({title = 'User:4madness/Calculators/Complete quest requirements/Extract quest skills.lua', args = {qname}})
for oneRawSkillReqArgs in string.gmatch(allRawSkillReqArgs, '([^!]+)') do -- split by '!' symbols
local args = {}
local hasData = false -- only save stat
for k, v in string.gmatch(oneRawSkillReqArgs, '([^|]*)=([^|]*)') do
hasData = true
if (k == 1 or k == '1') then
skill = firstToUpper(v:lower())
elseif (k == 2 or k == '2') then
level = v
end
end
if (hasData and (max_stats[skill] == nil or max_stats[skill] < level)) then
max_stats[skill] = level
end
end
end
local stats_string, eol = '', hideext and '|ext=no}}' or '}}'
for skill,level in pairsByKeys(max_stats) do -- pairs sorted alphabetically
stats_string = stats_string .. '{{Skillreq|' .. skill .. '|' .. level .. eol
end
return mw.html.create('table')
:addClass('wikitable')
:tag('tr')
:tag('th')
:wikitext('[[File:Quest.png|21px|link=]] Quest Name')
:done()
:tag('th')
:wikitext('Skill requirements')
:done()
:done() -- end header row
:tag('tr')
:tag('td')
:wikitext(hasData and qlink or ('\'\'\'' .. qlink .. '\'\'\''))
:done()
:node(hasData
and mw.html.create('td'):addClass('qc-active'):wikitext(frame:preprocess(stats_string)):done()
or mw.html.create('td'):wikitext('N/A'):done())
:done()
end
--
-- Recursive list function
-- Level determines how deep the indentation is
-- Replaces 'Started:' modifier
-- If the quest just listed was found in the big list and the limit for level is not reached
-- the quest's requirements will be listed as a sublist 1 level deeper
--l
function get_quest_list(quest,level,limit)
local req_list = {}
if quest then
local started
-- Look for the 'Started:' modifier and replace it
-- If found, set boolean to true
if quest:find('Started:') then
quest = quest:gsub('Started:%s*','')
started = true
end
-- new data table for current quest
req_list[quest] = {}
-- a handle for the current quest's data table
local cur_qdata = req_list[quest]
-- Look for quest in the list
local subreqs = quests[quest]
if subreqs then
cur_qdata.link = tidy_link(quest)
-- stage is '' for top-most quest, or one of 'started' or 'completed'
cur_qdata.stage = level == 1 and '' or (started and 'started' or 'completed')
if subreqs[1] then
if level <= limit then
for i, q in ipairs(subreqs) do
-- get quests under subquest q, then merge with master list
local sub_list = get_quest_list(q,level+1,limit)
for q, qdata in pairs(sub_list) do
req_list[q] = qdata
end
end
else
-- add marker about missing requirements
cur_qdata.incomplete = true
end
end
else
-- skip entries containing quest points
if (quest:lower():find('quest points')) then
return {}
end
end
end
return req_list -- associative array containing quest names as keys and data as values
end
--https://stackoverflow.com/a/2421746/3068190
function firstToUpper(str)
return (str:gsub("^%l", string.upper))
end
function trim(s)
return s and (s:gsub("^%s*(.-)%s*$", "%1")) or ''
end
--https://www.lua.org/pil/19.3.html
function pairsByKeys (t, f)
local a = {}
for n in pairs(t) do table.insert(a, n) end
table.sort(a, f)
local i = 0 -- iterator variable
local iter = function () -- iterator function
i = i + 1
if a[i] == nil then return nil
else return a[i], t[a[i]]
end
end
return iter
end
--
-- Function to tidy quest names into links
-- Any parenthetical (e.g. '(quest)') will be removed from the text, but remain in the link
-- 'Recipe for Disaster/' will be replaced in the RfD subquests, so that only the subquest name appears as text
-- Returns a link
-- The 'Full:' modifier is removed
--
function tidy_link(name)
if name then
if name:find('Full:') then
name = name:sub(6)
end
local alt = name:match('(.*)%(.*%)') or name
if name:find('Recipe for Disaster%/') then
alt = name:gsub('Recipe for Disaster%/','')
end
name = string.format('[[%s|%s]]',name,alt)
end
return name
end
return p