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

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

模块:Machine:修订间差异

来自星砂岛百科
Sizau-bot留言 | 贡献
同步更新
Sizau-bot留言 | 贡献
同步更新
第29行: 第29行:
     prev_machine = 'pm',
     prev_machine = 'pm',
     next_machine = 'nm',
     next_machine = 'nm',
}
local RECIPE_FIELD_DEFAULTS = {
    result = 'r',
    result_count = 'rc',
    materials = 'm',
    time = 't',
    machine = 'mc',
    cooking_method = 'cm',
}
}


local data_cache
local data_cache
local mapping_cache
local mapping_cache
local recipe_cache
local recipe_list
local item_recipe_cache
local field_map = RECIPE_FIELD_DEFAULTS


local function is_empty(value)
local function is_empty(value)
第42行: 第55行:
     end
     end
     return true
     return true
end
local function count_array(arr)
    if type(arr) ~= 'table' then
        return 0
    end
    local count = 0
    for _ in pairs(arr) do
        count = count + 1
    end
    return count
end
end


第71行: 第96行:
         return nil
         return nil
     end
     end
     local short_key = get_short_field(field)
     local short_key = get_short_field(field)
     if record[short_key] ~= nil then
     if record[short_key] ~= nil then
第102行: 第128行:
         return tostring(math.floor(seconds)) .. '秒'
         return tostring(math.floor(seconds)) .. '秒'
     end
     end
     local minutes = math.floor(seconds / 60)
     local minutes = math.floor(seconds / 60)
     local remain = math.floor(seconds % 60)
     local remain = math.floor(seconds % 60)
第114行: 第141行:
         return ''
         return ''
     end
     end
    local item_ids = {}
    for item_id in pairs(materials) do
        item_ids[#item_ids + 1] = item_id
    end
    table.sort(item_ids)


     local parts = {}
     local parts = {}
     for item_id, count in pairs(materials) do
     for _, item_id in ipairs(item_ids) do
         parts[#parts + 1] = frame:expandTemplate{
         parts[#parts + 1] = frame:expandTemplate{
             title = 'Item',
             title = 'Item',
             args = { item_id, tostring(count), class = 'block' },
             args = { item_id, tostring(materials[item_id]), class = 'block' },
         }
         }
     end
     end
第217行: 第250行:
end
end


local function render_recipe_materials(frame, materials)
local function filter_material_option(materials, excluded_item_id)
    if type(materials) ~= 'table' then
        return {}
    end
 
    local filtered = {}
    for item_id, count in pairs(materials) do
        if item_id ~= excluded_item_id then
            filtered[item_id] = count
        end
    end
    return filtered
end
 
local function render_material_option_or_empty(frame, materials)
    local rendered = render_material_option(frame, materials)
    if rendered == '' then
        return '—'
    end
    return rendered
end
 
local function render_recipe_materials(frame, materials, excluded_item_id)
     local options = normalize_material_options(materials)
     local options = normalize_material_options(materials)
     if #options == 0 then
     if #options == 0 then
         return ''
         return ''
    end
    if excluded_item_id and excluded_item_id ~= '' then
        local filtered_options = {}
        local has_non_empty_option = false
        for _, option in ipairs(options) do
            local filtered = filter_material_option(option, excluded_item_id)
            if not is_empty(filtered) then
                has_non_empty_option = true
            end
            filtered_options[#filtered_options + 1] = filtered
        end
        if not has_non_empty_option then
            return '—'
        end
        options = filtered_options
     end
     end


     if #options == 1 then
     if #options == 1 then
         return render_material_option(frame, options[1])
         return render_material_option_or_empty(frame, options[1])
     end
     end


第231行: 第302行:
     out[#out + 1] = '<ul>'
     out[#out + 1] = '<ul>'
     for _, option in ipairs(options) do
     for _, option in ipairs(options) do
         out[#out + 1] = '<li>' .. render_material_option(frame, option) .. '</li>'
         out[#out + 1] = '<li>' .. render_material_option_or_empty(frame, option) .. '</li>'
     end
     end
     out[#out + 1] = '</ul>'
     out[#out + 1] = '</ul>'
第265行: 第336行:
     out[#out + 1] = '</ul>'
     out[#out + 1] = '</ul>'
     return table.concat(out, '')
     return table.concat(out, '')
end
local function load_recipes()
    if recipe_cache then
        return
    end
    local data = common.loadJsonData('数据:Machine/machine_recipes.json') or {}
    recipe_list = data.recipes or {}
    recipe_cache = data.by_machine or {}
    item_recipe_cache = data.by_item or {}
    field_map = (data._meta or {}).field_map or RECIPE_FIELD_DEFAULTS
end
local function recipe_time_text(recipe)
    if not recipe then
        return ''
    end
    local value = tonumber(recipe[field_map.time]) or 0
    if value <= 0 then
        return ''
    end
    return format_time(value)
end
local function recipe_cooking_method(recipe)
    if not recipe or not field_map.cooking_method then
        return ''
    end
    return common.toText(recipe[field_map.cooking_method])
end
local function recipe_has_cooking_method(recipe)
    return recipe_cooking_method(recipe) ~= ''
end
local function render_recipe_result(frame, recipe)
    if not recipe then
        return ''
    end
    local result_id = recipe[field_map.result]
    if not result_id or result_id == '' then
        return ''
    end
    local result_count = tonumber(recipe[field_map.result_count]) or 1
    if result_count > 1 then
        return item.renderItemWithArgs(frame, { result_id, tostring(result_count), class = 'block' })
    end
    return item.renderItemWithArgs(frame, { result_id, class = 'block' })
end
local function machine_group_heading(frame, machine_id)
    local rendered = item.renderItemWithArgs(frame, { machine_id, class = 'block' })
    if rendered == '' then
        return ''
    end
    return '<h4>' .. rendered .. '</h4>'
end
local function split_recipe_groups_by_machine(recipe_ids)
    local ordered_groups = {}
    local groups_by_machine = {}
    for _, rid in ipairs(recipe_ids) do
        local recipe = recipe_list[rid + 1]
        if recipe then
            local machine_id = common.trim(recipe[field_map.machine] or '')
            local group = groups_by_machine[machine_id]
            if not group then
                group = {
                    machine_id = machine_id,
                    recipe_ids = {},
                }
                groups_by_machine[machine_id] = group
                ordered_groups[#ordered_groups + 1] = group
            end
            group.recipe_ids[#group.recipe_ids + 1] = rid
        end
    end
    local featured_groups = {}
    local remaining_ids = {}
    for _, group in ipairs(ordered_groups) do
        if #group.recipe_ids >= 3 then
            featured_groups[#featured_groups + 1] = group
        else
            for _, rid in ipairs(group.recipe_ids) do
                remaining_ids[#remaining_ids + 1] = rid
            end
        end
    end
    return featured_groups, remaining_ids
end
end


local function render_recipe_usage_note()
local function render_recipe_usage_note()
     return '<p><small>说明:每一行代表一类配方;同一行普通并列材料都需要满足。若材料栏显示列表,则列表中的每一项代表一套可选方案,任选其一即可;若同一产物出现多行,则表示仍有多套不同配方。</small></p>'
     return '<p><small>说明:当同一机器的配方条目大于等于 3 条时,会按机器分组展开。同行并列显示的材料都需要同时满足;如果材料栏出现列表,表示存在多套可任选其一的材料方案。在“用于制作”部分,“所需数量”只表示当前页面物品的用量,“其他材料”则列出该配方中的其余输入。</small></p>'
end
end


第297行: 第464行:
         return common.toText(get_craft_field(craft, 'e', 'exp'))
         return common.toText(get_craft_field(craft, 'e', 'exp'))
     end
     end
     local upgrade = get_record_field(record, key, 'upgrade')
     local upgrade = get_record_field(record, key, 'upgrade')
     if field == 'upgrade_from' and upgrade then
     if field == 'upgrade_from' and upgrade then
第352行: 第520行:
         return ''
         return ''
     end
     end
     local craft = get_record_field(record, key, 'craft')
     local craft = get_record_field(record, key, 'craft')
     if not craft then
     if not craft then
第365行: 第534行:
         return ''
         return ''
     end
     end
     local upgrade = get_record_field(record, key, 'upgrade')
     local upgrade = get_record_field(record, key, 'upgrade')
     if not upgrade then
     if not upgrade then
第380行: 第550行:


     local out = {}
     local out = {}
     local craft = get_record_field(record, key, 'craft')
     local craft = get_record_field(record, key, 'craft')
     local craft_materials = get_craft_field(craft, 'mat', 'materials')
     local craft_materials = get_craft_field(craft, 'mat', 'materials')
第429行: 第600行:


     return table.concat(out, '\n')
     return table.concat(out, '\n')
end
local recipe_cache
local recipe_list
local field_map
local function load_recipes()
    if recipe_cache then
        return
    end
    local data = common.loadJsonData('数据:Machine/machine_recipes.json') or {}
    recipe_list = data.recipes or {}
    recipe_cache = data.by_machine or {}
    field_map = (data._meta or {}).field_map or {
        result = 'r',
        result_count = 'rc',
        materials = 'm',
        time = 't',
        machine = 'mc'
    }
end
end


第457行: 第608行:
         return ''
         return ''
     end
     end
   
 
    -- 获取机器ID
     local machine_id = get_record_field(record, key, 'id')
     local machine_id = get_record_field(record, key, 'id')
     if not machine_id then
     if not machine_id or machine_id == '' then
         return ''
         return ''
     end
     end
   
 
    local machine_key = mw.ustring.lower(machine_id)
   
     load_recipes()
     load_recipes()
     local recipe_ids = recipe_cache[machine_key]
     local recipe_ids = recipe_cache[mw.ustring.lower(machine_id)]
     if not recipe_ids then
     if not recipe_ids then
         return '该机器暂无独有配方。'
         return '该机器暂无独有配方。'
     end
     end
   
 
    -- 检查是否有配方需要显示时间
     local has_time = false
     local has_time = false
     for _, rid in ipairs(recipe_ids) do
     for _, rid in ipairs(recipe_ids) do
         local recipe = recipe_list[rid + 1]
         local recipe = recipe_list[rid + 1]
         if recipe and recipe[field_map.time] and recipe[field_map.time] > 0 then
         if recipe and recipe_time_text(recipe) ~= '' then
             has_time = true
             has_time = true
             break
             break
         end
         end
     end
     end
   
 
    -- 构建HTML表格
     local out = {}
     local out = {}
     out[#out + 1] = '<table class="wikitable">'
     out[#out + 1] = '<table class="wikitable">'
第490行: 第636行:
     end
     end
     out[#out + 1] = '</tr>'
     out[#out + 1] = '</tr>'
   
 
     for _, rid in ipairs(recipe_ids) do
     for _, rid in ipairs(recipe_ids) do
         local recipe = recipe_list[rid + 1]
         local recipe = recipe_list[rid + 1]
         if recipe then
         if recipe then
             out[#out + 1] = '<tr>'
             out[#out + 1] = '<tr>'
             local result_count = recipe[field_map.result_count] or 1
             out[#out + 1] = '<td>' .. render_recipe_result(frame, recipe) .. '</td>'
            if result_count > 1 then
                out[#out + 1] = '<td>' .. item.renderItemWithArgs(frame, {recipe[field_map.result], tostring(result_count), class = 'block'}) .. '</td>'
            else
                out[#out + 1] = '<td>' .. item.renderItemWithArgs(frame, {recipe[field_map.result], class = 'block'}) .. '</td>'
            end
           
            -- 渲染材料
             out[#out + 1] = '<td>' .. render_recipe_materials(frame, recipe[field_map.materials]) .. '</td>'
             out[#out + 1] = '<td>' .. render_recipe_materials(frame, recipe[field_map.materials]) .. '</td>'
           
            -- 渲染时间
             if has_time then
             if has_time then
                local time_str = ''
                 out[#out + 1] = '<td>' .. recipe_time_text(recipe) .. '</td>'
                if recipe[field_map.time] and recipe[field_map.time] > 0 then
                    time_str = format_time(recipe[field_map.time])
                end
                 out[#out + 1] = '<td>' .. time_str .. '</td>'
             end
             end
           
             out[#out + 1] = '</tr>'
             out[#out + 1] = '</tr>'
         end
         end
     end
     end
   
 
     out[#out + 1] = '</table>'
     out[#out + 1] = '</table>'
   
     local css_out = css.quickCall('Item') or ''
     local css_out = css.quickCall('Item') or ''
     return css_out .. table.concat(out, '\n')
     return css_out .. table.concat(out, '\n')
end
end


local item_recipe_cache
local function recipe_ids_have_time(recipe_ids)
    for _, rid in ipairs(recipe_ids) do
        local recipe = recipe_list[rid + 1]
        if recipe and recipe_time_text(recipe) ~= '' then
            return true
        end
    end
    return false
end
 
local function recipe_ids_have_cooking_method(recipe_ids)
    for _, rid in ipairs(recipe_ids) do
        local recipe = recipe_list[rid + 1]
        if recipe and recipe_has_cooking_method(recipe) then
            return true
        end
    end
    return false
end
 
local function render_product_recipe_table(frame, recipe_ids, include_machine_column)
    local has_time = recipe_ids_have_time(recipe_ids)
    local has_cooking_method = recipe_ids_have_cooking_method(recipe_ids)
 
    local out = {}
    out[#out + 1] = '<table class="wikitable">'
    out[#out + 1] = '<tr><th>产出</th>'
    if include_machine_column then
        out[#out + 1] = '<th>机器</th>'
    end
    if has_cooking_method then
        out[#out + 1] = '<th>烹饪方式</th>'
    end
    out[#out + 1] = '<th>所需材料</th>'
    if has_time then
        out[#out + 1] = '<th>时间</th>'
    end
    out[#out + 1] = '</tr>'
 
    for _, rid in ipairs(recipe_ids) do
        local recipe = recipe_list[rid + 1]
        if recipe then
            out[#out + 1] = '<tr>'
            out[#out + 1] = '<td>' .. render_recipe_result(frame, recipe) .. '</td>'
            if include_machine_column then
                out[#out + 1] = '<td>' .. item.renderItemWithArgs(frame, { recipe[field_map.machine], class = 'block' }) .. '</td>'
            end
            if has_cooking_method then
                local cooking_method = recipe_cooking_method(recipe)
                if cooking_method == '' then
                    cooking_method = '—'
                end
                out[#out + 1] = '<td>' .. cooking_method .. '</td>'
            end
            out[#out + 1] = '<td>' .. render_recipe_materials(frame, recipe[field_map.materials]) .. '</td>'
            if has_time then
                out[#out + 1] = '<td>' .. recipe_time_text(recipe) .. '</td>'
            end
            out[#out + 1] = '</tr>'
        end
    end
 
    out[#out + 1] = '</table>'
    return table.concat(out, '')
end
 
local function render_ingredient_recipe_table(frame, recipe_ids, current_item_id, include_machine_column)
    local has_cooking_method = recipe_ids_have_cooking_method(recipe_ids)
 
    local out = {}
    out[#out + 1] = '<table class="wikitable">'
    out[#out + 1] = '<tr><th>产物</th>'
    if include_machine_column then
        out[#out + 1] = '<th>机器</th>'
    end
    if has_cooking_method then
        out[#out + 1] = '<th>烹饪方式</th>'
    end
    out[#out + 1] = '<th>所需数量</th><th>其他材料</th></tr>'
 
    for _, rid in ipairs(recipe_ids) do
        local recipe = recipe_list[rid + 1]
        if recipe then
            local counts = material_counts_for_item(recipe[field_map.materials], current_item_id)
            if #counts > 0 then
                out[#out + 1] = '<tr>'
                out[#out + 1] = '<td>' .. render_recipe_result(frame, recipe) .. '</td>'
                if include_machine_column then
                    out[#out + 1] = '<td>' .. item.renderItemWithArgs(frame, { recipe[field_map.machine], class = 'block' }) .. '</td>'
                end
                if has_cooking_method then
                    local cooking_method = recipe_cooking_method(recipe)
                    if cooking_method == '' then
                        cooking_method = '—'
                    end
                    out[#out + 1] = '<td>' .. cooking_method .. '</td>'
                end
                out[#out + 1] = '<td>' .. render_material_count_list(counts) .. '</td>'
                out[#out + 1] = '<td>' .. render_recipe_materials(frame, recipe[field_map.materials], current_item_id) .. '</td>'
                out[#out + 1] = '</tr>'
            end
        end
    end
 
    out[#out + 1] = '</table>'
    return table.concat(out, '')
end
 
local function append_recipe_usage_sections(out, frame, recipe_ids, current_item_id, section_title, render_table)
    local featured_groups, remaining_ids = split_recipe_groups_by_machine(recipe_ids)
 
    out[#out + 1] = '<h3>' .. section_title .. '</h3>'
    for _, group in ipairs(featured_groups) do
        local heading = machine_group_heading(frame, group.machine_id)
        if heading ~= '' then
            out[#out + 1] = heading
        end
        out[#out + 1] = render_table(frame, group.recipe_ids, current_item_id, false)
    end


local function load_item_recipes()
    if #remaining_ids > 0 then
    if item_recipe_cache then
        if #featured_groups > 0 then
         return
            out[#out + 1] = '<h4>其他机器</h4>'
        end
         out[#out + 1] = render_table(frame, remaining_ids, current_item_id, true)
     end
     end
    load_recipes()
    local data = common.loadJsonData('数据:Machine/machine_recipes.json') or {}
    item_recipe_cache = data.by_item or {}
end
end


第540行: 第788行:
         item_key = common.getCurrentTitleText()
         item_key = common.getCurrentTitleText()
     end
     end
   
 
    -- 获取物品ID
     local item_id = item.getIdByKey(item_key)
     local item_id = item.getIdByKey(item_key)
     if not item_id or item_id == '' then
     if not item_id or item_id == '' then
         return ''
         return ''
     end
     end
      
 
     local normalized_id = mw.ustring.lower(item_id)
     load_recipes()
   
     local item_data = item_recipe_cache[mw.ustring.lower(item_id)]
    load_item_recipes()
    local item_data = item_recipe_cache[normalized_id]
   
     if not item_data then
     if not item_data then
         return ''
         return ''
     end
     end
   
 
     local product_ids = item_data.p or {}
     local product_ids = item_data.p or {}
     local ingredient_ids = item_data.i or {}
     local ingredient_ids = item_data.i or {}
   
    -- 计算数组长度(处理有metatable的情况)
    local function count_array(arr)
        local count = 0
        for _ in pairs(arr) do
            count = count + 1
        end
        return count
    end
   
     local product_count = count_array(product_ids)
     local product_count = count_array(product_ids)
     local ingredient_count = count_array(ingredient_ids)
     local ingredient_count = count_array(ingredient_ids)
   
     if product_count == 0 and ingredient_count == 0 then
     if product_count == 0 and ingredient_count == 0 then
         return ''
         return ''
     end
     end
   
 
     local out = {}
     local out = {}
     out[#out + 1] = render_recipe_usage_note()
     out[#out + 1] = render_recipe_usage_note()
   
 
    -- 作为产物
     if product_count > 0 then
     if product_count > 0 then
         out[#out + 1] = '<h3>制作配方</h3>'
         append_recipe_usage_sections(out, frame, product_ids, item_id, '制作配方', function(current_frame, recipe_ids_for_table, _, include_machine_column)
        out[#out + 1] = '<table class="wikitable">'
            return render_product_recipe_table(current_frame, recipe_ids_for_table, include_machine_column)
        out[#out + 1] = '<tr><th>产物</th><th>机器</th><th>所需材料</th><th>时间</th></tr>'
         end)
       
        for _, rid in ipairs(product_ids) do
            local recipe = recipe_list[rid + 1]
            if recipe then
                out[#out + 1] = '<tr>'
                out[#out + 1] = '<td>' .. item.renderItemWithArgs(frame, {recipe[field_map.result], class = 'block'}) .. '</td>'
                out[#out + 1] = '<td>' .. item.renderItemWithArgs(frame, {recipe[field_map.machine], class = 'block'}) .. '</td>'
               
                out[#out + 1] = '<td>' .. render_recipe_materials(frame, recipe[field_map.materials]) .. '</td>'
               
                local time_str = ''
                if recipe[field_map.time] and recipe[field_map.time] > 0 then
                    time_str = format_time(recipe[field_map.time])
                end
                out[#out + 1] = '<td>' .. time_str .. '</td>'
                out[#out + 1] = '</tr>'
            end
         end
       
        out[#out + 1] = '</table>'
     end
     end
   
 
    -- 作为材料
     if ingredient_count > 0 then
     if ingredient_count > 0 then
         if #out > 0 then
         if #out > 0 then
             out[#out + 1] = ''
             out[#out + 1] = ''
         end
         end
         out[#out + 1] = '<h3>用于制作</h3>'
         append_recipe_usage_sections(out, frame, ingredient_ids, item_id, '用于制作', render_ingredient_recipe_table)
        out[#out + 1] = '<table class="wikitable">'
        out[#out + 1] = '<tr><th>产物</th><th>所需材料</th><th>机器</th></tr>'
       
        for _, rid in ipairs(ingredient_ids) do
            local recipe = recipe_list[rid + 1]
            if recipe then
                out[#out + 1] = '<tr>'
                out[#out + 1] = '<td>' .. item.renderItemWithArgs(frame, {recipe[field_map.result], class = 'block'}) .. '</td>'
               
                -- 渲染材料
                out[#out + 1] = '<td>' .. render_recipe_materials(frame, recipe[field_map.materials]) .. '</td>'
               
                out[#out + 1] = '<td>' .. item.renderItemWithArgs(frame, {recipe[field_map.machine], class = 'block'}) .. '</td>'
                out[#out + 1] = '</tr>'
            end
        end
       
        out[#out + 1] = '</table>'
     end
     end
   
 
     local css_out = css.quickCall('Item') or ''
     local css_out = css.quickCall('Item') or ''
     return css_out .. table.concat(out, '\n')
     return css_out .. table.concat(out, '\n')
第641行: 第834行:
         return ''
         return ''
     end
     end
   
 
     local machine_id = get_record_field(record, key, 'id')
     local machine_id = get_record_field(record, key, 'id')
     if not machine_id then
     if not machine_id or machine_id == '' then
         return ''
         return ''
     end
     end
      
 
     local machine_key = mw.ustring.lower(machine_id)
     load_recipes()
    load_item_recipes()
     local item_data = item_recipe_cache[mw.ustring.lower(machine_id)]
    local item_data = item_recipe_cache[machine_key]
     if not item_data or not item_data.i or count_array(item_data.i) == 0 then
   
     if not item_data or not item_data.i or #item_data.i == 0 then
         return ''
         return ''
     end
     end
   
 
     local out = {}
     local out = {}
     out[#out + 1] = '=== 用于制作 ==='
     out[#out + 1] = '=== 用于制作 ==='
     out[#out + 1] = '<table class="wikitable">'
     out[#out + 1] = '<table class="wikitable">'
     out[#out + 1] = '<tr><th>产物</th><th>所需数量</th></tr>'
     out[#out + 1] = '<tr><th>产物</th><th>所需数量</th></tr>'
   
 
     for _, rid in ipairs(item_data.i) do
     for _, rid in ipairs(item_data.i) do
         local recipe = recipe_list[rid + 1]
         local recipe = recipe_list[rid + 1]
第666行: 第857行:
             if #counts > 0 then
             if #counts > 0 then
                 out[#out + 1] = '<tr>'
                 out[#out + 1] = '<tr>'
                 out[#out + 1] = '<td>' .. item.renderItemWithArgs(frame, {recipe[field_map.result]}) .. '</td>'
                 out[#out + 1] = '<td>' .. render_recipe_result(frame, recipe) .. '</td>'
                 out[#out + 1] = '<td>' .. render_material_count_list(counts) .. '</td>'
                 out[#out + 1] = '<td>' .. render_material_count_list(counts) .. '</td>'
                 out[#out + 1] = '</tr>'
                 out[#out + 1] = '</tr>'
第672行: 第863行:
         end
         end
     end
     end
   
 
     out[#out + 1] = '</table>'
     out[#out + 1] = '</table>'
     local css_out = css.quickCall('Item') or ''
     local css_out = css.quickCall('Item') or ''

2026年3月17日 (二) 15:22的版本

概述

Machine 提供机器域的字段读取、材料列表、配方信息与反查展示,供 {{Infobox machine}}{{MachineRecipes}}{{RecipeUsage}} 调用。

用法

{{#invoke:Machine|getField|工作台|type_display}}
{{#invoke:Machine|craftInfo|工作台}}
{{#invoke:Machine|recipeList|工作台}}
{{#invoke:Machine|itemRecipes|木板}}

函数

  • getField:读取机器字段。
  • ingredientList:渲染建造材料。
  • upgradeIngredientList:渲染升级材料。
  • craftInfo:渲染机器信息块。
  • recipeList:渲染机器产出配方列表。
  • itemRecipes:渲染物品的机器配方反查。
  • machineAsIngredient:渲染机器作为材料的反查结果。

数据来源


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

local p = {}

local FIELD_MAP = {
    type = 't',
    type_display = 'td',
    item_level = 'lv',
    rarity = 'r',
    sell_price = 'sp',
    base_value = 'bv',
    max_stack = 'ms',
    max_quality = 'mq',
    tags = 'g',
    origin = 'o',
    locked_origin = 'lo',
    use_description = 'ud',
    unlock_title = 'ut',
    construct_template = 'ct',
    construction_template_count = 'ctc',
    energy_capacity = 'ec',
    production_formula = 'pf',
    production_formula_origin = 'pfo',
    craft = 'c',
    upgrade = 'u',
    prev_machine = 'pm',
    next_machine = 'nm',
}

local RECIPE_FIELD_DEFAULTS = {
    result = 'r',
    result_count = 'rc',
    materials = 'm',
    time = 't',
    machine = 'mc',
    cooking_method = 'cm',
}

local data_cache
local mapping_cache
local recipe_cache
local recipe_list
local item_recipe_cache
local field_map = RECIPE_FIELD_DEFAULTS

local function is_empty(value)
    if value == nil then
        return true
    end
    for _ in pairs(value) do
        return false
    end
    return true
end

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

    local count = 0
    for _ in pairs(arr) do
        count = count + 1
    end
    return count
end

local function load_data()
    if data_cache then
        return
    end

    local raw_data
    raw_data, mapping_cache = item_common.loadDomainData('数据:Machine/machine_index.json')
    if type(raw_data) == 'table' and type(raw_data.records) == 'table' then
        data_cache = raw_data.records
    else
        data_cache = raw_data or {}
    end
end

local function find_record(key)
    load_data()
    return item_common.findRecord(data_cache, mapping_cache, key)
end

local function get_short_field(field)
    return FIELD_MAP[field] or field
end

local function get_record_field(record, key, field)
    if type(record) ~= 'table' then
        return nil
    end

    local short_key = get_short_field(field)
    if record[short_key] ~= nil then
        return record[short_key]
    end
    if record[field] ~= nil then
        return record[field]
    end
    if field == 'id' or field == 'name' or field == 'name_en' then
        return item_common.getIdentityField(record, key, field)
    end
    return nil
end

local function get_craft_field(craft, short_key, long_key)
    if type(craft) ~= 'table' then
        return nil
    end
    if craft[short_key] ~= nil then
        return craft[short_key]
    end
    return craft[long_key]
end

local function format_time(seconds)
    seconds = tonumber(seconds) or 0
    if seconds <= 0 then
        return ''
    end
    if seconds < 60 then
        return tostring(math.floor(seconds)) .. '秒'
    end

    local minutes = math.floor(seconds / 60)
    local remain = math.floor(seconds % 60)
    if remain == 0 then
        return tostring(minutes) .. '分'
    end
    return tostring(minutes) .. '分' .. tostring(remain) .. '秒'
end

local function render_materials(frame, materials)
    if is_empty(materials) then
        return ''
    end

    local item_ids = {}
    for item_id in pairs(materials) do
        item_ids[#item_ids + 1] = item_id
    end
    table.sort(item_ids)

    local parts = {}
    for _, item_id in ipairs(item_ids) do
        parts[#parts + 1] = frame:expandTemplate{
            title = 'Item',
            args = { item_id, tostring(materials[item_id]), class = 'block' },
        }
    end
    return table.concat(parts, '')
end

local function render_item_template(frame, item_id)
    local resolved_id = common.trim(item_id or '')
    if resolved_id == '' then
        return ''
    end

    local css_out = css.quickCall('Item') or ''
    return css_out .. item.renderItemWithArgs(frame, { resolved_id })
end

local function render_craft_table(title_text, station_label, station_value, materials_label, materials_value, time_label, time_value, exp_label, exp_value)
    local out = {}
    out[#out + 1] = '<table class="wikitable machine-craft-table">'
    out[#out + 1] = '<tr><th colspan="2">' .. title_text .. '</th></tr>'
    out[#out + 1] = '<tr><th>' .. station_label .. '</th><td>' .. station_value .. '</td></tr>'
    out[#out + 1] = '<tr><th>' .. materials_label .. '</th><td>' .. materials_value .. '</td></tr>'
    if time_value ~= '' then
        out[#out + 1] = '<tr><th>' .. time_label .. '</th><td>' .. time_value .. '</td></tr>'
    end
    if exp_value ~= '' then
        out[#out + 1] = '<tr><th>' .. exp_label .. '</th><td>' .. exp_value .. '</td></tr>'
    end
    out[#out + 1] = '</table>'
    return table.concat(out, '\n')
end

local function is_array_table(value)
    if type(value) ~= 'table' then
        return false
    end

    local count = 0
    for key in pairs(value) do
        if type(key) ~= 'number' then
            return false
        end
        count = count + 1
    end

    if count == 0 then
        return false
    end

    for index = 1, count do
        if value[index] == nil then
            return false
        end
    end

    return true
end

local function normalize_material_options(materials)
    if type(materials) ~= 'table' then
        return {}
    end

    if is_array_table(materials) then
        local options = {}
        for _, option in ipairs(materials) do
            if type(option) == 'table' and not is_empty(option) then
                options[#options + 1] = option
            end
        end
        return options
    end

    if is_empty(materials) then
        return {}
    end

    return { materials }
end

local function render_material_option(frame, materials)
    if type(materials) ~= 'table' or is_empty(materials) then
        return ''
    end

    local item_ids = {}
    for item_id in pairs(materials) do
        item_ids[#item_ids + 1] = item_id
    end
    table.sort(item_ids)

    local parts = {}
    for _, item_id in ipairs(item_ids) do
        parts[#parts + 1] = item.renderItemWithArgs(frame, { item_id, tostring(materials[item_id]), class = 'block' })
    end
    return table.concat(parts, '')
end

local function filter_material_option(materials, excluded_item_id)
    if type(materials) ~= 'table' then
        return {}
    end

    local filtered = {}
    for item_id, count in pairs(materials) do
        if item_id ~= excluded_item_id then
            filtered[item_id] = count
        end
    end
    return filtered
end

local function render_material_option_or_empty(frame, materials)
    local rendered = render_material_option(frame, materials)
    if rendered == '' then
        return '—'
    end
    return rendered
end

local function render_recipe_materials(frame, materials, excluded_item_id)
    local options = normalize_material_options(materials)
    if #options == 0 then
        return ''
    end

    if excluded_item_id and excluded_item_id ~= '' then
        local filtered_options = {}
        local has_non_empty_option = false
        for _, option in ipairs(options) do
            local filtered = filter_material_option(option, excluded_item_id)
            if not is_empty(filtered) then
                has_non_empty_option = true
            end
            filtered_options[#filtered_options + 1] = filtered
        end
        if not has_non_empty_option then
            return '—'
        end
        options = filtered_options
    end

    if #options == 1 then
        return render_material_option_or_empty(frame, options[1])
    end

    local out = {}
    out[#out + 1] = '<div>任选其一:</div>'
    out[#out + 1] = '<ul>'
    for _, option in ipairs(options) do
        out[#out + 1] = '<li>' .. render_material_option_or_empty(frame, option) .. '</li>'
    end
    out[#out + 1] = '</ul>'
    return table.concat(out, '')
end

local function material_counts_for_item(materials, item_id)
    local counts = {}
    local seen = {}
    for _, option in ipairs(normalize_material_options(materials)) do
        local count = tonumber(option[item_id]) or 0
        if count > 0 and not seen[count] then
            seen[count] = true
            counts[#counts + 1] = count
        end
    end
    table.sort(counts)
    return counts
end

local function render_material_count_list(counts)
    if #counts == 0 then
        return ''
    end
    if #counts == 1 then
        return tostring(counts[1])
    end

    local out = { '<ul>' }
    for _, count in ipairs(counts) do
        out[#out + 1] = '<li>' .. tostring(count) .. '</li>'
    end
    out[#out + 1] = '</ul>'
    return table.concat(out, '')
end

local function load_recipes()
    if recipe_cache then
        return
    end

    local data = common.loadJsonData('数据:Machine/machine_recipes.json') or {}
    recipe_list = data.recipes or {}
    recipe_cache = data.by_machine or {}
    item_recipe_cache = data.by_item or {}
    field_map = (data._meta or {}).field_map or RECIPE_FIELD_DEFAULTS
end

local function recipe_time_text(recipe)
    if not recipe then
        return ''
    end

    local value = tonumber(recipe[field_map.time]) or 0
    if value <= 0 then
        return ''
    end
    return format_time(value)
end

local function recipe_cooking_method(recipe)
    if not recipe or not field_map.cooking_method then
        return ''
    end
    return common.toText(recipe[field_map.cooking_method])
end

local function recipe_has_cooking_method(recipe)
    return recipe_cooking_method(recipe) ~= ''
end

local function render_recipe_result(frame, recipe)
    if not recipe then
        return ''
    end

    local result_id = recipe[field_map.result]
    if not result_id or result_id == '' then
        return ''
    end

    local result_count = tonumber(recipe[field_map.result_count]) or 1
    if result_count > 1 then
        return item.renderItemWithArgs(frame, { result_id, tostring(result_count), class = 'block' })
    end
    return item.renderItemWithArgs(frame, { result_id, class = 'block' })
end

local function machine_group_heading(frame, machine_id)
    local rendered = item.renderItemWithArgs(frame, { machine_id, class = 'block' })
    if rendered == '' then
        return ''
    end
    return '<h4>' .. rendered .. '</h4>'
end

local function split_recipe_groups_by_machine(recipe_ids)
    local ordered_groups = {}
    local groups_by_machine = {}

    for _, rid in ipairs(recipe_ids) do
        local recipe = recipe_list[rid + 1]
        if recipe then
            local machine_id = common.trim(recipe[field_map.machine] or '')
            local group = groups_by_machine[machine_id]
            if not group then
                group = {
                    machine_id = machine_id,
                    recipe_ids = {},
                }
                groups_by_machine[machine_id] = group
                ordered_groups[#ordered_groups + 1] = group
            end
            group.recipe_ids[#group.recipe_ids + 1] = rid
        end
    end

    local featured_groups = {}
    local remaining_ids = {}
    for _, group in ipairs(ordered_groups) do
        if #group.recipe_ids >= 3 then
            featured_groups[#featured_groups + 1] = group
        else
            for _, rid in ipairs(group.recipe_ids) do
                remaining_ids[#remaining_ids + 1] = rid
            end
        end
    end

    return featured_groups, remaining_ids
end

local function render_recipe_usage_note()
    return '<p><small>说明:当同一机器的配方条目大于等于 3 条时,会按机器分组展开。同行并列显示的材料都需要同时满足;如果材料栏出现列表,表示存在多套可任选其一的材料方案。在“用于制作”部分,“所需数量”只表示当前页面物品的用量,“其他材料”则列出该配方中的其余输入。</small></p>'
end

function p.getField(frame)
    local key = common.getArg(frame, 1, '')
    local field = common.getArg(frame, 2, '')
    if field == '' then
        return ''
    end

    local record = find_record(key)
    if not record then
        return ''
    end

    local craft = get_record_field(record, key, 'craft')
    if field == 'craft_machine' and craft then
        local machine_id = get_craft_field(craft, 'm', 'machine')
        if machine_id and machine_id ~= '' then
            return render_item_template(frame, machine_id)
        end
        return ''
    end
    if field == 'craft_time' and craft then
        return format_time(get_craft_field(craft, 't', 'time'))
    end
    if field == 'craft_exp' and craft then
        return common.toText(get_craft_field(craft, 'e', 'exp'))
    end

    local upgrade = get_record_field(record, key, 'upgrade')
    if field == 'upgrade_from' and upgrade then
        local from_id = get_craft_field(upgrade, 'f', 'from_machine')
        if from_id and from_id ~= '' then
            local from_name = item.getNameByKey(from_id)
            if from_name and from_name ~= '' then
                return '[[' .. from_name .. ']]'
            end
        end
        return ''
    end
    if field == 'upgrade_time' and upgrade then
        return format_time(get_craft_field(upgrade, 't', 'time'))
    end
    if field == 'upgrade_exp' and upgrade then
        return common.toText(get_craft_field(upgrade, 'e', 'exp'))
    end
    if field == 'construction_template_count' then
        local value = tonumber(get_record_field(record, key, field)) or 0
        if value <= 0 then
            return ''
        end
        return tostring(value)
    end
    if field == 'energy_capacity' then
        local value = tonumber(get_record_field(record, key, field)) or 0
        if value <= 0 then
            return ''
        end
        return tostring(value)
    end
    if field == 'prev_machine' then
        local prev_id = get_record_field(record, key, 'prev_machine')
        if prev_id and prev_id ~= '' then
            return render_item_template(frame, prev_id)
        end
        return ''
    end
    if field == 'next_machine' then
        local next_id = get_record_field(record, key, 'next_machine')
        if next_id and next_id ~= '' then
            return render_item_template(frame, next_id)
        end
        return ''
    end

    return common.toText(get_record_field(record, key, field))
end

function p.ingredientList(frame)
    local key = common.getArg(frame, 1, '')
    local record = find_record(key)
    if not record then
        return ''
    end

    local craft = get_record_field(record, key, 'craft')
    if not craft then
        return ''
    end
    return render_materials(frame, get_craft_field(craft, 'mat', 'materials'))
end

function p.upgradeIngredientList(frame)
    local key = common.getArg(frame, 1, '')
    local record = find_record(key)
    if not record then
        return ''
    end

    local upgrade = get_record_field(record, key, 'upgrade')
    if not upgrade then
        return ''
    end
    return render_materials(frame, get_craft_field(upgrade, 'mat', 'materials'))
end

function p.craftInfo(frame)
    local key = common.getArg(frame, 1, '')
    local record = find_record(key)
    if not record then
        return ''
    end

    local out = {}

    local craft = get_record_field(record, key, 'craft')
    local craft_materials = get_craft_field(craft, 'mat', 'materials')
    if craft and not is_empty(craft_materials) then
        local machine_id = get_craft_field(craft, 'm', 'machine')
        local machine_display = '—'
        if machine_id and common.trim(machine_id) ~= '' then
            local machine_name = item.getNameByKey(machine_id)
            if machine_name and machine_name ~= '' then
                machine_display = '[[' .. machine_name .. ']]'
            end
        end
        out[#out + 1] = render_craft_table(
            '制造配方',
            '制造站',
            machine_display,
            '材料',
            render_materials(frame, craft_materials),
            '时间',
            format_time(get_craft_field(craft, 't', 'time')),
            '经验',
            common.toText(get_craft_field(craft, 'e', 'exp'))
        )
    end

    local upgrade = get_record_field(record, key, 'upgrade')
    local upgrade_materials = get_craft_field(upgrade, 'mat', 'materials')
    if upgrade and not is_empty(upgrade_materials) then
        local from_id = get_craft_field(upgrade, 'f', 'from_machine')
        local from_display = '—'
        if from_id and common.trim(from_id) ~= '' then
            local from_name = item.getNameByKey(from_id)
            if from_name and from_name ~= '' then
                from_display = '[[' .. from_name .. ']]'
            end
        end
        out[#out + 1] = render_craft_table(
            '升级配方',
            '升级自',
            from_display,
            '材料',
            render_materials(frame, upgrade_materials),
            '时间',
            format_time(get_craft_field(upgrade, 't', 'time')),
            '经验',
            common.toText(get_craft_field(upgrade, 'e', 'exp'))
        )
    end

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

function p.recipeList(frame)
    local key = common.getArg(frame, 1, '')
    local record = find_record(key)
    if not record then
        return ''
    end

    local machine_id = get_record_field(record, key, 'id')
    if not machine_id or machine_id == '' then
        return ''
    end

    load_recipes()
    local recipe_ids = recipe_cache[mw.ustring.lower(machine_id)]
    if not recipe_ids then
        return '该机器暂无独有配方。'
    end

    local has_time = false
    for _, rid in ipairs(recipe_ids) do
        local recipe = recipe_list[rid + 1]
        if recipe and recipe_time_text(recipe) ~= '' then
            has_time = true
            break
        end
    end

    local out = {}
    out[#out + 1] = '<table class="wikitable">'
    out[#out + 1] = '<tr><th>产物</th><th>材料</th>'
    if has_time then
        out[#out + 1] = '<th>时间</th>'
    end
    out[#out + 1] = '</tr>'

    for _, rid in ipairs(recipe_ids) do
        local recipe = recipe_list[rid + 1]
        if recipe then
            out[#out + 1] = '<tr>'
            out[#out + 1] = '<td>' .. render_recipe_result(frame, recipe) .. '</td>'
            out[#out + 1] = '<td>' .. render_recipe_materials(frame, recipe[field_map.materials]) .. '</td>'
            if has_time then
                out[#out + 1] = '<td>' .. recipe_time_text(recipe) .. '</td>'
            end
            out[#out + 1] = '</tr>'
        end
    end

    out[#out + 1] = '</table>'
    local css_out = css.quickCall('Item') or ''
    return css_out .. table.concat(out, '\n')
end

local function recipe_ids_have_time(recipe_ids)
    for _, rid in ipairs(recipe_ids) do
        local recipe = recipe_list[rid + 1]
        if recipe and recipe_time_text(recipe) ~= '' then
            return true
        end
    end
    return false
end

local function recipe_ids_have_cooking_method(recipe_ids)
    for _, rid in ipairs(recipe_ids) do
        local recipe = recipe_list[rid + 1]
        if recipe and recipe_has_cooking_method(recipe) then
            return true
        end
    end
    return false
end

local function render_product_recipe_table(frame, recipe_ids, include_machine_column)
    local has_time = recipe_ids_have_time(recipe_ids)
    local has_cooking_method = recipe_ids_have_cooking_method(recipe_ids)

    local out = {}
    out[#out + 1] = '<table class="wikitable">'
    out[#out + 1] = '<tr><th>产出</th>'
    if include_machine_column then
        out[#out + 1] = '<th>机器</th>'
    end
    if has_cooking_method then
        out[#out + 1] = '<th>烹饪方式</th>'
    end
    out[#out + 1] = '<th>所需材料</th>'
    if has_time then
        out[#out + 1] = '<th>时间</th>'
    end
    out[#out + 1] = '</tr>'

    for _, rid in ipairs(recipe_ids) do
        local recipe = recipe_list[rid + 1]
        if recipe then
            out[#out + 1] = '<tr>'
            out[#out + 1] = '<td>' .. render_recipe_result(frame, recipe) .. '</td>'
            if include_machine_column then
                out[#out + 1] = '<td>' .. item.renderItemWithArgs(frame, { recipe[field_map.machine], class = 'block' }) .. '</td>'
            end
            if has_cooking_method then
                local cooking_method = recipe_cooking_method(recipe)
                if cooking_method == '' then
                    cooking_method = '—'
                end
                out[#out + 1] = '<td>' .. cooking_method .. '</td>'
            end
            out[#out + 1] = '<td>' .. render_recipe_materials(frame, recipe[field_map.materials]) .. '</td>'
            if has_time then
                out[#out + 1] = '<td>' .. recipe_time_text(recipe) .. '</td>'
            end
            out[#out + 1] = '</tr>'
        end
    end

    out[#out + 1] = '</table>'
    return table.concat(out, '')
end

local function render_ingredient_recipe_table(frame, recipe_ids, current_item_id, include_machine_column)
    local has_cooking_method = recipe_ids_have_cooking_method(recipe_ids)

    local out = {}
    out[#out + 1] = '<table class="wikitable">'
    out[#out + 1] = '<tr><th>产物</th>'
    if include_machine_column then
        out[#out + 1] = '<th>机器</th>'
    end
    if has_cooking_method then
        out[#out + 1] = '<th>烹饪方式</th>'
    end
    out[#out + 1] = '<th>所需数量</th><th>其他材料</th></tr>'

    for _, rid in ipairs(recipe_ids) do
        local recipe = recipe_list[rid + 1]
        if recipe then
            local counts = material_counts_for_item(recipe[field_map.materials], current_item_id)
            if #counts > 0 then
                out[#out + 1] = '<tr>'
                out[#out + 1] = '<td>' .. render_recipe_result(frame, recipe) .. '</td>'
                if include_machine_column then
                    out[#out + 1] = '<td>' .. item.renderItemWithArgs(frame, { recipe[field_map.machine], class = 'block' }) .. '</td>'
                end
                if has_cooking_method then
                    local cooking_method = recipe_cooking_method(recipe)
                    if cooking_method == '' then
                        cooking_method = '—'
                    end
                    out[#out + 1] = '<td>' .. cooking_method .. '</td>'
                end
                out[#out + 1] = '<td>' .. render_material_count_list(counts) .. '</td>'
                out[#out + 1] = '<td>' .. render_recipe_materials(frame, recipe[field_map.materials], current_item_id) .. '</td>'
                out[#out + 1] = '</tr>'
            end
        end
    end

    out[#out + 1] = '</table>'
    return table.concat(out, '')
end

local function append_recipe_usage_sections(out, frame, recipe_ids, current_item_id, section_title, render_table)
    local featured_groups, remaining_ids = split_recipe_groups_by_machine(recipe_ids)

    out[#out + 1] = '<h3>' .. section_title .. '</h3>'
    for _, group in ipairs(featured_groups) do
        local heading = machine_group_heading(frame, group.machine_id)
        if heading ~= '' then
            out[#out + 1] = heading
        end
        out[#out + 1] = render_table(frame, group.recipe_ids, current_item_id, false)
    end

    if #remaining_ids > 0 then
        if #featured_groups > 0 then
            out[#out + 1] = '<h4>其他机器</h4>'
        end
        out[#out + 1] = render_table(frame, remaining_ids, current_item_id, true)
    end
end

function p.itemRecipes(frame)
    local item_key = common.getArg(frame, 1, '')
    if item_key == '' then
        item_key = common.getCurrentTitleText()
    end

    local item_id = item.getIdByKey(item_key)
    if not item_id or item_id == '' then
        return ''
    end

    load_recipes()
    local item_data = item_recipe_cache[mw.ustring.lower(item_id)]
    if not item_data then
        return ''
    end

    local product_ids = item_data.p or {}
    local ingredient_ids = item_data.i or {}
    local product_count = count_array(product_ids)
    local ingredient_count = count_array(ingredient_ids)
    if product_count == 0 and ingredient_count == 0 then
        return ''
    end

    local out = {}
    out[#out + 1] = render_recipe_usage_note()

    if product_count > 0 then
        append_recipe_usage_sections(out, frame, product_ids, item_id, '制作配方', function(current_frame, recipe_ids_for_table, _, include_machine_column)
            return render_product_recipe_table(current_frame, recipe_ids_for_table, include_machine_column)
        end)
    end

    if ingredient_count > 0 then
        if #out > 0 then
            out[#out + 1] = ''
        end
        append_recipe_usage_sections(out, frame, ingredient_ids, item_id, '用于制作', render_ingredient_recipe_table)
    end

    local css_out = css.quickCall('Item') or ''
    return css_out .. table.concat(out, '\n')
end

function p.machineAsIngredient(frame)
    local key = common.getArg(frame, 1, '')
    local record = find_record(key)
    if not record then
        return ''
    end

    local machine_id = get_record_field(record, key, 'id')
    if not machine_id or machine_id == '' then
        return ''
    end

    load_recipes()
    local item_data = item_recipe_cache[mw.ustring.lower(machine_id)]
    if not item_data or not item_data.i or count_array(item_data.i) == 0 then
        return ''
    end

    local out = {}
    out[#out + 1] = '=== 用于制作 ==='
    out[#out + 1] = '<table class="wikitable">'
    out[#out + 1] = '<tr><th>产物</th><th>所需数量</th></tr>'

    for _, rid in ipairs(item_data.i) do
        local recipe = recipe_list[rid + 1]
        if recipe then
            local counts = material_counts_for_item(recipe[field_map.materials], machine_id)
            if #counts > 0 then
                out[#out + 1] = '<tr>'
                out[#out + 1] = '<td>' .. render_recipe_result(frame, recipe) .. '</td>'
                out[#out + 1] = '<td>' .. render_material_count_list(counts) .. '</td>'
                out[#out + 1] = '</tr>'
            end
        end
    end

    out[#out + 1] = '</table>'
    local css_out = css.quickCall('Item') or ''
    return css_out .. table.concat(out, '\n')
end

return p