Module:Item recipe
From Space Station 14 Wiki
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.
-- todo: material sorting. based on alphabetical sorting? maybe at .json generation step, convert materials to an array?
local p = {} --p stands for package
local getArgs = require('Module:Arguments').getArgs
local itemModule = require('Module:Item')
local yesNo = require('Module:Yesno')
-- An array of recipe groups.
-- Recipes are grouped by `method`.
-- Note that the order matters for perfomance - the recipe lookup happens from top to bottom.
local recipe_groups = {
{ method = "autolathe", recipes = mw.loadJsonData("Module:Item recipe/recipes by lathe/autolathe.json") },
{ method = "protolathe", recipes = mw.loadJsonData("Module:Item recipe/recipes by lathe/protolathe.json") },
}
-- A table mapping overriding some recipes' products.
-- Keys are the recipe products, values are the new products for these recipes.
-- It's advised to use item IDs for values, as they offer much more perfomance.
--
-- Used for recipes which use separate items for produced items,
-- which are the same or almost the same as regular variants.
-- This lets the system know the proper items to display.
local recipes_products_overrides = mw.loadJsonData("Module:Item recipe/product overrides.json")
-- A table containing item recipes, identified by recipe IDs.
-- local recipes_by_recipe_id =
-- A table containing item recipe categories, identified by recipe category IDs.
-- local recipy_categories_by_recipe_category_id = mw.loadJsonData("Module:Item recipe/recipy categories by recipe category id.json")
-- ====================
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 table_has_value(tab, val)
for _, value in ipairs(tab) do
if value == val then
return true
end
end
return false
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, item in pairs(table) do
if condition(item, key, table) then
return item
end
end
end
local function find_first_table_item_key_matching_condition(table, condition)
for key, item in pairs(table) do
if condition(item, key, table) then
return key
end
end
end
-- ====================
local function get_recipes_by_method(method)
local recipe_group = find_first_numeric_table_item_matching_condition(
recipe_groups,
function (item)
return item.method == method
end
)
if recipe_group == nil then
error("failed to get recipes by method: no such recipe group with method " .. method)
end
return recipe_group.recipes
end
-- Given an item ID as a some recipe product, lookups it in the prduct overrides.
-- On a match, returns a new item ID to use as a product.
local function resolve_recipe_product_override_if_needed(product_item_id)
local match_override_product = find_first_table_item_matching_condition(
recipes_products_overrides,
function (override_product, recipe_product) return recipe_product == product_item_id end
)
if match_override_product == nil then
return product_item_id
else
return match_override_product
end
end
-- Searches a recipe for a given item, optionally with a specific production method.
-- @returns A table with `method` and `recipe`, or `nil` if no recipe was found.
local function lookup_recipe_by_item_id_and_method(item_id, method)
assert_value_not_nil(item_id, "failed to lookup recipes by method and item ID: item ID was not provided")
-- production methods to lookup through
local methods_to_lookup = {}
if method == nil then
-- no method specified = look through all methods until the recipe is found
for _, recipe_group in ipairs(recipe_groups) do
table.insert(methods_to_lookup, recipe_group.method)
end
else
-- method specified = only look through recipes with that production method
table.insert(methods_to_lookup, method)
end
-- do the search
for _, method in ipairs(methods_to_lookup) do
for _, recipe in ipairs(get_recipes_by_method(method)) do
if resolve_recipe_product_override_if_needed(recipe.result) == item_id then
return {
method = method,
recipe = recipe
}
end
end
end
end
-- ====================
function p.generate_item_recipe(frame)
local args = getArgs(frame)
-- [REQUIRED]
-- Item name, alias or ID. Required.
local item = args[1]
assert_value_not_nil(item, "failed to generate a recipe for item: item 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 amount = nil_or(args[2], "1")
-- Item production method. Can be "nil", in which case it's looked up.
local method = nil
-- Recipe layout.
local layout = args["layout"] or args["lay"] or "horizontal"
-- Whether to only generate a materials block.
local materials_only = yesNo(args["materials only"] or args["mat only"] or false)
-- ============
local current_frame = mw:getCurrentFrame()
local item_id = itemModule.lookup_item_id_by_name_and_amount{ [1] = item, [2] = amount }
local recipe_lookup_result = lookup_recipe_by_item_id_and_method(item_id, method)
assert_value_not_nil(recipe_lookup_result, "failed to generate a recipe for item: no recipe found for item " .. item_id .. " (method: " .. (method or "nil") ..")")
local recipe = recipe_lookup_result.recipe
local method = recipe_lookup_result.method
local recipe_el = mw.html.create("div")
:addClass("item-recipe")
if layout == "vertical" or layout == "ver" then
recipe_el:addClass("item-recipe-vertical")
else
recipe_el:addClass("item-recipe-horizontal")
end
if materials_only then
recipe_el:addClass("materials-only")
end
if not materials_only then
local product_el = mw.html.create("div")
:addClass("item-recipe-product")
product_el:node(itemModule.generate_item{ [1] = item_id, [2] = amount })
recipe_el:node(product_el)
local method_el = mw.html.create("div")
:addClass("item-recipe-method")
:node(mw.html.create("span"):addClass('recipe-supplementary-text'):node('is made on '))
-- TODO: not all methods will be items, so this will eventually break.
:node(itemModule.generate_item{ [1] = method })
:node(mw.html.create("span"):addClass('recipe-supplementary-text'):node(' with'))
recipe_el:node(method_el)
local notes_el = mw.html.create("div")
:addClass("item-recipe-notes")
if recipe.latheRecipeType then
if string.find(recipe.latheRecipeType, "dynamic", 1, true) ~= nil then
local researchable_recipe_notice_el = mw.html.create('sub')
:node("This recipe must be researched first")
:css('color', 'gold')
notes_el:node(researchable_recipe_notice_el)
end
if string.find(recipe.latheRecipeType, "emag", 1, true) ~= nil then
local researchable_recipe_notice_el = mw.html.create('sub')
:wikitext("This recipe is only available by [[Cryptographic Sequencer|EMAG]]")
:css('color', 'magenta')
notes_el:node(researchable_recipe_notice_el)
end
end
recipe_el:node(notes_el)
end
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 item " .. item_id .. " recipe (method: " .. method ..")")
for material, cost in pairs(recipe.materials) do
materials_el:node(current_frame:preprocess("{{item|" .. material .. "|" .. (cost * amount) .. "}}"))
end
recipe_el:node(materials_el)
return recipe_el
:allDone()
end
return p