Module:Item recipe: Difference between revisions
From Space Station 14 Wiki
(wip) |
(rework) |
||
Line 9: | Line 9: | ||
-- An array of recipe groups. | -- An array of recipe groups. | ||
-- | -- Keys are production methods, values are tables containing the recipes. | ||
local recipe_groups = { | local recipe_groups = { | ||
autolathe = mw.loadJsonData("Module:Item recipe/recipes by lathe/autolathe.json"), | |||
protolathe = mw.loadJsonData("Module:Item recipe/recipes by lathe/protolathe.json") | |||
} | } | ||
local product_overrides = mw.loadJsonData("Module:Item recipe/product overrides.json") | |||
local | |||
-- ==================== | -- ==================== | ||
Line 86: | Line 72: | ||
local function find_first_table_item_matching_condition(table, condition) | local function find_first_table_item_matching_condition(table, condition) | ||
for key, | for key, value in pairs(table) do | ||
if condition( | if condition(key, value, table) then | ||
return | return value | ||
end | end | ||
end | end | ||
Line 94: | Line 80: | ||
local function find_first_table_item_key_matching_condition(table, condition) | local function find_first_table_item_key_matching_condition(table, condition) | ||
for key, | for key, value in pairs(table) do | ||
if condition( | if condition(key, value, table) then | ||
return key | return key | ||
end | end | ||
Line 103: | Line 89: | ||
-- ==================== | -- ==================== | ||
local function | -- Searchs for given recipe product in product override table. | ||
local | -- If one is found, replaces it with the override product, | ||
-- otherwise returning the same product. | |||
-- Used for recipes which produce items that may not be present in the items table, | |||
-- e.g. a printed variation of a power cell. | |||
-- The exact use is for rendering recipe products - you can't render an unknown items, so you gotta override. | |||
local function apply_product_override(product_item_id) | |||
local match = product_overrides[product_item_id] | |||
if match then | |||
return match | |||
else | |||
return product_item_id | |||
end | end | ||
end | end | ||
-- | -- Reverse of `apply_product_override` - | ||
-- | -- searches for the original product based on the override product. | ||
local function | -- If none found - returns the same product. | ||
local | -- The exact use is for searching for a recipe for an item - the recipe may have a different product specified. | ||
local function reverse_product_override(product_item_id) | |||
function ( | local match = find_first_table_item_key_matching_condition( | ||
product_overrides, | |||
function (key, value) return value == product_item_id end | |||
) | ) | ||
if | if match then | ||
return match | |||
else | |||
return product_item_id | return product_item_id | ||
end | end | ||
end | end | ||
-- Searches a recipe | -- Searches a recipe of a given item. | ||
-- | -- If `production_method` is specified, only looks through the recipes with that method, | ||
local function | -- otherwise searching across all methods until a match is found. | ||
-- | |||
-- Returns table with `production_method` and `recipe`, or `nil` if no recipe was found. | |||
local function lookup_recipe_by_item_id(item_id, production_method) | |||
assert_value_not_nil(item_id, "failed to lookup recipes by method and item ID: item ID was not provided") | assert_value_not_nil(item_id, "failed to lookup recipes by method and item ID: item ID was not provided") | ||
-- production methods to | -- generate a list of production methods to search across | ||
local methods_to_lookup = {} | local methods_to_lookup = {} | ||
if | if production_method == nil then | ||
-- no method specified = | -- no method specified = search across all methods | ||
for _ | for recipe_group_method, _ in pairs(recipe_groups) do | ||
table.insert(methods_to_lookup, | table.insert(methods_to_lookup, recipe_group_method) | ||
end | end | ||
else | else | ||
-- method specified = only look through recipes with that production method | -- method specified = only look through recipes with that production method | ||
table.insert(methods_to_lookup, | table.insert(methods_to_lookup, production_method) | ||
end | end | ||
-- do the search | -- do the search | ||
for _, | for _, production_method in ipairs(methods_to_lookup) do | ||
local match = recipe_groups[production_method][item_id] | |||
if match then | |||
return { | |||
production_method = production_method, | |||
recipe = match | |||
} | |||
end | end | ||
end | end | ||
end | 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 function generate_note_element(color, text) | ||
return mw.html.create('span') | return mw.html.create('span') | ||
Line 172: | Line 164: | ||
-- ==================== | -- ==================== | ||
-- Generates a recipe element for a given item. | |||
-- This is the main external function of this module. | |||
function p.generate_item_recipe(frame) | function p.generate_item_recipe(frame) | ||
local args = getArgs(frame) | local args = getArgs(frame) | ||
Line 203: | Line 197: | ||
local item_id = itemModule.lookup_item_id_by_name_and_amount{ [1] = item, [2] = amount } | local item_id = itemModule.lookup_item_id_by_name_and_amount{ [1] = item, [2] = amount } | ||
local recipe_lookup_result = | local recipe_lookup_result = lookup_recipe_by_item_id(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") ..")") | 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 recipe = recipe_lookup_result.recipe | ||
local method = recipe_lookup_result. | local method = recipe_lookup_result.production_method | ||
Line 250: | Line 244: | ||
:addClass("item-recipe-notes") | :addClass("item-recipe-notes") | ||
if recipe. | if recipe.availability then | ||
if string.find(recipe. | if string.find(recipe.availability, "dynamic", 1, true) ~= nil then | ||
notes_el:node(generate_note_element("gold", "'''This recipe must be researched first'''")) | notes_el:node(generate_note_element("gold", "'''This recipe must be researched first'''")) | ||
end | end | ||
if string.find(recipe. | if string.find(recipe.availability, "emag", 1, true) ~= nil then | ||
notes_el:node(generate_note_element("var(--link-color-visited)", current_frame:preprocess("'''This recipe is only available by [[Cryptographic Sequencer|<u>{{item|emag|l=EMAG}}</u>]]'''"))) | notes_el:node(generate_note_element("var(--link-color-visited)", current_frame:preprocess("'''This recipe is only available by [[Cryptographic Sequencer|<u>{{item|emag|l=EMAG}}</u>]]'''"))) | ||
end | end | ||
Line 281: | Line 275: | ||
end | end | ||
-- Generates a list of recipe elements for a given production method. | |||
-- Used to list all recipes for a particular method. | |||
function p.generate_list_of_recipes_for_method(frame) | function p.generate_list_of_recipes_for_method(frame) | ||
local args = getArgs(frame) | local args = getArgs(frame) | ||
Line 288: | Line 283: | ||
assert_value_not_nil(method, "failed to generate a list of recipes for a method: method was not provided") | assert_value_not_nil(method, "failed to generate a list of recipes for a method: method was not provided") | ||
local recipes = | local recipes = recipe_groups[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") | local container_el = mw.html.create("div") |
Revision as of 01:59, 2 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.
-- 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.
-- Keys are production methods, values are tables containing the recipes.
local recipe_groups = {
autolathe = mw.loadJsonData("Module:Item recipe/recipes by lathe/autolathe.json"),
protolathe = mw.loadJsonData("Module:Item recipe/recipes by lathe/protolathe.json")
}
local product_overrides = mw.loadJsonData("Module:Item recipe/product overrides.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, 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
-- ====================
-- Searchs for given recipe product in product override table.
-- If one is found, replaces it with the override product,
-- otherwise returning the same product.
-- Used for recipes which produce items that may not be present in the items table,
-- e.g. a printed variation of a power cell.
-- The exact use is for rendering recipe products - you can't render an unknown items, so you gotta override.
local function apply_product_override(product_item_id)
local match = product_overrides[product_item_id]
if match then
return match
else
return product_item_id
end
end
-- Reverse of `apply_product_override` -
-- searches for the original product based on the override product.
-- If none found - returns the same product.
-- The exact use is for searching for a recipe for an item - the recipe may have a different product specified.
local function reverse_product_override(product_item_id)
local match = find_first_table_item_key_matching_condition(
product_overrides,
function (key, value) return value == product_item_id end
)
if match then
return match
else
return product_item_id
end
end
-- Searches a recipe of a given item.
-- If `production_method` is specified, only looks through the recipes with that method,
-- otherwise searching across all methods until a match is found.
--
-- Returns table with `production_method` and `recipe`, or `nil` if no recipe was found.
local function lookup_recipe_by_item_id(item_id, production_method)
assert_value_not_nil(item_id, "failed to lookup recipes by method and item ID: item ID was not provided")
-- generate a list of production methods to search across
local methods_to_lookup = {}
if production_method == nil then
-- no method specified = search across all methods
for recipe_group_method, _ in pairs(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, production_method)
end
-- do the search
for _, production_method in ipairs(methods_to_lookup) do
local match = recipe_groups[production_method][item_id]
if match then
return {
production_method = production_method,
recipe = match
}
end
end
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)
return mw.html.create('span')
:addClass("item-recipe-note")
:node(text)
:css('color', color)
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 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 = args[3]
-- 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(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.production_method
local recipe_el = mw.html.create("div")
:addClass("item-recipe")
:node(current_frame:extensionTag("templatestyles", "", { src = 'Template:Item/styles.css' }))
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, cap = true })
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.availability then
if string.find(recipe.availability, "dynamic", 1, true) ~= nil then
notes_el:node(generate_note_element("gold", "'''This recipe must be researched first'''"))
end
if string.find(recipe.availability, "emag", 1, true) ~= nil then
notes_el:node(generate_note_element("var(--link-color-visited)", current_frame:preprocess("'''This recipe is only available by [[Cryptographic Sequencer|<u>{{item|emag|l=EMAG}}</u>]]'''")))
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(itemModule.generate_item{ [1] = material, [2] = cost * amount })
end
recipe_el:node(materials_el)
return recipe_el
:allDone()
end
-- Generates a list of recipe elements for a given production method.
-- Used to list all recipes for a particular method.
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")
local recipes = recipe_groups[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")
:css("display", "flex")
:css("flex-diretion", "column")
for _, recipe in ipairs(recipes) do
container_el:node(p.generate_item_recipe{ [1] = recipe.result, [2] = 1, [3] = method })
end
return container_el
:allDone()
end
return p