Module:Lab Rotation

From IdleOn MMO Wiki
Revision as of 17:56, 7 October 2024 by BHY4A (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Documentation for this module may be created at Module:Lab Rotation/doc

require('strict')

local Random = require('Module:Random')
local Data = require('Module:Lab Rotation/Data')

local p = {}

--- The number of seconds in a week.
local SECONDS_PER_WEEK = 604800

--- The format to display dates in.
local DATE_FORMAT = '%x'

local CHIP_PREFIX = 'Lab - '
local JEWEL_PREFIX = 'Console Jewel '

--- Gets the current week
--- @return integer
local function getCurrentWeek()
    return math.floor(os.time() / SECONDS_PER_WEEK)
end

--- Checks whether the table contains the item.
--- @generic T
--- @param tbl T[] The table
--- @param item T The item to search for
--- @return boolean
local function contains(tbl, item)
	item = item:lower()
    for _, e in ipairs(tbl) do
    	if item == e:lower() then
            return true
        end
    end
    return false
end

--- Checks whether the item is a chip.
--- @param item string The item to check
--- @return boolean
local function isChip(item)
    return contains(Data.chips, item)
end

--- Checks whether the item is a jewel.
--- @param item string The item to check
--- @return boolean
local function isJewel(item)
    return contains(Data.jewels, item)
end

--- Formats a week into its date string.
--- @param week integer
--- @return string
local function formatDate(week)
    local day = tonumber(os.date("%d", week * SECONDS_PER_WEEK))
    local month = os.date("%B", week * SECONDS_PER_WEEK)
    local year = os.date("%Y", week * SECONDS_PER_WEEK)

    local suffix
    if day == 1 or day == 21 or day == 31 then
        suffix = "st"
    elseif day == 2 or day == 22 then
        suffix = "nd"
    elseif day == 3 or day == 23 then
        suffix = "rd"
    else
        suffix = "th"
    end

    return string.format("%d%s %s, %d", day, suffix, month, year)
end

--- Generates a random number.
--- @param seed integer The seed
--- @param upper_bound integer The upper bound
--- @return integer
local function generateRandomNumber(seed, upper_bound)
    local random = math.floor(Random:new(seed):rand() * 1000)
    return upper_bound and (random % upper_bound) or random
end

--- Gets the item that appears in the specified slot during the specified week.
--- @param week integer The week
--- @param slot integer The item slot
function p._getItem(week, slot)
    local choices = (slot == 3) and Data.jewels or Data.chips
    local numChoices = (slot == 1) and #choices - 10 or #choices

    local seed = week + ((slot - 1) * 500)
    local item = generateRandomNumber(seed, numChoices)

    local prevItem = generateRandomNumber(seed - 1, numChoices)
    if prevItem == item then
        local nextItem = generateRandomNumber(seed + 1, numChoices)
        while item == prevItem or item == nextItem do
            seed = seed + 765
            item = generateRandomNumber(seed, numChoices)
        end
    end

    -- Lua indices start at 1
    item = item + 1

    local ret = {}

    -- Generate alternative items for Jade Emporium-exclusive jewels.
    --
    -- Note: The game currently has a bug which causes chips to be rerolled
    -- as well as jewels if the bonus isn't unlocked. Once the bug is fixed,
    -- remove the commented from the line below and delete this note.
    if item >= 19 and item <= 21 --[[and slot == 2]] then
        table.insert(ret, 1, choices[item - 10])
    end
    table.insert(ret, choices[item])

    return ret
end

--- Template entry point for querying what items are currently in the shop.
--- @param frame table
--- @return string
function p.what(frame)
    return p._what(frame.args)
end

--- Module entry point for querying what items are currently in the shop.
--- @param args { slot: integer, week?: integer, offset?: integer }
--- @return string
function p._what(args)
    local offset = tonumber(args.offset) or 0
    local week = tonumber(args.week) or getCurrentWeek()
    local slot = tonumber(args.slot) or 1
    
    local prefix = (slot == 3) and JEWEL_PREFIX or CHIP_PREFIX

    assert(slot ~= nil, '"slot" is undefined')

    local ret = mw.html.create('div')

    local items = p._getItem(week + offset, slot)
    for _, item in ipairs(items) do
    	local content = '[[File:' .. prefix .. item .. '.png|32px]] ' .. item
    	
        ret:tag('span'):addClass('lab-rotation-item'):wikitext(content)
    end

    return tostring(ret)
end

--- Template entry point for querying when an item will be available next.
--- @param frame table
--- @return string|osdate
function p.when(frame)
    return p._when(frame.args)
end

--- Module entry point for querying when an item will be available next.
--- @param args { [1]: string }
--- @return string|osdate
function p._when(args)
    local item = args[1]:lower()
    local slots = isChip(item) and { 1, 2 } or isJewel(item) and { 3 } or nil
    
    assert(slots ~= nil, 'Invalid item name: ' .. item)

    local week = getCurrentWeek()
    while true do
        for _, slot in ipairs(slots) do
            local items = p._getItem(week, slot)
            if contains(items, item) then
            	local ret = mw.html.create('div')
            	
            	ret:tag('div'):wikitext(formatDate(week))
            	
            	if week == getCurrentWeek() then
            		ret:tag('div'):wikitext('Currently Available')
        		else
        			ret:tag('span'):
        				tag('span'):
        					wikitext('(in '):done():
    					tag('time'):
    						addClass('countdown-timer'):
    						attr('data-timestamp', week * SECONDS_PER_WEEK):
    						attr('data-mode', 'labeled'):
    						attr('data-min-unit', 'd'):
    						done():
						tag('span'):
							wikitext(')')
            	end
    		
            	return tostring(ret);
            	-- week is the time!
            	-- <div>
            	--  <span>formatDate(week)</span>
            	-- <time data-timestamp="week*></time>
            	-- </div>
                -- return formatDate(week)
            end
        end
        week = week + 1
    end
end

--- Module entry point for querying when an item will be available next.
--- @param frame table The template frame
--- @return string|osdate
function p.date(frame)
    return p._date(frame.args)
end

--- Template entry point for querying when an item will be available next.
--- @param args { week?: integer, offset?: integer }
--- @return string|osdate
function p._date(args)
    local week = tonumber(args.week) or getCurrentWeek()
    local offset = tonumber(args.offset) or 0
    return formatDate(week + offset)
end

return p