Jump to content

Module:GameData

From Bconomy Wiki
Revision as of 21:44, 3 November 2026 by GameReferenceSync (talk | contribs) (Synchronize public game reference data)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

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

local p = {}
local snapshot

local function data()
    if not snapshot then snapshot = mw.loadJsonData('GameData:Reference.json') end
    return snapshot
end

local function missing(key)
    return '<strong class="error">Missing game reference: ' .. mw.text.nowiki(key) ..
        '</strong>[[Category:Pages with missing game references]]'
end

local function duration(value)
    local seconds = value / 1000
    local units = {{86400, 'day'}, {3600, 'hour'}, {60, 'minute'}, {1, 'second'}}
    for _, unit in ipairs(units) do
        if seconds >= unit[1] then
            local amount = math.floor(seconds / unit[1] * 100 + 0.5) / 100
            return mw.language.getContentLanguage():formatNum(amount) .. ' ' .. unit[2] .. (amount == 1 and '' or 's')
        end
    end
    return tostring(value) .. ' milliseconds'
end

local function format(record)
    if record.format == 'markdown' then return record.wikitext or missing('markdown') end
    if record.format == 'duration' then return duration(tonumber(record.value)) end
    if record.format == 'number' then return mw.language.getContentLanguage():formatNum(record.value) end
    return mw.text.nowiki(tostring(record.value))
end

local function args(frame)
    return frame:getParent() and frame:getParent().args or frame.args
end

function p.value(frame)
    local a = args(frame)
    local key = mw.text.trim(a[1] or '')
    local record = data().values[key]
    if not record then return missing(key) end
    return format(record)
end

local function item(id)
    if not tostring(id):match('^%d+$') then return nil end
    return data().items['item' .. tostring(tonumber(id))]
end

local function linkByName(name)
    for _, entry in pairs(data().items) do
        if entry.idName == name then
            return '[[' .. mw.text.nowiki(entry.article) .. '|' .. mw.text.nowiki(entry.name) .. ']]'
        end
    end
    return mw.text.nowiki(name)
end

function p.itemValue(frame)
    local a = args(frame)
    local entry = item(a.id or a[1] or '')
    if not entry then return missing('item ' .. (a.id or a[1] or '')) end
    local field = a.field or a[2] or ''
    local value = entry[field]
    if value == nil or type(value) == 'table' then return missing(field) end
    local kind = 'text'
    if field == 'growTime' or field == 'cooldownTime' then kind = 'duration'
    elseif type(value) == 'number' then kind = 'number' end
    return format({value = value, format = kind})
end

function p.infobox(frame)
    local a = args(frame)
    local entry = item(a.id or a[1] or '')
    if not entry then return missing('item ' .. (a.id or a[1] or '')) end
    local box = mw.html.create('table'):addClass('wikitable bc-item-infobox')
    box:tag('caption'):wikitext(mw.text.nowiki(entry.name))
    local function row(label, value)
        if value == nil then return end
        local tr = box:tag('tr')
        tr:tag('th'):attr('scope', 'row'):wikitext(label)
        tr:tag('td'):wikitext(value)
    end
    local function number(value)
        return value and mw.language.getContentLanguage():formatNum(value) or nil
    end
    if entry.imageFile then
        box:tag('tr'):tag('td'):attr('colspan', 2):addClass('bc-item-picture')
            :wikitext('[[File:' .. mw.text.nowiki(entry.imageFile) .. '|128px|alt=' .. mw.text.nowiki(entry.name) .. ']]')
    end
    if entry.desc then row('Description', mw.text.nowiki(entry.desc)) end
    row('Item ID', tostring(entry.id))
    if entry.retired then row('Status', 'Retired from the current game catalog') end
    row('Base worth', number(entry.cost) .. ' BC')
    local attributes = {}
    for _, value in ipairs(entry.attributes) do attributes[value] = true end
    row('Account bound', attributes.accountBound and 'Yes' or 'No')
    row('Market listing', (attributes.accountBound or attributes.excludeFromMarket) and 'Unavailable' or 'Allowed on eligible profiles')
    row('Equippable', attributes.equippable and 'Yes' or 'No')
    row('Pet holdable', (attributes.petHoldable or entry.damageMultiplier) and 'Yes' or 'No')
    local labels = {petHoldable='Pet holdable', equippable='Equippable', excludeFromMarket='Excluded from Market',
        accountBound='Account bound', relayRestricted='Relay restricted', relayItem='Relay item'}
    local flags = {}
    for _, value in ipairs(entry.attributes) do table.insert(flags, mw.text.nowiki(labels[value] or value)) end
    if #flags > 0 then row('Attributes', table.concat(flags, ', ')) end
    if entry.growTime then row('Base crop grow time', duration(entry.growTime)) end
    if entry.cooldownTime then row('Cooldown', duration(entry.cooldownTime)) end
    row('Base damage', number(entry.damage))
    row('Damage / worth', entry.damageMultiplier and number(entry.damageMultiplier) .. '×' or nil)
    row('Food tier', number(entry.foodTier))
    row('Base pet energy', number(entry.petEnergy))
    local function boost(label, effect)
        if effect then row(label, number(effect.multiplier) .. '× for ' .. duration(effect.duration)) end
    end
    boost('Pet adventure boost', entry.petAdventureBoostEffect)
    boost('Farm plot boost', entry.farmPlotBoostEffect)
    boost('Augment', entry.augmentProperties)
    if entry.effect then
        local label = entry.effect.type == 'actionBoost' and (entry.effect.action .. ' action boost') or 'Rare loot boost'
        boost(mw.text.nowiki(label), entry.effect)
    end
    for field, label in pairs({worthPerGainedFromBurn='Base BC burned per item',
        worthPerGainedFromPetFeed='Base feeding worth per item', worthPerGainedFromHarvest='Base harvest worth per item'}) do
        row(label, number(entry[field]))
    end
    if entry.useLimit then row('Use limit', number(entry.useLimit)) end
    local recipe = {}
    for _, ingredient in ipairs(entry.recipe) do
        table.insert(recipe, linkByName(ingredient[1]) .. ' × ' .. number(ingredient[2]))
    end
    row('Crafting recipe', entry.uncraftable and 'Not craftable' or table.concat(recipe, '<br />'))
    local uses = {}
    for _, name in ipairs(entry.usedToCraft) do table.insert(uses, linkByName(name)) end
    if #uses > 0 then row('Used to craft', table.concat(uses, ', ')) end
    local sources = {}
    for _, source in ipairs(entry.lootSources) do table.insert(sources, mw.text.nowiki(source)) end
    if #sources > 0 then row('Loot pools', table.concat(sources, ', ')) end
    row('Source', '[[GameData:Reference.json|Synchronized game definitions]]; base properties before player bonuses')
    return tostring(box)
end

function p.referenceTable(frame)
    local key = args(frame)[1] or ''
    local source = data().tables[key]
    if not source then return missing(key) end
    local output = mw.html.create('table'):addClass('wikitable')
    local head = output:tag('tr')
    for _, column in ipairs(source.columns) do head:tag('th'):attr('scope', 'col'):wikitext(mw.text.nowiki(column)) end
    for _, record in ipairs(source.rows) do
        local tr = output:tag('tr')
        for _, value in ipairs(record) do
            tr:tag('td'):wikitext(format({value=value, format=type(value) == 'number' and 'number' or 'text'}))
        end
    end
    return tostring(output)
end

return p