Making trees that heal

Place to get help with not working mods / modding interface.
User avatar
Taehl
Long Handed Inserter
Long Handed Inserter
Posts: 50
Joined: Sun Jan 18, 2015 2:23 am
Contact:

Making trees that heal

Post by Taehl »

My friend suggested an excellent, simple idea for a mod: Damaged trees will slowly regenerate their health over time, so you don't have to see health bars all over forests after a battle. Now, if I understand correctly, I would only need to make a new mod with a data-updates.lua containing:

Code: Select all

for k, v in pairs( data.raw.tree ) do
	if not string.match(v.name, "dead") then
		v.healing_per_tick = .01
	end
end
Right? The wiki mentions something about healing_per_tick only working on "active entities", but doesn't elaborate...

User avatar
FreeER
Smart Inserter
Smart Inserter
Posts: 1266
Joined: Mon Feb 18, 2013 4:26 am
Contact:

Re: Making trees that heal

Post by FreeER »

Taehl wrote:an excellent, simple idea for a mod
Not so simple it turns out :lol:
in game you can set entity.active = false or entity.active = true (default for most), assuming entity is a valid reference gained through onbuiltentity, findentities, etc.
From my understanding some entities can't be active... but I'd never encountered an issue with anything, players, assembling machines, inserters, steam engines, solar panels, and most others can have their active state changed. Apparently trees are one of those entities which are always not active...

you can however do this 'manually' in control.lua, though you have to do it in 'chunks' (trying to do all of them each tick drops my UPS to 1/2 (30), on a NEW world! same reason there is a glob.trees.health table, game.entityprototypes is fairly slow so I limit it's use to only when it's needed)
I haven't tested this with a large world but it should be decent, note this won't work perfectly for already started games since onchunkgenerated won't be raised for the parts already generated. Newly generated areas would be checked though.

Code: Select all

require "defines"
game.oninit(function() -- ran when the mod is first loaded
  glob.trees = {}
  glob.trees.start = 1
  glob.trees.health = {}
end)

game.onload(function() -- ran when a game is loaded (needed for already started games)
  -- essentially the same as oninit... so... code duplication alert :)
  if not glob.trees then
    glob.trees = {}
    glob.trees.start = 1
    glob.trees.health = {}
  end
end)

game.onevent(defines.events.onchunkgenerated, function(event)
  local trees = game.findentitiesfiltered{type="tree", area=event.area}
  -- remove "dead" trees, loop backwards since we're deleting items
  for index=#trees, 1, -1 do
    local tree = trees[index]
    if tree.name:match('dead') then
      table.remove(trees, index)
    else
      if not glob.trees.health[tree.name] then
        glob.trees.health[tree.name] = game.entityprototypes[tree.name].maxhealth
      end
    end
  end
  table.insert(glob.trees, trees) -- insert adjusted area table
end)

game.onevent(defines.events.ontick, function(event) -- ran ~60 times per second
  if #glob.trees > 0 then
    for index, tree in ipairs(glob.trees[glob.trees.start]) do
      if tree.valid and tree.health ~= glob.trees.health[tree.name] then
        -- increment health based on how many areas there are between increments
        tree.health = tree.health + 0.1 * #glob.trees
      elseif not tree.valid then
        table.remove(glob.trees[glob.trees.start], index) -- remove trees that no longer exist
      end
    end
    -- increment start so that the next area is searched...
    -- using % so it stays within bounds of the tree table and adding 1
    -- because lua tables are 1 based instead of 0 based.
    glob.trees.start = ((glob.trees.start + 1) % #glob.trees) + 1
  end
end)
You could add a script interface that can be called from the command line to add areas to the glob.trees table, but that'd likely lead to overlapping areas and the same trees being checked multiple times. Something along these lines, might have to tweak 100 for better sizing, I just picked a random number (note I'm assuming that the onchunkgenerated function has been moved and given the name of addArea, if you want to do this and don't understand how that works let me know)

Code: Select all

remote.addinterface("some_interface_name_usually_mod_name", {
  addArea = function(wasWarned)
    if not wasWarned then
      for _, player in ipairs(game.players) do
        player.print("addArea will fail if there is more than 1 player on!\nTo continue call by adding true to the end of the call")
      end
    end
    -- table with area since we're 'pretending' to be the onchunkgenerated event that it was originally written for  
    local pos = game.player.position
    addArea({ area={ {pos.x - 100, pos.y - 100}, {pos.x + 100, pos.y + 100} } })
  end
})
that would be called like so on the command line: /c remote.call("some_interface_name_usually_mod_name", "addArea")
after the warning you'd then use: /c remote.call("some_interface_name_usually_mod_name", "addArea", true)
note, it fails when there is more than 1 player because there is no access to the player that actually called the interface, and I didn't want to loop through ALL the players, that that would be a relatively simple change if you wanted it to.
<I'm really not active any more so these may not be up to date>
~FreeER=Factorio Modding
- Factorio Wiki
- My Factorio Modding Guide
- Wiki Modding Guide
Feel free to pm me :)
Or drop into #factorio on irc.esper.net

User avatar
Taehl
Long Handed Inserter
Long Handed Inserter
Posts: 50
Joined: Sun Jan 18, 2015 2:23 am
Contact:

Re: Making trees that heal

Post by Taehl »

That is, indeed, less than easy. Wouldn't it be possible to, say, only once per second, call a function which gets a list of all trees in the map and increments their health?

Less than ideal, I know, but it could be refined... Like, all trees in the table get healed once per second, but the tree list is updated only once per minute or something.

User avatar
FreeER
Smart Inserter
Smart Inserter
Posts: 1266
Joined: Mon Feb 18, 2013 4:26 am
Contact:

Re: Making trees that heal

Post by FreeER »

Taehl wrote:call a function which gets a list of all trees in the map
There is not, to my knowledge, any provided function that can do that. That is essentially what the onchunkgenerated event function is doing for us, adding trees in new chunks (base trees don't reproduce so no need to recheck them) to our own table in the glob table (the only table that is saved and loaded when the player's game is).

The reason you have to do it in chunks is because Lua is quite slow relative to the C++ code (that most other things, pollution, energy use, etc. are done in), ~9x times slower according to this (note: that factor can change depending on the exact comparison test used, feel free to Google some yourself, though Lua will always be slower to run due to overhead in the language, but those overheads also make it nice to work in :)). This leads to a drop in updates per second (UPS), and thus frames per second (FPS) as well, when you try to do too much at any one time, regardless of how often those times occur. If it takes 1/3 of a second (20 ticks) to run through the entire list, and that time will grow as you explore more of the map, trying to do all of it in one tick (even if it is only every minute) forces "everything" else to wait on it, and logically leads to 19 ticks of lag on that tick. Splitting the work into a "reasonable" amount (that can be found through testing) every tick means that it can be finished within that tick and not cause any lag, it still takes approximately the same amount of time for the work to be done, but the work is 'un-noticeably' spread out over that time frame since it's not trying to get it all done at once.

As for 'updating' the tree list, it's quick to remove entries and can be done during that tick. Optimizing the tree list so that chunks are of a reasonable size (more than 10 tree but less than, say, 500, just an arbitrary number) would only make sense after x seconds since it's actually pretty uncommon for trees to die, and in the base game trees are never added. That can be done in ontick by checking if event.tick % 60 * x == 0 (ie, that the current tick is some multiple of the number of seconds you want to wait) and if so then calling the function which optimizes the list (theoretically that list could become so large that you'd want to spread that work out as well, but it'd just be the same principle at work). Of course, trees growing leads to another possible point, most obviously with the Treefarm mod, since it does grow trees, and so you'd need the code to recall findentitiesfiltered to see when more have grown, BUT that call is also relatively slow since the C++ has to first find all of the entities in that area, filter them, and then pass them back to Lua, so you wouldn't want to call it for areas that don't have a treefarm in them (you can detect when they are built with onbuiltentity and onrobotbuiltentity). You'd probably want to make 'special' areas that are, perhaps, checked more frequently using findentitiesfiltered (separate list, just storing the area to check instead of the trees theirselves?). But, perhaps the time that those trees live is so small and inconsequential that it wouldn't be worth actually considering them as 'special' and instead simply ignoring them would be easier?
Taehl wrote:That is, indeed, less than easy
It's not particularly difficult, but it requires some thought as to the "best" way to implement it.

P.S. If anyone happens to notice that something is incorrect then please correct me, but I believe everything I said there makes sense :lol:

User avatar
Taehl
Long Handed Inserter
Long Handed Inserter
Posts: 50
Joined: Sun Jan 18, 2015 2:23 am
Contact:

Re: Making trees that heal

Post by Taehl »

Maybe they should implement LuaJIT, so mods won't be that slow.

User avatar
FreeER
Smart Inserter
Smart Inserter
Posts: 1266
Joined: Mon Feb 18, 2013 4:26 am
Contact:

Re: Making trees that heal

Post by FreeER »

Taehl wrote:Maybe they should implement LuaJIT, so mods won't be that slow.
So far it hasn't actually been needed when a modicum of thought is put into the scripts (in this case, trying to loop over every tree in a new world drops UPS to 1/2, vs a very slight optimization with practically no thought put into it of only one "chunk" per tick gives a nice solid 60 UPS. Let alone your suggestion of calling a function to get all of the trees every minute) and, more importantly, it would take developer time away from the more important work on the game itself. Also (minor note), it doesn't fully support Lua 5.2 which is what's used in Factorio.

But, it has been mentioned and it'll probably happen at some point.

P.S. I can't really get any good benchmarks for LuaJIT vs C++ especially when it's LuaJIT calling back into C++ a lot, so I'm going to assume that LuaJIT here wouldn't be as large a difference as if it was just a pure Lua script.

User avatar
Adil
Filter Inserter
Filter Inserter
Posts: 945
Joined: Fri Aug 15, 2014 8:36 pm
Contact:

Re: Making trees that heal

Post by Adil »

Hello, Freer!
I wonder, would
this
be faster than
this
?
On a slightly related note, how would the former variant fare in comparison with this code?
this code
I do realize that given the chunkgenerated event it's not that important and that it's more of a general question about lua, but the book doesn't answer to that explicitly and I fail to comprise the query, that would bring me to related article on stackoverflow.
Not to mention I can't really speak about lua out of factorio context.

Also.
FreeER wrote: you'd need the code to recall findentitiesfiltered to see when more have grown, BUT that call is also relatively slow since the C++ has to first find all of the entities in that area, filter them, and then pass them back to Lua, so you wouldn't want to call it for areas that don't have a treefarm in them (you can detect when they are built with onbuiltentity and onrobotbuiltentity). You'd probably want to make 'special' areas that are, perhaps, checked more frequently using findentitiesfiltered (separate list, just storing the area to check instead of the trees theirselves?).
As another option, one could ask drs really nicely to raise a custom event when his mod places a tree? or maybe when the tree is placed outside of areas that get automatically harvested. His trees consume pollution too, someone might try artificial forests after all.
I do mods. Modding wiki is friend, it teaches how to mod. Api docs is friend too...
I also update mods, some of them even work.
Recently I did a mod tutorial.

User avatar
FreeER
Smart Inserter
Smart Inserter
Posts: 1266
Joined: Mon Feb 18, 2013 4:26 am
Contact:

Re: Making trees that heal

Post by FreeER »

Adil wrote:I wonder
Hm, I'm honestly not certain since all of my Lua experience has been with Factorio and I've never really worried about optimization much, but my, um, logical hypothesis, is that the for loop would be a bit faster since it removes the need for a method/function call. Of course with that reasoning your second option would be slightly slower than your first, but possibly faster than my suggestion. I'm not sure of the actual difference, but do keep in mind that with the hard-coded table that you can no longer work with mod-added trees (though you could generate that table on load by looping through game.entityprototypes and checking if the type == "tree" and ...). Not sure how to bench mark it though... a quick test with os.time() in a regular lua environment (outside Factorio) just results in 0 for all three.
Adil wrote:one could ask drs really nicely to raise a custom event
True :)

note: if this wasn't Lua (which has immutable strings and there's some extra stuff to make string comparisons fast) I'd mention that with 'dead' likely being at the start of the name means that less of the string is being checked than a full name comparison/hash, but this IS Lua so it probably doesn't matter :)

Post Reply

Return to “Modding help”