Module:InfoboxResearch: Difference between revisions

From Evospace
Jump to navigation Jump to search
Research display module and templates
Research display module and templates
 
Line 50: Line 50:
     end
     end
     return splitCamelCase(name)
     return splitCamelCase(name)
end
local function requireLink(req)
    local text = researchLink(req.name)
    if req.level then
        text = text .. ' level ' .. tostring(req.level)
    end
    return text
end
end


Line 139: Line 147:
         local links = {}
         local links = {}
         for _, req in ipairs(requires) do
         for _, req in ipairs(requires) do
             table.insert(links, researchLink(req))
             table.insert(links, requireLink(req))
         end
         end
         table.insert(out, '|-')
         table.insert(out, '|-')
Line 198: Line 206:
         local links = {}
         local links = {}
         for _, req in ipairs(level.requires or {}) do
         for _, req in ipairs(level.requires or {}) do
             table.insert(links, researchLink(req))
             table.insert(links, requireLink(req))
         end
         end
         table.insert(out, '|-')
         table.insert(out, '|-')

Latest revision as of 12:52, 10 September 2026

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

-- Module:InfoboxResearch
-- Renders the research pages from Module:Data/Research/*, which
-- wiki/generate_researches.py writes out of Content/Generated/Researches.
local p = {}

local DATA = 'Module:Data/Research/'

local function load(name)
    local ok, data = pcall(mw.loadData, DATA .. name)
    if ok and type(data) == 'table' then
        return data
    end
    return nil
end

-- mw.loadData hands back a proxy table: ipairs and pairs work on it, the # operator does not.
local function count(tbl)
    if not tbl then
        return 0
    end
    local n = 0
    for _ in ipairs(tbl) do
        n = n + 1
    end
    return n
end

local function splitCamelCase(str)
    str = str:gsub('_', ' ')
    str = str:gsub('(%l)(%u)', '%1 %2')
    str = str:gsub('(%a)(%d)', '%1 %2')
    str = str:gsub('(%d)(%a)', '%1 %2')
    return str
end

local function thousands(n)
    local digits = string.format('%d', n)
    local out = digits:reverse():gsub('(%d%d%d)', '%1 '):reverse()
    return (out:gsub('^%s+', ''))
end

local function pageTitle(row)
    return row.title or (row.label .. ' (research)')
end

local function researchLink(name)
    local data = load(name)
    if data then
        return string.format('[[%s|%s]]', pageTitle(data), data.label)
    end
    return splitCamelCase(name)
end

local function requireLink(req)
    local text = researchLink(req.name)
    if req.level then
        text = text .. ' level ' .. tostring(req.level)
    end
    return text
end

local function itemLink(item)
    return string.format('[[File:T_%s.png|22px|link=%s]] [[%s]]',
        item, splitCamelCase(item), splitCamelCase(item))
end

local function unlockCell(unlock)
    if unlock.item then
        return itemLink(unlock.item)
    end
    return splitCamelCase(unlock.recipe)
end

local function icon(data, size)
    if not data.icon then
        return ''
    end
    return string.format('[[File:T_%s.png|%dpx]]', data.icon, size)
end

local function effectLine(data)
    if data.kind == 'modifier' then
        return string.format('+%d%% %s per level', data.bonus or 0,
            splitCamelCase(data.modifier or ''))
    end
    if data.kind == 'bonusinventory' then
        return string.format('+%d inventory slots per level', data.slots or 0)
    end
    return nil
end

local function countUnlocks(data)
    local total = 0
    for _, level in ipairs(data.levels or {}) do
        total = total + count(level.unlocks)
    end
    return total
end

function p.show(frame)
    local name = frame.args[1]
    if not name or name == '' then
        return '❌ No research name provided'
    end
    local data = load(name)
    if not data then
        return '❌ No data module for ' .. name
    end

    local out = {}
    table.insert(out, '{| class="infobox"')
    table.insert(out, '|-')
    table.insert(out, '| colspan="2" style="text-align:center;" | ' ..
        icon(data, 64) .. " '''" .. data.label .. "'''")

    table.insert(out, '|-')
    table.insert(out, '! Science')
    table.insert(out, '| ' .. thousands(data.levels[1].science))

    table.insert(out, '|-')
    table.insert(out, '! Tier')
    table.insert(out, '| ' .. tostring(data.tier))

    local levelCount = count(data.levels)
    if levelCount > 1 then
        table.insert(out, '|-')
        table.insert(out, '! Levels')
        table.insert(out, '| ' .. tostring(levelCount))
    end

    local effect = effectLine(data)
    if effect then
        table.insert(out, '|-')
        table.insert(out, '! Effect')
        table.insert(out, '| ' .. effect)
    end

    local unlocks = countUnlocks(data)
    if unlocks > 0 then
        table.insert(out, '|-')
        table.insert(out, '! Recipes')
        table.insert(out, '| ' .. tostring(unlocks))
    end

    local requires = data.levels[1].requires
    if count(requires) > 0 then
        local links = {}
        for _, req in ipairs(requires) do
            table.insert(links, requireLink(req))
        end
        table.insert(out, '|-')
        table.insert(out, '! Requires')
        table.insert(out, '| ' .. table.concat(links, '<br />'))
    end

    table.insert(out, '|-')
    table.insert(out, '! Internal name')
    table.insert(out, '| <code>' .. data.name .. '</code>')
    table.insert(out, '|}')

    return table.concat(out, '\n')
end

local function renderUnlocks(data)
    local out = {}
    local byMachine = {}
    local order = {}
    for _, level in ipairs(data.levels) do
        for _, unlock in ipairs(level.unlocks or {}) do
            if not byMachine[unlock.machine] then
                byMachine[unlock.machine] = {}
                table.insert(order, unlock.machine)
            end
            table.insert(byMachine[unlock.machine], unlock)
        end
    end
    if #order == 0 then
        return nil
    end

    table.sort(order)
    table.insert(out, '{| class="wikitable"')
    table.insert(out, '! Made in !! Recipe')
    for _, machine in ipairs(order) do
        local cells = {}
        for _, unlock in ipairs(byMachine[machine]) do
            table.insert(cells, unlockCell(unlock))
        end
        local made = machine == 'Hand' and 'Hand crafting'
            or string.format('[[%s]]', splitCamelCase(machine))
        table.insert(out, '|-')
        table.insert(out, '| ' .. made .. ' || ' .. table.concat(cells, ' · '))
    end
    table.insert(out, '|}')
    return table.concat(out, '\n')
end

local function renderLevels(data)
    if count(data.levels) < 2 then
        return nil
    end
    local out = {}
    table.insert(out, '{| class="wikitable"')
    table.insert(out, '! Level !! Science !! Also requires')
    for _, level in ipairs(data.levels) do
        local links = {}
        for _, req in ipairs(level.requires or {}) do
            table.insert(links, requireLink(req))
        end
        table.insert(out, '|-')
        table.insert(out, string.format('| %d || %s || %s',
            level.level,
            thousands(level.science),
            #links > 0 and table.concat(links, ', ') or '—'))
    end
    table.insert(out, '|}')
    return table.concat(out, '\n')
end

function p.data(frame)
    local name = frame.args[1]
    if not name or name == '' then
        return '❌ No research name provided'
    end
    local data = load(name)
    if not data then
        return '❌ No data module for ' .. name
    end

    local out = {}

    local unlocks = renderUnlocks(data)
    if unlocks then
        table.insert(out, '== Unlocks ==')
        table.insert(out, unlocks)
    end

    local effect = effectLine(data)
    if effect then
        table.insert(out, '== Effect ==')
        table.insert(out, effect .. ', over ' .. tostring(count(data.levels)) .. ' levels.')
    end

    local levels = renderLevels(data)
    if levels then
        table.insert(out, '== Levels ==')
        table.insert(out, 'The levels past the first are hidden until the one before it is done.')
        table.insert(out, levels)
    end

    if count(data.leads_to) > 0 then
        local links = {}
        for _, follower in ipairs(data.leads_to) do
            table.insert(links, researchLink(follower))
        end
        table.sort(links)
        table.insert(out, '== Leads to ==')
        table.insert(out, table.concat(links, ' · '))
    end

    return table.concat(out, '\n\n')
end

function p.index(frame)
    local ok, index = pcall(mw.loadData, DATA .. 'Index')
    if not ok or type(index) ~= 'table' then
        return '❌ No research index'
    end

    local byTier = {}
    local tiers = {}
    for _, row in ipairs(index.researches or {}) do
        if not byTier[row.tier] then
            byTier[row.tier] = {}
            table.insert(tiers, row.tier)
        end
        table.insert(byTier[row.tier], row)
    end
    table.sort(tiers)

    local out = {}
    for _, tier in ipairs(tiers) do
        table.insert(out, '=== Tier ' .. tostring(tier) .. ' ===')
        table.insert(out, '{| class="wikitable sortable"')
        table.insert(out, '! !! Research !! Science !! Levels !! Effect')
        for _, row in ipairs(byTier[tier]) do
            local img = row.icon and string.format('[[File:T_%s.png|22px]]', row.icon) or ''
            local kind = row.kind == 'modifier' and 'Modifier'
                or row.kind == 'bonusinventory' and 'Inventory slots'
                or 'Recipes'
            table.insert(out, '|-')
            table.insert(out, string.format('| %s || [[%s|%s]] || %s || %s || %s',
                img, pageTitle(row), row.label, thousands(row.science),
                tostring(row.levels), kind))
        end
        table.insert(out, '|}')
    end
    return table.concat(out, '\n')
end

return p