欢迎大家来到沙石镇时光中文维基!本站编辑权限开放,欢迎加入中文维基 QQ 群「沙海时光」:372816689
目前正在进行全站数据更新,期间可能会存在显示异常的问题。

全站通知:

模块:Craft

来自沙石镇时光维基
跳到导航 跳到搜索

此模块的文档可以在模块:Craft/doc创建

local Helper      = require("Module:Helper")
local ItemHelper  = require("Module:ItemHelper")
local ItemModule  = require("Module:Item")
local Sort        = require("Module:Sort")
local Store       = require("Module:Store")
local cache       = require("mw.ext.LuaCache")

local Creation         = Helper.LazyLoad("Module:AssetCreationConfigCreation")
local CreationPart     = Helper.LazyLoad("Module:AssetCreationPartConfigCreationPart")
local ItemPrototype    = Helper.LazyLoad("Module:AssetItemPrototypeItem")
local Machine          = Helper.LazyLoad("Module:AssetMachineConfigMachine")
local Synthetics       = Helper.LazyLoad("Module:AssetSyntheticConfigSynthetics")
local RecycleConfigs   = Helper.LazyLoad("Module:AssetRecycleConfigs")
local UseItemDatas     = Helper.LazyLoad("Module:AssetUseItemDatas")
local ItemMonsters     = Helper.LazyLoad("Module:AssetItemMonsters")
local ScreeningConfigs = Helper.LazyLoad("Module:AssetScreeningConfigs")
local CookingConfigs   = Helper.LazyLoad("Module:AssetCookingConfigs")
local RestoreConfigs   = Helper.LazyLoad("Module:AssetRestoreConfigs")
local ItemSource       = Helper.LazyLoad("Module:AssetItemSource")

local expandTemplate = Helper.ExpandTemplate
local KEY_PREFIX = "Module:Craft"
local EXP_TIME = 172800 -- 保持CN Updated.lua的值

local unimplementedMonsters = {
    ["猪鼻猫头鹰"] = true
}

local MachineTag = {
    [0] = 0,
    [1] = 1,
    [2] = 2,
    [3] = 3,
    [4] = 4,
    [5] = 5,
    [6] = 6,
    [7] = 7,
    [8] = 8,
    [9] = 9,
    [10] = 10,
    [11] = 11,
    [12] = 12,
    [13] = 13,
    [14] = 14,
    [15] = 15,
    [16] = 16,
    [17] = 17,
    [18] = 18,
    [19] = 19,
    [20] = 20,
    [21] = 21,
    [22] = 22,
    [23] = 23,
    None = 0,
    SyntheticTable = 1,
    AssemblyTable = 2,
    ExperimentTable = 3,
    Furnace = 4,
    Cutting = 5,
    Refine = 6,
    Screening = 7,
    EnergySupply = 8,
    WaterSupply = 9,
    RepairTable = 10,
    Recycle = 11,
    Antique = 12,
    DryingStand = 13,
    Tailor = 14,
    Stirrer = 15,
    Weapons = 16,
    Equipments = 17,
    Jewelry = 18,
    Food = 19,
    PlantingBox = 20,
    Irrigate = 21,
    Grinder = 22,
    WaterCollection = 23
}

local stations = {}

for itemId, machineConfig in pairs(Machine) do
    local tag = MachineTag[machineConfig.tag]

    if not (stations[tag]) then
        stations[tag] = {}
    end

    stations[tag][machineConfig.level] = ItemHelper.getName(itemId)
end

local stationTypes = {
    [1] = "Worktable",
    [2] = "Assembly Station",
    [4] = "Furnace",
    [5] = "Processor",
    [7] = "Ore Refinery",
    [11] = "Recycler",
    [13] = "Drying Rack",
    [14] = "Tailoring Machine",
    [15] = "Blender",
    [16] = "Forging Machine",
    [18] = "Jewelry Processing Machine",
    [19] = "Cooking Station",
    [22] = "Grinder"
}

local cookingTypes = {
    [0] = "Steamer",
    [1] = "Cooking Pot",
    [2] = "Wok",
    [3] = "Oven"
}

local CookerType = {
    Steamer = 0,
    [0] = 0,
    Pot = 1,
    [1] = 1,
    FryingPan = 2,
    [2] = 2,
    Oven = 3,
    [3] = 3
}

-- APIs
local p = {}

-- 从Original Updated.lua新增的函数
p.ItemObtainingBuildTable = function(frame)
    local itemName = frame.args[1] or frame.args.item
    local cacheKey = KEY_PREFIX .. "ItemObtainingSynthetic" .. itemName

    if (cache.get(cacheKey)) then
    	local result = cache.get(cacheKey)
        result = mw.getCurrentFrame():preprocess(result)
        return result
    end

    local itemId = ItemHelper.getId(itemName)
    local result = ""

    if (itemId) then
        for _, recipe in pairs(Synthetics) do
            if recipe.itemId == itemId then
                local args = recipeToArgs(recipe)

                result = result .. Helper.ExpandTemplate("ItemObtainingCraft/table", args)
                break
            end
        end
    end

    if (result ~= "") then
        cache.set(cacheKey, result, EXP_TIME)
        result = mw.getCurrentFrame():preprocess(result)
        return result
    else
        error(itemName .. " is not craftable")
    end
end

-- 从Original Updated.lua新增的函数
p.ItemObtainingAssemblyTable = function(frame)
    local itemName = frame.args[1] or frame.args.item
    local cacheKey = KEY_PREFIX .. "ItemObtainingAssembly" .. itemName

    if (cache.get(cacheKey)) then
    	local result = cache.get(cacheKey)
        result = mw.getCurrentFrame():preprocess(result)
        return result
    end

    local itemId = ItemHelper.getId(itemName)
    local result = ""

    if (itemId) then
        for _, recipe in pairs(Creation) do
            if recipe.itemId == itemId then
                local args = assemblyRecipeToArgs(recipe)

                result = result .. Helper.ExpandTemplate("ItemObtainingCraft/table", args)
                break
            end
        end
    end

    if (result ~= "") then
        cache.set(cacheKey, result, EXP_TIME)
        result = mw.getCurrentFrame():preprocess(result)
        return result
    else
        error(itemName .. " is not assembleable")
    end
end

-- 从Original Updated.lua新增的函数
p.ItemObtainingScreeningTable = function(frame)
    local itemName = frame.args[1] or frame.args.item
    local cacheKey = KEY_PREFIX .. "ItemObtainingScreening" .. itemName

    if (cache.get(cacheKey)) then
    	local result = cache.get(cacheKey)
        result = mw.getCurrentFrame():preprocess(result)
        return result
    end

    local itemId = ItemHelper.getId(itemName)
    local result = ""

    if (itemId) then
        for _, screeningConfig in pairs(ScreeningConfigs) do
            for _, itemResultDe in pairs(screeningConfig.itemResultDes) do
                if (itemResultDe.itemId == itemId) then
                    result = result .. Helper.ExpandTemplate("ItemObtainingScreening/table", {
                        [1] = itemName,
                        [2] = ItemHelper.getName(screeningConfig.id),
                        [3] = screeningConfig.inputCount,
                        [4] = screeningConfig.costMinutes,
                        [5] = screeningConfig.exp,
                        [6] = screeningConfig.proficiencyExp,
                        [7] = screeningConfig.itemResultDes[1]
                            and ItemHelper.getName(screeningConfig.itemResultDes[1].itemId),
                        [8] = screeningConfig.itemResultDes[2]
                            and ItemHelper.getName(screeningConfig.itemResultDes[2].itemId),
                        [9] = screeningConfig.itemResultDes[3]
                            and ItemHelper.getName(screeningConfig.itemResultDes[3].itemId),
                        [10] = screeningConfig.itemResultDes[4]
                            and ItemHelper.getName(screeningConfig.itemResultDes[4].itemId),
                        [11] = screeningConfig.itemResultDes[5]
                            and ItemHelper.getName(screeningConfig.itemResultDes[5].itemId),
                        [12] = screeningConfig.itemResultDes[6]
                            and ItemHelper.getName(screeningConfig.itemResultDes[6].itemId)
                    })
                    break
                end
            end
        end
    end

    if (result ~= "") then
        cache.set(cacheKey, result, EXP_TIME)
        result = mw.getCurrentFrame():preprocess(result)
        return result
    else
        error(itemName .. " is not obtainable from screening")
    end
end

p.ItemObtainingTable = function(frame)
    local itemName = frame.args[1] or frame.args.item
    local cacheKey = KEY_PREFIX .. "ItemObtainingTable" .. itemName

    if (cache.get(cacheKey)) then
    	local result = cache.get(cacheKey)
        result = mw.getCurrentFrame():preprocess(result)
        if string.find(result, "Item cannot be cooked") then
        	result = ""
        else
        	return result
        end
    end

    local template = frame.args.template or "ItemObtainingCraft"
    local itemId = ItemHelper.getId(itemName)
    local result = ""

    if (itemId) then
        local itemSource = ItemSource[itemId]

        if (itemSource) then
            local showHeaders = true

            -- if (getTableLength(itemSource) <= 2) then
            --     showHeaders = false
            -- else
            --     showHeaders = true
            -- end

            for _, recipe in pairs(Synthetics) do
                if recipe.itemId == itemId then
                    local args = recipeToArgs(recipe)

                    if not string.find(result, "===制作===") then
                        result = result .. "\n===制作===\n"
                    end


                    result = result .. Helper.ExpandTemplate(template, args)
                    break
                end
            end

            for _, recipe in pairs(Creation) do
                if recipe.itemId == itemId then
                    local args = assemblyRecipeToArgs(recipe)
                    if not string.find(result, "===制作===") then
                        result = result .. "\n===制作===\n"
                    end

                    result = result .. Helper.ExpandTemplate(template, args)
                    break
                end
            end

            if (RestoreConfigs[itemId] ~= nil) then
                if not string.find(result, "===制作===") then
                    result = result .. "\n===制作===\n"
                end

                result = result .. Helper.ExpandTemplate("ItemObtainingRelic/table", {
                    [1] = itemName
                })
            end

            local resultFromScreeningConfigs = ""

            for _, screeningConfig in pairs(ScreeningConfigs) do
                for _, itemResultDe in pairs(screeningConfig.itemResultDes) do
                    if (itemResultDe.itemId == itemId) then
                        resultFromScreeningConfigs = resultFromScreeningConfigs .. "\n"
                                                         .. Helper.ExpandTemplate("RefineryRow", {
                                title = ItemHelper.getName(screeningConfig.id),
                                refineamt = screeningConfig.inputCount,
                                refinetime = screeningConfig.costMinutes,
                                refine1drop = screeningConfig.itemResultDes[1]
                                    and ItemHelper.getName(screeningConfig.itemResultDes[1].itemId),
                                refine2drop = screeningConfig.itemResultDes[2]
                                    and ItemHelper.getName(screeningConfig.itemResultDes[2].itemId),
                                refine3drop = screeningConfig.itemResultDes[3]
                                    and ItemHelper.getName(screeningConfig.itemResultDes[3].itemId),
                                refine4drop = screeningConfig.itemResultDes[4]
                                    and ItemHelper.getName(screeningConfig.itemResultDes[4].itemId),
                                refine5drop = screeningConfig.itemResultDes[5]
                                    and ItemHelper.getName(screeningConfig.itemResultDes[5].itemId),
                                refine6drop = screeningConfig.itemResultDes[6]
                                    and ItemHelper.getName(screeningConfig.itemResultDes[6].itemId),
                                refine1amt = screeningConfig.itemResultDes[1] and screeningConfig.itemResultDes[1].des,
                                refine2amt = screeningConfig.itemResultDes[2] and screeningConfig.itemResultDes[2].des,
                                refine3amt = screeningConfig.itemResultDes[3] and screeningConfig.itemResultDes[3].des,
                                refine4amt = screeningConfig.itemResultDes[4] and screeningConfig.itemResultDes[4].des,
                                refine5amt = screeningConfig.itemResultDes[5] and screeningConfig.itemResultDes[5].des,
                                refine6amt = screeningConfig.itemResultDes[6] and screeningConfig.itemResultDes[6].des,
                                refine1prob = screeningConfig.itemResultDes[1]
                                    and screeningConfig.itemResultDes[1].chance,
                                refine2prob = screeningConfig.itemResultDes[2]
                                    and screeningConfig.itemResultDes[2].chance,
                                refine3prob = screeningConfig.itemResultDes[3]
                                    and screeningConfig.itemResultDes[3].chance,
                                refine4prob = screeningConfig.itemResultDes[4]
                                    and screeningConfig.itemResultDes[4].chance,
                                refine5prob = screeningConfig.itemResultDes[5]
                                    and screeningConfig.itemResultDes[5].chance,
                                refine6prob = screeningConfig.itemResultDes[6]
                                    and screeningConfig.itemResultDes[6].chance,
                                refineexp = screeningConfig.exp
                            })
                        break
                    end
                end
            end

            if (resultFromScreeningConfigs ~= "") then
                if not string.find(result, "===制作===") then
                    result = result .. "\n===制作===\n"
                end

                result = result .. Helper.ExpandTemplate("RefineryTable", {
                    [1] = "header"
                })

                result = result .. resultFromScreeningConfigs

                result = result .. "\n" .. Helper.ExpandTemplate("RefineryTable", {
                    [1] = "footer"
                })
            end

            for _, cookingConfig in pairs(CookingConfigs) do
                if (cookingConfig.outItemId == itemId) then
                    if not string.find(result, "===烹饪===") then
                        result = result .. "\n===烹饪===\n"
                    end

                    result = result .. Helper.ExpandTemplate("ItemObtainingCookingStation/table", {
                        [1] = itemName
                    })
                    break
                end
            end

            if (ItemMonsters[itemId] ~= nil) then
                if not string.find(result, "===战斗===") then
                    result = result .. "\n===战斗===\n"
                end

                result = result .. "以下怪物可能会掉落" .. itemName .. ":<ul>"

                for _, monster in pairs(ItemMonsters[itemId].monsterProtos) do
                    if not unimplementedMonsters[monster.monsterName] then
                        result = result .. "<li>" .. Helper.ExpandTemplate("Monster", {
                            monster.monsterName
                        }) .. "[[" .. monster.monsterName .. "]]</li>"
                    end
                end

                result = result .. "</ul>\n"
                -- result = result .. "<div class='w-50'>{{Map:Monster main}}</div>\n"
            end

            local stores = Store.getItemFromStores(itemId)

            if (#stores > 0) then
            if not string.find(result, "===商店===") then
                    result = result .. "\n===商店===\n"
                end

                result = result .. Helper.ExpandTemplate("ItemObtainingPurchase", {
                    [1] = itemName
                })
            end

            if (result ~= "") then
                cache.set(cacheKey, result, EXP_TIME)
                result = mw.getCurrentFrame():preprocess(result)
                return result
            else
                error(itemName .. " is not craftable")
            end
        else
        end
    else
    end
end

p.ItemRecycleTable = function(frame)
    local itemName = frame.args[1] or frame.args.item
    local template = frame.args.template or "RecyclerTable"
    local itemId = ItemHelper.getId(itemName)
    local result = ""
    local header = nil

    for _, recycleConfig in pairs(RecycleConfigs) do
        for _, itemResultDes in pairs(recycleConfig.itemResultDes) do
            if (itemResultDes.itemId == itemId) then
                if (header == nil) then
                    header = Helper.ExpandTemplate(template, {
                        [1] = "header",
                        station = stations[11][recycleConfig.machineLevel]
                    })
                end

                local scrapName = ItemHelper.getName(recycleConfig.id)

                local recyclerTableRow = {
                    title = scrapName,
                    recycletime = recycleConfig.costMinutes,
                    exp = recycleConfig.exp,
                    proficiency = recycleConfig.proficiencyExp
                }
                
				local function formatChance(chance)
				    local formatted = chance * 100
				    if formatted == math.floor(formatted) then
				        return string.format("%d", formatted) .. "%"
				    else
				        return string.format("%.2f", formatted) .. "%"
				    end
				end
                
				if (recycleConfig.itemResultDes[1]) then
				    recyclerTableRow.recycle1drop = ItemHelper.getName(recycleConfig.itemResultDes[1].itemId)
				    recyclerTableRow.recycle1amt = recycleConfig.itemResultDes[1].des
				    recyclerTableRow.recycle1prob = formatChance(recycleConfig.itemResultDes[1].chance)
				end
				
				if (recycleConfig.itemResultDes[2]) then
				    recyclerTableRow.recycle2drop = ItemHelper.getName(recycleConfig.itemResultDes[2].itemId)
				    recyclerTableRow.recycle2amt = recycleConfig.itemResultDes[2].des
				    recyclerTableRow.recycle2prob = formatChance(recycleConfig.itemResultDes[2].chance)
				end
				
				if (recycleConfig.itemResultDes[3]) then
				    recyclerTableRow.recycle3drop = ItemHelper.getName(recycleConfig.itemResultDes[3].itemId)
				    recyclerTableRow.recycle3amt = recycleConfig.itemResultDes[3].des
				    recyclerTableRow.recycle3prob = formatChance(recycleConfig.itemResultDes[3].chance)
				end
				
				if (recycleConfig.itemResultDes[4]) then
				    recyclerTableRow.recycle4drop = ItemHelper.getName(recycleConfig.itemResultDes[4].itemId)
				    recyclerTableRow.recycle4amt = recycleConfig.itemResultDes[4].des
				    recyclerTableRow.recycle4prob = formatChance(recycleConfig.itemResultDes[4].chance)
				end
				
				if (recycleConfig.itemResultDes[5]) then
				    recyclerTableRow.recycle5drop = ItemHelper.getName(recycleConfig.itemResultDes[5].itemId)
				    recyclerTableRow.recycle5amt = recycleConfig.itemResultDes[5].des
				    recyclerTableRow.recycle5prob = formatChance(recycleConfig.itemResultDes[5].chance)
				end
				
				if (recycleConfig.itemResultDes[6]) then
				    recyclerTableRow.recycle6drop = ItemHelper.getName(recycleConfig.itemResultDes[6].itemId)
				    recyclerTableRow.recycle6amt = recycleConfig.itemResultDes[6].des
				    recyclerTableRow.recycle6prob = formatChance(recycleConfig.itemResultDes[6].chance)
				end

                result = result .. "\n" .. Helper.ExpandTemplate("RecyclerTableRow", recyclerTableRow)
                break
            end
        end
    end

    result = result .. "\n" .. Helper.ExpandTemplate(template, {
        [1] = "footer"
    })
    return header .. result
end

p.ItemUsageTable = function(frame)
    local itemName = frame.args[1]
    local stationType = frame.args[2]:lower()
    local itemId = ItemHelper.getId(itemName)
    local recipes = {}
    for _, recipe in pairs(Synthetics) do
        if stations[MachineTag[recipe.fromMachineType]] == nil then
            error("Missing machine type: " .. MachineTag[recipe.fromMachineType])
        end
        local station = stations[MachineTag[recipe.fromMachineType]][1]:lower()
        local goodType = (station:sub(1, #stationType) == stationType)
        local goodLevel = (recipe.fromMachineLevel < 99)
        if (goodType and goodLevel)
            or (MachineTag[recipe.fromMachineType] == 16 and recipe.fromMachineLevel == 2 and stationType
                == "intermediate forging machine") then
            for _, mat in ipairs(recipe.rawMaterials) do
                if mat.x == itemId then
                    table.insert(recipes, recipe)
                end
            end
        end
    end
    if (string.find(stationType, "assembly")) then
        for _, recipe in pairs(Creation) do
            for _, partId in ipairs(recipe.partIds) do
                if CreationPart[partId].material.x == itemId then
                    table.insert(recipes, recipe)
                end
            end
        end
    end
    return formatUsageTable(stationType, recipes)
end

p.Fill = function(frame)
    local template = frame.args[1]
    local itemName = frame.args[2]
    local itemId = ItemHelper.getId(itemName)
    for _, recipe in pairs(Synthetics) do
        if recipe.itemId == itemId then
            local args = recipeToArgs(recipe)
            return expandTemplate(template, args)
        end
    end
    error("Not craftable: " .. itemName)
end

p.StationTableContent = function(frame)
    local rowTemplate = frame.args.row
    local stationName = frame.args.station
    local onlyCurrentLevel = frame.args.only_current_level and frame.args.only_current_level ~= ""
    local stationId = ItemHelper.getId(stationName)
    local station = Machine[stationId]
    local unlockedRecipes = {}

    for _, s in pairs(Machine) do
        if s.tag == station.tag and s.level <= station.level then
            for _, recipeId in ipairs(s.unlockBlueprintIds) do
                unlockedRecipes[recipeId] = true
            end
        end
    end

    -- to recover order
    local recipeIds = {}

    for _, recipe in pairs(Synthetics) do
        table.insert(recipeIds, recipe.id)
    end

    table.sort(recipeIds)
    local rowArgs = {}

    for _, recipeId in ipairs(recipeIds) do
        local recipe = Synthetics[recipeId]
        local include = MachineTag[recipe.fromMachineType] == MachineTag[station.tag]

        if onlyCurrentLevel then
            include = include and recipe.fromMachineLevel == station.level
        else
            include = include and recipe.fromMachineLevel <= station.level
        end
        include = include and ItemHelper.isImplemented(recipe.itemId)

        if include then
            local args = recipeToArgs(recipe)
            local unlocked = unlockedRecipes[recipe.itemId]
            if not unlocked and MachineTag[recipe.fromMachineType] == 1 and recipe.fromMachineLevel <= 1 then
                local item = ItemPrototype[recipe.itemId]
                for _, tag in ipairs(item.itemTag) do
                    if tag == 85 then
                        unlocked = true
                    end
                end
            end
            if unlocked then
                args.need_book = ""
            else
                args.need_book = "1"
            end
            table.insert(rowArgs, {
                key = recipe.orderId,
                args = args,
                id = recipe.id
            })
        end
    end

    Sort.sortWithKey(rowArgs, "key")
    local rows = {}
    for i, args in ipairs(rowArgs) do
        rows[i] = Helper.ExpandTemplate(rowTemplate, args.args)
    end
    return table.concat(rows, "\n|-\n")
end

p.UsageTable = function(frame)
    local itemId = ItemHelper.getId(frame.args.item)
    local rowArgs = {}
    local minStationLevel = {}

    for _, recipe in pairs(Synthetics) do
        if recipe.fromMachineLevel < 10 and ItemHelper.isImplemented(recipe.itemId) then
            for _, mat in ipairs(recipe.rawMaterials) do
                if mat.x == itemId then
                    table.insert(rowArgs, recipeToArgs(recipe))
                    local typeId = MachineTag[recipe.fromMachineType]
                    if minStationLevel[typeId] == nil then
                        minStationLevel[typeId] = recipe.fromMachineLevel
                    else
                        minStationLevel[typeId] = math.min(minStationLevel[typeId], recipe.fromMachineLevel)
                    end
                    break
                end
            end
        end
    end

    for _, recipe in pairs(Creation) do
        if ItemHelper.isImplemented(recipe.itemId) then
            for _, partId in ipairs(recipe.partIds) do
                if CreationPart[partId].material.x == itemId then
                    table.insert(rowArgs, assemblyRecipeToArgs(recipe))
                    if minStationLevel[2] == nil then
                        minStationLevel[2] = recipe.fromMachineLevel
                    else
                        minStationLevel[2] = math.min(minStationLevel[2], recipe.fromMachineLevel)
                    end
                    break
                end
            end
        end
    end

    table.sort(rowArgs, function(a, b)
        if a.sort_key ~= b.sort_key then
            return a.sort_key < b.sort_key
        else
            return a.item < b.item
        end
    end)

    local ret = ""
    local firstTitle

    for i = 1, 22 do
        if minStationLevel[i] ~= nil then
            local station = stations[i][math.max(minStationLevel[i], 1)]
            local stationType = stationTypes[i]

            local title = Helper.ExpandTemplate(frame.args.title, {
                station
            })

            if firstTitle ~= nil then
                ret = firstTitle .. "\n" .. ret
                firstTitle = nil
            end
            if ret == "" then
                firstTitle = title
            else
                ret = ret .. "\n" .. title
            end

            local templateType = "station"
            if i == 1 then
                templateType = "worktable"
            elseif i == 2 then
                templateType = "assembly"
            elseif i == 19 then
                templateType = "cooking"
            end
            local headerTemplate = frame.args[templateType .. "_header"]
            local rowTemplate = frame.args[templateType .. "_row"]
            ret = ret .. "\n" .. Helper.ExpandTemplate(headerTemplate, {
                station = station,
                station_type = stationType
            })
            for _, args in ipairs(rowArgs) do
                if args.station_type == stationType then
                    ret = ret .. "\n|-\n" .. Helper.ExpandTemplate(rowTemplate, args)
                end
            end
            ret = ret .. "\n|}"
        end
    end
    return ret
end

p.UsageTableContent = function(frame)
    local rowTemplate = frame.args.row
    local itemName = frame.args.item
    local stationShortName = frame.args.station:lower()

    local itemId = ItemHelper.getId(itemName)

    local stationType
    for _, station in pairs(Machine) do
        local stationName = ItemHelper.getName(station.id):lower()
        if stationName:find(stationShortName, nil, true) ~= nil then
            stationType = station.tag
            break
        end
    end
    if stationType == nil then
        error("Unknown station: " .. stationShortName)
    end

    local rowArgs = {}
    for _, recipe in pairs(Synthetics) do
        if MachineTag[recipe.fromMachineType] == stationType and recipe.fromMachineLevel < 10 then
            for _, mat in ipairs(recipe.rawMaterials) do
                if mat.x == itemId then
                    local args = recipeToArgs(recipe)
                    table.insert(rowArgs, {
                        key = recipe.orderId,
                        args = args
                    })
                    break
                end
            end
        end
    end

    table.sort(rowArgs, function(a, b)
        if a.key ~= b.key then
            return a.key < b.key
        else
            return a.args.item < b.args.item
        end
    end)

    local rows = {}
    for i, args in ipairs(rowArgs) do
        rows[i] = Helper.ExpandTemplate(rowTemplate, args.args)
    end
    return table.concat(rows, "\n|-\n")
end

-- Functions

function formatUsageTable(stationType, recipes)
    local template = "CraftStationTable"

    if stationType == "worktable" then
        template = "WorktableTable"
    elseif (string.find(stationType, "assembly")) then
        template = "AssemblyTable"
    end
    local header

    if (ItemHelper.isFood(recipes[1].itemId)) then
        header = expandTemplate(template, {
            [1] = "cook",
            station = ItemHelper.restoreCase(stationType),
            level = recipes[1].fromMachineLevel
        })
    else
        header = expandTemplate(template, {
            [1] = "header",
            station = ItemHelper.restoreCase(stationType),
            level = recipes[1].fromMachineLevel
        })
    end
    local footer = expandTemplate(template, {
        [1] = "footer"
    })
    local rows = {}

    for _, recipe in ipairs(recipes) do
        local row

        if (string.find(stationType, "assembly")) then
            row = expandTemplate(template .. "Row", assemblyRecipeToArgs(recipe))
        elseif (ItemHelper.isFood(recipe.itemId)) then
            row = expandTemplate(template .. "Row/Cooking", recipeToArgs(recipe))
        else
            row = expandTemplate(template .. "Row2", recipeToArgs(recipe))
        end

        table.insert(rows, row)
    end

    return header .. "\n" .. table.concat(rows, "\n") .. "\n" .. footer
end

function recipeToArgs(recipe)
    -- if not ItemHelper.isImplemented(recipe.itemId) then
    --    return nil
    -- end

    if stations[MachineTag[recipe.fromMachineType]] == nil then
        error("Missing machine type: " .. MachineTag[recipe.fromMachineType])
    end

    local args = {}
    args.exp = tostring(recipe.exp)
    args.proficiency = tostring(recipe.proficiencyExp)
    args.sell = tostring(ItemModule.getSellPrice(recipe.itemId) or "")
    args.station = stations[MachineTag[recipe.fromMachineType]][math.max(recipe.fromMachineLevel, 1)]
    args.station_type = stationTypes[MachineTag[recipe.fromMachineType]]
    args.time = formatTime(recipe.makeTime)
    args.time_sort = tostring(recipe.makeTime)
    args.item = ItemHelper.getName(recipe.itemId)
    args.title = args.item
    args.station_is_worktable = ""
    args.crafttime = recipe.makeTime
    args.cookingType = cookingTypes[recipe.cookingType]

    if MachineTag[recipe.fromMachineType] == 1 then
        args.station_is_worktable = "1"
    end

    local tabs = {
        [0] = "Assembly",
        ["Creation"] = "Assembly",
        [1] = "Equipment",
        ["Equipment"] = "Equipment",
        [2] = "Food",
        ["Food"] = "Food",
        [3] = "Furniture",
        ["Furniture"] = "Furniture",
        [4] = "Resources",
        ["Material"] = "Resources",
        [5] = "Gift",
        ["HiddenHandBook"] = "Gift",
        -- 6
        ["Refine"] = "Refine",
        -- 7
        ["MachinePlugin"] = "MachinePlugin",
        [8] = "Weapons",
        ["Weapon"] = "Weapons",
        [9] = "Herbs",
        ["Seasoning"] = "Herbs",
        [10] = "Ingredients",
        ["Ingredient"] = "Ingredients",
        -- 11
        ["ExperimentCore"] = "ExperimentCore",
        -- 12
        ["Max"] = "Max"
    }

    local item = ItemPrototype[recipe.itemId]

    if (item.tags and item.tags[1]) then
        args.tab = tabs[item.tags[1]]
    end

    if recipe.itemCount == 1 then
        args.amtcrafted = ""
    else
        args.amtcrafted = tostring(recipe.itemCount)
    end

    local mats = {}
    local materials = {}

    for _, mat in ipairs(recipe.rawMaterials) do
        if (type(mat) == "number") then
            local name = ItemHelper.getName(mat)
            local matInfo = ItemHelper.addIconAndLink(name) .. " (1)"
            table.insert(mats, matInfo)
            table.insert(materials, {
                name = name,
                amt = 1
            })
        else
            local name = ItemHelper.getName(mat.x)
            local matInfo = ItemHelper.addIconAndLink(name) .. " (" .. mat.y .. ")"
            table.insert(mats, matInfo)
            table.insert(materials, {
                name = name,
                amt = mat.y
            })
        end
    end

    if (materials[1] ~= nil) then
        args.mat1 = materials[1].name
        args.mat1amt = materials[1].amt
    end

    if (materials[2] ~= nil) then
        args.mat2 = materials[2].name
        args.mat2amt = materials[2].amt
    end

    if (materials[3] ~= nil) then
        args.mat3 = materials[3].name
        args.mat3amt = materials[3].amt
    end

    if (materials[4] ~= nil) then
        args.mat4 = materials[4].name
        args.mat4amt = materials[4].amt
    end

    if (materials[5] ~= nil) then
        args.mat5 = materials[5].name
        args.mat5amt = materials[5].amt
    end

    if (materials[6] ~= nil) then
        args.mat6 = materials[6].name
        args.mat6amt = materials[6].amt
    end

    if (UseItemDatas[recipe.itemId] ~= nil) then
        args.effects = UseItemDatas[recipe.itemId].effects
    end

    args.materials = table.concat(mats, "<br>")

    if (recipe.orderId == nil) then
        args.sort_key = recipe.itemId
    else
        args.sort_key = recipe.orderId
    end

    return args
end

function assemblyRecipeToArgs(recipe)
    -- if not ItemHelper.isImplemented(recipe.itemId) then
    --    return nil
    -- end

    local args = {}
    args.exp = tostring(recipe.rewardExp)
    args.proficiency = tostring(recipe.rewardProficiencyExp)
    args.sell = tostring(ItemModule.getSellPrice(recipe.itemId) or "")
    args.station = stations[2][recipe.fromMachineLevel]
    args.station_type = stationTypes[2]
    args.time = "0"
    args.time_sort = "0"
    args.item = ItemHelper.getName(recipe.itemId)
    args.title = args.item
    args.station_is_worktable = ""
    args.tab = ""
    args.amtcrafted = ""
    local mats = {}
    local materials = {}
    for _, partId in ipairs(recipe.partIds) do
        local mat = CreationPart[partId].material
        local name = ItemHelper.getName(mat.x)
        local matInfo = ItemHelper.addIconAndLink(name) .. " (" .. mat.y .. ")"
        local materialInfo = {
            name = name,
            amt = mat.y
        }
        table.insert(mats, matInfo)
        table.insert(materials, materialInfo)
    end
    if (materials[1] ~= nil) then
        args.mat1 = materials[1].name
        args.mat1amt = materials[1].amt
    end
    if (materials[2] ~= nil) then
        args.mat2 = materials[2].name
        args.mat2amt = materials[2].amt
    end
    if (materials[3] ~= nil) then
        args.mat3 = materials[3].name
        args.mat3amt = materials[3].amt
    end
    if (materials[4] ~= nil) then
        args.mat4 = materials[4].name
        args.mat4amt = materials[4].amt
    end
    if (materials[5] ~= nil) then
        args.mat5 = materials[5].name
        args.mat5amt = materials[5].amt
    end
    args.materials = table.concat(mats, "<br>")
    args.sort_key = -recipe.handbookPriority
    return args
end

-- Helpers

function formatTime(time)
    time = math.floor(time + 0.5)
    local h = math.floor(time / 60)
    local m = time % 60
    local parts = {}
    if h > 0 then
        table.insert(parts, h .. "h")
    end
    if m > 0 then
        table.insert(parts, m .. "m")
    end
    return table.concat(parts, " ")
end

function getTableLength(t)
    local count = 0

    for _ in pairs(t) do
        count = count + 1
    end

    return count
end

-- Debug

p.debug = function()
    Helper.ExpandTemplate = Helper.ExpandTemplateDebug

    local args = {
        item = "大剧院",
        worktable_header = "ItemUsageCraft/header",
        worktable_row = "ItemUsageCraft/row",
        assembly_header = "ItemUsageCraft/header",
        assembly_row = "ItemUsageCraft/row",
        station_header = "ItemUsageCraft/header",
        station_row = "ItemUsageCraft/row",
        cooking_header = "ItemUsageCraft/header",
        cooking_row = "ItemUsageCraft/row",
        title = "h4",
        row = "CraftingStationRecipeTable/row",
        station = "Tailoring Machine lv1"
    }

    local frame = {
        args = args
    }

    local r = p.ItemObtainingTable(frame)
    mw.log(r)
end

p.clear_cache = function()
    local cacheKey

    for itemPrototypeId, itemPrototype in pairs(ItemPrototype) do
        local name = ItemHelper.getName(itemPrototypeId)

        if (name) then
            cacheKey = KEY_PREFIX .. "ItemObtainingTable" .. name
            cache.delete(cacheKey)
            mw.log(cacheKey)
        else
        end
    end
end

return p