Module:Item recipe: Difference between revisions
From Space Station 14 Wiki
(updated due to deps on Module:Item and its rework) |
(changed a few things) |
||
Line 1: | Line 1: | ||
-- Contains utilities for working with in-game item recipes. | -- Contains utilities for working with in-game item recipes. | ||
local p = {} --p stands for package | local p = {} --p stands for package | ||
Line 8: | Line 6: | ||
local yesNo = require('Module:Yesno') | local yesNo = require('Module:Yesno') | ||
-- A table mapping production methods to | -- A table mapping recipe IDs to recipes. | ||
local | local recipes_by_recipe_ids = mw.loadJsonData("Module:Item recipe/recipes by recipe IDs.json") | ||
-- A table mapping product IDs to recipe IDs. | |||
-- A product ID can have either a single recipe ID mapped to it, or multiple if there's are multiple recipes. | |||
local recipe_ids_by_product_ids = mw.loadJsonData("Module:Item recipe/recipe IDs by product IDs.json") | |||
-- A table mapping production methods to recipe IDs, with a intermediate mapping by availability. | |||
local recipe_ids_by_method_and_availability = mw.loadJsonData( | |||
"Module:Item recipe/recipe IDs by method and availability.json") | |||
-- A table mapping material IDs (item IDs) to their display order. | |||
-- Order is just a number. Materials with lesser order number will appear first. | |||
local materials_order_by_material_ids = mw.loadJsonData("Module:Item recipe/order of materials.json") | |||
-- A table remapping product IDs. | |||
-- | |||
-- Not all recipes produce products that you might think they do - | -- Not all recipes produce products that you might think they do - | ||
-- some produce their own "printed" or "empty" or other variants. | -- some produce their own "printed" or "empty" or other variants. | ||
-- | -- | ||
-- For instance, a recipe for the small power cell produces `PowerCellSmallPrinted` item, | -- For instance, a recipe for the small power cell produces `PowerCellSmallPrinted` item, | ||
Line 23: | Line 33: | ||
-- to the actual item `PowerCellSmall`. | -- to the actual item `PowerCellSmall`. | ||
-- | -- | ||
-- After that is done, | -- After that is done, a lookup for small power cell (or its id) will return the corresponding recipe. | ||
local product_overrides = mw.loadJsonData("Module:Item recipe/product overrides.json") | local product_overrides = mw.loadJsonData("Module:Item recipe/product overrides.json") | ||
local current_frame = mw:getCurrentFrame() | local current_frame = mw:getCurrentFrame() | ||
Line 49: | Line 57: | ||
end | end | ||
local function | local function numeric_table_has_value(tab, val) | ||
for _, value in ipairs(tab) do | for _, value in ipairs(tab) do | ||
if value == val then | if value == val then | ||
Line 57: | Line 65: | ||
return false | return false | ||
end | |||
local function filter_table(tab, predicate) | |||
local tab_filtered = {} | |||
for key, value in pairs(tab) do | |||
if predicate(key, value) then | |||
tab_filtered[key] = value | |||
end | |||
end | |||
return tab_filtered | |||
end | end | ||
Line 103: | Line 122: | ||
end | end | ||
end | end | ||
local function map_numeric_table(tbl, f) | |||
local t = {} | |||
for i, v in ipairs(tbl) do | |||
t[i] = f(v, i) | |||
end | |||
return t | |||
end | |||
local function map_table(tbl, f) | |||
local t = {} | |||
for k, v in pairs(tbl) do | |||
t[k] = f(k, v) | |||
end | |||
return t | |||
end | |||
local function passthrough_assert_true(value, valueToReturnIfTrue, errorMessageOnFalse) | |||
if value then | |||
return valueToReturnIfTrue | |||
else | |||
error(errorMessageOnFalse) | |||
end | |||
end | |||
-- ==================== | -- ==================== | ||
-- | -- ###################### | ||
-- ### METHOD LOOKUPS ### | |||
-- ###################### | |||
-- Lookups recipe IDs by production method `production_method`. | |||
-- | -- | ||
-- | -- Returns recipe IDs grouped by availability. | ||
-- | -- | ||
-- | -- Raises an error if no recipe IDs were found by the production method. | ||
-- Set `no_error` to `true` to return `nil` instead. | |||
local match = | function p.lookup_recipe_ids_with_availability_by_method(production_method, no_error) | ||
assert_value_not_nil(production_method) | |||
-- check for an exact match | |||
local exact_match = recipe_ids_by_method_and_availability[production_method] | |||
if exact_match ~= nil then | |||
return exact_match | |||
end | |||
-- check for case-independent match | |||
local production_method_lower = string.lower(production_method) | |||
for method, recipe_ids_grouped in pairs(recipe_ids_by_method_and_availability) do | |||
if string.lower(method) == production_method_lower then | |||
return recipe_ids_grouped | |||
end | |||
end | |||
if | if not no_error then | ||
error("recipe IDs by production method lookup failed: production method '" .. | |||
production_method .. "' is not defined") | |||
end | end | ||
end | end | ||
-- | -- Lookups production methods by recipe ID `recipe_id`. | ||
-- | -- | ||
-- | -- Returns an array of tables, each containg: | ||
-- | -- - `method` - production method. | ||
-- - `availability` - availability for the recipe for this production method. | |||
-- | -- | ||
-- | -- **NOTE:** This is an expensive function. | ||
function p.lookup_methods_with_availability_by_recipe_id(inpit_recipe_id) | |||
local | local result = {} | ||
if | for method, recipe_ids_grouped in pairs(recipe_ids_by_method_and_availability) do | ||
for availability, recipe_ids in pairs(recipe_ids_grouped) do | |||
if numeric_table_has_value(recipe_ids, inpit_recipe_id) then | |||
table.insert(result, { | |||
method = method, | |||
availability = availability | |||
}) | |||
end | |||
end | |||
end | end | ||
return result | |||
end | |||
-- Checks whether a production method `production_method` exists. | |||
-- Any casing is allowed. | |||
function p.method_exists(production_method) | |||
assert_value_not_nil(production_method) | |||
return p.lookup_recipe_ids_with_availability_by_method(production_method, true) ~= nil | |||
end | end | ||
-- Asserts that a | -- Asserts that a production method `production_method` exists. | ||
-- | -- Any casing is allowed. | ||
function p.assert_method_exists(production_method, custom_message) | |||
if not | assert_value_not_nil(production_method) | ||
if not p.method_exists(production_method) then | |||
if custom_message then | |||
error("production method exists assertion failed for method '" .. | |||
production_method .. "': " .. custom_message) | |||
else | |||
error("production method exists assertion failed for method '" .. | |||
production_method .. "': production method is not defined") | |||
end | |||
end | end | ||
end | |||
-- ################################ | |||
-- ### PRODUCT OVERRIDE LOOKUPS ### | |||
-- ################################ | |||
-- Lookups an override for product ID `product_id`. | |||
-- | |||
-- For instance, if there was an override `PowerCellSmallPrinted` that maps to to item ID `PowerCellSmall`, | |||
-- this functions would return `PowerCellSmall` if `product_id` was `PowerCellSmallPrinted`. | |||
-- | |||
-- Raises an error if no match was found. Set `no_error` to `true` to return `nil` instead. | |||
function p.lookup_product_id_override(product_id, no_error) | |||
assert_value_not_nil(product_id) | |||
return product_overrides[product_id] | |||
or passthrough_assert_true( | |||
no_error, | |||
nil, | |||
"product override lookup failed: no override was found with product ID '" .. | |||
product_id .. "'" | |||
) | |||
end | |||
-- Lookups the prodct ID that was overriden with item ID `item_id`. | |||
-- | |||
product .. | -- For instance, if there was an override `PowerCellSmallPrinted` that maps to to item ID `PowerCellSmall`, | ||
"' (product | -- this functions would return `PowerCellSmallPrinted` if `item_id` was `PowerCellSmall`. | ||
-- | |||
-- **NOTE:** This is an expensive function. | |||
-- | |||
-- Raises an error if no match was found. Set `no_error` to `true` to return `nil` instead. | |||
function p.reverse_lookup_product_id_override(item_id, no_error) | |||
assert_value_not_nil(item_id) | |||
return find_first_table_item_key_matching_condition( | |||
product_overrides, | |||
function(_, value) return value == item_id end | |||
) | |||
or passthrough_assert_true( | |||
no_error, | |||
nil, | |||
"reverse product override lookup failed: no override was found that maps to item ID '" .. | |||
item_id .. "'" | |||
) | |||
end | |||
-- ####################### | |||
-- ### PRODUCT LOOKUPS ### | |||
-- ####################### | |||
-- Checks whether a an item `product` exists. | |||
-- Takes into account that `product` can have a product override. | |||
function p.product_exsits(product) | |||
assert_value_not_nil(product) | |||
local product_with_override = p.lookup_product_id_override(product, true) | |||
or product | |||
return itemModule.item_exists(product_with_override) | |||
end | |||
-- Asserts that a product `product` exists. | |||
-- `product` can be a product ID (including overriden ones), item ID or name. | |||
function p.assert_product_exists(product, custom_message) | |||
assert_value_not_nil(product) | |||
if not p.product_exsits(product) then | |||
if custom_message then | |||
error("product exist assertion failed for product '" .. product .. "': " .. custom_message) | |||
else | |||
error("product exist assertion failed for product '" .. | |||
product .. | |||
"': no product was found. Make sure that a recipe exists with given product or that a product override is defined in Module:Item recipe") | |||
end | |||
end | end | ||
end | end | ||
-- | -- Lookups recipe IDs by product ID `product_id`. | ||
-- | -- Accepts overriden `product_id`s. | ||
-- | -- | ||
-- Returns | -- Returns an array of matches. | ||
-- | |||
assert_value_not_nil( | -- **NOTE:** This is an expensive function. | ||
function p.lookup_recipe_ids_by_product_id(product_id) | |||
assert_value_not_nil(product_id) | |||
-- | local result = recipe_ids_by_product_ids[ | ||
local | p.reverse_lookup_product_id_override(product_id, true) | ||
if | or product_id | ||
] | |||
for | |||
table.insert( | -- always returns an array | ||
if type(result) == 'string' then | |||
return { result } | |||
else | |||
return result | |||
end | |||
end | |||
-- ###################### | |||
-- ### RECIPE LOOKUPS ### | |||
-- ###################### | |||
-- Lookups recipe by recipe ID `recipe_id`. | |||
-- | |||
-- Returns `nil` if no recipe was found. | |||
-- Set `no_error` to `true` to return `nil` instead. | |||
function p.lookup_recipe_by_recipe_id(recipe_id, no_error) | |||
assert_value_not_nil(recipe_id) | |||
return recipes_by_recipe_ids[recipe_id] | |||
or passthrough_assert_true( | |||
no_error, | |||
nil, | |||
"failed to lookup recipe by recipe id '" .. recipe_id .. "': no recipe was found" | |||
) | |||
end | |||
-- Asserts that a recipe ID `recipe_id` exists. | |||
-- function assert_recipe_id_exists(recipe_id) | |||
-- error("not impl") | |||
-- end | |||
-- Searches recipes by `query`. `query` can be an product ID (item ID - including overrden ones), | |||
-- name or a recipe ID. | |||
function p.search_recipes(query) | |||
assert_value_not_nil(query, "failed to lookup recipes by method and item ID: item ID was not provided") | |||
-- check if query is a recipe ID | |||
local match_recipe_by_recipe_id = recipes_by_recipe_ids[query] | |||
if match_recipe_by_recipe_id then | |||
-- if so - we got a direct match! | |||
return { match_recipe_by_recipe_id } | |||
end | |||
-- check if query is an item name/ID | |||
-- this is a last possibility. | |||
-- TODO add a custom errror here? | |||
local item_id = itemModule.lookup_item_id(query) | |||
local match_recipe_ids_by_product_id = p.lookup_recipe_ids_by_product_id(item_id) | |||
local recipes = {} | |||
if match_recipe_ids_by_product_id ~= nil then | |||
for _, recipe_id in ipairs(match_recipe_ids_by_product_id) do | |||
table.insert(recipes, p.lookup_recipe_by_recipe_id(recipe_id)) | |||
end | end | ||
end | end | ||
-- | return recipes | ||
end | |||
-- Lookups recipes by production method. | |||
-- | |||
-- Raises an error if no recipes were found by the production method. | |||
function p.lookup_recipes_by_production_method(method) | |||
assert_value_not_nil(method, "failed to lookup recipes by production method: no method was given") | |||
p.assert_method_exists(method, | |||
"failed to lookup recipes by production method: method '" .. method .. "' doesn't exist") | |||
local recipe_ids_grouped = p.lookup_recipe_ids_with_availability_by_method(method) | |||
local recipes = {} | |||
for availability, recipe_ids in pairs(recipe_ids_grouped) do | |||
for _, recipe_id in ipairs(recipe_ids) do | |||
local recipe = p.lookup_recipe_by_recipe_id(recipe_id) | |||
table.insert(recipes, recipe) | |||
end | end | ||
end | end | ||
return recipes | |||
end | end | ||
-- Searches a material item | -- Filters given recipes by production method. | ||
-- if the config doesn't have the queried item. | -- Any casing is allowed for production method. | ||
local function | -- function p.filter_recipes_by_production_method(recipes, production_method) | ||
assert_value_not_nil( | -- p.assert_method_exists(production_method) | ||
-- return filter_table( | |||
-- p.lookup_recipe_ids_by_method(production_method), | |||
-- function(_, recipe_ids_grouped) | |||
-- return find_first_numeric_table_item_matching_condition( | |||
-- recipes, | |||
-- function(recipe) return recipe.id == recipe_ids_grouped end | |||
-- ) ~= nil | |||
-- end | |||
-- ) | |||
-- end | |||
-- ############################## | |||
-- ### MATERIAL ORDER LOOKUPS ### | |||
-- ############################## | |||
-- Searches a material item using the material order config, returning the order number or `nil`, | |||
-- if the material order config doesn't have the queried item. | |||
-- | |||
-- Takes in an item ID or name. | |||
local function try_lookup_order_of_material(material) | |||
assert_value_not_nil(material, "failed to lookup order of material: material was not provided") | |||
local item_id = itemModule.lookup_item_id(material, true) | |||
if item_id == nil then | |||
error("failed to lookup order of material: material '" .. material .. "' does not exist") | |||
end | |||
return | return materials_order_by_material_ids[item_id] | ||
end | end | ||
-- ======================= | |||
-- Produces a "note" element used in generation of item recipes. | -- Produces a "note" element used in generation of item recipes. | ||
Line 255: | Line 492: | ||
-- [REQUIRED] | -- [REQUIRED] | ||
-- Item name, alias or ID. Required. | -- Item name, alias, item ID or a recipe ID. Required. | ||
local | local input_query = args[1] | ||
assert_value_not_nil( | assert_value_not_nil(input_query, "failed to generate a recipe for query: query was not provided") | ||
-- [OPTIONAL] | -- [OPTIONAL] | ||
Line 264: | Line 501: | ||
-- Must be a string since Module:Item uses string amount. | -- Must be a string since Module:Item uses string amount. | ||
-- All values from templates come as strings. | -- All values from templates come as strings. | ||
local | local input_amount = nil_or(args[2], "1") | ||
-- Item production method. Can be "nil", in which case it's looked up. | -- Item production method. Can be "nil", in which case it's looked up. | ||
local | local input_method = args[3] | ||
-- Whether to only generate a materials block. | -- Whether to only generate a materials block. | ||
local | local input_materials_only = yesNo(args["materials only"] or args["mat only"] or false) | ||
-- Layout of materials in materials only mode. | -- Layout of materials in materials only mode. | ||
local | local input_materials_only_layout = args["materials only layout"] or args["mat only layout"] or "vertical" | ||
-- ============ | -- ============ | ||
-- | -- search recipes | ||
-- | |||
local recipes = p.search_recipes(input_query) | |||
local recipes_count = numeric_table_length(recipes) | |||
if recipes_count == 0 then | |||
error("failed to generate a recipe for item: no recipe was found for item '" .. | |||
input_query .. | |||
"' (input method: '" .. | |||
(input_method or "nil") .. | |||
"'). Make sure a recipe exists for this item or define a product override for an existing recipe in Module:Item recipe") | |||
elseif recipes_count > 1 then | |||
error("failed to generate a recipe for item: found multiple recipes for item '" .. | |||
input_query .. | |||
"' (input method: '" .. | |||
(input_method or "nil") .. | |||
"'). Rendering multiple recipes is currently unsupported") | |||
end | |||
local recipe = recipes[1] | |||
-- search recipe methods | |||
local recipe_methods_lookup = p.lookup_methods_with_availability_by_recipe_id(recipe.id) | |||
local recipe_methods_count = table_length(recipe_methods_lookup) | |||
if recipe_methods_count == 0 then | |||
error("failed to generate a recipe for item: no methods were found for recipe ID '" .. | |||
recipe.id .. | |||
"' (input query: '" .. input_query .. "'; input method: '" .. | |||
(input_method or "nil") .. | |||
"'). This shouldn't usually happen because the present recipes are bound to some production methods. Probable cause: bug in the recipe generation code") | |||
elseif recipe_methods_count > 1 and input_method == nil then | |||
local methods = map_numeric_table( | |||
recipe_methods_lookup, | |||
function(match) | |||
return match.method | |||
end | |||
) | |||
error("failed to generate a recipe for item: found multiple production methods for recipe ID '" .. | |||
recipe.id .. | |||
"' (input query: '" .. | |||
input_query .. | |||
"') and input production method was NOT specified. Rendering multiple recipes is unsupported, so please specify a production method from available methods for this recipe: '" .. | |||
table.concat(methods, "', '") .. "'") | |||
end | |||
local recipe_method | |||
local recipe_availability | |||
if input_method == nil then | |||
-- if no input methods is specified, use the single available recipe method | |||
recipe_method = recipe_methods_lookup[1].method | |||
recipe_availability = recipe_methods_lookup[1].availability | |||
else | |||
-- otherwise, the number of available methods can vary, | |||
-- so filter it down to a single one based on the input method. | |||
-- todo maybe convert this to a function | |||
local input_method_lower = string.lower(input_method); | |||
local recipe_methods_lookup_match = find_first_numeric_table_item_matching_condition( | |||
recipe_methods_lookup, | |||
function(lookup_result) | |||
return string.lower(lookup_result.method) == input_method_lower | |||
end | |||
) | |||
if recipe_methods_lookup_match == nil then | |||
error("failed to generate a recipe for item: no production methods were found for recipe ID '" .. | |||
recipe.id .. | |||
"' (input query: '" .. | |||
input_query .. | |||
"') matching input method '" .. | |||
(input_method or "nil") .. | |||
"'. Make sure a recipe exists for this item with the specified production method or define a product override for an existing recipe with the specified production method in Module:Item recipe") | |||
end | |||
recipe_method = recipe_methods_lookup_match.method | |||
recipe_availability = recipe_methods_lookup_match.availability | |||
end | |||
-- extract products | |||
-- recipe product IDs mapped to amounts | |||
local recipe_products = {} | |||
if recipe.result then | |||
recipe_products[recipe.result] = 1 | |||
elseif recipe.resultReagents then | |||
for product_id, amount in pairs(recipe.resultReagents) do | |||
recipe_products[product_id] = amount | |||
end | |||
else | |||
error("failed to generate a recipe for item: no products were found for recipe ID '" .. | |||
recipe.id .. | |||
"' (input query: '" .. | |||
input_query .. | |||
"'). This might be due to recipe having another way to describe products") | |||
end | |||
-- generate recipe element | |||
local recipe_el = mw.html.create("div") | local recipe_el = mw.html.create("div") | ||
:addClass("item-recipe") | :addClass("item-recipe") | ||
if | if input_materials_only_layout == "vertical" or input_materials_only_layout == "ver" then | ||
recipe_el:addClass("item-recipe-materials-layout-vertical") | recipe_el:addClass("item-recipe-materials-layout-vertical") | ||
else | else | ||
Line 327: | Line 634: | ||
assert_value_not_nil(recipe.materials, | assert_value_not_nil(recipe.materials, | ||
"failed to generate a recipe for item: no 'materials' are specified for | "failed to generate a recipe for item: no 'materials' are specified for recipe ID '" .. | ||
recipe.id .. "' (input query: '" .. | |||
input_query .. | |||
"')") | |||
-- | -- copy list of materials, but without costs | ||
local | local materials = {} | ||
for | for material, cost in pairs(recipe.materials) do | ||
table.insert( | table.insert(materials, material) | ||
end | end | ||
-- sort materials list based on material order config | -- sort materials list based on material order config. | ||
-- | -- materials not in config will come after the ordered materials. | ||
table.sort(materials, function(first, second) | |||
table.sort( | return (try_lookup_order_of_material(first) or 999999999999) | ||
return ( | < (try_lookup_order_of_material(second) or 999999999999) | ||
< ( | |||
end) | end) | ||
for _, material in ipairs( | -- generate materials elements in sorted order | ||
for _, material in ipairs(materials) do | |||
local cost = recipe.materials[material] | local cost = recipe.materials[material] | ||
local material_item_id = itemModule.lookup_item_id(material, true) | |||
error("failed to generate a recipe for item | if material_item_id == nil then | ||
error("failed to generate a recipe for item: material '" .. | |||
material .. | |||
"' was not found in item registry (for recipe ID '" .. | |||
recipe.id .. "'; input query: '" .. | |||
input_query .. | |||
"'). Make sure that the material is added to the item name overrides in Module:Item") | |||
end | end | ||
materials_el:node(itemModule.generate_item{ material_item_id, cost }) | |||
materials_el:node(itemModule.generate_item { | |||
end | end | ||
Line 364: | Line 672: | ||
if | if input_materials_only then | ||
recipe_el:addClass("materials-only") | recipe_el:addClass("materials-only") | ||
Line 382: | Line 690: | ||
local product_el = itemModule.generate_item { [1] = | local products_el = mw.html.create("div") | ||
:addClass("item-recipe-products") | |||
product_and_method_container_el:node(products_el) | |||
for product_id, amount in pairs(recipe_products) do | |||
local product_el = itemModule.generate_item { [1] = product_id, [2] = amount * input_amount, cap = true } | |||
:addClass("item-recipe-product") | |||
products_el:node(product_el) | |||
end | |||
-- TODO: not all methods will be items, so this will eventually break. | -- TODO: not all methods will be items, so this will eventually break. | ||
local method_el = methods_items_els_cache[ | local method_el = methods_items_els_cache[recipe_method] | ||
if method_el == nil then | if method_el == nil then | ||
method_el = mw.html.create("span") | method_el = mw.html.create("span") | ||
:addClass('item-recipe-method') | :addClass('item-recipe-method') | ||
:node(itemModule.generate_item { [1] = | :node(itemModule.generate_item { [1] = recipe_method, capitalize = true }) | ||
methods_items_els_cache[ | methods_items_els_cache[recipe_method] = method_el | ||
end | end | ||
Line 416: | Line 732: | ||
complete_time_el:node("Instant") | complete_time_el:node("Instant") | ||
else | else | ||
complete_time_el:node((recipe.completetime * | complete_time_el:node((recipe.completetime * input_amount) .. " " .. "sec.") | ||
end | end | ||
Line 426: | Line 742: | ||
:addClass("item-recipe-notes") | :addClass("item-recipe-notes") | ||
:css('background', 'linear-gradient(40deg, var(--bg-color-light-x3), var(--bg-color-light-x2))') | :css('background', 'linear-gradient(40deg, var(--bg-color-light-x3), var(--bg-color-light-x2))') | ||
-- if recipe is not available by default, | -- if recipe is not available by default, | ||
-- generate "info icons" and notes (if needed) telling about it. | -- generate "info icons" and notes (if needed) telling about it. | ||
if | if recipe_availability ~= 'static' then | ||
local is_recipe_unlocked_by_research = string.find( | local is_recipe_unlocked_by_research = string.find(recipe_availability, "dynamic", 1, true) ~= nil | ||
local is_recipe_unlocked_by_emag = string.find( | local is_recipe_unlocked_by_emag = string.find(recipe_availability, "emag", 1, true) ~= nil | ||
local is_recipe_unlocked_by_research_and_then_emag = is_recipe_unlocked_by_research and | local is_recipe_unlocked_by_research_and_then_emag = is_recipe_unlocked_by_research and | ||
is_recipe_unlocked_by_emag | is_recipe_unlocked_by_emag | ||
Line 510: | Line 822: | ||
local recipes_limit = tonumber(args.limit) or 99999999 | local recipes_limit = tonumber(args.limit) or 99999999 | ||
local recipes = | local recipes = p.lookup_recipes_by_production_method(method) | ||
assert_value_not_nil(recipes, "failed to generate a list of recipes for a method: unknown method: " .. method) | assert_value_not_nil(recipes, "failed to generate a list of recipes for a method: unknown method: " .. method) | ||
Line 518: | Line 830: | ||
:addClass("item-recipes-list") | :addClass("item-recipes-list") | ||
-- generate a list of products | -- -- generate a list of products | ||
local products = {} | -- local products = {} | ||
local i = 1 | -- local i = 1 | ||
for product_item_id, _ in | -- for product_item_id, _ in ipairs(recipes) do | ||
-- table.insert(products, product_item_id) | |||
-- if i == recipes_limit then | |||
-- break | |||
-- end | |||
-- i = i + 1 | |||
-- end | |||
-- -- sort the list of products alphabetically | |||
-- -- by looking up the item names and comparing them | |||
-- table.sort(products, function(first, second) | |||
-- local first_item_id = p.lookup_product_id_override(first, true) | |||
-- or first | |||
-- local second_item_id = p.lookup_product_id_override(second, true) | |||
-- or second | |||
-- | -- p.assert_product_exists(first_item_id) | ||
-- | -- p.assert_product_exists(second_item_id) | ||
-- return itemModule.lookup_item_name_by_item_id(first_item_id) | |||
-- < itemModule.lookup_item_name_by_item_id(second_item_id) | |||
-- end) | |||
-- -- generate recipe elements | |||
-- for _, product in ipairs(products) do | |||
end | -- container_el:node(p.generate_item_recipe { | ||
-- [1] = p.lookup_product_id_override(product), | |||
-- [2] = 1, | |||
-- [3] = method | |||
-- }) | |||
-- end | |||
-- generate recipe elements | -- generate recipe elements | ||
for _, | for _, recipe in ipairs(recipes) do | ||
container_el:node(p.generate_item_recipe { | container_el:node( | ||
p.generate_item_recipe { | |||
[1] = recipe.id, | |||
[2] = 1, | |||
[3] = method | |||
} | |||
) | |||
end | end | ||
Revision as of 11:34, 15 September 2024
Module documentation
|
---|
View or edit this documentation • (about module documentation) |
Implements {{item recipe}}.
JSON files
JSON files that are updated automatically, syncing with the upstream:
- Module:Item recipe/recipes by recipe IDs.json - contains 1 to 1 mapping of recipe IDs to recipes.
- Module:Item recipe/recipe IDs by product IDs.json - contains mapping of recipe products to recipe IDs that produce these...products. Can be 1 to 1, or 1 to many.
- Module:Item recipe/recipe IDs by method and availability.json - contains a mapping of recipe production methods (e.g. Autolathe) to → availability (condition under which a recipe is available for this production method) to → recipe IDs.
Warning
Do not make changes to the above JSON files - any changes made will be erased on next update.
JSON files that are filled manually:
- Module:Item recipe/order of materials.json - a 1 to 1 mapping of recipe materials to order at which they appear in recipes. Less number = higher order. Materials that do not have an order defined here, will appear after those that do.
- Module:Item recipe/product overrides.json - a 1 to 1 mapping of recipe products to item IDs. Not all products are the same as item IDs they "represent", so sometimes a connection needs to be established explicitly.
-- Contains utilities for working with in-game item recipes.
local p = {} --p stands for package
local getArgs = require('Module:Arguments').getArgs
local itemModule = require('Module:Item')
local yesNo = require('Module:Yesno')
-- A table mapping recipe IDs to recipes.
local recipes_by_recipe_ids = mw.loadJsonData("Module:Item recipe/recipes by recipe IDs.json")
-- A table mapping product IDs to recipe IDs.
-- A product ID can have either a single recipe ID mapped to it, or multiple if there's are multiple recipes.
local recipe_ids_by_product_ids = mw.loadJsonData("Module:Item recipe/recipe IDs by product IDs.json")
-- A table mapping production methods to recipe IDs, with a intermediate mapping by availability.
local recipe_ids_by_method_and_availability = mw.loadJsonData(
"Module:Item recipe/recipe IDs by method and availability.json")
-- A table mapping material IDs (item IDs) to their display order.
-- Order is just a number. Materials with lesser order number will appear first.
local materials_order_by_material_ids = mw.loadJsonData("Module:Item recipe/order of materials.json")
-- A table remapping product IDs.
--
-- Not all recipes produce products that you might think they do -
-- some produce their own "printed" or "empty" or other variants.
--
-- For instance, a recipe for the small power cell produces `PowerCellSmallPrinted` item,
-- whereas the actual power cell item has ID `PowerCellSmall`.
--
-- So, for the module functions to be able to find the recipe for the small power cell,
-- we first would need to define a mapping from the recipe product `PowerCellSmallPrinted`
-- to the actual item `PowerCellSmall`.
--
-- After that is done, a lookup for small power cell (or its id) will return the corresponding recipe.
local product_overrides = mw.loadJsonData("Module:Item recipe/product overrides.json")
local current_frame = mw:getCurrentFrame()
local methods_items_els_cache = {}
local was_template_styles_tag_el_added = false
-- ====================
local function numeric_table_length(t)
local count = 0
for _ in ipairs(t) do count = count + 1 end
return count
end
local function table_length(t)
local count = 0
for _ in pairs(t) do count = count + 1 end
return count
end
local function numeric_table_has_value(tab, val)
for _, value in ipairs(tab) do
if value == val then
return true
end
end
return false
end
local function filter_table(tab, predicate)
local tab_filtered = {}
for key, value in pairs(tab) do
if predicate(key, value) then
tab_filtered[key] = value
end
end
return tab_filtered
end
local function assert_value_not_nil(value, error_message)
if value == nil then
if error_message == nil then
error("value is nil")
else
error(error_message)
end
end
end
-- Given a value, checks if it's "nil".
-- * If it's not - returns the `value`.
-- * IF it is - returns the `value_if_nil`.
local function nil_or(value, value_if_nil)
if value == nil then
return value_if_nil
else
return value
end
end
local function find_first_numeric_table_item_matching_condition(table, condition)
for i, item in ipairs(table) do
if condition(item, i, table) then
return item
end
end
end
local function find_first_table_item_matching_condition(table, condition)
for key, value in pairs(table) do
if condition(key, value, table) then
return value
end
end
end
local function find_first_table_item_key_matching_condition(table, condition)
for key, value in pairs(table) do
if condition(key, value, table) then
return key
end
end
end
local function map_numeric_table(tbl, f)
local t = {}
for i, v in ipairs(tbl) do
t[i] = f(v, i)
end
return t
end
local function map_table(tbl, f)
local t = {}
for k, v in pairs(tbl) do
t[k] = f(k, v)
end
return t
end
local function passthrough_assert_true(value, valueToReturnIfTrue, errorMessageOnFalse)
if value then
return valueToReturnIfTrue
else
error(errorMessageOnFalse)
end
end
-- ====================
-- ######################
-- ### METHOD LOOKUPS ###
-- ######################
-- Lookups recipe IDs by production method `production_method`.
--
-- Returns recipe IDs grouped by availability.
--
-- Raises an error if no recipe IDs were found by the production method.
-- Set `no_error` to `true` to return `nil` instead.
function p.lookup_recipe_ids_with_availability_by_method(production_method, no_error)
assert_value_not_nil(production_method)
-- check for an exact match
local exact_match = recipe_ids_by_method_and_availability[production_method]
if exact_match ~= nil then
return exact_match
end
-- check for case-independent match
local production_method_lower = string.lower(production_method)
for method, recipe_ids_grouped in pairs(recipe_ids_by_method_and_availability) do
if string.lower(method) == production_method_lower then
return recipe_ids_grouped
end
end
if not no_error then
error("recipe IDs by production method lookup failed: production method '" ..
production_method .. "' is not defined")
end
end
-- Lookups production methods by recipe ID `recipe_id`.
--
-- Returns an array of tables, each containg:
-- - `method` - production method.
-- - `availability` - availability for the recipe for this production method.
--
-- **NOTE:** This is an expensive function.
function p.lookup_methods_with_availability_by_recipe_id(inpit_recipe_id)
local result = {}
for method, recipe_ids_grouped in pairs(recipe_ids_by_method_and_availability) do
for availability, recipe_ids in pairs(recipe_ids_grouped) do
if numeric_table_has_value(recipe_ids, inpit_recipe_id) then
table.insert(result, {
method = method,
availability = availability
})
end
end
end
return result
end
-- Checks whether a production method `production_method` exists.
-- Any casing is allowed.
function p.method_exists(production_method)
assert_value_not_nil(production_method)
return p.lookup_recipe_ids_with_availability_by_method(production_method, true) ~= nil
end
-- Asserts that a production method `production_method` exists.
-- Any casing is allowed.
function p.assert_method_exists(production_method, custom_message)
assert_value_not_nil(production_method)
if not p.method_exists(production_method) then
if custom_message then
error("production method exists assertion failed for method '" ..
production_method .. "': " .. custom_message)
else
error("production method exists assertion failed for method '" ..
production_method .. "': production method is not defined")
end
end
end
-- ################################
-- ### PRODUCT OVERRIDE LOOKUPS ###
-- ################################
-- Lookups an override for product ID `product_id`.
--
-- For instance, if there was an override `PowerCellSmallPrinted` that maps to to item ID `PowerCellSmall`,
-- this functions would return `PowerCellSmall` if `product_id` was `PowerCellSmallPrinted`.
--
-- Raises an error if no match was found. Set `no_error` to `true` to return `nil` instead.
function p.lookup_product_id_override(product_id, no_error)
assert_value_not_nil(product_id)
return product_overrides[product_id]
or passthrough_assert_true(
no_error,
nil,
"product override lookup failed: no override was found with product ID '" ..
product_id .. "'"
)
end
-- Lookups the prodct ID that was overriden with item ID `item_id`.
--
-- For instance, if there was an override `PowerCellSmallPrinted` that maps to to item ID `PowerCellSmall`,
-- this functions would return `PowerCellSmallPrinted` if `item_id` was `PowerCellSmall`.
--
-- **NOTE:** This is an expensive function.
--
-- Raises an error if no match was found. Set `no_error` to `true` to return `nil` instead.
function p.reverse_lookup_product_id_override(item_id, no_error)
assert_value_not_nil(item_id)
return find_first_table_item_key_matching_condition(
product_overrides,
function(_, value) return value == item_id end
)
or passthrough_assert_true(
no_error,
nil,
"reverse product override lookup failed: no override was found that maps to item ID '" ..
item_id .. "'"
)
end
-- #######################
-- ### PRODUCT LOOKUPS ###
-- #######################
-- Checks whether a an item `product` exists.
-- Takes into account that `product` can have a product override.
function p.product_exsits(product)
assert_value_not_nil(product)
local product_with_override = p.lookup_product_id_override(product, true)
or product
return itemModule.item_exists(product_with_override)
end
-- Asserts that a product `product` exists.
-- `product` can be a product ID (including overriden ones), item ID or name.
function p.assert_product_exists(product, custom_message)
assert_value_not_nil(product)
if not p.product_exsits(product) then
if custom_message then
error("product exist assertion failed for product '" .. product .. "': " .. custom_message)
else
error("product exist assertion failed for product '" ..
product ..
"': no product was found. Make sure that a recipe exists with given product or that a product override is defined in Module:Item recipe")
end
end
end
-- Lookups recipe IDs by product ID `product_id`.
-- Accepts overriden `product_id`s.
--
-- Returns an array of matches.
--
-- **NOTE:** This is an expensive function.
function p.lookup_recipe_ids_by_product_id(product_id)
assert_value_not_nil(product_id)
local result = recipe_ids_by_product_ids[
p.reverse_lookup_product_id_override(product_id, true)
or product_id
]
-- always returns an array
if type(result) == 'string' then
return { result }
else
return result
end
end
-- ######################
-- ### RECIPE LOOKUPS ###
-- ######################
-- Lookups recipe by recipe ID `recipe_id`.
--
-- Returns `nil` if no recipe was found.
-- Set `no_error` to `true` to return `nil` instead.
function p.lookup_recipe_by_recipe_id(recipe_id, no_error)
assert_value_not_nil(recipe_id)
return recipes_by_recipe_ids[recipe_id]
or passthrough_assert_true(
no_error,
nil,
"failed to lookup recipe by recipe id '" .. recipe_id .. "': no recipe was found"
)
end
-- Asserts that a recipe ID `recipe_id` exists.
-- function assert_recipe_id_exists(recipe_id)
-- error("not impl")
-- end
-- Searches recipes by `query`. `query` can be an product ID (item ID - including overrden ones),
-- name or a recipe ID.
function p.search_recipes(query)
assert_value_not_nil(query, "failed to lookup recipes by method and item ID: item ID was not provided")
-- check if query is a recipe ID
local match_recipe_by_recipe_id = recipes_by_recipe_ids[query]
if match_recipe_by_recipe_id then
-- if so - we got a direct match!
return { match_recipe_by_recipe_id }
end
-- check if query is an item name/ID
-- this is a last possibility.
-- TODO add a custom errror here?
local item_id = itemModule.lookup_item_id(query)
local match_recipe_ids_by_product_id = p.lookup_recipe_ids_by_product_id(item_id)
local recipes = {}
if match_recipe_ids_by_product_id ~= nil then
for _, recipe_id in ipairs(match_recipe_ids_by_product_id) do
table.insert(recipes, p.lookup_recipe_by_recipe_id(recipe_id))
end
end
return recipes
end
-- Lookups recipes by production method.
--
-- Raises an error if no recipes were found by the production method.
function p.lookup_recipes_by_production_method(method)
assert_value_not_nil(method, "failed to lookup recipes by production method: no method was given")
p.assert_method_exists(method,
"failed to lookup recipes by production method: method '" .. method .. "' doesn't exist")
local recipe_ids_grouped = p.lookup_recipe_ids_with_availability_by_method(method)
local recipes = {}
for availability, recipe_ids in pairs(recipe_ids_grouped) do
for _, recipe_id in ipairs(recipe_ids) do
local recipe = p.lookup_recipe_by_recipe_id(recipe_id)
table.insert(recipes, recipe)
end
end
return recipes
end
-- Filters given recipes by production method.
-- Any casing is allowed for production method.
-- function p.filter_recipes_by_production_method(recipes, production_method)
-- p.assert_method_exists(production_method)
-- return filter_table(
-- p.lookup_recipe_ids_by_method(production_method),
-- function(_, recipe_ids_grouped)
-- return find_first_numeric_table_item_matching_condition(
-- recipes,
-- function(recipe) return recipe.id == recipe_ids_grouped end
-- ) ~= nil
-- end
-- )
-- end
-- ##############################
-- ### MATERIAL ORDER LOOKUPS ###
-- ##############################
-- Searches a material item using the material order config, returning the order number or `nil`,
-- if the material order config doesn't have the queried item.
--
-- Takes in an item ID or name.
local function try_lookup_order_of_material(material)
assert_value_not_nil(material, "failed to lookup order of material: material was not provided")
local item_id = itemModule.lookup_item_id(material, true)
if item_id == nil then
error("failed to lookup order of material: material '" .. material .. "' does not exist")
end
return materials_order_by_material_ids[item_id]
end
-- =======================
-- Produces a "note" element used in generation of item recipes.
-- Takes in a CSS-compatible color and text content.
local function generate_note_element(color, text)
local el = mw.html.create('span')
:addClass("item-recipe-note")
:node(text)
if color then
el:css('color', color)
end
return el
end
local function generate_info_icon(frame, kind)
if kind == 'research' then
return frame:expandTemplate {
title = 'Tooltip',
args = {
"[[File:JobIconResearchDirector.png|24px|link=Research_and_Development#R&D_Tree]]",
"This recipe is unlocked by '''research'''"
}
}
elseif kind == 'emag' then
return frame:expandTemplate {
title = 'Tooltip',
args = {
"[[File:Emag.png|42px|link=Cryptographic Sequencer]]",
"This recipe is unlocked by '''EMAG'''"
}
}
elseif kind == 'progression-symbol' then
return mw.html.create("span")
:addClass('info-icon-progression-symbol')
:node("↓")
else
error("failed to generate an info icon: unknown kind " .. kind)
end
end
-- ====================
-- Generates a recipe element for a given item.
-- This is the main external function of this module.
function p.generate_item_recipe(frame)
local args = getArgs(frame)
-- [REQUIRED]
-- Item name, alias, item ID or a recipe ID. Required.
local input_query = args[1]
assert_value_not_nil(input_query, "failed to generate a recipe for query: query was not provided")
-- [OPTIONAL]
-- Amount of item. Default is 1.
-- Must be a string since Module:Item uses string amount.
-- All values from templates come as strings.
local input_amount = nil_or(args[2], "1")
-- Item production method. Can be "nil", in which case it's looked up.
local input_method = args[3]
-- Whether to only generate a materials block.
local input_materials_only = yesNo(args["materials only"] or args["mat only"] or false)
-- Layout of materials in materials only mode.
local input_materials_only_layout = args["materials only layout"] or args["mat only layout"] or "vertical"
-- ============
-- search recipes
local recipes = p.search_recipes(input_query)
local recipes_count = numeric_table_length(recipes)
if recipes_count == 0 then
error("failed to generate a recipe for item: no recipe was found for item '" ..
input_query ..
"' (input method: '" ..
(input_method or "nil") ..
"'). Make sure a recipe exists for this item or define a product override for an existing recipe in Module:Item recipe")
elseif recipes_count > 1 then
error("failed to generate a recipe for item: found multiple recipes for item '" ..
input_query ..
"' (input method: '" ..
(input_method or "nil") ..
"'). Rendering multiple recipes is currently unsupported")
end
local recipe = recipes[1]
-- search recipe methods
local recipe_methods_lookup = p.lookup_methods_with_availability_by_recipe_id(recipe.id)
local recipe_methods_count = table_length(recipe_methods_lookup)
if recipe_methods_count == 0 then
error("failed to generate a recipe for item: no methods were found for recipe ID '" ..
recipe.id ..
"' (input query: '" .. input_query .. "'; input method: '" ..
(input_method or "nil") ..
"'). This shouldn't usually happen because the present recipes are bound to some production methods. Probable cause: bug in the recipe generation code")
elseif recipe_methods_count > 1 and input_method == nil then
local methods = map_numeric_table(
recipe_methods_lookup,
function(match)
return match.method
end
)
error("failed to generate a recipe for item: found multiple production methods for recipe ID '" ..
recipe.id ..
"' (input query: '" ..
input_query ..
"') and input production method was NOT specified. Rendering multiple recipes is unsupported, so please specify a production method from available methods for this recipe: '" ..
table.concat(methods, "', '") .. "'")
end
local recipe_method
local recipe_availability
if input_method == nil then
-- if no input methods is specified, use the single available recipe method
recipe_method = recipe_methods_lookup[1].method
recipe_availability = recipe_methods_lookup[1].availability
else
-- otherwise, the number of available methods can vary,
-- so filter it down to a single one based on the input method.
-- todo maybe convert this to a function
local input_method_lower = string.lower(input_method);
local recipe_methods_lookup_match = find_first_numeric_table_item_matching_condition(
recipe_methods_lookup,
function(lookup_result)
return string.lower(lookup_result.method) == input_method_lower
end
)
if recipe_methods_lookup_match == nil then
error("failed to generate a recipe for item: no production methods were found for recipe ID '" ..
recipe.id ..
"' (input query: '" ..
input_query ..
"') matching input method '" ..
(input_method or "nil") ..
"'. Make sure a recipe exists for this item with the specified production method or define a product override for an existing recipe with the specified production method in Module:Item recipe")
end
recipe_method = recipe_methods_lookup_match.method
recipe_availability = recipe_methods_lookup_match.availability
end
-- extract products
-- recipe product IDs mapped to amounts
local recipe_products = {}
if recipe.result then
recipe_products[recipe.result] = 1
elseif recipe.resultReagents then
for product_id, amount in pairs(recipe.resultReagents) do
recipe_products[product_id] = amount
end
else
error("failed to generate a recipe for item: no products were found for recipe ID '" ..
recipe.id ..
"' (input query: '" ..
input_query ..
"'). This might be due to recipe having another way to describe products")
end
-- generate recipe element
local recipe_el = mw.html.create("div")
:addClass("item-recipe")
if input_materials_only_layout == "vertical" or input_materials_only_layout == "ver" then
recipe_el:addClass("item-recipe-materials-layout-vertical")
else
recipe_el:addClass("item-recipe-materials-layout-horizontal")
end
local body_el = mw.html.create("div")
:addClass('item-recipe-body')
:css('background', 'linear-gradient(135deg, var(--bg-color-light-x2), var(--bg-color-light))')
local materials_el = mw.html.create("div")
:addClass("item-recipe-materials")
assert_value_not_nil(recipe.materials,
"failed to generate a recipe for item: no 'materials' are specified for recipe ID '" ..
recipe.id .. "' (input query: '" ..
input_query ..
"')")
-- copy list of materials, but without costs
local materials = {}
for material, cost in pairs(recipe.materials) do
table.insert(materials, material)
end
-- sort materials list based on material order config.
-- materials not in config will come after the ordered materials.
table.sort(materials, function(first, second)
return (try_lookup_order_of_material(first) or 999999999999)
< (try_lookup_order_of_material(second) or 999999999999)
end)
-- generate materials elements in sorted order
for _, material in ipairs(materials) do
local cost = recipe.materials[material]
local material_item_id = itemModule.lookup_item_id(material, true)
if material_item_id == nil then
error("failed to generate a recipe for item: material '" ..
material ..
"' was not found in item registry (for recipe ID '" ..
recipe.id .. "'; input query: '" ..
input_query ..
"'). Make sure that the material is added to the item name overrides in Module:Item")
end
materials_el:node(itemModule.generate_item{ material_item_id, cost })
end
body_el:node(materials_el)
if input_materials_only then
recipe_el:addClass("materials-only")
recipe_el:node(body_el)
else
local header_el = mw.html.create("div")
:addClass("item-recipe-header")
:css('background', 'linear-gradient(120deg, var(--bg-color-light-x3), var(--bg-color-light-x2))')
recipe_el:node(header_el)
local product_and_method_container_el = mw.html.create("div")
:addClass("item-recipe-product-and-method-container")
header_el:node(product_and_method_container_el)
local products_el = mw.html.create("div")
:addClass("item-recipe-products")
product_and_method_container_el:node(products_el)
for product_id, amount in pairs(recipe_products) do
local product_el = itemModule.generate_item { [1] = product_id, [2] = amount * input_amount, cap = true }
:addClass("item-recipe-product")
products_el:node(product_el)
end
-- TODO: not all methods will be items, so this will eventually break.
local method_el = methods_items_els_cache[recipe_method]
if method_el == nil then
method_el = mw.html.create("span")
:addClass('item-recipe-method')
:node(itemModule.generate_item { [1] = recipe_method, capitalize = true })
methods_items_els_cache[recipe_method] = method_el
end
product_and_method_container_el:node(method_el)
local info_icons_el = mw.html.create("div")
:addClass("item-recipe-info-icons")
header_el:node(info_icons_el)
recipe_el:node(body_el)
local complete_time_el = mw.html.create("span")
:addClass('item-recipe-complete-time')
if recipe.completetime == 0 then
complete_time_el:node("Instant")
else
complete_time_el:node((recipe.completetime * input_amount) .. " " .. "sec.")
end
body_el:node(complete_time_el)
local notes_el = mw.html.create("div")
:addClass("item-recipe-notes")
:css('background', 'linear-gradient(40deg, var(--bg-color-light-x3), var(--bg-color-light-x2))')
-- if recipe is not available by default,
-- generate "info icons" and notes (if needed) telling about it.
if recipe_availability ~= 'static' then
local is_recipe_unlocked_by_research = string.find(recipe_availability, "dynamic", 1, true) ~= nil
local is_recipe_unlocked_by_emag = string.find(recipe_availability, "emag", 1, true) ~= nil
local is_recipe_unlocked_by_research_and_then_emag = is_recipe_unlocked_by_research and
is_recipe_unlocked_by_emag
if is_recipe_unlocked_by_research_and_then_emag then
recipe_el:addClass('item-recipe-by-research')
recipe_el:addClass('item-recipe-by-emag')
info_icons_el:node(generate_info_icon(current_frame, 'research'))
info_icons_el:node(generate_info_icon(current_frame, 'progression-symbol'))
info_icons_el:node(generate_info_icon(current_frame, 'emag'))
notes_el:node(
generate_note_element(
nil,
current_frame:preprocess(
"'''This recipe is unlocked by [[Cryptographic Sequencer|EMAG]] after it has been [[Research_and_Development#R&D_Tree|researched]]'''")
)
)
elseif is_recipe_unlocked_by_research then
recipe_el:addClass('item-recipe-by-research')
info_icons_el:node(generate_info_icon(current_frame, 'research'))
-- if not is_recipe_unlocked_by_research_and_then_emag then
-- notes_el:node(
-- generate_note_element(
-- "gold",
-- "'''This recipe is unlocked by research'''"
-- )
-- )
-- end
else
recipe_el:addClass('item-recipe-by-emag')
info_icons_el:node(generate_info_icon(current_frame, 'emag'))
-- if not is_recipe_unlocked_by_research_and_then_emag then
-- notes_el:node(
-- generate_note_element(
-- "var(--danger-color)",
-- current_frame:preprocess(
-- "'''This recipe is unlocked by [[Cryptographic Sequencer|{{item|EmagUnlimited|l=EMAG}}]]'''")
-- )
-- )
-- end
end
end
recipe_el:node(notes_el)
end
if not was_template_styles_tag_el_added then
recipe_el:node(current_frame:extensionTag("templatestyles", "", { src = 'Template:Item recipe/styles.css' }))
was_template_styles_tag_el_added = true
end
return recipe_el
:allDone()
end
-- Generates an alphabetical list of recipes (elements) for a given production method.
-- Used to list all recipes for a particular method.
-- Recipes are sorted based on their products (the items display names).
function p.generate_list_of_recipes_for_method(frame)
local args = getArgs(frame)
local method = args[1]
assert_value_not_nil(method, "failed to generate a list of recipes for a method: method was not provided")
-- Limit on how many recipes to generate
local recipes_limit = tonumber(args.limit) or 99999999
local recipes = p.lookup_recipes_by_production_method(method)
assert_value_not_nil(recipes, "failed to generate a list of recipes for a method: unknown method: " .. method)
-- =======================
local container_el = mw.html.create("div")
:addClass("item-recipes-list")
-- -- generate a list of products
-- local products = {}
-- local i = 1
-- for product_item_id, _ in ipairs(recipes) do
-- table.insert(products, product_item_id)
-- if i == recipes_limit then
-- break
-- end
-- i = i + 1
-- end
-- -- sort the list of products alphabetically
-- -- by looking up the item names and comparing them
-- table.sort(products, function(first, second)
-- local first_item_id = p.lookup_product_id_override(first, true)
-- or first
-- local second_item_id = p.lookup_product_id_override(second, true)
-- or second
-- p.assert_product_exists(first_item_id)
-- p.assert_product_exists(second_item_id)
-- return itemModule.lookup_item_name_by_item_id(first_item_id)
-- < itemModule.lookup_item_name_by_item_id(second_item_id)
-- end)
-- -- generate recipe elements
-- for _, product in ipairs(products) do
-- container_el:node(p.generate_item_recipe {
-- [1] = p.lookup_product_id_override(product),
-- [2] = 1,
-- [3] = method
-- })
-- end
-- generate recipe elements
for _, recipe in ipairs(recipes) do
container_el:node(
p.generate_item_recipe {
[1] = recipe.id,
[2] = 1,
[3] = method
}
)
end
return container_el
:allDone()
end
return p