Module:Item recipe: Difference between revisions

From Space Station 14 Wiki
(wip)
(wip)
Line 1: Line 1:
-- Contains utilities for working with in-game items.
-- 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?
-- todo: material sorting. based on alphabetical sorting? maybe at .json generation step, convert materials to an array?
Line 5: Line 5:
local p = {} --p stands for package
local p = {} --p stands for package
local getArgs = require('Module:Arguments').getArgs
local getArgs = require('Module:Arguments').getArgs
local item = require('Module:Item')
local itemModule = require('Module:Item')


local recipes_by_recipe_method = {
-- An array of recipe groups.
lathe = mw.loadJsonData("Module:Item recipe/recipes by method/lathe.json")
-- 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 method/autolathe.json") },
{ method = "protolathe", recipes = mw.loadJsonData("Module:Item recipe/recipes by method/protolathe.json") },
}
}


Line 20: Line 24:




function numeric_table_length(t)
local function numeric_table_length(t)
local count = 0
local count = 0
for _ in ipairs(t) do count = count + 1 end
for _ in ipairs(t) do count = count + 1 end
Line 26: Line 30:
end
end


function table_length(t)
local function table_length(t)
local count = 0
local count = 0
for _ in pairs(t) do count = count + 1 end
for _ in pairs(t) do count = count + 1 end
Line 32: Line 36:
end
end


function table_has_value(tab, val)
local function 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 42: Line 46:
end
end


function assert_value_not_nil(value, error_message)
local function assert_value_not_nil(value, error_message)
if value == nil then
if value == nil then
if error_message == nil then
if error_message == nil then
Line 49: Line 53:
error(error_message)
error(error_message)
end
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
end
end
Line 54: Line 69:
-- ====================
-- ====================


-- Searches for recipes using production method and a product.
-- Searches a recipe for a given item, optionally with a specific production method.
-- Product is the item that results from a recipe. It can be a name, alias or an ID.
-- @returns A table with `method` and `recipe`, or `nil` if no recipe was found.
-- Name and alias can both be written in any case.
local function lookup_recipe_by_item_id(item_id, method)
-- Returns an array of recipes found.
assert_value_not_nil(item_id, "failed to lookup recipes by method and item ID: item ID was not provided")
function lookup_recipes_by_method_and_product(method, product)
assert_value_not_nil(method, "method was not provided")
assert_value_not_nil(product, "product was not provided")
-- lookup the actual item ID
product = item.lookup_item_id_by_name_and_amount({ [1] = product, [2] = "1" })


if method == "protolathe" or method == "autolathe" then
-- production methods to lookup through
local matching_recipes = {}
local methods_to_lookup = {}
for _, recipe in ipairs(recipes_by_recipe_method.lathe) do
if method == nil then
local recipe_result = recipe.result
-- no method specified = look through all methods until the recipe is found
-- todo err message
for _, recipe_group in ipairs(recipe_groups) do
assert_value_not_nil(recipe_result)
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


if recipe_result == product then
for _, method in ipairs(methods_to_lookup) do
table.insert(matching_recipes, recipe)
for _, recipe in ipairs(recipe_groups[method].recipes) do
if recipe.result == item_id then
return {
method = method,
recipe = recipe
}
end
end
end
end
return matching_recipes
else
error("unknown recipe method: " .. method)
end
end
end
end
Line 87: Line 102:
-- Generates an HTML element, containing recipe components used to produce given item with a given method.
-- Generates an HTML element, containing recipe components used to produce given item with a given method.
-- Returns a DIV containg {{item}}s.
-- Returns a DIV containg {{item}}s.
function p.generate_recipe_skeleton(frame)
function generate_recipe_materials(frame)
local args = getArgs(frame)
local args = getArgs(frame)


Line 95: Line 110:
assert_value_not_nil(product, "failed to generate a recipe skeleton: product was not provided")
assert_value_not_nil(product, "failed to generate a recipe skeleton: product was not provided")


local matching_recipes = lookup_recipes_by_method_and_product(method, product)
local matching_recipes = lookup_recipes_by_method_and_item_id(method, product)
local matching_recipes_len = numeric_table_length(matching_recipes)
local matching_recipes_len = numeric_table_length(matching_recipes)


local container_el = mw.html.create("div")
 
end
 
function p.generate_item_recipe(frame)
local args = getArgs(frame)
-- Item name, alias or ID. Required.
local item = args[1]
 
-- 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
assert_value_not_nil(item, "failed to generate a recipe for item: item was not provided")
 
-- ============
 
local current_frame = mw:getCurrentFrame()
 
local item_id = itemModule.lookup_item_id_by_name_and_amount(item, 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 ..")")
 
local recipe = recipe_lookup_result.recipe
local method = recipe_lookup_result.method
 
local recipe_el = mw.html.create("div")
:addClass("item-recipe")
:addClass("item-recipe")
local product_el = mw.html.create("div")
:addClass("item-recipe-product")
recipe_el:node(current_frame:preprocess("{{item|" .. item_id .. "|" .. amount .. "}}"))
recipe_el:node(product_el)
local method_el = mw.html.create("div")
:addClass("item-recipe-method")


if matching_recipes_len == 0 then
-- TODO: not all methods will be items, so this will eventually break.
error("failed to generate a recipe skeleton: no recipe found for product '" .. product .. "' that's made using '" .. method .. "'")
recipe_el:node(current_frame:preprocess("{{item|" .. method .. "}}"))
elseif matching_recipes_len == 1 then
local matching_recipe = matching_recipes[1]


assert_value_not_nil(matching_recipe.materials, "'materials' field is nil for recipe " .. matching_recipe.id)
recipe_el:node(method_el)


for material, cost in pairs(matching_recipe.materials) do
container_el:node(mw:getCurrentFrame():preprocess("{{item|" .. material .. "|" .. cost .. "}}"))
end


return container_el
local materials_el = mw.html.create("div")
:allDone()
:addClass("item-recipe-materials")
else
 
error("failed to generate a recipe skeleton: found multiple matching recipes for given method " .. method .. " and product " .. product)
assert_value_not_nil(recipe_lookup_result.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_lookup_result.materials) do
recipe_el:node(current_frame:preprocess("{{item|" .. material .. "|" .. (cost * amount) .. "}}"))
end
end
materials_el:node(materials_el)
return recipe_el
:allDone()
end
end


-- -- Generates a list of items needed for a recipe, along with exact amounts.
-- -- Generates a list of items needed for a recipe, along with exact amounts.

Revision as of 17:22, 25 August 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:

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')

-- 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 method/autolathe.json") },
	{ method = "protolathe", 	recipes = mw.loadJsonData("Module:Item recipe/recipes by method/protolathe.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

-- ====================

-- 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(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

	for _, method in ipairs(methods_to_lookup) do
		for _, recipe in ipairs(recipe_groups[method].recipes) do
			if recipe.result == item_id then
				return {
					method = method,
					recipe = recipe
				}
			end
		end
	end
end

-- ====================

-- Generates an HTML element, containing recipe components used to produce given item with a given method.
-- Returns a DIV containg {{item}}s.
function generate_recipe_materials(frame)
	local args = getArgs(frame)

	local method = args[1]
	assert_value_not_nil(method, "failed to generate a recipe skeleton: method was not provided")
	local product = args[2]
	assert_value_not_nil(product, "failed to generate a recipe skeleton: product was not provided")

	local matching_recipes = lookup_recipes_by_method_and_item_id(method, product)
	local matching_recipes_len = numeric_table_length(matching_recipes)


end

function p.generate_item_recipe(frame)
	local args = getArgs(frame)
	
	-- Item name, alias or ID. Required.
	local item = args[1]

	-- 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
	
	assert_value_not_nil(item, "failed to generate a recipe for item: item was not provided")

	-- ============

	local current_frame = mw:getCurrentFrame()

	local item_id = itemModule.lookup_item_id_by_name_and_amount(item, 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 ..")")

	local recipe = recipe_lookup_result.recipe
	local method = recipe_lookup_result.method

	
	local recipe_el = mw.html.create("div")
		:addClass("item-recipe")
		

	local product_el = mw.html.create("div")
		:addClass("item-recipe-product")

	recipe_el:node(current_frame:preprocess("{{item|" .. item_id .. "|" .. amount .. "}}"))

	recipe_el:node(product_el)
	

	local method_el = mw.html.create("div")
		:addClass("item-recipe-method")

	-- TODO: not all methods will be items, so this will eventually break.
	recipe_el:node(current_frame:preprocess("{{item|" .. method .. "}}"))

	recipe_el:node(method_el)


	local materials_el = mw.html.create("div")
		:addClass("item-recipe-materials")

	assert_value_not_nil(recipe_lookup_result.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_lookup_result.materials) do
		recipe_el:node(current_frame:preprocess("{{item|" .. material .. "|" .. (cost * amount) .. "}}"))
	end

	materials_el:node(materials_el)


	return recipe_el
		:allDone()

end

-- -- Generates a list of items needed for a recipe, along with exact amounts.
-- -- Needs a recipe ID passed as a single frame argument.
-- -- Uses {{Item}} to produce the items. Returns a div containing them.
-- function p.generate_recipe_items(frame)
-- 	local recipe_id = args[1]
-- 	assert_value_not_nil(recipe_id, "recipe ID was not provided")
-- end

return p