Jump to content

Module:Items navbox

From Bconomy Wiki

Documentation for this module may be created at Module:Items navbox/doc

local p = {}

local function has(values, wanted)
    for _, value in ipairs(values or {}) do
        if value == wanted then return true end
    end
    return false
end

local function currentGroup(entry)
    local presentation = entry.presentation or {}
    local boost = presentation.boostTier
    if boost then return 'boosts', boost.category end
    if entry.effect then
        if entry.effect.type == 'actionBoost' then
            local actions = {fish='Fish', hunt='Hunt', mine='Mine', explore='Explore', work='Work'}
            return 'boosts', actions[entry.effect.action] or 'Other'
        end
        return 'boosts', 'Rare loot'
    end
    if entry.petAdventureBoostEffect then return 'boosts', 'Pet' end
    if entry.farmPlotBoostEffect then return 'boosts', 'Farm' end
    if entry.augmentProperties then return 'boosts', 'Tool' end
    if presentation.loot and presentation.loot[1] then return 'boxes', '' end
    if entry.damage or entry.damageMultiplier then return 'weapons', '' end
    if has(entry.attributes, 'equippable') then return 'equipment', '' end
    if entry.growTime then return 'crops', '' end
    if entry.petEnergy then return 'food', '' end
    if entry.worthPerGainedFromBurn then return 'pyre', '' end
    if has(entry.attributes, 'relayItem') then return 'relay', '' end
    if has(entry.lootSources, 'rare') then return 'rare', '' end
    for _, source in ipairs({'explore', 'fish', 'hunt', 'mine'}) do
        if has(entry.lootSources, source) then return 'materials', source end
    end
    if not entry.uncraftable then return 'materials', 'crafted' end
    return 'materials', 'other'
end

local function worth(entry)
    local value = tonumber(entry.cost)
    -- Unknown final worth is placed after known values, without inventing zero.
    return value and value >= 0 and value or math.huge
end

local function less(a, b)
    if a.worth ~= b.worth then return a.worth < b.worth end
    local left, right = mw.ustring.lower(a.name), mw.ustring.lower(b.name)
    if left ~= right then return left < right end
    return a.article < b.article
end

-- Values and current Item membership are supplied by the existing reference
-- poller. The registry selects canonical removed concepts from the frozen archive;
-- it intentionally does not infer identity from reused numeric Item slots.
function p.groups(reference, archive, registry)
    local grouped, seen = {}, {}
    local function add(group, subgroup, entry, article)
        local title = article or entry.article or entry.name
        if seen[title] then error('Duplicate Item navigation article: ' .. title) end
        seen[title] = true
        grouped[group] = grouped[group] or {}
        grouped[group][subgroup] = grouped[group][subgroup] or {}
        table.insert(grouped[group][subgroup], {
            article=title, name=article or entry.name, worth=worth(entry)
        })
    end
    for _, entry in pairs(reference.items) do
        if not entry.retired then
            local group, subgroup = currentGroup(entry)
            add(group, subgroup, entry)
        end
    end
    for key, article in pairs(registry.removedItems) do
        local entry = archive.items[key]
        if not entry or entry.status ~= 'removed' then
            error('Missing removed Item navigation record: ' .. key)
        end
        add('removed', registry.removedTypes[entry.type] or 'utilities', entry, article)
    end
    for _, subgroups in pairs(grouped) do
        for _, entries in pairs(subgroups) do table.sort(entries, less) end
    end
    return grouped
end

local function links(cell, entries)
    local list = cell:tag('ul'):addClass('bc-release-links')
    for _, entry in ipairs(entries) do
        list:tag('li'):wikitext('[[' .. mw.text.nowiki(entry.article) .. '|' .. mw.text.nowiki(entry.name) .. ']]')
    end
end

function p.render()
    local reference = mw.loadJsonData('GameData:Reference.json')
    local archive = mw.loadJsonData('GameData:Historical items.json')
    local registry = mw.loadJsonData('GameData:Item navigation.json')
    local groups = p.groups(reference, archive, registry)
    local root = mw.html.create('table'):addClass('bc-navbox-table bc-item-nav-table')
    for _, definition in ipairs(registry.groups) do
        local group = groups[definition.key]
        if group then
            local row = root:tag('tr')
            row:tag('th'):attr('scope', 'row'):addClass('bc-item-nav-group'):wikitext(mw.text.nowiki(definition.label))
            local body = row:tag('td')
            if definition.subgroups then
                body:addClass('bc-item-nav-body')
                local nested = body:tag('table'):addClass('bc-navbox-months bc-item-nav-subgroups')
                local emitted = {}
                local function subgroup(key, label)
                    if not group[key] or #group[key] == 0 then return end
                    emitted[key] = true
                    local subrow = nested:tag('tr')
                    subrow:tag('th'):attr('scope', 'row'):wikitext(mw.text.nowiki(label))
                    links(subrow:tag('td'), group[key])
                end
                for _, sub in ipairs(definition.subgroups) do subgroup(sub.key, sub.label) end
                -- New feed classifications remain visible pending editorial labels.
                local unknown = {}
                for key in pairs(group) do if not emitted[key] then table.insert(unknown, key) end end
                table.sort(unknown)
                for _, key in ipairs(unknown) do subgroup(key, key == '' and 'Other' or key) end
            else
                links(body, group[''] or {})
            end
        end
    end
    return tostring(root)
end

return p