打开/关闭菜单
打开/关闭外观设置菜单
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。

本站正在进行早期测试,目前仍存在许多内容的缺失。

模块:ItemOverview

来自星砂岛百科

ItemOverview 提供物品页通用的“条目速览”区块,把 `获取 / 用途 / 赠礼 / 关联条目或特性` 汇总成读者更容易扫读的卡片。

常见调用方式:

  • {{#invoke:ItemOverview|renderOverview|面粉}}
  • 更推荐通过 {{ItemOverview}} 包装模板调用,让标题和变量处理保持统一。

local common = require('Module:Common')
local css = require('Module:CSS')
local item_common = require('Module:ItemCommon')

local p = {}

local shop_by_item_cache
local gift_items_cache
local gift_redirects_cache
local gift_item_index_by_id
local machine_by_item_cache
local domain_record_caches

local POSITIVE_GIFT_BUCKETS = {
    { key = 'r', label = '共鸣' },
    { key = 's', label = '特殊' },
    { key = 'v', label = '最爱' },
    { key = 'k', label = '喜欢' },
}

local RESPONSE_GIFT_BUCKETS = {
    { key = 'n', label = '一般' },
    { key = 'd', label = '不喜欢' },
    { key = 'h', label = '讨厌' },
}

local DOMAIN_RECORD_CONFIGS = {
    { data_page = '数据:Resource/resource_index.json', mapping_page = '数据:Resource/resource_mapping.json' },
    { data_page = '数据:Processed/processed_index.json', mapping_page = '数据:Processed/processed_mapping.json' },
    { data_page = '数据:Food/food_index.json', mapping_page = '数据:Food/food_mapping.json' },
    { data_page = '数据:Seed/seed_index.json', mapping_page = '数据:Seed/seed_mapping.json' },
    { data_page = '数据:Tool/tool_index.json', mapping_page = '数据:Tool/tool_mapping.json' },
    { data_page = '数据:Crop/crop_index.json', mapping_page = '数据:Crop/crop_mapping.json' },
}

local function normalize_key(value)
    return common.normalizeKey(value)
end

local function count_array(values)
    if type(values) ~= 'table' then
        return 0
    end

    local count = 0
    for _, _ in ipairs(values) do
        count = count + 1
    end
    return count
end

local function trim_text(value)
    return common.trim(common.toText(value))
end

local function non_empty_parts(parts)
    local out = {}
    for _, part in ipairs(parts or {}) do
        local text = trim_text(part)
        if text ~= '' then
            out[#out + 1] = text
        end
    end
    return out
end

local function join_parts(parts)
    local filtered = non_empty_parts(parts)
    if #filtered == 0 then
        return ''
    end
    return table.concat(filtered, ' · ')
end

local function has_items(values)
    return count_array(values) > 0
end

local function count_bucket_members(bucket)
    if type(bucket) ~= 'table' then
        return 0
    end

    local seen = {}
    local count = 0

    local function append(values)
        if type(values) ~= 'table' then
            return
        end
        for _, value in ipairs(values) do
            local numeric = tonumber(value)
            if numeric ~= nil and not seen[numeric] then
                seen[numeric] = true
                count = count + 1
            end
        end
    end

    append(bucket.p)
    append(bucket.g)
    return count
end

local function count_unique_bucket_group(source, keys)
    if type(source) ~= 'table' then
        return 0
    end

    local seen = {}
    local count = 0
    for _, key in ipairs(keys) do
        local bucket = source[key]
        if type(bucket) == 'table' then
            for _, list_key in ipairs({ 'p', 'g' }) do
                local values = bucket[list_key]
                if type(values) == 'table' then
                    for _, value in ipairs(values) do
                        local numeric = tonumber(value)
                        if numeric ~= nil and not seen[numeric] then
                            seen[numeric] = true
                            count = count + 1
                        end
                    end
                end
            end
        end
    end
    return count
end

local function load_shop_data()
    if shop_by_item_cache then
        return
    end

    local raw = common.loadJsonData('数据:Shop/shop_by_item.json') or {}
    if type(raw) == 'table' and type(raw.records) == 'table' then
        shop_by_item_cache = raw.records
    else
        shop_by_item_cache = raw or {}
    end
end

local function get_shop_summary(item_id)
    load_shop_data()
    local entries = shop_by_item_cache[normalize_key(item_id)] or {}
    local shops_seen = {}
    local entry_count = 0

    for _, entry in ipairs(entries) do
        entry_count = entry_count + 1
        if type(entry) == 'table' then
            local shop_id = trim_text(entry.sid or entry.s)
            if shop_id ~= '' then
                shops_seen[normalize_key(shop_id)] = true
            end
        end
    end

    local shop_count = 0
    for _, _ in pairs(shops_seen) do
        shop_count = shop_count + 1
    end

    return {
        entry_count = entry_count,
        shop_count = shop_count,
    }
end

local function load_gift_data()
    if gift_items_cache then
        return
    end

    local raw = common.loadJsonData('数据:Gifting/gift_preferences_by_item.json') or {}
    gift_items_cache = raw.items or {}
    gift_redirects_cache = raw.item_redirects or {}

    local registry = common.loadJsonData('数据:Gifting/gift_id_registry.json') or {}
    local registry_items = registry.items or {}
    gift_item_index_by_id = {}
    for idx, value in ipairs(registry_items) do
        local normalized = normalize_key(value)
        if normalized ~= '' then
            gift_item_index_by_id[normalized] = idx - 1
        end
    end
end

local function resolve_gift_index(index)
    local key = tostring(index)
    local numeric = tonumber(index)

    if gift_items_cache[key] then
        return key
    end
    if numeric ~= nil and gift_items_cache[numeric] then
        return numeric
    end

    local redirected = gift_redirects_cache[key]
    if redirected == nil and numeric ~= nil then
        redirected = gift_redirects_cache[numeric]
    end
    if redirected == nil then
        return nil
    end

    local redirected_key = tostring(redirected)
    local redirected_numeric = tonumber(redirected)
    if gift_items_cache[redirected_key] then
        return redirected_key
    end
    if redirected_numeric ~= nil and gift_items_cache[redirected_numeric] then
        return redirected_numeric
    end

    return nil
end

local function get_gift_summary(item_id)
    load_gift_data()
    local item_index = gift_item_index_by_id[normalize_key(item_id)]
    if item_index == nil then
        return {
            value = '不可送礼',
            meta = '',
        }
    end

    local resolved_index = resolve_gift_index(item_index)
    local record = resolved_index ~= nil and gift_items_cache[resolved_index] or nil
    local summary = type(record) == 'table' and record.s or nil
    if type(summary) ~= 'table' then
        return {
            value = '不可送礼',
            meta = '',
        }
    end

    local positive_total = count_unique_bucket_group(summary, { 'r', 's', 'v', 'k' })
    if positive_total > 0 then
        local meta_parts = {}
        for _, bucket in ipairs(POSITIVE_GIFT_BUCKETS) do
            local count = count_bucket_members(summary[bucket.key])
            if count > 0 then
                meta_parts[#meta_parts + 1] = bucket.label .. ' ' .. tostring(count)
            end
        end
        return {
            value = tostring(positive_total) .. ' 位对象偏好',
            meta = join_parts(meta_parts),
        }
    end

    local response_total = count_unique_bucket_group(summary, { 'n', 'd', 'h' })
    if response_total > 0 then
        local meta_parts = {}
        for _, bucket in ipairs(RESPONSE_GIFT_BUCKETS) do
            local count = count_bucket_members(summary[bucket.key])
            if count > 0 then
                meta_parts[#meta_parts + 1] = bucket.label .. ' ' .. tostring(count)
            end
        end
        return {
            value = '可送礼',
            meta = join_parts(meta_parts),
        }
    end

    return {
        value = '不可送礼',
        meta = '',
    }
end

local function load_machine_data()
    if machine_by_item_cache then
        return
    end

    local raw = common.loadJsonData('数据:Machine/machine_recipes.json') or {}
    machine_by_item_cache = raw.by_item or {}
end

local function load_domain_record_caches()
    if domain_record_caches then
        return
    end

    domain_record_caches = {}
    for _, config in ipairs(DOMAIN_RECORD_CONFIGS) do
        local data, mapping = item_common.loadDomainData(config.data_page, config.mapping_page)
        domain_record_caches[#domain_record_caches + 1] = {
            data = data,
            mapping = mapping,
        }
    end
end

local function append_unique_value(values, seen, value)
    local text = trim_text(value)
    local normalized = normalize_key(text)
    if text == '' or normalized == '' or seen[normalized] then
        return
    end
    seen[normalized] = true
    values[#values + 1] = text
end

local function find_detail_record(key, item_id)
    load_domain_record_caches()

    local candidates = {}
    local seen = {}
    append_unique_value(candidates, seen, key)
    append_unique_value(candidates, seen, item_id)

    for _, cache in ipairs(domain_record_caches) do
        for _, candidate in ipairs(candidates) do
            local record = item_common.findRecord(cache.data, cache.mapping, candidate)
            if type(record) == 'table' then
                return record
            end
        end
    end

    return nil
end

local function get_recipe_summary(item_id)
    load_machine_data()
    local record = machine_by_item_cache[normalize_key(item_id)]
    if type(record) ~= 'table' then
        return {
            production_count = 0,
            usage_count = 0,
        }
    end

    return {
        production_count = count_array(record.p),
        usage_count = count_array(record.i),
    }
end

local function collect_machine_names(record)
    local out = {}
    local seen = {}

    local function append(values)
        if type(values) ~= 'table' then
            return
        end
        for _, value in ipairs(values) do
            local name = trim_text(value)
            local normalized = normalize_key(name)
            if name ~= '' and normalized ~= '' and not seen[normalized] then
                seen[normalized] = true
                out[#out + 1] = name
            end
        end
    end

    append(record.production_machines)
    append(record.process_machines)
    return out
end

local function build_source_card(record, shop_summary)
    local value = trim_text(item_common.getDisplayField(record, 'origin'))
    local locked_origin = trim_text(item_common.getDisplayField(record, 'locked_origin'))
    local meta_parts = {}

    if shop_summary.shop_count > 0 then
        meta_parts[#meta_parts + 1] = '共 ' .. tostring(shop_summary.shop_count) .. ' 处商店出售'
    end
    if locked_origin ~= '' then
        meta_parts[#meta_parts + 1] = '解锁:' .. locked_origin
    end

    if value == '' then
        if shop_summary.shop_count > 0 then
            value = '商店出售'
        elseif locked_origin ~= '' then
            value = '需要解锁'
        else
            return nil
        end
    end

    return {
        label = '获取',
        value = value,
        meta = join_parts(meta_parts),
    }
end

local function build_usage_card(record, recipe_summary)
    local value = trim_text(item_common.getDisplayField(record, 'use_description'))
    local meta_parts = {}

    if recipe_summary.production_count > 0 then
        meta_parts[#meta_parts + 1] = '可由 ' .. tostring(recipe_summary.production_count) .. ' 种配方获得'
    end
    if recipe_summary.usage_count > 0 then
        meta_parts[#meta_parts + 1] = '用于 ' .. tostring(recipe_summary.usage_count) .. ' 个配方'
    end

    local machine_names = collect_machine_names(record)
    if has_items(machine_names) then
        meta_parts[#meta_parts + 1] = '相关设备:' .. item_common.machineLinks(machine_names)
    end

    if value == '' then
        if recipe_summary.usage_count > 0 then
            value = '参与制作'
        elseif recipe_summary.production_count > 0 then
            value = '可被制作'
        elseif has_items(machine_names) then
            value = '设备相关'
        else
            return nil
        end
    end

    return {
        label = '用途',
        value = value,
        meta = join_parts(meta_parts),
    }
end

local function build_gift_card(item_id)
    local summary = get_gift_summary(item_id)
    return {
        label = '赠礼',
        value = summary.value,
        meta = summary.meta,
    }
end

local function build_feature_card(record)
    local crop_link = trim_text(item_common.getDisplayField(record, 'crop_item_link'))
    if crop_link ~= '' then
        return {
            label = '关联条目',
            value = crop_link,
            meta = '对应作物',
        }
    end

    local seed_link = trim_text(item_common.getDisplayField(record, 'seed_item_link'))
    if seed_link ~= '' then
        return {
            label = '关联条目',
            value = seed_link,
            meta = '对应种子',
        }
    end

    local tool_usage = trim_text(item_common.getDisplayField(record, 'tool_usage'))
    local weapon_type = trim_text(item_common.getDisplayField(record, 'hunting_weapon_type'))
    if tool_usage ~= '' or weapon_type ~= '' then
        local value = tool_usage ~= '' and tool_usage or weapon_type
        local meta = ''
        if tool_usage ~= '' and weapon_type ~= '' then
            meta = '发射类型:' .. weapon_type
        end
        return {
            label = tool_usage ~= '' and '工具用途' or '武器类型',
            value = value,
            meta = meta,
        }
    end

    local pet_target = trim_text(item_common.getDisplayField(record, 'pet_target_tag'))
    if pet_target ~= '' then
        return {
            label = '适用对象',
            value = pet_target,
            meta = '',
        }
    end

    local placement = trim_text(item_common.getDisplayField(record, 'placement_template'))
    if placement ~= '' then
        return {
            label = '摆放类型',
            value = placement,
            meta = '',
        }
    end

    local process_formula_origin = trim_text(item_common.getDisplayField(record, 'process_formula_origin'))
    if process_formula_origin ~= '' then
        return {
            label = '适用设备',
            value = process_formula_origin,
            meta = '',
        }
    end

    return nil
end

local function render_card(grid, card)
    if type(card) ~= 'table' then
        return false
    end

    local value = trim_text(card.value)
    if value == '' then
        return false
    end

    local node = grid:tag('div'):addClass('item-overview-card')
    node:tag('div'):addClass('item-overview-label'):wikitext(trim_text(card.label))
    node:tag('div'):addClass('item-overview-value'):wikitext(value)

    local meta = trim_text(card.meta)
    if meta ~= '' then
        node:tag('div'):addClass('item-overview-meta'):wikitext(meta)
    end

    return true
end

function p.renderOverview(frame)
    local key = common.getArg(frame, 1, '')
    local identity_record = item_common.findItemRecord(key)
    if type(identity_record) ~= 'table' then
        return ''
    end

    local item_id = item_common.resolveItemId(identity_record, key)
    if item_id == '' then
        return ''
    end

    local record = find_detail_record(key, item_id) or identity_record
    local cards = {}

    local function append_card(card)
        if type(card) == 'table' then
            cards[#cards + 1] = card
        end
    end

    append_card(build_source_card(record, get_shop_summary(item_id)))
    append_card(build_usage_card(record, get_recipe_summary(item_id)))
    append_card(build_gift_card(item_id))
    append_card(build_feature_card(record))

    local root = mw.html.create('div'):addClass('item-overview')
    local grid = root:tag('div'):addClass('item-overview-grid')
    local rendered_count = 0

    for _, card in ipairs(cards) do
        if render_card(grid, card) then
            rendered_count = rendered_count + 1
        end
    end

    if rendered_count == 0 then
        return ''
    end

    return (css.quickCall('ItemOverview') or '') .. tostring(root)
end

return p