Module:ParseRecipes

local p = {} local cargo = mw.ext.cargo local recipes for i, node in pairs(mw.loadData('Module:GetDocs')) do if node.NativeClass == "Class'/Script/FactoryGame.FGRecipe'" then recipes = node.Classes break end end

local decodeProducedIn = {['Build_ConstructorMk1_C']='Constructor', ['Build_AssemblerMk1_C']='Assembler', ['Build_ManufacturerMk1_C']='Manufacturer', ['Build_OilRefinery_C']='Refinery', ['Build_FoundryMk1_C']='Foundry', ['Build_SmelterMk1_C']='Smelter', ['BP_BuildGun_C']='Build Gun', ['FGBuildGun']='Build Gun'}

local stringStartsWith = function(String,Start) return string.sub(String,1,string.len(Start))==Start end

local stringSplit = function(inputstr, sep) if sep == nil then sep = "%s" end local t={} for str in string.gmatch(inputstr, "([^"..sep.."]+)") do     table.insert(t, str) end return t end

local getItemDisplayName = function(itemClass) local fields = 'mDisplayName' local args = { where = 'ClassName="' .. itemClass .. '"' } local result = cargo.query('items', fields, args) if #result == 0 then return itemClass else return result[1].mDisplayName end end

-- takes string of ingredients or producs in format "((...),(...),(...))" -- itemRole can be either 'ingredient' or 'product' local parseRecipeItems = function(items, itemRole, recipeClassName, productBuildingName) local args = {} args.recipeClassName = recipeClassName args.itemRole = itemRole -- remove outer parenthesis and split into pairs of strings of format tokens[1] = "(ItemClass=BlahBlah.Desc_IronRod_C\"'" and tokens[2] = "Amount=5)" local tokens = stringSplit(string.sub(items, 2, -2), ',')  -- every odd token contains item name and every even contains the amount  for i, token in pairs(tokens) do    if i % 2 == 1 then      -- after the dot in the end is the class name of the item (also have to remove extra 2 characters in the end)      local itemClassName = stringSplit(token, '.')      -- info about buildings and vehicles in docs.json is rather inconvenient to parse and in some cases it's stright up missing      -- we'll use recipe name as a product name (they conveniently match)      if productBuildingName then        args.itemDisplayName = productBuildingName      else        args.itemDisplayName = getItemDisplayName(string.sub(itemClassName[#itemClassName], 1, -3)) end else -- every even token looks like "Amount=25)"     args.itemAmount = tonumber(string.sub(token, 8, -2))

-- store this item in the table after every pair (name & amount) of tokens mw.getCurrentFrame:expandTemplate{title='Recipe items', args=args} end end end

local function parseRecipes local list = '' for i, recipe in pairs(recipes) do       -- recipe is read-only, let's copy it to temp table args local args = {} for k,v in pairs(recipe) do     args[k] = v    end -- parse in what buildings this recipe can be crafted -- craftedInMultiple looks like "(buildingPath1, buildingPath2, ...)" -- buildingPath looks like "/Game/blahBlahBlah.Build_ManufacturerMk1_C" args.inCraftBench = false args.inEquipmentWorkshop = false local craftedInMultiple = stringSplit(string.sub(args.mProducedIn, 2, -2), ',') args.mProducedIn = nil for i, buildingPath in pairs(craftedInMultiple) do     local t = stringSplit(buildingPath, '.') t = t[#t] -- name of the building is the last part of the path after the dot if t == 'BP_WorkBenchComponent_C' or t == 'FGBuildableAutomatedWorkBench' or t == 'Build_AutomatedWorkBench_C' then args.inCraftBench = true elseif t == 'BP_WorkshopComponent_C' then args.inEquipmentWorkshop = true -- add a building if it's one of the actual buildings else args.mProducedIn = decodeProducedIn[t] end end args.inBuildGun = args.mProducedIn == 'Build Gun' -- if a valid building wasn't found then skip this recipe altogether if not (args.mProducedIn == nil and args.inEquipmentWorkshop == false) then args.isAlternate = stringStartsWith(args.ClassName, 'Recipe_Alternate') or false -- cut off the 'Alternate: ' from the start of the recipe name if stringStartsWith(args.mDisplayName, 'Alternate: ') then args.mDisplayName = string.sub(args.mDisplayName, 12) end -- store ingredients and products parseRecipeItems(args.mIngredients, 'ingredient', args.ClassName) parseRecipeItems(args.mProduct, 'product', args.ClassName, args.inBuildGun and args.mDisplayName or nil) -- store the recipe itself local name = mw.getCurrentFrame:expandTemplate{title='Recipe', args=args} list = list .. name .. '\n\n' end end return list end

-- hardcoded recipes for mined/pumped raw resources because there's currently no proper way in docs.json to identify which items are ores and can be mined instead of crafting local addMiningRecipes = function local inMiner = { 'Iron Ore', 'Copper Ore', 'Limestone', 'Coal', 'Caterium Ore', 'Sulfur', 'Raw Quartz', 'Bauxite', 'Uranium', 'S.A.M. Ore' } local inOilExtractor = { 'Crude Oil' } local inWaterExtractor = { 'Water' } local args = {['mManufactoringDuration'] = 1, ['mManualManufacturingMultiplier'] = 1, ['inCraftBench'] = false, ['inEquipmentWorkshop'] = false, ['isAlternate'] = false} for i, ore in pairs(inMiner) do   args.mDisplayName = ore args.mProducedIn = 'Miner' args.ClassName = 'Recipe_' .. ore:gsub(' ', '') .. '_manual' parseRecipeItems("((ItemClass=BlahBlah." .. ore .. "\"',Amount=1))", 'product', args.ClassName)   mw.getCurrentFrame:expandTemplate{title='Recipe', args=args}  end  for i, water in pairs(inWaterExtractor) do    args.mDisplayName = water    args.mProducedIn = 'Water Extractor'    args.ClassName = 'Recipe_' .. water:gsub(' ', '') .. '_manual'    parseRecipeItems("((ItemClass=BlahBlah." .. water .. "\"',Amount=2000))", 'product', args.ClassName) mw.getCurrentFrame:expandTemplate{title='Recipe', args=args} end for i, oil in pairs(inOilExtractor) do   args.mDisplayName = oil args.mProducedIn = 'Oil Extractor' args.ClassName = 'Recipe_' .. oil:gsub(' ', '') .. '_manual' parseRecipeItems("((ItemClass=BlahBlah." .. oil .. "\"',Amount=2000))", 'product', args.ClassName)   mw.getCurrentFrame:expandTemplate{title='Recipe', args=args}  end end

p.parse = function addMiningRecipes return parseRecipes end

return p