Hi,
I'm currently trying to implement some kind of recycler which takes items and cracks them into their components. Then cracks these again until whitelisted or "raw" items (wood, ore) without a recipe are found.
Now that 0.11.16 is finally here ^^ I tried out LuaRecipe.ingredients.
As described in the wiki It gives me a nice list of the entity's ingredients, much to my delight.
What it however does not show is a, let's call it "gives amount of", attribute. For example this is necessary with the copper wire: 1x copper plate gives 2x copper wire
The ingredients tells me for one copper wire it's composed of 1 copper plate, which is not what the game recipe is, as you get two copper wire for one copper plate.
So it should be 0,5 copper plate as an ingredient for the copper wire or an additional "gives amount of" attribute like there is for PrototypeRecipe: result_count
Edit: okay, found this one: LuaRecipe.products[n].amount but why is this structure so different than in PrototypeRecipe?
Then I totally lost it and am questioning now what this Lua/* Prototype/* is all about.
I have questions that hopefully someone can answer to get the model I'm builing in my head, how this is supposed to work, raised.
1) why is there a Lua/Recipe and a Prototype/Recipe?
1a) both seem to try to accomplish the same, give similar information (ingredients) but they are still so different (result_count, energy)
1b) one is not castable into the other, they are AFAICS not connected in any way
2) why are there prototypes at all?
2a) like 3c above - what the either bring above the other, why is there a Lua/* and a Prototype/* ? I can't see what this is needed for
3) strings to objects
3a) often the exposed item information is a string, e.g. the item type is e.g. "iron-plate". Sometimes it's an object, see the event handlers. I assume this has been done to save memory and just say "this chest has 400x iron-plate" than keeping 400 entity objects.
3b) Probably fair enough but how do I get an Entity froma string, e.g. to get it's recipe?
3c) Currently I'm using "local aItemToCrackRecipe = game.player.force.recipes[cargItemToCrackName]" but this does not seem right to me and gives me (only) LuaRecipe, where no "result_count" is given.
4) where is the crafting time stored?
4a) neither LuaRecipe nor ProtoypeRecipe indicate how much time crafting from a recipe takes. I would have expected an attribute (or hackishly a time ingredient) here for this?
Confused.
Recipe woes
-
- Long Handed Inserter
- Posts: 66
- Joined: Sat May 10, 2014 8:48 am
- Contact:
Re: Recipe woes
I think all the data you want is in data.raw.recipe. It might be easier to walk that table after it is built to construct your item cracking list.
[edit] Ignore me. It looks like the data you want is in all three places.
[edit] Ignore me. It looks like the data you want is in all three places.
Re: Recipe woes
I already have the "cracking" working:Flux Faraday wrote:I think all the data you want is in data.raw.recipe. It might be easier to walk that table after it is built to construct your item cracking list.
[edit] Ignore me. It looks like the data you want is in all three places.
Code: Select all
--[[!
Contains the central logic for the homebrew modified "Barter" mod:
- all event handlers
- iteration of export chests
- cracking exported items into their ingredients with "don't crack"-whitelists and individual trade bonus
- storing the cracked items into the available import chests
- fluids are discarded when cracked so probably whitelist the item type it's an ingredient of
Attention: needs at least Factorio version 0.11.16
@author dee-, 15.02.2015
@change 15.02.2015, dee-, rewrote the mod to be compatible with 0.11, added tons of comments, renamed variables, indenting, etc pp., several other code fixes
@change 20.02.2015, dee-, updated for the new LuaRecipe functionality added in 0.11.16 to get the entity's ingredients, dropped previous logic and data of the cost_table.
This probably makes this mod now compatible to ALL other mods and allows cracking of mod items
@change 20.02.2015, dee-, trade bonus and whitelist added, bonus defaults to 1.01 (1% gain)
]]--
----- helper functions -----
--! flag to indicate if we are developing (true) or productive (false)
local bIsDebug = true
--[[!
Prints a debug message to the game window.
@param cMessage the message to show
]]--
function debugLog(cMessage)
if bIsDebug then
game.player.print(math.floor(game.tick / 60) .. " : " .. cMessage)
end
end
--[[!
Prints a localized string to the game window.
@param cStringkey key of the string
]]--
function localizedPrint(cStringkey)
game.player.print({cStringkey})
end
----- imports -----
-- standard types, etc.
require "defines"
----- general attributes -----
--! how many ticks are between each attempt to barter
local barterPeriod = 12500
if bIsDebug then
barterPeriod = 1000
end
----- hook events -----
-- initialization
game.oninit(function() onInit() end)
game.onload(function() onLoad() end)
-- every tick
game.onevent(defines.events.ontick, function(event) onTick(event) end)
-- when an entity was placed on the map
game.onevent(defines.events.onbuiltentity, function(event) entityWasPlaced(event) end)
game.onevent(defines.events.onrobotbuiltentity, function(event) entityWasPlaced(event) end)
-- when an entity was removed from the map
game.onevent(defines.events.onentitydied, function(event) entityWasRemoved(event) end)
game.onevent(defines.events.onpreplayermineditem, function(event) entityWasRemoved(event) end)
game.onevent(defines.events.onrobotpremined, function(event) entityWasRemoved(event) end)
----- crack down recipes -----
--! multiplicator for the trade bonus, 1: no bonus, <1: negative bonus, >1: positive bonus. Default is 1% gain (= 1.01)
--! remember the final amount is rounded down, so anything lower 1 will cost for sure at least one item as 1 * 0.99 = 0.99, rounded down to 0
local nDefaultTradeBonus = 1.01
--! special multiplicators, given in e.g. '["explosives"] = 2' to double the returned amount of explosives rather than using the default multiplier
local aSpecialTradeBonus = {}
--! do not crack these items. If you want to add/remove items, add/remove the item name here, the number can be anything
local aWhitelistedItems = {
["battery"] = 0,
["explosives"] = 0,
["plastic-bar"] = 0,
["processing-unit"] = 0,
["sulfur"] = 0
}
--[[!
Adds the given amount of given item type to the array.
If the item type is already present, the amount is added to the stored amount, if not a new key is created with the amount
@param aargTable the table to modify
@param cargItemName the item type
@param nargAmount the amount to add (or substract when negative)
--]]
function addItemsToTable(aargTable, cargItemName, nargAmount)
--debugLog("addItemsToTable: adding " .. tostring(nargAmount) .. "x " .. cargItemName)
if (aargTable[cargItemName]) then
aargTable[cargItemName] = aargTable[cargItemName] + nargAmount
else
aargTable[cargItemName] = nargAmount
end
end
--[[!
Cracks down the given amount of given item type into it's ingredients, recursively until only base materials or whitelisted items are left.
@param aargBaseResources contains the base items found so far. This array will be modified and new base items added to it when cracking
@param cargItemToCrackName name of the item to crack, e.g. "copper-wire"
@param nargItemToCrackCount the amount of the items to crack, serves as multiplicator
]]--
function crackDownToBaseResources(aargBaseResources, cargItemToCrackName, nargItemToCrackCount)
--debugLog("crackDownToBaseResources: trying to crack " .. tostring(nargItemToCrackCount) .. "x " .. cargItemToCrackName)
-- get the trade bonus for this item
local nTradeBonus = aSpecialTradeBonus[cargItemToCrackName]
-- was no trade bonus specified?
if not nTradeBonus then
-- then use the default bonus
nTradeBonus = nDefaultTradeBonus
end
-- is this a whitelisted item?
if aWhitelistedItems[cargItemToCrackName] then
-- yes, then don't crack it but just add it to the result and exit
--debugLog("crackDownToBaseResources: no cracking: whitelisted item");
-- add it to the result array
local nAmount = nargItemToCrackCount * nTradeBonus
addItemsToTable(aargBaseResources, cargItemToCrackName, nAmount)
-- we're done here
return
end
-- get the recipe of the item
local aItemToCrackRecipe = game.player.force.recipes[cargItemToCrackName]
-- no recipe?
if not aItemToCrackRecipe then
-- no recipe: base item
--debugLog("crackDownToBaseResources: no cracking: no recipe, base item");
-- add it to the result array
local nAmount = nargItemToCrackCount * nTradeBonus
addItemsToTable(aargBaseResources, cargItemToCrackName, nAmount)
-- we're done here
return
end
-- how many products you get when using this recipe. Attention: it always uses the first recipe
local nProductAmount = aItemToCrackRecipe.products[1].amount
--debugLog("crackDownToBaseResources: cracking with recipe: " .. aItemToCrackRecipe.name .. " (which gave " .. tostring(nProductAmount) .. " of these)")
-- go through all ingredients
for nCurrentIngredientIndex, aCurrentIngredient in pairs(aItemToCrackRecipe.ingredients) do
-- non-items like fluids are dumped
if (aCurrentIngredient.type ~= 0) then
debugLog("crackDownToBaseResources: dumping non-item " .. aCurrentIngredient.name)
else
-- how many ingredients you need when using this recipe
local nAmount = nargItemToCrackCount * (aCurrentIngredient.amount / nProductAmount)
-- crack this ingredient and add it to the result
crackDownToBaseResources(aargBaseResources, aCurrentIngredient.name, nAmount)
end
end
end
----- barter logic -----
--[[!
Gets called when a barter is to be made.
]]--
function performBarter()
debugLog("-- performBarter --")
debugLog("number of import chests: " .. tostring(#glob.barterImportChests))
debugLog("number of export chests: " .. tostring(#glob.barterExportChests))
-- get the first of our import chests
local nCurrentImportChestIndex, oCurrentImportChest = next(glob.barterImportChests, nil)
-- no import chest?
if not oCurrentImportChest then
-- we're done
return
end
-- flag which indicates if we could sell something
local bSoldSomething = false
-- flag which indicates when all our import chests ran out of space
local bOverflow = false
-- flag to show if we encountered unknown/unlisted items to trade
-- FINDME: to re-add?
local bUnknownItemsFound = false
-- iterate through all export chests
for nCurrentExportChestIndex, oCurrentExportChest in ipairs(glob.barterExportChests) do
-- only if this entity is valid
if oCurrentExportChest.valid then
--debugLog("processing export chest at index " .. tostring(nCurrentExportChestIndex))
-- get the current export chest's inventory
local oECInventory = oCurrentExportChest.getinventory(defines.inventory.chest)
-- get the inventory's contents
local oECContents = oECInventory.getcontents()
-- iterate through every item stack in the chest
for cItemToTradeName, nItemToTradeCount in pairs(oECContents) do
-- will contain all exchanged items of all recipes of all traded items
local aTradedForThese = {}
debugLog("trading " .. tostring(nItemToTradeCount) .. "x " .. cItemToTradeName .. " for...")
crackDownToBaseResources(aTradedForThese, cItemToTradeName, nItemToTradeCount)
-- start again at the first import chest to minimize non-full stacks across the chests when trading different types of items
nCurrentImportChestIndex, oCurrentImportChest = next(glob.barterImportChests, nil)
-- iterate through each item type we receive for selling this item type
for cTradedForItemName, cTradedForItemCount in pairs(aTradedForThese) do
-- there was a trade
bSoldSomething = true
-- how many do we get for this
local nTotal = math.floor(cTradedForItemCount)
debugLog("... " .. tostring(nTotal) .. "x " .. cTradedForItemName)
-- while there is still something to insert
while (nTotal > 0) do
-- try to insert in packs of 50 as this is the highest stack size for ore
local nInsertLimit = 50
-- check if there is less available
local nToInsert = nInsertLimit
if (nTotal < nInsertLimit) then
nToInsert = nTotal
end
-- can we insert a pack into the current import chest?
if oCurrentImportChest.caninsert{name=cTradedForItemName, count=nToInsert} then
-- then do it
oCurrentImportChest.insert{name=cTradedForItemName, count=nToInsert}
-- account
nTotal = nTotal - nToInsert
else
--debugLog("the current import chest got full, switching to the next import chest")
-- the current import chest got full, switch to the next import chest
nCurrentImportChestIndex, oCurrentImportChest = next(glob.barterImportChests, nCurrentImportChestIndex)
--debugLog("new import chest is at index " .. tostring(nCurrentImportChestIndex))
-- there is no next import chest?
if not oCurrentImportChest then
-- this means overflow, so indicate and break
debugLog("there is no next import chest -> overflow")
bOverflow = true
break
end
end
end
-- no space left?
if bOverflow then
break
end
end
-- remove all of the traded items from the export chest. Attention: this will remove more items than we got stuff for if the import chests overflew
--debugLog("removing " .. tostring(nItemToTradeCount) .. "x " .. cItemToTradeName .. " from export chest");
oECInventory.remove{name=cItemToTradeName, count=nItemToTradeCount}
-- no space left?
if bOverflow then
break
end
end
end
-- no space left?
if bOverflow then
break
end
end
-- say something nice
if bOverflow then
localizedPrint("barter-overflow")
else
if bUnknownItemsFound then
if bSoldSomething then
localizedPrint("barter-successful-invitems")
else
localizedPrint("barter-invitems")
end
else
if bSoldSomething then
localizedPrint("barter-successful")
end
end
end
end
----- event handlers -----
--[[!
Gets called when the game starts.
Creates internal data structures when this mod is added for the first time.
]]--
function onInit()
--debugLog("-- onInit --")
-- is the mod not already active in the savegame?
if not glob.nextBarter then
-- determine when we will barter for the first time
glob.nextBarter = game.tick + barterPeriod
-- initialize the internal data structures which will contain our import and export chests
glob.barterImportChests = {}
glob.barterExportChests = {}
end
end
--[[!
Gets called when the scenario is loaded.
Does the same as our onInit().
@see onInit
]]--
function onLoad()
--debugLog("-- onLoad --")
onInit()
end
--[[!
Gets called every tick.
Checks if a barter should happen.
@param event the firing event
]]--
function onTick(event)
-- has the game's tick reached a barter?
if (game.tick >= glob.nextBarter) then
-- yes, calculate a new tick when to barter
glob.nextBarter = glob.nextBarter + barterPeriod
-- and let's trade
performBarter()
end
end
--[[!
Gets called when a new entity was placed, e.g. by the player or by a bot.
@param event the firing event
]]--
function entityWasPlaced(event)
--debugLog("-- entityWasPlaced --")
local oEntity = event.createdentity
-- was it an import chest?
if ((oEntity.name == "import-chest") or (oEntity.name == "import-provider-chest")) then
-- then add the import chest to the data structure
debugLog("adding new import chest: " .. oEntity.name)
table.insert(glob.barterImportChests, oEntity)
debugLog("there are now " .. tostring(#glob.barterImportChests) .. " import chests")
end
-- was it an export chest?
if ((oEntity.name == "export-chest") or (oEntity.name == "export-requester-chest")) then
-- then add the export chest to the data structure
debugLog("adding new export chest: " .. oEntity.name)
table.insert(glob.barterExportChests, oEntity)
debugLog("there are now " .. tostring(#glob.barterExportChests) .. " export chests")
end
end
--[[!
Gets called when an existing entity was removed, e.g. by the player or by a bot or when it has been destroyed.
@param event the firing event
]]--
function entityWasRemoved(event)
--debugLog("-- entityWasRemoved --")
local oEntity = event.entity
-- was it an import chest?
if ((oEntity.name == "import-chest") or (oEntity.name == "import-provider-chest")) then
-- find the affected chest in the data structure
for i = 1, #glob.barterImportChests do
if (glob.barterImportChests[i].equals(oEntity)) then
-- remove it from the import chest data structure
debugLog("removing import chest at index " .. tostring(i) .. ": " .. oEntity.name)
table.remove(glob.barterImportChests,i)
debugLog("there are now " .. tostring(#glob.barterImportChests) .. " import chests")
-- done, leave the loop
break
end
end
end
-- was it an export chest?
if ((oEntity.name == "export-chest") or (oEntity.name == "export-requester-chest")) then
-- find the affected chest in the data structure
for i = 1, #glob.barterExportChests do
if (glob.barterExportChests[i].equals(oEntity)) then
-- remove it from the export chest data structure
debugLog("removing export chest at index " .. tostring(i) .. ": " .. oEntity.name)
table.remove(glob.barterExportChests,i)
debugLog("there are now " .. tostring(#glob.barterExportChests) .. " export chests")
-- done, leave the loop
break
end
end
end
end
Re: Recipe woes
According to https://forums.factorio.com/wiki/inde ... ype/Recipe it's energy_required, don't know if it's accessible via lua thoughdee- wrote:but I still don't know where to get the recipe duration from
-
- Long Handed Inserter
- Posts: 66
- Joined: Sat May 10, 2014 8:48 am
- Contact:
Re: Recipe woes
It is. I put this in data.lua after all the require statements:Choumiko wrote:don't know if it's accessible via lua though
Code: Select all
print(data.raw.recipe["speed-module"].energy_required)
print(data.raw.recipe["speed-module-2"].energy_required)
print(data.raw.recipe["speed-module-3"].energy_required)
15
30
60
Re: Recipe woes
Oh, I am so blind... must have skipped it because of the name.
Thanks!
Edit:
the code line
gives me:
what did I do wrong?
Thanks!
Edit:
the code line
Code: Select all
local oItemToCrackRecipe = data.raw.recipe[cargItemToCrackName]
Code: Select all
Error while running the event handler: __barter__/control.lua:164: attempt to index global 'data' (a nil value)
-
- Long Handed Inserter
- Posts: 66
- Joined: Sat May 10, 2014 8:48 am
- Contact:
Re: Recipe woes
I don't know the answer to that one, I'm still too new to this. data.raw... is visible from data.lua, but not from control.lua. There might be a way to make it so, but I don't know if/what it is.dee- wrote:what did I do wrong?