Jump to content

Module:GameData: Difference between revisions

From Bconomy Wiki
GameReferenceSync (talk | contribs)
No edit summary
GameReferenceSync (talk | contribs)
No edit summary
 
Line 170: Line 170:
         if entry.desc then picture:tag('blockquote'):addClass('bc-item-flavor'):wikitext(mw.text.nowiki(entry.desc)) end
         if entry.desc then picture:tag('blockquote'):addClass('bc-item-flavor'):wikitext(mw.text.nowiki(entry.desc)) end
     end
     end
    if entry.retired then row('Status', 'Removed') end
     row('Type', itemType(entry))
     row('Type', itemType(entry))
     if entry.retired then row('Status', '[[:Category:Removed features|Removed from the current game]]') end
    local effects = {}
    local function boost(label, effect)
        if effect then table.insert(effects, number(effect.multiplier) .. '× ' .. label .. ' for ' .. itemDuration(effect.duration)) end
    end
    boost('Pet Adventure Speed', entry.petAdventureBoostEffect)
    boost('Farm Plot Multiplier', entry.farmPlotBoostEffect)
    boost('Tool Augment', entry.augmentProperties)
     if entry.effect then
        local actions = {work='Work', fish='Fish', hunt='Hunt', mine='Mine', explore='Explore'}
        boost(entry.effect.type == 'actionBoost' and (actions[entry.effect.action] or mw.text.nowiki(entry.effect.action)) or 'Rare Loot', entry.effect)
    end
    row('Effect', table.concat(effects, '<br />'))
     row('Worth', compact(entry.cost) .. ' BC')
     row('Worth', compact(entry.cost) .. ' BC')
     local attributes = {}
     local attributes = {}
Line 188: Line 200:
             ' (' .. number(entry.damageMultiplier) .. '× worth)' or ''))
             ' (' .. number(entry.damageMultiplier) .. '× worth)' or ''))
     end
     end
    local effects = {}
    local function boost(label, effect)
        if effect then table.insert(effects, number(effect.multiplier) .. '× ' .. label .. ' for ' .. itemDuration(effect.duration)) end
    end
    boost('Pet Adventure Speed', entry.petAdventureBoostEffect)
    boost('Farm Plot Multiplier', entry.farmPlotBoostEffect)
    boost('Tool Augment', entry.augmentProperties)
    if entry.effect then
        local actions = {work='Work', fish='Fish', hunt='Hunt', mine='Mine', explore='Explore'}
        boost(entry.effect.type == 'actionBoost' and (actions[entry.effect.action] or mw.text.nowiki(entry.effect.action)) or 'Rare Loot', entry.effect)
    end
    row('Effect', table.concat(effects, '<br />'))
     if entry.cooldownTime then row('Cooldown', itemDuration(entry.cooldownTime, true)) end
     if entry.cooldownTime then row('Cooldown', itemDuration(entry.cooldownTime, true)) end
     if entry.growTime then row('Grow time', itemDuration(entry.growTime)) end
     if entry.growTime then row('Grow Time', itemDuration(entry.growTime, true)) end
     if entry.petEnergy then
     if entry.petEnergy then
         row('Pet food', compact(entry.petEnergy) .. ' energy per item' ..
         row('Pet Food', compact(entry.petEnergy) .. ' Energy' ..
             (entry.foodTier and ' · Food tier ' .. number(entry.foodTier) or ''))
             (entry.foodTier and ' (Tier ' .. number(entry.foodTier) .. ')' or ''))
     end
     end
     local obtained = {}
     local obtained = {}
     for _, source in ipairs(presentation.lootSources or {}) do
     for _, source in ipairs(presentation.lootSources or {}) do
         local article = ({fish='Fishing', hunt='Hunting', mine='Mining', explore='Explore'})[source.source]
         local chance = ' (' .. string.format('%.2f', math.max(0.01, source.percent)) .. '%)'
        local target = source.itemId and itemLink(source.itemId) or
        if source.source == 'rare' then
            (source.emoji and icon(source.emoji) or '') ..
            table.insert(obtained, icon('🍀') .. '[[Rare Items|Rare Loot]]' .. chance)
            (article and '[[' .. article .. '|' .. mw.text.nowiki(source.label) .. ']]' or mw.text.nowiki(source.label))
            if source.itemId then table.insert(obtained, itemLink(source.itemId) .. chance) end
        local label = source.itemId and (source.source == 'rare' and 'Rare Loot / ' .. target or target .. ' Loot') or target
        else
        table.insert(obtained, label .. ' (' .. string.format('%.2f', math.max(0.01, source.percent)) .. '%)')
            local article = ({fish='Fishing', hunt='Hunting', mine='Mining', explore='Explore'})[source.source]
            local target = source.itemId and itemLink(source.itemId) or
                (source.emoji and icon(source.emoji) or '') ..
                (article and '[[' .. article .. '|' .. mw.text.nowiki(source.label) .. ']]' or mw.text.nowiki(source.label))
            table.insert(obtained, target .. chance)
        end
     end
     end
     -- Older retained records may predate presentation metadata.
     -- Older retained records may predate presentation metadata.
Line 222: Line 227:
     if entry.worthPerGainedFromPetFeed then table.insert(obtained, '[[Pets|Pet feeding]]: 1 per ' .. compact(entry.worthPerGainedFromPetFeed) .. ' BC of food fed on average, before perks') end
     if entry.worthPerGainedFromPetFeed then table.insert(obtained, '[[Pets|Pet feeding]]: 1 per ' .. compact(entry.worthPerGainedFromPetFeed) .. ' BC of food fed on average, before perks') end
     if entry.worthPerGainedFromHarvest then table.insert(obtained, '[[Farm]]: 1 per ' .. compact(entry.worthPerGainedFromHarvest) .. ' BC of unboosted crops harvested on average, before perks') end
     if entry.worthPerGainedFromHarvest then table.insert(obtained, '[[Farm]]: 1 per ' .. compact(entry.worthPerGainedFromHarvest) .. ' BC of unboosted crops harvested on average, before perks') end
     row('Obtained from', table.concat(obtained, '<br />'))
     row('Sources', table.concat(obtained, '<br />'))
     local recipe = {}
     local recipe = {}
     for _, ingredient in ipairs(entry.recipe) do
     for _, ingredient in ipairs(entry.recipe) do

Latest revision as of 23:52, 5 November 2026

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 itemDuration(value, full)
    local parts = {}
    local remaining = value
    for _, unit in ipairs({{86400000, 'd', 'day'}, {3600000, 'h', 'hour'}, {60000, 'm', 'minute'}, {1000, 's', 'second'}, {1, 'ms', 'millisecond'}}) do
        local amount = math.floor(remaining / unit[1])
        if amount > 0 then
            local label = full and (' ' .. unit[3] .. (amount == 1 and '' or 's')) or unit[2]
            table.insert(parts, tostring(amount) .. label)
            remaining = remaining - amount * unit[1]
        end
    end
    return #parts > 0 and table.concat(parts, full and ' ' or '') or (full and '0 seconds' or '0s')
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 iconFile(filename)
    if not filename then return '' end
    return '<span class="bc-item-inline-icon">[[File:' .. mw.text.nowiki(filename) .. '|20x20px|middle|link=|alt=]]</span>'
end

local function icon(emoji)
    local record = (data().icons or {})[emoji]
    return record and iconFile(record.imageFile) or ''
end

local function boostType(entry)
    local tier = (entry.presentation or {}).boostTier
    if not tier then return nil end
    local category = tier.category == 'Tool' and 'Tool Augment' or (tier.category or '') .. ' Boost'
    return 'T' .. mw.language.getContentLanguage():formatNum(tier.tier) .. ' ' .. category
end

local function itemType(entry)
    local boost = boostType(entry)
    if boost then return boost end
    if entry.effect then return 'Rare Loot Boost' end
    if entry.damage then return 'Weapon' end
    for _, attribute in ipairs(entry.attributes) do
        if attribute == 'equippable' then return 'Equipment' end
    end
    if (entry.presentation or {}).loot and entry.presentation.loot[1] then return 'Loot Container' end
    if entry.growTime then return 'Crop' end
    for _, attribute in ipairs(entry.attributes) do
        if attribute == 'relayItem' then return 'Relay Material' end
    end
    for _, source in ipairs(entry.lootSources) do
        if source == 'rare' then return 'Rare Item' end
    end
    if entry.usedToCraft[1] then return 'Crafting Ingredient' end
    if entry.useLimit then return 'Usable Item' end
    if entry.petEnergy then return 'Pet Food' end
    return 'Material'
end

local function entryLink(entry, detailed)
    local kind = detailed and boostType(entry)
    local suffix = kind and '<span class="bc-item-link-tier"> (' .. mw.text.nowiki(kind) .. ')</span>' or ''
    return iconFile(entry.imageFile) .. '[[' .. mw.text.nowiki(entry.article) .. '|' .. mw.text.nowiki(entry.name) .. ']]' .. suffix
end

local function linkByName(name, detailed)
    for _, entry in pairs(data().items) do
        if entry.idName == name then
            return entryLink(entry, detailed)
        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 == 'cooldownTime' then return itemDuration(value, true)
    elseif field == 'growTime' then return itemDuration(value)
    elseif type(value) == 'number' then kind = 'number' end
    return format({value = value, format = kind})
end

local function number(value)
    return mw.language.getContentLanguage():formatNum(value)
end

local function compact(value)
    local units = {{1e12, 'T'}, {1e9, 'B'}, {1e6, 'M'}, {1e3, 'K'}}
    for _, unit in ipairs(units) do
        if value >= unit[1] then
            local amount = math.floor(value / unit[1] * 100 + 0.5) / 100
            return tostring(mw.html.create('abbr'):attr('title', number(value))
                :wikitext(number(amount) .. unit[2]))
        end
    end
    return number(value)
end

local function itemLink(id, detailed)
    local entry = item(id)
    if not entry then return missing('item ' .. tostring(id)) end
    return entryLink(entry, detailed)
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 presentation = entry.presentation or {}
    local box = mw.html.create('table'):addClass('wikitable bc-infobox bc-item-infobox')
    box:tag('caption'):wikitext(mw.text.nowiki(entry.name))
    local function row(label, value)
        if not value or value == '' then return end
        local tr = box:tag('tr')
        tr:tag('th'):attr('scope', 'row'):wikitext(label)
        tr:tag('td'):wikitext(value)
    end
    if entry.imageFile or entry.desc then
        local picture = box:tag('tr'):tag('td'):attr('colspan', 2):addClass('bc-item-picture')
        if entry.imageFile then picture:wikitext('[[File:' .. mw.text.nowiki(entry.imageFile) .. '|128px|alt=' .. mw.text.nowiki(entry.name) .. ']]') end
        if entry.desc then picture:tag('blockquote'):addClass('bc-item-flavor'):wikitext(mw.text.nowiki(entry.desc)) end
    end
    if entry.retired then row('Status', 'Removed') end
    row('Type', itemType(entry))
    local effects = {}
    local function boost(label, effect)
        if effect then table.insert(effects, number(effect.multiplier) .. '× ' .. label .. ' for ' .. itemDuration(effect.duration)) end
    end
    boost('Pet Adventure Speed', entry.petAdventureBoostEffect)
    boost('Farm Plot Multiplier', entry.farmPlotBoostEffect)
    boost('Tool Augment', entry.augmentProperties)
    if entry.effect then
        local actions = {work='Work', fish='Fish', hunt='Hunt', mine='Mine', explore='Explore'}
        boost(entry.effect.type == 'actionBoost' and (actions[entry.effect.action] or mw.text.nowiki(entry.effect.action)) or 'Rare Loot', entry.effect)
    end
    row('Effect', table.concat(effects, '<br />'))
    row('Worth', compact(entry.cost) .. ' BC')
    local attributes = {}
    for _, value in ipairs(entry.attributes) do attributes[value] = true end
    local flags = {}
    if entry.useLimit then table.insert(flags, 'Usable') end
    if attributes.equippable then table.insert(flags, '[[Equipment|Equippable]]') end
    if attributes.petHoldable or entry.damageMultiplier then table.insert(flags, '[[Pets|Pet holdable]]') end
    if attributes.accountBound then table.insert(flags, 'Account bound') end
    if attributes.relayItem then table.insert(flags, '[[Relay Network|Relay item]]') end
    if attributes.relayRestricted then table.insert(flags, 'Cannot be [[Refinery|refined]]') end
    if attributes.excludeFromMarket and not attributes.accountBound then table.insert(flags, 'Cannot be listed on the [[Market]]') end
    row('Properties', table.concat(flags, ' · '))
    if entry.damage then
        row('Damage', compact(entry.damage) .. (entry.damageMultiplier and
            ' (' .. number(entry.damageMultiplier) .. '× worth)' or ''))
    end
    if entry.cooldownTime then row('Cooldown', itemDuration(entry.cooldownTime, true)) end
    if entry.growTime then row('Grow Time', itemDuration(entry.growTime, true)) end
    if entry.petEnergy then
        row('Pet Food', compact(entry.petEnergy) .. ' Energy' ..
            (entry.foodTier and ' (Tier ' .. number(entry.foodTier) .. ')' or ''))
    end
    local obtained = {}
    for _, source in ipairs(presentation.lootSources or {}) do
        local chance = ' (' .. string.format('%.2f', math.max(0.01, source.percent)) .. '%)'
        if source.source == 'rare' then
            table.insert(obtained, icon('🍀') .. '[[Rare Items|Rare Loot]]' .. chance)
            if source.itemId then table.insert(obtained, itemLink(source.itemId) .. chance) end
        else
            local article = ({fish='Fishing', hunt='Hunting', mine='Mining', explore='Explore'})[source.source]
            local target = source.itemId and itemLink(source.itemId) or
                (source.emoji and icon(source.emoji) or '') ..
                (article and '[[' .. article .. '|' .. mw.text.nowiki(source.label) .. ']]' or mw.text.nowiki(source.label))
            table.insert(obtained, target .. chance)
        end
    end
    -- Older retained records may predate presentation metadata.
    if not presentation.lootSources then
        for _, source in ipairs(entry.lootSources) do table.insert(obtained, linkByName(source)) end
    end
    if entry.worthPerGainedFromBurn then table.insert(obtained, '[[Burn]]: 1 per ' .. compact(entry.worthPerGainedFromBurn) .. ' BC burned on average, before perks') end
    if entry.worthPerGainedFromPetFeed then table.insert(obtained, '[[Pets|Pet feeding]]: 1 per ' .. compact(entry.worthPerGainedFromPetFeed) .. ' BC of food fed on average, before perks') end
    if entry.worthPerGainedFromHarvest then table.insert(obtained, '[[Farm]]: 1 per ' .. compact(entry.worthPerGainedFromHarvest) .. ' BC of unboosted crops harvested on average, before perks') end
    row('Sources', table.concat(obtained, '<br />'))
    local recipe = {}
    for _, ingredient in ipairs(entry.recipe) do
        table.insert(recipe, linkByName(ingredient[1]) .. ' × ' .. compact(ingredient[2]))
    end
    if #recipe > 0 then row('Recipe', table.concat(recipe, '<br />')) end
    row('Internal ID', '<code>' .. tostring(entry.id) .. '</code>')
    return tostring(box)
end

function p.itemSections(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 output = {}
    local presentation = entry.presentation or {}
    local usages = presentation.uses or {}
    local categories = {tools='Tools', farm='Farm', pets='Pets', relay='Relay Network'}
    local groups = {}
    local crafting = {}
    for _, name in ipairs(entry.usedToCraft) do table.insert(crafting, linkByName(name, true)) end
    table.insert(groups, {title='Used to craft', entries=crafting})
    for _, group in ipairs({{'upgrades', 'Used to upgrade'}, {'purchases', 'Used to purchase'}, {'research', 'Used to research'}}) do
        local lines = {}
        for _, usage in ipairs(usages[group[1]] or {}) do
            local suffix = ''
            if usage.threshold then
                local prefix = ({bay='#', count='#', tier='Tier ', level='Lv. '})[usage.thresholdKind] or ''
                suffix = ' (' .. prefix .. number(usage.threshold) .. '+)'
            end
            table.insert(lines, (usage.emoji and icon(usage.emoji) or '') .. '[[' .. categories[usage.category] .. '|' .. mw.text.nowiki(usage.label) .. ']]' .. suffix)
        end
        table.insert(groups, {title=group[2], entries=lines})
    end
    for _, group in ipairs(groups) do
        if #group.entries > 0 then
            if #output == 0 then table.insert(output, '\n== Item uses ==\n') end
            table.insert(output, '\n=== ' .. group.title .. ' ===\n* ' .. table.concat(group.entries, '\n* ') .. '\n')
        end
    end
    if presentation.loot and presentation.loot[1] then
        table.insert(output, '\n== Loot ==\nThe following chances apply to each draw from this item’s loot pool.\n')
        local loot = mw.html.create('table'):addClass('wikitable bc-item-loot')
        local header = loot:tag('tr')
        for _, title in ipairs({'Item', 'Chance', 'Odds'}) do header:tag('th'):attr('scope', 'col'):wikitext(title) end
        for _, reward in ipairs(presentation.loot) do
            local tr = loot:tag('tr')
            tr:tag('td'):wikitext(itemLink(reward.itemId, true))
            tr:tag('td'):wikitext(string.format('%.2f', reward.probability * 100) .. '%')
            tr:tag('td'):wikitext(mw.text.nowiki(reward.odds))
        end
        table.insert(output, tostring(loot) .. '\n')
    end
    return table.concat(output)
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

local function special(frame)
    local a = args(frame)
    if a.archive and a.archive ~= '' then
        return mw.loadJsonData('GameData:Historical specials.json')[mw.text.trim(a.archive)]
    end
    return (data().specials or {})[mw.text.trim(a.type or a[1] or '')]
end

function p.specialInfobox(frame)
    local entry = special(frame)
    if not entry then return missing('Special ' .. (args(frame).type or args(frame).archive or args(frame)[1] or '')) end
    local box = mw.html.create('table'):addClass('wikitable bc-infobox bc-special-infobox')
    box:tag('caption'):wikitext(mw.text.nowiki(entry.name))
    local function row(label, value)
        if not value or value == '' then return end
        local tr = box:tag('tr')
        tr:tag('th'):attr('scope', 'row'):wikitext(label)
        tr:tag('td'):wikitext(value)
    end
    local description = entry.usable ~= false and entry.description or nil
    if entry.imageFile or description then
        local picture = box:tag('tr'):tag('td'):attr('colspan', 2):addClass('bc-item-picture')
        if entry.imageFile then picture:wikitext('[[File:' .. mw.text.nowiki(entry.imageFile) .. '|128px|alt=' .. mw.text.nowiki(entry.name) .. ']]') end
        if description and description ~= '' then picture:tag('blockquote'):addClass('bc-item-flavor'):wikitext(mw.text.nowiki(description)) end
    end
    row('Type', '[[Specials|Special]]')
    if entry.retired then row('Status', 'Removed') end
    if entry.cooldown then row('Cooldown', itemDuration(entry.cooldown, true)) end
    if entry.multiUse ~= nil then row('Bulk use?', entry.multiUse and 'Yes' or 'No') end
    local variants = {}
    local labels = {action='Action', species='Species', skin='Skin', aura='Aura'}
    for _, key in ipairs({'action', 'species', 'skin', 'aura'}) do
        if entry.variants and entry.variants[key] then table.insert(variants, labels[key]) end
    end
    row('Attributes', table.concat(variants, ', '))
    local minimum, maximum
    for _, offer in ipairs(entry.storeOffers or {}) do
        minimum = minimum and math.min(minimum, offer.spCost) or offer.spCost
        maximum = maximum and math.max(maximum, offer.spCost) or offer.spCost
    end
    if minimum then row('Cost', compact(minimum) .. (minimum ~= maximum and ('–' .. compact(maximum)) or '') .. ' SP') end
    row('Internal ID', '<code>' .. mw.text.nowiki(entry.type) .. '</code>')
    return tostring(box)
end

function p.specialsTable()
    local entries = {}
    for _, entry in pairs(data().specials or {}) do
        if not entry.retired then table.insert(entries, entry) end
    end
    table.sort(entries, function(a, b) return a.name < b.name end)
    local output = mw.html.create('table'):addClass('wikitable')
    local head = output:tag('tr')
    head:tag('th'):attr('scope', 'col'):wikitext('Special')
    head:tag('th'):attr('scope', 'col'):wikitext('Effect')
    for _, entry in ipairs(entries) do
        local row = output:tag('tr')
        row:tag('td'):wikitext(iconFile(entry.imageFile) .. '[[' .. mw.text.nowiki(entry.article) .. '|' .. mw.text.nowiki(entry.name) .. ']]')
        row:tag('td'):wikitext(mw.text.nowiki(entry.description))
    end
    return tostring(output)
end

return p