Module:Rotations

-- Various functions for activities that have time based rotations -- local p = {}

local purge = require('Module:Purge')._purge

local seconds_in_day = 24 * 60 * 60 local _on_ = 'status-active' local _off_ = 'status-inactive'

local yesno = { [true] = _on_, [false] = _off_ }

local unit_seconds_from_name = { minute = 60, day = seconds_in_day }

local lang = mw.language.new('en') -- Adds a table row with a purge button to the table passed Also gives the table an anchor id so that clicking purge will return readers to the table -- function p.add_purge(builder,cols) local page_title = mw.title.getCurrentTitle.fullText local url = tostring(mw.uri.fullUrl(page_title..'#reload','action=purge')) builder:attr('id','reload') builder:tag('tr') :tag('td') :attr({ colspan = cols or 1 }) :wikitext(purge('reload')) :done :done end

function p.add_purge2(table, td) local page_title = mw.title.getCurrentTitle.fullText local url = tostring(mw.uri.fullUrl(page_title..'#reload','action=purge')) table:attr('id','reload') td:wikitext(' '..purge('reload')) :done end

-- Returns the plural of the word -- function p.plural(word, n, plural) if n == 1 then return word else return plural or (word .. 's') end end

--[[

--]] function p.on_off(on_time, total_time, offset, unit_seconds) local units_after_utc = math.floor(os.time / unit_seconds) local units_into_start = (units_after_utc + offset) % total_time

local on = units_into_start < on_time

local units_until_change if on then units_until_change = on_time - units_into_start else units_until_change = total_time - units_into_start end

return on, units_until_change end

--[[ Returns a number that can be used to identify the rotation based on:
 * The number of days per rotation
 * The number of rotations available
 * The offset of days such that Tuesday 1 January 1970 + offset would be the starting day for rotation 1 (this should be between 0 and 6)

Also returns a second value that determines how many days until the next rotation --]] function p.rotation_days(interval, rotation_count, offset) local days_after_utc = math.floor(os.time / seconds_in_day) local days_into_period = (days_after_utc + offset) % (interval * rotation_count)

local rotation = math.floor(days_into_period / interval) + 1 local days_until_next_rotation = interval - days_into_period % interval

return rotation, days_until_next_rotation end

--[[

--]] function p.simple_on_off(on_time, total_time, offset, unit_name) local unit_seconds = unit_seconds_from_name[unit_name]

local on, change_time = p.on_off(on_time, total_time, offset, unit_seconds)

local ret_table = mw.html.create('table') :addClass('wikitable') :css({ ['text-align'] = 'center',					float = 'right' }) :tag('tr') :tag('td') :wikitext('Time until ' .. (on and 'end' or 'start') .. ': ' .. change_time .. ' ' .. p.plural(unit_name, change_time)) :addClass(yesno[on]) :done :done

p.add_purge(ret_table) return ret_table end

local function date_of(i_rot, interval, curr_rot, next_in, total_rots) local s = os.time + seconds_in_day * (next_in + interval * ((i_rot - curr_rot - 1) % total_rots) ) if i_rot==curr_rot then return 'Now!' else return lang:formatDate('j M', '@' .. s, nil) end end

--[[

--]] function p.simple_table(rotation_names, interval, offset, dated) local rotation, next = p.rotation_days(interval, #rotation_names, offset) local align = 'center'

if dated then align = 'left' pad = '0.5em' end

local ret_table = mw.html.create('table') :addClass('wikitable') :css({ ['text-align'] = align,					margin = '3px',					float = 'right' }) :tag('caption') :wikitext('Current rotation') :done local td	for i, v in ipairs(rotation_names) do		td = ret_table:tag('tr'):tag('td') td			:addClass(yesno[i==rotation]) :wikitext(v) :done :done :done if dated then td:css('padding-left', '0.5em') :tag('span') :css({ ['float'] = 'right',					['text-align'] = 'right',					['font-size'] = '80%',					['margin-left'] = '5px' }) :wikitext(date_of(i, interval, rotation, next, #rotation_names)) end end td = ret_table:tag('tr'):tag('td') td			:css('text-align', 'center') :wikitext("Next: "..next..' '..p.plural('day', next).."") :done :done --if dated then p.add_purge2(ret_table, td) --else --	p.add_purge(ret_table) --end return ret_table end

--[=======================================================[ --						ON/OFF --]=======================================================]

--[==========[ --	Ravens --]==========] function p.raven local on, change_time = p.on_off(1, 13, 7, seconds_in_day)

local text if on then text = 'There is currently a raven spawned.' else local last = 13 - change_time

local date_format = "%e %B %Y" local last_date = os.date(date_format, os.time - last * seconds_in_day) local next_date = os.date(date_format, os.time + change_time * seconds_in_day)

text = 'The last raven spawned  .. last .. ' ' .. p.plural('day', last) .. ' ago on ' .. last_date .. '. The next raven will spawn on ' .. next_date .. ' in ' .. change_time .. ' ' .. p.plural('day', change_time) .. . '	end

return text .. purge end

--[=============[ --	Sinkholes --]=============] function p.sinkholes return p.simple_on_off(15, 60, 30, 'minute') end

--[================[ --	Guthix cache --]================] function p.guthix_cache return p.simple_on_off(10, 3 * 60, 0, 'minute') end

--[==================[ --	Big chinchompa --]==================] function p.big_chinchompa return p.simple_on_off(20, 60, 30, 'minute') end

--[=======================================================[ --						 Cycles --]=======================================================]

--[=======================[ --	Minigame spotlight --]=======================]

--modified version of p.simple_table local function spotlight_table(rotation_names, interval, offset) local rotation, next = p.rotation_days(interval, #rotation_names, offset) local align = 'left' local pad = '0.5em'

local ret_table = mw.html.create('table') :addClass('wikitable') :css({ ['text-align'] = align,					margin = '3px',					float = 'right' }) :tag('caption') :wikitext('Current rotation') :done local td	local starti = 0 for i,v in ipairs(rotation_names) do		if date_of(i, interval, rotation, next, #rotation_names) == 'Now!' then starti = i-1 break end end

local j = 0 local i, v	while j < #rotation_names do		i = ((starti + j) % #rotation_names)+1 v = ..rotation_names[i].. td = ret_table:tag('tr'):tag('td') td			:addClass(yesno[i==rotation]) :wikitext(v) :done :done :done td:css('padding-left', '0.5em') :tag('span') :css({ ['float'] = 'right',				['text-align'] = 'right',				['font-size'] = '80%',				['margin-left'] = '5px' }) :wikitext(date_of(i, interval, rotation, next, #rotation_names)) j = j + 1 end td = ret_table:tag('tr'):tag('td') td		 :css('text-align', 'center') :wikitext("Next: "..next..' '..p.plural('day', next).."") :done :done p.add_purge2(ret_table, td) return ret_table end

local function get_spotlight_list return { 'Pest Control', 'Soul Wars', 'Fist of Guthix', 'Barbarian Assault', 'Conquest', 'Fishing Trawler', 'The Great Orb Project', 'Flash Powder Factory', 'Castle Wars', 'Stealing Creation', 'Cabbage Facepunch Bonanza', 'Heist', 'Mobilising Armies', 'Barbarian Assault', 'Conquest', 'Fist of Guthix', 'Castle Wars', 'Pest Control', 'Soul Wars', 'Fishing Trawler', 'The Great Orb Project', 'Flash Powder Factory', 'Stealing Creation', 'Cabbage Facepunch Bonanza', 'Heist', 'Trouble Brewing', 'Castle Wars' } end

function p.spotlight return spotlight_table(get_spotlight_list, 3, -49) -- -49 to force same order as news post end

local function to_row(val) return '|-\n!Next spotlight\n|'..val..' '..purge --use wikicode until infoboxes are modulised end

--find the date of the next spotlighted minigame --returns '' if minigame is not found in the list --returns a row with the date of the next spotlight, or 'Now!' function p.next_spotlight(frame) local interval, offset = 3, -49 local rotations = get_spotlight_list local name = frame:getParent.args[1] local rotation, next_in = p.rotation_days(interval, #rotations, offset) local pos = {} local found = false

for i,v in ipairs(rotations) do		if name == v then pos[i] = '0' found = true end end if not found then return '' end

for i,v in pairs(pos) do		if i == rotation then return to_row("Now!") end pos[i] = os.time + seconds_in_day * (next_in + interval * ((tonumber(i) - rotation - 1) % #rotations) ) end

local next_rot = os.time + seconds_in_day*365 for i,v in pairs(pos) do		if next_rot > v then next_rot = v		end end

return to_row(lang:formatDate('j F', '@' .. next_rot, nil)) end

--[============[ --	Vorago --]============] function p.vorago local rotations = { 'Ceiling collapse', 'Scopulus', 'Vitalis', 'Green bomb', 'TeamSplit', 'The end', }

return p.simple_table(rotations, 7, -6, false) -- -6 to force "the end" to be last end --[==========[ --	Rots --]==========] -- Array borrowed from http://www.pso-clan.com/rotations.js function p.rots local b = { A = 'Ahrim', D = 'Dharok', G = 'Guthan', K = 'Karil', T = 'Torag', V = 'Verac' }	local rotations = { {{b.D,b.T,b.V},{b.K,b.A,b.G}}, {{b.K,b.T,b.G},{b.A,b.D,b.V}}, {{b.K,b.G,b.V},{b.A,b.T,b.D}}, {{b.G,b.T,b.V},{b.K,b.A,b.D}}, {{b.K,b.T,b.V},{b.A,b.G,b.D}}, {{b.A,b.G,b.D},{b.K,b.T,b.V}}, {{b.K,b.A,b.D},{b.G,b.T,b.V}}, {{b.A,b.T,b.D},{b.K,b.G,b.V}}, {{b.A,b.D,b.V},{b.K,b.T,b.G}}, {{b.K,b.A,b.G},{b.T,b.D,b.V}}, {{b.A,b.T,b.G},{b.K,b.D,b.V}}, {{b.A,b.G,b.V},{b.K,b.T,b.D}}, {{b.K,b.A,b.T},{b.G,b.D,b.V}}, {{b.K,b.A,b.V},{b.D,b.T,b.G}}, {{b.A,b.T,b.V},{b.K,b.D,b.G}}, {{b.K,b.D,b.G},{b.A,b.T,b.V}}, {{b.D,b.T,b.G},{b.K,b.A,b.V}}, {{b.G,b.D,b.V},{b.K,b.A,b.T}}, {{b.K,b.T,b.D},{b.A,b.G,b.V}}, {{b.K,b.D,b.V},{b.A,b.T,b.G}} }	local days_after_utc = math.floor(os.time / seconds_in_day) local rotation = (days_after_utc % 20) + 1 rotation = rotations[rotation] local today = os.date("%e %B %Y") local ret = mw.html.create('table') :addClass('wikitable') :css({ ['text-align'] = 'center',					float = 'right' }) :tag('caption') :wikitext(today) :done :tag('tr') :tag('th') :wikitext('West') :done :tag('th') :attr('rowspan','4') :done :tag('th') :wikitext('East') :done :done local s1,s2 = unpack(rotation) for i=1,3 do		ret:tag('tr') :tag('td') :wikitext(s1[i]) :done :tag('td') :wikitext(s2[i]) :done

end p.add_purge(ret,3) return ret end

--[==========[ --	TH D&D --]==========] function p.th_dnd local rotations = { '\Evil Tree', '\Shooting Star', '\Penguin Hide and Seek', '\Circus' }

return p.simple_table(rotations, 7, 1, false) end

--[========================[ --	Circus city template --]========================] function p.circus(frame) local args = frame:getParent.args local disp = mw.text.trim( string.lower(args[1] or '') ) if disp == 'image' then return p.circus_city_image elseif disp == 'city' then return p.circus_city_name else return p.circus_city_table end end

--[======================[ --	Circus city (both) --	Simple return vals --]======================] function p.circus_city local rotations = { { name = 'Tree Gnome Stronghold - south of the entrance; near the gate', image = 'Circus location - Tree Gnome Stronghold.png', short = 'Tree Gnome' },		{ name = 'Seers\' Village', image = 'Circus location - Seers\' Village.png', short = 'Seers\' Village' },		{ name = 'Catherby', image = 'Circus location - Catherby.png', short = 'Catherby' },		{ name = 'Taverley', image = 'Circus location - Taverley.png', short = 'Taverley' },		{ name = 'Edgeville', image = 'Circus location - Edgeville.png', short = 'Edgeville' },		{ name = 'Falador', image = 'Circus location - Falador.png', short = 'Falador' },		{ name = 'Rimmington', image = 'Circus location - Rimmington.png', short = 'Rimmington' },		{ name = 'Draynor Village', image = 'Circus location - Draynor Village.png', short = 'Draynor' },		{ name = 'Al Kharid', image = 'Circus location - Al Kharid.png', short = 'Al Kharid' },		{ name = 'Lumbridge', image = 'Circus location - Lumbridge.png', short = 'Lumbridge' },		{ name = 'Varrock - south-east of Lumber Yard', image = 'Circus location - Lumber Yard.png', short = 'Lumber Yard' },		{ name = 'Varrock - north of Gertrude\'s house', image = 'Circus location - Cooks\' Guild.png', short = 'Gertrude\'s'		} }	local rotation,next = p.rotation_days(7,#rotations,1) return rotations,rotation,next end

--[======================[ --	Circus city (both) --	Pretty table --]======================] function p.circus_city_table local rotations,rot,next = p.circus_city local img = rotations[rot].image local loc = rotations[rot].name local ret = mw.html.create('table') :addClass('wikitable') :css('text-align','center') :tag('tr') :tag('th') :wikitext('Current location:') :done :tag('td') :attr('rowspan','13') :wikitext('') :done :done for i, v in ipairs(rotations) do		ret:tag('tr') :tag('td') :addClass(yesno[i==rot]) :wikitext(v.short) :done :done end ret	:tag('tr') :tag('td') :attr('colspan','2') :wikitext(loc) :done :done local td = ret:tag('tr'):tag('td') td			:attr('colspan','2') :wikitext("Days until next: "..next.."") :done :done :done p.add_purge2(ret, td) return ret end

--[====================[ --	Circus city name --]====================] function p.circus_city_name local rotations,rot = p.circus_city return rotations[rot].name end

--[=====================[ --	Circus city image --]=====================] function p.circus_city_image local rotations,rot = p.circus_city return rotations[rot].image end

--[===========[ --	Araxxor --]===========] function p.araxxor local rax_rotations = { 'Minions', 'Acid', 'Darkness' }

local rotation, days_to_next = p.rotation_days(4, #rax_rotations, 3)

function is_open(path) if path == rotation then return false else return true end end

function add_body(path, tb) local open = is_open(path) local td = tb:tag('td') if open then td:wikitext('Open'):addClass(_on_) else td:wikitext('Closed'):addClass(_off_) end end

local t = mw.html.create('table'):addClass('wikitable'):css('text-align','center') local th = t:tag('tr') local tb = t:tag('tr') for i=1,3 do th:tag('th'):wikitext('Path ' .. i .. ' (' .. rax_rotations[i] .. ')')		add_body(i, tb) end

local td = t:tag('th'):attr('colspan', '3'):wikitext('Days until next rotation: ' .. days_to_next)

local cocoon if is_open(1) and is_open(2) then cocoon = 'I died covered in acid and spiders.' elseif is_open(1) and is_open(3) then cocoon = 'I died in the dark, covered in spiders.' elseif is_open(2) and is_open(3) then cocoon = 'I died in a dark acid pool.' end t:tag('tr'):tag('td'):attr('colspan','3'):css('font-style','italic'):wikitext(cocoon) p.add_purge2(t,td) return t end

return p