Module:InfoboxResearch: Difference between revisions

From Evospace
Jump to navigation Jump to search
Research display module and templates
 
Research display module and templates
 
(One intermediate revision by the same user not shown)
Line 12: Line 12:
     end
     end
     return nil
     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
end


Line 38: 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 73: Line 93:
     local total = 0
     local total = 0
     for _, level in ipairs(data.levels or {}) do
     for _, level in ipairs(data.levels or {}) do
         total = total + #(level.unlocks or {})
         total = total + count(level.unlocks)
     end
     end
     return total
     return total
Line 102: Line 122:
     table.insert(out, '| ' .. tostring(data.tier))
     table.insert(out, '| ' .. tostring(data.tier))


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


Line 123: Line 144:


     local requires = data.levels[1].requires
     local requires = data.levels[1].requires
     if requires and #requires > 0 then
     if count(requires) > 0 then
         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 176: Line 197:


local function renderLevels(data)
local function renderLevels(data)
     if #data.levels < 2 then
     if count(data.levels) < 2 then
         return nil
         return nil
     end
     end
Line 185: 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, '|-')
Line 218: Line 239:
     if effect then
     if effect then
         table.insert(out, '== Effect ==')
         table.insert(out, '== Effect ==')
         table.insert(out, effect .. ', over ' .. tostring(#data.levels) .. ' levels.')
         table.insert(out, effect .. ', over ' .. tostring(count(data.levels)) .. ' levels.')
     end
     end


Line 228: Line 249:
     end
     end


     if data.leads_to and #data.leads_to > 0 then
     if count(data.leads_to) > 0 then
         local links = {}
         local links = {}
         for _, follower in ipairs(data.leads_to) do
         for _, follower in ipairs(data.leads_to) do

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