I'd suggest writing Lua scripts rather than plugins, as it seems these are intended to be used as commands entered by the player, and neither of them should require compiled code for efficiency reasons.
All of them seem to be quite possible to implement.
As a starting point for the first one, here's one of my scripts intended to control which trees to cut, sparing one of each resource providing kind of tree, selecting the ones closest to the center of the embark. It skips cavern trees, and deals with elven logging restrictions.
local include_all_edible = true -- If false, only booze and thread providing trees (which don't exist in vanilla) are kept.
local reserved_trees = 10 -- How many trees we save for emergency logging.
local skip_evil = false -- If set, ignores trees in an evil biome. The main purpose for this is to
-- allow you to keep out of evil rain/clouds on multi biome embarks. Note that it will also
-- refrain from setting up gathering zones in the evil biome. Note that the code doesn't check
-- if a zone would enter the evil biome, so a manual check is still needed.
local tree_quota = 10000 -- To meet pointyear demands. This default is used if the script fails to find a quota agreement.
local max_trees = tree_quota - reserved_trees
local embark_width = (df.global.world.world_data.active_site [0].global_max_x - df.global.world.world_data.active_site [0].global_min_x + 1) * 48
local embark_height = (df.global.world.world_data.active_site [0].global_max_y - df.global.world.world_data.active_site [0].global_min_y + 1) * 48
local is_evil_offset = {}
for i = -1, 1 do
local x = df.global.world.world_data.region_details [0].pos.x + i
if x < 0 then
x = 0
elseif x == df.global.world.world_data.world_width then
x = df.global.world.world_data.world_width - 1
end
for k = -1, 1 do
local y = df.global.world.world_data.region_details [0].pos.y + k
if y < 0 then
y = 0
elseif y == df.global.world.world_data.world_height then
y = df.global.world.world_data.world_height - 1
end
is_evil_offset [1 + i + (k + 1) * 3] = df.global.world.world_data.region_map [x]:_displace (y).evilness >= 66
end
end
--====================================
function reject_evil (block, x, y)
return skip_evil and is_evil_offset [block.region_offset [block.designation [x] [y].biome]]
end
----------------------------
function closer (x1, y1, x2, y2)
local dx1
local dx2
local dy1
local dy2
local max_x, max_y, max_z = dfhack.maps.getTileSize()
local center_x = max_x / 2
local center_y = max_y / 2
dx1 = center_x - x1
dy1 = center_y - y1
dx2 = center_x - x2
dy2 = center_y - y2
return dx1 * dx1 + dy1 * dy1 < dx2 * dx2 + dy2 * dy2
end
----------------------------
function make_zone (pos, plant_raw)
local zone_found = false
local horizontal_extent = 3
local vertical_extent = 3
local position = {}
position.x = pos.x - 1
position.y = pos.y - 1
position.z = pos.z
if position.x < 0 then
position.x = 0
horizontal_extent = 2
end
if position.y < 0 then
position.y = 0
horizontal_extent = 2
end
for i, zone in ipairs (df.global.world.buildings.other.ANY_ZONE) do
if zone.zone_flags.gather and
zone.centerx == pos.x and
zone.centery == pos.y and
zone.z == pos.z and
zone.name:len () >= plant_raw.id:len () and
string.sub (zone.name:upper (), 1, plant_raw.id:len ()) == plant_raw.id then
zone_found = true
break
end
end
if not zone_found then
local zone = dfhack.buildings.allocInstance (position, df.building_type.Civzone, df.civzone_type.ActivityZone, -1)
zone.is_room = true
dfhack.buildings.setSize (zone, horizontal_extent, vertical_extent, 0)
zone.zone_flags.active = true
zone.zone_flags.gather = true
zone.gather_flags.pick_trees = true
zone.gather_flags.gather_fallen = true
zone.name = plant_raw.id .. " Gathering Zone"
dfhack.buildings.constructAbstract (zone)
-- Work around for constructAbstract not setting up zone.room as it ought to. Should be possible to remove once that's corrected.
zone.room.extents = df.reinterpret_cast (df.building_extents_type, df.new ("uint8_t", horizontal_extent * vertical_extent))
for i = 0, horizontal_extent - 1 do
for k = 0, vertical_extent - 1 do
zone.room.extents:_displace (i * vertical_extent + k).value = df.building_extents_type.Stockpile
end
end
zone.room.x = position.x
zone.room.y = position.y
zone.room.width = horizontal_extent
zone.room.height = vertical_extent
-- Restore the tree to the zone. It's removed by the creation operations as it's impassable...
--
if zone.room.extents then
for l = 0, (zone.x2 - zone.x1 + 1) * (zone.y2 - zone.y1 + 1) - 1 do
zone.room.extents [l] = 1
end
end
end
end
----------------------------
function remove_zone (pos, plant_raw)
for i, zone in ipairs (df.global.world.buildings.other.ANY_ZONE) do
if zone.zone_flags.gather and
zone.centerx == pos.x and
zone.centery == pos.y and
zone.z == pos.z and
zone.name:len () >= plant_raw.id:len () and
string.sub (zone.name:upper (), 1, plant_raw.id:len ()) == plant_raw.id then
dfhack.buildings.deconstruct (zone)
break
end
end
end
----------------------------
function spareone ()
local tree_quota_found = false
local current
local skip
-- This code assumes there's only one civ with which our civ has tree cutting agreements.
--
for i, diplomacy in ipairs (df.global.world.entities.all [df.global.ui.civ_id].relations.diplomacy) do
if df.global.world.entities.all [diplomacy.group_id].type == df.historical_entity_type.Civilization then
for k, meeting in ipairs (df.global.world.entities.all [diplomacy.group_id].meeting_events) do
if meeting.type == df.meeting_event_type.AcceptAgreement and
meeting.topic == df.meeting_topic.TreeQuota then
tree_quota = meeting.quota_remaining
max_trees = tree_quota - reserved_trees
dfhack.println ("Max trees to cut determined to be " .. tostring (max_trees))
tree_quota_found = true
break
end
end
if tree_quota_found then
break
end
end
end
local trees_designated = 0
local fell_tree_1 = {}
local fell_tree_2 = {}
current = df.global.world.jobs.list
while current ~= nil do
if current.item ~= nil and
current.item.job_type == df.job_type.FellTree then
table.insert (fell_tree_1, current)
end
current = current.next
end
for i, u in ipairs (df.global.world.jobs.postings) do
if u.job ~= nil and
u.job.job_type == df.job_type.FellTree then
table.insert (fell_tree_2, u)
end
end
for i, plant in ipairs (df.global.world.plants.all) do
if df.global.world.raws.plants.all [plant.material].flags.TREE then
local cur = dfhack.maps.getTileBlock (plant.pos)
local x = plant.pos.x
local y = plant.pos.y
local z = plant.pos.z
if cur.tiletype [x % 16] [y % 16] == df.tiletype.TreeTrunkPillar or
cur.tiletype [x % 16] [y % 16] == df.tiletype.TreeTrunkNW or
cur.tiletype [x % 16] [y % 16] == df.tiletype.TreeTrunkNE or
cur.tiletype [x % 16] [y % 16] == df.tiletype.TreeTrunkSW or
cur.tiletype [x % 16] [y % 16] == df.tiletype.TreeTrunkSE then
if cur.designation [x % 16] [y % 16].dig == df.tile_dig_designation.Default then -- Designated for cutting
trees_designated = trees_designated + 1
elseif cur.designation [x % 16] [y % 16].dig == df.tile_dig_designation.No then
skip = false
for k, job in ipairs (fell_tree_1) do
if job.item.pos.x == x and
job.item.pos.y == y and
job.item.pos.z == z then
trees_designated = trees_designated + 1
skip = true
break
end
end
if not skip then
for k, job in ipairs (fell_tree_2) do
if job.job.pos.x == x and
job.job.pos.y == y and
job.job.pos.z == z then
trees_designated = trees_designated + 1
skip = true
break
end
end
end
end
end
end
end
dfhack.println ("Trees already designated when the script is invoked: " .. tostring (trees_designated))
if false then -- This section identifies trees that won't yield wood, and thus won't appear on the embark due to a bug.
-- Skipped as it's probably not of interest. The subjected vanilla trees are Abaca and Banana.
for i, plant in ipairs (df.global.world.raws.plants.all) do
if plant.flags.TREE and
plant.material_defs.type == -1 then
dfhack.println ("Non wood yielding tree: " .. plant.id)
end
end
end
local valuable
for i, v in ipairs (df.global.world.raws.plants.all) do
if v.flags.TREE and
not v.flags.BIOME_SUBTERRANEAN_WATER and -- Skipping subterranean trees.
not v.flags.BIOME_SUBTERRANEAN_CHASM and
not v.flags.BIOME_SUBTERRANEAN_LAVA then -- No such vanilla trees exist.
if not v.material_defs.type == -1 then
dfhack.println ("Found non 'type' plant " .. v.id)
end
valuable = v.flags.DRINK or
v.flags.THREAD
if include_all_edible then
for k, material in ipairs (v.material) do
if material.flags.EDIBLE_RAW or
material.flags.EDIBLE_COOKED then
valuable = true
break
end
end
end
local found = false
local skipped = nil
local skipped_2 = nil
local skipped_3 = nil
local sapling_found = false
local spare_count = 1
for k, growth in ipairs (v.growths) do
if growth.id == "FRUIT" then -- Should really tie to reaction
if not growth.locations.twigs and
not growth.locations.light_branches and
not growth.locations.heavy_branches and
growth.locations.trunk and
v.trunk_branching == 0 then
spare_count = 3 -- Grows only on the trunk, so the yield is poor.
end
end
end
for k, plant in ipairs (df.global.world.plants.all) do
if plant.material == i then
local cur = dfhack.maps.getTileBlock (plant.pos)
local x = plant.pos.x
local y = plant.pos.y
local z = plant.pos.z
if not reject_evil (cur, x % 16, y % 16) and
cur.tiletype [x % 16] [y % 16] == df.tiletype.TreeTrunkPillar or
cur.tiletype [x % 16] [y % 16] == df.tiletype.TreeTrunkNW or
cur.tiletype [x % 16] [y % 16] == df.tiletype.TreeTrunkNE or
cur.tiletype [x % 16] [y % 16] == df.tiletype.TreeTrunkSW or
cur.tiletype [x % 16] [y % 16] == df.tiletype.TreeTrunkSE then
if cur.designation [x % 16] [y % 16].dig == df.tile_dig_designation.No then
current = df.global.world.jobs.list
skip = false
while current ~= nil do
if current.item ~= nil and
current.item.job_type == df.job_type.FellTree and
current.item.pos.x == x and
current.item.pos.y == y and
current.item.pos.z == z then
skip = true
break
end
current = current.next
end
if not skip then
for l, u in ipairs (df.global.world.jobs.postings) do
if u.job ~= nil and
u.job.job_type == df.job_type.FellTree and
u.job.pos.x == x and
u.job.pos.y == y and
u.job.pos.z == z then
skip = true
break
end
end
end
if not skip then
if skipped == nil and
valuable then
skipped = plant.pos
found = true
dfhack.println ("Skipping (" .. tostring (x) .. ", " .. tostring (y) .. ") " .. v.id)
make_zone (plant.pos, v)
elseif spare_count == 3 and
skipped_2 == nil and
valuable then
skipped_2 = plant.pos
dfhack.println ("Skipping (" .. tostring (x) .. ", " .. tostring (y) .. ") " .. v.id)
make_zone (plant.pos, v)
elseif spare_count == 3 and
skipped_3 == nil and
valuable then
skipped_3 = plant.pos
dfhack.println ("Skipping (" .. tostring (x) .. ", " .. tostring (y) .. ") " .. v.id)
make_zone (plant.pos, v)
else
if valuable then
if closer (x, y, skipped.x, skipped.y) then
cur = dfhack.maps.getTileBlock (skipped)
x = skipped.x
y = skipped.y
z = skipped.z
remove_zone (skipped, v)
make_zone (plant.pos, v)
skipped = plant.pos
dfhack.println ("Swapping (" .. tostring (x) .. ", " .. tostring (y) .. ") for ("
.. tostring (skipped.x) .. ", " .. tostring (skipped.y) .. ")")
elseif spare_count == 3 and
closer (x, y, skipped_2.x, skipped_2.y) then
cur = dfhack.maps.getTileBlock (skipped_2)
x = skipped_2.x
y = skipped_2.y
z = skipped_2.z
remove_zone (skipped_2, v)
make_zone (plant.pos, v)
skipped_2 = plant.pos
dfhack.println ("Swapping (" .. tostring (x) .. ", " .. tostring (y) .. ") for ("
.. tostring (skipped_2.x) .. ", " .. tostring (skipped_2.y) .. ")")
elseif spare_count == 3 and
closer (x, y, skipped_3.x, skipped_3.y) then
cur = dfhack.maps.getTileBlock (skipped_3)
x = skipped_3.x
y = skipped_3.y
z = skipped_3.z
remove_zone (skipped_3, v)
make_zone (plant.pos, v)
skipped_3 = plant.pos
dfhack.println ("Swapping (" .. tostring (x) .. ", " .. tostring (y) .. ") for ("
.. tostring (skipped_3.x) .. ", " .. tostring (skipped_3.y) .. ")")
end
end
if trees_designated < max_trees then
cur.designation [x % 16] [y % 16].dig = df.tile_dig_designation.Default
cur.flags.designated = true
remove_zone ({x, y, z}, v)
dfhack.println ("Designated " .. v.id .. " at (" .. tostring (x) .. ", " .. tostring (y) .. ")")
trees_designated = trees_designated + 1
end
found = true
end
end
end
elseif cur.tiletype [x % 16] [y % 16] == df.tiletype.Sapling then
sapling_found = true
end
end
end
if valuable and not found then
if sapling_found then
dfhack.println ("Found " .. v.id .. " sapling")
else
dfhack.println ("Failed to find " .. v.id)
end
end
end
end
dfhack.println ("Number of trees designated: " .. tostring (trees_designated))
end
spareone()
[code]
[/code]