Module:Rotations/Merchant
Jump to navigation
Jump to search
Module documentation
This documentation is transcluded from Module:Rotations/Merchant/doc. [edit] [history] [purge]
Module:Rotations/Merchant is invoked by .
Module:Rotations/Merchant requires .
Module:Rotations/Merchant loads data from Module:Rotations/Merchant/data.
Module:Rotations/Merchant is required by Module:Sandbox/User:Juliantup598/Traveling Merchant Selection calculator.
| Function list |
|---|
| L 21 — formatQuantity L 28 — p.get L 33 — p.slotA L 38 — p.slotB L 43 — p.slotC L 48 — getSlots L 55 — p.api L 59 — p._api L 102 — makeFullRow L 124 — p.today L 130 — p.todayManual L 148 — p._today L 201 — plink L 206 — makeShortRow L 273 — p.future L 279 — p._future L 287 — parseOffsetsToString L 332 — p.orderAB L 370 — p.orderC L 393 — p.itemsC L 411 — p.listItems L 471 — p.nextItem L 483 — p._nextItem L 561 — check_count L 667 — makeIconRow L 691 — p.todayIcons L 697 — p.todayIconsManual L 712 — p._todayIcons |
--[=[
-- Rotation of the [[Travelling Merchant's Shop]]
-- See [[Talk:Travelling Merchant's Shop#History and pattern]] for more info
--]=]
local rotations = require('Module:Rotations')
local rsrandom = require('Module:Rotations/RsRandom')
local coins = require('Module:Currency')._amount
local lang = mw.getContentLanguage()
local purge = require('Module:Purge')._purge
local removeDuplicates = require('Module:Array').unique
local storeLine = require("Module:StoreLine")._main
local p = {}
local items
do
local _data = mw.loadData("Module:Rotations/Merchant/data")
items = _data.items
p.slot_map = _data.slot_map
end
local function formatQuantity(quantity)
if quantity[1] == quantity[2] then
return lang:formatNum(quantity[1])
end
return lang:formatNum(quantity[1]) .. '–' .. lang:formatNum(quantity[2])
end
function p.get(runedate, k, n)
local seed = runedate * (2 ^ 32) + (runedate % k)
return rsrandom.nextInt(seed, n)
end
function p.slotA(offsetFromNow)
local index = p.get(rsrandom.runeDate_offset(offsetFromNow), 3, 19)
return p.slot_map.A[index], items[p.slot_map.A[index]]
end
function p.slotB(offsetFromNow)
local index = p.get(rsrandom.runeDate_offset(offsetFromNow), 8, 19)
return p.slot_map.B[index], items[p.slot_map.B[index]]
end
function p.slotC(offsetFromNow)
local index = p.get(rsrandom.runeDate_offset(offsetFromNow), 5, 13)
return p.slot_map.C[index], items[p.slot_map.C[index]]
end
local function getSlots(offset)
local slotA = {p.slotA(offset or 0)}
local slotB = {p.slotB(offset or 0)}
local slotC = {p.slotC(offset or 0)}
return {A = slotA, B = slotB, C = slotC}
end
function p.api(frame)
local args = frame:getParent().args
return p._api(args.offset, args.format)
end
function p._api(offset, format)
offset = tonumber(offset or 0) or 0
local slots = getSlots(offset)
local a, a_d = unpack(slots.A)
local b, b_d = unpack(slots.B)
local c, c_d = unpack(slots.C)
if format == 'simple' then
return string.format('@%s¦%s¦%s@', a, b ,c)
end
if format == 'json' then
local jsonret = {}
jsonret['timestamp'] = os.time()
jsonret['items'] = {
{ name = a, cost = a_d.cost, quantity = formatQuantity(a_d.quantity), use = mw.text.encode(a_d.use, '%[%]') },
{ name = b, cost = b_d.cost, quantity = formatQuantity(b_d.quantity), use = mw.text.encode(b_d.use, '%[%]') },
{ name = c, cost = c_d.cost, quantity = formatQuantity(c_d.quantity), use = mw.text.encode(c_d.use, '%[%]') }
}
return mw.text.jsonEncode(jsonret)
end
local ret = mw.html.create('div')
ret :tag('span')
:addClass('slotA')
:tag('span'):addClass('name'):wikitext(a):done()
:tag('span'):addClass('cost'):wikitext(a_d.cost):done()
:tag('span'):addClass('quantity'):wikitext(formatQuantity(a_d.quantity)):done()
:tag('span'):addClass('use'):wikitext(a_d.use):done()
:done()
:addClass('slotB')
:tag('span'):addClass('name'):wikitext(b):done()
:tag('span'):addClass('cost'):wikitext(b_d.cost):done()
:tag('span'):addClass('quantity'):wikitext(formatQuantity(b_d.quantity)):done()
:tag('span'):addClass('use'):wikitext(b_d.use):done()
:done()
:addClass('slotC')
:tag('span'):addClass('name'):wikitext(c):done()
:tag('span'):addClass('cost'):wikitext(c_d.cost):done()
:tag('span'):addClass('quantity'):wikitext(formatQuantity(c_d.quantity)):done()
:tag('span'):addClass('use'):wikitext(c_d.use):done()
:done()
return ret
end
local function makeFullRow(name, details)
local tr = mw.html.create('tr')
local page = details.name or string.format('[[%s]]', name)
tr :tag('td')
:wikitext(string.format('[[File:%s.png|link=%s]]', name, name))
:addClass('inventory-image')
:done()
:tag('td')
:wikitext(page)
:done()
:tag('td')
:wikitext(coins(details.cost, 'coins'))
:done()
:tag('td')
:wikitext(formatQuantity(details.quantity))
:done()
:tag('td')
:wikitext(details.use)
:done()
return tr
end
function p.today()
local slots = getSlots(0)
return p._today(slots.A, slots.B, slots.C)
end
-- Deprecated; previously used by [[Template:Travelling Merchant/manual]]
function p.todayManual(frame)
local args = frame:getParent().args
-- if date is specified, only show manual stock for that date
if args.date == nil or lang:formatDate('F d Y', args.date) == lang:formatDate('F d Y') then
local slotA = args.A or args.a or args[1]
slotA = {slotA, items[slotA]}
local slotB = args.B or args.b or args[2]
slotB = {slotB, items[slotB]}
local slotC = args.C or args.c or args[3]
slotC = {slotC, items[slotC]}
return p._today(slotA, slotB, slotC)
end
return p.today()
end
function p._today(slotA, slotB, slotC)
local t = mw.html.create('table')
t :addClass('wikitable align-center-1 align-center-4')
:tag('tr')
:tag('th')
:wikitext('Item')
:attr('colspan', 2)
:done()
:tag('th')
:wikitext('Cost')
:done()
:tag('th')
:wikitext('Quantity')
:done()
:tag('th')
:wikitext('Use')
:done()
t :node(makeFullRow('Uncharted island map (Deep Sea Fishing)', items['Uncharted island map (Deep Sea Fishing)']))
:node(makeFullRow(slotA[1], slotA[2]))
:node(makeFullRow(slotB[1], slotB[2]))
:node(makeFullRow(slotC[1], slotC[2]))
local cd = mw.html.create('span')
cd :addClass('countdown')
:attr('data-end', 'stop')
:tag('span')
:addClass('countdowndate')
:wikitext(lang:formatDate('F d Y') .. ' 23:59 UTC')
if mw.title.getCurrentTitle().fullText == "Travelling Merchant's Shop" then
-- Slot 1's item acts like a normal shop; simulate {{StoreLine}} usage to populate bucket data
storeLine{
name = "Uncharted island map (Deep Sea Fishing)",
stock = "1",
sell = "800000",
buy = "N/A",
gemw = "no",
alch = "no",
}
storeLine{
name = "Uncharted island map (red)",
stock = "1",
sell = "800000",
buy = "N/A",
gemw = "no",
alch = "no",
--misc = "Very rarely obtained when purchasing the green map",
-- removed so as not to emit an unused citation on [[Travelling Merchant's Shop]]
}
end
return string.format("''This is the stock for %s ([[Runedate]] %s).'' <sup>%s</sup><br>Valid for %s\n%s", lang:formatDate('[[j F]] [[Y]]'), rsrandom.runeDate_today(), tostring(purge()), tostring(cd), tostring(t))
end
local function plink(img, page)
local link = page or string.format('[[%s]]', img)
return string.format('[[File:%s.png|link=%s]] %s', img, img, link)
end
local function makeShortRow(offset, onlyitem_list)
local slots = getSlots(offset)
local a_n, a_d = unpack(slots.A)
local b_n, b_d = unpack(slots.B)
local c_n, c_d = unpack(slots.C)
local classDate, classA, classB, classC = nil,nil,nil,nil
local founditems = {}
if onlyitem_list then
local foundonlyitem = false
local highlightclass = 'table-bg-green'
if offset == 0 then
classDate = highlightclass
end
for _,onlyitem in ipairs(onlyitem_list) do
if a_n == onlyitem then
classA = highlightclass
table.insert(founditems, onlyitem)
foundonlyitem = true
end
if b_n == onlyitem then
classB = highlightclass
table.insert(founditems, onlyitem)
foundonlyitem = true
end
if c_n == onlyitem then
classC = highlightclass
table.insert(founditems, onlyitem)
foundonlyitem = true
end
end
if not foundonlyitem then
return {nil, {nil}}
end
founditems = removeDuplicates(founditems)
end
local tr = mw.html.create('tr')
tr :tag('td')
:wikitext(lang:formatDate('j F Y', string.format('%s%s day', offset >= 0 and '+' or '', offset)))
:addClass(classDate)
:done()
:tag('td')
:wikitext(rsrandom.runeDate_offset(offset))
:addClass(classDate)
:done()
:tag('td')
:wikitext(plink(a_n, a_d.name))
:addClass(classA)
:addClass('inventory-image')
:done()
:tag('td')
:wikitext(plink(b_n, b_d.name))
:addClass(classB)
:addClass('inventory-image')
:done()
:tag('td')
:wikitext(plink(c_n, c_d.name))
:addClass(classC)
:addClass('inventory-image')
:done()
return {tr, founditems}
end
function p.future(frame)
local args = frame:getParent().args
local s = args.start or args[1] or 0
local e = args['end'] or args[2] or 5
return p._future(s,e)
end
function p._future(startOffset, endOffset)
-- enforce numbers
startOffset = tonumber(startOffset or 0) or 0
endOffset = tonumber(endOffset or 5) or 5
-- switch around if start/end mixed
if startOffset > endOffset then
startOffset, endOffset = endOffset, startOffset
end
local function parseOffsetsToString()
local start_s = math.abs(startOffset) > 1 and 's' or ''
local end_s = math.abs(endOffset) > 1 and 's' or ''
if startOffset == endOffset then
if startOffset > 0 then return string.format('%s day%s after today', startOffset, start_s)
elseif startOffset < 0 then return string.format('%s day%s before today', startOffset*-1, start_s)
elseif startOffset == 0 then return 'today' end
else
local s = ''
if startOffset > 0 then s = string.format('%s%s day%s after today', s, startOffset, start_s)
elseif startOffset < 0 then s = string.format('%s%s day%s before today', s, startOffset*-1, start_s)
elseif startOffset == 0 then s = string.format('%stoday', s) end
s = string.format('%s to ', s)
if endOffset > 0 then s = string.format('%s%s day%s after today', s, endOffset, end_s)
elseif endOffset < 0 then s = string.format('%s%s day%s before today', s, endOffset*-1, end_s)
elseif endOffset == 0 then s = string.format('%stoday', s) end
return s
end
return '(error: start and/or end not numbers)'
end
local t = mw.html.create('table')
t :addClass('wikitable sticky-header')
:tag('tr')
:tag('th')
:wikitext('Date')
:done()
:tag('th')
:wikitext('[[Runedate]]')
:done()
:tag('th')
:wikitext('Slot A')
:done()
:tag('th')
:wikitext('Slot B')
:done()
:tag('th')
:wikitext('Slot C')
:done()
for i=startOffset,endOffset,1 do
t:node(makeShortRow(i)[1])
end
return string.format("This table is the stock for %s (%s)<sup>%s</sup>\n%s", parseOffsetsToString(), lang:formatDate('j F Y'), purge(), tostring(t))
end
function p.orderAB()
local t = mw.html.create('table')
t :addClass('wikitable sticky-header mw-collapsible mw-collapsed align-center-1 align-center-4 align-center-7')
:tag('tr')
:tag('th')
:attr('colspan', 11)
:wikitext('Slot A and B cycles')
:done()
:done()
:tag('tr')
:tag('th'):wikitext('Day'):done()
:tag('th'):wikitext('Slot A'):done()
:tag('th'):wikitext('Slot B'):done()
:tag('th'):attr('rowspan', 41):wikitext(' '):done()
:tag('th'):wikitext('Day'):done()
:tag('th'):wikitext('Slot A'):done()
:tag('th'):wikitext('Slot B'):done()
:tag('th'):attr('rowspan', 41):wikitext(' '):done()
:tag('th'):wikitext('Day'):done()
:tag('th'):wikitext('Slot A'):done()
:tag('th'):wikitext('Slot B'):done()
for i=1,40,1 do
t :tag('tr')
:tag('td'):wikitext(i):done()
:tag('td'):wikitext(itemIDs[slotA_rotation[i]]):done()
:tag('td'):wikitext(itemIDs[slotB_rotation[i]]):done()
:tag('td'):wikitext(i+40):done()
:tag('td'):wikitext(itemIDs[slotA_rotation[i+40]]):done()
:tag('td'):wikitext(itemIDs[slotB_rotation[i+40]]):done()
:tag('td'):wikitext(i+80):done()
:tag('td'):wikitext(itemIDs[slotA_rotation[i+80]]):done()
:tag('td'):wikitext(itemIDs[slotB_rotation[i+80]]):done()
end
return t
end
function p.orderC()
local t = mw.html.create('table')
t :addClass('wikitable mw-collapsible align-center-1 align-center-2 align-center-3 align-center-4')
:tag('tr')
:tag('th'):attr('colspan', 5):wikitext('Item order (IDs)'):done()
:done()
:tag('tr')
:tag('th'):wikitext('Day'):done()
:tag('th'):wikitext('Item order ID'):done()
:tag('th'):attr('rowspan', 21):wikitext(' '):done()
:tag('th'):wikitext('Day'):done()
:tag('th'):wikitext('Item order ID'):done()
for i=1,20,1 do
t :tag('tr')
:tag('td'):wikitext(i):done()
:tag('td'):wikitext(slotC_rotation[i]):done()
:tag('td'):wikitext(i+20):done()
:tag('td'):wikitext(slotC_rotation[i+20]):done()
end
return t
end
function p.itemsC()
local t = mw.html.create('table')
t :addClass('wikitable mw-collapsible align-center-1')
:tag('tr')
:tag('th'):attr('colspan', 2):wikitext('Item map'):done()
:done()
:tag('tr')
:tag('th'):wikitext('Item map ID'):done()
:tag('th'):wikitext('Item'):done()
for i=1,#p.slot_map.C,1 do
t :tag('tr')
:tag('td'):wikitext(i):done()
:tag('td'):wikitext(p.slot_map.C[i]):done()
end
return t
end
function p.listItems()
local slotAB_list = {
'Barrel of bait', 'Tangled fishbowl', 'Broken fishing rod',
'Small goebie burial charm', 'Goebie burial charm', 'Menaphite gift offering (small)',
'Menaphite gift offering (medium)', 'Unstable air rune', 'Anima crystal',
'Slayer VIP Coupon', 'Distraction & Diversion reset token (daily)', 'Unfocused damage enhancer',
'Sacred clay (Deep Sea Fishing)', 'Shattered anima', 'Advanced pulse core',
'Livid plant (Deep Sea Fishing)', 'Gift for the Reaper', 'Silverhawk down', 'Horn of honour'
}
local slotC_list = {
'Large goebie burial charm', 'Message in a bottle (Deep Sea Fishing)', 'Dragonkin lamp',
'Dungeoneering Wildcard', 'Menaphite gift offering (large)', 'Taijitu',
'Distraction & Diversion reset token (weekly)', 'Distraction & Diversion reset token (monthly)', 'Starved ancient effigy',
'Harmonic dust', 'Crystal triskelion', 'Deathtouched dart',
'Unfocused reward enhancer'
}
local t = mw.html.create('table')
t :addClass('wikitable align-center-1 align-center-4')
:tag('tr')
:tag('th'):attr('colspan', 5):wikitext('Slot 1 (always)'):done()
:done()
:tag('tr')
:tag('th'):attr('colspan', 2):wikitext('Item'):done()
:tag('th'):wikitext('Cost'):done()
:tag('th'):wikitext('Quantity'):done()
:tag('th'):wikitext('Use'):done()
:done()
:node(makeFullRow('Uncharted island map (Deep Sea Fishing)', items['Uncharted island map (Deep Sea Fishing)']))
:tag('tr')
:tag('th'):attr('colspan', 5):wikitext('Slots 2 and 3'):done()
:done()
:tag('tr')
:tag('th'):attr('colspan', 2):wikitext('Item'):done()
:tag('th'):wikitext('Cost'):done()
:tag('th'):wikitext('Quantity'):done()
:tag('th'):wikitext('Use'):done()
:done()
for i,v in ipairs(slotAB_list) do
t:node(makeFullRow(v, items[v]))
end
t :tag('tr')
:tag('th'):attr('colspan', 5):wikitext('Slot 4'):done()
:done()
:tag('tr')
:tag('th'):attr('colspan', 2):wikitext('Item'):done()
:tag('th'):wikitext('Cost'):done()
:tag('th'):wikitext('Quantity'):done()
:tag('th'):wikitext('Use'):done()
:done()
for i,v in ipairs(slotC_list) do
t:node(makeFullRow(v, items[v]))
end
return t
end
function p.nextItem(frame)
local args = frame:getParent().args
local item = args.item
if not item then
item = mw.title.getCurrentTitle().text
end
local item_list = mw.text.split(item,",%s*")
local boolconverter = {["true"]=true,["false"]=false}
local individual = boolconverter[args.individual]
return p._nextItem(item_list, tonumber(args.number), tonumber(args.limit), individual)
end
function p._nextItem(item_list, n, dayslimit, individual)
local list = {
['Barrel of bait'] = 'ab',
['Tangled fishbowl'] = 'ab',
['Broken fishing rod'] = 'ab',
['Small goebie burial charm'] = 'ab',
['Goebie burial charm'] = 'ab',
['Menaphite gift offering (small)'] = 'ab',
['Menaphite gift offering (medium)'] = 'ab',
['Unstable air rune'] = 'ab',
['Anima crystal'] = 'ab',
['Slayer VIP Coupon'] = 'ab',
['Distraction & Diversion reset token (daily)'] = 'ab',
['Unfocused damage enhancer'] = 'ab',
['Sacred clay (Deep Sea Fishing)'] = 'ab',
['Shattered anima'] = 'ab',
['Advanced pulse core'] = 'ab',
['Livid plant (Deep Sea Fishing)'] = 'ab',
['Gift for the Reaper'] = 'ab',
['Silverhawk down'] = 'ab',
['Horn of honour'] = 'ab',
['Large goebie burial charm'] = 'c',
['Message in a bottle (Deep Sea Fishing)'] = 'c',
['Dragonkin lamp'] = 'c',
['Dungeoneering Wildcard'] = 'c',
['Menaphite gift offering (large)'] = 'c',
['Taijitu'] = 'c',
['Distraction & Diversion reset token (weekly)'] = 'c',
['Distraction & Diversion reset token (monthly)'] = 'c',
['Starved ancient effigy'] = 'c',
['Harmonic dust'] = 'c',
['Crystal triskelion'] = 'c',
['Deathtouched dart'] = 'c',
['Unfocused reward enhancer'] = 'c'
}
if not n then
n = 5
end
if not dayslimit then
dayslimit = 200
end
if not individual or #item_list == 1 then
individual = false
end
local unrecognised_item = ""
for _, item in ipairs(item_list) do
if not list[item] then
unrecognised_item = unrecognised_item .. "Unrecognised item ''"..item.."''. Please enter the item name exactly.<br>"
end
end
if unrecognised_item ~= "" then
return unrecognised_item:sub(1, -5)
end
local t = mw.html.create('table')
t :addClass('wikitable sticky-header')
:tag('tr')
:tag('th')
:wikitext('Date')
:done()
:tag('th')
:wikitext('[[Runedate]]')
:done()
:tag('th')
:wikitext('Slot A')
:done()
:tag('th')
:wikitext('Slot B')
:done()
:tag('th')
:wikitext('Slot C')
:done()
local d = 0
local function check_count(count_all_table, n)
local count_reached = true
for _,v in pairs(count_all_table) do
if v < n then
count_reached = false
end
end
return count_reached
end
local finalcount = math.huge
if not individual then
local count = 0
while count < n and d < dayslimit do
local r = makeShortRow(d, item_list)[1]
if r then
t:node(r)
count = count + 1
end
d = d + 1
end
finalcount = count
else
local count = {}
for _,item in ipairs(item_list) do
count[item] = 0
end
local count_all = false
while not count_all and d < dayslimit do
local r = makeShortRow(d, item_list)
if r[1] then
t:node(r[1])
for _,item in ipairs(r[2]) do
count[item] = count[item] + 1
end
end
count_all = check_count(count, n)
d = d + 1
end
-- local test = ""
-- for k,v in pairs(count) do
-- test = test .. k .. ": " .. tostring(v) .. "<br>"
-- end
-- return test
for _,v in pairs(count) do
finalcount = math.min(finalcount, v)
end
end
local simpleitem = ""
for item_count = 1, #item_list do
local item = item_list[item_count]
local simpleitem_temp = item:lower()
local iteminfo = items[item]
if iteminfo._name then
simpleitem_temp = iteminfo._name
end
local andor = "or"
if individual then
andor = "and"
end
if item_count == #item_list then
if #item_list == 1 then
simpleitem = simpleitem..", "
elseif #item_list == 2 then
simpleitem = simpleitem.." " .. andor .. " "
else
simpleitem = simpleitem..", " .. andor .. " "
end
else
simpleitem = simpleitem..", "
end
simpleitem = simpleitem.."<i>[["..simpleitem_temp.."]]</i>"
end
simpleitem = simpleitem:sub(3)
local be_string = "is"
if individual then
be_string = "are"
end
local intro = string.format('This table shows the next %s dates<sup>%s</sup> on which %s %s available in the [[Travelling Merchant\'s Shop]]', finalcount, tostring(purge()), simpleitem, be_string)
if #item_list == 1 then
local iteminfo = items[item_list[1]]
local quantity_string = formatQuantity(iteminfo.quantity)
local cost_per_string = ''
if iteminfo.quantity[1] == iteminfo.quantity[2] then
if iteminfo.quantity[1] == 1 then
quantity_string = 'it'
end
else
cost_per_string = string.format(' (%s–%s each)', coins(iteminfo.cost / iteminfo.quantity[2], 'nocoins'), coins(iteminfo.cost / iteminfo.quantity[1], 'nocoinsc'))
end
intro = intro..string.format(', where %s can be purchased for %s%s', quantity_string, coins(iteminfo.cost, 'nocoinsc'), cost_per_string)
end
return intro .. '.\n' .. tostring(t)
end
local function makeIconRow(slotAname, slotBname, slotCname)
local m_n = 'Uncharted island map (Deep Sea Fishing)'
local tr = mw.html.create('tr')
tr :tag('td')
:wikitext(string.format('[[File:%s.png|link=%s]]', m_n, m_n))
:addClass('inventory-image')
:done()
:tag('td')
:wikitext(string.format('[[File:%s.png|link=%s]]', slotAname, slotAname))
:addClass('inventory-image')
:done()
:tag('td')
:wikitext(string.format('[[File:%s.png|link=%s]]', slotBname, slotBname))
:addClass('inventory-image')
:done()
:tag('td')
:wikitext(string.format('[[File:%s.png|link=%s]]', slotCname, slotCname))
:addClass('inventory-image')
:done()
return tr
end
function p.todayIcons()
local slots = getSlots(0)
return p._todayIcons(slots.A[1], slots.B[1], slots.C[1])
end
-- Deprecated; previously used by [[Template:Travelling Merchant icons/manual]]
function p.todayIconsManual(frame)
local args = frame:getParent().args
-- if date is specified, only show manual icons for that date
if args.date == nil or lang:formatDate('F d Y', args.date) == lang:formatDate('F d Y') then
local slotA = args.A or args.a or args[1]
local slotB = args.B or args.b or args[2]
local slotC = args.C or args.c or args[3]
return p._todayIcons(slotA, slotB, slotC)
end
return p.todayIcons()
end
function p._todayIcons(slotAname, slotBname, slotCname)
local t = mw.html.create('table')
t :addClass('wikitable align-center-1 align-center-4')
:tag('tr')
:tag('th')
:attr('colspan', 4)
:wikitext(string.format('Stock for %s', lang:formatDate('[[j F]] [[Y]]')))
:tag('br'):done()
:wikitext('([[Runedate]] '..rsrandom.runeDate_today()..')')
:done()
t :node(makeIconRow(slotAname, slotBname, slotCname))
t :tag('tr')
:tag('td')
:wikitext( tostring(purge()) )
:attr('colspan', 4)
:done()
return t
end
return p