|
|
Line 68: |
Line 68: |
|
| |
|
| local MARKUP_NEWLINE_FORCED = "\n<!-- -->\n" | | local MARKUP_NEWLINE_FORCED = "\n<!-- -->\n" |
|
| |
|
| |
|
| |
|
| |
| local MAX_INGREDIENTS = 3
| |
| local INDEX_OPTION_STACK_SIZE = "stackSize"
| |
| local INDEX_OPTION_GOOD_NAME = "name"
| |
| local INDEX_OPTION_GOOD_ICON = "icon"
| |
|
| |
| local TABLE_HEADER_BOTH = "Product or Service"
| |
| local TABLE_HEADER_PRODUCT = "Product"
| |
| local TABLE_HEADER_SERVICE = "Service"
| |
|
| |
|
| --endregion | | --endregion |
Line 86: |
Line 74: |
|
| |
|
| --region Private member variables | | --region Private member variables |
| | | --none! |
| --endregion | |
| | |
| | |
| | |
| --region Private methods
| |
| | |
| ---
| |
| --- Get the ingredients from the specified recipe and return a list of
| |
| --- the names and icons of the ingredients for that recipe.
| |
| ---
| |
| ---@param recipeID string the ID of the recipe from which to extract ingredients
| |
| ---@return table of ingredients and options (stack size, name, and icon)
| |
| local function extractIngredientsNamesAndIcons(recipeID)
| |
| | |
| local ingredientsList = {}
| |
| -- Need to convert IDs to names and icons and keep stack size.
| |
| for i = 1, MAX_INGREDIENTS do
| |
| for j = 1, WorkshopsRecipesData.getRecipeNumberOfOptionsByID(recipeID, i) do
| |
| | |
| if not ingredientsList[i] then
| |
| ingredientsList[i] = {}
| |
| end
| |
| | |
| local goodID, stackSize = WorkshopsRecipesData.getRecipeOptionByID(recipeID, i, j)
| |
| ingredientsList[i][j] = {}
| |
| ingredientsList[i][j][INDEX_OPTION_STACK_SIZE] = stackSize
| |
| ingredientsList[i][j][INDEX_OPTION_GOOD_NAME] = GoodsData.getGoodNameByID(goodID)
| |
| ingredientsList[i][j][INDEX_OPTION_GOOD_ICON] = GoodsData.getGoodIconByID(goodID)
| |
| end
| |
| end
| |
| | |
| return ingredientsList
| |
| end
| |
| | |
| | |
| | |
| ---
| |
| --- Get the ingredients from the specified recipe and return a list of
| |
| --- the names and icons of the ingredients for that recipe.
| |
| ---
| |
| ---@param recipeID string the ID of the recipe from which to extract ingredients
| |
| ---@return table of ingredients and options (stack size, name, and icon)
| |
| local function extractServiceGoodsNamesAndIcons(recipeID)
| |
| | |
| local ingredientsList = {}
| |
| -- Need to convert IDs to names and icons and keep stack size.
| |
| for i = 1, ServicesRecipesData.getRecipeNumberOfServiceGoodsByID(recipeID) do
| |
| | |
| ingredientsList[i] = {}
| |
| | |
| -- Need to structure this the same way as when there are several
| |
| -- ingredient option groups. But for services there's just one.
| |
| local j = 1
| |
| ingredientsList[i][j] = {}
| |
| local goodID, stackSize = ServicesRecipesData.getRecipeOptionByID(recipeID, i)
| |
| ingredientsList[i][j][INDEX_OPTION_STACK_SIZE] = stackSize
| |
| ingredientsList[i][j][INDEX_OPTION_GOOD_NAME] = GoodsData.getGoodNameByID(goodID)
| |
| ingredientsList[i][j][INDEX_OPTION_GOOD_ICON] = GoodsData.getGoodIconByID(goodID)
| |
| end
| |
| | |
| return ingredientsList
| |
| end
| |
| | |
| | |
| | |
| ---
| |
| --- Main engine that builds the view based on the list of buildings from
| |
| --- workshops, which are slightly different than other building lists.
| |
| ---
| |
| ---@param recipeListFromWorkshops table the list of recipes to render
| |
| ---@param displayOverride string to control if the display isn't default
| |
| ---@param numIngredients number of ingredient columns to display
| |
| ---@param requiredBuilding string the name of the required building
| |
| local function buildRowsForWorkshops(recipeListFromWorkshops, displayOverride, numIngredients, requiredBuilding)
| |
| | |
| -- Build the rows of the middle of the view. If it's nil and there are no
| |
| -- recipes, skip it by getting ipairs on an empty table.
| |
| for _, recipeID in ipairs(recipeListFromWorkshops or {}) do
| |
| | |
| local gradeStars = WorkshopsRecipesData.getRecipeGradeByID(recipeID)
| |
| local productionTime = WorkshopsRecipesData.getRecipeProductionTimeByID(recipeID)
| |
| local productID, productStackSize = WorkshopsRecipesData.getRecipeProductByID(recipeID)
| |
| | |
| local ingredientsList = extractIngredientsNamesAndIcons(recipeID)
| |
| | |
| local productName = GoodsData.getGoodNameByID(productID)
| |
| local productIcon = GoodsData.getGoodIconByID(productID)
| |
| | |
| local buildingListFromWorkshops = WorkshopsData.getWorkshopNamesWithRecipeID(recipeID)
| |
| | |
| for _, buildingName in ipairs(buildingListFromWorkshops) do
| |
| | |
| -- If we're screening for a building (not nil), then we have
| |
| -- to match the current building
| |
| if not requiredBuilding or buildingName == requiredBuilding then
| |
| | |
| local buildingIcon = WorkshopsData.getWorkshopIcon(buildingName)
| |
| | |
| RecipeView.addRowForRecipe(displayOverride, numIngredients,
| |
| buildingName, buildingIcon,
| |
| gradeStars, productionTime, ingredientsList,
| |
| productStackSize, productName, productIcon, requiredBuilding)
| |
| | |
| end
| |
| end
| |
| | |
| end
| |
| end
| |
| | |
| | |
| | |
| ---
| |
| --- Main engine that builds the view based on the list of buildings from
| |
| --- services, which are slightly different than other building lists.
| |
| ---
| |
| ---@param recipeListFromServices table the list of recipes to render
| |
| ---@param displayOverride string to control if the display isn't default
| |
| ---@param numIngredients number of ingredient columns to display
| |
| ---@param requiredBuilding string the name of the required building
| |
| local function buildRowsForServices(recipeListFromServices, displayOverride, numIngredients, requiredBuilding)
| |
| | |
| -- Same. If it's nil and there are no recipes, skip it by getting ipairs
| |
| -- on an empty table.
| |
| for _, recipeID in ipairs(recipeListFromServices or {}) do
| |
| | |
| local gradeStars = ServicesRecipesData.getRecipeGradeByID(recipeID)
| |
| local serviceName = ServicesRecipesData.getRecipeServiceNameByID(recipeID)
| |
| local serviceIcon = ServicesRecipesData.getRecipeServiceIconByID(recipeID)
| |
| | |
| local ingredientsList = extractServiceGoodsNamesAndIcons(recipeID)
| |
| | |
| local buildingListFromInstitutions = InstitutionsData.getInstitutionNamesWithRecipeID(recipeID)
| |
| | |
| local productionTime -- = nil
| |
| local resultStackSize -- = nil
| |
| | |
| for _, buildingName in ipairs(buildingListFromInstitutions) do
| |
| | |
| -- If we're screening for a building (not nil), then we have
| |
| -- to match the current building
| |
| if not requiredBuilding or buildingName == requiredBuilding then
| |
| | |
| local buildingIcon = InstitutionsData.getInstitutionIcon(buildingName)
| |
| | |
| RecipeView.addRowForRecipe(displayOverride, numIngredients,
| |
| buildingName, buildingIcon,
| |
| gradeStars, productionTime, ingredientsList,
| |
| resultStackSize, serviceName, serviceIcon, requiredBuilding)
| |
| end
| |
| end
| |
| | |
| end
| |
| end
| |
| | |
| | |
| | |
| ---
| |
| --- Main engine that builds the view based on the list of buildings from
| |
| --- farms, which are slightly different than other building lists.
| |
| ---
| |
| ---@param buildingListFromFarms table the list of recipes to render
| |
| ---@param displayOverride string to control if the display isn't default
| |
| ---@param numIngredients number of ingredient columns to display
| |
| ---@param productID string of the product to render
| |
| ---@param requiredBuilding string the name of the required building
| |
| local function buildRowsForFarms(buildingListFromFarms, displayOverride, numIngredients, productID, requiredBuilding)
| |
| | |
| for _, buildingName in ipairs(buildingListFromFarms or {}) do
| |
| | |
| -- If we're screening for a building (not nil), then we have to match
| |
| -- the current building
| |
| if not requiredBuilding or buildingName == requiredBuilding then
| |
| | |
| for i = 1, FarmsData.getFarmNumberOfRecipes(buildingName) do
| |
| | |
| local goodID, stackSize = FarmsData.getFarmRecipeProduct(buildingName, i)
| |
| | |
| -- If we're screening for a productID (not nil), then we have
| |
| -- to match the current goodID
| |
| if not productID or goodID == productID then
| |
| | |
| local buildingIcon = FarmsData.getFarmIcon(buildingName)
| |
| local gradeStars, plantingTime, harvestingTime = FarmsData.getFarmRecipeStats(buildingName, i)
| |
| local ingredientsList = {}
| |
| local productName = GoodsData.getGoodNameByID(goodID)
| |
| local productIcon = GoodsData.getGoodIconByID(goodID)
| |
| | |
| RecipeView.addRowForRecipe(displayOverride, numIngredients,
| |
| buildingName, buildingIcon,
| |
| gradeStars, (plantingTime + harvestingTime),
| |
| ingredientsList, stackSize,
| |
| productName, productIcon, requiredBuilding)
| |
| end
| |
| | |
| end
| |
| end
| |
| | |
| end
| |
| end
| |
| | |
| | |
| | |
| ---
| |
| --- Main engine that builds the view based on the list of buildings from
| |
| --- camps, which are slightly different than other building lists.
| |
| ---
| |
| ---@param buildingListFromCamps table the list of recipes to render
| |
| ---@param displayOverride string to control if the display isn't default
| |
| ---@param numIngredients number of ingredient columns to display
| |
| ---@param productID string of the product to render
| |
| ---@param requiredBuilding string the name of the required building
| |
| local function buildRowsForCamps(buildingListFromCamps, displayOverride, numIngredients, productID, requiredBuilding)
| |
| | |
| for _, buildingName in ipairs(buildingListFromCamps or {}) do
| |
| | |
| -- If we're screening for a building (not nil), then we have to match
| |
| -- the current building
| |
| if not requiredBuilding or buildingName == requiredBuilding then
| |
| | |
| for i = 1, CampsData.getCampNumberOfRecipes(buildingName) do
| |
| | |
| local goodID, stackSize = CampsData.getCampRecipeProduct(buildingName, i)
| |
| | |
| -- If we're screening for a productID (not nil), then we have to
| |
| -- match the current goodID
| |
| if not productID or goodID == productID then
| |
| | |
| local buildingIcon = CampsData.getCampIcon(buildingName)
| |
| local gradeStars, gatheringTime = CampsData.getCampRecipeStats(buildingName, i)
| |
| local ingredientsList = {}
| |
| local productName = GoodsData.getGoodNameByID(goodID)
| |
| local productIcon = GoodsData.getGoodIconByID(goodID)
| |
| | |
| RecipeView.addRowForRecipe(displayOverride, numIngredients,
| |
| buildingName, buildingIcon,
| |
| gradeStars, gatheringTime,
| |
| ingredientsList, stackSize,
| |
| productName, productIcon, requiredBuilding)
| |
| end
| |
| | |
| end
| |
| end
| |
| | |
| end
| |
| end
| |
| | |
| | |
| | |
| ---
| |
| --- Main engine that builds the view based on the list of recipes from
| |
| --- collectors, , which are slightly different than other building lists.
| |
| ---
| |
| ---@param buildingListFromCollectors table the list of recipes to render
| |
| ---@param displayOverride string to control if the display isn't default
| |
| ---@param numIngredients number of ingredient columns to display
| |
| ---@param productID string of the product to render
| |
| ---@param requiredBuilding string the name of the required building
| |
| local function buildRowsForCollectors(buildingListFromCollectors, displayOverride, numIngredients, productID, requiredBuilding)
| |
| | |
| for _, buildingName in ipairs(buildingListFromCollectors or {}) do
| |
| | |
| -- If we're screening for a building (not nil), then we have to match
| |
| -- the current building
| |
| if not requiredBuilding or buildingName == requiredBuilding then
| |
| | |
| for i = 1, CollectorsData.getCollectorNumberOfRecipes(buildingName) do
| |
| | |
| local goodID, stackSize = CollectorsData.getCollectorRecipeProduct(buildingName, i)
| |
| | |
| -- If we're screening for a productID (not nil), then we have to
| |
| -- match the current goodID
| |
| if not productID or goodID == productID then
| |
| | |
| local buildingIcon = CollectorsData.getCollectorIcon(buildingName)
| |
| local gradeStars, productionTime = CollectorsData.getCollectorRecipeStats(buildingName, i)
| |
| local ingredientsList = {}
| |
| local productName = GoodsData.getGoodNameByID(goodID)
| |
| local productIcon = GoodsData.getGoodIconByID(goodID)
| |
| | |
| RecipeView.addRowForRecipe(displayOverride, numIngredients,
| |
| buildingName, buildingIcon,
| |
| gradeStars, productionTime,
| |
| ingredientsList, stackSize,
| |
| productName, productIcon, requiredBuilding)
| |
| end
| |
| | |
| end
| |
| end
| |
| | |
| end
| |
| end
| |
| | |
| | |
| | |
| ---
| |
| --- Takes two lists and returns the intersection of the two. Slightly more
| |
| --- efficient if the second list is smaller than the first.
| |
| ---
| |
| ---@param list1 table a flat table (list of values)
| |
| ---@param list2 table another flat table
| |
| ---@return table a similarly flat table consisting only of the values that are in both provided lists
| |
| local function findIntersection(list1, list2)
| |
| | |
| local intersection = {}
| |
| | |
| -- Create a new mapping to efficiently check for existence in list2 by
| |
| -- checking the same ID in the next loop.
| |
| local setList2 = {}
| |
| for _, value in ipairs(list2 or {}) do
| |
| setList2[value] = true
| |
| end
| |
| | |
| -- Check for intersection and populate the result table.
| |
| for _, valueToFind in ipairs(list1 or {}) do
| |
| if setList2[valueToFind] then
| |
| table.insert(intersection, valueToFind)
| |
| end
| |
| end
| |
| | |
| if #intersection == 0 then
| |
| return nil
| |
| end
| |
| | |
| return intersection
| |
| end
| |
| | |
| --endregion
| |
| | |
| | |
| | |
| --region Public methods
| |
| | |
| ---
| |
| --- Compiles the data, organizes it appropriately and in a way that protects
| |
| --- the view from needing the details, and sends it to the view for rendering
| |
| --- before returning it to the template.
| |
| ---
| |
| ---@param ingredientName string the ingredient to base the table on
| |
| ---@param buildingName string the building to base the table on
| |
| ---@param displayOverride string to control the display if not default
| |
| local function renderWithIngredientAndBuilding(ingredientName, buildingName, displayOverride)
| |
| | |
| -- Service and workshops recipes have ingredients. Camps and farms do not.
| |
| local ingredientID = GoodsData.getGoodID(ingredientName)
| |
| if not ingredientID then
| |
| return "No ingredient found with that name: " .. ingredientName .. "."
| |
| end
| |
| local recipeListFromWorkshopsRecipes = WorkshopsRecipesData.getAllRecipeIDsWithIngredientID(ingredientID)
| |
| local recipeListFromWorkshops = WorkshopsData.getAllWorkshopRecipes(buildingName)
| |
| recipeListFromWorkshops = findIntersection(recipeListFromWorkshopsRecipes, recipeListFromWorkshops)
| |
| | |
| local recipeListFromServicesRecipes = ServicesRecipesData.getAllRecipeIDsWithServiceGoodID(ingredientID)
| |
| local recipeListFromServices = InstitutionsData.getAllInstitutionRecipes(buildingName)
| |
| recipeListFromServices = findIntersection(recipeListFromServicesRecipes, recipeListFromServices)
| |
| | |
| -- Find out what the largest number of ingredients in this table is. Need
| |
| -- this before we start building any of the beginning and the middle so
| |
| -- that they are all the same.
| |
| local numIngredients = 1
| |
| if recipeListFromWorkshops then
| |
| for _, recipeID in ipairs(recipeListFromWorkshops) do
| |
| local thisNum = WorkshopsRecipesData.getRecipeNumberOfIngredientsByID(recipeID)
| |
| if numIngredients < thisNum then
| |
| numIngredients = thisNum
| |
| end
| |
| end
| |
| end
| |
| if recipeListFromServices then
| |
| for _, recipeID in ipairs(recipeListFromServices) do
| |
| local thisNum = ServicesRecipesData.getRecipeNumberOfServiceGoodsByID(recipeID)
| |
| if numIngredients < thisNum then
| |
| numIngredients = thisNum
| |
| end
| |
| end
| |
| end
| |
| | |
| -- Count the number of recipes in both the Workshops and Services.
| |
| local numRecipes = 0
| |
| local header -- = nil
| |
| if recipeListFromWorkshops ~= nil then
| |
| numRecipes = numRecipes + #recipeListFromWorkshops
| |
| header = TABLE_HEADER_PRODUCT
| |
| end
| |
| if recipeListFromServices ~= nil then
| |
| if numRecipes > 0 then
| |
| header = TABLE_HEADER_BOTH
| |
| else
| |
| header = TABLE_HEADER_SERVICE
| |
| end
| |
| numRecipes = numRecipes + #recipeListFromServices
| |
| end
| |
| | |
| if numRecipes == 0 then
| |
| return "No recipes found for ingredient " .. ingredientName .. " in the building " .. buildingName .. "."
| |
| end
| |
| | |
| RecipeView.startViewForIngredientAndBuilding(ingredientName, buildingName, displayOverride, numRecipes, numIngredients, header)
| |
| | |
| buildRowsForWorkshops(recipeListFromWorkshops, displayOverride, numIngredients, buildingName)
| |
| | |
| buildRowsForServices(recipeListFromServices, displayOverride, numIngredients, buildingName)
| |
| | |
| return RecipeView.endView(displayOverride)
| |
| end
| |
| | |
| | |
| | |
| ---
| |
| --- Compiles the data, organizes it appropriately and in a way that protects
| |
| --- the view from needing the details, and sends it to the view for rendering
| |
| --- before returning it to the template.
| |
| ---
| |
| ---@param ingredientName string the ingredient to base the table on
| |
| ---@param displayOverride string to control the display if not default
| |
| local function renderWithIngredient(ingredientName, displayOverride)
| |
| | |
| -- Service and workshops recipes have ingredients. Camps and farms do not.
| |
| local ingredientID = GoodsData.getGoodID(ingredientName)
| |
| if not ingredientID then
| |
| return "No ingredient found with that name: " .. ingredientName .. "."
| |
| end
| |
| local recipeListFromWorkshops = WorkshopsRecipesData.getAllRecipeIDsWithIngredientID(ingredientID)
| |
| local recipeListFromServices = ServicesRecipesData.getAllRecipeIDsWithServiceGoodID(ingredientID)
| |
| | |
| -- Find out what the largest number of ingredients in this table is. Need
| |
| -- this before we start building any of the beginning and the middle so
| |
| -- that they are all the same.
| |
| local numIngredients = 1
| |
| if recipeListFromWorkshops then
| |
| for _, recipeID in ipairs(recipeListFromWorkshops) do
| |
| local thisNum = WorkshopsRecipesData.getRecipeNumberOfIngredientsByID(recipeID)
| |
| if numIngredients < thisNum then
| |
| numIngredients = thisNum
| |
| end
| |
| end
| |
| end
| |
| if recipeListFromServices then
| |
| for _, recipeID in ipairs(recipeListFromServices) do
| |
| local thisNum = ServicesRecipesData.getRecipeNumberOfServiceGoodsByID(recipeID)
| |
| if numIngredients < thisNum then
| |
| numIngredients = thisNum
| |
| end
| |
| end
| |
| end
| |
| | |
| -- Count the number of recipes in both the Workshops and Services.
| |
| local numRecipes = 0
| |
| local header -- = nil
| |
| if recipeListFromWorkshops ~= nil then
| |
| numRecipes = numRecipes + #recipeListFromWorkshops
| |
| header = TABLE_HEADER_PRODUCT
| |
| end
| |
| if recipeListFromServices ~= nil then
| |
| if numRecipes > 0 then
| |
| header = TABLE_HEADER_BOTH
| |
| else
| |
| header = TABLE_HEADER_SERVICE
| |
| end
| |
| numRecipes = numRecipes + #recipeListFromServices
| |
| end
| |
| | |
| if numRecipes == 0 then
| |
| return "No recipes found for ingredient: " .. ingredientName .. "."
| |
| end
| |
| | |
| RecipeView.startViewForIngredient(ingredientName, displayOverride, numRecipes, numIngredients, header)
| |
| | |
| buildRowsForWorkshops(recipeListFromWorkshops, displayOverride, numIngredients)
| |
| | |
| buildRowsForServices(recipeListFromServices, displayOverride, numIngredients)
| |
| | |
| return RecipeView.endView(displayOverride)
| |
| end
| |
| | |
| | |
| | |
| ---
| |
| --- Compiles the data, organizes it appropriately and in a way that protects
| |
| --- the view from needing the details, and sends it to the view for rendering
| |
| --- before returning it to the template.
| |
| ---
| |
| ---@param productName string the product to base the table on
| |
| ---@param buildingName string the building to base the table on
| |
| ---@param displayOverride string to control the display if not default
| |
| local function renderWithProductAndBuilding(productName, buildingName, displayOverride)
| |
| | |
| -- Get lists from all buildings
| |
| local productID = GoodsData.getGoodID(productName)
| |
| | |
| local recipeListFromWorkshopsRecipes
| |
| if productID then
| |
| recipeListFromWorkshopsRecipes = WorkshopsRecipesData.getAllRecipeIDsForProductID(productID)
| |
| end
| |
| local recipeListFromWorkshops = WorkshopsData.getAllWorkshopRecipes(buildingName)
| |
| recipeListFromWorkshops = findIntersection(recipeListFromWorkshopsRecipes, recipeListFromWorkshops)
| |
| | |
| -- It could be the name of a service instead of the name of a product.
| |
| local recipeListFromServicesRecipes = ServicesRecipesData.getAllRecipeIDsForServiceName(productName)
| |
| local recipeListFromServices = InstitutionsData.getAllInstitutionRecipes(buildingName)
| |
| recipeListFromServices = findIntersection(recipeListFromServicesRecipes, recipeListFromServices)
| |
| | |
| -- These result in a list of buildings, not recipes.
| |
| local buildingListFromCamps
| |
| local buildingListFromFarms
| |
| local buildingListFromCollectors
| |
| if productID then
| |
| buildingListFromCamps = CampsData.getCampNamesWithRecipeProductID(productID)
| |
| buildingListFromFarms = FarmsData.getFarmNamesWithRecipeProductID(productID)
| |
| buildingListFromCollectors = CollectorsData.getCollectorNamesWithRecipeProductID(productID)
| |
| end
| |
| buildingListFromCamps = findIntersection(buildingListFromCamps, {buildingName} )
| |
| buildingListFromFarms = findIntersection(buildingListFromFarms, {buildingName})
| |
| buildingListFromCollectors = findIntersection(buildingListFromCollectors, {buildingName})
| |
| | |
| -- Find out what the largest number of ingredients in this table is. Need
| |
| -- this before we start building any of the beginning and the middle so
| |
| -- that they are all the same.
| |
| local numIngredients = 1
| |
| if recipeListFromWorkshops then
| |
| for _, recipeID in ipairs(recipeListFromWorkshops) do
| |
| local thisNum = WorkshopsRecipesData.getRecipeNumberOfIngredientsByID(recipeID)
| |
| if numIngredients < thisNum then
| |
| numIngredients = thisNum
| |
| end
| |
| end
| |
| end
| |
| if recipeListFromServices then
| |
| for _, recipeID in ipairs(recipeListFromServices) do
| |
| local thisNum = ServicesRecipesData.getRecipeNumberOfServiceGoodsByID(recipeID)
| |
| if numIngredients < thisNum then
| |
| numIngredients = thisNum
| |
| end
| |
| end
| |
| end
| |
| | |
| -- Count the number of recipes in all the lists
| |
| local numRecipes = 0
| |
| local header -- = nil
| |
| if recipeListFromWorkshops ~= nil then
| |
| numRecipes = numRecipes + #recipeListFromWorkshops
| |
| header = TABLE_HEADER_PRODUCT
| |
| end
| |
| if buildingListFromCamps ~= nil then
| |
| numRecipes = numRecipes + #buildingListFromCamps
| |
| header = TABLE_HEADER_PRODUCT
| |
| end
| |
| if buildingListFromFarms ~= nil then
| |
| numRecipes = numRecipes + #buildingListFromFarms
| |
| header = TABLE_HEADER_PRODUCT
| |
| end
| |
| if buildingListFromCollectors ~= nil then
| |
| numRecipes = numRecipes + #buildingListFromCollectors
| |
| header = TABLE_HEADER_PRODUCT
| |
| end
| |
| if recipeListFromServices ~= nil then
| |
| if numRecipes > 0 then
| |
| header = TABLE_HEADER_BOTH
| |
| else
| |
| header = TABLE_HEADER_SERVICE
| |
| end
| |
| numRecipes = numRecipes + #recipeListFromServices
| |
| end
| |
| | |
| if numRecipes == 0 then
| |
| return "No recipes found for product " .. productName .. " at the building " .. buildingName .. "."
| |
| end
| |
| | |
| RecipeView.startViewForProductAndBuilding(productName, buildingName, displayOverride, numRecipes, numIngredients, header)
| |
| | |
| buildRowsForWorkshops(recipeListFromWorkshops, displayOverride, numIngredients, buildingName)
| |
| | |
| buildRowsForServices(recipeListFromServices, displayOverride, numIngredients, buildingName)
| |
| | |
| buildRowsForFarms(buildingListFromFarms, displayOverride, numIngredients, productID, buildingName)
| |
| | |
| buildRowsForCamps(buildingListFromCamps, displayOverride, numIngredients, productID, buildingName)
| |
| | |
| buildRowsForCollectors(buildingListFromCollectors, displayOverride, numIngredients, productID, buildingName)
| |
| | |
| return RecipeView.endView(displayOverride)
| |
| end
| |
| | |
| | |
| | |
| ---
| |
| --- Compiles the data, organizes it appropriately and in a way that protects
| |
| --- the view from needing the details, and sends it to the view for rendering
| |
| --- before returning it to the template.
| |
| ---
| |
| ---@param productName string the product to base the table on
| |
| ---@param displayOverride string to control the display if not default
| |
| local function renderWithProduct(productName, displayOverride)
| |
| | |
| -- Get lists from all buildings
| |
| local productID = GoodsData.getGoodID(productName)
| |
| | |
| local recipeListFromWorkshops
| |
| if productID then
| |
| recipeListFromWorkshops = WorkshopsRecipesData.getAllRecipeIDsForProductID(productID)
| |
| end
| |
| -- It could be the name of a service instead of the name of a product.
| |
| local recipeListFromServices = ServicesRecipesData.getAllRecipeIDsForServiceName(productName)
| |
| | |
| -- These result in a list of buildings, not recipes.
| |
| local buildingListFromCamps
| |
| local buildingListFromFarms
| |
| local buildingListFromCollectors
| |
| if productID then
| |
| buildingListFromCamps = CampsData.getCampNamesWithRecipeProductID(productID)
| |
| buildingListFromFarms = FarmsData.getFarmNamesWithRecipeProductID(productID)
| |
| buildingListFromCollectors = CollectorsData.getCollectorNamesWithRecipeProductID(productID)
| |
| end
| |
| | |
| | |
| -- Find out what the largest number of ingredients in this table is. Need
| |
| -- this before we start building any of the beginning and the middle so
| |
| -- that they are all the same.
| |
| local numIngredients = 1
| |
| if recipeListFromWorkshops then
| |
| for _, recipeID in ipairs(recipeListFromWorkshops) do
| |
| local thisNum = WorkshopsRecipesData.getRecipeNumberOfIngredientsByID(recipeID)
| |
| if numIngredients < thisNum then
| |
| numIngredients = thisNum
| |
| end
| |
| end
| |
| end
| |
| if recipeListFromServices then
| |
| for _, recipeID in ipairs(recipeListFromServices) do
| |
| local thisNum = ServicesRecipesData.getRecipeNumberOfServiceGoodsByID(recipeID)
| |
| if numIngredients < thisNum then
| |
| numIngredients = thisNum
| |
| end
| |
| end
| |
| end
| |
| | |
| -- Count the number of recipes in all the lists
| |
| local numRecipes = 0
| |
| local header -- = nil
| |
| if recipeListFromWorkshops ~= nil then
| |
| numRecipes = numRecipes + #recipeListFromWorkshops
| |
| header = TABLE_HEADER_PRODUCT
| |
| end
| |
| if buildingListFromCamps ~= nil then
| |
| numRecipes = numRecipes + #buildingListFromCamps
| |
| header = TABLE_HEADER_PRODUCT
| |
| end
| |
| if buildingListFromFarms ~= nil then
| |
| numRecipes = numRecipes + #buildingListFromFarms
| |
| header = TABLE_HEADER_PRODUCT
| |
| end
| |
| if buildingListFromCollectors ~= nil then
| |
| numRecipes = numRecipes + #buildingListFromCollectors
| |
| header = TABLE_HEADER_PRODUCT
| |
| end
| |
| if recipeListFromServices ~= nil then
| |
| if numRecipes > 0 then
| |
| header = TABLE_HEADER_BOTH
| |
| else
| |
| header = TABLE_HEADER_SERVICE
| |
| end
| |
| numRecipes = numRecipes + #recipeListFromServices
| |
| end
| |
| | |
| if numRecipes == 0 then
| |
| return "No recipes found for product: " .. productName .. "."
| |
| end
| |
| | |
| RecipeView.startViewForProduct(productName, displayOverride, numRecipes, numIngredients, header)
| |
| | |
| buildRowsForWorkshops(recipeListFromWorkshops, displayOverride, numIngredients)
| |
| | |
| buildRowsForServices(recipeListFromServices, displayOverride, numIngredients)
| |
| | |
| buildRowsForFarms(buildingListFromFarms, displayOverride, numIngredients, productID)
| |
| | |
| buildRowsForCamps(buildingListFromCamps, displayOverride, numIngredients, productID)
| |
| | |
| buildRowsForCollectors(buildingListFromCollectors, displayOverride, numIngredients, productID)
| |
| | |
| return RecipeView.endView(displayOverride)
| |
| end
| |
| | |
| | |
| | |
| ---
| |
| --- Compiles the data, organizes it appropriately and in a way that protects
| |
| --- the view from needing the details, and sends it to the view for rendering
| |
| --- before returning it to the template.
| |
| ---
| |
| ---@param buildingName string the building to base the table on
| |
| ---@param displayOverride string to control the display if not default
| |
| local function renderWithBuilding(buildingName, displayOverride)
| |
| | |
| local recipeListFromWorkshops = WorkshopsData.getAllWorkshopRecipes(buildingName)
| |
| local recipeListFromServices = InstitutionsData.getAllInstitutionRecipes(buildingName)
| |
| | |
| -- For camps, farms, and collectors where recipes are stored in the
| |
| -- building data file, just check to make sure the building has recipes
| |
| -- from that data. If so, that's all we need to make the building list.
| |
| local buildingListFromCamps = {}
| |
| if CampsData.getCampNumberOfRecipes(buildingName) > 0 then
| |
| buildingListFromCamps = { buildingName }
| |
| end
| |
| local buildingListFromFarms = {}
| |
| if FarmsData.getFarmNumberOfRecipes(buildingName) > 0 then
| |
| buildingListFromFarms = { buildingName }
| |
| end
| |
| local buildingListFromCollectors = {}
| |
| if CollectorsData.getCollectorNumberOfRecipes(buildingName) > 0 then
| |
| buildingListFromCollectors = { buildingName }
| |
| end
| |
| | |
| -- Find out what the largest number of ingredients in this table is. Need
| |
| -- this before we start building any of the beginning and the middle so
| |
| -- that they are all the same.
| |
| local numIngredients = 1
| |
| if recipeListFromWorkshops then
| |
| for _, recipeID in ipairs(recipeListFromWorkshops) do
| |
| local thisNum = WorkshopsRecipesData.getRecipeNumberOfIngredientsByID(recipeID)
| |
| if numIngredients < thisNum then
| |
| numIngredients = thisNum
| |
| end
| |
| end
| |
| end
| |
| if recipeListFromServices then
| |
| for _, recipeID in ipairs(recipeListFromServices) do
| |
| local thisNum = ServicesRecipesData.getRecipeNumberOfServiceGoodsByID(recipeID)
| |
| if numIngredients < thisNum then
| |
| numIngredients = thisNum
| |
| end
| |
| end
| |
| end
| |
| | |
| -- Count the number of recipes in all the lists
| |
| local numRecipes = 0
| |
| local header -- = nil
| |
| if recipeListFromWorkshops ~= nil then
| |
| numRecipes = numRecipes + #recipeListFromWorkshops
| |
| header = TABLE_HEADER_PRODUCT
| |
| end
| |
| if buildingListFromCamps ~= nil then
| |
| numRecipes = numRecipes + CampsData.getCampNumberOfRecipes(buildingName)
| |
| header = TABLE_HEADER_PRODUCT
| |
| end
| |
| if buildingListFromFarms ~= nil then
| |
| numRecipes = numRecipes + FarmsData.getFarmNumberOfRecipes(buildingName)
| |
| header = TABLE_HEADER_PRODUCT
| |
| end
| |
| if buildingListFromCollectors ~= nil then
| |
| numRecipes = numRecipes + CollectorsData.getCollectorNumberOfRecipes(buildingName)
| |
| header = TABLE_HEADER_PRODUCT
| |
| end
| |
| if recipeListFromServices ~= nil then
| |
| if numRecipes > 0 then
| |
| header = TABLE_HEADER_BOTH
| |
| else
| |
| header = TABLE_HEADER_SERVICE
| |
| end
| |
| numRecipes = numRecipes + #recipeListFromServices
| |
| end
| |
| | |
| if numRecipes == 0 then
| |
| return "No recipes found at building: " .. buildingName .. "."
| |
| end
| |
| | |
| RecipeView.startViewForBuilding(buildingName, displayOverride, numRecipes, numIngredients, header)
| |
| | |
| buildRowsForWorkshops(recipeListFromWorkshops, displayOverride, numIngredients, buildingName)
| |
| | |
| buildRowsForServices(recipeListFromServices, displayOverride, numIngredients, buildingName)
| |
| | |
| buildRowsForFarms(buildingListFromFarms, displayOverride, numIngredients, nil, buildingName)
| |
| | |
| buildRowsForCamps(buildingListFromCamps, displayOverride, numIngredients, nil, buildingName)
| |
| | |
| buildRowsForCollectors(buildingListFromCollectors, displayOverride, numIngredients, nil, buildingName)
| |
| | |
| return RecipeView.endView(displayOverride)
| |
| end
| |
| | |
| --endregion | | --endregion |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
|
| |
|
Line 971: |
Line 171: |
| ---Benchmarking: ~0.0003 seconds | | ---Benchmarking: ~0.0003 seconds |
| --- | | --- |
| ---@param DataModel table a require'd data model that implements the recipe query interface, passed in for code reuse | | ---@param DataModel table a required data model that implements the recipe query interface, passed in for code reuse |
| ---@param productID string the ID of the product, or nil if any | | ---@param productID string the ID of the product, or nil if any |
| ---@param buildingID string the ID of the building, or nil if any | | ---@param buildingID string the ID of the building, or nil if any |
Line 1,089: |
Line 289: |
| for _, product in pairs(recipeList) do | | for _, product in pairs(recipeList) do |
| for _, grade in pairs(product) do | | for _, grade in pairs(product) do |
| for _, recipe in pairs(grade) do | | for _, _ in pairs(grade) do |
| count = count + 1 | | count = count + 1 |
| end | | end |
Line 1,251: |
Line 451: |
| end | | end |
|
| |
|
| | ---renderListView |
| | ---Takes the table of recipes gathered from the data models and returns a markup-unordered-list of the recipes. Buildings are shown when the author requested the product, otherwise the products are shown. |
| | --- |
| | ---@param frame table the Mediawiki context for the template |
| | ---@param recipeList table 3-factor table of Recipe objects in [product][grade][amount] |
| | ---@param requiredProduct string name of the product, or nil if any |
| | ---@param _ string name of the building, or nil if any (unused) |
| | ---@param _ string name of the ingredient, or nil if any (unused) |
| local function renderListView(frame, recipeList, requiredProduct, _, _) | | local function renderListView(frame, recipeList, requiredProduct, _, _) |
|
| |
|
Line 1,452: |
Line 660: |
| --region Public methods | | --region Public methods |
|
| |
|
| | ---main |
| | ---Called from Template:Recipe. Returns markup text for display by using external view templates. |
| | --- |
| | ---@param frame table the Mediawiki calling context for the template |
| | ---@return string wiki markup |
| function RecipeController.main(frame) | | function RecipeController.main(frame) |
|
| |
|
Documentation for this module may be created at Module:RecipeController2/doc
--- @module RecipeController
local RecipeController = {}
--region Dependencies
local DataModelsArray = {
require("Module:CampsData2"),
require("Module:FarmsData2"),
require("Module:FishingData"),
require("Module:GatheringData"),
require("Module:InstitutionsData2"),
require("Module:RainCollectorsData"),
require("Module:WorkshopsData2"),
}
local GoodsData = require("Module:GoodsData")
local BaseDataModel = require("Module:BaseDataModel")
local VIEW_TEMPLATE_START = "Recipe/view"
local VIEW_TEMPLATE_ROW = "Recipe/view/row"
local VIEW_TEMPLATE_END = "Recipe/view/end"
local VIEW_BUILDING_LINK = "Building_link/view"
local VIEW_RESOURCE_LINK = "Resource_link/view"
--endregion
--region Private constants
local ARG_DISPLAY_OVERRIDE_LIST = "list"
local INDEX_RECIPE_BUILDINGS_ARRAY = "buildingsArray"
local INDEX_RECIPE_GRADE = "grade"
local INDEX_RECIPE_TIME = "time"
local INDEX_RECIPE_PRODUCT_NAME = "productName"
local INDEX_RECIPE_PRODUCT_AMOUNT = "productAmount"
local INDEX_RECIPE_INGREDIENTS = "ingredientsTable"
local INDEX_OPTION_ID = "name" -- this is for backwards compatibility, it's actually an ID
local INDEX_OPTION_AMOUNT = "amount"
local VIEW_TABLE_BUILDING_SINGLE_ICON_SIZE = "huge"
local VIEW_TABLE_BUILDING_MULTIPLE_ICON_SIZE = "large"
local VIEW_TABLE_INGREDIENT_ICON_SIZE = "medium"
local VIEW_TABLE_PRODUCT_ICON_SIZE = "huge"
local VIEW_CLASS_TABLE_INGREDIENTS_SINGLE_ICON = 'class=ats-single-ingredient-icon'
local VIEW_CLASS_TABLE_INGREDIENTS_SWAPPABLE_ICON = 'class=ats-swappable-ingredient-icon'
local VIEW_GRADES = {
[0] = '0Star',
[1] = '1Star',
[2] = '2Star',
[3] = '3Star',
}
--- Transform the grade only when using the value as an index, to help it sort better whenever possible.
local STORE_GRADES = {
[0] = 1,
[1] = 2,
[2] = 3,
[3] = 4,
}
local MARKUP_NEWLINE_FORCED = "\n<!-- -->\n"
--endregion
--region Private member variables
--none!
--endregion
--region Private methods
---getFlatRecipeValues
---Extracts a handful of values from the provided recipe pair.
---
---@param recipeData table pair of recipe data retrieved from a data model
---@return string, number, number, string, number building ID, efficiency grade, production time, product ID, and product amount (respectively)
local function getFlatRecipeValues(recipeData)
local buildingID = BaseDataModel.getRecipeBuildingID(recipeData)
local grade = BaseDataModel.getRecipeGrade(recipeData)
local time = BaseDataModel.getRecipeTime(recipeData)
local productID = BaseDataModel.getRecipeProductID(recipeData)
local productAmount = BaseDataModel.getRecipeProductAmount(recipeData)
return buildingID, grade, time, productID, productAmount
end
---buildingIngredientsTable
---Extracts values from the provided recipe pair and builds an ingredients table for use in a Recipe object.
---
---@param recipeData table pair of recipe data retrieved from a data model
---@return table nested ingredients, options (ID and amount)
local function buildingIngredientsTable(recipeData)
local ingredientsTable = {}
for i = 1, BaseDataModel.getRecipeNumIngredientSlots(recipeData) do
if not ingredientsTable[i] then
ingredientsTable[i] = {}
end
for j = 1, BaseDataModel.getRecipeIngredientNumOptions(recipeData, i) do
ingredientsTable[i][j] = {
[INDEX_OPTION_ID] = BaseDataModel.getRecipeIngredientOptionIDAt(recipeData, i, j),
[INDEX_OPTION_AMOUNT] = BaseDataModel.getRecipeIngredientOptionAmountAt(recipeData, i, j),
}
end
end
return ingredientsTable
end
---resolveBuildingName
---Quickly runs through the data models and attempts with each to resolve the provided ID into a name.
---
---@param buildingID string the ID
---@return string the name, or nil if not found
local function resolveBuildingName(buildingID)
for _, dataModel in ipairs(DataModelsArray) do
local buildingName = dataModel:getName(buildingID)
if buildingName then
return buildingName
end
end
return nil
end
---resolveBuildingID
---Quickly runs through the data models and attempts with each to resolve the provided name into an ID.
---
---@param buildingName string the display name
---@return string the building's ID
local function resolveBuildingID(buildingName)
for _, dataModel in ipairs(DataModelsArray) do
local id = dataModel:getID(buildingName)
if id then
return id
end
end
return nil
end
---resolveBuildingIcon
---Quickly runs through the data models and attempts with each to resolve the provided name into an icon.
---
---@param buildingName string the display name
---@return string the filename for its icon
local function resolveBuildingIcon(buildingName)
for _, dataModel in ipairs(DataModelsArray) do
local id = dataModel:getID(buildingName)
if id then
return dataModel:getIcon(id)
end
end
return nil
end
---getRawRecipes
---Queries the data models with the supplied parameters to construct an array of Recipe objects storing the recipes found in the data model.
---
---Benchmarking: ~0.0003 seconds
---
---@param DataModel table a required data model that implements the recipe query interface, passed in for code reuse
---@param productID string the ID of the product, or nil if any
---@param buildingID string the ID of the building, or nil if any
---@param ingredientID string the ID of an ingredient, or nil if any
---@return table array of pairs of buildingID and recipe data
local function getRawRecipes(DataModel, productID, buildingID, ingredientID)
local rawRecipeList = {}
if productID and buildingID then
rawRecipeList = DataModel:getIDsAndRecipesWhereProductIDAndBuildingID(productID, buildingID)
elseif productID then
rawRecipeList = DataModel:getIDsAndRecipesWhereProductID(productID)
elseif ingredientID and buildingID then
rawRecipeList = DataModel:getIDsAndRecipesWhereIngredientIDAndBuildingID(ingredientID, buildingID)
elseif ingredientID then
rawRecipeList = DataModel:getIDsAndRecipesWhereIngredientID(ingredientID)
elseif buildingID then
rawRecipeList = DataModel:getIDsAndRecipesWhereBuildingID(buildingID)
else
error("You must specify a product, building, or ingredient. Please see the template documentation for how to use the parameters")
end
return rawRecipeList
end
---compileRecipeLists
---Adds the second list to the first, but restructures into Recipe objects along the way.
---
---@param recipeObjectTable table 3-factor array of Recipe objects, by product, grade, amount
---@param rawRecipeTable table list of recipe pairs, as gotten from a data model
---@return table the same recipeObjectTable, but with new and updated entries
local function compileRecipeLists(recipeObjectTable, rawRecipeTable)
for _, pair in ipairs(rawRecipeTable) do
local buildingID, grade, time, productID, productAmount = getFlatRecipeValues(pair)
local ingredientsTable = buildingIngredientsTable(pair)
local buildingName = resolveBuildingName(buildingID)
-- Services identify their need by name, but goods to not. If it's a service, this is a simple renaming.
local productName = productID
if not BaseDataModel.isRecipeProvidingService(pair) then
productName = GoodsData.getName(productID)
end
-- Now that we have everything extracted from rawRecipeTable for this pair, load it into recipeObjectTable, whether as a new Recipe object or adding a building to an existing Recipe object if one already exists. Recipes are uniquely identified by the 3-way combination of product, grade, and product amount.
if not recipeObjectTable[productName] then
recipeObjectTable[productName] = {}
end
-- Transform the grade values to store them in a natural order.
if not recipeObjectTable[productName][STORE_GRADES[grade]] then
recipeObjectTable[productName][STORE_GRADES[grade]] = {}
end
if not recipeObjectTable[productName][STORE_GRADES[grade]][productAmount] then
-- Create a new Recipe object at this place in the table.
recipeObjectTable[productName][STORE_GRADES[grade]][productAmount] = RecipeController.Recipe.new( { buildingName }, grade, time, productID, productAmount, ingredientsTable)
else
-- Add the building to the existing Recipe object at this place in the table.
recipeObjectTable[productName][STORE_GRADES[grade]][productAmount]:addBuilding(buildingName)
end
end
return recipeObjectTable
end
---getRecipesFromAllDataModels
---Goes through all data models and compiles the results into a single 3-factor table of Recipe objects, [product][grade][amount]. This table will be sparse, and note sometimes the grade is harder to spot in the console if it starts at 1 and is followed by 2 (because the console interprets it as an un-keyed array.
---
---For example, finding the recipe for Biscuits in the Field Kitchen: recipeObjectArray["Biscuits"][0][10]
---
---@param requiredProduct string the name of the product, or nil if any
---@param requiredBuilding string the name of the building, or nil if any
---@param requiredIngredient string the name of the ingredient, or nil if any
---@return table a 3-factor compiled table of Recipe objects, [product][grade][amount]
local function getRecipesFromAllDataModels(requiredProduct, requiredBuilding, requiredIngredient)
--Resolve names to IDs, start them all as nil as wildcards.
local productID
local buildingID
local ingredientID
if requiredProduct then
productID = GoodsData.getGoodID(requiredProduct)
-- If it's not a good, then it's a service, which has ID == name
if not productID then
productID = requiredProduct
end
end
if requiredBuilding then
buildingID = resolveBuildingID(requiredBuilding)
end
if requiredIngredient then
ingredientID = GoodsData.getGoodID(requiredIngredient)
end
recipeObjectArray = {}
for _, dataModel in ipairs(DataModelsArray) do
local newRecipeList = getRawRecipes(dataModel, productID, buildingID, ingredientID)
recipeObjectArray = compileRecipeLists(recipeObjectArray, newRecipeList)
end
return recipeObjectArray
end
---countMaxIngredients
---Scans through the provided table of Recipe objects to count them.
---
---@param recipeList table 3-factor table of Recipe objects
---@return number of recipe objects
local function countRecipes(recipeList)
local count = 0
for _, product in pairs(recipeList) do
for _, grade in pairs(product) do
for _, _ in pairs(grade) do
count = count + 1
end
end
end
return count
end
---countMaxIngredients
---Scans through the provided table of Recipe objects to find the recipe with the maximum number of ingredients slots (not options, whole slots of options).
---
---@param recipeList table 3-factor table of Recipe objects
---@return number of ingredients slots required to represent them all
local function countMaxIngredients(recipeList)
local max = 0
for _, product in pairs(recipeList) do
for _, grade in pairs(product) do
for _, recipe in pairs(grade) do
local num = recipe:getNumIngredients()
if max < num then
max = num
end
end
end
end
return max
end
---calculateCaption
---Simple cascading of rewriting the author requirements into the caption and how many were returned.
---
---@param requiredProduct string name of product, or nil if any
---@param requiredBuilding string name of building, or nil if any
---@param requiredIngredient string name of ingredient, or nil if any
---@param numRecipes number of recipes
---@return string the caption
local function calculateCaption(requiredProduct, requiredBuilding, requiredIngredient, numRecipes)
local caption = numRecipes .. " recipes"
if requiredProduct then
caption = caption .. " for " .. requiredProduct
end
if requiredIngredient then
caption = caption .. " using " .. requiredIngredient
end
if requiredBuilding then
return caption .. " in the " .. requiredBuilding .. "."
else
return caption .. "."
end
end
---buildMiddle
---Calls the view to render table rows for each Recipe object and links to buildings and resources within the table rows.
---
---Benchmarking: ~0.005 seconds
---
---@param frame table MediaWiki template context
---@param recipeList table 3-factor list of Recipe objects, by [product][grade][amount]
---@param maxIngredients number of ingredients the largest Recipe has
---@param requiredProduct string name of product, or nil if any
---@param requiredBuilding string name of building, or nil if any
---@param _ string name of ingredient, or nil if any
---@return string a long string of wiki markup
local function buildMiddle(frame, recipeList, maxIngredients, requiredProduct, requiredBuilding, _)
local ret = ""
for _, recipeProductSubtable in pairs(recipeList) do
for _, recipeGradeSubtable in pairs(recipeProductSubtable) do
for _, recipe in pairs(recipeGradeSubtable) do
local rowArgs = {}
rowArgs["maxingredients"] = maxIngredients
local numBuildings = #recipe[INDEX_RECIPE_BUILDINGS_ARRAY]
rowArgs["building"] = MARKUP_NEWLINE_FORCED .. "{|" --all table markup has to start on its own line; this html comment accomplishes this
for _, buildingName in ipairs(recipe[INDEX_RECIPE_BUILDINGS_ARRAY]) do
local buildingLinkArgs = {}
buildingLinkArgs["name"] = buildingName
buildingLinkArgs["iconfilename"] = resolveBuildingIcon(buildingName)
buildingLinkArgs["iconsize"] = (numBuildings < 2) and VIEW_TABLE_BUILDING_SINGLE_ICON_SIZE or VIEW_TABLE_BUILDING_MULTIPLE_ICON_SIZE
-- Redundant to label the building if the author required it--the image alt-text and link still work with name.
if requiredBuilding then
buildingLinkArgs["display"] = "notext"
end
rowArgs["building"] = rowArgs["building"] .. "\n|-\n| " .. frame:expandTemplate{
title = VIEW_BUILDING_LINK,
args = buildingLinkArgs,
}
end
rowArgs["building"] = rowArgs["building"] .. "\n|}"
rowArgs["grade"] = frame:expandTemplate{
title = VIEW_GRADES[recipe[INDEX_RECIPE_GRADE]],
args = {},
}
local minutes = math.floor(recipe[INDEX_RECIPE_TIME] / 60)
local seconds = recipe[INDEX_RECIPE_TIME] % 60
rowArgs["grade"] = rowArgs["grade"] .. "<br>" .. string.format("%d:%02d", minutes, seconds)
-- rowArgs[ingredientI]
for i, ingredientSlot in ipairs(recipe[INDEX_RECIPE_INGREDIENTS]) do
local innerTable = MARKUP_NEWLINE_FORCED .. "{|" --all table markup has to start on its own line; this html comment accomplishes this
local numOptions = #ingredientSlot
if numOptions > 1 then
innerTable = innerTable .. VIEW_CLASS_TABLE_INGREDIENTS_SWAPPABLE_ICON
else
innerTable = innerTable .. VIEW_CLASS_TABLE_INGREDIENTS_SINGLE_ICON
end
for _, option in ipairs(ingredientSlot) do
local rlArgs = {}
rlArgs["name"] = GoodsData.getName(option[INDEX_OPTION_ID])
rlArgs["iconfilename"] = GoodsData.getIcon(option[INDEX_OPTION_ID])
rlArgs["iconsize"] = VIEW_TABLE_INGREDIENT_ICON_SIZE
innerTable = innerTable .. "\n|-\n| " .. option[INDEX_OPTION_AMOUNT] .. " |"
.. "| " .. frame:expandTemplate{
title = VIEW_RESOURCE_LINK,
args = rlArgs,
}
end
innerTable = innerTable .. "\n|}\n"
rowArgs["ingredient" .. i] = innerTable
end
local resourceLinkArgs = {}
resourceLinkArgs["name"] = GoodsData.getName(recipe[INDEX_RECIPE_PRODUCT_NAME])
resourceLinkArgs["iconfilename"] = GoodsData.getIcon(recipe[INDEX_RECIPE_PRODUCT_NAME])
resourceLinkArgs["iconsize"] = VIEW_TABLE_PRODUCT_ICON_SIZE
-- Redundant to label the resource if the author required it--the image alt-text and link still work with name.
if requiredProduct then
resourceLinkArgs["display"] = "notext"
end
rowArgs["product"] = recipe[INDEX_RECIPE_PRODUCT_AMOUNT] .. " "
.. frame:expandTemplate{
title = VIEW_RESOURCE_LINK,
args = resourceLinkArgs,
}
ret = ret .. frame:expandTemplate{
title = VIEW_TEMPLATE_ROW,
args = rowArgs,
}
ret = ret .. "\n"
end
end
end
return ret
end
---renderListView
---Takes the table of recipes gathered from the data models and returns a markup-unordered-list of the recipes. Buildings are shown when the author requested the product, otherwise the products are shown.
---
---@param frame table the Mediawiki context for the template
---@param recipeList table 3-factor table of Recipe objects in [product][grade][amount]
---@param requiredProduct string name of the product, or nil if any
---@param _ string name of the building, or nil if any (unused)
---@param _ string name of the ingredient, or nil if any (unused)
local function renderListView(frame, recipeList, requiredProduct, _, _)
local ret = ""
for _, recipeProductSubtable in pairs(recipeList) do
for _, recipeGradeSubtable in pairs(recipeProductSubtable) do
for _, recipe in pairs(recipeGradeSubtable) do
for buildingCount, buildingName in ipairs(recipe[INDEX_RECIPE_BUILDINGS_ARRAY]) do
local rowText = '\n*<span class="nowrap">'
if requiredProduct then
-- When queried by product, show the building name
rowText = rowText .. frame:expandTemplate{
title = VIEW_BUILDING_LINK,
args = {
["name"] = buildingName,
["iconsize"] = "none",
},
}
rowText = rowText .. " ("
rowText = rowText .. frame:expandTemplate{
title = VIEW_GRADES[recipe[INDEX_RECIPE_GRADE]],
args = {},
}
rowText = rowText .. ")"
else
-- If past the first building and we're not showing building names, then this will create duplicate entries.
if buildingCount > 1 then
break
end
-- When queried by building or ingredient, show the product name
rowText = rowText .. frame:expandTemplate{
title = VIEW_RESOURCE_LINK,
args = {
["name"] = GoodsData.getName(recipe[INDEX_RECIPE_PRODUCT_NAME]),
["iconfilename"] = GoodsData.getIcon(recipe[INDEX_RECIPE_PRODUCT_NAME]),
["iconsize"] = "small",
},
}
rowText = rowText .. " ("
rowText = rowText .. frame:expandTemplate{
title = VIEW_GRADES[recipe[INDEX_RECIPE_GRADE]],
args = {},
}
rowText = rowText .. ")</span>"
end
ret = ret .. rowText
end
end
end
end
return ret
end
--endregion
--region Public classes
local Recipe = {}
-- This class available outside for read-only access
RecipeController.Recipe = Recipe
RecipeController.Recipe.OPTION_ID = INDEX_OPTION_ID
RecipeController.Recipe.OPTION_AMOUNT = INDEX_OPTION_AMOUNT
---new
---constructs a new Recipe instance from the provided data.
---
---the ingredientsTable must follow this format:
---ingredientsTable = {
--- --ingredient slots in recipe, between 1 and 3
--- [1] = {
--- --options for that slot, between 1 and 6
--- [1] = {
--- --each option's ID and amount
--- [Recipe.OPTION_ID] = string,
--- [Recipe.OPTION_STACK_SIZE] = number,
--- },
--- [2] = ...
--- },
--- [2] = ...
---}
---
---@param buildingArray table array of the names of buildings that make this recipe
---@param grade number of stars, between 0 and 3
---@param time number of seconds to produce
---@param productName string name of the good produced
---@param productStackSize number of goods produced each cycle
---@param ingredientsTable table array of 1-3 ingredient slots, each with 1-6 options, each with name and amount (see doc above)
function Recipe.new(buildingArray, grade, time, productName, productStackSize, ingredientsTable)
local instance = {}
setmetatable(instance, { __index = Recipe} ) -- allow this instance to use Recipe class methods
if not buildingArray or type(buildingArray) ~= "table" or #buildingArray < 1 then
error("Cannot construct new Recipe with an empty building list")
end
instance[INDEX_RECIPE_BUILDINGS_ARRAY] = buildingArray
if not grade or grade == "" then
error("Cannot construct new Recipe with an empty grade.")
elseif type(grade) ~= "number" or grade > 4 or grade < 0 then
error ("Cannot construct new Recipe with an invalid grade value")
end
instance[INDEX_RECIPE_GRADE] = grade
if not time or time == "" then
error ("Cannot construct new Recipe with an empty production time")
elseif type(time) ~= "number" or time < 0 then
error ("Cannot construct new Recipe with an invalid production time value")
end
instance[INDEX_RECIPE_TIME] = time
if not productName or productionName == "" then
error ("Cannot construct new Recipe with an empty product name")
end
instance[INDEX_RECIPE_PRODUCT_NAME] = productName
if not productStackSize or productStackSize == "" then
error("Cannot construct new Recipe with an empty product amount")
elseif type(productStackSize) ~= "number" or productStackSize < 1 then
error("Cannot construct new Recipe with an invalid product amount value")
end
instance[INDEX_RECIPE_PRODUCT_AMOUNT] = productStackSize
if not ingredientsTable or type(ingredientsTable) ~= "table" then
error("Cannot construct new Recipe with an invalid ingredients table")
end
if #ingredientsTable > 3 then
error("Cannot construct new Recipe with an ingredients table larger than 3 subtables")
end
for i, optionsArray in ipairs(ingredientsTable) do
if not optionsArray or type(optionsArray) ~= "table" or #optionsArray < 1 then
error("Cannot construct new Recipe with an empty options list (at index " .. i .. ")")
end
if #optionsArray > 6 then
error("Cannot construct new Recipe with an options array larger than 6 subtables (at index " .. i .. ")")
end
for j, option in ipairs(optionsArray) do
if not option or type(option) ~= "table" then
error("Cannot construct new Recipe with an empty option (at index " .. i .. ", " .. j .. ")")
end
if not option[INDEX_OPTION_ID] or option[INDEX_OPTION_ID] == "" then
error("Cannot construct a new Recipe with an empty option ID (at index " .. i .. ", " .. j .. ")")
end
if not option[INDEX_OPTION_AMOUNT] or type(option[INDEX_OPTION_AMOUNT]) ~= "number" then
error("Cannot construct a new Recipe with an empty option amount (at index " .. i .. ", " .. j .. ")")
end
if option[INDEX_OPTION_AMOUNT] < 1 then
error("Cannot construct a new Recipe with an invalid option amount (at index" .. i .. ", " .. j .. ")")
end
end
end
instance[INDEX_RECIPE_INGREDIENTS] = ingredientsTable
return instance
end
---addBuilding
---Adds the provided building to this Recipe object's list of buildings where the recipe is made.
---
---@param buildingName string name
function Recipe:addBuilding(buildingName)
if not self[INDEX_RECIPE_BUILDINGS_ARRAY] then
self[INDEX_RECIPE_BUILDINGS_ARRAY] = { buildingName }
else
-- Skip duplicates. It shouldn't happen in 99% of cases, but just to be sure.
for _, existingBuilding in ipairs(self[INDEX_RECIPE_BUILDINGS_ARRAY]) do
if existingBuilding == buildingName then
return
end
end
table.insert(self[INDEX_RECIPE_BUILDINGS_ARRAY], buildingName)
end
end
---getNumIngredients
---The number of ingredient slots (0-3) in the Recipe object.
---
---@return number of ingredients slots
function Recipe:getNumIngredients()
if not self[INDEX_RECIPE_INGREDIENTS] then
return 0
end
return #self[INDEX_RECIPE_INGREDIENTS]
end
--endregion
--region Public methods
---main
---Called from Template:Recipe. Returns markup text for display by using external view templates.
---
---@param frame table the Mediawiki calling context for the template
---@return string wiki markup
function RecipeController.main(frame)
local requiredProduct = frame.args.product
local requiredBuilding = frame.args.building
local requiredIngredient = frame.args.ingredient
local displayOverride = frame.args.display
--Unset blanks back to nil
if requiredProduct == "" then
requiredProduct = nil
end
if requiredBuilding == "" then
requiredBuilding = nil
end
if requiredIngredient == "" then
requiredIngredient = nil
end
-- recipeList is a 3-factor array of Recipe objects, by [product][grade][stackSize]
local recipeList = getRecipesFromAllDataModels(requiredProduct, requiredBuilding, requiredIngredient)
if displayOverride == ARG_DISPLAY_OVERRIDE_LIST then
return renderListView(frame, recipeList, requiredProduct, requiredBuilding, requiredIngredient)
end
local numRecipes = countRecipes(recipeList)
local maxIngredients = countMaxIngredients(recipeList)
local caption = calculateCaption(requiredProduct, requiredBuilding, requiredIngredient, numRecipes)
local retStart = frame:expandTemplate{
title = VIEW_TEMPLATE_START,
args = {
["maxingredients"] = maxIngredients,
["caption"] = caption,
}
}
local retMiddle = buildMiddle(frame, recipeList, maxIngredients, requiredProduct, requiredBuilding, requiredIngredient)
local retEnd = frame:expandTemplate{
title = VIEW_TEMPLATE_END,
args = {},
}
return retStart .. retMiddle .. retEnd
end
--endregion
return RecipeController