Module:Recipe: Difference between revisions

From Against the Storm Official Wiki
debugging; maybe solved? # operator doesn't work on arrays loaded from mw.loadData!
m small doc improvement
 
(2 intermediate revisions by the same user not shown)
Line 26: Line 26:
-- Use `pairs()` to iterate, not `ipairs()`.<br>
-- Use `pairs()` to iterate, not `ipairs()`.<br>
-- For sorting, extract keys, then sort, then iterate.
-- For sorting, extract keys, then sort, then iterate.
---@alias RecipeSublistByStacksize table<integer, Recipe>
---@alias RecipeSublistByStacksize table<Amount, Recipe>


-- The ID and amount of a good or service made.
-- The ID and amount of a good or service made.
Line 32: Line 32:


-- The ID, amount, and probability of an extra product.
-- The ID, amount, and probability of an extra product.
---@alias ExtraProductChance {_id: ProductID, _amount: integer, _chance: number}
---@alias ExtraProductChance {_id: ProductID, _amount: Amount, _chance: number}


-- The ID of a good or service produced.
-- The ID of a good or service produced.
---@alias ProductID ResourceID
---@alias ProductID ResourceID
-- The display name of a good or service produced.
---@alias ProductName ResourceName|NeedName
-- An amount of a good or resource.
---@alias Amount integer


-- A number 0, 1, 2, or 3 (worst to best) of a recipe indicating its efficiency, effectiveness, or capability.
-- A number 0, 1, 2, or 3 (worst to best) of a recipe indicating its efficiency, effectiveness, or capability.
Line 43: Line 49:


--#region Public Methods
--#region Public Methods
-- Adds the given building ID to the recipe's list of buildings that can make it.
---@param recipe Recipe
---@param buildingID BuildingID
---@return BuildingID[] buildings
function Recipe.addBuilding(recipe, buildingID)
table.insert(recipe._buildings, buildingID)
return recipe._buildings
end


-- Checks if the given ingredient is anywhere in the recipe's ingredient options.
-- Checks if the given ingredient is anywhere in the recipe's ingredient options.
Line 84: Line 81:
---@param slotIndex integer
---@param slotIndex integer
---@param optionIndex integer
---@param optionIndex integer
---@return ResourceID
---@return ResourceID # ingredient ID
---@return integer # amount
---@return Amount # amount
function Recipe.getIngredient(recipe, slotIndex, optionIndex)
function Recipe.getIngredient(recipe, slotIndex, optionIndex)
local ingredientPair = recipe._ingredients[slotIndex][optionIndex]
local ingredientPair = recipe._ingredients[slotIndex][optionIndex]
Line 93: Line 90:
-- Gets the product of the recipe, both the ID and amount.
-- Gets the product of the recipe, both the ID and amount.
---@param recipe Recipe
---@param recipe Recipe
---@return ProductID
---@return ProductID # product ID
---@return integer # amount
---@return Amount # amount
function Recipe.getProduct(recipe)
function Recipe.getProduct(recipe)
return recipe._productPair._id, recipe._productPair._amount
return recipe._productPair._id, recipe._productPair._amount
end
end


-- Gets the number of ingredient slots in the recipe.
-- Gets the number of ingredient slots in the recipe.<br>
-- This *must* loop over the array due to MediaWiki's metatables on data loaded with `mw.loadData` messing with `#` operator.
---@param recipe Recipe
---@param recipe Recipe
---@return integer
---@return integer # number of ingredient slots
function Recipe.getNumIngredients(recipe)
function Recipe.getNumIngredients(recipe)
if not recipe._ingredients then
if not recipe._ingredients then
Line 113: Line 111:
end
end


-- Gets the number of options for the given ingredient slot.
-- Gets the number of options for the given ingredient slot.<br>
-- This *must* loop over the array due to MediaWiki's metatables on data loaded with `mw.loadData` messing with `#` operator.
---@param recipe Recipe
---@param recipe Recipe
---@param slotIndex integer
---@param slotIndex integer
---@return integer
---@return integer # number of options in the slot
function Recipe.getNumOptions(recipe, slotIndex)
function Recipe.getNumOptions(recipe, slotIndex)
if not recipe._ingredients then
if not recipe._ingredients then
Line 130: Line 129:
-- Gets the array of buildings that can make the recipe.
-- Gets the array of buildings that can make the recipe.
---@param recipe Recipe
---@param recipe Recipe
---@return BuildingID[]
---@return BuildingID[] _buildings
function Recipe.getBuildings(recipe)
function Recipe.getBuildings(recipe)
return recipe._buildings
return recipe._buildings
end
-- Gets the number of buildings that can make the recipe.<br>
-- This *must* loop over the array due to MediaWiki's metatables on data loaded with `mw.loadData` messing with `#` operator.
---@param recipe Recipe
---@return integer # number of buildings that can make the recipe
function Recipe.getNumBuildings(recipe)
if not recipe._buildings then
error("Recipe.getNumBuildings cannot work with nil array.")
end
local count = 0
for _ in ipairs(recipe._buildings) do
count = count + 1
end
return count
end
end


Line 149: Line 163:
local seconds = (recipe._time or 0) % 60
local seconds = (recipe._time or 0) % 60
return string.format("%d:%02d", minutes, seconds)
return string.format("%d:%02d", minutes, seconds)
end
-- Adds the given building ID to the recipe's list of buildings that can make it.
---@param recipe Recipe
---@param buildingID BuildingID
---@return BuildingID[] _buildings with the new building added
function Recipe.addBuilding(recipe, buildingID)
table.insert(recipe._buildings, buildingID)
return recipe._buildings
end
-- Copies the given recipe.<br>
-- *This is required for any recipes loaded with `mw.loadData` so they can be modified and so the `#` operator works.
---@param recipe Recipe
---@return Recipe copy
function Recipe.copy(recipe)
---@type Recipe
local copy = {
_buildings = {},
    _grade = recipe._grade,
    _time = recipe._time,
    _productPair = {
      _id = recipe._productPair._id,
      _amount = recipe._productPair._amount
    },
    _isService = recipe._isService,
_extraProductChances = nil, -- optional and needs to be nil unless the original has it
    _ingredients = {}
}
for _, buildingID in ipairs(recipe._buildings) do
table.insert(copy._buildings, buildingID)
end
if recipe._extraProductChances then
copy._extraProductChances = {}
for i, extra in ipairs(recipe._extraProductChances) do
copy._extraProductChances[i] = {
_id = extra._id,
_amount = extra._amount,
_chance = extra._chance
}
end
end
for i, slot in ipairs(recipe._ingredients) do
copy._ingredients[i] = {}
for j, option in ipairs(slot) do
copy._ingredients[i][j] = {
_id = option._id,
_amount = option._amount
}
end
end
return copy
end
end



Latest revision as of 18:40, 3 November 2025

Documentation for this module may be created at Module:Recipe/doc

-- Provides a standard way of interacting with recipe data.
local Recipe = {}

-- A single recipe with one product, one grade, and one stack; possibly in more than one building.
---@class Recipe
---@field package _buildings BuildingID[] The ID codes of buildings that can make this recipe.
---@field package _grade Grade How many efficiency stars (0-3) the recipe has.
---@field package _time number Seconds to produce one product.
---@field package _productPair ProductPair The product and amount of it produced.
---@field package _isService boolean `true` if this recipe offers a service instead of a product.
---@field package _extraProductChances? ExtraProductChance[] Possible extra products for each cycle, if any, before any upgrades.
---@field package _ingredients IngredientSlot[] Ingredient slots (0-3, usually 1-2), each with multiple option choices.

-- Array of (usually 1-6, can be 8) interchangeable options for one ingredient slot in a recipe.
---@alias IngredientSlot IngredientOption[]
-- One acceptable option of good to use in the recipe.
---@alias IngredientOption ResourcePair

-- Recipes sorted by [productID][grade][stackSize].
---@alias RecipeBook table<ProductID, RecipeListByGrade>
-- Subset of recipes sorted by [grade][stackSize] (sparse: only existing grades present).<br>
-- Use `pairs()` to iterate, not `ipairs()`.<br>
-- For sorting, extract keys, then sort, then iterate.
---@alias RecipeListByGrade table<Grade, RecipeSublistByStacksize>
-- Subsubset of recipes sorted by [stackSize] (sparse: only existing stack sizes present).<br>
-- Use `pairs()` to iterate, not `ipairs()`.<br>
-- For sorting, extract keys, then sort, then iterate.
---@alias RecipeSublistByStacksize table<Amount, Recipe>

-- The ID and amount of a good or service made.
---@alias ProductPair ResourcePair

-- The ID, amount, and probability of an extra product.
---@alias ExtraProductChance {_id: ProductID, _amount: Amount, _chance: number}

-- The ID of a good or service produced.
---@alias ProductID ResourceID

-- The display name of a good or service produced.
---@alias ProductName ResourceName|NeedName

-- An amount of a good or resource.
---@alias Amount integer

-- A number 0, 1, 2, or 3 (worst to best) of a recipe indicating its efficiency, effectiveness, or capability.
---@alias Grade 0|1|2|3



--#region Public Methods

-- Checks if the given ingredient is anywhere in the recipe's ingredient options.
---@param recipe Recipe
---@param ingredientID ResourceID
---@return boolean
---@return integer|nil slotIndex
---@return integer|nil optionIndex
function Recipe.isIngredientInOptions(recipe, ingredientID)
	if not recipe then error("Cannot find ingredient in nil recipe") end
	if not ingredientID then error("Cannot find ingredient using nil ingredientID") end
	for slotIndex, slot in ipairs(recipe._ingredients) do
		for optionIndex, option in ipairs(slot) do
			if option._id == ingredientID then
				return true, slotIndex, optionIndex
			end
		end
	end
	return false, nil, nil
end

-- Gets the array of ingredient options for the given slot.
---@param recipe Recipe
---@param slotIndex integer
---@return IngredientOption[]
function Recipe.getIngredientOptions(recipe, slotIndex)
	return recipe._ingredients[slotIndex]
end

-- Gets the ingredient at the given slot and option index, both the ID and amount.
---@param recipe Recipe
---@param slotIndex integer
---@param optionIndex integer
---@return ResourceID # ingredient ID
---@return Amount # amount
function Recipe.getIngredient(recipe, slotIndex, optionIndex)
	local ingredientPair = recipe._ingredients[slotIndex][optionIndex]
	return ingredientPair._id, ingredientPair._amount
end

-- Gets the product of the recipe, both the ID and amount.
---@param recipe Recipe
---@return ProductID # product ID
---@return Amount # amount
function Recipe.getProduct(recipe)
	return recipe._productPair._id, recipe._productPair._amount
end

-- Gets the number of ingredient slots in the recipe.<br>
-- This *must* loop over the array due to MediaWiki's metatables on data loaded with `mw.loadData` messing with `#` operator.
---@param recipe Recipe
---@return integer # number of ingredient slots
function Recipe.getNumIngredients(recipe)
	if not recipe._ingredients then
		error("Recipe.getNumIngredients cannot work with nil array.")
	end
	local count = 0
	for _ in ipairs(recipe._ingredients) do
		count = count + 1
	end
	return count
end

-- Gets the number of options for the given ingredient slot.<br>
-- This *must* loop over the array due to MediaWiki's metatables on data loaded with `mw.loadData` messing with `#` operator.
---@param recipe Recipe
---@param slotIndex integer
---@return integer # number of options in the slot
function Recipe.getNumOptions(recipe, slotIndex)
	if not recipe._ingredients then
		error("Recipe.getNumOptions cannot work with nil array.")
	end
	local count = 0
	for _ in ipairs(recipe._ingredients[slotIndex]) do
		count = count + 1
	end
	return count
end

-- Gets the array of buildings that can make the recipe.
---@param recipe Recipe
---@return BuildingID[] _buildings
function Recipe.getBuildings(recipe)
	return recipe._buildings
end

-- Gets the number of buildings that can make the recipe.<br>
-- This *must* loop over the array due to MediaWiki's metatables on data loaded with `mw.loadData` messing with `#` operator.
---@param recipe Recipe
---@return integer # number of buildings that can make the recipe
function Recipe.getNumBuildings(recipe)
	if not recipe._buildings then
		error("Recipe.getNumBuildings cannot work with nil array.")
	end
	local count = 0
	for _ in ipairs(recipe._buildings) do
		count = count + 1
	end
	return count
end

-- Gets the production time of the recipe.
---@param recipe Recipe
---@return number
function Recipe.getTime(recipe)
	return recipe._time
end

-- Gets the production time of the recipe in a clock format.
---@param recipe Recipe
---@return string # formatted as "M:SS"
function Recipe.getTimeClock(recipe)
	local minutes = math.floor((recipe._time or 0) / 60)
	local seconds = (recipe._time or 0) % 60
	return string.format("%d:%02d", minutes, seconds)
end

-- Adds the given building ID to the recipe's list of buildings that can make it.
---@param recipe Recipe
---@param buildingID BuildingID
---@return BuildingID[] _buildings with the new building added
function Recipe.addBuilding(recipe, buildingID)
	table.insert(recipe._buildings, buildingID)
	return recipe._buildings
end

-- Copies the given recipe.<br>
-- *This is required for any recipes loaded with `mw.loadData` so they can be modified and so the `#` operator works.
---@param recipe Recipe
---@return Recipe copy
function Recipe.copy(recipe)
	---@type Recipe
	local copy = {
		_buildings = {},
    _grade = recipe._grade,
    _time = recipe._time,
    _productPair = {
      _id = recipe._productPair._id,
      _amount = recipe._productPair._amount
    },
    _isService = recipe._isService,
		_extraProductChances = nil, -- optional and needs to be nil unless the original has it
    _ingredients = {}
	}
	for _, buildingID in ipairs(recipe._buildings) do
		table.insert(copy._buildings, buildingID)
	end
	if recipe._extraProductChances then
		copy._extraProductChances = {}
		for i, extra in ipairs(recipe._extraProductChances) do
			copy._extraProductChances[i] = {
				_id = extra._id,
				_amount = extra._amount,
				_chance = extra._chance
			}
		end
	end
	for i, slot in ipairs(recipe._ingredients) do
		copy._ingredients[i] = {}
		for j, option in ipairs(slot) do
			copy._ingredients[i][j] = {
				_id = option._id,
				_amount = option._amount
			}
		end
	end
	return copy
end

--#endregion Public Methods



return Recipe