I finally got sapling deletion working, here is what I ended up with:
-- Install a periodic ticker that kills saplings over a certain count.
do return end -- Remove this once I figure out a good way to calculate a sapling limit.
-- TODO: Change the way this works:
-- Rather than set a sapling limit set a tree limit. At the game start get a tree count, then use that
-- (plus a few) as the tree cap. Then if there are more trees than the limit delete all saplings, else
-- allow saplings to grow as they will. This will preserve the relative amount of trees on the map at
-- all times. If you embark in a heavily forested biome it will always be heavily forested, while grass
-- lands will remain mostly grass.
-- How many saplings to leave alive on the surface.
local livecount = 25
local function treeTicker()
-- Get a list of all the saplings on the map.
local plants = df.global.world.plants.all
local saplings = {}
for i = 0, #plants - 1, 1 do
local plant = plants[i]
local ttype = dfhack.map.getTileType(plant.pos)
if not plant.flags.is_shrub and df.tiletype.attrs[ttype].shape == df.tiletype_shape["SAPLING"] and df.tiletype.attrs[ttype].special ~= df.tiletype_special["DEAD"] then
-- plant is a live sapling...
if df.tiletype.attrs[dfhack.map.getTileType(plant.pos.x, plant.pos.y, plant.pos.z-1)].material == df.tiletype_material["SOIL"] then
-- ...growing in a soil layer (probably the surface).
table.insert(saplings, {i = i, ref = plant})
end
end
end
-- Choose random saplings to delete...
local del = {}
for #saplings > livecount do
local i = math.random(1, #saplings)
local plant = saplings[i]
table.insert(del, plant)
table.remove(saplings, i)
end
-- ...and sort the list in reverse order by index in plants.all
table.sort(del, function(a, b)
return a.i > b.i
end)
-- Finally delete the selected saplings.
for _, plant in ipairs(del) do
local block = dfhack.maps.ensureTileBlock(plant.pos.x, plant.pos.y, plant.pos.z)
block.tiletype[plant.pos.x%16][plant.pos.y%16] = df.tiletype["SoilFloor1"]
-- The list is high indexes to low indexes, so we can just delete from the list without fear of messing up later indexes.
df.global.world.plants.all:erase(plant.i)
-- Plants do not have an ID number, so we need to compare them based on location.
for j = 0, #df.global.world.plants.tree_dry - 1, 1 do
if same_xyz(df.global.world.plants.tree_dry[j].pos, plant.ref.pos) then
df.global.world.plants.tree_dry:erase(j)
break
end
end
for j = 0, #df.global.world.plants.tree_wet - 1, 1 do
if same_xyz(df.global.world.plants.tree_wet[j].pos, plant.ref.pos) then
df.global.world.plants.tree_wet:erase(j)
break
end
end
plant.ref:delete()
end
-- Every 60 seconds (at 100 FPS) should be fast enough.
dfhack.timeout(6000, 'ticks', treeTicker)
end
treeTicker()
Obviously I am still not done with it yet, but the "delete sapling" part seems to work correctly.