Module:RenderRecipesTable

local checkType = require('libraryUtil').checkType;

local cargo = mw.ext.cargo local moduleTooltips = require('Module:ItemTooltip') local tableTools = require('Module:TableUtil'); local argsUtil = require('Module:ArgsUtil');

local p = {} local buildings = {} local experimental_style = 'position:absolute; font-variant:all-small-caps; font-size:small; font-weight:bold; left:0; top:0; transform: rotate(-45deg) translate(-15px, -5px); background:red; color:white; padding: 1px 20px;' local unreleased_style = 'position: absolute; font-variant: all-small-caps; font-size: smaller; left: 0; width: 100%; font-weight: bold; background: repeating-linear-gradient( -45deg, yellow, yellow 8px, black 8px, black 16px); color: black; text-shadow: -1px -1px 0 #fff, -1px 1px 0 #fff, 1px -1px 0 #fff, 1px 1px 0 #fff; padding-bottom: 2px;'

local function loadBuildingsPower local tables = 'buildings' local fields = 'name, powerUsage' local args = { where = 'powerUsage > 0' } local result = cargo.query(tables, fields, args)

buildings = {} for _, building in pairs(result) do		buildings[building.name] = tonumber(building.powerUsage) end end

local function round(num, numDecimalPlaces) local mult = 10^(numDecimalPlaces or 0) return math.floor(num * mult + 0.5) / mult end

local function generateRecipeTableRow(recipe, useItemTooltips) local html = ''

-- fix some parameters recipe.alternateRecipe = (tonumber(recipe.alternateRecipe) or 0) > 0 recipe.inCraftBench = (tonumber(recipe.inCraftBench) or 0) > 0 recipe.inWorkShop = (tonumber(recipe.inWorkShop) or 0) > 0 recipe.craftingTime = tonumber(recipe.craftingTime) or 0 recipe.avgPower = tonumber(recipe.avgPower) if recipe.craftedIn == nil or #tostring(recipe.craftedIn) <= 1 then -- <=1 instead of ==0 just in case of \n or single space characters; normally craftedIn is a long word if recipe.inWorkShop then recipe.craftedIn = 'Equipment Workshop' elseif recipe.inCraftBench then recipe.craftedIn = 'Craft Bench' else recipe.craftedIn = 'Build Gun' end end

-- split ingredients and products from recipe into separate arrays local ingredients = {} for i=1, 9, 1 do local item = recipe["ingredient" .. i]		if item ~= nil and #item > 0 then ingredients[i] = { name = item, amount = tonumber(recipe["quantity" .. i]) or 1 } end end

local products = {} for i=1, 4, 1 do local item = recipe["product" .. i]		if item ~= nil and #item > 0 then products[i] = { name = item, amount = tonumber(recipe["productCount" .. i]) or 1 } end end

-- calculations for table cells local ingredientsCols = #ingredients <= 1 and 1 or math.max(2, math.ceil(#ingredients/2)) local ingredientsRows = math.ceil(#ingredients/ingredientsCols) local ingredientsColspan = 12 / ingredientsCols -- ingredients column is 12 columns wide (with colspan) to be divisible by 1, 2, 3 or 4

local productsCols = #products <= 1 and 1 or math.max(2, math.ceil(#products/2)) local productsRows = math.ceil(#products/productsCols) local productsColspan = 2 / productsCols

local maxRows = math.max(ingredientsRows, productsRows) local ingredientsRowspan = maxRows / ingredientsRows local productsRowspan = maxRows / productsRows

local ingredientCells = {} local productCells = {}

-- function generates HTML for ingredients/products cells and stores generated code in targetTable array local function generateCells(list, rowspan, colspan, targetTable, addEnergyPerItem) for i=1, #list, 1 do local td = '' local ipm = 60 / recipe.craftingTime * list[i].amount

if useItemTooltips then --td = td .. mw.getCurrentFrame:expandTemplate{title = 'ItemTooltip', args = { list[i].name, list[i].amount }} local itemAmount = mw.getCurrentFrame:expandTemplate{title='Tooltip', args={[1]=list[i].amount, [2]='The amount of this resource per crafting cycle, from which the per minute value is calculated', [3]='no underline'}} td = td .. moduleTooltips.getTooltip{ args = { item = list[i].name, amount = itemAmount, size = 40 } } else local itemAmount = list[i].amount .. ' × '				td = td .. ' ' .. mw.getCurrentFrame:expandTemplate{title='Tooltip', args={[1]=itemAmount, [2]='The amount of this resource per crafting cycle, from which the per minute value is calculated', [3]='no underline'}} .. ' '				td = td .. ' ' .. list[i].name .. '  '			end

if recipe.craftingTime > 0 then ipm = ipm .. ' / min' td = td .. ' ' .. mw.getCurrentFrame:expandTemplate{title='Tooltip', args={[1]=ipm, [2]='How much of this resource has to be supplied to/withdrawn from the machine for maximum efficiency', [3]='no underline'}} .. ' '

-- calculate energy per item if addEnergyPerItem then buildingPower = buildings[recipe.craftedIn] if buildingPower == nil then buildingPower = recipe.avgPower end if buildingPower ~= nil then local displayText = round(recipe.craftingTime * buildingPower / list[i].amount, 2) .. ' MJ/item' local tooltipText = 'The energy consumed by a ' .. recipe.craftedIn .. ' to craft 1 item at 100% clock speed in this crafting cycle' td = td .. '' .. mw.getCurrentFrame:expandTemplate{title='Tooltip', args={[1]=displayText, [2]=tooltipText, [3]='no underline'}} .. ' '					end end else -- if no craftingTime and no ipm to display then add some empty block for proper spacing td = td .. ' '			end td = td .. ' '			targetTable[i] = td		end -- odd amount of ingredients/products leaves one empty cell in the column, which should be filled by a blank cell -- also generate one empty cell when there's no ingredients/products at all if #list == 0 or #list > 1 and #list % 2 == 1 then targetTable[#targetTable+1] = ' ' end end

-- generate all ingredients and products cells generateCells(ingredients, ingredientsRowspan, ingredientsColspan, ingredientCells) generateCells(products, productsRowspan, productsColspan, productCells, true) -- for products add energy per item

-- assemble the table for i=1, maxRows, 1 do		-- recipe name column if i == 1 then recipeName = recipe.recipeName if (recipeName == nil or recipeName == '') then recipeName = recipe.product1 end if (recipeName == nil or recipeName == '') then recipeName = 'Not specified' end if recipe.alternateRecipe then recipeName = recipeName .. ' ' .. 'Alternate ' end local stripe = '' if recipe.experimental == '1' then stripe = '' .. mw.getCurrentFrame:expandTemplate{title='Tooltip', args={[1]='Exp', [2]='This recipe is only available in the Experimental branch', [3]='no underline'}} .. ' '			elseif recipe.unreleased == '1' then stripe = '' .. mw.getCurrentFrame:expandTemplate{title='Tooltip', args={[1]='Unreleased', [2]='This recipe is not available, it was either datamined or removed', [3]='no underline'}} .. ' '					.. ' ' end local season = recipe.season if (season ~= nil and season ~= '') then stripe = stripe .. mw.getCurrentFrame:expandTemplate{ title = "CraftingTable/season", args = { season = season }, }			end html = html .. '' .. '' .. recipeName .. stripe .. ' '		else html = html .. ' '		end

-- ingredients column for j=1, ingredientsCols, 1 do			local index = (i-1)*ingredientsCols+j if index <= #ingredientCells then html = html .. ingredientCells[index] end end

-- building column if i == 1 then html = html .. ' ' .. recipe.craftedIn .. ' ' if recipe.craftingTime > 0 then html = html .. ' ' .. recipe.craftingTime .. ' sec' end if recipe.inCraftBench or recipe.inWorkShop then local displayText = ' × ' .. recipe.craftingClicks local tooltipText = 'Manual crafting rate: ' .. 240 / (tonumber(recipe.craftingClicks) or 0) * (tonumber(recipe.productCount1) or 1) .. '/min' html = html ..' ' .. mw.getCurrentFrame:expandTemplate{title='Tooltip', args={[1]=displayText, [2]=tooltipText, [3]='no underline'}} end html = html .. ' '		end

-- products column for j=1, productsCols, 1 do			local index = (i-1)*productsCols+j if index <= #productCells then html = html .. productCells[index] end end

-- prerequisites column if i == 1 then html = html .. ' ' .. recipe.researchTier .. ' '		end

html = html .. ' '	end

return html end

local fields = 'recipeName, alternateRecipe, mainRecipe, craftedIn, avgPower, inCraftBench, inWorkShop, craftingTime, craftingClicks, researchTier, unreleased, experimental, product=product1, product2, product3, product4, productCount=productCount1, productCount2, productCount3, productCount4, ingredient1, quantity1, ingredient2, quantity2, ingredient3, quantity3, ingredient4, quantity4, ingredient5, quantity5, ingredient6, quantity6, ingredient7, quantity7, ingredient8, quantity8, ingredient9, quantity9, ingredient10, quantity10, CONCAT((CASE craftedIn WHEN "Build Gun" THEN 1 ELSE 0 END)) = isBuilding'

if(pcall(mw.ext.cargo.query, "crafting_recipes", "crafting_recipes.season", { limit=1 })) then -- TODO: Merge this into the `fields` declaration above once an admin -- recreates the Special:CargoTables/crafting_recipes cargo table: fields = fields .. ', season' end

local function generateTable(recipes, useItemTooltips) local html = ' ' return html end

function p.generateCraftingRecipes(frame) local itemName = frame.args.item or frame.args[1] local useItemTooltips = frame.args.tooltips and (#frame.args.tooltips > 0) or false local experimental = frame.args.experimental and (#frame.args.experimental > 0) or false local experimentalCondition = experimental and ' and (experimental=True)' or ' and (experimental is null or experimental=False)'

local recipes = cargo.query("crafting_recipes", fields, {		where = '(product="' .. itemName .. '" or product2="' .. itemName .. '" or product3="' .. itemName .. '" or product4="' .. itemName .. '")',		orderBy = 'isBuilding ASC, mainRecipe ASC, alternateRecipe ASC, product ASC'	});

if #recipes > 0 then loadBuildingsPower return generateTable(recipes, useItemTooltips) else return "'''" .. itemName .. "''' can't be crafted." end end

function p.generateCraftingRecipesForItems(frame) local itemNames = {}; for _, itemName in tableTools.sparseIpairs(frame.args) do		itemName = mw.text.trim(itemName); if (#itemName > 0) then itemNames[#itemNames + 1] = itemName; end end

return p._generateCraftingRecipesForItems(		itemNames,		{			useItemTooltips = frame.args.tooltips and (#frame.args.tooltips > 0) or false,			experimental = frame.args.experimental and (#frame.args.experimental > 0) or false,		}	); end

function p._generateCraftingRecipesForItems(itemNames, options) checkType('generateCraftingRecipesForItems', 1, itemNames, 'table'); checkType('generateCraftingRecipesForItems', 2, options, 'table', true); if (#itemNames == 0) then return tostring(mw.html.create("strong"):addClass("error"):wikitext("No items specified"):allDone); end

options = options or {}; local experimentalCondition = options.experimental and ' and (experimental=True)' or ' and (experimental is null or experimental=False)'; local where; for _, itemName in ipairs(itemNames) do		if (where) then where = where .. " or "; end where = (where or "") .. '(product="' .. itemName .. '" or product2="' .. itemName .. '" or product3="' .. itemName .. '" or product4="' .. itemName .. '")'; end where = "(" .. where .. ")";

local recipes = cargo.query("crafting_recipes", fields, {		where = where,		orderBy = 'isBuilding ASC, mainRecipe ASC, alternateRecipe ASC, product ASC',	});

if #recipes > 0 then loadBuildingsPower; return generateTable(recipes, options.useItemTooltips or false); else return "No recipes found"; end end

function p.generateCraftingUsage(frame) local itemName = frame.args.item or frame.args[1] local useItemTooltips = frame.args.tooltips and (#frame.args.tooltips > 0) or false local experimental = frame.args.experimental and (#frame.args.experimental > 0) or false local unreleased = frame.args.unreleased and (#frame.args.unreleased > 0) or false local buildingRecipes = frame.args.buildings local experimentalCondition = experimental and ' and (experimental=True)' or ' and (experimental is null or experimental=False)' local unreleasedCondition = unreleased and '' or ' and (unreleased is null or unreleased=False)' local buildingCondition = '' if buildingRecipes == '0' then buildingCondition = ' and (craftedIn<>"Build Gun")' elseif buildingRecipes == '1' then buildingCondition = ' and (craftedIn="Build Gun")' end

local recipes = cargo.query("crafting_recipes", fields, {		where = 'product IS NOT NULL and _pageName NOT LIKE "%/%" and (ingredient1="' .. itemName .. '" or ingredient2="' .. itemName .. '" or ingredient3="' .. itemName .. '" or ingredient4="' .. itemName .. '" or ingredient5="' .. itemName .. '" or ingredient6="' .. itemName .. '" or ingredient7="' .. itemName .. '" or ingredient8="' .. itemName .. '" or ingredient9="' .. itemName .. '" or ingredient10="' .. itemName .. '")' .. unreleasedCondition .. buildingCondition,		limit = argsUtil.parseNumber(frame.args.limit),		orderBy = 'isBuilding ASC, alternateRecipe ASC, product ASC'	});

if #recipes > 0 then loadBuildingsPower return generateTable(recipes, useItemTooltips) else return "'''" .. itemName .. "''' isn't used to craft anything." end end

function p.generateCraftingUsageForItems(frame) local itemNames = {}; for _, itemName in tableTools.sparseIpairs(frame.args) do		itemName = mw.text.trim(itemName); if (#itemName > 0) then itemNames[#itemNames + 1] = itemName; end end

return p._generateCraftingUsageForItems(		itemNames,		{			useItemTooltips = frame.args.tooltips and (#frame.args.tooltips > 0) or false,			experimental = frame.args.experimental and (#frame.args.experimental > 0) or false,			unreleased = frame.args.unreleased and (#frame.args.unreleased > 0) or false,			limit = argsUtil.parseNumber(frame.args.limit),		}	); end

function p._generateCraftingUsageForItems(itemNames, options) checkType('generateCraftingUsageForItems', 1, itemNames, 'table'); checkType('generateCraftingUsageForItems', 2, options, 'table', true); if (#itemNames == 0) then return tostring(mw.html.create("strong"):addClass("error"):wikitext("No items specified"):allDone); end

options = options or {}; local experimentalCondition = options.experimental and ' and (experimental=True)' or ' and (experimental is null or experimental=False)'; local unreleasedCondition = options.unreleased and '' or ' and (unreleased is null or unreleased=False)'

local where; for _, itemName in ipairs(itemNames) do		if (where) then where = where .. " or "; end where = (where or "") .. '(ingredient1="' .. itemName .. '" or ingredient2="' .. itemName			.. '" or ingredient3="' .. itemName .. '" or ingredient4="' .. itemName			.. '" or ingredient5="' .. itemName .. '" or ingredient6="' .. itemName			.. '" or ingredient7="' .. itemName .. '" or ingredient8="' .. itemName			.. '" or ingredient9="' .. itemName .. '" or ingredient10="' .. itemName			.. '")'; end where = 'product IS NOT NULL and _pageName NOT LIKE "%/%" and (' .. where .. ")" .. unreleasedCondition;

local recipes = cargo.query("crafting_recipes", fields, {		where = where,		limit = options.limit,		orderBy = 'isBuilding ASC, alternateRecipe ASC, product ASC',	});

if #recipes > 0 then loadBuildingsPower; return generateTable(recipes, options.useItemTooltips or false); else return "No recipes found"; end end

return p