Module:Lab Rotation

From IdleOn MMO Wiki
Revision as of 02:45, 17 May 2024 by Nads (talk | contribs)

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 = '!%d %b %Y'

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|osdate
local function formatDate(week)
    return os.date(DATE_FORMAT, week * SECONDS_PER_WEEK)
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
                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