Module:RenderRecipesTable

From Satisfactory Wiki
Jump to navigation Jump to search


This module is used to render recipe tables using the renderRecipesProducing, renderRecipesUsing and renderRecipesProducedIn functions.

Invocation

All arguments are unnamed and expect ClassNames. Wrapper templates are provided and preferred on content pages. Otherwise the module can be invoked directly.

Usage Module invocation Wrapper template
Recipes producing specified item(s) {{#invoke:RenderRecipesTable|renderRecipesProducing|...}} {{Recipes producing|...}}
Recipes using specified item(s) {{#invoke:RenderRecipesTable|renderRecipesUsing|...}} {{Recipes using|...}}
Recipes produced in specified building(s) {{#invoke:RenderRecipesTable|renderRecipesProducedIn|...}} {{Recipes produced in|...}}
Alternate recipes {{#invoke:RenderRecipesTable|renderAlternateRecipes}} -
Recipes produced in:
  • Craft Bench
  • Equipment Workshop
  • Build Gun
  • Customizer
{{#invoke:RenderRecipesTable|renderProducedInSpecial|...}} -

require("Module:DocsUtils")

local p = {}

-- hover tooltips
loc.tooltips = {
    ingredientsPerCycle = "How much of this resource is consumed every crafting cycle",
    ingredientsPerMinute = "How much of this resource has to be supplied to the machine every minute for 100% efficiency",
    productsPerCycle = "How much of this resource is produced every crafting cycle",
    productsPerMinute = "How much of this resource has to be withdrawn from the machine every minute for 100% efficiency",
    minMaxPower = "The power consumption of this machine when producing this recipe increases throughout the crafting cycle",
    manualCraftingRate = "Manual crafting rate"
}

-- table column headers
loc.columns = {
    recipe = "Recipe",
    ingredients = "Ingredients",
	duration = "Duration",
    producedIn = "Produced in",
    products = "Products",
    unlockedBy = "Unlocked by"
}

-- errors shown when no recipes are found in place of the recipe table
-- make sure to respect spaces
loc.errors = {
    listAnd = "and",
    listOr = "or",
    noRecipes = "No recipes found.",
    producing = " cannot be crafted.",
    using = "No recipe uses ",
    producedIn = "No recipes can be produced in ",
}

-- page links, may be wiki links
loc.links = {
    inCraftBench = "[[Craft Bench]]",
    inWorkshop = "[[Equipment Workshop]]",
    inBuildGun = "[[Build Gun]]",
    inCustomizer = "[[Customizer]]",
    alternate = "[[Hard Drive|Alternate]]",
    asByproduct = " As byproduct",
}

-- manual crafting hammer
loc.manualCrafting = "File:Manual crafting.png"

-- TEMP - Remove recipes using Customizer materials
-- - recipes - array of recipes
local function filterCustomizerRecipes(recipes)
    local filteredRecipes = {}
    for _,recipe in pairs(recipes) do
    	-- ily2, regex
        if not (recipe.inCustomizer or string.match(recipe.className, [=[^Recipe_Wall]=]) or string.match(recipe.className, [=[^Recipe_WallSet]=]) or string.match(recipe.className, [=[^Recipe_SteelWall]=]) or string.match(recipe.className, [=[^Recipe_Foundation_[^%s_]+_]=]) or string.match(recipe.className, [=[^Recipe_Ramp]=]) or string.match(recipe.className, [=[^Recipe_InvertedRamp]=]) or string.match(recipe.className, [=[^Recipe_Roof]=]) or string.match(recipe.className, [=[^Recipe_Pillar]=]) or string.match(recipe.className, [=[QuarterPipe]=]) or string.match(recipe.className, [=[Asphalt]=])) then
            table.insert(filteredRecipes, recipe)
        end
    end
    return filteredRecipes
end

-- Sort by inBuildGun descending, alternate descending, name ascending, experimental descending
-- - a - recipe
-- - b - recipe
local function recipeComparator(a, b)
    if a.inBuildGun ~= b.inBuildGun then
        return not a.inBuildGun and b.inBuildGun
    elseif a.alternate ~= b.alternate then
        return not a.alternate and b.alternate
    elseif a.name ~= b.name then
        return a.name < b.name
    else
        return not a.experimental and b.experimental
    end
end

-- Populates a table cell with a recipe's ingredients or products.
-- - recipe - table
-- - mode - string, either "ingredients" or "products"
local function renderItemsCell(recipe, mode)
    local html = [=[<div class="recipe-items" data-items-count="]=] .. #recipe[mode] .. [=[">]=]
    local showPerMinValues = not recipe.inBuildGun and not recipe.inCustomizer
    for _,item in pairs(recipe[mode]) do
        local itemLink, itemName  = getLinkAndName(item.item, recipe.stable, recipe.experimental)
        html = html .. [=[<div class="recipe-item"><span class="item-amount" title="]=] .. loc.tooltips[mode .. "PerCycle"] .. [=[">]=] .. formatNumber(item.amount) .. "&nbsp;&times; </span>[[" .. loc.filePage .. itemLink .. ".png|40px|link=" .. itemLink .. [=[]]<span class="item-name">]=] .. itemName .. [=[</span>]=] .. (showPerMinValues and ([=[<span class="item-minute" title="]=] .. loc.tooltips[mode .. "PerMinute"] .. [=[">]=] .. formatNumber((60.0/recipe.duration)*item.amount) ..  loc.units.pmin .. "</span>") or "") .. "</div>"
    end
    html = html .. "</div>"
    return html
end

-- Populates a table cell with a recipe's produced in buildings, plus the cycle duration or craft steps.
-- - recipe - table
-- - singleBuilding - boolean -  if the recipe is produced in a single building, do not render the building name
local function renderProducedInCell(recipe, singleBuilding)
    local html = ""
    if singleBuilding then
        html = recipe.duration .. loc.units.sec
        if recipe.minPower and recipe.maxPower then
            html = html .. [=[<br><span title="]=] .. loc.tooltips.minMaxPower .. [=[">]=] .. formatNumber(recipe.minPower) .. " - " .. formatNumber(recipe.maxPower) .. loc.units.mw .. "</span>"
        end
    else
        for _,building in pairs(recipe.producedIn) do
            local buildingLink, buildingName = getLinkAndName(building, recipe.stable, recipe.experimental)
            html = html .. [=[<div class="recipe-building">[[]=] .. buildingLink .. "|" .. buildingName .. [=[]]<br>]=] .. recipe.duration .. loc.units.sec
            if building == "Desc_HadronCollider_C" and recipe.minPower and recipe.maxPower then
                html = html .. [=[<br><span class="recipe-energy" title="]=] .. loc.tooltips.minMaxPower .. [=[">]=] .. formatNumber(recipe.minPower) .. " - " .. formatNumber(recipe.maxPower) .. loc.units.mw .. "</span>"
            end
            html = html .. "</div>"
        end
        if recipe.inCraftBench then
            html = html .. [=[<div class="recipe-building">]=] .. loc.links.inCraftBench .. [=[<br><span title="]=] .. loc.tooltips.manualCraftingRate .. " (" ..  formatNumber(240/math.ceil((recipe.manualCraftingMultiplier*recipe.duration)/2)) .. loc.units.pmin .. [=[)">[[]=] .. loc.manualCrafting .. [=[|16px|link=|class=invert-on-dark]]&nbsp;&times; ]=] .. formatNumber(math.ceil((recipe.manualCraftingMultiplier*recipe.duration)/2))  .. "</span></div>"
        end
        if recipe.inWorkshop then
            html = html .. [=[<div class="recipe-building">]=] .. loc.links.inWorkshop .. [=[<br><span title="]=] .. loc.tooltips.manualCraftingRate .. " (" ..  formatNumber(240/math.ceil((recipe.manualCraftingMultiplier*recipe.duration)/2)) .. loc.units.pmin .. [=[)">[[]=] .. loc.manualCrafting .. [=[|16px|link=|class=invert-on-dark]]&nbsp;&times; ]=] .. formatNumber(math.ceil((recipe.manualCraftingMultiplier*recipe.duration)/2))  .. "</span></div>"
        end
        if recipe.inBuildGun then
            html = html .. [=[<div class="recipe-building">]=] .. loc.links.inBuildGun .. [=[</div>]=]
        end
        if recipe.inCustomizer then
            html = html .. [=[<div class="recipe-building">]=] .. loc.links.inCustomizer .. [=[</div>]=]
        end
    end
    return html
end

-- Renders a recipe table from supplied recipes.
-- - recipes - array of recipes
-- - singleBuilding - boolean, building mode, changes the "Produced in" column
-- - targetProducts - array of product classNames, used for "recipes producing items", used for marking byproducts
-- - noRecipeMessage - string to return in place of the table if the recipes array is empty
local function renderRecipeTable(recipes, singleBuilding, targetProducts, noRecipeMessage)
    if #recipes == 0 then
        return noRecipeMessage or loc.errors.noRecipes
    end
    table.sort(recipes, recipeComparator)
    recipes = filterCustomizerRecipes(recipes)
    -- start table
    local html = [=[{| class="wikitable sortable recipetable"]=] .. "\n|-\n" .. [=[!]=] .. loc.columns.recipe .. [=[!!class="unsortable"|]=] .. loc.columns.ingredients .. [=[!!]=] .. (singleBuilding and loc.columns.duration or loc.columns.producedIn) .. [=[!!class="unsortable"|]=] .. loc.columns.products .. [=[!!]=] .. loc.columns.unlockedBy
    for _,recipe in pairs(recipes) do
        -- season check
        local season = ""
        for _,recipeSeason in ipairs(recipe.seasons) do
        	if recipeSeason == "ficsmas" then -- more seasons to be added later
        		season = season .. [=[<br><span class="recipe-badge recipe-season-]=] .. string.lower(recipeSeason) .. [=[">]=] .. string.upper(recipeSeason) .. [=[</span>]=]
        	end
    	end
        -- byproduct check
        local asByproduct = ""
        if targetProducts ~= nil then
            for i=2, #recipe.products do
                for _,targetProduct in pairs(targetProducts) do
                    if targetProduct == recipe.products[i].item then
                        asByproduct = asByproduct .. "[[" .. loc.filePage .. getLinkAndName(targetProduct, recipe.stable, recipe.experimental) .. ".png|16px|link=]]"
                    end
                end
            end
        end
        -- add table row
        html = html .. "\n|-\n|" .. recipe.name .. ((recipe.stable and not recipe.experimental) and [=[<br><span class="recipe-badge recipe-stable">]=] .. loc.branches.stable ..[=[</span>]=] or ((recipe.experimental and not recipe.stable) and [=[<br><span class="recipe-badge recipe-experimental">]=] .. loc.branches.experimental ..[=[</span>]=]) or "") .. season .. (recipe.alternate and ([=[<br><span class="recipe-badge recipe-alternate">]=] .. loc.links.alternate .. [=[</span>]=]) or "") .. ((asByproduct ~= "") and ([=[<br><span class="recipe-badge recipe-byproduct">]=] .. asByproduct .. loc.links.asByproduct .. [=[</span>]=]) or "") .. "||" .. renderItemsCell(recipe, "ingredients") .. "||" .. renderProducedInCell(recipe, singleBuilding) .. "||" .. renderItemsCell(recipe, "products") .. "||" .. recipe.unlockedBy
    end
    html = html .. "\n|-\n|}"
    return html
end

-- Render a table with recipes that yield specified products.
function p.renderRecipesProducing(frame)
    local recipes = {}
    local uniqueRecipes = {}
    local items = {}
    local seasonsEnabled = false
    
    for name,item in pairs(frame:getParent().args) do
    	if name == "seasons" and item ~= "" and item ~= "0" then
    		seasonsEnabled = true
		else
        	table.insert(items, item)
        end
    end
    
    -- go through all recipes, branch entries, products, check if at least one is in the items array
    for _,branchRecipes in pairs(recipesJSON) do
        for _,recipe in pairs(branchRecipes) do
        	if seasonsEnabled or #recipe.seasons == 0 then
	            for _,product in pairs(recipe.products) do
	                for _,item in pairs(items) do
	                    if item == product.item and uniqueRecipes[recipe.className] == nil then
	                        table.insert(recipes, recipe)
	                        uniqueRecipes[recipe.className] = true
	                        break
	                    end
	                end
	            end
            end
        end
    end
    return renderRecipeTable(recipes, false, items, "'''" .. table.concat(items, "'''" .. loc.errors.listAnd .. "'''") .. "'''" .. loc.errors.producing)
end

-- Render a table with recipes that use specified ingredients.
function p.renderRecipesUsing(frame)
    local recipes = {}
    local uniqueRecipes = {}
    local items = {}
    local seasonsEnabled = false
    
    for name,item in pairs(frame:getParent().args) do
    	if name == "seasons" and item ~= "" and item ~= "0" then
    		seasonsEnabled = true
		else
        	table.insert(items, item)
        end
    end
    
    -- go through all recipes, branch entries, ingredients, check if at least one is in the items array
    for _,branchRecipes in pairs(recipesJSON) do
        for _,recipe in pairs(branchRecipes) do
        	if seasonsEnabled or #recipe.seasons == 0 then
	            for _,ingredient in pairs(recipe.ingredients) do
	                for _,item in pairs(items) do
	                    if item == ingredient.item and uniqueRecipes[recipe.className] == nil then
	                        table.insert(recipes, recipe)
	                        uniqueRecipes[recipe.className] = true
	                        break
	                    end
	                end
	            end
            end
        end
    end
    return renderRecipeTable(recipes, false, nil, loc.errors.using .. "'''" .. table.concat(items, "'''" .. loc.errors.listOr .. "'''") .. "'''")
end

-- Render a table with recipes produced in specified buildings.
function p.renderRecipesProducedIn(frame)
    local recipes = {}
    local uniqueRecipes = {}
    local buildings = {}
    local seasonsEnabled = false
    
    for name,building in pairs(frame:getParent().args) do
    	if name == "seasons" and building ~= "" and building ~= "0" then
    		seasonsEnabled = true
		else
        	table.insert(buildings, building)
        end
    end
    
    -- go through all recipes, branch entries, producedIn buildings, check if at least one is in the buildings array
    for _,branchRecipes in pairs(recipesJSON) do
        for _,recipe in pairs(branchRecipes) do
        	if seasonsEnabled or #recipe.seasons == 0 then
	            for _,producedInBuilding in pairs(recipe.producedIn) do
	                for _,building in pairs(buildings) do
	                    if producedInBuilding == building and uniqueRecipes[recipe.className] == nil then
	                        table.insert(recipes, recipe)
	                        uniqueRecipes[recipe.className] = true
	                        break
	                    end
	                end
	            end
            end
        end
    end
    return renderRecipeTable(recipes, #buildings == 1, nil, loc.errors.producedIn .. "'''" .. table.concat(buildings, "'''" .. loc.errors.listOr .. "'''") .. "'''")
end

-- Render alternate recipes
function p.renderAlternateRecipes(frame)
    local recipes = {}
    for _,branchRecipes in pairs(recipesJSON) do
        for _,recipe in pairs(branchRecipes) do
            if recipe.alternate then
                table.insert(recipes, recipe)
            end
        end
    end
    table.sort(recipes, function(a, b) return a.name < b.name end)
    return renderRecipeTable(recipes, nil, nil)
end

function p.renderRecipesProducedInSpecial(frame)
    local recipes = {}
    for _,branchRecipes in pairs(recipesJSON) do
        for _,recipe in pairs(branchRecipes) do
    		local target = frame.args[1]
            if (target == "Build Gun" and recipe.inBuildGun) or (target == "Customizer" and recipe.inCustomizer) or (target == "Craft Bench" and recipe.inCraftBench) or (target == "Equipment Workshop" and recipe.inWorkshop) then
                -- empty the building list
                -- recipe.producedIn = {}
                table.insert(recipes, recipe)
            end
        end
    end
    table.sort(recipes, function(a, b) return a.name < b.name end)
    return renderRecipeTable(recipes, false)
end

return p