From f33c9bc8813a8588c3288304f271cddb295d0e9d Mon Sep 17 00:00:00 2001 From: quarque2 <107313885+quarque2@users.noreply.github.com> Date: Mon, 20 Jun 2022 21:38:37 +0200 Subject: [PATCH 001/111] Update tile-material.lua (#2218) * Update tile-material.lua * Update changelog.txt * Update changelog.txt * Update changelog.txt * Update tile-material.lua --- docs/changelog.txt | 1 + library/lua/tile-material.lua | 154 +++++++++++++++------------------- 2 files changed, 69 insertions(+), 86 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index e91a427a9..35f344adb 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -46,6 +46,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## API ## Lua +- ``tile-material``: fix the order of declarations. The ``GetTileMat`` function now returns the material as intended (always returned nil before). Also changed the license info, with permission of the original author. # 0.47.05-r6 diff --git a/library/lua/tile-material.lua b/library/lua/tile-material.lua index 0e5565d09..ca8b25030 100644 --- a/library/lua/tile-material.lua +++ b/library/lua/tile-material.lua @@ -1,25 +1,7 @@ -- tile-material: Functions to help retrieve the material for a tile. --[[ -Copyright 2015-2016 Milo Christiansen - -This software is provided 'as-is', without any express or implied warranty. In -no event will the authors be held liable for any damages arising from the use of -this software. - -Permission is granted to anyone to use this software for any purpose, including -commercial applications, and to alter it and redistribute it freely, subject to -the following restrictions: - -1. The origin of this software must not be misrepresented; you must not claim -that you wrote the original software. If you use this software in a product, an -acknowledgment in the product documentation would be appreciated but is not -required. - -2. Altered source versions must be plainly marked as such, and must not be -misrepresented as being the original software. - -3. This notice may not be removed or altered from any source distribution. +Original code provided by Milo Christiansen in 2015 under the MIT license. Relicensed under the ZLib license to align with the rest of DFHack, with his permission. ]] local _ENV = mkmodule("tile-material") @@ -85,73 +67,6 @@ local function fixedMat(id) end end --- BasicMats is a matspec table to pass to GetTileMatSpec or GetTileTypeMat. This particular --- matspec table covers the common case of returning plant materials for plant tiles and other --- materials for the remaining tiles. -BasicMats = { - [df.tiletype_material.AIR] = nil, -- Empty - [df.tiletype_material.SOIL] = GetLayerMat, - [df.tiletype_material.STONE] = GetLayerMat, - [df.tiletype_material.FEATURE] = GetFeatureMat, - [df.tiletype_material.LAVA_STONE] = GetLavaStone, - [df.tiletype_material.MINERAL] = GetVeinMat, - [df.tiletype_material.FROZEN_LIQUID] = fixedMat("WATER:NONE"), - [df.tiletype_material.CONSTRUCTION] = GetConstructionMat, - [df.tiletype_material.GRASS_LIGHT] = GetGrassMat, - [df.tiletype_material.GRASS_DARK] = GetGrassMat, - [df.tiletype_material.GRASS_DRY] = GetGrassMat, - [df.tiletype_material.GRASS_DEAD] = GetGrassMat, - [df.tiletype_material.PLANT] = GetShrubMat, - [df.tiletype_material.HFS] = nil, -- Eerie Glowing Pit - [df.tiletype_material.CAMPFIRE] = GetLayerMat, - [df.tiletype_material.FIRE] = GetLayerMat, - [df.tiletype_material.ASHES] = GetLayerMat, - [df.tiletype_material.MAGMA] = nil, -- SMR - [df.tiletype_material.DRIFTWOOD] = GetLayerMat, - [df.tiletype_material.POOL] = GetLayerMat, - [df.tiletype_material.BROOK] = GetLayerMat, - [df.tiletype_material.ROOT] = GetLayerMat, - [df.tiletype_material.TREE] = GetTreeMat, - [df.tiletype_material.MUSHROOM] = GetTreeMat, - [df.tiletype_material.UNDERWORLD_GATE] = nil, -- I guess this is for the gates found in vaults? -} - --- NoPlantMats is a matspec table to pass to GetTileMatSpec or GetTileTypeMat. This particular --- matspec table will ignore plants, returning layer materials (or nil for trees) instead. -NoPlantMats = { - [df.tiletype_material.SOIL] = GetLayerMat, - [df.tiletype_material.STONE] = GetLayerMat, - [df.tiletype_material.FEATURE] = GetFeatureMat, - [df.tiletype_material.LAVA_STONE] = GetLavaStone, - [df.tiletype_material.MINERAL] = GetVeinMat, - [df.tiletype_material.FROZEN_LIQUID] = fixedMat("WATER:NONE"), - [df.tiletype_material.CONSTRUCTION] = GetConstructionMat, - [df.tiletype_material.GRASS_LIGHT] = GetLayerMat, - [df.tiletype_material.GRASS_DARK] = GetLayerMat, - [df.tiletype_material.GRASS_DRY] = GetLayerMat, - [df.tiletype_material.GRASS_DEAD] = GetLayerMat, - [df.tiletype_material.PLANT] = GetLayerMat, - [df.tiletype_material.CAMPFIRE] = GetLayerMat, - [df.tiletype_material.FIRE] = GetLayerMat, - [df.tiletype_material.ASHES] = GetLayerMat, - [df.tiletype_material.DRIFTWOOD] = GetLayerMat, - [df.tiletype_material.POOL] = GetLayerMat, - [df.tiletype_material.BROOK] = GetLayerMat, - [df.tiletype_material.ROOT] = GetLayerMat, -} - --- OnlyPlantMats is a matspec table to pass to GetTileMatSpec or GetTileTypeMat. This particular --- matspec table will return nil for any non-plant tile. Plant tiles return the plant material. -OnlyPlantMats = { - [df.tiletype_material.GRASS_LIGHT] = GetGrassMat, - [df.tiletype_material.GRASS_DARK] = GetGrassMat, - [df.tiletype_material.GRASS_DRY] = GetGrassMat, - [df.tiletype_material.GRASS_DEAD] = GetGrassMat, - [df.tiletype_material.PLANT] = GetShrubMat, - [df.tiletype_material.TREE] = GetTreeMat, - [df.tiletype_material.MUSHROOM] = GetTreeMat, -} - -- GetLayerMat returns the layer material for the given tile. -- AFAIK this will never return nil. function GetLayerMat(x, y, z) @@ -349,6 +264,73 @@ function GetFeatureMat(x, y, z) return nil end +-- BasicMats is a matspec table to pass to GetTileMatSpec or GetTileTypeMat. This particular +-- matspec table covers the common case of returning plant materials for plant tiles and other +-- materials for the remaining tiles. +BasicMats = { + [df.tiletype_material.AIR] = nil, -- Empty + [df.tiletype_material.SOIL] = GetLayerMat, + [df.tiletype_material.STONE] = GetLayerMat, + [df.tiletype_material.FEATURE] = GetFeatureMat, + [df.tiletype_material.LAVA_STONE] = GetLavaStone, + [df.tiletype_material.MINERAL] = GetVeinMat, + [df.tiletype_material.FROZEN_LIQUID] = fixedMat("WATER:NONE"), + [df.tiletype_material.CONSTRUCTION] = GetConstructionMat, + [df.tiletype_material.GRASS_LIGHT] = GetGrassMat, + [df.tiletype_material.GRASS_DARK] = GetGrassMat, + [df.tiletype_material.GRASS_DRY] = GetGrassMat, + [df.tiletype_material.GRASS_DEAD] = GetGrassMat, + [df.tiletype_material.PLANT] = GetShrubMat, + [df.tiletype_material.HFS] = nil, -- Eerie Glowing Pit + [df.tiletype_material.CAMPFIRE] = GetLayerMat, + [df.tiletype_material.FIRE] = GetLayerMat, + [df.tiletype_material.ASHES] = GetLayerMat, + [df.tiletype_material.MAGMA] = nil, -- SMR + [df.tiletype_material.DRIFTWOOD] = GetLayerMat, + [df.tiletype_material.POOL] = GetLayerMat, + [df.tiletype_material.BROOK] = GetLayerMat, + [df.tiletype_material.ROOT] = GetLayerMat, + [df.tiletype_material.TREE] = GetTreeMat, + [df.tiletype_material.MUSHROOM] = GetTreeMat, + [df.tiletype_material.UNDERWORLD_GATE] = nil, -- I guess this is for the gates found in vaults? +} + +-- NoPlantMats is a matspec table to pass to GetTileMatSpec or GetTileTypeMat. This particular +-- matspec table will ignore plants, returning layer materials (or nil for trees) instead. +NoPlantMats = { + [df.tiletype_material.SOIL] = GetLayerMat, + [df.tiletype_material.STONE] = GetLayerMat, + [df.tiletype_material.FEATURE] = GetFeatureMat, + [df.tiletype_material.LAVA_STONE] = GetLavaStone, + [df.tiletype_material.MINERAL] = GetVeinMat, + [df.tiletype_material.FROZEN_LIQUID] = fixedMat("WATER:NONE"), + [df.tiletype_material.CONSTRUCTION] = GetConstructionMat, + [df.tiletype_material.GRASS_LIGHT] = GetLayerMat, + [df.tiletype_material.GRASS_DARK] = GetLayerMat, + [df.tiletype_material.GRASS_DRY] = GetLayerMat, + [df.tiletype_material.GRASS_DEAD] = GetLayerMat, + [df.tiletype_material.PLANT] = GetLayerMat, + [df.tiletype_material.CAMPFIRE] = GetLayerMat, + [df.tiletype_material.FIRE] = GetLayerMat, + [df.tiletype_material.ASHES] = GetLayerMat, + [df.tiletype_material.DRIFTWOOD] = GetLayerMat, + [df.tiletype_material.POOL] = GetLayerMat, + [df.tiletype_material.BROOK] = GetLayerMat, + [df.tiletype_material.ROOT] = GetLayerMat, +} + +-- OnlyPlantMats is a matspec table to pass to GetTileMatSpec or GetTileTypeMat. This particular +-- matspec table will return nil for any non-plant tile. Plant tiles return the plant material. +OnlyPlantMats = { + [df.tiletype_material.GRASS_LIGHT] = GetGrassMat, + [df.tiletype_material.GRASS_DARK] = GetGrassMat, + [df.tiletype_material.GRASS_DRY] = GetGrassMat, + [df.tiletype_material.GRASS_DEAD] = GetGrassMat, + [df.tiletype_material.PLANT] = GetShrubMat, + [df.tiletype_material.TREE] = GetTreeMat, + [df.tiletype_material.MUSHROOM] = GetTreeMat, +} + -- GetTileMat will return the material of the specified tile as determined by its tile type and the -- world geology data, etc. -- The returned material should exactly match the material reported by DF except in cases where is From 9163728b99f40bd5b89577d29d735eaa20fe0def Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 21 Jun 2022 12:09:12 -0700 Subject: [PATCH 002/111] hide blueprints that should be hidden, update help --- data/blueprints/library/dreamfort.csv | 57 +++++++++++++-------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/data/blueprints/library/dreamfort.csv b/data/blueprints/library/dreamfort.csv index 296e6c88b..a00a00e9e 100644 --- a/data/blueprints/library/dreamfort.csv +++ b/data/blueprints/library/dreamfort.csv @@ -74,12 +74,12 @@ quickfort run library/dreamfort.csv -n /farming3,# Run when furniture has been p quickfort run library/dreamfort.csv -n /industry2,# Run when the industry level has been dug out. prioritize ConstructBuilding,# To get those workshops up and running ASAP. You may have to run this several times as the materials for the building construction jobs become ready. quickfort run library/dreamfort.csv -n /surface4,"# Run after the walls and floors are built on the surface. Even if /surface3 is finished before you run /industry2, though, wait until after /industry2 to run this blueprint so that surface walls, floors, and roofing don't prevent your workshops from being built (due to lack of blocks)." -"quickfort run,orders library/dreamfort.csv -n /services2",# Run when the services levels have been dug out. Feel free to remove the orders for the ropes if you already brought them with you. +"quickfort orders,run library/dreamfort.csv -n /services2",# Run when the services levels have been dug out. Feel free to remove the orders for the ropes if you already brought them with you. orders import basic,"# Run after the first migration wave, so you have dorfs to do all the basic tasks. Note that this is the ""orders"" plugin, not the ""quickfort orders"" command." -"quickfort run,orders library/dreamfort.csv -n /surface5","# Run when all marked trees on the surface are chopped down and walls and floors have been constructed, including the roof section over the future barracks." +"quickfort orders,run library/dreamfort.csv -n /surface5","# Run when all marked trees on the surface are chopped down and walls and floors have been constructed, including the roof section over the future barracks." prioritize ConstructBuilding,# Run when you see the bridges ready to be built so the busy masons come and build them. -"quickfort run,orders library/dreamfort.csv -n /surface6",# Run when at least the beehives and weapon rack are constructed and you have linked all levers to their respective bridges. -"quickfort run,orders library/dreamfort.csv -n /surface7",# Run after the surface walls are completed and any marked trees are chopped down. +"quickfort orders,run library/dreamfort.csv -n /surface6",# Run when at least the beehives and weapon rack are constructed and you have linked all levers to their respective bridges. +"quickfort orders,run library/dreamfort.csv -n /surface7",# Run after the surface walls are completed and any marked trees are chopped down. "" -- Plumbing -- "This is a good time to fill your well cisterns, either with a bucket brigade or by routing water from a freshwater stream or an aquifer (see the library/aquifer_tap.csv blueprint for help with this)." @@ -87,23 +87,23 @@ prioritize ConstructBuilding,# Run when you see the bridges ready to be built so "" -- Mature fort (third migration wave onward) -- orders import furnace,# Automated production of basic furnace-related items. Don't forget to create a sand collection zone (or remove the sand- and glass-related orders if you have no sand). -"quickfort run,orders library/dreamfort.csv -n /suites2",# Run when the suites level has been dug out. -"quickfort run,orders library/dreamfort.csv -n /surface8","# Run if/when you need longer trap corridors on the surface for larger sieges, anytime after you run /surface7." -"quickfort run,orders library/dreamfort.csv -n /apartments2",# Run when the first apartment level has been dug out. -"quickfort run,orders library/dreamfort.csv -n /services3","# Run after the dining table and chair, weapon rack, and archery targets have been constructed. Also wait until after you complete /surface7, though, because surface defenses are more important than a grand dining hall." -"quickfort run,orders library/dreamfort.csv -n /guildhall2",# Run when the guildhall level has been dug out. -"quickfort run,orders library/dreamfort.csv -n ""/guildhall3, /guildhall4""",# Optionally run after /guildhall2 to build default furnishings and declare a library and temple. -"quickfort run,orders library/dreamfort.csv -n /apartments3",# Run when all beds have been constructed on the first apartments level. -"quickfort run,orders library/dreamfort.csv -n /farming4",# Run once you have a cache of potash. +"quickfort orders,run library/dreamfort.csv -n /suites2",# Run when the suites level has been dug out. +"quickfort orders,run library/dreamfort.csv -n /surface8","# Run if/when you need longer trap corridors on the surface for larger sieges, anytime after you run /surface7." +"quickfort orders,run library/dreamfort.csv -n /apartments2",# Run when the first apartment level has been dug out. +"quickfort orders,run library/dreamfort.csv -n /services3","# Run after the dining table and chair, weapon rack, and archery targets have been constructed. Also wait until after you complete /surface7, though, because surface defenses are more important than a grand dining hall." +"quickfort orders,run library/dreamfort.csv -n /guildhall2",# Run when the guildhall level has been dug out. +"quickfort orders,run library/dreamfort.csv -n ""/guildhall3, /guildhall4""",# Optionally run after /guildhall2 to build default furnishings and declare a library and temple. +"quickfort orders,run library/dreamfort.csv -n /apartments3",# Run when all beds have been constructed on the first apartments level. +"quickfort orders,run library/dreamfort.csv -n /farming4",# Run once you have a cache of potash. orders import military,# Automated production of military equipment. Turn on automelt in the meltables piles on the industry level to automatically upgrade all metal military equipment to masterwork quality. These orders are optional if you are not using a military. orders import smelting,# Automated production of all types of metal bars. -"quickfort run,orders library/dreamfort.csv -n /services4","# Run when you need a jail, anytime after the restraints are placed from /services3." +"quickfort orders,run library/dreamfort.csv -n /services4","# Run when you need a jail, anytime after the restraints are placed from /services3." orders import rockstock,# Maintains a small stock of all types of rock furniture. orders import glassstock,# Maintains a small stock of all types of glass furniture and parts (only import if you have sand). "" -- Repeat for each remaining apartments level as needed -- -"quickfort run,orders library/dreamfort.csv -n /apartments2",# Run when the apartment level has been dug out. -"quickfort run,orders library/dreamfort.csv -n /apartments3",# Run when all beds have been constructed. +"quickfort orders,run library/dreamfort.csv -n /apartments2",# Run when the apartment level has been dug out. +"quickfort orders,run library/dreamfort.csv -n /apartments3",# Run when all beds have been constructed. burial -pets,# Run once the coffins are placed to set them to allow for burial. See this checklist online at https://docs.google.com/spreadsheets/d/13PVZ2h3Mm3x_G1OXQvwKd7oIR2lK4A1Ahf6Om1kFigw/edit#gid=1459509569 @@ -232,6 +232,7 @@ https://drive.google.com/file/d/1Et42JTzeYK23iI5wrPMsFJ7lUXwVBQob/view?usp=shari [PET:2:BIRD_GOOSE:FEMALE:STANDARD] [PET:2:BIRD_GOOSE:MALE:STANDARD] #meta label(all_orders) hidden() references all blueprints that generate orders; for testing only +/surface1 /surface2 /surface3 /surface4 @@ -239,27 +240,25 @@ https://drive.google.com/file/d/1Et42JTzeYK23iI5wrPMsFJ7lUXwVBQob/view?usp=shari /surface6 /surface7 /surface8 +/farming1 /farming2 /farming3 /farming4 +/industry1 /industry2 +/services1 /services2 /services3 /services4 +/guildhall1 /guildhall2 +/guildhall3 +/guildhall4 +/suites1 /suites2 -/apartments2 -/apartments2 -/apartments2 -/apartments2 -/apartments2 -/apartments2 -/apartments3 -/apartments3 -/apartments3 -/apartments3 -/apartments3 -/apartments3 +/apartments1 repeat(>5) +/apartments2 repeat(>5) +/apartments3 repeat(>5) #notes label(surface_help) Sets up a protected entrance to your fort in a flat area on the surface. Screenshot: https://drive.google.com/file/d/1YL_vQJLB2YnUEFrAg9y3HEdFq3Wpw9WP @@ -2382,7 +2381,7 @@ query_jail/services_query_jail ,`,`,`,`,`,,`,`,`,`,` ,`,`,`,`,`,,`,`,`,`,` -"#query label(services_query_dining) start(18; 18) message(The tavern is restricted to residents only by default. If you'd like your tavern to attract vistors, please go to the (l)ocation menu and change the restriction.) set up dining room/tavern and barracks" +"#query label(services_query_dining) start(18; 18) hidden() message(The tavern is restricted to residents only by default. If you'd like your tavern to attract vistors, please go to the (l)ocation menu and change the restriction.) set up dining room/tavern and barracks" ,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` ,`,`,`,,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,` @@ -2418,7 +2417,7 @@ query_jail/services_query_jail ,`,`,`,`,`,,`,`,`,`,` ,`,`,`,`,`,,`,`,`,`,` -#query label(services_query_rented_rooms) start(18; 18) attach rented rooms to tavern +#query label(services_query_rented_rooms) start(18; 18) hidden() attach rented rooms to tavern ,r&l-&,r&l-&,r&l-&,,r&l-&,r&l-&,r&l-&,,r&l-&,r&l-&,r&l-&,,`,`,`,,`,,`,`,` ,`,`,`,,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,` From ba629b8e0a022e8891d7177ebc58f53cc923244b Mon Sep 17 00:00:00 2001 From: Myk Date: Tue, 21 Jun 2022 16:38:04 -0700 Subject: [PATCH 003/111] manually handle DestroyBuilding jobs (#2209) * don't delete general refs from jobs that we cancel though we still disconnect the refs if we can * get job remove working in all cases we apparently need to manually handle DestroyBuilding jobs everything else we should let cancel_job handle * update changelog --- docs/changelog.txt | 1 + library/modules/Job.cpp | 31 ++++++++++++++----------------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 35f344adb..6499a1fb5 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -38,6 +38,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## New Tweaks ## Fixes +- ``job.removeJob()``: ensure jobs are removed from the world list when they are canceled ## Misc Improvements diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index 749be199e..ff158caa0 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -360,27 +360,24 @@ bool DFHack::Job::removeJob(df::job* job) { using df::global::world; CHECK_NULL_POINTER(job); - // cancel_job below does not clean up refs, so we have to do that first + // cancel_job below does not clean up all refs, so we have to do some work - // clean up general refs - for (auto genRef : job->general_refs) { - if (!genRef) continue; - - // disconnectJobGeneralRef only handles buildings and units - if (genRef->getType() != general_ref_type::BUILDING_HOLDER && - genRef->getType() != general_ref_type::UNIT_WORKER) - return false; - } + // manually handle DESTROY_BUILDING jobs (cancel_job doesn't handle them) + if (job->job_type == df::job_type::DestroyBuilding) { + for (auto &genRef : job->general_refs) { + disconnectJobGeneralRef(job, genRef); + if (genRef) delete genRef; + } + job->general_refs.resize(0); - for (auto genRef : job->general_refs) { - // this should always succeed because of the check in the preceding loop - bool success = disconnectJobGeneralRef(job, genRef); - assert(success); (void)success; - if (genRef) delete genRef; + // remove the job from the world + job->list_link->prev->next = job->list_link->next; + delete job->list_link; + delete job; + return true; } - job->general_refs.resize(0); - // clean up item refs + // clean up item refs and delete them for (auto &item_ref : job->items) { disconnectJobItem(job, item_ref); if (item_ref) delete item_ref; From 234fcb8fe32f84aadfefc13266020c276f9e70e0 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 22 Jun 2022 07:17:32 +0000 Subject: [PATCH 004/111] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 592f6bd41..90abe4a64 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 592f6bd41b4ad90655a09d1939da589e6c421be6 +Subproject commit 90abe4a64d1babea491a113b487df73aecc1e3ff From 280737365687a810da7d4d465e2054ed472624ad Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 22 Jun 2022 19:02:58 +0000 Subject: [PATCH 005/111] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 90abe4a64..dab8b2246 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 90abe4a64d1babea491a113b487df73aecc1e3ff +Subproject commit dab8b22464cc81c7bc59e3e0f19ea3999676ffdf From b0bff47f0377f8845425e92b9c42ddde056e712c Mon Sep 17 00:00:00 2001 From: Simon Lees Date: Thu, 23 Jun 2022 22:19:05 +0930 Subject: [PATCH 006/111] Fix use after free's This was detected by gcc and causing the build to fail on my Linux machine --- plugins/orders.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/orders.cpp b/plugins/orders.cpp index c984f5060..85ba62486 100644 --- a/plugins/orders.cpp +++ b/plugins/orders.cpp @@ -540,10 +540,10 @@ static command_result orders_import(color_ostream &out, Json::Value &orders) } else { - delete order; - out << COLOR_LIGHTRED << "Invalid item subtype for imported manager order: " << enum_item_key(order->item_type) << ":" << it["item_subtype"].asString() << std::endl; + delete order; + return CR_FAILURE; } } @@ -716,10 +716,10 @@ static command_result orders_import(color_ostream &out, Json::Value &orders) } else { - delete condition; - out << COLOR_YELLOW << "Invalid item condition item subtype for imported manager order: " << enum_item_key(condition->item_type) << ":" << it2["item_subtype"].asString() << std::endl; + delete condition; + continue; } } From 9788a8a22a344e0b1c50cde410b7c57ebad44496 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Wed, 29 Jun 2022 01:27:18 +0200 Subject: [PATCH 007/111] Add default selection handler to `materials.ItemTraitsDialog` (#2211) * add forward compatibility for future `job_item_flags` * add default selection handler to `materials.ItemTraitsDialog` * add a call to `error()` in 'unknown'-branch inside `setTrait` * add `ItemTraitsDialog` improvement description to changelog.txt --- docs/changelog.txt | 2 + library/lua/gui/materials.lua | 96 ++++++++++++++++++++++++++++++++++- 2 files changed, 97 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 6499a1fb5..2d5a5cf64 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -42,6 +42,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements +- ``materials.ItemTraitsDialog``: added a default ``on_select``-handler which toggles the traits. + ## Documentation ## API diff --git a/library/lua/gui/materials.lua b/library/lua/gui/materials.lua index c9eaaf38d..c81161bc5 100644 --- a/library/lua/gui/materials.lua +++ b/library/lua/gui/materials.lua @@ -345,7 +345,8 @@ end function ItemTraitsDialog(args) local job_item_flags_map = {} - for i = 1, 3 do + for i = 1, 5 do + if not df['job_item_flags'..i] then break end for _, f in ipairs(df['job_item_flags'..i]) do if f then job_item_flags_map[f] = 'flags'..i @@ -600,6 +601,99 @@ function ItemTraitsDialog(args) args.on_select = function(idx, obj) return cb(obj) end + else + local function toggleFlag(obj, ffield, flag) + local job_item = obj.job_item + job_item[ffield][flag] = not job_item[ffield][flag] + end + + local function toggleToolUse(obj, tool_use) + local job_item = obj.job_item + tool_use = df.tool_uses[tool_use] + if job_item.has_tool_use == tool_use then + job_item.has_tool_use = df.tool_uses.NONE + else + job_item.has_tool_use = tool_use + end + end + + local function toggleMetalOre(obj, ore_ix) + local job_item = obj.job_item + if job_item.metal_ore == ore_ix then + job_item.metal_ore = -1 + else + job_item.metal_ore = ore_ix + end + end + + function toggleReactionClass(obj, reaction_class) + local job_item = obj.job_item + if job_item.reaction_class == reaction_class then + job_item.reaction_class = '' + else + job_item.reaction_class = reaction_class + end + end + + local function toggleProductMaterial(obj, product_materials) + local job_item = obj.job_item + if job_item.has_material_reaction_product == product_materials then + job_item.has_material_reaction_product = '' + else + job_item.has_material_reaction_product = product_materials + end + end + + local function unsetFlags(obj) + local job_item = obj.job_item + for flag, ffield in pairs(job_item_flags_map) do + if job_item[ffield][flag] then + toggleFlag(obj, ffield, flag) + end + end + end + + local function setTrait(obj, sel) + if sel.ffield then + --print('toggle flag', sel.ffield, sel.flag) + toggleFlag(obj, sel.ffield, sel.flag) + elseif sel.reset_flags then + --print('reset every flag') + unsetFlags(obj) + elseif sel.tool_use then + --print('toggle tool_use', sel.tool_use) + toggleToolUse(obj, sel.tool_use) + elseif sel.ore_ix then + --print('toggle ore', sel.ore_ix) + toggleMetalOre(obj, sel.ore_ix) + elseif sel.reaction_class then + --print('toggle reaction class', sel.reaction_class) + toggleReactionClass(obj, sel.reaction_class) + elseif sel.product_materials then + --print('toggle product materials', sel.product_materials) + toggleProductMaterial(obj, sel.product_materials) + elseif sel.reset_all_traits then + --print('reset every trait') + -- flags + unsetFlags(obj) + -- tool use + toggleToolUse(obj, 'NONE') + -- metal ore + toggleMetalOre(obj, -1) + -- reaction class + toggleReactionClass(obj, '') + -- producing + toggleProductMaterial(obj, '') + else + print('unknown sel') + printall(sel) + error('Selected entry in ItemTraitsDialog was of unknown type') + end + end + + args.on_select = function(idx, choice) + setTrait(args, choice) + end end return dlg.ListBox(args) From 12958e15c6ae80e88191ccf40cc7e0c07e83f546 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 29 Jun 2022 07:17:24 +0000 Subject: [PATCH 008/111] Auto-update submodules plugins/stonesense: master --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index 841803ca6..8ddba1944 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 841803ca6f173f4d97eea376b4dcd512336e35e2 +Subproject commit 8ddba1944274d0f22fef4bc6cc52229818e580bb From 8a605e190305f80603a99ae77860e080cb9e5626 Mon Sep 17 00:00:00 2001 From: Quietust Date: Wed, 29 Jun 2022 08:27:37 -0600 Subject: [PATCH 009/111] The great de-anon-ification --- library/xml | 2 +- plugins/building-hacks.cpp | 12 ++++++------ plugins/orders.cpp | 8 ++++---- scripts | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/library/xml b/library/xml index ef5c180ff..e803099f5 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit ef5c180ff220e53ec6b51e01ca3594a5780b8b4b +Subproject commit e803099f5d983055e4b5b00a6baf4bcc91c6f25d diff --git a/plugins/building-hacks.cpp b/plugins/building-hacks.cpp index 2c75d4622..635d99261 100644 --- a/plugins/building-hacks.cpp +++ b/plugins/building-hacks.cpp @@ -88,8 +88,8 @@ struct work_hook : df::building_workshopst{ df::general_ref_creaturest* ref = static_cast(DFHack::Buildings::getGeneralRef(this, general_ref_type::CREATURE)); if (ref) { - info->produced = ref->anon_1; - info->consumed = ref->anon_2; + info->produced = ref->unk_1; + info->consumed = ref->unk_2; return true; } else @@ -118,14 +118,14 @@ struct work_hook : df::building_workshopst{ df::general_ref_creaturest* ref = static_cast(DFHack::Buildings::getGeneralRef(this, general_ref_type::CREATURE)); if (ref) { - ref->anon_1 = produced; - ref->anon_2 = consumed; + ref->unk_1 = produced; + ref->unk_2 = consumed; } else { ref = df::allocate(); - ref->anon_1 = produced; - ref->anon_2 = consumed; + ref->unk_1 = produced; + ref->unk_2 = consumed; general_refs.push_back(ref); } } diff --git a/plugins/orders.cpp b/plugins/orders.cpp index 85ba62486..fb678ca6d 100644 --- a/plugins/orders.cpp +++ b/plugins/orders.cpp @@ -467,7 +467,7 @@ static command_result orders_export_command(color_ostream & out, const std::stri condition["order"] = it2->order_id; condition["condition"] = enum_item_key(it2->condition); - // TODO: anon_1 + // TODO: unk_1 conditions.append(condition); } @@ -475,7 +475,7 @@ static command_result orders_export_command(color_ostream & out, const std::stri order["order_conditions"] = conditions; } - // TODO: anon_1 + // TODO: items orders.append(order); } @@ -873,13 +873,13 @@ static command_result orders_import(color_ostream &out, Json::Value &orders) continue; } - // TODO: anon_1 + // TODO: unk_1 order->order_conditions.push_back(condition); } } - // TODO: anon_1 + // TODO: items world->manager_orders.push_back(order); } diff --git a/scripts b/scripts index dab8b2246..274b81ded 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit dab8b22464cc81c7bc59e3e0f19ea3999676ffdf +Subproject commit 274b81ded9c72bff23eca4f0c2a78cb6cb900f8e From 0aa7ec877e4201af76b429438037fb5fc8a501c2 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 29 Jun 2022 15:17:13 +0000 Subject: [PATCH 010/111] Auto-update submodules library/xml: master scripts: master --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index e803099f5..8d15b2d07 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit e803099f5d983055e4b5b00a6baf4bcc91c6f25d +Subproject commit 8d15b2d07c18cc9dfbd550b32ad43e46b2d4014d diff --git a/scripts b/scripts index 274b81ded..7b92cec52 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 274b81ded9c72bff23eca4f0c2a78cb6cb900f8e +Subproject commit 7b92cec5246f97f278e4d608899278d2cafc0992 From 8bb047fcc6a67aa0c0faef880aa1d7dd2ed758a0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 28 May 2022 16:55:20 -0400 Subject: [PATCH 011/111] Remove Notes module Only used in a devel plugin that prints notes, and can be easily replaced with `ui.waypoints.points` --- docs/changelog.txt | 2 + library/CMakeLists.txt | 3 - library/Core.cpp | 6 - library/include/Core.h | 9 - library/include/ModuleFactory.h | 1 - library/include/modules/Notes.h | 65 ------- library/include/modules/Windows.h | 270 ------------------------------ library/modules/Notes.cpp | 53 ------ library/modules/Windows.cpp | 118 ------------- plugins/devel/CMakeLists.txt | 1 - plugins/devel/notes.cpp | 72 -------- 11 files changed, 2 insertions(+), 598 deletions(-) delete mode 100644 library/include/modules/Notes.h delete mode 100644 library/include/modules/Windows.h delete mode 100644 library/modules/Notes.cpp delete mode 100644 library/modules/Windows.cpp delete mode 100644 plugins/devel/notes.cpp diff --git a/docs/changelog.txt b/docs/changelog.txt index 2d5a5cf64..290c4ec3b 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -47,6 +47,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Documentation ## API +- Removed ``Notes`` module (C++-only). Access ``ui.waypoints.points`` directly instead. +- Removed ``Windows`` module (C++-only) - unused. ## Lua - ``tile-material``: fix the order of declarations. The ``GetTileMat`` function now returns the material as intended (always returned nil before). Also changed the license info, with permission of the original author. diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 9e7bf8590..440281cf7 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -138,7 +138,6 @@ set(MODULE_HEADERS include/modules/MapCache.h include/modules/Maps.h include/modules/Materials.h - include/modules/Notes.h include/modules/Once.h include/modules/Persistence.h include/modules/Random.h @@ -165,7 +164,6 @@ set(MODULE_SOURCES modules/MapCache.cpp modules/Maps.cpp modules/Materials.cpp - modules/Notes.cpp modules/Once.cpp modules/Persistence.cpp modules/Random.cpp @@ -173,7 +171,6 @@ set(MODULE_SOURCES modules/Screen.cpp modules/Translation.cpp modules/Units.cpp - modules/Windows.cpp modules/World.cpp ) diff --git a/library/Core.cpp b/library/Core.cpp index 10f5057f3..55aa9dc55 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -54,7 +54,6 @@ using namespace std; #include "modules/Gui.h" #include "modules/World.h" #include "modules/Graphic.h" -#include "modules/Windows.h" #include "modules/Persistence.h" #include "RemoteServer.h" #include "RemoteTools.h" @@ -1564,7 +1563,6 @@ Core::Core() : last_local_map_ptr = NULL; last_pause_state = false; top_viewscreen = NULL; - screen_window = NULL; color_ostream::log_errors_to_stderr = true; @@ -1835,8 +1833,6 @@ bool Core::Init() cerr << "Starting DF input capture thread.\n"; // set up hotkey capture d->hotkeythread = std::thread(fHKthread, (void *) temp); - screen_window = new Windows::top_level_window(); - screen_window->addChild(new Windows::dfhack_dummy(5,10)); started = true; modstate = 0; @@ -1984,7 +1980,6 @@ int Core::TileUpdate() { if(!started) return false; - screen_window->paint(); return true; } @@ -2922,5 +2917,4 @@ TYPE * Core::get##TYPE() \ } MODULE_GETTER(Materials); -MODULE_GETTER(Notes); MODULE_GETTER(Graphic); diff --git a/library/include/Core.h b/library/include/Core.h index 1648ae113..f6acd9425 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -58,7 +58,6 @@ namespace DFHack class Process; class Module; class Materials; - class Notes; struct VersionInfo; class VersionInfoFactory; class PluginManager; @@ -69,10 +68,6 @@ namespace DFHack namespace Lua { namespace Core { DFHACK_EXPORT void Reset(color_ostream &out, const char *where); } } - namespace Windows - { - class df_window; - } namespace Screen { @@ -146,8 +141,6 @@ namespace DFHack /// get the materials module Materials * getMaterials(); - /// get the notes module - Notes * getNotes(); /// get the graphic module Graphic * getGraphic(); /// sets the current hotkey command @@ -193,7 +186,6 @@ namespace DFHack std::unique_ptr p; std::shared_ptr vinfo; - DFHack::Windows::df_window * screen_window; static void print(const char *format, ...) Wformat(printf,1,2); static void printerr(const char *format, ...) Wformat(printf,1,2); @@ -242,7 +234,6 @@ namespace DFHack struct { Materials * pMaterials; - Notes * pNotes; Graphic * pGraphic; } s_mods; std::vector> allModules; diff --git a/library/include/ModuleFactory.h b/library/include/ModuleFactory.h index 5c3c149a2..c99e7b328 100644 --- a/library/include/ModuleFactory.h +++ b/library/include/ModuleFactory.h @@ -33,7 +33,6 @@ namespace DFHack { class Module; std::unique_ptr createMaterials(); - std::unique_ptr createNotes(); std::unique_ptr createGraphic(); } #endif diff --git a/library/include/modules/Notes.h b/library/include/modules/Notes.h deleted file mode 100644 index 14bb9db84..000000000 --- a/library/include/modules/Notes.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once -#ifndef CL_MOD_NOTES -#define CL_MOD_NOTES -/** - * \defgroup grp_notes In game notes (and routes) - * @ingroup grp_notes - */ -#include "Export.h" -#include "Module.h" - -#include -#include - -#ifdef __cplusplus -namespace DFHack -{ -#endif - /** - * Game's structure for a note. - * \ingroup grp_notes - */ - struct t_note - { - // First note created has id 0, second has id 1, etc. Not affected - // by lower id notes being deleted. - uint32_t id; // 0 - uint8_t symbol; // 4 - uint8_t unk1; // alignment padding? - uint16_t foreground; // 6 - uint16_t background; // 8 - uint16_t unk2; // alignment padding? - - std::string name; // C - std::string text; // 10 - - uint16_t x; // 14 - uint16_t y; // 16 - uint16_t z; // 18 - - // Is there more? - }; - -#ifdef __cplusplus - - /** - * The notes module - allows reading DF in-game notes - * \ingroup grp_modules - * \ingroup grp_notes - */ - class DFHACK_EXPORT Notes : public Module - { - public: - Notes(); - ~Notes(){}; - bool Finish() - { - return true; - } - std::vector* notes; - }; - -} -#endif // __cplusplus - -#endif diff --git a/library/include/modules/Windows.h b/library/include/modules/Windows.h deleted file mode 100644 index f9b282cf3..000000000 --- a/library/include/modules/Windows.h +++ /dev/null @@ -1,270 +0,0 @@ -/* -https://github.com/peterix/dfhack -Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -#pragma once -#include "Export.h" -#include - -namespace DFHack -{ -namespace Windows -{ - /* - * DF window stuffs - */ - enum df_color - { - black, - blue, - green, - cyan, - red, - magenta, - brown, - lgray, - dgray, - lblue, - lgreen, - lcyan, - lred, - lmagenta, - yellow, - white - // maybe add transparency? - }; - - // The tile format DF uses internally - struct df_screentile - { - uint8_t symbol; - uint8_t foreground; ///< df_color - uint8_t background; ///< df_color - uint8_t bright; - }; - - - // our silly painter things and window things follow. - class df_window; - struct df_tilebuf - { - df_screentile * data; - unsigned int width; - unsigned int height; - }; - - DFHACK_EXPORT df_screentile *getScreenBuffer(); - - class DFHACK_EXPORT painter - { - friend class df_window; - public: - df_screentile* get(unsigned int x, unsigned int y) - { - if(x >= width || y >= height) - return 0; - return &buffer[x*height + y]; - }; - bool set(unsigned int x, unsigned int y, df_screentile tile ) - { - if(x >= width || y >= height) - return false; - buffer[x*height + y] = tile; - return true; - } - df_color foreground (df_color change = (df_color) -1) - { - if(change != -1) - current_foreground = change; - return current_foreground; - } - df_color background (df_color change = (df_color) -1) - { - if(change != -1) - current_background = change; - return current_background; - } - void bright (bool change) - { - current_bright = change; - } - bool bright () - { - return current_bright; - } - void printStr(std::string & str, bool wrap = false) - { - for ( auto iter = str.begin(); iter != str.end(); iter++) - { - auto elem = *iter; - if(cursor_y >= (int)height) - break; - if(wrap) - { - if(cursor_x >= (int)width) - cursor_x = wrap_column; - } - df_screentile & tile = buffer[cursor_x * height + cursor_y]; - tile.symbol = elem; - tile.foreground = current_foreground; - tile.background = current_background; - tile.bright = current_bright; - cursor_x++; - } - } - void set_wrap (int new_column) - { - wrap_column = new_column; - } - void gotoxy(unsigned int x, unsigned int y) - { - cursor_x = x; - cursor_y = y; - } - void reset() - { - cursor_x = 0; - cursor_y = 0; - current_background = black; - current_foreground = white; - current_bright = false; - wrap_column = 0; - } - private: - painter (df_window * orig, df_screentile * buf, unsigned int width, unsigned int height) - { - origin = orig; - this->width = width; - this->height = height; - this->buffer = buf; - reset(); - } - df_window* origin; - unsigned int width; - unsigned int height; - df_screentile* buffer; - // current paint cursor position - int cursor_x; - int cursor_y; - int wrap_column; - // current foreground color - df_color current_foreground; - // current background color - df_color current_background; - // make bright? - bool current_bright; - }; - - class DFHACK_EXPORT df_window - { - friend class painter; - public: - df_window(int x, int y, unsigned int width, unsigned int height); - virtual ~df_window(); - virtual bool move (int left_, int top_, unsigned int width_, unsigned int height_) = 0; - virtual void paint () = 0; - virtual painter * lock(); - bool unlock (painter * painter); - virtual bool addChild(df_window *); - virtual df_tilebuf getBuffer() = 0; - public: - df_screentile* buffer; - unsigned int width; - unsigned int height; - protected: - df_window * parent; - std::vector children; - int left; - int top; - // FIXME: FAKE - bool locked; - painter * current_painter; - }; - - class DFHACK_EXPORT top_level_window : public df_window - { - public: - top_level_window(); - virtual bool move (int left_, int top_, unsigned int width_, unsigned int height_); - virtual void paint (); - virtual painter * lock(); - virtual df_tilebuf getBuffer(); - }; - class DFHACK_EXPORT buffered_window : public df_window - { - public: - buffered_window(int x, int y, unsigned int width, unsigned int height):df_window(x,y,width, height) - { - buffer = new df_screentile[width*height]; - }; - virtual ~buffered_window() - { - delete buffer; - } - virtual void blit_to_parent () - { - df_tilebuf par = parent->getBuffer(); - for(unsigned xi = 0; xi < width; xi++) - { - for(unsigned yi = 0; yi < height; yi++) - { - unsigned parx = left + xi; - unsigned pary = top + yi; - if(pary >= par.height) continue; - if(parx >= par.width) continue; - par.data[parx * par.height + pary] = buffer[xi * height + yi]; - } - } - } - virtual df_tilebuf getBuffer() - { - df_tilebuf buf; - buf.data = buffer; - buf.width = width; - buf.height = height; - return buf; - }; - }; - class DFHACK_EXPORT dfhack_dummy : public buffered_window - { - public: - dfhack_dummy(int x, int y):buffered_window(x,y,6,1){}; - virtual bool move (int left_, int top_, unsigned int width_, unsigned int height_) - { - top = top_; - left = left_; - return true; - } - virtual void paint () - { - painter * p = lock(); - p->bright(true); - p->background(black); - p->foreground(white); - std::string dfhack = "DFHack"; - p->printStr(dfhack); - blit_to_parent(); - } - }; -} -} diff --git a/library/modules/Notes.cpp b/library/modules/Notes.cpp deleted file mode 100644 index 04fb59e4d..000000000 --- a/library/modules/Notes.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/* -www.sourceforge.net/projects/dfhack -Copyright (c) 2009 Petr Mrázek (peterix), Kenneth Ferland (Impaler[WrG]), dorf - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -#include "Internal.h" - -#include -#include -#include -using namespace std; - -#include "VersionInfo.h" -#include "Types.h" -#include "Error.h" -#include "MemAccess.h" -#include "MiscUtils.h" -#include "ModuleFactory.h" -#include "Core.h" -#include "modules/Notes.h" -#include -#include "df/ui.h" -using namespace DFHack; - -std::unique_ptr DFHack::createNotes() -{ - return dts::make_unique(); -} - -// FIXME: not even a wrapper now -Notes::Notes() -{ - notes = (std::vector*) &df::global::ui->waypoints.points; -} diff --git a/library/modules/Windows.cpp b/library/modules/Windows.cpp deleted file mode 100644 index af5368e50..000000000 --- a/library/modules/Windows.cpp +++ /dev/null @@ -1,118 +0,0 @@ -/* -https://github.com/peterix/dfhack -Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -#include "Export.h" -#include "Module.h" -#include "BitArray.h" -#include - -#include "DataDefs.h" -#include "df/init.h" -#include "df/ui.h" -#include -#include "modules/Windows.h" - -using namespace DFHack; -using df::global::gps; - -Windows::df_screentile *Windows::getScreenBuffer() -{ - if (!gps) return NULL; - return (df_screentile *) gps->screen; -} - -Windows::df_window::df_window(int x, int y, unsigned int width, unsigned int height) -:buffer(0), width(width), height(height), parent(0), left(x), top(y), current_painter(NULL) -{ - buffer = 0; -}; -Windows::df_window::~df_window() -{ - for(auto iter = children.begin();iter != children.end();iter++) - { - delete *iter; - } - children.clear(); -}; -Windows::painter * Windows::df_window::lock() -{ - locked = true; - current_painter = new Windows::painter(this,buffer,width, height); - return current_painter; -}; - -bool Windows::df_window::addChild( df_window * child) -{ - children.push_back(child); - child->parent = this; - return true; -} - -bool Windows::df_window::unlock (painter * painter) -{ - if(current_painter == painter) - { - delete current_painter; - current_painter = 0; - locked = false; - return true; - } - return false; -} - -Windows::top_level_window::top_level_window() : df_window(0,0,gps ? gps->dimx : 80,gps ? gps->dimy : 25) -{ - buffer = 0; -} - -bool Windows::top_level_window::move (int left_, int top_, unsigned int width_, unsigned int height_) -{ - width = width_; - height = height_; - // what if we are painting already? Is that possible? - return true; -}; - -Windows::painter * Windows::top_level_window::lock() -{ - buffer = getScreenBuffer(); - return df_window::lock(); -} - -void Windows::top_level_window::paint () -{ - for(auto iter = children.begin();iter != children.end();iter++) - { - (*iter)->paint(); - } -}; - -Windows::df_tilebuf Windows::top_level_window::getBuffer() -{ - df_tilebuf buf; - buf.data = getScreenBuffer(); - buf.height = df::global::gps->dimy; - buf.width = df::global::gps->dimx; - return buf; -} diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 6d15f09dd..d1b84614f 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -13,7 +13,6 @@ dfhack_plugin(eventExample eventExample.cpp) dfhack_plugin(frozen frozen.cpp) dfhack_plugin(kittens kittens.cpp LINK_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) dfhack_plugin(memview memview.cpp memutils.cpp LINK_LIBRARIES lua) -dfhack_plugin(notes notes.cpp) dfhack_plugin(onceExample onceExample.cpp) dfhack_plugin(renderer-msg renderer-msg.cpp) dfhack_plugin(rprobe rprobe.cpp) diff --git a/plugins/devel/notes.cpp b/plugins/devel/notes.cpp deleted file mode 100644 index 33b768af7..000000000 --- a/plugins/devel/notes.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include "Core.h" -#include -#include -#include -#include -#include -#include - -using std::vector; -using std::string; -using namespace DFHack; - -command_result df_notes (color_ostream &out, vector & parameters); - -DFHACK_PLUGIN("notes"); - -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) -{ - commands.push_back(PluginCommand("dumpnotes", - "Dumps in-game notes", - df_notes)); - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - return CR_OK; -} - -command_result df_notes (color_ostream &con, vector & parameters) -{ - CoreSuspender suspend; - - DFHack::Notes * note_mod = Core::getInstance().getNotes(); - std::vector* note_list = note_mod->notes; - - if (note_list == NULL) - { - con.printerr("Notes are not supported under this version of DF.\n"); - return CR_OK; - } - - if (note_list->empty()) - { - con << "There are no notes." << std::endl; - return CR_OK; - } - - - for (size_t i = 0; i < note_list->size(); i++) - { - t_note* note = (*note_list)[i]; - - con.print("Note %p at: %d/%d/%d\n",note, note->x, note->y, note->z); - con.print("Note id: %d\n", note->id); - con.print("Note symbol: '%c'\n", note->symbol); - - if (note->name.length() > 0) - con << "Note name: " << (note->name) << std::endl; - if (note->text.length() > 0) - con << "Note text: " << (note->text) << std::endl; - - if (note->unk1 != 0) - con.print("unk1: %x\n", note->unk1); - if (note->unk2 != 0) - con.print("unk2: %x\n", note->unk2); - - con << std::endl; - } - - return CR_OK; -} From 4c7caa2658a29c77841bb7fa401ce58e6b000ae8 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 4 Jun 2022 12:57:18 -0400 Subject: [PATCH 012/111] Remove unneeded dependencies on modules/Graphic.h --- docs/changelog.txt | 1 + library/CMakeLists.txt | 2 - library/Core.cpp | 1 - library/PlugLoad-windows.cpp | 1 - library/include/DFHack.h | 1 - library/include/Hooks.h | 1 - library/include/modules/Engravings.h | 63 ---------------------- library/modules/Engravings.cpp | 78 ---------------------------- 8 files changed, 1 insertion(+), 147 deletions(-) delete mode 100644 library/include/modules/Engravings.h delete mode 100644 library/modules/Engravings.cpp diff --git a/docs/changelog.txt b/docs/changelog.txt index 290c4ec3b..8f663d9bc 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -47,6 +47,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Documentation ## API +- Removed ``Engravings`` module (C++-only). Access ``world.engravings`` directly instead. - Removed ``Notes`` module (C++-only). Access ``ui.waypoints.points`` directly instead. - Removed ``Windows`` module (C++-only) - unused. diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 440281cf7..c9e9cd844 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -126,7 +126,6 @@ set(MODULE_HEADERS include/modules/Burrows.h include/modules/Constructions.h include/modules/Designations.h - include/modules/Engravings.h include/modules/EventManager.h include/modules/Filesystem.h include/modules/Graphic.h @@ -153,7 +152,6 @@ set(MODULE_SOURCES modules/Burrows.cpp modules/Constructions.cpp modules/Designations.cpp - modules/Engravings.cpp modules/EventManager.cpp modules/Filesystem.cpp modules/Graphic.cpp diff --git a/library/Core.cpp b/library/Core.cpp index 55aa9dc55..cfb029d57 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -53,7 +53,6 @@ using namespace std; #include "modules/Filesystem.h" #include "modules/Gui.h" #include "modules/World.h" -#include "modules/Graphic.h" #include "modules/Persistence.h" #include "RemoteServer.h" #include "RemoteTools.h" diff --git a/library/PlugLoad-windows.cpp b/library/PlugLoad-windows.cpp index 96c2e900a..848d25f50 100644 --- a/library/PlugLoad-windows.cpp +++ b/library/PlugLoad-windows.cpp @@ -35,7 +35,6 @@ distribution. #include #include "tinythread.h" -#include "modules/Graphic.h" #include "../plugins/uicommon.h" /* diff --git a/library/include/DFHack.h b/library/include/DFHack.h index 8a094cf86..0a5183adc 100644 --- a/library/include/DFHack.h +++ b/library/include/DFHack.h @@ -54,7 +54,6 @@ distribution. // DFHack modules #include "modules/Buildings.h" -#include "modules/Engravings.h" #include "modules/Materials.h" #include "modules/Constructions.h" #include "modules/Units.h" diff --git a/library/include/Hooks.h b/library/include/Hooks.h index f5ef7079c..7a266e92c 100644 --- a/library/include/Hooks.h +++ b/library/include/Hooks.h @@ -47,7 +47,6 @@ namespace SDL // these functions are here because they call into DFHack::Core and therefore need to // be declared as friend functions/known #ifdef _DARWIN -#include "modules/Graphic.h" DFhackCExport int DFH_SDL_NumJoysticks(void); DFhackCExport void DFH_SDL_Quit(void); DFhackCExport int DFH_SDL_PollEvent(SDL::Event* event); diff --git a/library/include/modules/Engravings.h b/library/include/modules/Engravings.h deleted file mode 100644 index bf30c62a8..000000000 --- a/library/include/modules/Engravings.h +++ /dev/null @@ -1,63 +0,0 @@ -/* -https://github.com/peterix/dfhack -Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -#pragma once -#ifndef CL_MOD_ENGRAVINGS -#define CL_MOD_ENGRAVINGS -/* -* DF engravings -*/ -#include "Export.h" -#include "DataDefs.h" -#include "df/engraving.h" -/** - * \defgroup grp_engraving Engraving module parts - * @ingroup grp_modules - */ -namespace DFHack -{ -namespace Engravings -{ -// "Simplified" copy of engraving -struct t_engraving { - int32_t artist; - int32_t masterpiece_event; - int32_t skill_rating; - df::coord pos; - df::engraving_flags flags; - int8_t tile; - int32_t art_id; - int16_t art_subid; - df::item_quality quality; - // Pointer to original object, in case you want to modify it - df::engraving *origin; -}; - -DFHACK_EXPORT bool isValid(); -DFHACK_EXPORT uint32_t getCount(); -DFHACK_EXPORT bool copyEngraving (const int32_t index, t_engraving &out); -DFHACK_EXPORT df::engraving * getEngraving (const int32_t index); -} -} -#endif diff --git a/library/modules/Engravings.cpp b/library/modules/Engravings.cpp deleted file mode 100644 index c2e0e6fce..000000000 --- a/library/modules/Engravings.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/* -https://github.com/peterix/dfhack -Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - - -#include "Internal.h" - -#include -#include -#include -using namespace std; - -#include "VersionInfo.h" -#include "MemAccess.h" -#include "Types.h" -#include "Core.h" - -#include "modules/Engravings.h" -#include "df/world.h" - -using namespace DFHack; -using df::global::world; - -bool Engravings::isValid() -{ - return (world != NULL); -} - -uint32_t Engravings::getCount() -{ - return world->engravings.size(); -} - -df::engraving * Engravings::getEngraving(int index) -{ - if (uint32_t(index) >= getCount()) - return NULL; - return world->engravings[index]; -} - -bool Engravings::copyEngraving(const int32_t index, t_engraving &out) -{ - if (uint32_t(index) >= getCount()) - return false; - - out.origin = world->engravings[index]; - - out.artist = out.origin->artist; - out.masterpiece_event = out.origin->masterpiece_event; - out.skill_rating = out.origin->skill_rating; - out.pos = out.origin->pos; - out.flags = out.origin->flags; - out.tile = out.origin->tile; - out.art_id = out.origin->art_id; - out.art_subid = out.origin->art_subid; - out.quality = out.origin->quality; - return true; -} From 1147add5207c730c2b162020347518144b451ddf Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 28 Jun 2022 23:44:34 -0400 Subject: [PATCH 013/111] Constructions module: remove some old/unused functions/types --- docs/changelog.txt | 1 + library/include/modules/Constructions.h | 16 ------------ library/modules/Constructions.cpp | 33 ------------------------- 3 files changed, 1 insertion(+), 49 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 8f663d9bc..a06629f62 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -50,6 +50,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - Removed ``Engravings`` module (C++-only). Access ``world.engravings`` directly instead. - Removed ``Notes`` module (C++-only). Access ``ui.waypoints.points`` directly instead. - Removed ``Windows`` module (C++-only) - unused. +- ``Constructions`` module (C++-only): removed ``t_construction``, ``isValid()``, ``getCount()``, ``getConstruction()``, and ``copyConstruction()``. Access ``world.constructions`` directly instead. ## Lua - ``tile-material``: fix the order of declarations. The ``GetTileMat`` function now returns the material as intended (always returned nil before). Also changed the license info, with permission of the original author. diff --git a/library/include/modules/Constructions.h b/library/include/modules/Constructions.h index 3831d4bb1..c7a1c048b 100644 --- a/library/include/modules/Constructions.h +++ b/library/include/modules/Constructions.h @@ -42,23 +42,7 @@ namespace DFHack { namespace Constructions { -// "Simplified" copy of construction -struct t_construction { - df::coord pos; - df::item_type item_type; - int16_t item_subtype; - int16_t mat_type; - int32_t mat_index; - df::construction_flags flags; - int16_t original_tile; - // Pointer to original object, in case you want to modify it - df::construction *origin; -}; -DFHACK_EXPORT bool isValid(); -DFHACK_EXPORT uint32_t getCount(); -DFHACK_EXPORT bool copyConstruction (const int32_t index, t_construction &out); -DFHACK_EXPORT df::construction * getConstruction (const int32_t index); DFHACK_EXPORT df::construction * findAtTile(df::coord pos); DFHACK_EXPORT bool designateNew(df::coord pos, df::construction_type type, diff --git a/library/modules/Constructions.cpp b/library/modules/Constructions.cpp index 9cec2eab9..5fc632500 100644 --- a/library/modules/Constructions.cpp +++ b/library/modules/Constructions.cpp @@ -51,22 +51,6 @@ using namespace DFHack; using namespace df::enums; using df::global::world; -bool Constructions::isValid() -{ - return (world != NULL); -} - -uint32_t Constructions::getCount() -{ - return world->constructions.size(); -} - -df::construction * Constructions::getConstruction(const int32_t index) -{ - if (uint32_t(index) >= getCount()) - return NULL; - return world->constructions[index]; -} df::construction * Constructions::findAtTile(df::coord pos) { @@ -77,23 +61,6 @@ df::construction * Constructions::findAtTile(df::coord pos) return NULL; } -bool Constructions::copyConstruction(const int32_t index, t_construction &out) -{ - if (uint32_t(index) >= getCount()) - return false; - - out.origin = world->constructions[index]; - - out.pos = out.origin->pos; - out.item_type = out.origin->item_type; - out.item_subtype = out.origin->item_subtype; - out.mat_type = out.origin->mat_type; - out.mat_index = out.origin->mat_index; - out.flags = out.origin->flags; - out.original_tile = out.origin->original_tile; - return true; -} - bool Constructions::designateNew(df::coord pos, df::construction_type type, df::item_type item, int mat_index) { From 739871bc0fafe1a80f498c88447ae5c9b912b5e0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 29 Jun 2022 00:03:49 -0400 Subject: [PATCH 014/111] Remove Hooks-egg.cpp and related code and configuration options --- CMakeLists.txt | 2 - docs/changelog.txt | 1 + library/CMakeLists.txt | 42 ++++--------------- library/Core.cpp | 7 ---- library/Hooks-egg.cpp | 92 ----------------------------------------- library/include/Core.h | 7 ---- library/include/Hooks.h | 18 -------- 7 files changed, 10 insertions(+), 159 deletions(-) delete mode 100644 library/Hooks-egg.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8271840e6..d01d105c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -206,11 +206,9 @@ set(DFHACK_BUILD_ID "" CACHE STRING "Build ID (should be specified on command li if(UNIX) # put the lib into DF/hack set(DFHACK_LIBRARY_DESTINATION hack) - set(DFHACK_EGGY_DESTINATION libs) else() # windows is crap, therefore we can't do nice things with it. leave the libs on a nasty pile... set(DFHACK_LIBRARY_DESTINATION .) - set(DFHACK_EGGY_DESTINATION .) endif() # external tools will be installed here: diff --git a/docs/changelog.txt b/docs/changelog.txt index a06629f62..9ff01740f 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -47,6 +47,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Documentation ## API +- Removed "egg" ("eggy") hook support (Linux only). The only remaining method of hooking into DF is by interposing SDL calls, which has been the method used by all binary releases of DFHack. - Removed ``Engravings`` module (C++-only). Access ``world.engravings`` directly instead. - Removed ``Notes`` module (C++-only). Access ``ui.waypoints.points`` directly instead. - Removed ``Windows`` module (C++-only) - unused. diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index c9e9cd844..95a8e610a 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -114,13 +114,6 @@ set(MAIN_SOURCES_DARWIN Process-darwin.cpp ) -set(MAIN_SOURCES_LINUX_EGGY - ${CONSOLE_SOURCES} - Hooks-egg.cpp - PlugLoad-linux.cpp - Process-linux.cpp -) - set(MODULE_HEADERS include/modules/Buildings.h include/modules/Burrows.h @@ -206,10 +199,7 @@ list(APPEND PROJECT_SOURCES ${MAIN_SOURCES}) list(APPEND PROJECT_SOURCES ${MODULE_SOURCES}) if(UNIX) - option(BUILD_EGGY "Make DFHack strangely egg-shaped." OFF) - if(BUILD_EGGY) - list(APPEND PROJECT_SOURCES ${MAIN_SOURCES_LINUX_EGGY}) - elseif(APPLE) + if(APPLE) list(APPEND PROJECT_SOURCES ${MAIN_SOURCES_DARWIN}) else() list(APPEND PROJECT_SOURCES ${MAIN_SOURCES_LINUX}) @@ -371,15 +361,9 @@ add_executable(dfhack-run dfhack-run.cpp) add_executable(binpatch binpatch.cpp) target_link_libraries(binpatch dfhack-md5) -if(BUILD_EGGY) - set_target_properties(dfhack PROPERTIES OUTPUT_NAME "egg" ) -else() - if(WIN32) - set_target_properties(dfhack PROPERTIES OUTPUT_NAME "SDL" ) - endif() -endif() - if(WIN32) + # name the resulting library SDL.dll on Windows + set_target_properties(dfhack PROPERTIES OUTPUT_NAME "SDL" ) set_target_properties(dfhack PROPERTIES COMPILE_FLAGS "/FI\"Export.h\"" ) set_target_properties(dfhack-client PROPERTIES COMPILE_FLAGS "/FI\"Export.h\"" ) else() @@ -434,23 +418,15 @@ if(UNIX) DESTINATION .) endif() else() - if(NOT BUILD_EGGY) - # On windows, copy the renamed SDL so DF can still run. - install(PROGRAMS ${dfhack_SOURCE_DIR}/package/windows/win${DFHACK_BUILD_ARCH}/SDLreal.dll - DESTINATION ${DFHACK_LIBRARY_DESTINATION}) - endif() + # On windows, copy the renamed SDL so DF can still run. + install(PROGRAMS ${dfhack_SOURCE_DIR}/package/windows/win${DFHACK_BUILD_ARCH}/SDLreal.dll + DESTINATION ${DFHACK_LIBRARY_DESTINATION}) endif() # install the main lib -if(NOT BUILD_EGGY) - install(TARGETS dfhack - LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} - RUNTIME DESTINATION ${DFHACK_LIBRARY_DESTINATION}) -else() - install(TARGETS dfhack - LIBRARY DESTINATION ${DFHACK_EGGY_DESTINATION} - RUNTIME DESTINATION ${DFHACK_EGGY_DESTINATION}) -endif() +install(TARGETS dfhack + LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} + RUNTIME DESTINATION ${DFHACK_LIBRARY_DESTINATION}) # install the offset file install(FILES xml/symbols.xml diff --git a/library/Core.cpp b/library/Core.cpp index cfb029d57..941d1bebf 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1975,13 +1975,6 @@ bool Core::isSuspended(void) return ownerThread.load() == std::this_thread::get_id(); } -int Core::TileUpdate() -{ - if(!started) - return false; - return true; -} - void Core::doUpdate(color_ostream &out, bool first_update) { Lua::Core::Reset(out, "DF code execution"); diff --git a/library/Hooks-egg.cpp b/library/Hooks-egg.cpp deleted file mode 100644 index c98cf5da2..000000000 --- a/library/Hooks-egg.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/* -https://github.com/peterix/dfhack -Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ -#include -#include -#include -#include -#include -#include -#include - -#include "Core.h" -#include "Hooks.h" -#include - -// hook - called before rendering -DFhackCExport int egg_init(void) -{ - // reroute stderr - freopen("stderr.log", "w", stderr); - // we don't reroute stdout until we figure out if this should be done at all - // See: Console-linux.cpp - fprintf(stderr,"dfhack: hooking successful\n"); - return true; -} - -// hook - called before rendering -DFhackCExport int egg_shutdown(void) -{ - DFHack::Core & c = DFHack::Core::getInstance(); - return c.Shutdown(); -} - -// hook - called for each game tick (or more often) -DFhackCExport int egg_tick(void) -{ - DFHack::Core & c = DFHack::Core::getInstance(); - return c.Update(); -} -// hook - called before rendering -DFhackCExport int egg_prerender(void) -{ - DFHack::Core & c = DFHack::Core::getInstance(); - return c.TileUpdate(); -} - -// hook - called for each SDL event, returns 0 when the event has been consumed. 1 otherwise -DFhackCExport int egg_sdl_event(SDL::Event* event) -{ - // if the event is valid, intercept - if( event != 0 ) - { - DFHack::Core & c = DFHack::Core::getInstance(); - return c.DFH_SDL_Event(event); - } - return true; -} - -// return this if you want to kill the event. -const int curses_error = -1; -// hook - ncurses event, -1 signifies error. -DFhackCExport int egg_curses_event(int orig_return) -{ - /* - if(orig_return != -1) - { - DFHack::Core & c = DFHack::Core::getInstance(); - int out; - return c.ncurses_wgetch(orig_return,); - }*/ - return orig_return; -} diff --git a/library/include/Core.h b/library/include/Core.h index f6acd9425..531d1b581 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -121,12 +121,6 @@ namespace DFHack friend int ::SDL_Init(uint32_t flags); friend int ::wgetch(WINDOW * w); #endif - friend int ::egg_init(void); - friend int ::egg_shutdown(void); - friend int ::egg_tick(void); - friend int ::egg_prerender(void); - friend int ::egg_sdl_event(SDL::Event* event); - friend int ::egg_curses_event(int orig_return); public: /// Get the single Core instance or make one. static Core& getInstance() @@ -205,7 +199,6 @@ namespace DFHack bool Init(); int Update (void); - int TileUpdate (void); int Shutdown (void); int DFH_SDL_Event(SDL::Event* event); bool ncurses_wgetch(int in, int & out); diff --git a/library/include/Hooks.h b/library/include/Hooks.h index 7a266e92c..d17b96acf 100644 --- a/library/include/Hooks.h +++ b/library/include/Hooks.h @@ -74,21 +74,3 @@ DFhackCExport void * SDL_GetVideoSurface(void); DFhackCExport int SDL_SemWait(vPtr sem); DFhackCExport int SDL_SemPost(vPtr sem); - -// hook - called early from DF's main() -DFhackCExport int egg_init(void); - -// hook - called before rendering -DFhackCExport int egg_shutdown(void); - -// hook - called for each game tick (or more often) -DFhackCExport int egg_tick(void); - -// hook - called before rendering -DFhackCExport int egg_prerender(void); - -// hook - called for each SDL event, can filter both the event and the return value -DFhackCExport int egg_sdl_event(SDL::Event* event); - -// hook - ncurses event. return -1 to consume -DFhackCExport int egg_curses_event(int orig_return); From 112d2938dfa2e47ac0ddec4ac6582f73866854fc Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Fri, 1 Jul 2022 15:58:24 +0000 Subject: [PATCH 015/111] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 7b92cec52..9480cad97 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 7b92cec5246f97f278e4d608899278d2cafc0992 +Subproject commit 9480cad97c3c0e71f8cb76004c74e2912702af95 From 04e0b600a520ba5cd0e9af47f58f95d7d3c27689 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Wed, 22 Jun 2022 12:33:54 +0200 Subject: [PATCH 016/111] add keybinding for `gui/workorder-details` to dfhack.init-example --- dfhack.init-example | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dfhack.init-example b/dfhack.init-example index bcc5615d6..88528f650 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -68,6 +68,9 @@ keybinding add Ctrl-Shift-R view-unit-reports # view extra unit information keybinding add Alt-I@dwarfmode/ViewUnits|unitlist gui/unit-info-viewer +# set workorder item details (on workorder details screen press D again) +keybinding add D@workquota_details gui/workorder-details + ############################## # Generic adv mode bindings # ############################## From 69b6de20456d986c778dd88ba0d86031937633c8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 5 Jul 2022 01:19:30 -0400 Subject: [PATCH 017/111] [pre-commit.ci] pre-commit autoupdate (#2235) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/python-jsonschema/check-jsonschema: 0.16.0 → 0.16.2](https://github.com/python-jsonschema/check-jsonschema/compare/0.16.0...0.16.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 774c42a95..4e7a4f48b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,7 @@ repos: args: ['--fix=lf'] - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.16.0 + rev: 0.16.2 hooks: - id: check-github-workflows - repo: https://github.com/Lucas-C/pre-commit-hooks From aec56848976ce5334aa0dedb5b3219ec4d7a70c0 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 5 Jul 2022 07:17:21 +0000 Subject: [PATCH 018/111] Auto-update submodules library/xml: master scripts: master --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 8d15b2d07..219676497 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 8d15b2d07c18cc9dfbd550b32ad43e46b2d4014d +Subproject commit 2196764977011991127244b28ff13b90cef19af3 diff --git a/scripts b/scripts index 9480cad97..f08794136 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 9480cad97c3c0e71f8cb76004c74e2912702af95 +Subproject commit f08794136e20705b87cfaf509b182f07ec394238 From fabcf7c9795f3583acbf77f634256a59fb54e44c Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 6 Jul 2022 07:16:58 +0000 Subject: [PATCH 019/111] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index f08794136..6bc683d2c 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit f08794136e20705b87cfaf509b182f07ec394238 +Subproject commit 6bc683d2ca42ef467465b0905513ad590a7dc4c2 From 091068c7102c8f5ddb7133cfe1b07133f5e818cb Mon Sep 17 00:00:00 2001 From: Myk Date: Wed, 6 Jul 2022 06:57:13 -0700 Subject: [PATCH 020/111] [prospect] give player control over which information is output (#2231) * give player control over prospect output * suspend the core *before* we call to Lua --- docs/Plugins.rst | 58 +++++-- docs/changelog.txt | 2 +- plugins/CMakeLists.txt | 2 +- plugins/lua/prospector.lua | 45 ++++++ plugins/prospector.cpp | 322 ++++++++++++++++++++++++------------- 5 files changed, 299 insertions(+), 130 deletions(-) create mode 100644 plugins/lua/prospector.lua diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 3b42bc16a..e5c662ffa 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -373,29 +373,55 @@ selected objects. prospect ======== -Prints a big list of all the present minerals and plants. By default, only -the visible part of the map is scanned. -Options: +**Usage:** -:all: Scan the whole map, as if it were revealed. -:value: Show material value in the output. Most useful for gems. -:hell: Show the Z range of HFS tubes. Implies 'all'. + ``prospect [all|hell] []`` -If prospect is called during the embark selection screen, it displays an estimate of -layer stone availability. +Shows a summary of resources that exist on the map. By default, only the visible +part of the map is scanned. Include the ``all`` keyword if you want ``prospect`` +to scan the whole map as if it were revealed. Use ``hell`` instead of ``all`` if +you also want to see the Z range of HFS tubes in the 'features' report section. -.. note:: +**Options:** - The results of pre-embark prospect are an *estimate*, and can at best be expected - to be somewhere within +/- 30% of the true amount; sometimes it does a lot worse. - Especially, it is not clear how to precisely compute how many soil layers there - will be in a given embark tile, so it can report a whole extra layer, or omit one - that is actually present. +:``-h``, ``--help``: + Shows this help text. +:``-s``, ``--show ``: + Shows only the named comma-separated list of report sections. Report section + names are: summary, liquids, layers, features, ores, gems, veins, shrubs, + and trees. If run during pre-embark, only the layers, ores, gems, and veins + report sections are available. +:``-v``, ``--values``: + Includes material value in the output. Most useful for the 'gems' report + section. -Options: +**Examples:** + +``prospect all`` + Shows the entire report for the entire map. + +``prospect hell --show layers,ores,veins`` + Shows only the layers, ores, and other vein stone report sections, and + includes information on HFS tubes when a fort is loaded. + +``prospect all -sores`` + Show only information about ores for the pre-embark or fortress map report. + +**Pre-embark estimate:** + +If prospect is called during the embark selection screen, it displays an +estimate of layer stone availability. If the ``all`` keyword is specified, it +also estimates ores, gems, and vein material. The estimate covers all tiles of +the embark rectangle. + +.. note:: -:all: Also estimate vein mineral amounts. + The results of pre-embark prospect are an *estimate*, and can at best be + expected to be somewhere within +/- 30% of the true amount; sometimes it + does a lot worse. Especially, it is not clear how to precisely compute how + many soil layers there will be in a given embark tile, so it can report a + whole extra layer, or omit one that is actually present. .. _remotefortressreader: diff --git a/docs/changelog.txt b/docs/changelog.txt index 2d5a5cf64..0c1422306 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -41,8 +41,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - ``job.removeJob()``: ensure jobs are removed from the world list when they are canceled ## Misc Improvements - - ``materials.ItemTraitsDialog``: added a default ``on_select``-handler which toggles the traits. +- `prospect`: add new ``--show`` option to give the player control over which report sections are shown. e.g. ``prospect all --show ores`` will just show information on ores. ## Documentation diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index ed9404b8e..4a4b74eaa 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -148,7 +148,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(petcapRemover petcapRemover.cpp) dfhack_plugin(plants plants.cpp) dfhack_plugin(probe probe.cpp) - dfhack_plugin(prospector prospector.cpp) + dfhack_plugin(prospector prospector.cpp LINK_LIBRARIES lua) dfhack_plugin(power-meter power-meter.cpp LINK_LIBRARIES lua) dfhack_plugin(regrass regrass.cpp) add_subdirectory(remotefortressreader) diff --git a/plugins/lua/prospector.lua b/plugins/lua/prospector.lua new file mode 100644 index 000000000..403a1f43f --- /dev/null +++ b/plugins/lua/prospector.lua @@ -0,0 +1,45 @@ +local _ENV = mkmodule('plugins.prospector') + +local argparse = require('argparse') +local utils = require('utils') + +local VALID_SHOW_VALUES = utils.invert{ + 'summary', 'liquids', 'layers', 'features', 'ores', + 'gems', 'veins', 'shrubs', 'trees' +} + +function parse_commandline(opts, ...) + local show = {} + local positionals = argparse.processArgsGetopt({...}, { + {'h', 'help', handler=function() opts.help = true end}, + {'s', 'show', hasArg=true, handler=function(optarg) + show = argparse.stringList(optarg) end}, + {'v', 'values', handler=function() opts.value = true end}, + }) + + for _,p in ipairs(positionals) do + if p == 'all' then opts.hidden = true + elseif p == 'hell' then + opts.hidden = true + opts.tube = true + else + qerror(('unknown keyword: "%s"'):format(p)) + end + end + + if #show > 0 then + for s in pairs(VALID_SHOW_VALUES) do + opts[s] = false + end + end + + for _,s in ipairs(show) do + if VALID_SHOW_VALUES[s] then + opts[s] = true + else + qerror(('unknown report section: "%s"'):format(s)) + end + end +end + +return _ENV diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp index 6b2079cf0..0516dd552 100644 --- a/plugins/prospector.cpp +++ b/plugins/prospector.cpp @@ -17,6 +17,7 @@ using namespace std; #include "Core.h" #include "Console.h" #include "Export.h" +#include "LuaTools.h" #include "PluginManager.h" #include "modules/Gui.h" #include "modules/MapCache.h" @@ -44,6 +45,50 @@ using df::coord2d; DFHACK_PLUGIN("prospector"); REQUIRE_GLOBAL(world); +struct prospect_options { + // whether to display help + bool help = false; + + // whether to scan the whole map or just the unhidden tiles + bool hidden = false; + + // whether to also show material values + bool value = false; + + // whether to show adamantine tube z-levels + bool tube = false; + + // which report sections to show + bool summary = true; + bool liquids = true; + bool layers = true; + bool features = true; + bool ores = true; + bool gems = true; + bool veins = true; + bool shrubs = true; + bool trees = true; + + static struct_identity _identity; +}; +static const struct_field_info prospect_options_fields[] = { + { struct_field_info::PRIMITIVE, "help", offsetof(prospect_options, help), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "hidden", offsetof(prospect_options, hidden), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "value", offsetof(prospect_options, value), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "tube", offsetof(prospect_options, tube), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "summary", offsetof(prospect_options, summary), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "liquids", offsetof(prospect_options, liquids), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "layers", offsetof(prospect_options, layers), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "features", offsetof(prospect_options, features), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "ores", offsetof(prospect_options, ores), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "gems", offsetof(prospect_options, gems), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "veins", offsetof(prospect_options, veins), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "shrubs", offsetof(prospect_options, shrubs), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "trees", offsetof(prospect_options, trees), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::END } +}; +struct_identity prospect_options::_identity(sizeof(prospect_options), &df::allocator_fn, NULL, "prospect_options", NULL, prospect_options_fields); + struct matdata { const static int invalid_z = -30000; @@ -123,9 +168,9 @@ static void printMatdata(color_ostream &con, const matdata &data, bool only_z = con << std::setw(9) << int(data.count); if(data.lower_z != data.upper_z) - con <<" Z:" << std::setw(4) << data.lower_z << ".." << data.upper_z << std::endl; + con <<" Z:" << std::setw(4) << data.lower_z << ".." << data.upper_z << std::endl; else - con <<" Z:" << std::setw(4) << data.lower_z << std::endl; + con <<" Z:" << std::setw(4) << data.lower_z << std::endl; } static int getValue(const df::inorganic_raw &info) @@ -139,7 +184,7 @@ static int getValue(const df::plant_raw &info) } template class P> -void printMats(color_ostream &con, MatMap &mat, std::vector &materials, bool show_value) +void printMats(color_ostream &con, MatMap &mat, std::vector &materials, const prospect_options &options) { unsigned int total = 0; MatSorter sorting_vector; @@ -161,7 +206,7 @@ void printMats(color_ostream &con, MatMap &mat, std::vector &materials, bool T* mat = materials[it->first]; // Somewhat of a hack, but it works because df::inorganic_raw and df::plant_raw both have a field named "id" con << std::setw(25) << mat->id << " : "; - if (show_value) + if (options.value) con << std::setw(3) << getValue(*mat) << " : "; printMatdata(con, it->second); total += it->second.count; @@ -171,7 +216,7 @@ void printMats(color_ostream &con, MatMap &mat, std::vector &materials, bool } void printVeins(color_ostream &con, MatMap &mat_map, - DFHack::Materials* mats, bool show_value) + const prospect_options &options) { MatMap ores; MatMap gems; @@ -194,14 +239,20 @@ void printVeins(color_ostream &con, MatMap &mat_map, rest[kv.first] = kv.second; } - con << "Ores:" << std::endl; - printMats(con, ores, world->raws.inorganics, show_value); + if (options.ores) { + con << "Ores:" << std::endl; + printMats(con, ores, world->raws.inorganics, options); + } - con << "Gems:" << std::endl; - printMats(con, gems, world->raws.inorganics, show_value); + if (options.gems) { + con << "Gems:" << std::endl; + printMats(con, gems, world->raws.inorganics, options); + } - con << "Other vein stone:" << std::endl; - printMats(con, rest, world->raws.inorganics, show_value); + if (options.veins) { + con << "Other vein stone:" << std::endl; + printMats(con, rest, world->raws.inorganics, options); + } } command_result prospector (color_ostream &out, vector & parameters); @@ -211,18 +262,43 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector ]\n" + "\n" + " Shows a summary of resources that exist on the map. By default,\n" + " only the visible part of the map is scanned. Include the 'all' keyword\n" + " if you want prospect to scan the whole map as if it were revealed.\n" + " Use 'hell' instead of 'all' if you also want to see the Z range of HFS\n" + " tubes in the 'features' report section.\n" + "\n" + "Options:\n" + " -h,--help\n" + " Shows this help text.\n" + " -s,--show \n" + " Shows only the named comma-separated list of report sections.\n" + " Report section names are: summary, liquids, layers, features, ores,\n" + " gems, veins, shrubs, and trees. If run during pre-embark, only the\n" + " layers, ores, gems, and veins report sections are available.\n" + " -v,--values\n" + " Includes material value in the output. Most useful for the 'gems'\n" + " report section.\n" + "\n" + "Examples:\n" + " prospect all\n" + " Shows the entire report for the entire map.\n" + "\n" + " prospect hell --show layers,ores,veins\n" + " Shows only the layers, ores, and other vein stone report sections,\n" + " and includes information on HFS tubes when a fort is loaded.\n" + "\n" + " prospect all -sores\n" + " Show only information about ores for the pre-embark or fortress map\n" + " report.\n" + "\n" + "Pre-embark estimate:\n" + " If called during the embark selection screen, displays a rough\n" + " estimate of layer stone availability. If the 'all' keyword is\n" + " specified, also estimates ores, gems, and other vein material. The\n" + " estimate covers all tiles of the embark rectangle.\n" )); return CR_OK; } @@ -522,8 +598,9 @@ bool estimate_materials(color_ostream &out, EmbarkTileLayout &tile, MatMap &laye return true; } -static command_result embark_prospector(color_ostream &out, df::viewscreen_choose_start_sitest *screen, - bool showHidden, bool showValue) +static command_result embark_prospector(color_ostream &out, + df::viewscreen_choose_start_sitest *screen, + const prospect_options &options) { if (!world || !world->world_data) { @@ -549,12 +626,6 @@ static command_result embark_prospector(color_ostream &out, df::viewscreen_choos // Compute biomes std::map biomes; - /*if (screen->biome_highlighted) - { - out.print("Processing one embark tile of biome F%d.\n\n", screen->biome_idx+1); - biomes[screen->biome_rgn[screen->biome_idx]]++; - }*/ - for (int x = screen->location.embark_pos_min.x; x <= 15 && x <= screen->location.embark_pos_max.x; x++) { for (int y = screen->location.embark_pos_min.y; y <= 15 && y <= screen->location.embark_pos_max.y; y++) @@ -570,12 +641,14 @@ static command_result embark_prospector(color_ostream &out, df::viewscreen_choos } // Print the report - out << "Layer materials:" << std::endl; - printMats(out, layerMats, world->raws.inorganics, showValue); + if (options.layers) { + out << "Layer materials:" << std::endl; + printMats(out, layerMats, world->raws.inorganics, options); + } - if (showHidden) { + if (options.hidden) { DFHack::Materials *mats = Core::getInstance().getMaterials(); - printVeins(out, veinMats, mats, showValue); + printVeins(out, veinMats, options); mats->Finish(); } @@ -587,40 +660,8 @@ static command_result embark_prospector(color_ostream &out, df::viewscreen_choos return CR_OK; } -command_result prospector (color_ostream &con, vector & parameters) -{ - bool showHidden = false; - bool showPlants = true; - bool showSlade = true; - bool showTemple = true; - bool showValue = false; - bool showTube = false; - - for(size_t i = 0; i < parameters.size();i++) - { - if (parameters[i] == "all") - { - showHidden = true; - } - else if (parameters[i] == "value") - { - showValue = true; - } - else if (parameters[i] == "hell") - { - showHidden = showTube = true; - } - else - return CR_WRONG_USAGE; - } - - CoreSuspender suspend; - - // Embark screen active: estimate using world geology data - auto screen = Gui::getViewscreenByType(0); - if (screen) - return embark_prospector(con, screen, showHidden, showValue); - +static command_result map_prospector(color_ostream &con, + const prospect_options &options) { if (!Maps::IsValid()) { con.printerr("Map is not available!\n"); @@ -636,7 +677,6 @@ command_result prospector (color_ostream &con, vector & parameters) DFHack::t_feature blockFeatureGlobal; DFHack::t_feature blockFeatureLocal; - bool hasAquifer = false; bool hasDemonTemple = false; bool hasLair = false; MatMap baseMats; @@ -680,7 +720,7 @@ command_result prospector (color_ostream &con, vector & parameters) df::tile_occupancy occ = b->OccupancyAt(coord); // Skip hidden tiles - if (!showHidden && des.bits.hidden) + if (!options.hidden && des.bits.hidden) { continue; } @@ -688,7 +728,6 @@ command_result prospector (color_ostream &con, vector & parameters) // Check for aquifer if (des.bits.water_table) { - hasAquifer = true; aquiferTiles.add(global_z); } @@ -752,14 +791,13 @@ command_result prospector (color_ostream &con, vector & parameters) { veinMats[blockFeatureLocal.sub_material].add(global_z); } - else if (showTemple - && blockFeatureLocal.type == feature_type::deep_surface_portal) + else if (blockFeatureLocal.type == feature_type::deep_surface_portal) { hasDemonTemple = true; } } - if (showSlade && blockFeatureGlobal.type != -1 && des.bits.feature_global + if (blockFeatureGlobal.type != -1 && des.bits.feature_global && blockFeatureGlobal.type == feature_type::underworld_from_layer && blockFeatureGlobal.main_material == 0) // stone { @@ -777,7 +815,7 @@ command_result prospector (color_ostream &con, vector & parameters) // Check plants this way, as the other way wasn't getting them all // and we can check visibility more easily here - if (showPlants) + if (options.shrubs) { auto block = Maps::getBlockColumn(b_x,b_y); vector *plants = block ? &block->plants : NULL; @@ -790,7 +828,7 @@ command_result prospector (color_ostream &con, vector & parameters) continue; df::coord2d loc(plant.pos.x, plant.pos.y); loc = loc % 16; - if (showHidden || !b->DesignationAt(loc).bits.hidden) + if (options.hidden || !b->DesignationAt(loc).bits.hidden) { if(plant.flags.bits.is_shrub) plantMats[plant.material].add(global_z); @@ -810,15 +848,18 @@ command_result prospector (color_ostream &con, vector & parameters) MatMap::const_iterator it; - con << "Base materials:" << std::endl; - for (it = baseMats.begin(); it != baseMats.end(); ++it) - { - con << std::setw(25) << ENUM_KEY_STR(tiletype_material,(df::tiletype_material)it->first) << " : " << it->second.count << std::endl; + if (options.summary) { + con << "Base materials:" << std::endl; + for (it = baseMats.begin(); it != baseMats.end(); ++it) + { + con << std::setw(25) << ENUM_KEY_STR(tiletype_material,(df::tiletype_material)it->first) << " : " << it->second.count << std::endl; + } + con << std::endl; } - if (liquidWater.count || liquidMagma.count) + if (options.liquids && (liquidWater.count || liquidMagma.count)) { - con << std::endl << "Liquids:" << std::endl; + con << "Liquids:" << std::endl; if (liquidWater.count) { con << std::setw(25) << "WATER" << " : "; @@ -829,51 +870,108 @@ command_result prospector (color_ostream &con, vector & parameters) con << std::setw(25) << "MAGMA" << " : "; printMatdata(con, liquidMagma); } + con << std::endl; } - con << std::endl << "Layer materials:" << std::endl; - printMats(con, layerMats, world->raws.inorganics, showValue); - - printVeins(con, veinMats, mats, showValue); - - if (showPlants) - { - con << "Shrubs:" << std::endl; - printMats(con, plantMats, world->raws.plants.all, showValue); - con << "Wood in trees:" << std::endl; - printMats(con, treeMats, world->raws.plants.all, showValue); + if (options.layers) { + con << "Layer materials:" << std::endl; + printMats(con, layerMats, world->raws.inorganics, options); } - if (hasAquifer) - { - con << "Has aquifer"; + if (options.features) { + con << "Features:" << std::endl; + + bool hasFeature = false; if (aquiferTiles.count) { - con << " : "; + con << std::setw(25) << "Has aquifer" << " : "; + if (options.value) + con << " "; printMatdata(con, aquiferTiles); + hasFeature = true; } - else - con << std::endl; - } - if (showTube && tubeTiles.count) - { - con << "Has HFS tubes : "; - printMatdata(con, tubeTiles); + if (options.tube && tubeTiles.count) + { + con << std::setw(25) << "Has HFS tubes" << " : "; + if (options.value) + con << " "; + printMatdata(con, tubeTiles, true); + hasFeature = true; + } + + if (hasDemonTemple) + { + con << std::setw(25) << "Has demon temple" << std::endl; + hasFeature = true; + } + + if (hasLair) + { + con << std::setw(25) << "Has lair" << std::endl; + hasFeature = true; + } + + if (!hasFeature) + con << std::setw(25) << "None" << std::endl; + + con << std::endl; } - if (hasDemonTemple) - { - con << "Has demon temple" << std::endl; + printVeins(con, veinMats, options); + + if (options.shrubs) { + con << "Shrubs:" << std::endl; + printMats(con, plantMats, world->raws.plants.all, options); } - if (hasLair) - { - con << "Has lair" << std::endl; + if (options.trees) { + con << "Wood in trees:" << std::endl; + printMats(con, treeMats, world->raws.plants.all, options); } // Cleanup mats->Finish(); - con << std::endl; + return CR_OK; } + +static bool get_options(color_ostream &out, + prospect_options &opts, + const vector ¶meters) +{ + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, parameters.size() + 2) || + !Lua::PushModulePublic( + out, L, "plugins.prospector", "parse_commandline")) { + out.printerr("Failed to load prospector Lua code\n"); + return false; + } + + Lua::Push(L, &opts); + + for (const string ¶m : parameters) + Lua::Push(L, param); + + if (!Lua::SafeCall(out, L, parameters.size() + 1, 0)) + return false; + + return true; +} + +command_result prospector(color_ostream &con, vector & parameters) +{ + CoreSuspender suspend; + + prospect_options options; + if (!get_options(con, options, parameters) || options.help) + return CR_WRONG_USAGE; + + // Embark screen active: estimate using world geology data + auto screen = Gui::getViewscreenByType(0); + return screen ? + embark_prospector(con, screen, options) : + map_prospector(con, options); +} From e0d37a31ae9fab5bec8fb06d977d45f99785f70a Mon Sep 17 00:00:00 2001 From: Myk Date: Wed, 6 Jul 2022 07:03:29 -0700 Subject: [PATCH 021/111] Make the manager orders library available by default (#2233) * move orders out of examples directory * install orders into library dir * read orders from new library dir * update documentation * update dreamfort references to orders import * update changelog * ignore json files in pre-commit --- .pre-commit-config.yaml | 2 +- data/CMakeLists.txt | 3 + data/blueprints/library/dreamfort.csv | 22 +++-- data/{examples => }/orders/basic.json | 0 data/{examples => }/orders/furnace.json | 0 data/{examples => }/orders/glassstock.json | 0 data/{examples => }/orders/military.json | 0 data/{examples => }/orders/rockstock.json | 0 data/{examples => }/orders/smelting.json | 0 docs/Plugins.rst | 98 +++++++++++++++++++++ docs/changelog.txt | 2 + docs/guides/examples-guide.rst | 99 ---------------------- library/modules/Filesystem.cpp | 9 +- plugins/orders.cpp | 41 ++++++++- 14 files changed, 160 insertions(+), 116 deletions(-) rename data/{examples => }/orders/basic.json (100%) rename data/{examples => }/orders/furnace.json (100%) rename data/{examples => }/orders/glassstock.json (100%) rename data/{examples => }/orders/military.json (100%) rename data/{examples => }/orders/rockstock.json (100%) rename data/{examples => }/orders/smelting.json (100%) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4e7a4f48b..25601d8b8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -41,4 +41,4 @@ repos: entry: python3 ci/authors-rst.py files: docs/Authors\.rst pass_filenames: false -exclude: '^(depends/|data/examples/.*\.json$|.*\.diff$)' +exclude: '^(depends/|data/.*\.json$|.*\.diff$)' diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt index 53e88fed9..72df7272a 100644 --- a/data/CMakeLists.txt +++ b/data/CMakeLists.txt @@ -1,6 +1,9 @@ install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/quickfort/ DESTINATION "${DFHACK_DATA_DESTINATION}/data/quickfort") +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/orders/ + DESTINATION dfhack-config/orders/library) + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/examples/ DESTINATION "${DFHACK_DATA_DESTINATION}/examples") diff --git a/data/blueprints/library/dreamfort.csv b/data/blueprints/library/dreamfort.csv index a00a00e9e..ec1a05c51 100644 --- a/data/blueprints/library/dreamfort.csv +++ b/data/blueprints/library/dreamfort.csv @@ -31,7 +31,7 @@ "Other DFHack commands also work very well with Dreamfort, such as autofarm, autonestbox, prioritize, seedwatch, tailor, and, of course, buildingplan. An init file that gets everything configured for you is distributed with DFHack as hack/examples/init/onMapLoad_dreamfort.init." Put that file in your Dwarf Fortress directory -- the same directory that has dfhack.init. "" -"Also copy the files in hack/examples/orders/ to dfhack-config/orders/ and the files in hack/examples/professions/ to professions/. We'll be using these files later. See https://docs.dfhack.org/en/stable/docs/guides/examples-guide.html for more information, including suggestions on how many dwarves of each profession you are likely to need at each stage of fort maturity." +"Also copy the files in hack/examples/professions/ to professions/. We'll be using these files later. See https://docs.dfhack.org/en/stable/docs/guides/examples-guide.html for more information, including suggestions on how many dwarves of each profession you are likely to need at each stage of fort maturity." "" "Once you have your starting surface workshops up and running, you might want to configure buildingplan (in its global settings, accessible from any building placement screen, e.g.: b-a-G) to only use blocks for constructions so it won't use your precious wood, boulders, and bars to build floors and walls. If you bring at least 7 blocks with you on embark, you can even set this in your onMapLoad.init file like this:" on-new-fortress buildingplan set boulders false; buildingplan set logs false @@ -50,7 +50,6 @@ interactively." "" -- Preparation (before you embark!) -- Copy hack/examples/init/onMapLoad_dreamfort.init to your DF directory -Copy the fort automation orders from hack/examples/orders/*.json to the dfhack-config/orders/ directory Optionally copy the premade profession definitions from hack/examples/professions/ to the professions/ directory Optionally copy the premade Dreamfort embark profile from the online spreadsheets to the data/init/embark_profiles.txt file "" @@ -75,7 +74,7 @@ quickfort run library/dreamfort.csv -n /industry2,# Run when the industry level prioritize ConstructBuilding,# To get those workshops up and running ASAP. You may have to run this several times as the materials for the building construction jobs become ready. quickfort run library/dreamfort.csv -n /surface4,"# Run after the walls and floors are built on the surface. Even if /surface3 is finished before you run /industry2, though, wait until after /industry2 to run this blueprint so that surface walls, floors, and roofing don't prevent your workshops from being built (due to lack of blocks)." "quickfort orders,run library/dreamfort.csv -n /services2",# Run when the services levels have been dug out. Feel free to remove the orders for the ropes if you already brought them with you. -orders import basic,"# Run after the first migration wave, so you have dorfs to do all the basic tasks. Note that this is the ""orders"" plugin, not the ""quickfort orders"" command." +orders import library/basic,"# Run after the first migration wave, so you have dorfs to do all the basic tasks. Note that this is the ""orders"" plugin, not the ""quickfort orders"" command." "quickfort orders,run library/dreamfort.csv -n /surface5","# Run when all marked trees on the surface are chopped down and walls and floors have been constructed, including the roof section over the future barracks." prioritize ConstructBuilding,# Run when you see the bridges ready to be built so the busy masons come and build them. "quickfort orders,run library/dreamfort.csv -n /surface6",# Run when at least the beehives and weapon rack are constructed and you have linked all levers to their respective bridges. @@ -86,7 +85,7 @@ prioritize ConstructBuilding,# Run when you see the bridges ready to be built so "Also consider bringing magma up to your services level so you can replace the forge and furnaces on your industry level with more powerful magma versions. This is especially important if your embark has insufficient trees to convert into charcoal. Keep in mind that moving magma is a tricky process and can take a long time. Don't forget to continue making progress through the checklist! If you choose to use magma, I suggest getting it in place before importing the military and smelting automation orders since they make heavy use of furnaces and forges." "" -- Mature fort (third migration wave onward) -- -orders import furnace,# Automated production of basic furnace-related items. Don't forget to create a sand collection zone (or remove the sand- and glass-related orders if you have no sand). +orders import library/furnace,# Automated production of basic furnace-related items. Don't forget to create a sand collection zone (or remove the sand- and glass-related orders if you have no sand). "quickfort orders,run library/dreamfort.csv -n /suites2",# Run when the suites level has been dug out. "quickfort orders,run library/dreamfort.csv -n /surface8","# Run if/when you need longer trap corridors on the surface for larger sieges, anytime after you run /surface7." "quickfort orders,run library/dreamfort.csv -n /apartments2",# Run when the first apartment level has been dug out. @@ -95,11 +94,11 @@ orders import furnace,# Automated production of basic furnace-related items. Don "quickfort orders,run library/dreamfort.csv -n ""/guildhall3, /guildhall4""",# Optionally run after /guildhall2 to build default furnishings and declare a library and temple. "quickfort orders,run library/dreamfort.csv -n /apartments3",# Run when all beds have been constructed on the first apartments level. "quickfort orders,run library/dreamfort.csv -n /farming4",# Run once you have a cache of potash. -orders import military,# Automated production of military equipment. Turn on automelt in the meltables piles on the industry level to automatically upgrade all metal military equipment to masterwork quality. These orders are optional if you are not using a military. -orders import smelting,# Automated production of all types of metal bars. +orders import library/military,# Automated production of military equipment. Turn on automelt in the meltables piles on the industry level to automatically upgrade all metal military equipment to masterwork quality. These orders are optional if you are not using a military. +orders import library/smelting,# Automated production of all types of metal bars. "quickfort orders,run library/dreamfort.csv -n /services4","# Run when you need a jail, anytime after the restraints are placed from /services3." -orders import rockstock,# Maintains a small stock of all types of rock furniture. -orders import glassstock,# Maintains a small stock of all types of glass furniture and parts (only import if you have sand). +orders import library/rockstock,# Maintains a small stock of all types of rock furniture. +orders import library/glassstock,# Maintains a small stock of all types of glass furniture and parts (only import if you have sand). "" -- Repeat for each remaining apartments level as needed -- "quickfort orders,run library/dreamfort.csv -n /apartments2",# Run when the apartment level has been dug out. @@ -1830,7 +1829,6 @@ Workshops: Manual steps you have to take: - Assign minecarts to your quantum stockpile hauling routes "- Give from the ""Goods"" quantum stockpile to the jugs, pots, and bags stockpiles on the farming level" -- Copy the fort automation manager orders (the .json files) from hack/examples/orders/ and put them in your dfhack-config/orders/ directory. "" Optional manual steps you can take: - Restrict the Mechanic's workshop to only allow skilled workers so unskilled trap-resetters won't be tasked to build mechanisms. @@ -1846,11 +1844,11 @@ Industry Walkthrough: "" "3) Once the area is dug out, run /industry2. Remember to assign minecarts to to your quantum stockpile hauling routes, and if the farming level is already built, give from the ""Goods"" quantum stockpile (the one on the left) to the jugs, pots, and bags stockpiles on the farming level." "" -"4) Once you have enough dwarves to do maintenance tasks (that is, after the first or second migration wave), run ""orders import basic"" to use the provided basic.json to take care of your fort's basic needs, such as food, booze, and raw material processing." +"4) Once you have enough dwarves to do maintenance tasks (that is, after the first or second migration wave), run ""orders import library/basic"" to use the provided basic.json to take care of your fort's basic needs, such as food, booze, and raw material processing." "" "5) If you want to automatically melt goblinite and other low-quality weapons and armor, mark the south-east stockpiles for auto-melt. If you don't have a high density of trees to make into charcoal, though, be sure to route magma to the level beneath this one and replace the forge and furnaces with magma equivalents." "" -"6) Once you have magma furnaces (or abundant fuel) and more dwarves, run ""orders import furnace"", ""orders import military"", and ""orders import smelting"" to import the remaining fort automation orders. The military orders are optional if you are not planning to have a military, of course." +"6) Once you have magma furnaces (or abundant fuel) and more dwarves, run ""orders import library/furnace"", ""orders import library/military"", and ""orders import library/smelting"" to import the remaining fort automation orders. The military orders are optional if you are not planning to have a military, of course." "" "7) At any time, feel free to build extra workshops or designate custom stockpiles in the unused space in the top and bottom right. The space is there for you to use!" "#dig label(industry1) start(18; 18; central stairs) message(Once the area is dug out, continue with /industry2.)" @@ -1969,7 +1967,7 @@ query/industry_query - assign minecarts to to your quantum stockpile hauling routes (use ""assign-minecarts all"") - if the farming level is already built, give from the ""Goods"" quantum stockpile to the jugs, pots, and bags stockpiles on the farming level - if you want to automatically melt goblinite and other low-quality weapons and armor, mark the south-east stockpiles for auto-melt -- once you have enough dwarves, run ""orders import basic"" to automate your fort's basic needs (see /industry_help for more info on this file) +- once you have enough dwarves, run ""orders import library/basic"" to automate your fort's basic needs (see /industry_help for more info on this file) - optionally, restrict the labors for your Craftsdwarf's and Mechanic's workshops as per the guidance in /industry_help)" diff --git a/data/examples/orders/basic.json b/data/orders/basic.json similarity index 100% rename from data/examples/orders/basic.json rename to data/orders/basic.json diff --git a/data/examples/orders/furnace.json b/data/orders/furnace.json similarity index 100% rename from data/examples/orders/furnace.json rename to data/orders/furnace.json diff --git a/data/examples/orders/glassstock.json b/data/orders/glassstock.json similarity index 100% rename from data/examples/orders/glassstock.json rename to data/orders/glassstock.json diff --git a/data/examples/orders/military.json b/data/orders/military.json similarity index 100% rename from data/examples/orders/military.json rename to data/orders/military.json diff --git a/data/examples/orders/rockstock.json b/data/orders/rockstock.json similarity index 100% rename from data/examples/orders/rockstock.json rename to data/orders/rockstock.json diff --git a/data/examples/orders/smelting.json b/data/orders/smelting.json similarity index 100% rename from data/examples/orders/smelting.json rename to data/orders/smelting.json diff --git a/docs/Plugins.rst b/docs/Plugins.rst index e5c662ffa..c73bfab3a 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -1129,6 +1129,7 @@ A plugin for manipulating manager orders. Subcommands: +:list: Shows the list of previously exported orders, including the orders library. :export NAME: Exports the current list of manager orders to a file named ``dfhack-config/orders/NAME.json``. :import NAME: Imports manager orders from a file named ``dfhack-config/orders/NAME.json``. :clear: Deletes all manager orders in the current embark. @@ -1141,6 +1142,103 @@ your ``onMapLoad.init`` file:: repeat -name orders-sort -time 1 -timeUnits days -command [ orders sort ] + +The orders library +------------------ + +DFHack comes with a library of useful manager orders that are ready for import: + +:source:`basic.json ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This collection of orders handles basic fort necessities: + +- prepared meals and food products (and by-products like oil) +- booze/mead +- thread/cloth/dye +- pots/jugs/buckets/mugs +- bags of leather, cloth, silk, and yarn +- crafts and totems from otherwise unusable by-products +- mechanisms/cages +- splints/crutches +- lye/soap +- ash/potash +- beds/wheelbarrows/minecarts +- scrolls + +You should import it as soon as you have enough dwarves to perform the tasks. +Right after the first migration wave is usually a good time. + +:source:`furnace.json ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This collection creates basic items that require heat. It is separated out from +``basic.json`` to give players the opportunity to set up magma furnaces first in +order to save resources. It handles: + +- charcoal (including smelting of bituminous coal and lignite) +- pearlash +- sand +- green/clear/crystal glass +- adamantine processing +- item melting + +Orders are missing for plaster powder until DF :bug:`11803` is fixed. + +:source:`military.json ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This collection adds high-volume smelting jobs for military-grade metal ores and +produces weapons and armor: + +- leather backpacks/waterskins/cloaks/quivers/armor +- bone/wooden bolts +- smelting for platinum, silver, steel, bronze, bismuth bronze, and copper (and + their dependencies) +- bronze/bismuth bronze/copper bolts +- platinum/silver/steel/iron/bismuth bronze/bronze/copper weapons and armor, + with checks to ensure only the best available materials are being used + +If you set a stockpile to take weapons and armor of less than masterwork quality +and turn on `automelt` (like what `dreamfort` provides on its industry level), +these orders will automatically upgrade your military equipment to masterwork. +Make sure you have a lot of fuel (or magma forges and furnaces) before you turn +``automelt`` on, though! + +This file should only be imported, of course, if you need to equip a military. + +:source:`smelting.json ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This collection adds smelting jobs for all ores. It includes handling the ores +already managed by ``military.json``, but has lower limits. This ensures all +ores will be covered if a player imports ``smelting`` but not ``military``, but +the higher-volume ``military`` orders will take priority if both are imported. + +:source:`rockstock.json ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This collection of orders keeps a small stock of all types of rock furniture. +This allows you to do ad-hoc furnishings of guildhalls, libraries, temples, or +other rooms with `buildingplan` and your masons will make sure there is always +stock on hand to fulfill the plans. + +:source:`glassstock.json ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Similar to ``rockstock`` above, this collection keeps a small stock of all types +of glass furniture. If you have a functioning glass industry, this is more +sustainable than ``rockstock`` since you can never run out of sand. If you have +plenty of rock and just want the variety, you can import both ``rockstock`` and +``glassstock`` to get a mixture of rock and glass furnishings in your fort. + +There are a few items that ``glassstock`` produces that ``rockstock`` does not, +since there are some items that can not be made out of rock, for example: + +- tubes and corkscrews for building magma-safe screw pumps +- windows +- terrariums (as an alternative to wooden cages) + .. _seedwatch: seedwatch diff --git a/docs/changelog.txt b/docs/changelog.txt index 0c1422306..ccc94701c 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -42,6 +42,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements - ``materials.ItemTraitsDialog``: added a default ``on_select``-handler which toggles the traits. + +- `orders`: added useful library of manager orders. see them with ``orders list`` and import them with, for example, ``orders import library/basic`` - `prospect`: add new ``--show`` option to give the player control over which report sections are shown. e.g. ``prospect all --show ores`` will just show information on ores. ## Documentation diff --git a/docs/guides/examples-guide.rst b/docs/guides/examples-guide.rst index 8ea463d8c..58446506e 100644 --- a/docs/guides/examples-guide.rst +++ b/docs/guides/examples-guide.rst @@ -48,105 +48,6 @@ it is useful (and customizable) for any fort. It includes the following config: settings won't be overridden. - Enables `automelt`, `tailor`, `zone`, `nestboxes`, and `autonestbox`. -The ``orders/`` subfolder -------------------------- - -The :source:`orders/ ` subfolder contains manager orders -that, along with the ``onMapLoad_dreamfort.init`` file above, allow a fort to be -self-sustaining. Copy them to your ``dfhack-config/orders/`` folder and import -as required with the `orders` DFHack plugin. - -:source:`basic.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This collection of orders handles basic fort necessities: - -- prepared meals and food products (and by-products like oil) -- booze/mead -- thread/cloth/dye -- pots/jugs/buckets/mugs -- bags of leather, cloth, silk, and yarn -- crafts and totems from otherwise unusable by-products -- mechanisms/cages -- splints/crutches -- lye/soap -- ash/potash -- beds/wheelbarrows/minecarts -- scrolls - -You should import it as soon as you have enough dwarves to perform the tasks. -Right after the first migration wave is usually a good time. - -:source:`furnace.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This collection creates basic items that require heat. It is separated out from -``basic.json`` to give players the opportunity to set up magma furnaces first in -order to save resources. It handles: - -- charcoal (including smelting of bituminous coal and lignite) -- pearlash -- sand -- green/clear/crystal glass -- adamantine processing -- item melting - -Orders are missing for plaster powder until DF :bug:`11803` is fixed. - -:source:`military.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This collection adds high-volume smelting jobs for military-grade metal ores and -produces weapons and armor: - -- leather backpacks/waterskins/cloaks/quivers/armor -- bone/wooden bolts -- smelting for platinum, silver, steel, bronze, bismuth bronze, and copper (and - their dependencies) -- bronze/bismuth bronze/copper bolts -- platinum/silver/steel/iron/bismuth bronze/bronze/copper weapons and armor, - with checks to ensure only the best available materials are being used - -If you set a stockpile to take weapons and armor of less than masterwork quality -and turn on `automelt` (like what `dreamfort` provides on its industry level), -these orders will automatically upgrade your military equipment to masterwork. -Make sure you have a lot of fuel (or magma forges and furnaces) before you turn -``automelt`` on, though! - -This file should only be imported, of course, if you need to equip a military. - -:source:`smelting.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This collection adds smelting jobs for all ores. It includes handling the ores -already managed by ``military.json``, but has lower limits. This ensures all -ores will be covered if a player imports ``smelting`` but not ``military``, but -the higher-volume ``military`` orders will take priority if both are imported. - -:source:`rockstock.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This collection of orders keeps a small stock of all types of rock furniture. -This allows you to do ad-hoc furnishings of guildhalls, libraries, temples, or -other rooms with `buildingplan` and your masons will make sure there is always -stock on hand to fulfill the plans. - -:source:`glassstock.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Similar to ``rockstock`` above, this collection keeps a small stock of all types -of glass furniture. If you have a functioning glass industry, this is more -sustainable than ``rockstock`` since you can never run out of sand. If you have -plenty of rock and just want the variety, you can import both ``rockstock`` and -``glassstock`` to get a mixture of rock and glass furnishings in your fort. - -There are a few items that ``glassstock`` produces that ``rockstock`` does not, -since there are some items that can not be made out of rock, for example: - -- tubes and corkscrews for building magma-safe screw pumps -- windows -- terrariums (as an alternative to wooden cages) - The ``professions/`` subfolder ------------------------------ diff --git a/library/modules/Filesystem.cpp b/library/modules/Filesystem.cpp index e0d0bc8c2..a182ac062 100644 --- a/library/modules/Filesystem.cpp +++ b/library/modules/Filesystem.cpp @@ -246,6 +246,7 @@ static int listdir_recursive_impl (std::string prefix, std::string path, int err = Filesystem::listdir(prefixed_path, curdir_files); if (err) return err; + bool out_of_depth = false; for (auto file = curdir_files.begin(); file != curdir_files.end(); ++file) { if (*file == "." || *file == "..") @@ -254,6 +255,12 @@ static int listdir_recursive_impl (std::string prefix, std::string path, std::string path_file = path + *file; if (Filesystem::isdir(prefixed_file)) { + if (depth == 0) + { + out_of_depth = true; + continue; + } + files.insert(std::pair(include_prefix ? prefixed_file : path_file, true)); err = listdir_recursive_impl(prefix, path_file + "/", files, depth - 1, include_prefix); if (err) @@ -264,7 +271,7 @@ static int listdir_recursive_impl (std::string prefix, std::string path, files.insert(std::pair(include_prefix ? prefixed_file : path_file, false)); } } - return 0; + return out_of_depth ? -1 : 0; } int Filesystem::listdir_recursive (std::string dir, std::map &files, diff --git a/plugins/orders.cpp b/plugins/orders.cpp index fb678ca6d..9e1bf3e1a 100644 --- a/plugins/orders.cpp +++ b/plugins/orders.cpp @@ -41,6 +41,7 @@ DFHACK_PLUGIN("orders"); REQUIRE_GLOBAL(world); static const std::string ORDERS_DIR = "dfhack-config/orders"; +static const std::string ORDERS_LIBRARY_DIR = "dfhack-config/orders/library"; static command_result orders_command(color_ostream & out, std::vector & parameters); @@ -53,7 +54,7 @@ DFhackCExport command_result plugin_init(color_ostream & out, std::vector files; + if (0 < Filesystem::listdir_recursive(ORDERS_LIBRARY_DIR, files, 0, false)) { + // if the library directory doesn't exist, just skip it + return; + } + + if (files.empty()) { + // if no files in the library directory, just skip it + return; + } + + for (auto it : files) + { + if (it.second) + continue; // skip directories + std::string name = it.first; + if (name.length() <= 5 || name.rfind(".json") != name.length() - 5) + continue; // skip non-.json files + name.resize(name.length() - 5); + out << "library/" << name << std::endl; + } +} + static command_result orders_list_command(color_ostream & out) { // use listdir_recursive instead of listdir even though orders doesn't @@ -150,6 +175,8 @@ static command_result orders_list_command(color_ostream & out) out << name << std::endl; } + list_library(out); + return CR_OK; } @@ -889,12 +916,20 @@ static command_result orders_import(color_ostream &out, Json::Value &orders) static command_result orders_import_command(color_ostream & out, const std::string & name) { - if (!is_safe_filename(out, name)) + std::string fname = name; + bool is_library = false; + if (0 == name.find("library/")) { + is_library = true; + fname = name.substr(8); + } + + if (!is_safe_filename(out, fname)) { return CR_WRONG_USAGE; } - const std::string filename(ORDERS_DIR + "/" + name + ".json"); + const std::string filename((is_library ? ORDERS_LIBRARY_DIR : ORDERS_DIR) + + "/" + fname + ".json"); Json::Value orders; { From 9f44fd3f723e887587ebe3c54a20a51dbe0de059 Mon Sep 17 00:00:00 2001 From: Myk Date: Wed, 6 Jul 2022 07:21:26 -0700 Subject: [PATCH 022/111] [manipulator] add the professions library (#2234) * move professions out of the examples folder * install professions into professions/library * guard unguarded header from multiple inclusion * load and display library professions * update changelog * move example professions docs from examples guide * update dreamfort documentation * note that professions folder has changed * Fix bad merge --- data/CMakeLists.txt | 3 + data/blueprints/library/dreamfort.csv | 8 +- data/{examples => }/professions/Chef | 2 +- data/{examples => }/professions/Craftsdwarf | 2 +- data/{examples => }/professions/Doctor | 2 +- data/{examples => }/professions/Farmer | 2 +- data/{examples => }/professions/Fisherdwarf | 2 +- data/{examples => }/professions/Hauler | 2 +- data/{examples => }/professions/Laborer | 2 +- data/{examples => }/professions/Marksdwarf | 2 +- data/{examples => }/professions/Mason | 2 +- data/{examples => }/professions/Meleedwarf | 2 +- data/{examples => }/professions/Migrant | 2 +- data/{examples => }/professions/Miner | 2 +- data/{examples => }/professions/Outdoorsdwarf | 2 +- data/{examples => }/professions/Smith | 2 +- data/{examples => }/professions/StartManager | 2 +- data/{examples => }/professions/Tailor | 2 +- docs/Plugins.rst | 117 ++++++++++++++++- docs/changelog.txt | 4 +- docs/guides/examples-guide.rst | 122 ------------------ plugins/listcolumn.h | 2 + plugins/manipulator.cpp | 59 ++++++--- 23 files changed, 183 insertions(+), 164 deletions(-) rename data/{examples => }/professions/Chef (92%) rename data/{examples => }/professions/Craftsdwarf (92%) rename data/{examples => }/professions/Doctor (93%) rename data/{examples => }/professions/Farmer (83%) rename data/{examples => }/professions/Fisherdwarf (90%) rename data/{examples => }/professions/Hauler (92%) rename data/{examples => }/professions/Laborer (92%) rename data/{examples => }/professions/Marksdwarf (89%) rename data/{examples => }/professions/Mason (92%) rename data/{examples => }/professions/Meleedwarf (90%) rename data/{examples => }/professions/Migrant (92%) rename data/{examples => }/professions/Miner (66%) rename data/{examples => }/professions/Outdoorsdwarf (92%) rename data/{examples => }/professions/Smith (92%) rename data/{examples => }/professions/StartManager (96%) rename data/{examples => }/professions/Tailor (91%) diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt index 72df7272a..26befdb34 100644 --- a/data/CMakeLists.txt +++ b/data/CMakeLists.txt @@ -7,6 +7,9 @@ install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/orders/ install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/examples/ DESTINATION "${DFHACK_DATA_DESTINATION}/examples") +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/professions/ + DESTINATION dfhack-config/professions/library) + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/blueprints/ DESTINATION blueprints FILES_MATCHING PATTERN "*" diff --git a/data/blueprints/library/dreamfort.csv b/data/blueprints/library/dreamfort.csv index ec1a05c51..ff0b8b4fc 100644 --- a/data/blueprints/library/dreamfort.csv +++ b/data/blueprints/library/dreamfort.csv @@ -29,9 +29,9 @@ "Dreamfort works best at an embark site that is flat and has at least one soil layer. New players should avoid embarks with aquifers if they are not prepared to deal with them. Bring picks for mining, an axe for woodcutting, and an anvil for a forge. Bring a few blocks to speed up initial workshop construction as well. That's all you really need, but see the example embark profile in the online spreadsheets for a more complete setup." "" "Other DFHack commands also work very well with Dreamfort, such as autofarm, autonestbox, prioritize, seedwatch, tailor, and, of course, buildingplan. An init file that gets everything configured for you is distributed with DFHack as hack/examples/init/onMapLoad_dreamfort.init." -Put that file in your Dwarf Fortress directory -- the same directory that has dfhack.init. +Put that file in your dfhack-config/init/ directory -- the same directory that has dfhack.init. "" -"Also copy the files in hack/examples/professions/ to professions/. We'll be using these files later. See https://docs.dfhack.org/en/stable/docs/guides/examples-guide.html for more information, including suggestions on how many dwarves of each profession you are likely to need at each stage of fort maturity." +"Also check out https://docs.dfhack.org/en/stable/docs/Plugins.html#professions for more information on the default labor professions that are distributed with DFHack, including suggestions on how many dwarves of each profession you are likely to need at each stage of fort maturity." "" "Once you have your starting surface workshops up and running, you might want to configure buildingplan (in its global settings, accessible from any building placement screen, e.g.: b-a-G) to only use blocks for constructions so it won't use your precious wood, boulders, and bars to build floors and walls. If you bring at least 7 blocks with you on embark, you can even set this in your onMapLoad.init file like this:" on-new-fortress buildingplan set boulders false; buildingplan set logs false @@ -49,9 +49,7 @@ interactively." "Here is the recommended order for Dreamfort commands. You can copy/paste the command lines directly into the DFHack terminal, or, if you prefer, you can run the blueprints in the UI with gui/quickfort. See the walkthroughs (the ""help"" blueprints) for context and details. Also remember to read the messages the blueprints print out after you run them so you don't miss any important manual steps." "" -- Preparation (before you embark!) -- -Copy hack/examples/init/onMapLoad_dreamfort.init to your DF directory -Optionally copy the premade profession definitions from hack/examples/professions/ to the professions/ directory -Optionally copy the premade Dreamfort embark profile from the online spreadsheets to the data/init/embark_profiles.txt file +Copy hack/examples/init/onMapLoad_dreamfort.init to your dfhack-config/init directory inside your DF installation "" -- Set settings and preload initial orders -- quickfort run library/dreamfort.csv -n /setup,# Run before making any manual adjustments to settings! Run the /setup_help blueprint for details on what this blueprint does. diff --git a/data/examples/professions/Chef b/data/professions/Chef similarity index 92% rename from data/examples/professions/Chef rename to data/professions/Chef index 1f777c81a..218e08301 100644 --- a/data/examples/professions/Chef +++ b/data/professions/Chef @@ -1,4 +1,4 @@ -NAME Chef +NAME library/Chef BUTCHER TANNER COOK diff --git a/data/examples/professions/Craftsdwarf b/data/professions/Craftsdwarf similarity index 92% rename from data/examples/professions/Craftsdwarf rename to data/professions/Craftsdwarf index 29ed1ad0d..8c9707f17 100644 --- a/data/examples/professions/Craftsdwarf +++ b/data/professions/Craftsdwarf @@ -1,4 +1,4 @@ -NAME Craftsdwarf +NAME library/Craftsdwarf WOOD_CRAFT STONE_CRAFT BONE_CARVE diff --git a/data/examples/professions/Doctor b/data/professions/Doctor similarity index 93% rename from data/examples/professions/Doctor rename to data/professions/Doctor index 893708947..14959f599 100644 --- a/data/examples/professions/Doctor +++ b/data/professions/Doctor @@ -1,4 +1,4 @@ -NAME Doctor +NAME library/Doctor ANIMALCARE DIAGNOSE SURGERY diff --git a/data/examples/professions/Farmer b/data/professions/Farmer similarity index 83% rename from data/examples/professions/Farmer rename to data/professions/Farmer index 149b3c368..0b2801f4c 100644 --- a/data/examples/professions/Farmer +++ b/data/professions/Farmer @@ -1,4 +1,4 @@ -NAME Farmer +NAME library/Farmer PLANT MILLER BREWER diff --git a/data/examples/professions/Fisherdwarf b/data/professions/Fisherdwarf similarity index 90% rename from data/examples/professions/Fisherdwarf rename to data/professions/Fisherdwarf index 3c369e61d..1b5d7a1a8 100644 --- a/data/examples/professions/Fisherdwarf +++ b/data/professions/Fisherdwarf @@ -1,4 +1,4 @@ -NAME Fisherdwarf +NAME library/Fisherdwarf FISH CLEAN_FISH DISSECT_FISH diff --git a/data/examples/professions/Hauler b/data/professions/Hauler similarity index 92% rename from data/examples/professions/Hauler rename to data/professions/Hauler index a108b1bfd..4fd8b89f8 100644 --- a/data/examples/professions/Hauler +++ b/data/professions/Hauler @@ -1,4 +1,4 @@ -NAME Hauler +NAME library/Hauler FEED_WATER_CIVILIANS SIEGEOPERATE MECHANIC diff --git a/data/examples/professions/Laborer b/data/professions/Laborer similarity index 92% rename from data/examples/professions/Laborer rename to data/professions/Laborer index bca22a302..ccd688428 100644 --- a/data/examples/professions/Laborer +++ b/data/professions/Laborer @@ -1,4 +1,4 @@ -NAME Laborer +NAME library/Laborer SOAP_MAKER BURN_WOOD POTASH_MAKING diff --git a/data/examples/professions/Marksdwarf b/data/professions/Marksdwarf similarity index 89% rename from data/examples/professions/Marksdwarf rename to data/professions/Marksdwarf index 583afd08e..f34d67cd2 100644 --- a/data/examples/professions/Marksdwarf +++ b/data/professions/Marksdwarf @@ -1,4 +1,4 @@ -NAME Marksdwarf +NAME library/Marksdwarf MECHANIC HAUL_STONE HAUL_WOOD diff --git a/data/examples/professions/Mason b/data/professions/Mason similarity index 92% rename from data/examples/professions/Mason rename to data/professions/Mason index 5f996f448..1977d2df5 100644 --- a/data/examples/professions/Mason +++ b/data/professions/Mason @@ -1,4 +1,4 @@ -NAME Mason +NAME library/Mason MASON CUT_GEM ENCRUST_GEM diff --git a/data/examples/professions/Meleedwarf b/data/professions/Meleedwarf similarity index 90% rename from data/examples/professions/Meleedwarf rename to data/professions/Meleedwarf index 8eac5ffd6..6a8338fea 100644 --- a/data/examples/professions/Meleedwarf +++ b/data/professions/Meleedwarf @@ -1,4 +1,4 @@ -NAME Meleedwarf +NAME library/Meleedwarf RECOVER_WOUNDED MECHANIC HAUL_STONE diff --git a/data/examples/professions/Migrant b/data/professions/Migrant similarity index 92% rename from data/examples/professions/Migrant rename to data/professions/Migrant index 59fd70405..23a3eeddb 100644 --- a/data/examples/professions/Migrant +++ b/data/professions/Migrant @@ -1,4 +1,4 @@ -NAME Migrant +NAME library/Migrant FEED_WATER_CIVILIANS SIEGEOPERATE MECHANIC diff --git a/data/examples/professions/Miner b/data/professions/Miner similarity index 66% rename from data/examples/professions/Miner rename to data/professions/Miner index 7be84512d..3170969d9 100644 --- a/data/examples/professions/Miner +++ b/data/professions/Miner @@ -1,4 +1,4 @@ -NAME Miner +NAME library/Miner MINE DETAIL RECOVER_WOUNDED diff --git a/data/examples/professions/Outdoorsdwarf b/data/professions/Outdoorsdwarf similarity index 92% rename from data/examples/professions/Outdoorsdwarf rename to data/professions/Outdoorsdwarf index a3f696419..31dbd2ad8 100644 --- a/data/examples/professions/Outdoorsdwarf +++ b/data/professions/Outdoorsdwarf @@ -1,4 +1,4 @@ -NAME Outdoorsdwarf +NAME library/Outdoorsdwarf CARPENTER BOWYER CUTWOOD diff --git a/data/examples/professions/Smith b/data/professions/Smith similarity index 92% rename from data/examples/professions/Smith rename to data/professions/Smith index f5fe0f982..7809d80e1 100644 --- a/data/examples/professions/Smith +++ b/data/professions/Smith @@ -1,4 +1,4 @@ -NAME Smith +NAME library/Smith FORGE_WEAPON FORGE_ARMOR FORGE_FURNITURE diff --git a/data/examples/professions/StartManager b/data/professions/StartManager similarity index 96% rename from data/examples/professions/StartManager rename to data/professions/StartManager index 751d75cc9..a70c705bf 100644 --- a/data/examples/professions/StartManager +++ b/data/professions/StartManager @@ -1,4 +1,4 @@ -NAME StartManager +NAME library/StartManager CUTWOOD ANIMALCARE DIAGNOSE diff --git a/data/examples/professions/Tailor b/data/professions/Tailor similarity index 91% rename from data/examples/professions/Tailor rename to data/professions/Tailor index 74ac03a93..f7986553a 100644 --- a/data/examples/professions/Tailor +++ b/data/professions/Tailor @@ -1,4 +1,4 @@ -NAME Tailor +NAME library/Tailor DYER LEATHER WEAVER diff --git a/docs/Plugins.rst b/docs/Plugins.rst index c73bfab3a..8acddfc6d 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -3096,8 +3096,121 @@ To apply a profession, either highlight a single dwarf or select multiple with :kbd:`x`, and press :kbd:`p` to select the profession to apply. All labors for the selected dwarves will be reset to the labors of the chosen profession. -Professions are saved as human-readable text files in the "professions" folder -within the DF folder, and can be edited or deleted there. +Professions are saved as human-readable text files in the +``dfhack-config/professions`` folder within the DF folder, and can be edited or +deleted there. + +The professions library +~~~~~~~~~~~~~~~~~~~~~~~ + +The manipulator plugin comes with a library of professions that you can assign +to your dwarves. + +If you'd rather use Dwarf Therapist to manage your labors, it is easy to import +these professions to DT and use them there. Simply assign the professions you +want to import to a dwarf. Once you have assigned a profession to at least one +dwarf, you can select "Import Professions from DF" in the DT "File" menu. The +professions will then be available for use in DT. + +In the charts below, the "At Start" and "Max" columns indicate the approximate +number of dwarves of each profession that you are likely to need at the start of +the game and how many you are likely to need in a mature fort. These are just +approximations. Your playstyle may demand more or fewer of each profession. + +============= ======== ===== ================================================= +Profession At Start Max Description +============= ======== ===== ================================================= +Chef 0 3 Buchery, Tanning, and Cooking. It is important to + focus just a few dwarves on cooking since + well-crafted meals make dwarves very happy. They + are also an excellent trade good. +Craftsdwarf 0 4-6 All labors used at Craftsdwarf's workshops, + Glassmaker's workshops, and kilns. +Doctor 0 2-4 The full suite of medical labors, plus Animal + Caretaking for those using the dwarfvet plugin. +Farmer 1 4 Food- and animal product-related labors. This + profession also has the ``Alchemist`` labor + enabled since they need to focus on food-related + jobs, though you might want to disable + ``Alchemist`` for your first farmer until there + are actual farming duties to perform. +Fisherdwarf 0 0-1 Fishing and fish cleaning. If you assign this + profession to any dwarf, be prepared to be + inundated with fish. Fisherdwarves *never stop + fishing*. Be sure to also run ``prioritize -a + PrepareRawFish ExtractFromRawFish`` or else + caught fish will just be left to rot. +Hauler 0 >20 All hauling labors plus Siege Operating, Mechanic + (so haulers can assist in reloading traps) and + Architecture (so haulers can help build massive + windmill farms and pump stacks). As you + accumulate enough Haulers, you can turn off + hauling labors for other dwarves so they can + focus on their skilled tasks. You may also want + to restrict your Mechanic's workshops to only + skilled mechanics so your haulers don't make + low-quality mechanisms. +Laborer 0 10-12 All labors that don't improve quality with skill, + such as Soapmaking and furnace labors. +Marksdwarf 0 10-30 Similar to Hauler. See the description for + Meleedwarf below for more details. +Mason 2 2-4 Masonry and Gem Cutting/Encrusting. In the early + game, you may need to run "`prioritize` + ConstructBuilding" to get your masons to build + wells and bridges if they are too busy crafting + stone furniture. +Meleedwarf 0 20-50 Similar to Hauler, but without most civilian + labors. This profession is separate from Hauler + so you can find your military dwarves easily. + Meleedwarves and Marksdwarves have Mechanics and + hauling labors enabled so you can temporarily + deactivate your military after sieges and allow + your military dwarves to help clean up. +Migrant 0 0 You can assign this profession to new migrants + temporarily while you sort them into professions. + Like Marksdwarf and Meleedwarf, the purpose of + this profession is so you can find your new + dwarves more easily. +Miner 2 2-10 Mining and Engraving. This profession also has + the ``Alchemist`` labor enabled, which disables + hauling for those using the `autohauler` plugin. + Once the need for Miners tapers off in the late + game, dwarves with this profession make good + military dwarves, wielding their picks as + weapons. +Outdoorsdwarf 1 2-4 Carpentry, Bowyery, Woodcutting, Animal Training, + Trapping, Plant Gathering, Beekeeping, and Siege + Engineering. +Smith 0 2-4 Smithing labors. You may want to specialize your + Smiths to focus on a single smithing skill to + maximize equipment quality. +StartManager 1 0 All skills not covered by the other starting + professions (Miner, Mason, Outdoorsdwarf, and + Farmer), plus a few overlapping skills to + assist in critical tasks at the beginning of the + game. Individual labors should be turned off as + migrants are assigned more specialized + professions that cover them, and the StartManager + dwarf can eventually convert to some other + profession. +Tailor 0 2 Textile industry labors: Dying, Leatherworking, + Weaving, and Clothesmaking. +============= ======== ===== ================================================= + +A note on autohauler +~~~~~~~~~~~~~~~~~~~~ + +These profession definitions are designed to work well with or without the +`autohauler` plugin (which helps to keep your dwarves focused on skilled labors +instead of constantly being distracted by hauling). If you do want to use +autohauler, adding the following lines to your ``onMapLoad.init`` file will +configure it to let the professions manage the "Feed water to civilians" and +"Recover wounded" labors instead of enabling those labors for all hauling +dwarves:: + + on-new-fortress enable autohauler + on-new-fortress autohauler FEED_WATER_CIVILIANS allow + on-new-fortress autohauler RECOVER_WOUNDED allow .. _mousequery: diff --git a/docs/changelog.txt b/docs/changelog.txt index ccc94701c..29e0d8e22 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -41,8 +41,10 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - ``job.removeJob()``: ensure jobs are removed from the world list when they are canceled ## Misc Improvements -- ``materials.ItemTraitsDialog``: added a default ``on_select``-handler which toggles the traits. +- `manipulator`: add a library of useful default professions +- `manipulator`: move professions configuration from ``professions/`` to ``dfhack-config/professions/`` to keep it together with other dfhack configuration. If you have saved professions that you would like to keep, please manually move them to the new folder. +- ``materials.ItemTraitsDialog``: added a default ``on_select``-handler which toggles the traits. - `orders`: added useful library of manager orders. see them with ``orders list`` and import them with, for example, ``orders import library/basic`` - `prospect`: add new ``--show`` option to give the player control over which report sections are shown. e.g. ``prospect all --show ores`` will just show information on ores. diff --git a/docs/guides/examples-guide.rst b/docs/guides/examples-guide.rst index 58446506e..b699b8204 100644 --- a/docs/guides/examples-guide.rst +++ b/docs/guides/examples-guide.rst @@ -47,125 +47,3 @@ it is useful (and customizable) for any fort. It includes the following config: fortress is first started, so any later changes you make to autobutcher settings won't be overridden. - Enables `automelt`, `tailor`, `zone`, `nestboxes`, and `autonestbox`. - -The ``professions/`` subfolder ------------------------------- - -The :source:`professions/ ` subfolder contains -professions, or sets of related labors, that you can assign to your dwarves with -the DFHack `manipulator` plugin. Copy them into the ``professions/`` -subdirectory under the main Dwarf Fortress folder (you may have to create this -subdirectory) and assign them to your dwarves in the manipulator UI, accessible -from the ``units`` screen via the :kbd:`l` hotkey. Make sure that the -``manipulator`` plugin is enabled in your ``dfhack.init`` file! You can assign a -profession to a dwarf by selecting the dwarf in the ``manipulator`` UI and -hitting :kbd:`p`. The list of professions that you copied into the -``professions/`` folder will show up for you to choose from. This is very useful -for assigning roles to new migrants to ensure that all the tasks in your fort -have adequate numbers of dwarves attending to them. - -If you'd rather use Dwarf Therapist to manage your labors, it is easy to import -these professions to DT and use them there. Simply assign the professions you -want to import to a dwarf. Once you have assigned a profession to at least one -dwarf, you can select "Import Professions from DF" in the DT "File" menu. The -professions will then be available for use in DT. - -In the charts below, the "At Start" and "Max" columns indicate the approximate -number of dwarves of each profession that you are likely to need at the start of -the game and how many you are likely to need in a mature fort. - -============= ======== ===== ================================================= -Profession At Start Max Description -============= ======== ===== ================================================= -Chef 0 3 Buchery, Tanning, and Cooking. It is important to - focus just a few dwarves on cooking since - well-crafted meals make dwarves very happy. They - are also an excellent trade good. -Craftsdwarf 0 4-6 All labors used at Craftsdwarf's workshops, - Glassmaker's workshops, and kilns. -Doctor 0 2-4 The full suite of medical labors, plus Animal - Caretaking for those using the dwarfvet plugin. -Farmer 1 4 Food- and animal product-related labors. This - profession also has the ``Alchemist`` labor - enabled since they need to focus on food-related - jobs, though you might want to disable - ``Alchemist`` for your first farmer until there - are actual farming duties to perform. -Fisherdwarf 0 0-1 Fishing and fish cleaning. If you assign this - profession to any dwarf, be prepared to be - inundated with fish. Fisherdwarves *never stop - fishing*. Be sure to also run ``prioritize -a - PrepareRawFish ExtractFromRawFish`` (or use the - ``onMapLoad_dreamfort.init`` file above) or else - caught fish will just be left to rot. -Hauler 0 >20 All hauling labors plus Siege Operating, Mechanic - (so haulers can assist in reloading traps) and - Architecture (so haulers can help build massive - windmill farms and pump stacks). As you - accumulate enough Haulers, you can turn off - hauling labors for other dwarves so they can - focus on their skilled tasks. You may also want - to restrict your Mechanic's workshops to only - skilled mechanics so your haulers don't make - low-quality mechanisms. -Laborer 0 10-12 All labors that don't improve quality with skill, - such as Soapmaking and furnace labors. -Marksdwarf 0 10-30 Similar to Hauler. See the description for - Meleedwarf below for more details. -Mason 2 2-4 Masonry, Gem Cutting/Encrusting, and - Architecture. In the early game, you may need to - run "`prioritize` ConstructBuilding" to get your - masons to build wells and bridges if they are too - busy crafting stone furniture. -Meleedwarf 0 20-50 Similar to Hauler, but without most civilian - labors. This profession is separate from Hauler - so you can find your military dwarves easily. - Meleedwarves and Marksdwarves have Mechanics and - hauling labors enabled so you can temporarily - deactivate your military after sieges and allow - your military dwarves to help clean up. -Migrant 0 0 You can assign this profession to new migrants - temporarily while you sort them into professions. - Like Marksdwarf and Meleedwarf, the purpose of - this profession is so you can find your new - dwarves more easily. -Miner 2 2-10 Mining and Engraving. This profession also has - the ``Alchemist`` labor enabled, which disables - hauling for those using the `autohauler` plugin. - Once the need for Miners tapers off in the late - game, dwarves with this profession make good - military dwarves, wielding their picks as - weapons. -Outdoorsdwarf 1 2-4 Carpentry, Bowyery, Woodcutting, Animal Training, - Trapping, Plant Gathering, Beekeeping, and Siege - Engineering. -Smith 0 2-4 Smithing labors. You may want to specialize your - Smiths to focus on a single smithing skill to - maximize equipment quality. -StartManager 1 0 All skills not covered by the other starting - professions (Miner, Mason, Outdoorsdwarf, and - Farmer), plus a few overlapping skills to - assist in critical tasks at the beginning of the - game. Individual labors should be turned off as - migrants are assigned more specialized - professions that cover them, and the StartManager - dwarf can eventually convert to some other - profession. -Tailor 0 2 Textile industry labors: Dying, Leatherworking, - Weaving, and Clothesmaking. -============= ======== ===== ================================================= - -A note on autohauler -~~~~~~~~~~~~~~~~~~~~ - -These profession definitions are designed to work well with or without the -`autohauler` plugin (which helps to keep your dwarves focused on skilled labors -instead of constantly being distracted by hauling). If you do want to use -autohauler, adding the following lines to your ``onMapLoad.init`` file will -configure it to let the professions manage the "Feed water to civilians" and -"Recover wounded" labors instead of enabling those labors for all hauling -dwarves:: - - on-new-fortress enable autohauler - on-new-fortress autohauler FEED_WATER_CIVILIANS allow - on-new-fortress autohauler RECOVER_WOUNDED allow diff --git a/plugins/listcolumn.h b/plugins/listcolumn.h index 08d48bd58..a9a7dc23c 100644 --- a/plugins/listcolumn.h +++ b/plugins/listcolumn.h @@ -1,3 +1,5 @@ +#pragma once + #include "uicommon.h" using df::global::enabler; diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 4604cd7ea..2d3f3bc0a 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -652,10 +652,12 @@ namespace unit_ops { struct ProfessionTemplate { std::string name; + std::string displayName; + bool library; bool mask; std::vector labors; - bool load(string directory, string file) + bool load(string directory, string file, bool isLibrary) { cerr << "Attempt to load " << file << endl; std::ifstream infile(directory + "/" + file); @@ -663,14 +665,25 @@ struct ProfessionTemplate return false; } + library = isLibrary; + std::string line; name = file; // If no name is given we default to the filename + displayName = name; mask = false; while (std::getline(infile, line)) { if (strcmp(line.substr(0,5).c_str(),"NAME ")==0) { auto nextInd = line.find(' '); - name = line.substr(nextInd + 1); + displayName = line.substr(nextInd + 1); + name = displayName; + size_t slashpos = name.find_first_of("\\/"); + while (name.npos != slashpos) { + name = name.substr(slashpos + 1); + slashpos = name.find_first_of("\\/"); + } + if (name == "") + name = file; continue; } if (line == "MASK") @@ -745,7 +758,8 @@ struct ProfessionTemplate } }; -static std::string professions_folder = Filesystem::getcwd() + "/professions"; +static std::string professions_folder = "dfhack-config/professions"; +static std::string professions_library_folder = "dfhack-config/professions/library"; class ProfessionTemplateManager { public: @@ -760,27 +774,21 @@ public: } void load() { - vector files; - cerr << "Attempting to load professions: " << professions_folder.c_str() << endl; if (!Filesystem::isdir(professions_folder) && !Filesystem::mkdir(professions_folder)) { cerr << professions_folder << ": Does not exist and cannot be created" << endl; return; } - Filesystem::listdir(professions_folder, files); - std::sort(files.begin(), files.end()); - for(size_t i = 0; i < files.size(); i++) - { - if (files[i] == "." || files[i] == "..") - continue; + _load(professions_folder, false); + _load(professions_library_folder, true); - ProfessionTemplate t; - if (t.load(professions_folder, files[i])) - { - templates.push_back(t); - } - } + // sort alphabetically by display name, with user data above library data + std::sort(templates.begin(), templates.end(), + [](const ProfessionTemplate &a, const ProfessionTemplate &b) { + return (a.library == b.library && a.displayName < b.displayName) + || (b.library && !a.library); + }); } void save_from_unit(UnitInfo *unit) { @@ -792,6 +800,21 @@ public: t.save(professions_folder); reload(); } + +private: + void _load(const std::string &path, bool library) { + vector files; + Filesystem::listdir(path, files); + for (auto &fname : files) { + if (Filesystem::isdir(path + "/" + fname)) + continue; + + ProfessionTemplate t; + if (t.load(path, fname, library)) { + templates.push_back(t); + } + } + } }; static ProfessionTemplateManager manager; @@ -992,7 +1015,7 @@ public: manager.reload(); for (size_t i = 0; i < manager.templates.size(); i++) { - std::string name = manager.templates[i].name; + std::string name = manager.templates[i].displayName; if (manager.templates[i].mask) name += " (mask)"; ListEntry elem(name, i); From c7107e9c2356ab2eccec91d807c311396f012af7 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 9 Jul 2022 23:34:57 -0700 Subject: [PATCH 023/111] ignore docs in the scripts repo --- conf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/conf.py b/conf.py index 79b873bf9..ccad5c806 100644 --- a/conf.py +++ b/conf.py @@ -283,6 +283,7 @@ exclude_patterns = [ 'build*', 'docs/_auto/news*', 'docs/_changelogs/', + 'scripts/docs/', ] # The reST default role (used for this markup: `text`) to use for all From 9c32a52cb08af66603a593b70f9dd42d5e7c9ff4 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 9 Jul 2022 23:43:35 -0700 Subject: [PATCH 024/111] actually ignore the script docs --- conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf.py b/conf.py index ccad5c806..5ec58fc98 100644 --- a/conf.py +++ b/conf.py @@ -283,7 +283,7 @@ exclude_patterns = [ 'build*', 'docs/_auto/news*', 'docs/_changelogs/', - 'scripts/docs/', + 'scripts/docs/*', ] # The reST default role (used for this markup: `text`) to use for all From b560bcc25654cf3fcf2410b1c992ca0f141ad509 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sun, 10 Jul 2022 07:17:43 +0000 Subject: [PATCH 025/111] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 6bc683d2c..ef5fc459c 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 6bc683d2ca42ef467465b0905513ad590a7dc4c2 +Subproject commit ef5fc459c4f0a65a9fd709c14fed0c5892c9eebd From 28e15162a5921fab465a221ecfa998df33c7b3c7 Mon Sep 17 00:00:00 2001 From: Myk Date: Sun, 10 Jul 2022 08:54:55 -0700 Subject: [PATCH 026/111] reorganize init scripts into dfhack-config (#2232) * reorganize init scripts into dfhack-config allows player init scripts to build on defaults instead of replace them this also moves the init scripts out of the main df directory * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * escape asterisks in docs * remove unneeded dfhack.init file creation for test * write the test init script to the new init dir * create the init dir before trying to write a file * rename default init files for clarity * Update changelog * Update docs/changelog.txt Co-authored-by: Alan * Try to get buildmaster to work with old branches * Update changelog * get keybindings from all init scripts * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix spacing in changelog * split default loading into its own file * update docs with new changes * update help text wording in default init files * Apply suggestions from code review Co-authored-by: Alan * Alphabetize changelog * Update onMapLoad.default.init * Update onMapLoad.init * Update Core.rst Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Alan --- .github/workflows/build.yml | 1 - ci/run-tests.py | 11 +- conf.py | 36 +++-- data/CMakeLists.txt | 3 + data/init/dfhack.default.init | 8 + .../init/dfhack.keybindings.init | 140 +----------------- data/init/dfhack.tools.init | 131 ++++++++++++++++ data/init/onLoad.default.init | 5 + data/init/onMapLoad.default.init | 6 + data/init/onMapUnload.default.init | 5 + data/init/onUnload.default.init | 5 + dfhack-config/init/default.dfhack.init | 7 + dfhack-config/init/default.onLoad.init | 7 + dfhack-config/init/default.onMapLoad.init | 7 + dfhack-config/init/default.onMapUnload.init | 7 + dfhack-config/init/default.onUnload.init | 7 + dfhack-config/init/dfhack.init | 5 + dfhack-config/init/onLoad.init | 6 + dfhack-config/init/onMapLoad.init | 5 + dfhack-config/init/onMapUnload.init | 5 + dfhack-config/init/onUnload.init | 4 + docs/Core.rst | 104 +++++++------ docs/changelog.txt | 2 +- library/CMakeLists.txt | 3 - library/Core.cpp | 17 ++- 25 files changed, 329 insertions(+), 208 deletions(-) create mode 100644 data/init/dfhack.default.init rename dfhack.init-example => data/init/dfhack.keybindings.init (60%) create mode 100644 data/init/dfhack.tools.init create mode 100644 data/init/onLoad.default.init create mode 100644 data/init/onMapLoad.default.init create mode 100644 data/init/onMapUnload.default.init create mode 100644 data/init/onUnload.default.init create mode 100644 dfhack-config/init/default.dfhack.init create mode 100644 dfhack-config/init/default.onLoad.init create mode 100644 dfhack-config/init/default.onMapLoad.init create mode 100644 dfhack-config/init/default.onMapUnload.init create mode 100644 dfhack-config/init/default.onUnload.init create mode 100644 dfhack-config/init/dfhack.init create mode 100644 dfhack-config/init/onLoad.init create mode 100644 dfhack-config/init/onMapLoad.init create mode 100644 dfhack-config/init/onMapUnload.init create mode 100644 dfhack-config/init/onUnload.init diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2c710d91a..bef01888f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -100,7 +100,6 @@ jobs: run: | export TERM=dumb status=0 - mv "$DF_FOLDER"/dfhack.init-example "$DF_FOLDER"/dfhack.init script -qe -c "python ci/run-tests.py --headless --keep-status \"$DF_FOLDER\"" || status=$((status + 1)) python ci/check-rpc.py "$DF_FOLDER/dfhack-rpc.txt" || status=$((status + 2)) mkdir -p artifacts diff --git a/ci/run-tests.py b/ci/run-tests.py index be2a02b3f..a6a35bc76 100755 --- a/ci/run-tests.py +++ b/ci/run-tests.py @@ -68,7 +68,16 @@ init_contents = change_setting(init_contents, 'FPS', 'YES') if args.headless: init_contents = change_setting(init_contents, 'PRINT_MODE', 'TEXT') -test_init_file = 'dfhackzzz_test.init' # Core sorts these alphabetically +init_path = 'dfhack-config/init' +if not os.path.isdir('hack/init'): + # we're on an old branch that still reads init files from the root dir + init_path = '.' +try: + os.mkdir(init_path) +except OSError as error: + # ignore already exists errors + pass +test_init_file = os.path.join(init_path, 'dfhackzzz_test.init') # Core sorts these alphabetically with open(test_init_file, 'w') as f: f.write(''' devel/dump-rpc dfhack-rpc.txt diff --git a/conf.py b/conf.py index 5ec58fc98..71ba65fb7 100644 --- a/conf.py +++ b/conf.py @@ -24,32 +24,40 @@ import sys # -- Support :dfhack-keybind:`command` ------------------------------------ -# this is a custom directive that pulls info from dfhack.init-example +# this is a custom directive that pulls info from default keybindings from docutils import nodes from docutils.parsers.rst import roles sphinx_major_version = sphinx.version_info[0] -def get_keybinds(): +def get_keybinds(root, files, keybindings): + """Add keybindings in the specified files to the + given keybindings dict. + """ + for file in files: + with open(os.path.join(root, file)) as f: + lines = [l.replace('keybinding add', '').strip() for l in f.readlines() + if l.startswith('keybinding add')] + for k in lines: + first, command = k.split(' ', 1) + bind, context = (first.split('@') + [''])[:2] + if ' ' not in command: + command = command.replace('"', '') + tool = command.split(' ')[0].replace('"', '') + keybindings[tool] = keybindings.get(tool, []) + [ + (command, bind.split('-'), context)] + +def get_all_keybinds(root_dir): """Get the implemented keybinds, and return a dict of {tool: [(full_command, keybinding, context), ...]}. """ - with open('dfhack.init-example') as f: - lines = [l.replace('keybinding add', '').strip() for l in f.readlines() - if l.startswith('keybinding add')] keybindings = dict() - for k in lines: - first, command = k.split(' ', 1) - bind, context = (first.split('@') + [''])[:2] - if ' ' not in command: - command = command.replace('"', '') - tool = command.split(' ')[0].replace('"', '') - keybindings[tool] = keybindings.get(tool, []) + [ - (command, bind.split('-'), context)] + for root, _, files in os.walk(root_dir): + get_keybinds(root, files, keybindings) return keybindings -KEYBINDS = get_keybinds() +KEYBINDS = get_all_keybinds('data/init') # pylint:disable=unused-argument,dangerous-default-value,too-many-arguments diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt index 26befdb34..ea88d4473 100644 --- a/data/CMakeLists.txt +++ b/data/CMakeLists.txt @@ -1,3 +1,6 @@ +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/init/ + DESTINATION "${DFHACK_DATA_DESTINATION}/init") + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/quickfort/ DESTINATION "${DFHACK_DATA_DESTINATION}/data/quickfort") diff --git a/data/init/dfhack.default.init b/data/init/dfhack.default.init new file mode 100644 index 000000000..78dc70450 --- /dev/null +++ b/data/init/dfhack.default.init @@ -0,0 +1,8 @@ +# Default DFHack commands to run on program init + +# Please do not edit this file directly. It will be overwritten with new +# defaults when you update DFHack. Instead, add your configuration to +# dfhack-config/init/dfhack.init + +script hack/init/dfhack.keybindings.init +script hack/init/dfhack.tools.init diff --git a/dfhack.init-example b/data/init/dfhack.keybindings.init similarity index 60% rename from dfhack.init-example rename to data/init/dfhack.keybindings.init index 88528f650..e32a7c58e 100644 --- a/dfhack.init-example +++ b/data/init/dfhack.keybindings.init @@ -1,3 +1,9 @@ +# Default DFHack keybindings + +# Please do not edit this file directly. It will be overwritten with new +# defaults when you update DFHack. Instead, add your configuration to +# dfhack-config/init/dfhack.init + ############################## # Generic dwarfmode bindings # ############################## @@ -164,137 +170,3 @@ keybinding add Shift-B@pet/List/Unit "gui/autobutcher" # view pathable tiles from active cursor keybinding add Alt-Shift-P@dwarfmode/LookAround gui/pathable - -############################ -# UI and game logic tweaks # -############################ - -# stabilize the cursor of dwarfmode when switching menus -tweak stable-cursor - -# stop stacked liquid/bar/thread/cloth items from lasting forever -# if used in reactions that use only a fraction of the dimension. -# might be fixed by DF -# tweak fix-dimensions - -# make reactions requiring containers usable in advmode - the issue is -# that the screen asks for those reagents to be selected directly -tweak advmode-contained - -# support Shift-Enter in Trade and Move Goods to Depot screens for faster -# selection; it selects the current item or stack and scrolls down one line -tweak fast-trade - -# stop the right list in military->positions from resetting to top all the time -tweak military-stable-assign -# in same list, color units already assigned to squads in brown & green -tweak military-color-assigned - -# make crafted cloth items wear out with time like in old versions (bug 6003) -tweak craft-age-wear - -# stop adamantine clothing from wearing out (bug 6481) -#tweak adamantine-cloth-wear - -# Add "Select all" and "Deselect all" options to farm plot menus -tweak farm-plot-select - -# Add Shift-Left/Right controls to import agreement screen -tweak import-priority-category - -# Fixes a crash in the work order contition material list (bug 9905). -tweak condition-material - -# Adds an option to clear currently-bound hotkeys -tweak hotkey-clear - -# Allows lowercase letters in embark profile names, and allows exiting the name prompt without saving -tweak embark-profile-name - -# Reduce performance impact of temperature changes -tweak fast-heat 100 - -# Misc. UI tweaks -tweak block-labors # Prevents labors that can't be used from being toggled -tweak burrow-name-cancel -tweak cage-butcher -tweak civ-view-agreement -tweak do-job-now -tweak eggs-fertile -tweak fps-min -tweak hide-priority -tweak kitchen-prefs-all -tweak kitchen-prefs-empty -tweak max-wheelbarrow -tweak partial-items -tweak shift-8-scroll -tweak stone-status-all -tweak title-start-rename -tweak tradereq-pet-gender - -########################### -# Globally acting plugins # -########################### - -# Display DFHack version on title screen -enable title-version - -# Dwarf Manipulator (simple in-game Dwarf Therapist replacement) -enable manipulator - -# Search tool in various screens (by falconne) -enable search - -# Improved build material selection interface (by falconne) -enable automaterial - -# Other interface improvement tools -enable \ - confirm \ - dwarfmonitor \ - mousequery \ - autogems \ - autodump \ - automelt \ - autotrade \ - buildingplan \ - resume \ - trackstop \ - zone \ - stocks \ - autochop \ - stockpiles -#end a line with a backslash to make it continue to the next line. The \ is deleted for the final command. -# Multiline commands are ONLY supported for scripts like dfhack.init. You cannot do multiline command manually on the DFHack console. -# You cannot extend a commented line. -# You can comment out the extension of a line. - -# enable mouse controls and sand indicator in embark screen -embark-tools enable sticky sand mouse - -# enable option to enter embark assistant -enable embark-assistant - -########### -# Scripts # -########### - -# write extra information to the gamelog -modtools/extra-gamelog enable - -# extended status screen (bedrooms page) -enable gui/extended-status - -# add information to item viewscreens -view-item-info enable - -# a replacement for the "load game" screen -gui/load-screen enable - -############################## -# Extra DFHack command files # -############################## - -# Create a file named "onLoad.init" to run commands when a world is loaded -# and/or create a file named "onMapLoad.init" to run commands when a map is -# loaded. See the hack/examples/init/ directory for useful pre-made init files. diff --git a/data/init/dfhack.tools.init b/data/init/dfhack.tools.init new file mode 100644 index 000000000..aab17ebbe --- /dev/null +++ b/data/init/dfhack.tools.init @@ -0,0 +1,131 @@ +# Default DFHack tool configuration + +# Please do not edit this file directly. It will be overwritten with new +# defaults when you update DFHack. Instead, add your configuration to +# dfhack-config/init/dfhack.init + +############################ +# UI and game logic tweaks # +############################ + +# stabilize the cursor of dwarfmode when switching menus +tweak stable-cursor + +# stop stacked liquid/bar/thread/cloth items from lasting forever +# if used in reactions that use only a fraction of the dimension. +# might be fixed by DF +# tweak fix-dimensions + +# make reactions requiring containers usable in advmode - the issue is +# that the screen asks for those reagents to be selected directly +tweak advmode-contained + +# support Shift-Enter in Trade and Move Goods to Depot screens for faster +# selection; it selects the current item or stack and scrolls down one line +tweak fast-trade + +# stop the right list in military->positions from resetting to top all the time +tweak military-stable-assign +# in same list, color units already assigned to squads in brown & green +tweak military-color-assigned + +# make crafted cloth items wear out with time like in old versions (bug 6003) +tweak craft-age-wear + +# stop adamantine clothing from wearing out (bug 6481) +#tweak adamantine-cloth-wear + +# Add "Select all" and "Deselect all" options to farm plot menus +tweak farm-plot-select + +# Add Shift-Left/Right controls to import agreement screen +tweak import-priority-category + +# Fixes a crash in the work order contition material list (bug 9905). +tweak condition-material + +# Adds an option to clear currently-bound hotkeys +tweak hotkey-clear + +# Allows lowercase letters in embark profile names, and allows exiting the name prompt without saving +tweak embark-profile-name + +# Reduce performance impact of temperature changes +tweak fast-heat 100 + +# Misc. UI tweaks +tweak block-labors # Prevents labors that can't be used from being toggled +tweak burrow-name-cancel +tweak cage-butcher +tweak civ-view-agreement +tweak do-job-now +tweak eggs-fertile +tweak fps-min +tweak hide-priority +tweak kitchen-prefs-all +tweak kitchen-prefs-empty +tweak max-wheelbarrow +tweak partial-items +tweak shift-8-scroll +tweak stone-status-all +tweak title-start-rename +tweak tradereq-pet-gender + +########################### +# Globally acting plugins # +########################### + +# Display DFHack version on title screen +enable title-version + +# Dwarf Manipulator (simple in-game Dwarf Therapist replacement) +enable manipulator + +# Search tool in various screens (by falconne) +enable search + +# Improved build material selection interface (by falconne) +enable automaterial + +# Other interface improvement tools +enable \ + confirm \ + dwarfmonitor \ + mousequery \ + autogems \ + autodump \ + automelt \ + autotrade \ + buildingplan \ + resume \ + trackstop \ + zone \ + stocks \ + autochop \ + stockpiles +#end a line with a backslash to make it continue to the next line. The \ is deleted for the final command. +# Multiline commands are ONLY supported for scripts like dfhack.init. You cannot do multiline command manually on the DFHack console. +# You cannot extend a commented line. +# You can comment out the extension of a line. + +# enable mouse controls and sand indicator in embark screen +embark-tools enable sticky sand mouse + +# enable option to enter embark assistant +enable embark-assistant + +########### +# Scripts # +########### + +# write extra information to the gamelog +modtools/extra-gamelog enable + +# extended status screen (bedrooms page) +enable gui/extended-status + +# add information to item viewscreens +view-item-info enable + +# a replacement for the "load game" screen +gui/load-screen enable diff --git a/data/init/onLoad.default.init b/data/init/onLoad.default.init new file mode 100644 index 000000000..c13190357 --- /dev/null +++ b/data/init/onLoad.default.init @@ -0,0 +1,5 @@ +# Default DFHack commands to run when a world is loaded + +# Please do not edit this file directly. It will be overwritten with new +# defaults when you update DFHack. Instead, add your configuration to +# dfhack-config/init/onLoad.init diff --git a/data/init/onMapLoad.default.init b/data/init/onMapLoad.default.init new file mode 100644 index 000000000..44986a044 --- /dev/null +++ b/data/init/onMapLoad.default.init @@ -0,0 +1,6 @@ +# Default DFHack commands to run when a map is loaded, either in +# adventure or fort mode. + +# Please do not edit this file directly. It will be overwritten with new +# defaults when you update DFHack. Instead, add your configuration to +# dfhack-config/init/onMapLoad.init diff --git a/data/init/onMapUnload.default.init b/data/init/onMapUnload.default.init new file mode 100644 index 000000000..6441d72ff --- /dev/null +++ b/data/init/onMapUnload.default.init @@ -0,0 +1,5 @@ +# Default DFHack commands to run when a map is unloaded + +# Please do not edit this file directly. It will be overwritten with new +# defaults when you update DFHack. Instead, add your configuration to +# dfhack-config/init/onMapUnload.init diff --git a/data/init/onUnload.default.init b/data/init/onUnload.default.init new file mode 100644 index 000000000..9254a257b --- /dev/null +++ b/data/init/onUnload.default.init @@ -0,0 +1,5 @@ +# Default DFHack commands to run when a world is unloaded + +# Please do not edit this file directly. It will be overwritten with new +# defaults when you update DFHack. Instead, add your configuration to +# dfhack-config/init/onUnload.init diff --git a/dfhack-config/init/default.dfhack.init b/dfhack-config/init/default.dfhack.init new file mode 100644 index 000000000..aad18dd1c --- /dev/null +++ b/dfhack-config/init/default.dfhack.init @@ -0,0 +1,7 @@ +# Load DFHack defaults. +# +# If you delete this file, it will reappear when you restart DFHack. +# Instead, please comment out the following line if you do not want DFHack to +# load its default configuration. + +script hack/init/dfhack.default.init diff --git a/dfhack-config/init/default.onLoad.init b/dfhack-config/init/default.onLoad.init new file mode 100644 index 000000000..fe87d4209 --- /dev/null +++ b/dfhack-config/init/default.onLoad.init @@ -0,0 +1,7 @@ +# Load DFHack defaults. +# +# If you delete this file, it will reappear when you restart DFHack. +# Instead, please comment out the following line if you do not want DFHack to +# load its default configuration. + +script hack/init/onLoad.default.init diff --git a/dfhack-config/init/default.onMapLoad.init b/dfhack-config/init/default.onMapLoad.init new file mode 100644 index 000000000..9e781b924 --- /dev/null +++ b/dfhack-config/init/default.onMapLoad.init @@ -0,0 +1,7 @@ +# Load DFHack defaults. +# +# If you delete this file, it will reappear when you restart DFHack. +# Instead, please comment out the following line if you do not want DFHack to +# load its default configuration. + +script hack/init/onMapLoad.default.init diff --git a/dfhack-config/init/default.onMapUnload.init b/dfhack-config/init/default.onMapUnload.init new file mode 100644 index 000000000..716680fd0 --- /dev/null +++ b/dfhack-config/init/default.onMapUnload.init @@ -0,0 +1,7 @@ +# Load DFHack defaults. +# +# If you delete this file, it will reappear when you restart DFHack. +# Instead, please comment out the following line if you do not want DFHack to +# load its default configuration. + +script hack/init/onMapUnload.default.init diff --git a/dfhack-config/init/default.onUnload.init b/dfhack-config/init/default.onUnload.init new file mode 100644 index 000000000..712c35098 --- /dev/null +++ b/dfhack-config/init/default.onUnload.init @@ -0,0 +1,7 @@ +# Load DFHack defaults. +# +# If you delete this file, it will reappear when you restart DFHack. +# Instead, please comment out the following line if you do not want DFHack to +# load its default configuration. + +script hack/init/onUnload.default.init diff --git a/dfhack-config/init/dfhack.init b/dfhack-config/init/dfhack.init new file mode 100644 index 000000000..b05598f98 --- /dev/null +++ b/dfhack-config/init/dfhack.init @@ -0,0 +1,5 @@ +# This file runs when DFHack is initialized, when Dwarf Fortress is first +# started, before any world or save data is loaded. +# +# You can extend or override DFHack's default configuration by adding commands +# to this file. diff --git a/dfhack-config/init/onLoad.init b/dfhack-config/init/onLoad.init new file mode 100644 index 000000000..ef4fd97af --- /dev/null +++ b/dfhack-config/init/onLoad.init @@ -0,0 +1,6 @@ +# This file runs when a world is loaded. This happens when you open a save file +# in fort, adventure, or legends mode. If a fort is being loaded, this file runs +# before any onMapLoad.init files. +# +# You can extend or override DFHack's default configuration by adding commands +# to this file. diff --git a/dfhack-config/init/onMapLoad.init b/dfhack-config/init/onMapLoad.init new file mode 100644 index 000000000..90c6b9e14 --- /dev/null +++ b/dfhack-config/init/onMapLoad.init @@ -0,0 +1,5 @@ +# This file runs when a map is loaded in adventure or fort mode, after any +# onLoad.init files (which run earlier, when the world is loaded). +# +# You can extend or override DFHack's default configuration by adding commands +# to this file. diff --git a/dfhack-config/init/onMapUnload.init b/dfhack-config/init/onMapUnload.init new file mode 100644 index 000000000..c513d9cae --- /dev/null +++ b/dfhack-config/init/onMapUnload.init @@ -0,0 +1,5 @@ +# This file runs when a fortress map is unloaded, before any onUnload.init files +# (which run later, when the world is unloaded). +# +# You can extend or override DFHack's default configuration by adding commands +# to this file. diff --git a/dfhack-config/init/onUnload.init b/dfhack-config/init/onUnload.init new file mode 100644 index 000000000..c8ed3ab5b --- /dev/null +++ b/dfhack-config/init/onUnload.init @@ -0,0 +1,4 @@ +# This file runs when a world is unloaded. +# +# You can extend or override DFHack's default configuration by adding commands +# to this file. diff --git a/docs/Core.rst b/docs/Core.rst index ef178cc6e..fe881a54d 100644 --- a/docs/Core.rst +++ b/docs/Core.rst @@ -366,16 +366,27 @@ The following commands are *not* built-in, but offer similarly useful functions. * `repeat` +.. _dfhack-config: + +Configuration Files +=================== + +Most DFHack settings can be changed by modifying files in the ``dfhack-config`` +folder (which is in the DF folder). The default versions of these files, if they +exist, are in ``dfhack-config/default`` and are installed when DFHack starts if +necessary. + .. _init-files: Init Files -========== +---------- .. contents:: :local: DFHack allows users to automatically run commonly-used DFHack commands -when DF is first loaded, when a game is loaded, and when a game is unloaded. +when DF is first loaded, when a world is loaded, when a map is loaded, when a +map is unloaded, and when a world is unloaded. Init scripts function the same way they would if the user manually typed in their contents, but are much more convenient. In order to facilitate @@ -385,32 +396,33 @@ save-specific init files in the save folders. DFHack looks for init files in three places each time they could be run: -#. The main DF directory +#. The :file:`dfhack-config/init` subdirectory in the main DF directory #. :file:`data/save/{world}/raw`, where ``world`` is the current save, and #. :file:`data/save/{world}/raw/objects` -When reading commands from dfhack.init or with the `script` command, if the final -character on a line is a backslash then the next uncommented line is considered a -continuation of that line, with the backslash deleted. Commented lines are skipped, -so it is possible to comment out parts of a command with the ``#`` character. +For each of those directories, all matching init files will be executed in +alphabetical order. +Before running matched init scripts in any of those locations, the +:file:`dfhack-config/init/default.*` file that matches the event will be run to +load DFHack defaults. Only the :file:`dfhack-config/init` directory is checked +for this file, not any :file:`raw` directories. If you want DFHack to load +without running any of its default configuration commands, edit the +:file:`dfhack-config/init/default.*` files and comment out the commands you see +there. -.. _dfhack.init: +When reading commands from the init files or with the `script` command, if the +final character on a line is a backslash then the next uncommented line is +considered a continuation of that line, with the backslash deleted. Commented +lines are skipped, so it is possible to comment out parts of a command with the +``#`` character. -dfhack*.init ------------- -If your DF folder contains at least one file named ``dfhack*.init`` -(where ``*`` is a placeholder for any string), then all such files -are executed in alphabetical order when DF is first started. - -DFHack is distributed with :download:`/dfhack.init-example` as an example -with an up-to-date collection of basic commands; mostly setting standard -keybindings and `enabling ` plugins. You are encouraged to look -through this file to learn which features it makes available under which -key combinations. You may also customise it and rename it to ``dfhack.init``. +.. _dfhack.init: -If your DF folder does not contain any ``dfhack*.init`` files, the example -will be run as a fallback. +dfhack\*.init +............. +On startup, DFHack looks for files of the form ``dfhack*.init`` (where ``*`` is +a placeholder for any string, including the empty string). These files are best used for keybindings and enabling persistent plugins which do not require a world to be loaded. @@ -418,51 +430,49 @@ which do not require a world to be loaded. .. _onLoad.init: -onLoad*.init ------------- +onLoad\*.init +............. When a world is loaded, DFHack looks for files of the form ``onLoad*.init``, where ``*`` can be any string, including the empty string. -All matching init files will be executed in alphabetical order. A world being loaded can mean a fortress, an adventurer, or legends mode. These files are best used for non-persistent commands, such as setting a `fix ` script to run on `repeat`. -.. _onUnload.init: +.. _onMapLoad.init: -onUnload*.init --------------- -When a world is unloaded, DFHack looks for files of the form ``onUnload*.init``. -Again, these files may be in any of the above three places. -All matching init files will be executed in alphebetical order. +onMapLoad\*.init +................ +When a map is loaded, either in adventure or fort mode, DFHack looks for files +of the form ``onMapLoad*.init``, where ``*`` can be any string, including the +empty string. -Modders often use such scripts to disable tools which should not affect -an unmodded save. +These files are best used for commands that are only relevant once there is a +game map loaded. -.. _other_init_files: -Other init files ----------------- +.. _onMapUnload.init: +.. _onUnload.init: -* ``onMapLoad*.init`` and ``onMapUnload*.init`` are run when a map, - distinct from a world, is loaded. This is good for map-affecting - commands (e.g. `clean`), or avoiding issues in Legends mode. +onMapUnload\*.init and onUnload\*.init +...................................... +When a map or world is unloaded, DFHack looks for files of the form +``onMapUnload*.init`` or ``onUnload*.init``, respectively. -* Any lua script named ``raw/init.d/*.lua``, in the save or main DF - directory, will be run when any world or that save is loaded. +Modders often use unload init scripts to disable tools which should not run +after a modded save is unloaded. -.. _dfhack-config: +.. _other_init_files: -Configuration Files -=================== +raw/init.d/\*.lua +................. + +Any lua script named ``raw/init.d/*.lua``, in the save or main DF directory, +will be run when any world or that save is loaded. -Some DFHack settings can be changed by modifying files in the ``dfhack-config`` -folder (which is in the DF folder). The default versions of these files, if they -exist, are in ``dfhack-config/default`` and are installed when DFHack starts if -necessary. .. _script-paths: diff --git a/docs/changelog.txt b/docs/changelog.txt index 29e0d8e22..31f8b4615 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -41,7 +41,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - ``job.removeJob()``: ensure jobs are removed from the world list when they are canceled ## Misc Improvements - +- Init scripts: ``dfhack.init`` and other init scripts have moved to ``dfhack-config/init/``. If you have customized your ``dfhack.init`` file and want to keep your changes, please move the part that you have customized to the new location at ``dfhack-config/init/dfhack.init``. If you do not have changes that you want to keep, do not copy anything, and the new defaults will be used automatically. - `manipulator`: add a library of useful default professions - `manipulator`: move professions configuration from ``professions/`` to ``dfhack-config/professions/`` to keep it together with other dfhack configuration. If you have saved professions that you would like to keep, please manually move them to the new folder. - ``materials.ItemTraitsDialog``: added a default ``on_select``-handler which toggles the traits. diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 9e7bf8590..b3cdf93b9 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -460,9 +460,6 @@ endif() # install the offset file install(FILES xml/symbols.xml DESTINATION ${DFHACK_DATA_DESTINATION}) -# install the example autoexec file -install(FILES ../dfhack.init-example - DESTINATION ${DFHACK_BINARY_DESTINATION}) install(TARGETS dfhack-run dfhack-client binpatch LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} diff --git a/library/Core.cpp b/library/Core.cpp index 10f5057f3..aa43f3c2b 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1450,13 +1450,12 @@ static void run_dfhack_init(color_ostream &out, Core *core) return; } + // load baseline defaults + core->loadScriptFile(out, "dfhack-config/init/default.dfhack.init", false); + + // load user overrides std::vector prefixes(1, "dfhack"); - size_t count = loadScriptFiles(core, out, prefixes, "."); - if (!count || !Filesystem::isfile("dfhack.init")) - { - core->runCommand(out, "gui/no-dfhack-init"); - core->loadScriptFile(out, "dfhack.init-example", false); - } + loadScriptFiles(core, out, prefixes, "dfhack-config/init"); } // Load dfhack.init in a dedicated thread (non-interactive console mode) @@ -2226,7 +2225,11 @@ void Core::handleLoadAndUnloadScripts(color_ostream& out, state_change_event eve auto i = table.find(event); if ( i != table.end() ) { const std::vector& set = i->second; - loadScriptFiles(this, out, set, "." ); + + // load baseline defaults + this->loadScriptFile(out, "dfhack-config/init/default." + set[0] + ".init", false); + + loadScriptFiles(this, out, set, "dfhack-config/init"); loadScriptFiles(this, out, set, rawFolder); loadScriptFiles(this, out, set, rawFolder + "objects/"); } From f021dd0e0a468204f08808a8b0d63a9b66647ad7 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 12 Jul 2022 11:25:16 -0400 Subject: [PATCH 027/111] Gui::getAnyItem(): add support for viewscreen_treasurelistst --- docs/changelog.txt | 1 + library/modules/Gui.cpp | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 9bc714e48..447f2f22e 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -56,6 +56,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - Removed ``Notes`` module (C++-only). Access ``ui.waypoints.points`` directly instead. - Removed ``Windows`` module (C++-only) - unused. - ``Constructions`` module (C++-only): removed ``t_construction``, ``isValid()``, ``getCount()``, ``getConstruction()``, and ``copyConstruction()``. Access ``world.constructions`` directly instead. +- ``Gui::getSelectedItem()``, ``Gui::getAnyItem()``: added support for the artifacts screen ## Lua - ``tile-material``: fix the order of declarations. The ``GetTileMat`` function now returns the material as intended (always returned nil before). Also changed the license info, with permission of the original author. diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 963b2ecd6..fc78bb57d 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -106,6 +106,7 @@ using namespace DFHack; #include "df/viewscreen_unitlistst.h" #include "df/viewscreen_unitst.h" #include "df/viewscreen_reportlistst.h" +#include "df/viewscreen_treasurelistst.h" #include "df/viewscreen_workquota_conditionst.h" #include "df/viewscreen_workshop_profilest.h" #include "df/world.h" @@ -1181,6 +1182,13 @@ df::item *Gui::getAnyItem(df::viewscreen *top) return NULL; } + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_treasurelistst, top)) + { + if (world) + return vector_get(world->items.other[df::items_other_id::ANY_ARTIFACT], screen->sel_idx); + return NULL; + } + if (auto dfscreen = dfhack_viewscreen::try_cast(top)) return dfscreen->getSelectedItem(); From a20612b0a8325c2abdbec29fb1f202907d1558e0 Mon Sep 17 00:00:00 2001 From: Quietust Date: Sat, 16 Jul 2022 10:17:46 -0600 Subject: [PATCH 028/111] Update structures --- library/xml | 2 +- plugins/strangemood.cpp | 214 ++++++++-------------------------------- 2 files changed, 43 insertions(+), 173 deletions(-) diff --git a/library/xml b/library/xml index 219676497..df19b880f 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 2196764977011991127244b28ff13b90cef19af3 +Subproject commit df19b880fb3cbaa1a31a12b058acf9936d7ddada diff --git a/plugins/strangemood.cpp b/plugins/strangemood.cpp index 5050b3621..de7fa426d 100644 --- a/plugins/strangemood.cpp +++ b/plugins/strangemood.cpp @@ -153,145 +153,40 @@ void selectWord (const df::language_word_table &table, int32_t &word, df::part_o } } -void generateName(df::language_name &output, int language, df::language_name_type mode, const df::language_word_table &table1, const df::language_word_table &table2) +void generateName(df::language_name &output, int language, const df::language_word_table &table1, const df::language_word_table &table2) { for (int i = 0; i < 100; i++) { - if (mode != 8 && mode != 9) - { - output = df::language_name(); - if (language == -1) - language = rng.df_trandom(world->raws.language.translations.size()); - output.type = mode; - output.language = language; - } + output = df::language_name(); + if (language == -1) + language = rng.df_trandom(world->raws.language.translations.size()); + output.type = language_name_type::Artifact; + output.language = language; output.has_name = 1; if (output.language == -1) output.language = rng.df_trandom(world->raws.language.translations.size()); int r, r2, r3; - switch (mode) + r = rng.df_trandom(3); + if (r == 0 || r == 1) { - case language_name_type::Figure: - case language_name_type::FigureNoFirst: - case language_name_type::FigureFirstOnly: - if (mode != 9) - { - int32_t word; df::part_of_speech part; - output.first_name.clear(); - selectWord(table1, word, part, 2); - if (word >= 0 && size_t(word) < world->raws.language.words.size()) - output.first_name = *world->raws.language.translations[language]->words[word]; - } - if (mode != 10) - { - case language_name_type::Site: - case language_name_type::Monument: - if (rng.df_trandom(2)) - { - selectWord(table2, output.words[0], output.parts_of_speech[0], 0); - selectWord(table1, output.words[1], output.parts_of_speech[1], 1); - } - else - { - selectWord(table1, output.words[0], output.parts_of_speech[0], 0); - selectWord(table2, output.words[1], output.parts_of_speech[1], 1); - } - } - break; - - case language_name_type::Artifact: - case language_name_type::Unk13: - case language_name_type::River: - r = rng.df_trandom(3); - if (r == 0 || r == 1) - { - if (rng.df_trandom(2)) - { - selectWord(table2, output.words[0], output.parts_of_speech[0], 0); - selectWord(table1, output.words[1], output.parts_of_speech[1], 1); - } - else - { - selectWord(table1, output.words[0], output.parts_of_speech[0], 0); - selectWord(table2, output.words[1], output.parts_of_speech[1], 1); - } - } - if (r == 1 || r == 2) + if (rng.df_trandom(2)) { - case language_name_type::Squad: - case language_name_type::LegendaryFigure: - case language_name_type::ArtImage: // this is not a typo either - r2 = rng.df_trandom(2); - if (r2) - selectWord(table1, output.words[5], output.parts_of_speech[5], 2); - else - selectWord(table2, output.words[5], output.parts_of_speech[5], 2); - r3 = rng.df_trandom(3); - if (rng.df_trandom(50)) - r3 = rng.df_trandom(2); - switch (r3) - { - case 0: - case 2: - if (r3 == 2) - r2 = rng.df_trandom(2); - if (r2) - selectWord(table2, output.words[6], output.parts_of_speech[6], 5); - else - selectWord(table1, output.words[6], output.parts_of_speech[6], 5); - if (r3 == 0) - break; - r2 = -r2; - case 1: - if (r2) - selectWord(table1, output.words[2], output.parts_of_speech[2], 3); - else - selectWord(table2, output.words[2], output.parts_of_speech[2], 3); - if (!(rng.df_trandom(100))) - selectWord(table1, output.words[3], output.parts_of_speech[3], 3); - break; - } + selectWord(table2, output.words[0], output.parts_of_speech[0], 0); + selectWord(table1, output.words[1], output.parts_of_speech[1], 1); } - if (rng.df_trandom(100)) + else { - if (rng.df_trandom(2)) - selectWord(table1, output.words[4], output.parts_of_speech[4], 4); - else - selectWord(table2, output.words[4], output.parts_of_speech[4], 4); + selectWord(table1, output.words[0], output.parts_of_speech[0], 0); + selectWord(table2, output.words[1], output.parts_of_speech[1], 1); } - if ((mode == 3) && (output.parts_of_speech[5] == part_of_speech::Noun) && (output.words[5] != -1) && (world->raws.language.words[output.words[5]]->forms[1].length())) - output.parts_of_speech[5] = part_of_speech::NounPlural; - break; - - case language_name_type::Civilization: - case language_name_type::World: - case language_name_type::Region: - case language_name_type::AdventuringGroup: - case language_name_type::SiteGovernment: - case language_name_type::NomadicGroup: - case language_name_type::Vessel: - case language_name_type::MilitaryUnit: - case language_name_type::Religion: - case language_name_type::MountainPeak: - case language_name_type::Temple: - case language_name_type::Keep: - case language_name_type::MeadHall: - case language_name_type::Unk24: - case language_name_type::Unk25: - case language_name_type::Unk26: - case language_name_type::Market: - case language_name_type::Tavern: - case language_name_type::War: - case language_name_type::Battle: - case language_name_type::Siege: - case language_name_type::Road: - case language_name_type::Wall: - case language_name_type::Bridge: - case language_name_type::Tunnel: - case language_name_type::PretentiousEntityPosition: - case language_name_type::Tomb: - case language_name_type::OutcastGroup: - selectWord(table1, output.words[5], output.parts_of_speech[5], 2); + } + if (r == 1 || r == 2) + { + r2 = rng.df_trandom(2); + if (r2) + selectWord(table1, output.words[5], output.parts_of_speech[5], 2); + else + selectWord(table2, output.words[5], output.parts_of_speech[5], 2); r3 = rng.df_trandom(3); if (rng.df_trandom(50)) r3 = rng.df_trandom(2); @@ -299,56 +194,31 @@ void generateName(df::language_name &output, int language, df::language_name_typ { case 0: case 2: - selectWord(table2, output.words[6], output.parts_of_speech[6], 5); + if (r3 == 2) + r2 = rng.df_trandom(2); + if (r2) + selectWord(table2, output.words[6], output.parts_of_speech[6], 5); + else + selectWord(table1, output.words[6], output.parts_of_speech[6], 5); if (r3 == 0) break; + r2 = -r2; case 1: - selectWord(table2, output.words[2], output.parts_of_speech[2], 3); - if (!(rng.df_trandom(100))) - selectWord(table2, output.words[3], output.parts_of_speech[3], 3); - break; - } - if (rng.df_trandom(100)) - selectWord(table2, output.words[4], output.parts_of_speech[4], 4); - break; - - case language_name_type::Dungeon: - r = rng.df_trandom(3); - if (r == 0 || r == 1) - { - selectWord(table2, output.words[0], output.parts_of_speech[0], 0); - selectWord(table1, output.words[1], output.parts_of_speech[1], 1); - } - if (r == 1 || r == 2) - { - r2 = rng.df_trandom(2); - if (r == 2 || r2 == 1) - selectWord(table1, output.words[5], output.parts_of_speech[5], 2); + if (r2) + selectWord(table1, output.words[2], output.parts_of_speech[2], 3); else - selectWord(table2, output.words[5], output.parts_of_speech[5], 2); - r3 = rng.df_trandom(3); - if (rng.df_trandom(50)) - r3 = rng.df_trandom(2); - switch (r3) - { - case 0: - case 2: - selectWord(table1, output.words[6], output.parts_of_speech[6], 5); - if (r3 == 0) - break; - case 1: selectWord(table2, output.words[2], output.parts_of_speech[2], 3); - if (!(rng.df_trandom(100))) - selectWord(table2, output.words[3], output.parts_of_speech[3], 3); - break; - } + if (!(rng.df_trandom(100))) + selectWord(table1, output.words[3], output.parts_of_speech[3], 3); + break; } - if (rng.df_trandom(100)) + } + if (rng.df_trandom(100)) + { + if (rng.df_trandom(2)) + selectWord(table1, output.words[4], output.parts_of_speech[4], 4); + else selectWord(table2, output.words[4], output.parts_of_speech[4], 4); - break; - default: - // not handled yet - break; } if (output.words[2] != -1 && output.words[3] != -1 && world->raws.language.words[output.words[3]]->adj_dist < world->raws.language.words[output.words[2]]->adj_dist) @@ -1351,10 +1221,10 @@ command_result df_strangemood (color_ostream &out, vector & parameters) // Generate the artifact's name if (type == mood_type::Fell || type == mood_type::Macabre) - generateName(unit->status.artifact_name, unit->name.language, language_name_type::Artifact, world->raws.language.word_table[0][2], world->raws.language.word_table[1][2]); + generateName(unit->status.artifact_name, unit->name.language, world->raws.language.word_table[0][2], world->raws.language.word_table[1][2]); else { - generateName(unit->status.artifact_name, unit->name.language, language_name_type::Artifact, world->raws.language.word_table[0][1], world->raws.language.word_table[1][1]); + generateName(unit->status.artifact_name, unit->name.language, world->raws.language.word_table[0][1], world->raws.language.word_table[1][1]); if (!rng.df_trandom(100)) unit->status.artifact_name = unit->name; } From e5961b45b4ef0e3f236d17374c6a988584114e7f Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 19 Jul 2022 07:17:31 +0000 Subject: [PATCH 029/111] Auto-update submodules library/xml: master --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index df19b880f..1595cc1fe 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit df19b880fb3cbaa1a31a12b058acf9936d7ddada +Subproject commit 1595cc1fe1f4662eebffdb4a77355491aafacea6 From c9f69081a64f6b27a7604bd605c20ad2338f5dce Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 20 Jul 2022 07:17:41 +0000 Subject: [PATCH 030/111] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index ef5fc459c..bd21e9664 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit ef5fc459c4f0a65a9fd709c14fed0c5892c9eebd +Subproject commit bd21e9664abf9963d5cf5f0f86222dd0dadda2c5 From 64b793b409fedb38e42367ac9b6ec1543e84d18e Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 20 Jul 2022 15:40:49 -0700 Subject: [PATCH 031/111] support EditField:setText() so scripts can use it and be compatible with both the develop and docs branch --- library/lua/gui/widgets.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 1a1b4c6fb..ddd5a01d0 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -207,6 +207,10 @@ function EditField:getPreferredFocusState() return not self.key end +function EditField:setText(text, cursor) + self.text = text +end + function EditField:postUpdateLayout() self.text_offset = self.subviews[1]:getTextWidth() end From 837215ea647b4decdfdb3f320088974d5a89a048 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 20 Jul 2022 16:28:11 -0700 Subject: [PATCH 032/111] modify ci/script-docs.py to read new doc locations --- ci/script-docs.py | 69 +++++++++++++++++++---------------------------- 1 file changed, 27 insertions(+), 42 deletions(-) diff --git a/ci/script-docs.py b/ci/script-docs.py index 7ef287f8a..0106a8f70 100755 --- a/ci/script-docs.py +++ b/ci/script-docs.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 import os -from os.path import basename, dirname, join, splitext +from os.path import basename, dirname, exists, join, splitext import sys SCRIPT_PATH = sys.argv[1] if len(sys.argv) > 1 else 'scripts' +DOCS_PATH = join(SCRIPT_PATH, 'docs') IS_GITHUB_ACTIONS = bool(os.environ.get('GITHUB_ACTIONS')) -def expected_cmd(path): +def get_cmd(path): """Get the command from the name of a script.""" dname, fname = basename(dirname(path)), splitext(basename(path))[0] if dname in ('devel', 'fix', 'gui', 'modtools'): @@ -14,16 +15,6 @@ def expected_cmd(path): return fname -def check_ls(fname, line): - """Check length & existence of leading comment for "ls" builtin command.""" - line = line.strip() - comment = '--' if fname.endswith('.lua') else '#' - if '[====[' in line or not line.startswith(comment): - print_error('missing leading comment (requred for `ls`)', fname) - return 1 - return 0 - - def print_error(message, filename, line=None): if not isinstance(line, int): line = 1 @@ -32,48 +23,42 @@ def print_error(message, filename, line=None): print('::error file=%s,line=%i::%s' % (filename, line, message)) +def check_ls(docfile, lines): + """Check length & existence of first sentence for "ls" builtin command.""" + # TODO + return 0 + + def check_file(fname): - errors, doclines = 0, [] - tok1, tok2 = ('=begin', '=end') if fname.endswith('.rb') else \ - ('[====[', ']====]') - doc_start_line = None - with open(fname, errors='ignore') as f: + errors, doc_start_line = 0, None + docfile = join(DOCS_PATH, get_cmd(fname)+'.rst') + if not exists(docfile): + print_error('missing documentation file: {!r}'.format(docfile), fname) + return 1 + with open(docfile, errors='ignore') as f: lines = f.readlines() if not lines: - print_error('empty file', fname) + print_error('empty documentation file', docfile) return 1 - errors += check_ls(fname, lines[0]) for i, l in enumerate(lines): - if doclines or l.strip().endswith(tok1): - if not doclines: - doc_start_line = i + 1 - doclines.append(l.rstrip()) - if l.startswith(tok2): - break - else: - if doclines: - print_error('docs start but do not end', fname, doc_start_line) - else: - print_error('no documentation found', fname) - return 1 - - if not doclines: - print_error('missing or malformed documentation', fname) - return 1 + l = l.strip() + if l and not doc_start_line and doc_start_line != 0: + doc_start_line = i + doc_end_line = i + lines[i] = l - title, underline = [d for d in doclines - if d and '=begin' not in d and '[====[' not in d][:2] - title_line = doc_start_line + doclines.index(title) + errors += check_ls(docfile, lines) + title, underline = lines[doc_start_line:doc_start_line+2] expected_underline = '=' * len(title) if underline != expected_underline: print_error('title/underline mismatch: expected {!r}, got {!r}'.format( expected_underline, underline), - fname, title_line + 1) + docfile, doc_start_line+1) errors += 1 - if title != expected_cmd(fname): + if title != get_cmd(fname): print_error('expected script title {!r}, got {!r}'.format( - expected_cmd(fname), title), - fname, title_line) + get_cmd(fname), title), + docfile, doc_start_line) errors += 1 return errors From 2f50d161d932d9aa9c8bc8c017869e8eebd8ee1a Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 20 Jul 2022 23:31:20 +0000 Subject: [PATCH 033/111] Auto-update submodules library/xml: master scripts: master --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 1595cc1fe..a04727cc4 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 1595cc1fe1f4662eebffdb4a77355491aafacea6 +Subproject commit a04727cc4ec350399ce4d2ded4425f33c50cea50 diff --git a/scripts b/scripts index bd21e9664..6f5c7edc4 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit bd21e9664abf9963d5cf5f0f86222dd0dadda2c5 +Subproject commit 6f5c7edc49beec38c41ac8c3f0b5bded519ab5ce From 738611383b3a465ee420742e0d44faed1fbc9ae0 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 20 Jul 2022 23:33:08 +0000 Subject: [PATCH 034/111] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 6f5c7edc4..dbdfacd33 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 6f5c7edc49beec38c41ac8c3f0b5bded519ab5ce +Subproject commit dbdfacd33a213f081fbb1d6c85c11424ce3df234 From 6e89f89b3f1600156c5638d9cc9755b11ec42eb5 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 20 Jul 2022 23:38:25 +0000 Subject: [PATCH 035/111] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index dbdfacd33..78b7554a1 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit dbdfacd33a213f081fbb1d6c85c11424ce3df234 +Subproject commit 78b7554a17d7d385986b2f801d5791485e740afb From 5b0f9ddd4f94e8607ff1987a4dbccd03cc4cf909 Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 26 Jul 2022 10:24:05 -0700 Subject: [PATCH 036/111] bump the default history size to 5000 100 is just too small, especially since we're not removing duplicate entries. --- library/include/Console.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/include/Console.h b/library/include/Console.h index 0882ba449..f0f56ca85 100644 --- a/library/include/Console.h +++ b/library/include/Console.h @@ -44,7 +44,7 @@ namespace DFHack class CommandHistory { public: - CommandHistory(std::size_t capacity = 100) + CommandHistory(std::size_t capacity = 5000) { this->capacity = capacity; } From 474dae21c678a91e43c27f2e142b713952024105 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 27 Jul 2022 07:18:22 +0000 Subject: [PATCH 037/111] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 78b7554a1..b9d49dfc3 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 78b7554a17d7d385986b2f801d5791485e740afb +Subproject commit b9d49dfc36dae9b8b1bf91c2a8e8179ca554abdc From 507b1632a23f58f277a295b0d3d39efeff6e232f Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 26 Jul 2022 11:35:31 -0700 Subject: [PATCH 038/111] support backtick as a keybinding --- docs/Core.rst | 3 ++- docs/changelog.txt | 1 + library/Core.cpp | 3 +++ plugins/hotkeys.cpp | 3 +++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/Core.rst b/docs/Core.rst index fe881a54d..0cc62bef9 100644 --- a/docs/Core.rst +++ b/docs/Core.rst @@ -224,7 +224,8 @@ To set keybindings, use the built-in ``keybinding`` command. Like any other command it can be used at any time from the console, but bindings are not remembered between runs of the game unless re-created in `dfhack.init`. -Currently, any combinations of Ctrl/Alt/Shift with A-Z, 0-9, or F1-F12 are supported. +Currently, any combinations of Ctrl/Alt/Shift with A-Z, 0-9, F1-F12 or \` +are supported. Possible ways to call the command: diff --git a/docs/changelog.txt b/docs/changelog.txt index 447f2f22e..c57bf3435 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -42,6 +42,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements - Init scripts: ``dfhack.init`` and other init scripts have moved to ``dfhack-config/init/``. If you have customized your ``dfhack.init`` file and want to keep your changes, please move the part that you have customized to the new location at ``dfhack-config/init/dfhack.init``. If you do not have changes that you want to keep, do not copy anything, and the new defaults will be used automatically. +- `keybinding`: support backquote (``\```) as a hotkey - `manipulator`: add a library of useful default professions - `manipulator`: move professions configuration from ``professions/`` to ``dfhack-config/professions/`` to keep it together with other dfhack configuration. If you have saved professions that you would like to keep, please manually move them to the new folder. - ``materials.ItemTraitsDialog``: added a default ``on_select``-handler which toggles the traits. diff --git a/library/Core.cpp b/library/Core.cpp index 6d1f13589..12faa9f7c 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -2611,6 +2611,9 @@ static bool parseKeySpec(std::string keyspec, int *psym, int *pmod, std::string if (keyspec.size() == 1 && keyspec[0] >= 'A' && keyspec[0] <= 'Z') { *psym = SDL::K_a + (keyspec[0]-'A'); return true; + } else if (keyspec.size() == 1 && keyspec[0] == '`') { + *psym = SDL::K_BACKQUOTE; + return true; } else if (keyspec.size() == 1 && keyspec[0] >= '0' && keyspec[0] <= '9') { *psym = SDL::K_0 + (keyspec[0]-'0'); return true; diff --git a/plugins/hotkeys.cpp b/plugins/hotkeys.cpp index e4cd13574..ecf3b2969 100644 --- a/plugins/hotkeys.cpp +++ b/plugins/hotkeys.cpp @@ -44,6 +44,7 @@ static void find_active_keybindings(df::viewscreen *screen) sorted_keys.clear(); vector valid_keys; + for (char c = 'A'; c <= 'Z'; c++) { valid_keys.push_back(string(&c, 1)); @@ -54,6 +55,8 @@ static void find_active_keybindings(df::viewscreen *screen) valid_keys.push_back("F" + int_to_string(i)); } + valid_keys.push_back("`"); + auto current_focus = Gui::getFocusString(screen); for (int shifted = 0; shifted < 2; shifted++) { From b6e27b1875754f0d7a77c91290a7730892bf101d Mon Sep 17 00:00:00 2001 From: myk002 Date: Thu, 28 Jul 2022 05:46:25 -0700 Subject: [PATCH 039/111] fix rendering of quoted backtick --- docs/changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index c57bf3435..242ec37da 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -42,7 +42,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements - Init scripts: ``dfhack.init`` and other init scripts have moved to ``dfhack-config/init/``. If you have customized your ``dfhack.init`` file and want to keep your changes, please move the part that you have customized to the new location at ``dfhack-config/init/dfhack.init``. If you do not have changes that you want to keep, do not copy anything, and the new defaults will be used automatically. -- `keybinding`: support backquote (``\```) as a hotkey +- `keybinding`: support backquote (\`) as a hotkey - `manipulator`: add a library of useful default professions - `manipulator`: move professions configuration from ``professions/`` to ``dfhack-config/professions/`` to keep it together with other dfhack configuration. If you have saved professions that you would like to keep, please manually move them to the new folder. - ``materials.ItemTraitsDialog``: added a default ``on_select``-handler which toggles the traits. From d3f83eca44027870bfc441b41c75e2065b546889 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 2 Aug 2022 07:19:34 +0000 Subject: [PATCH 040/111] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index b9d49dfc3..3aac1b07b 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit b9d49dfc36dae9b8b1bf91c2a8e8179ca554abdc +Subproject commit 3aac1b07be05b58217c2ebfecafff908dadb2044 From 2c9537b98e1186323f381f24e57aa104fdb04180 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Aug 2022 21:29:09 +0000 Subject: [PATCH 041/111] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/python-jsonschema/check-jsonschema: 0.16.2 → 0.17.1](https://github.com/python-jsonschema/check-jsonschema/compare/0.16.2...0.17.1) - [github.com/Lucas-C/pre-commit-hooks: v1.2.0 → v1.3.0](https://github.com/Lucas-C/pre-commit-hooks/compare/v1.2.0...v1.3.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 25601d8b8..125b1e931 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,11 +20,11 @@ repos: args: ['--fix=lf'] - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.16.2 + rev: 0.17.1 hooks: - id: check-github-workflows - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.2.0 + rev: v1.3.0 hooks: - id: forbid-tabs exclude_types: From 391a4d8883c5d92053df59a15cb82c4318fbc680 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 23 Jul 2022 22:12:34 -0700 Subject: [PATCH 042/111] use dfhack-config/lua.history instead of lua.history --- library/LuaTools.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index cef46c053..fea90b394 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -1145,7 +1145,7 @@ bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state, return false; if (!hfile) - hfile = "lua.history"; + hfile = "dfhack-config/lua.history"; if (!prompt) prompt = "lua"; From 98f8fcd0688e77a685194375842e9c207897ea14 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sat, 23 Jul 2022 22:12:58 -0700 Subject: [PATCH 043/111] move liquids.history to dfhack-config/liquids.history --- plugins/liquids.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/liquids.cpp b/plugins/liquids.cpp index 5dd64812f..76c098c8a 100644 --- a/plugins/liquids.cpp +++ b/plugins/liquids.cpp @@ -55,6 +55,7 @@ using namespace df::enums; DFHACK_PLUGIN("liquids"); REQUIRE_GLOBAL(world); +static const char * HISTORY_FILE = "dfhack-config/liquids.history"; CommandHistory liquids_hist; command_result df_liquids (color_ostream &out, vector & parameters); @@ -62,7 +63,7 @@ command_result df_liquids_here (color_ostream &out, vector & parameters DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - liquids_hist.load("liquids.history"); + liquids_hist.load(HISTORY_FILE); commands.push_back(PluginCommand( "liquids", "Place magma, water or obsidian.", df_liquids, true, @@ -80,7 +81,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector Date: Sat, 23 Jul 2022 22:13:19 -0700 Subject: [PATCH 044/111] move tiletypes.history to dfhack-config/tiletypes.history --- plugins/tiletypes.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/tiletypes.cpp b/plugins/tiletypes.cpp index 7f39ddd4e..b8bbc8d04 100644 --- a/plugins/tiletypes.cpp +++ b/plugins/tiletypes.cpp @@ -74,7 +74,7 @@ static const struct_field_info tiletypes_options_fields[] = { }; struct_identity tiletypes_options::_identity(sizeof(tiletypes_options), &df::allocator_fn, NULL, "tiletypes_options", NULL, tiletypes_options_fields); - +static const char * HISTORY_FILE = "dfhack-config/tiletypes.history"; CommandHistory tiletypes_hist; command_result df_tiletypes (color_ostream &out, vector & parameters); @@ -84,7 +84,7 @@ command_result df_tiletypes_here_point (color_ostream &out, vector & pa DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - tiletypes_hist.load("tiletypes.history"); + tiletypes_hist.load(HISTORY_FILE); commands.push_back(PluginCommand("tiletypes", "Paint map tiles freely, similar to liquids.", df_tiletypes, true)); commands.push_back(PluginCommand("tiletypes-command", "Run tiletypes commands (seperated by ' ; ')", df_tiletypes_command)); commands.push_back(PluginCommand("tiletypes-here", "Repeat tiletypes command at cursor (with brush)", df_tiletypes_here)); @@ -94,7 +94,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector Date: Tue, 26 Jul 2022 10:21:09 -0700 Subject: [PATCH 045/111] move dfhack.history to dfhack-config/dfhack.history --- library/Core.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 12faa9f7c..1cd00d3aa 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1469,12 +1469,14 @@ void fInitthread(void * iodata) // A thread function... for the interactive console. void fIOthread(void * iodata) { + static const char * HISTORY_FILE = "dfhack-config/dfhack.history"; + IODATA * iod = ((IODATA*) iodata); Core * core = iod->core; PluginManager * plug_mgr = ((IODATA*) iodata)->plug_mgr; CommandHistory main_history; - main_history.load("dfhack.history"); + main_history.load(HISTORY_FILE); Console & con = core->getConsole(); if (plug_mgr == 0) @@ -1515,7 +1517,7 @@ void fIOthread(void * iodata) { // a proper, non-empty command was entered main_history.add(command); - main_history.save("dfhack.history"); + main_history.save(HISTORY_FILE); } auto rv = core->runCommand(con, command); From 7e3acc410e5030883f5d2aea06c0f2abfe9b4b45 Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 26 Jul 2022 10:40:10 -0700 Subject: [PATCH 046/111] update changelog --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 242ec37da..ce61d31d3 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -42,6 +42,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Misc Improvements - Init scripts: ``dfhack.init`` and other init scripts have moved to ``dfhack-config/init/``. If you have customized your ``dfhack.init`` file and want to keep your changes, please move the part that you have customized to the new location at ``dfhack-config/init/dfhack.init``. If you do not have changes that you want to keep, do not copy anything, and the new defaults will be used automatically. +- History files: ``dfhack.history``, ``tiletypes.history``, ``lua.history``, and ``liquids.history`` have moved to the ``dfhack-config`` directory. If you'd like to keep the contents of your current history files, please move them to ``dfhack-config``. - `keybinding`: support backquote (\`) as a hotkey - `manipulator`: add a library of useful default professions - `manipulator`: move professions configuration from ``professions/`` to ``dfhack-config/professions/`` to keep it together with other dfhack configuration. If you have saved professions that you would like to keep, please manually move them to the new folder. From 5b26d3361bee3e24ae95840f6be65f8eced5e7d9 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 3 Aug 2022 07:17:28 +0000 Subject: [PATCH 047/111] Auto-update submodules library/xml: master scripts: master --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index a04727cc4..dc118c5e9 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit a04727cc4ec350399ce4d2ded4425f33c50cea50 +Subproject commit dc118c5e90aea6181a290e4bbf40e7f2974fb053 diff --git a/scripts b/scripts index 3aac1b07b..6a66be4fa 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 3aac1b07be05b58217c2ebfecafff908dadb2044 +Subproject commit 6a66be4facd1d5a7b3ab4cb61c34c678d19e0795 From 0096f7c88279e71af9e6ac23149de7534692a9e3 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 31 Jul 2022 23:42:59 -0700 Subject: [PATCH 048/111] split autonestbox out from zone --- plugins/CMakeLists.txt | 1 + plugins/autonestbox.cpp | 418 ++++++++++++++++++++++++++++++++++++ plugins/lua/autonestbox.lua | 51 +++++ plugins/zone.cpp | 234 +------------------- 4 files changed, 472 insertions(+), 232 deletions(-) create mode 100644 plugins/autonestbox.cpp create mode 100644 plugins/lua/autonestbox.lua diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 4a4b74eaa..6ef5b59db 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -92,6 +92,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(autolabor autolabor.cpp) dfhack_plugin(automaterial automaterial.cpp LINK_LIBRARIES lua) dfhack_plugin(automelt automelt.cpp) + dfhack_plugin(autonestbox autonestbox.cpp LINK_LIBRARIES lua) dfhack_plugin(autotrade autotrade.cpp) dfhack_plugin(blueprint blueprint.cpp LINK_LIBRARIES lua) dfhack_plugin(burrows burrows.cpp LINK_LIBRARIES lua) diff --git a/plugins/autonestbox.cpp b/plugins/autonestbox.cpp new file mode 100644 index 000000000..6c878806c --- /dev/null +++ b/plugins/autonestbox.cpp @@ -0,0 +1,418 @@ +// - full automation of handling mini-pastures over nestboxes: +// go through all pens, check if they are empty and placed over a nestbox +// find female tame egg-layer who is not assigned to another pen and assign it to nestbox pasture +// maybe check for minimum age? it's not that useful to fill nestboxes with freshly hatched birds +// state and sleep setting is saved the first time autonestbox is started (to avoid writing stuff if the plugin is never used) + +#include "df/building_cagest.h" +#include "df/building_civzonest.h" +#include "df/building_nest_boxst.h" +#include "df/general_ref_building_civzone_assignedst.h" +#include "df/world.h" + +#include "Debug.h" +#include "LuaTools.h" +#include "PluginManager.h" + +#include "modules/Buildings.h" +#include "modules/Gui.h" +#include "modules/Maps.h" +#include "modules/Persistence.h" +#include "modules/Units.h" +#include "modules/World.h" + +using std::string; +using std::vector; + +using namespace DFHack; + +DFHACK_PLUGIN("autonestbox"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + +REQUIRE_GLOBAL(world); + +static const string autonestbox_help = + "Assigns unpastured female egg-layers to nestbox zones.\n" + "Requires that you create pen/pasture zones above nestboxes.\n" + "If the pen is bigger than 1x1 the nestbox must be in the top left corner.\n" + "Only 1 unit will be assigned per pen, regardless of the size.\n" + "The age of the units is currently not checked, most birds grow up quite fast.\n" + "When called without options autonestbox will instantly run once.\n" + "Usage:\n" + "\n" + "enable autonestbox\n" + " Start checking for unpastured egg-layers and assigning them to nestbox zones.\n" + "autonestbox now\n" + " Run a scan and assignment cycle right now. Does not require that the plugin is enabled.\n" + "autonestbox ticks \n" + " Change the number of ticks between scan and assignment cycles when the plugin is enabled.\n" + " The default is 6000 (about 8 days)\n"; + +namespace DFHack { + DBG_DECLARE(autonestbox, status); + DBG_DECLARE(autonestbox, cycle); +} + +static const string CONFIG_KEY = "autonestbox/config"; +static PersistentDataItem config; +enum ConfigValues { + IS_ENABLED = 0, + CYCLE_TICKS = 1, +}; +static int get_config_val(int index) { + if (!config.isValid()) + return -1; + return config.ival(index); +} +static bool set_config_val(int index, int value) { + if (!config.isValid()) + return false; + config.ival(index) = value; + return true; +} + +static bool did_complain = false; // avoids message spam +static size_t cycle_counter = 0; // how many ticks since the last cycle + +struct autonestbox_options { + // whether to display help + bool help = false; + + // whether to run a cycle right now + bool now = false; + + // how many ticks to wait between automatic cycles, -1 means unset + int32_t ticks = -1; + + static struct_identity _identity; +}; +static const struct_field_info autonestbox_options_fields[] = { + { struct_field_info::PRIMITIVE, "help", offsetof(autonestbox_options, help), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "now", offsetof(autonestbox_options, now), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "ticks", offsetof(autonestbox_options, ticks), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::END } +}; +struct_identity autonestbox_options::_identity(sizeof(autonestbox_options), &df::allocator_fn, NULL, "autonestbox_options", NULL, autonestbox_options_fields); + +static command_result df_autonestbox(color_ostream &out, vector ¶meters); +static void autonestbox_cycle(color_ostream &out); + +static void init_autonestbox(color_ostream &out) { + config = World::GetPersistentData(CONFIG_KEY); + + if (!config.isValid()) + config = World::AddPersistentData(CONFIG_KEY); + + if (get_config_val(IS_ENABLED) == -1) { + set_config_val(IS_ENABLED, 0); + set_config_val(CYCLE_TICKS, 6000); + } + + if (is_enabled) + set_config_val(IS_ENABLED, 1); + else + is_enabled = (get_config_val(IS_ENABLED) == 1); + did_complain = false; +} + +static void cleanup_autonestbox(color_ostream &out) { + is_enabled = false; +} + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + commands.push_back(PluginCommand( + "autonestbox", + "Auto-assign egg-laying female pets to nestbox zones.", + df_autonestbox, + false, + autonestbox_help.c_str())); + + init_autonestbox(out); + return CR_OK; +} + +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { + if (!Maps::IsValid()) { + out.printerr("Cannot run autonestbox without a loaded map.\n"); + return CR_FAILURE; + } + + if (enable != is_enabled) { + is_enabled = enable; + if (is_enabled) + init_autonestbox(out); + else + cleanup_autonestbox(out); + } + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown (color_ostream &out) { + cleanup_autonestbox(out); + return CR_OK; +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { + switch (event) { + case DFHack::SC_MAP_LOADED: + init_autonestbox(out); + break; + case DFHack::SC_MAP_UNLOADED: + cleanup_autonestbox(out); + break; + default: + break; + } + return CR_OK; +} + +DFhackCExport command_result plugin_onupdate(color_ostream &out) { + if (is_enabled && ++cycle_counter >= (size_t)get_config_val(CYCLE_TICKS)) + autonestbox_cycle(out); + return CR_OK; +} + +static bool get_options(color_ostream &out, + autonestbox_options &opts, + const vector ¶meters) +{ + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, parameters.size() + 2) || + !Lua::PushModulePublic( + out, L, "plugins.autonestbox", "parse_commandline")) { + out.printerr("Failed to load autonestbox Lua code\n"); + return false; + } + + Lua::Push(L, &opts); + for (const string ¶m : parameters) + Lua::Push(L, param); + + if (!Lua::SafeCall(out, L, parameters.size() + 1, 0)) + return false; + + return true; +} + +static command_result df_autonestbox(color_ostream &out, vector ¶meters) { + CoreSuspender suspend; + + if (!Maps::IsValid()) { + out.printerr("Cannot run autonestbox without a loaded map.\n"); + return CR_FAILURE; + } + + autonestbox_options opts; + if (!get_options(out, opts, parameters) || opts.help) + return CR_WRONG_USAGE; + + if (opts.ticks > -1) { + set_config_val(CYCLE_TICKS, opts.ticks); + INFO(status,out).print("New cycle timer: %d ticks.\n", opts.ticks); + } + else if (opts.now) { + autonestbox_cycle(out); + } + return CR_OK; +} + +///////////////////////////////////////////////////// +// autonestbox cycle logic +// + +static bool isEmptyPasture(df::building *building) { + if (!Buildings::isPenPasture(building)) + return false; + df::building_civzonest *civ = (df::building_civzonest *)building; + return (civ->assigned_units.size() == 0); +} + +static bool isFreeNestboxAtPos(int32_t x, int32_t y, int32_t z) { + for (auto building : world->buildings.all) { + if (building->getType() == df::building_type::NestBox + && building->x1 == x + && building->y1 == y + && building->z == z) { + df::building_nest_boxst *nestbox = (df::building_nest_boxst *)building; + if (nestbox->claimed_by == -1 && nestbox->contained_items.size() == 1) { + return true; + } + } + } + return false; +} + +static df::building* findFreeNestboxZone() { + for (auto building : world->buildings.all) { + if (isEmptyPasture(building) && + Buildings::isActive(building) && + isFreeNestboxAtPos(building->x1, building->y1, building->z)) { + return building; + } + } + return NULL; +} + +static bool isInBuiltCage(df::unit *unit) { + for (auto building : world->buildings.all) { + if (building->getType() == df::building_type::Cage) { + df::building_cagest* cage = (df::building_cagest *)building; + for (auto unitid : cage->assigned_units) { + if (unitid == unit->id) + return true; + } + } + } + return false; +} + +// check if assigned to pen, pit, (built) cage or chain +// note: BUILDING_CAGED is not set for animals (maybe it's used for dwarves who get caged as sentence) +// animals in cages (no matter if built or on stockpile) get the ref CONTAINED_IN_ITEM instead +// removing them from cages on stockpiles is no problem even without clearing the ref +// and usually it will be desired behavior to do so. +static bool isAssigned(df::unit *unit) { + for (auto ref : unit->general_refs) { + auto rtype = ref->getType(); + if(rtype == df::general_ref_type::BUILDING_CIVZONE_ASSIGNED + || rtype == df::general_ref_type::BUILDING_CAGED + || rtype == df::general_ref_type::BUILDING_CHAIN + || (rtype == df::general_ref_type::CONTAINED_IN_ITEM && isInBuiltCage(unit))) { + return true; + } + } + return false; +} + +static bool isFreeEgglayer(df::unit *unit) +{ + return Units::isActive(unit) && !Units::isUndead(unit) + && Units::isFemale(unit) + && Units::isTame(unit) + && Units::isOwnCiv(unit) + && Units::isEggLayer(unit) + && !isAssigned(unit) + && !Units::isGrazer(unit) // exclude grazing birds because they're messy + && !Units::isMerchant(unit) // don't steal merchant mounts + && !Units::isForest(unit); // don't steal birds from traders, they hate that +} + +static df::unit * findFreeEgglayer() { + for (auto unit : world->units.all) { + if (isFreeEgglayer(unit)) + return unit; + } + return NULL; +} + +static df::general_ref_building_civzone_assignedst * createCivzoneRef() { + static bool vt_initialized = false; + + // after having run successfully for the first time it's safe to simply create the object + if (vt_initialized) { + return (df::general_ref_building_civzone_assignedst *) + df::general_ref_building_civzone_assignedst::_identity.instantiate(); + } + + // being called for the first time, need to initialize the vtable + for (auto creature : world->units.all) { + for (auto ref : creature->general_refs) { + if (ref->getType() == df::general_ref_type::BUILDING_CIVZONE_ASSIGNED) { + if (strict_virtual_cast(ref)) { + vt_initialized = true; + // !! calling new() doesn't work, need _identity.instantiate() instead !! + return (df::general_ref_building_civzone_assignedst *) + df::general_ref_building_civzone_assignedst::_identity.instantiate(); + } + } + } + } + return NULL; +} + +static bool assignUnitToZone(color_ostream &out, df::unit *unit, df::building *building) { + // try to get a fresh civzone ref + df::general_ref_building_civzone_assignedst *ref = createCivzoneRef(); + if (!ref) { + ERR(cycle,out).print("Could not find a clonable activity zone reference!" + " You need to pen/pasture/pit at least one creature" + " before autonestbox can function.\n"); + return false; + } + + ref->building_id = building->id; + unit->general_refs.push_back(ref); + + df::building_civzonest *civz = (df::building_civzonest *)building; + civz->assigned_units.push_back(unit->id); + + INFO(cycle,out).print("Unit %d (%s) assigned to nestbox zone %d (%s)\n", + unit->id, Units::getRaceName(unit).c_str(), + building->id, building->name.c_str()); + + return true; +} + +static size_t countFreeEgglayers() { + size_t count = 0; + for (auto unit : world->units.all) { + if (isFreeEgglayer(unit)) + ++count; + } + return count; +} + +static size_t assign_nestboxes(color_ostream &out) { + size_t processed = 0; + df::building *free_building = NULL; + df::unit *free_unit = NULL; + do { + free_building = findFreeNestboxZone(); + free_unit = findFreeEgglayer(); + if (free_building && free_unit) { + if (!assignUnitToZone(out, free_unit, free_building)) { + DEBUG(cycle,out).print("Failed to assign unit to building.\n"); + return processed; + } + ++processed; + } + } while (free_unit && free_building); + + if (free_unit && !free_building) { + static size_t old_count = 0; + size_t freeEgglayers = countFreeEgglayers(); + // avoid spamming the same message + if (old_count != freeEgglayers) + did_complain = false; + old_count = freeEgglayers; + if (!did_complain) { + stringstream ss; + ss << freeEgglayers; + string announce = "Not enough free nestbox zones found! You need " + ss.str() + " more."; + Gui::showAnnouncement(announce, 6, true); + out << announce << endl; + did_complain = true; + } + } + return processed; +} + +static void autonestbox_cycle(color_ostream &out) { + // mark that we have recently run + cycle_counter = 0; + + size_t processed = assign_nestboxes(out); + if (processed > 0) { + stringstream ss; + ss << processed << " nestboxes were assigned."; + string announce = ss.str(); + Gui::showAnnouncement(announce, 2, false); + out << announce << endl; + // can complain again + // (might lead to spamming the same message twice, but catches the case + // where for example 2 new egglayers hatched right after 2 zones were created and assigned) + did_complain = false; + } +} diff --git a/plugins/lua/autonestbox.lua b/plugins/lua/autonestbox.lua new file mode 100644 index 000000000..f7140b0b7 --- /dev/null +++ b/plugins/lua/autonestbox.lua @@ -0,0 +1,51 @@ +local _ENV = mkmodule('plugins.autonestbox') + +local argparse = require('argparse') + +local function is_int(val) + return val and val == math.floor(val) +end + +local function is_positive_int(val) + return is_int(val) and val > 0 +end + +local function process_args(opts, args) + if args[1] == 'help' then + opts.help = true + return + end + + return argparse.processArgsGetopt(args, { + {'h', 'help', handler=function() opts.help = true end}, + }) +end + +function parse_commandline(opts, ...) + local positionals = process_args(opts, {...}) + + if opts.help then return end + + local in_ticks = false + for _,arg in ipairs(positionals) do + if in_ticks then + arg = tonumber(arg) + if not is_positive_int(arg) then + qerror('number of ticks must be a positive integer: ' .. arg) + else + opts.ticks = arg + end + in_ticks = false + elseif arg == 'ticks' then + in_ticks = true + elseif arg == 'now' then + opts.now = true + end + end + + if in_ticks then + qerror('missing number of ticks') + end +end + +return _ENV diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 385d79728..6353bfd35 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -112,7 +112,6 @@ REQUIRE_GLOBAL(ui_menu_width); using namespace DFHack::Gui; command_result df_zone (color_ostream &out, vector & parameters); -command_result df_autonestbox (color_ostream &out, vector & parameters); command_result df_autobutcher(color_ostream &out, vector & parameters); DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable); @@ -201,19 +200,6 @@ const string zone_help_examples = " well, unless you have a mod with egg-laying male elves who give milk...\n"; -const string autonestbox_help = - "Assigns unpastured female egg-layers to nestbox zones.\n" - "Requires that you create pen/pasture zones above nestboxes.\n" - "If the pen is bigger than 1x1 the nestbox must be in the top left corner.\n" - "Only 1 unit will be assigned per pen, regardless of the size.\n" - "The age of the units is currently not checked, most birds grow up quite fast.\n" - "When called without options autonestbox will instantly run once.\n" - "Options:\n" - " start - run every X frames (df simulation ticks)\n" - " default: X=6000 (~60 seconds at 100fps)\n" - " stop - stop running automatically\n" - " sleep X - change timer to sleep X frames between runs.\n"; - const string autobutcher_help = "Assigns your lifestock for slaughter once it reaches a specific count. Requires\n" "that you add the target race(s) to a watch list. Only tame units will be\n" @@ -277,27 +263,19 @@ command_result init_autobutcher(color_ostream &out); command_result cleanup_autobutcher(color_ostream &out); command_result start_autobutcher(color_ostream &out); -command_result init_autonestbox(color_ostream &out); -command_result cleanup_autonestbox(color_ostream &out); -command_result start_autonestbox(color_ostream &out); - /////////////// -// stuff for autonestbox and autobutcher +// stuff for autobutcher // should be moved to own plugin once the tool methods it shares with the zone plugin are moved to Unit.h / Building.h command_result autoNestbox( color_ostream &out, bool verbose ); command_result autoButcher( color_ostream &out, bool verbose ); -static bool enable_autonestbox = false; static bool enable_autobutcher = false; static bool enable_autobutcher_autowatch = false; -static size_t sleep_autonestbox = 6000; static size_t sleep_autobutcher = 6000; -static bool autonestbox_did_complain = false; // avoids message spam static PersistentDataItem config_autobutcher; -static PersistentDataItem config_autonestbox; DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { @@ -306,14 +284,11 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan case DFHack::SC_MAP_LOADED: // initialize from the world just loaded init_autobutcher(out); - init_autonestbox(out); break; case DFHack::SC_MAP_UNLOADED: - enable_autonestbox = false; enable_autobutcher = false; // cleanup cleanup_autobutcher(out); - cleanup_autonestbox(out); break; default: break; @@ -323,18 +298,8 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { - static size_t ticks_autonestbox = 0; static size_t ticks_autobutcher = 0; - if(enable_autonestbox) - { - if(++ticks_autonestbox >= sleep_autonestbox) - { - ticks_autonestbox = 0; - autoNestbox(out, false); - } - } - if(enable_autobutcher) { if(++ticks_autobutcher >= sleep_autobutcher) @@ -2242,138 +2207,6 @@ command_result df_zone (color_ostream &out, vector & parameters) return CR_OK; } -//////////////////// -// autonestbox stuff - -command_result df_autonestbox(color_ostream &out, vector & parameters) -{ - CoreSuspender suspend; - - bool verbose = false; - - for (size_t i = 0; i < parameters.size(); i++) - { - string & p = parameters[i]; - - if (p == "help" || p == "?") - { - out << autonestbox_help << endl; - return CR_OK; - } - if (p == "start") - { - autonestbox_did_complain = false; - start_autonestbox(out); - return autoNestbox(out, verbose); - } - if (p == "stop") - { - enable_autonestbox = false; - if(config_autonestbox.isValid()) - config_autonestbox.ival(0) = 0; - out << "Autonestbox stopped." << endl; - return CR_OK; - } - else if(p == "verbose") - { - verbose = true; - } - else if(p == "sleep") - { - if(i == parameters.size()-1) - { - out.printerr("No duration specified!\n"); - return CR_WRONG_USAGE; - } - else - { - size_t ticks = 0; - stringstream ss(parameters[i+1]); - i++; - ss >> ticks; - if(ticks <= 0) - { - out.printerr("Invalid duration specified (must be > 0)!\n"); - return CR_WRONG_USAGE; - } - sleep_autonestbox = ticks; - if(config_autonestbox.isValid()) - config_autonestbox.ival(1) = sleep_autonestbox; - out << "New sleep timer for autonestbox: " << ticks << " ticks." << endl; - return CR_OK; - } - } - else - { - out << "Unknown command: " << p << endl; - return CR_WRONG_USAGE; - } - } - return autoNestbox(out, verbose); -} - -command_result autoNestbox( color_ostream &out, bool verbose = false ) -{ - bool stop = false; - size_t processed = 0; - - if (!Maps::IsValid()) - { - out.printerr("Map is not available!\n"); - enable_autonestbox = false; - return CR_FAILURE; - } - - do - { - df::building * free_building = findFreeNestboxZone(); - df::unit * free_unit = findFreeEgglayer(); - if(free_building && free_unit) - { - command_result result = assignUnitToBuilding(out, free_unit, free_building, verbose); - if(result != CR_OK) - return result; - processed ++; - } - else - { - stop = true; - if(free_unit && !free_building) - { - static size_t old_count = 0; - size_t freeEgglayers = countFreeEgglayers(); - // avoid spamming the same message - if(old_count != freeEgglayers) - autonestbox_did_complain = false; - old_count = freeEgglayers; - if(!autonestbox_did_complain) - { - stringstream ss; - ss << freeEgglayers; - string announce = "Not enough free nestbox zones found! You need " + ss.str() + " more."; - Gui::showAnnouncement(announce, 6, true); - out << announce << endl; - autonestbox_did_complain = true; - } - } - } - } while (!stop); - if(processed > 0) - { - stringstream ss; - ss << processed; - string announce; - announce = ss.str() + " nestboxes were assigned."; - Gui::showAnnouncement(announce, 2, false); - out << announce << endl; - // can complain again - // (might lead to spamming the same message twice, but catches the case - // where for example 2 new egglayers hatched right after 2 zones were created and assigned) - autonestbox_did_complain = false; - } - return CR_OK; -} - //////////////////// // autobutcher stuff @@ -3129,7 +2962,7 @@ command_result autoButcher( color_ostream &out, bool verbose = false ) } //////////////////////////////////////////////////// -// autobutcher and autonestbox start/init/cleanup +// autobutcher start/init/cleanup command_result start_autobutcher(color_ostream &out) { @@ -3227,62 +3060,6 @@ command_result cleanup_autobutcher(color_ostream &out) return CR_OK; } -command_result start_autonestbox(color_ostream &out) -{ - plugin_enable(out, true); - enable_autonestbox = true; - - if (!config_autonestbox.isValid()) - { - config_autonestbox = World::AddPersistentData("autonestbox/config"); - - if (!config_autonestbox.isValid()) - { - out << "Cannot enable autonestbox without a world!" << endl; - return CR_OK; - } - - config_autonestbox.ival(1) = sleep_autonestbox; - } - - config_autonestbox.ival(0) = enable_autonestbox; - - out << "Starting autonestbox." << endl; - init_autonestbox(out); - return CR_OK; -} - -command_result init_autonestbox(color_ostream &out) -{ - cleanup_autonestbox(out); - - config_autonestbox = World::GetPersistentData("autonestbox/config"); - if(config_autonestbox.isValid()) - { - if (config_autonestbox.ival(0) == -1) - { - config_autonestbox.ival(0) = enable_autonestbox; - config_autonestbox.ival(1) = sleep_autonestbox; - out << "Autonestbox's persistent config object was invalid!" << endl; - } - else - { - enable_autonestbox = config_autonestbox.ival(0); - sleep_autonestbox = config_autonestbox.ival(1); - } - } - if (enable_autonestbox) - plugin_enable(out, true); - return CR_OK; -} - -command_result cleanup_autonestbox(color_ostream &out) -{ - // nothing to cleanup currently - // (future version of autonestbox could store info about cages for useless male kids) - return CR_OK; -} - // abuse WatchedRace struct for counting stocks (since it sorts by gender and age) // calling method must delete pointer! WatchedRace * checkRaceStocksTotal(int race) @@ -4121,24 +3898,17 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector Date: Tue, 2 Aug 2022 01:07:13 -0700 Subject: [PATCH 049/111] split autobutcher out from zone --- plugins/CMakeLists.txt | 3 +- plugins/autobutcher.cpp | 1166 +++++++++++++++++++++ plugins/lua/autobutcher.lua | 89 ++ plugins/lua/zone.lua | 12 - plugins/zone.cpp | 1965 ++++------------------------------- 5 files changed, 1437 insertions(+), 1798 deletions(-) create mode 100644 plugins/autobutcher.cpp create mode 100644 plugins/lua/autobutcher.lua delete mode 100644 plugins/lua/zone.lua diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 6ef5b59db..338db4ab9 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -83,6 +83,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(3dveins 3dveins.cpp) dfhack_plugin(add-spatter add-spatter.cpp) # dfhack_plugin(advtools advtools.cpp) + dfhack_plugin(autobutcher autobutcher.cpp LINK_LIBRARIES lua) dfhack_plugin(autochop autochop.cpp) dfhack_plugin(autoclothing autoclothing.cpp) dfhack_plugin(autodump autodump.cpp) @@ -178,7 +179,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(workflow workflow.cpp LINK_LIBRARIES lua) dfhack_plugin(workNow workNow.cpp) dfhack_plugin(xlsxreader xlsxreader.cpp LINK_LIBRARIES lua xlsxio_read_STATIC zip expat) - dfhack_plugin(zone zone.cpp LINK_LIBRARIES lua) + dfhack_plugin(zone zone.cpp) # If you are adding a plugin that you do not intend to commit to the DFHack repo, # see instructions for adding "external" plugins at the end of this file. diff --git a/plugins/autobutcher.cpp b/plugins/autobutcher.cpp new file mode 100644 index 000000000..0d7a9c632 --- /dev/null +++ b/plugins/autobutcher.cpp @@ -0,0 +1,1166 @@ +// - full automation of marking live-stock for slaughtering +// races can be added to a watchlist and it can be set how many male/female kids/adults are left alive +// adding to the watchlist can be automated as well. +// config for autobutcher (state and sleep setting) is saved the first time autobutcher is started +// config for watchlist entries is saved when they are created or modified + +#include +#include + +#include "df/building_cagest.h" +#include "df/creature_raw.h" +#include "df/world.h" + +#include "Debug.h" +#include "LuaTools.h" +#include "PluginManager.h" + +#include "modules/Gui.h" +#include "modules/Maps.h" +#include "modules/Persistence.h" +#include "modules/Units.h" +#include "modules/World.h" + +using std::string; +using std::unordered_map; +using std::unordered_set; +using std::vector; + +using namespace DFHack; + +DFHACK_PLUGIN("autobutcher"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + +REQUIRE_GLOBAL(world); + +const string autobutcher_help = + "Assigns your lifestock for slaughter once it reaches a specific count. Requires\n" + "that you add the target race(s) to a watch list. Only tame units will be\n" + "processed. Named units will be completely ignored (you can give animals\n" + "nicknames with the tool 'rename unit' to protect them from getting slaughtered\n" + "automatically. Trained war or hunting pets will be ignored.\n" + "Once you have too much adults, the oldest will be butchered first.\n" + "Once you have too much kids, the youngest will be butchered first.\n" + "If you don't set a target count the following default will be used:\n" + "1 male kid, 5 female kids, 1 male adult, 5 female adults.\n" + "Options:\n" + " start - run every X frames (df simulation ticks)\n" + " default: X=6000 (~60 seconds at 100fps)\n" + " stop - stop running automatically\n" + " sleep X - change timer to sleep X frames between runs.\n" + " watch R - start watching race(s)\n" + " R = valid race RAW id (ALPACA, BIRD_TURKEY, etc)\n" + " or a list of RAW ids seperated by spaces\n" + " or the keyword 'all' which affects your whole current watchlist.\n" + " unwatch R - stop watching race(s)\n" + " the current target settings will be remembered\n" + " forget R - unwatch race(s) and forget target settings for it/them\n" + " autowatch - automatically adds all new races (animals you buy\n" + " from merchants, tame yourself or get from migrants)\n" + " to the watch list using default target count\n" + " noautowatch - stop auto-adding new races to the watch list\n" + " list - print status and watchlist\n" + " list_export - print status and watchlist in batchfile format\n" + " can be used to copy settings into another savegame\n" + " usage: 'dfhack-run autobutcher list_export > xyz.bat' \n" + " target fk mk fa ma R\n" + " - set target count for specified race:\n" + " fk = number of female kids\n" + " mk = number of male kids\n" + " fa = number of female adults\n" + " ma = number of female adults\n" + " R = 'all' sets count for all races on the current watchlist\n" + " including the races which are currenly set to 'unwatched'\n" + " and sets the new default for future watch commands\n" + " R = 'new' sets the new default for future watch commands\n" + " without changing your current watchlist\n" + " example - print some usage examples\n"; + +const string autobutcher_help_example = + "Examples:\n" + " autobutcher target 4 3 2 1 ALPACA BIRD_TURKEY\n" + " autobutcher watch ALPACA BIRD_TURKEY\n" + " autobutcher start\n" + " This means you want to have max 7 kids (4 female, 3 male) and max 3 adults\n" + " (2 female, 1 male) of the races alpaca and turkey. Once the kids grow up the\n" + " oldest adults will get slaughtered. Excess kids will get slaughtered starting\n" + " the the youngest to allow that the older ones grow into adults.\n" + " autobutcher target 0 0 0 0 new\n" + " autobutcher autowatch\n" + " autobutcher start\n" + " This tells autobutcher to automatically put all new races onto the watchlist\n" + " and mark unnamed tame units for slaughter as soon as they arrive in your\n" + " fortress. Settings already made for some races will be left untouched.\n"; + +namespace DFHack { + DBG_DECLARE(autobutcher, status); + DBG_DECLARE(autobutcher, cycle); +} + +static const string CONFIG_KEY = "autobutcher/config"; +static const string WATCHLIST_CONFIG_KEY_PREFIX = "autobutcher/watchlist/"; + +static PersistentDataItem config; +enum ConfigValues { + CONFIG_IS_ENABLED = 0, + CONFIG_CYCLE_TICKS = 1, + CONFIG_AUTOWATCH = 2, + CONFIG_DEFAULT_FK = 3, + CONFIG_DEFAULT_MK = 4, + CONFIG_DEFAULT_FA = 5, + CONFIG_DEFAULT_MA = 6, +}; +static int get_config_val(int index) { + if (!config.isValid()) + return -1; + return config.ival(index); +} +static bool get_config_bool(int index) { + return get_config_val(index) == 1; +} +static void set_config_val(int index, int value) { + if (config.isValid()) + config.ival(index) = value; +} +static void set_config_bool(int index, bool value) { + set_config_val(index, value ? 1 : 0); +} + +static unordered_map race_to_id; +static size_t cycle_counter = 0; // how many ticks since the last cycle + +static size_t DEFAULT_CYCLE_TICKS = 6000; + +struct autobutcher_options { + // whether to display help + bool help = false; + + // the command to run. + string command; + + // the set of (unverified) races that the command should affect, and whether + // "all" or "new" was specified as the race + vector races; + bool races_all = false; + bool races_new = false; + + // params for the "target" command + int32_t fk = -1; + int32_t mk = -1; + int32_t fa = -1; + int32_t ma = -1; + + // how many ticks to wait between automatic cycles, -1 means unset + int32_t ticks = -1; + + static struct_identity _identity; + + // non-virtual destructor so offsetof() still works for the fields + ~autobutcher_options() { + for (auto str : races) + delete str; + } +}; +static const struct_field_info autobutcher_options_fields[] = { + { struct_field_info::PRIMITIVE, "help", offsetof(autobutcher_options, help), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "command", offsetof(autobutcher_options, command), df::identity_traits::get(), 0, 0 }, + { struct_field_info::STL_VECTOR_PTR, "races", offsetof(autobutcher_options, races), df::identity_traits::get(), 0, 0 }, + { struct_field_info::PRIMITIVE, "races_all", offsetof(autobutcher_options, races_all), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "races_new", offsetof(autobutcher_options, races_new), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "fk", offsetof(autobutcher_options, fk), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "mk", offsetof(autobutcher_options, mk), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "fa", offsetof(autobutcher_options, fa), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "ma", offsetof(autobutcher_options, ma), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "ticks", offsetof(autobutcher_options, ticks), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::END } +}; +struct_identity autobutcher_options::_identity(sizeof(autobutcher_options), &df::allocator_fn, NULL, "autobutcher_options", NULL, autobutcher_options_fields); + +static void init_autobutcher(color_ostream &out); +static void cleanup_autobutcher(color_ostream &out); +static command_result df_autobutcher(color_ostream &out, vector ¶meters); +static void autobutcher_cycle(color_ostream &out); + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + commands.push_back(PluginCommand( + "autobutcher", + "Automatically butcher excess livestock.", + df_autobutcher, + false, + autobutcher_help.c_str())); + + init_autobutcher(out); + return CR_OK; +} + +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { + if (!Maps::IsValid()) { + out.printerr("Cannot run autobutcher without a loaded map.\n"); + return CR_FAILURE; + } + + if (enable != is_enabled) { + is_enabled = enable; + if (is_enabled) + init_autobutcher(out); + else + cleanup_autobutcher(out); + } + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown (color_ostream &out) { + cleanup_autobutcher(out); + return CR_OK; +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { + switch (event) { + case DFHack::SC_MAP_LOADED: + init_autobutcher(out); + break; + case DFHack::SC_MAP_UNLOADED: + cleanup_autobutcher(out); + break; + default: + break; + } + return CR_OK; +} + +DFhackCExport command_result plugin_onupdate(color_ostream &out) { + if (is_enabled && ++cycle_counter >= (size_t)get_config_val(CONFIG_CYCLE_TICKS)) + autobutcher_cycle(out); + return CR_OK; +} + +///////////////////////////////////////////////////// +// autobutcher config logic +// + +static void doMarkForSlaughter(df::unit *unit) { + unit->flags2.bits.slaughter = 1; +} + +// getUnitAge() returns 0 if born in current year, therefore the look at birth_time in that case +// (assuming that the value from there indicates in which tick of the current year the unit was born) +static bool compareUnitAgesYounger(df::unit *i, df::unit *j) { + int32_t age_i = (int32_t)Units::getAge(i, true); + int32_t age_j = (int32_t)Units::getAge(j, true); + if (age_i == 0 && age_j == 0) { + age_i = i->birth_time; + age_j = j->birth_time; + } + return age_i < age_j; +} + +static bool compareUnitAgesOlder(df::unit* i, df::unit* j) { + int32_t age_i = (int32_t)Units::getAge(i, true); + int32_t age_j = (int32_t)Units::getAge(j, true); + if(age_i == 0 && age_j == 0) { + age_i = i->birth_time; + age_j = j->birth_time; + } + return age_i > age_j; +} + +enum unit_ptr_index { + fk_index = 0, + mk_index = 1, + fa_index = 2, + ma_index = 3 +}; + +struct WatchedRace { +public: + PersistentDataItem rconfig; + + int raceId; + bool isWatched; // if true, autobutcher will process this race + + // target amounts + unsigned fk; // max female kids + unsigned mk; // max male kids + unsigned fa; // max female adults + unsigned ma; // max male adults + + // amounts of protected (not butcherable) units + unsigned fk_prot; + unsigned fa_prot; + unsigned mk_prot; + unsigned ma_prot; + + // butcherable units + vector unit_ptr[4]; + + // priority butcherable units + vector prot_ptr[4]; + + WatchedRace(color_ostream &out, int id, bool watch, unsigned _fk, unsigned _mk, unsigned _fa, unsigned _ma) { + raceId = id; + isWatched = watch; + fk = _fk; + mk = _mk; + fa = _fa; + ma = _ma; + fk_prot = fa_prot = mk_prot = ma_prot = 0; + + DEBUG(status,out).print("creating new WatchedRace: id=%d, watched=%s, fk=%u, mk=%u, fa=%u, ma=%u\n", + id, watch ? "true" : "false", fk, mk, fa, ma); + } + + WatchedRace(color_ostream &out, const PersistentDataItem &p) + : WatchedRace(out, p.ival(0), p.ival(1), p.ival(2), p.ival(3), p.ival(4), p.ival(5)) { + rconfig = p; + } + + ~WatchedRace() { + ClearUnits(); + } + + void UpdateConfig(color_ostream &out) { + if(!rconfig.isValid()) { + string keyname = WATCHLIST_CONFIG_KEY_PREFIX + Units::getRaceNameById(raceId); + rconfig = World::GetPersistentData(keyname, NULL); + } + if(rconfig.isValid()) { + rconfig.ival(0) = raceId; + rconfig.ival(1) = isWatched; + rconfig.ival(2) = fk; + rconfig.ival(3) = mk; + rconfig.ival(4) = fa; + rconfig.ival(5) = ma; + } + else { + ERR(status,out).print("could not create persistent key for race: %s", + Units::getRaceNameById(raceId).c_str()); + } + } + + void RemoveConfig(color_ostream &out) { + if(!rconfig.isValid()) + return; + World::DeletePersistentData(rconfig); + } + + void SortUnitsByAge() { + sort(unit_ptr[fk_index].begin(), unit_ptr[fk_index].end(), compareUnitAgesOlder); + sort(unit_ptr[mk_index].begin(), unit_ptr[mk_index].end(), compareUnitAgesOlder); + sort(unit_ptr[fa_index].begin(), unit_ptr[fa_index].end(), compareUnitAgesYounger); + sort(unit_ptr[ma_index].begin(), unit_ptr[ma_index].end(), compareUnitAgesYounger); + sort(prot_ptr[fk_index].begin(), prot_ptr[fk_index].end(), compareUnitAgesOlder); + sort(prot_ptr[mk_index].begin(), prot_ptr[mk_index].end(), compareUnitAgesOlder); + sort(prot_ptr[fa_index].begin(), prot_ptr[fa_index].end(), compareUnitAgesYounger); + sort(prot_ptr[ma_index].begin(), prot_ptr[ma_index].end(), compareUnitAgesYounger); + } + + void PushUnit(df::unit *unit) { + if(Units::isFemale(unit)) { + if(Units::isBaby(unit) || Units::isChild(unit)) + unit_ptr[fk_index].push_back(unit); + else + unit_ptr[fa_index].push_back(unit); + } + else //treat sex n/a like it was male + { + if(Units::isBaby(unit) || Units::isChild(unit)) + unit_ptr[mk_index].push_back(unit); + else + unit_ptr[ma_index].push_back(unit); + } + } + + void PushPriorityUnit(df::unit *unit) { + if(Units::isFemale(unit)) { + if(Units::isBaby(unit) || Units::isChild(unit)) + prot_ptr[fk_index].push_back(unit); + else + prot_ptr[fa_index].push_back(unit); + } + else { + if(Units::isBaby(unit) || Units::isChild(unit)) + prot_ptr[mk_index].push_back(unit); + else + prot_ptr[ma_index].push_back(unit); + } + } + + void PushProtectedUnit(df::unit *unit) { + if(Units::isFemale(unit)) { + if(Units::isBaby(unit) || Units::isChild(unit)) + fk_prot++; + else + fa_prot++; + } + else { //treat sex n/a like it was male + if(Units::isBaby(unit) || Units::isChild(unit)) + mk_prot++; + else + ma_prot++; + } + } + + void ClearUnits() { + fk_prot = fa_prot = mk_prot = ma_prot = 0; + for (size_t i = 0; i < 4; i++) { + unit_ptr[i].clear(); + prot_ptr[i].clear(); + } + } + + int ProcessUnits(vector& unit_ptr, vector& unit_pri_ptr, unsigned prot, unsigned goal) { + int subcount = 0; + while (unit_pri_ptr.size() && (unit_ptr.size() + unit_pri_ptr.size() + prot > goal)) { + df::unit *unit = unit_pri_ptr.back(); + doMarkForSlaughter(unit); + unit_pri_ptr.pop_back(); + subcount++; + } + while (unit_ptr.size() && (unit_ptr.size() + prot > goal)) { + df::unit *unit = unit_ptr.back(); + doMarkForSlaughter(unit); + unit_ptr.pop_back(); + subcount++; + } + return subcount; + } + + int ProcessUnits() { + SortUnitsByAge(); + int slaughter_count = 0; + slaughter_count += ProcessUnits(unit_ptr[fk_index], prot_ptr[fk_index], fk_prot, fk); + slaughter_count += ProcessUnits(unit_ptr[mk_index], prot_ptr[mk_index], mk_prot, mk); + slaughter_count += ProcessUnits(unit_ptr[fa_index], prot_ptr[fa_index], fa_prot, fa); + slaughter_count += ProcessUnits(unit_ptr[ma_index], prot_ptr[ma_index], ma_prot, ma); + ClearUnits(); + return slaughter_count; + } +}; + +// vector of races handled by autobutcher +// the name is a bit misleading since entries can be set to 'unwatched' +// to ignore them for a while but still keep the target count settings +static unordered_map watched_races; + +static void init_autobutcher(color_ostream &out) { + config = World::GetPersistentData(CONFIG_KEY); + + if (!config.isValid()) + config = World::AddPersistentData(CONFIG_KEY); + + if (get_config_val(CONFIG_IS_ENABLED) == -1) { + set_config_bool(CONFIG_IS_ENABLED, false); + set_config_val(CONFIG_CYCLE_TICKS, DEFAULT_CYCLE_TICKS); + set_config_bool(CONFIG_AUTOWATCH, false); + set_config_val(CONFIG_DEFAULT_FK, 5); + set_config_val(CONFIG_DEFAULT_MK, 1); + set_config_val(CONFIG_DEFAULT_FA, 5); + set_config_val(CONFIG_DEFAULT_MA, 1); + } + + if (is_enabled) + set_config_bool(CONFIG_IS_ENABLED, true); + else + is_enabled = (get_config_val(CONFIG_IS_ENABLED) == 1); + + if (!config.isValid()) + return; + + if (!race_to_id.size()) { + const size_t num_races = world->raws.creatures.all.size(); + for(size_t i = 0; i < num_races; ++i) + race_to_id.emplace(Units::getRaceNameById(i), i); + } + + std::vector watchlist; + World::GetPersistentData(&watchlist, WATCHLIST_CONFIG_KEY_PREFIX, true); + for (auto & p : watchlist) { + DEBUG(status,out).print("Reading from save: %s\n", p.key().c_str()); + WatchedRace *w = new WatchedRace(out, p); + watched_races.emplace(w->raceId, w); + } +} + +static void cleanup_autobutcher(color_ostream &out) { + is_enabled = false; + race_to_id.clear(); + for (auto w : watched_races) + delete w.second; + watched_races.clear(); +} + +static bool get_options(color_ostream &out, + autobutcher_options &opts, + const vector ¶meters) +{ + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, parameters.size() + 2) || + !Lua::PushModulePublic( + out, L, "plugins.autobutcher", "parse_commandline")) { + out.printerr("Failed to load autobutcher Lua code\n"); + return false; + } + + Lua::Push(L, &opts); + for (const string ¶m : parameters) + Lua::Push(L, param); + + if (!Lua::SafeCall(out, L, parameters.size() + 1, 0)) + return false; + + return true; +} + +static void autobutcher_export(color_ostream &out); +static void autobutcher_status(color_ostream &out); +static void autobutcher_target(color_ostream &out, const autobutcher_options &opts); +static void autobutcher_modify_watchlist(color_ostream &out, const autobutcher_options &opts); + +static command_result df_autobutcher(color_ostream &out, vector ¶meters) { + CoreSuspender suspend; + + if (!Maps::IsValid()) { + out.printerr("Cannot run autobutcher without a loaded map.\n"); + return CR_FAILURE; + } + + autobutcher_options opts; + if (!get_options(out, opts, parameters) || opts.help) + return CR_WRONG_USAGE; + + if (opts.command == "now") { + autobutcher_cycle(out); + } + else if (opts.command == "autowatch") { + set_config_bool(CONFIG_AUTOWATCH, true); + } + else if (opts.command == "noautowatch") { + set_config_bool(CONFIG_AUTOWATCH, false); + } + else if (opts.command == "list_export") { + autobutcher_export(out); + } + else if (opts.command == "target") { + autobutcher_target(out, opts); + } + else if (opts.command == "watch" || + opts.command == "unwatch" || + opts.command == "forget") { + autobutcher_modify_watchlist(out, opts); + } + else if (opts.command == "ticks") { + set_config_val(CONFIG_CYCLE_TICKS, opts.ticks); + INFO(status,out).print("New cycle timer: %d ticks.\n", opts.ticks); + } + else { + autobutcher_status(out); + } + + return CR_OK; +} + +// helper for sorting the watchlist alphabetically +static bool compareRaceNames(WatchedRace* i, WatchedRace* j) { + string name_i = Units::getRaceNamePluralById(i->raceId); + string name_j = Units::getRaceNamePluralById(j->raceId); + + return name_i < name_j; +} + +// sort watchlist alphabetically +static vector getSortedWatchList() { + vector list; + for (auto w : watched_races) { + list.push_back(w.second); + } + sort(list.begin(), list.end(), compareRaceNames); + return list; +} + +static void autobutcher_export(color_ostream &out) { + out << "enable autobutcher" << endl; + out << "autobutcher ticks " << get_config_val(CONFIG_CYCLE_TICKS) << endl; + out << "autobutcher " << (get_config_bool(CONFIG_AUTOWATCH) ? "" : "no") + << "autowatch" << endl; + out << "autobutcher target" + << " " << get_config_val(CONFIG_DEFAULT_FK) + << " " << get_config_val(CONFIG_DEFAULT_MK) + << " " << get_config_val(CONFIG_DEFAULT_FA) + << " " << get_config_val(CONFIG_DEFAULT_MA) + << " new" << endl; + + for (auto w : getSortedWatchList()) { + df::creature_raw *raw = world->raws.creatures.all[w->raceId]; + string name = raw->creature_id; + out << "autobutcher target" + << " " << w->fk + << " " << w->mk + << " " << w->fa + << " " << w->ma + << " " << name << endl; + if (w->isWatched) + out << "autobutcher watch " << name << endl; + } +} + +static void autobutcher_status(color_ostream &out) { + out << "autobutcher is " << (is_enabled ? "" : "not ") << "enabled\n"; + if (is_enabled) + out << " running every " << get_config_val(CONFIG_CYCLE_TICKS) << " game ticks\n"; + out << " " << (get_config_bool(CONFIG_AUTOWATCH) ? "" : "not ") << "autowatching for new races\n"; + + out << "\ndefault setting for new races:" + << " fk=" << get_config_val(CONFIG_DEFAULT_FK) + << " mk=" << get_config_val(CONFIG_DEFAULT_MK) + << " fa=" << get_config_val(CONFIG_DEFAULT_FA) + << " ma=" << get_config_val(CONFIG_DEFAULT_MA) + << endl << endl; + + if (!watched_races.size()) { + out << "not currently watching any races. to find out how to add some, run:\n help autobutcher" << endl; + return; + } + + out << "monitoring races: " << endl; + for (auto w : getSortedWatchList()) { + df::creature_raw *raw = world->raws.creatures.all[w->raceId]; + out << " " << Units::getRaceNamePluralById(w->raceId) << " \t"; + out << "(" << raw->creature_id; + out << " fk=" << w->fk + << " mk=" << w->mk + << " fa=" << w->fa + << " ma=" << w->ma; + if (!w->isWatched) + out << "; autobutchering is paused"; + out << ")" << endl; + } +} + +static void autobutcher_target(color_ostream &out, const autobutcher_options &opts) { + if (opts.races_new) { + DEBUG(status,out).print("setting targets for new races\n"); + set_config_val(CONFIG_DEFAULT_FK, opts.fk); + set_config_val(CONFIG_DEFAULT_MK, opts.mk); + set_config_val(CONFIG_DEFAULT_FA, opts.fa); + set_config_val(CONFIG_DEFAULT_MA, opts.ma); + } + + if (opts.races_all) { + DEBUG(status,out).print("setting targets for all races on watchlist\n"); + for (auto w : watched_races) { + w.second->fk = opts.fk; + w.second->mk = opts.mk; + w.second->fa = opts.fa; + w.second->ma = opts.ma; + w.second->UpdateConfig(out); + } + } + + for (auto race : opts.races) { + if (!race_to_id.count(*race)) { + out.printerr("race not found: '%s'", race->c_str()); + continue; + } + int id = race_to_id[*race]; + WatchedRace *w; + if (!watched_races.count(id)) { + DEBUG(status,out).print("adding new targets for %s\n", race->c_str()); + w = new WatchedRace(out, id, true, opts.fk, opts.mk, opts.fa, opts.ma); + watched_races.emplace(id, w); + } else { + w = watched_races[id]; + w->fk = opts.fk; + w->mk = opts.mk; + w->fa = opts.fa; + w->ma = opts.ma; + } + w->UpdateConfig(out); + } +} + +static void autobutcher_modify_watchlist(color_ostream &out, const autobutcher_options &opts) { + unordered_set ids; + + if (opts.races_all) { + DEBUG(status,out).print("modifying all races on watchlist: %s\n", + opts.command.c_str()); + for (auto w : watched_races) + ids.emplace(w.first); + } + + for (auto race : opts.races) { + if (!race_to_id.count(*race)) { + out.printerr("race not found: '%s'", race->c_str()); + continue; + } + ids.emplace(race_to_id[*race]); + } + + for (int id : ids) { + if (opts.command == "watch") + watched_races[id]->isWatched = true; + else if (opts.command == "unwatch") + watched_races[id]->isWatched = false; + else if (opts.command == "forget") { + watched_races[id]->RemoveConfig(out); + watched_races.erase(id); + continue; + } + watched_races[id]->UpdateConfig(out); + } +} + +///////////////////////////////////////////////////// +// autobutcher cycle logic +// + +// check if contained in item (e.g. animals in cages) +static bool isContainedInItem(df::unit *unit) { + for (auto gref : unit->general_refs) { + if (gref->getType() == df::general_ref_type::CONTAINED_IN_ITEM) { + return true; + } + } + return false; +} + +// found a unit with weird position values on one of my maps (negative and in the thousands) +// it didn't appear in the animal stocks screen, but looked completely fine otherwise (alive, tame, own, etc) +// maybe a rare bug, but better avoid assigning such units to zones or slaughter etc. +static bool hasValidMapPos(df::unit *unit) { + return unit->pos.x >= 0 && unit->pos.y >= 0 && unit->pos.z >= 0 + && unit->pos.x < world->map.x_count + && unit->pos.y < world->map.y_count + && unit->pos.z < world->map.z_count; +} + +// built cage defined as room (supposed to detect zoo cages) +static bool isInBuiltCageRoom(df::unit *unit) { + for (auto building : world->buildings.all) { + // !!! building->isRoom() returns true if the building can be made a room but currently isn't + // !!! except for coffins/tombs which always return false + // !!! using the bool is_room however gives the correct state/value + if (!building->is_room || building->getType() != df::building_type::Cage) + continue; + + df::building_cagest* cage = (df::building_cagest*)building; + for (auto cu : cage->assigned_units) + if (cu == unit->id) return true; + } + return false; +} + +static void autobutcher_cycle(color_ostream &out) { + DEBUG(cycle,out).print("running autobutcher_cycle\n"); + + // check if there is anything to watch before walking through units vector + if (!get_config_bool(CONFIG_AUTOWATCH)) { + bool watching = false; + for (auto w : watched_races) { + if (w.second->isWatched) { + watching = true; + break; + } + } + if (!watching) + return; + } + + for (auto unit : world->units.all) { + // this check is now divided into two steps, squeezed autowatch into the middle + // first one ignores completely inappropriate units (dead, undead, not belonging to the fort, ...) + // then let autowatch add units to the watchlist which will probably start breeding (owned pets, war animals, ...) + // then process units counting those which can't be butchered (war animals, named pets, ...) + // so that they are treated as "own stock" as well and count towards the target quota + if ( !Units::isActive(unit) + || Units::isUndead(unit) + || Units::isMarkedForSlaughter(unit) + || Units::isMerchant(unit) // ignore merchants' draft animals + || Units::isForest(unit) // ignore merchants' caged animals + || !Units::isOwnCiv(unit) + || !Units::isTame(unit) + ) + continue; + + // found a bugged unit which had invalid coordinates but was not in a cage. + // marking it for slaughter didn't seem to have negative effects, but you never know... + if(!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + WatchedRace *w; + if (watched_races.count(unit->race)) { + w = watched_races[unit->race]; + } + else if (!get_config_bool(CONFIG_AUTOWATCH)) { + continue; + } + else { + w = new WatchedRace(out, unit->race, true, get_config_val(CONFIG_DEFAULT_FK), + get_config_val(CONFIG_DEFAULT_MK), get_config_val(CONFIG_DEFAULT_FA), + get_config_val(CONFIG_DEFAULT_MA)); + w->UpdateConfig(out); + watched_races.emplace(unit->race, w); + + string announce = "New race added to autobutcher watchlist: " + Units::getRaceNamePluralById(unit->race); + Gui::showAnnouncement(announce, 2, false); + } + + if (w->isWatched) { + // don't butcher protected units, but count them as stock as well + // this way they count towards target quota, so if you order that you want 1 female adult cat + // and have 2 cats, one of them being a pet, the other gets butchered + if( Units::isWar(unit) // ignore war dogs etc + || Units::isHunter(unit) // ignore hunting dogs etc + // ignore creatures in built cages which are defined as rooms to leave zoos alone + // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) + || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() + || Units::isAvailableForAdoption(unit) + || unit->name.has_name) + w->PushProtectedUnit(unit); + else if ( Units::isGay(unit) + || Units::isGelded(unit)) + w->PushPriorityUnit(unit); + else + w->PushUnit(unit); + } + } + + int slaughter_count = 0; + for (auto w : watched_races) { + int slaughter_subcount = w.second->ProcessUnits(); + slaughter_count += slaughter_subcount; + if (slaughter_subcount) { + stringstream ss; + ss << slaughter_subcount; + string announce = Units::getRaceNamePluralById(w.first) + " marked for slaughter: " + ss.str(); + Gui::showAnnouncement(announce, 2, false); + } + } +} + +///////////////////////////////////// +// API functions to control autobutcher with a lua script + +// abuse WatchedRace struct for counting stocks (since it sorts by gender and age) +// calling method must delete pointer! +static WatchedRace * checkRaceStocksTotal(color_ostream &out, int race) { + WatchedRace * w = new WatchedRace(out, race, true, 0, 0, 0, 0); + for (auto unit : world->units.all) { + if (unit->race != race) + continue; + + if ( !Units::isActive(unit) + || Units::isUndead(unit) + || Units::isMerchant(unit) // ignore merchants' draft animals + || Units::isForest(unit) // ignore merchants' caged animals + || !Units::isOwnCiv(unit) + ) + continue; + + if(!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + w->PushUnit(unit); + } + return w; +} + +WatchedRace * checkRaceStocksProtected(color_ostream &out, int race) { + WatchedRace * w = new WatchedRace(out, race, true, 0, 0, 0, 0); + for (auto unit : world->units.all) { + if (unit->race != race) + continue; + + if ( !Units::isActive(unit) + || Units::isUndead(unit) + || Units::isMerchant(unit) // ignore merchants' draft animals + || Units::isForest(unit) // ignore merchants' caged animals + || !Units::isOwnCiv(unit) + ) + continue; + + // found a bugged unit which had invalid coordinates but was not in a cage. + // marking it for slaughter didn't seem to have negative effects, but you never know... + if (!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + if ( !Units::isTame(unit) + || Units::isWar(unit) // ignore war dogs etc + || Units::isHunter(unit) // ignore hunting dogs etc + // ignore creatures in built cages which are defined as rooms to leave zoos alone + // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) + || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() + || Units::isAvailableForAdoption(unit) + || unit->name.has_name ) + w->PushUnit(unit); + } + return w; +} + +WatchedRace * checkRaceStocksButcherable(color_ostream &out, int race) { + WatchedRace * w = new WatchedRace(out, race, true, 0, 0, 0, 0); + for (auto unit : world->units.all) { + if (unit->race != race) + continue; + + if ( !Units::isActive(unit) + || Units::isUndead(unit) + || Units::isMerchant(unit) // ignore merchants' draft animals + || Units::isForest(unit) // ignore merchants' caged animals + || !Units::isOwnCiv(unit) + || !Units::isTame(unit) + || Units::isWar(unit) // ignore war dogs etc + || Units::isHunter(unit) // ignore hunting dogs etc + // ignore creatures in built cages which are defined as rooms to leave zoos alone + // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) + || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() + || Units::isAvailableForAdoption(unit) + || unit->name.has_name + ) + continue; + + if (!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + w->PushUnit(unit); + } + return w; +} + +WatchedRace * checkRaceStocksButcherFlag(color_ostream &out, int race) { + WatchedRace * w = new WatchedRace(out, race, true, 0, 0, 0, 0); + for (auto unit : world->units.all) { + if(unit->race != race) + continue; + + if ( !Units::isActive(unit) + || Units::isUndead(unit) + || Units::isMerchant(unit) // ignore merchants' draft animals + || Units::isForest(unit) // ignore merchants' caged animals + || !Units::isOwnCiv(unit) + ) + continue; + + if (!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + if (Units::isMarkedForSlaughter(unit)) + w->PushUnit(unit); + } + return w; +} + +static bool autowatch_isEnabled() { + return get_config_bool(CONFIG_AUTOWATCH); +} + +static unsigned autobutcher_getSleep(color_ostream &out) { + return get_config_val(CONFIG_CYCLE_TICKS); +} + +static void autobutcher_setSleep(color_ostream &out, unsigned ticks) { + + set_config_val(CONFIG_CYCLE_TICKS, ticks); +} + +static void autowatch_setEnabled(color_ostream &out, bool enable) { + DEBUG(status,out).print("auto-adding to watchlist %s\n", enable ? "started" : "stopped"); + set_config_bool(CONFIG_AUTOWATCH, enable); +} + +// set all data for a watchlist race in one go +// if race is not already on watchlist it will be added +// params: (id, fk, mk, fa, ma, watched) +static void autobutcher_setWatchListRace(color_ostream &out, unsigned id, unsigned fk, unsigned mk, unsigned fa, unsigned ma, bool watched) { + if (watched_races.count(id)) { + DEBUG(status,out).print("updating watchlist entry\n"); + WatchedRace * w = watched_races[id]; + w->fk = fk; + w->mk = mk; + w->fa = fa; + w->ma = ma; + w->isWatched = watched; + w->UpdateConfig(out); + return; + } + + DEBUG(status,out).print("creating new watchlist entry\n"); + WatchedRace * w = new WatchedRace(out, id, watched, fk, mk, fa, ma); + w->UpdateConfig(out); + watched_races.emplace(id, w); + + string announce; + announce = "New race added to autobutcher watchlist: " + Units::getRaceNamePluralById(id); + Gui::showAnnouncement(announce, 2, false); +} + +// remove entry from watchlist +static void autobutcher_removeFromWatchList(color_ostream &out, unsigned id) { + if (watched_races.count(id)) { + DEBUG(status,out).print("removing watchlist entry\n"); + WatchedRace * w = watched_races[id]; + w->RemoveConfig(out); + watched_races.erase(id); + } +} + +// set default target values for new races +static void autobutcher_setDefaultTargetNew(color_ostream &out, unsigned fk, unsigned mk, unsigned fa, unsigned ma) { + set_config_val(CONFIG_DEFAULT_FK, fk); + set_config_val(CONFIG_DEFAULT_MK, mk); + set_config_val(CONFIG_DEFAULT_FA, fa); + set_config_val(CONFIG_DEFAULT_MA, ma); +} + +// set default target values for ALL races (update watchlist and set new default) +static void autobutcher_setDefaultTargetAll(color_ostream &out, unsigned fk, unsigned mk, unsigned fa, unsigned ma) { + for (auto w : watched_races) { + w.second->fk = fk; + w.second->mk = mk; + w.second->fa = fa; + w.second->ma = ma; + w.second->UpdateConfig(out); + } + autobutcher_setDefaultTargetNew(out, fk, mk, fa, ma); +} + +static void autobutcher_butcherRace(color_ostream &out, int id) { + for (auto unit : world->units.all) { + if(unit->race != id) + continue; + + if( !Units::isActive(unit) + || Units::isUndead(unit) + || Units::isMerchant(unit) // ignore merchants' draught animals + || Units::isForest(unit) // ignore merchants' caged animals + || !Units::isOwnCiv(unit) + || !Units::isTame(unit) + || Units::isWar(unit) // ignore war dogs etc + || Units::isHunter(unit) // ignore hunting dogs etc + // ignore creatures in built cages which are defined as rooms to leave zoos alone + // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) + || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() + || Units::isAvailableForAdoption(unit) + || unit->name.has_name + ) + continue; + + // found a bugged unit which had invalid coordinates but was not in a cage. + // marking it for slaughter didn't seem to have negative effects, but you never know... + if(!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + doMarkForSlaughter(unit); + } +} + +// remove butcher flag for all units of a given race +static void autobutcher_unbutcherRace(color_ostream &out, int id) { + for (auto unit : world->units.all) { + if(unit->race != id) + continue; + + if( !Units::isActive(unit) + || Units::isUndead(unit) + || !Units::isMarkedForSlaughter(unit) + ) + continue; + + if(!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + unit->flags2.bits.slaughter = 0; + } +} + +// push autobutcher settings on lua stack +static int autobutcher_getSettings(lua_State *L) { + lua_newtable(L); + int ctable = lua_gettop(L); + Lua::SetField(L, get_config_bool(CONFIG_IS_ENABLED), ctable, "enable_autobutcher"); + Lua::SetField(L, get_config_bool(CONFIG_AUTOWATCH), ctable, "enable_autowatch"); + Lua::SetField(L, get_config_val(CONFIG_DEFAULT_FK), ctable, "fk"); + Lua::SetField(L, get_config_val(CONFIG_DEFAULT_MK), ctable, "mk"); + Lua::SetField(L, get_config_val(CONFIG_DEFAULT_FA), ctable, "fa"); + Lua::SetField(L, get_config_val(CONFIG_DEFAULT_MA), ctable, "ma"); + Lua::SetField(L, get_config_val(CONFIG_CYCLE_TICKS), ctable, "sleep"); + return 1; +} + +// push the watchlist vector as nested table on the lua stack +static int autobutcher_getWatchList(lua_State *L) { + color_ostream *out = Lua::GetOutput(L); + if (!out) + out = &Core::getInstance().getConsole(); + + lua_newtable(L); + int entry_index = 0; + for (auto wr : watched_races) { + lua_newtable(L); + int ctable = lua_gettop(L); + + WatchedRace * w = wr.second; + int id = w->raceId; + Lua::SetField(L, id, ctable, "id"); + Lua::SetField(L, w->isWatched, ctable, "watched"); + Lua::SetField(L, Units::getRaceNamePluralById(id), ctable, "name"); + Lua::SetField(L, w->fk, ctable, "fk"); + Lua::SetField(L, w->mk, ctable, "mk"); + Lua::SetField(L, w->fa, ctable, "fa"); + Lua::SetField(L, w->ma, ctable, "ma"); + + WatchedRace *tally = checkRaceStocksTotal(*out, id); + Lua::SetField(L, tally->unit_ptr[fk_index].size(), ctable, "fk_total"); + Lua::SetField(L, tally->unit_ptr[mk_index].size(), ctable, "mk_total"); + Lua::SetField(L, tally->unit_ptr[fa_index].size(), ctable, "fa_total"); + Lua::SetField(L, tally->unit_ptr[ma_index].size(), ctable, "ma_total"); + delete tally; + + tally = checkRaceStocksProtected(*out, id); + Lua::SetField(L, tally->unit_ptr[fk_index].size(), ctable, "fk_protected"); + Lua::SetField(L, tally->unit_ptr[mk_index].size(), ctable, "mk_protected"); + Lua::SetField(L, tally->unit_ptr[fa_index].size(), ctable, "fa_protected"); + Lua::SetField(L, tally->unit_ptr[ma_index].size(), ctable, "ma_protected"); + delete tally; + + tally = checkRaceStocksButcherable(*out, id); + Lua::SetField(L, tally->unit_ptr[fk_index].size(), ctable, "fk_butcherable"); + Lua::SetField(L, tally->unit_ptr[mk_index].size(), ctable, "mk_butcherable"); + Lua::SetField(L, tally->unit_ptr[fa_index].size(), ctable, "fa_butcherable"); + Lua::SetField(L, tally->unit_ptr[ma_index].size(), ctable, "ma_butcherable"); + delete tally; + + tally = checkRaceStocksButcherFlag(*out, id); + Lua::SetField(L, tally->unit_ptr[fk_index].size(), ctable, "fk_butcherflag"); + Lua::SetField(L, tally->unit_ptr[mk_index].size(), ctable, "mk_butcherflag"); + Lua::SetField(L, tally->unit_ptr[fa_index].size(), ctable, "fa_butcherflag"); + Lua::SetField(L, tally->unit_ptr[ma_index].size(), ctable, "ma_butcherflag"); + delete tally; + + lua_rawseti(L, -2, ++entry_index); + } + + return 1; +} + +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(autowatch_isEnabled), + DFHACK_LUA_FUNCTION(autowatch_setEnabled), + DFHACK_LUA_FUNCTION(autobutcher_getSleep), + DFHACK_LUA_FUNCTION(autobutcher_setSleep), + DFHACK_LUA_FUNCTION(autobutcher_setWatchListRace), + DFHACK_LUA_FUNCTION(autobutcher_setDefaultTargetNew), + DFHACK_LUA_FUNCTION(autobutcher_setDefaultTargetAll), + DFHACK_LUA_FUNCTION(autobutcher_butcherRace), + DFHACK_LUA_FUNCTION(autobutcher_unbutcherRace), + DFHACK_LUA_FUNCTION(autobutcher_removeFromWatchList), + DFHACK_LUA_END +}; + +DFHACK_PLUGIN_LUA_COMMANDS { + DFHACK_LUA_COMMAND(autobutcher_getSettings), + DFHACK_LUA_COMMAND(autobutcher_getWatchList), + DFHACK_LUA_END +}; diff --git a/plugins/lua/autobutcher.lua b/plugins/lua/autobutcher.lua new file mode 100644 index 000000000..ab334896c --- /dev/null +++ b/plugins/lua/autobutcher.lua @@ -0,0 +1,89 @@ +local _ENV = mkmodule('plugins.autobutcher') + +--[[ + + Native functions: + + * autobutcher_isEnabled() + * autowatch_isEnabled() + +--]] + +local argparse = require('argparse') + +local function is_int(val) + return val and val == math.floor(val) +end + +local function is_positive_int(val) + return is_int(val) and val > 0 +end + +local function check_nonnegative_int(str) + local val = tonumber(str) + if is_positive_int(val) or val == 0 then return val end + qerror('expecting a non-negative integer, but got: '..tostring(str)) +end + +local function process_args(opts, args) + if args[1] == 'help' then + opts.help = true + return + end + + return argparse.processArgsGetopt(args, { + {'h', 'help', handler=function() opts.help = true end}, + }) +end + +local function process_races(opts, races, start_idx) + if #races < start_idx then + qerror('missing list of races (or "all" or "new" keywords)') + end + for i=start_idx,#races do + local race = races[i] + if race == 'all' then + opts.races_all = true + elseif race == 'new' then + opts.races_new = true + else + local str = df.new('string') + str.value = race + opts.races:insert('#', str) + end + end +end + +function parse_commandline(opts, ...) + local positionals = process_args(opts, {...}) + + local command = positionals[1] + if command then opts.command = command end + + if opts.help or not command or command == 'now' or + command == 'autowatch' or command == 'noautowatch' or + command == 'list' or command == 'list_export' then + return + end + + if command == 'watch' or command == 'unwatch' or command == 'forget' then + process_races(opts, positionals, 2) + elseif command == 'target' then + opts.fk = check_nonnegative_int(positionals[2]) + opts.mk = check_nonnegative_int(positionals[3]) + opts.fa = check_nonnegative_int(positionals[4]) + opts.ma = check_nonnegative_int(positionals[5]) + process_races(opts, positionals, 6) + elseif command == 'ticks' then + local ticks = tonumber(positionals[2]) + if not is_positive_int(arg) then + qerror('number of ticks must be a positive integer: ' .. ticks) + else + opts.ticks = ticks + end + else + qerror(('unrecognized command: "%s"'):format(command)) + end +end + +return _ENV diff --git a/plugins/lua/zone.lua b/plugins/lua/zone.lua deleted file mode 100644 index 75c9feec8..000000000 --- a/plugins/lua/zone.lua +++ /dev/null @@ -1,12 +0,0 @@ -local _ENV = mkmodule('plugins.zone') - ---[[ - - Native functions: - - * autobutcher_isEnabled() - * autowatch_isEnabled() - ---]] - -return _ENV diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 6353bfd35..60f1f2b61 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -14,107 +14,59 @@ // - mass-assign creatures using filters // - unassign single creature under cursor from current zone // - pitting own dwarves :) -// - full automation of handling mini-pastures over nestboxes: -// go through all pens, check if they are empty and placed over a nestbox -// find female tame egg-layer who is not assigned to another pen and assign it to nestbox pasture -// maybe check for minimum age? it's not that useful to fill nestboxes with freshly hatched birds -// state and sleep setting is saved the first time autonestbox is started (to avoid writing stuff if the plugin is never used) -// - full automation of marking live-stock for slaughtering -// races can be added to a watchlist and it can be set how many male/female kids/adults are left alive -// adding to the watchlist can be automated as well. -// config for autobutcher (state and sleep setting) is saved the first time autobutcher is started -// config for watchlist entries is saved when they are created or modified - -#include -#include -#include -#include -#include -#include -#include + #include -#include -#include #include -#include -#include -#include - -#include "Core.h" -#include "Console.h" -#include "Export.h" -#include "PluginManager.h" -#include "MiscUtils.h" -#include "uicommon.h" - -#include "LuaTools.h" -#include "DataFuncs.h" - -#include "modules/Units.h" -#include "modules/Maps.h" -#include "modules/Gui.h" -#include "modules/Materials.h" -#include "modules/MapCache.h" -#include "modules/Buildings.h" -#include "modules/World.h" -#include "modules/Screen.h" -#include "MiscUtils.h" -#include +#include +#include -#include "df/ui.h" -#include "df/world.h" -#include "df/world_raws.h" -#include "df/building_def.h" -#include "df/building_civzonest.h" #include "df/building_cagest.h" #include "df/building_chainst.h" -#include "df/building_nest_boxst.h" +#include "df/building_civzonest.h" #include "df/general_ref_building_civzone_assignedst.h" -#include -#include +#include "df/ui.h" +#include "df/unit.h" #include "df/unit_relationship_type.h" -#include "df/unit_soul.h" -#include "df/unit_wound.h" #include "df/viewscreen_dwarfmodest.h" +#include "df/world.h" + +#include "PluginManager.h" +#include "uicommon.h" +#include "VTableInterpose.h" + +#include "modules/Buildings.h" +#include "modules/Gui.h" +#include "modules/Units.h" #include "modules/Translation.h" +using std::function; using std::make_pair; +using std::ostringstream; +using std::pair; +using std::runtime_error; using std::string; using std::unordered_map; using std::unordered_set; using std::vector; using namespace DFHack; -using namespace DFHack::Units; -using namespace DFHack::Buildings; -using namespace df::enums; DFHACK_PLUGIN("zone"); DFHACK_PLUGIN_IS_ENABLED(is_enabled); -REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(cursor); -REQUIRE_GLOBAL(ui); -REQUIRE_GLOBAL(ui_build_selector); REQUIRE_GLOBAL(gps); -REQUIRE_GLOBAL(cur_year); -REQUIRE_GLOBAL(cur_year_tick); - +REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(ui_building_item_cursor); REQUIRE_GLOBAL(ui_building_assign_type); REQUIRE_GLOBAL(ui_building_assign_is_marked); REQUIRE_GLOBAL(ui_building_assign_units); REQUIRE_GLOBAL(ui_building_assign_items); REQUIRE_GLOBAL(ui_building_in_assign); - REQUIRE_GLOBAL(ui_menu_width); +REQUIRE_GLOBAL(world); -using namespace DFHack::Gui; - -command_result df_zone (color_ostream &out, vector & parameters); -command_result df_autobutcher(color_ostream &out, vector & parameters); - -DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable); +static command_result df_zone (color_ostream &out, vector & parameters); const string zone_help = "Allows easier management of pens/pastures, pits and cages.\n" @@ -200,134 +152,20 @@ const string zone_help_examples = " well, unless you have a mod with egg-laying male elves who give milk...\n"; -const string autobutcher_help = - "Assigns your lifestock for slaughter once it reaches a specific count. Requires\n" - "that you add the target race(s) to a watch list. Only tame units will be\n" - "processed. Named units will be completely ignored (you can give animals\n" - "nicknames with the tool 'rename unit' to protect them from getting slaughtered\n" - "automatically. Trained war or hunting pets will be ignored.\n" - "Once you have too much adults, the oldest will be butchered first.\n" - "Once you have too much kids, the youngest will be butchered first.\n" - "If you don't set a target count the following default will be used:\n" - "1 male kid, 5 female kids, 1 male adult, 5 female adults.\n" - "Options:\n" - " start - run every X frames (df simulation ticks)\n" - " default: X=6000 (~60 seconds at 100fps)\n" - " stop - stop running automatically\n" - " sleep X - change timer to sleep X frames between runs.\n" - " watch R - start watching race(s)\n" - " R = valid race RAW id (ALPACA, BIRD_TURKEY, etc)\n" - " or a list of RAW ids seperated by spaces\n" - " or the keyword 'all' which affects your whole current watchlist.\n" - " unwatch R - stop watching race(s)\n" - " the current target settings will be remembered\n" - " forget R - unwatch race(s) and forget target settings for it/them\n" - " autowatch - automatically adds all new races (animals you buy\n" - " from merchants, tame yourself or get from migrants)\n" - " to the watch list using default target count\n" - " noautowatch - stop auto-adding new races to the watch list\n" - " list - print status and watchlist\n" - " list_export - print status and watchlist in batchfile format\n" - " can be used to copy settings into another savegame\n" - " usage: 'dfhack-run autobutcher list_export > xyz.bat' \n" - " target fk mk fa ma R\n" - " - set target count for specified race:\n" - " fk = number of female kids\n" - " mk = number of male kids\n" - " fa = number of female adults\n" - " ma = number of female adults\n" - " R = 'all' sets count for all races on the current watchlist\n" - " including the races which are currenly set to 'unwatched'\n" - " and sets the new default for future watch commands\n" - " R = 'new' sets the new default for future watch commands\n" - " without changing your current watchlist\n" - " example - print some usage examples\n"; - -const string autobutcher_help_example = - "Examples:\n" - " autobutcher target 4 3 2 1 ALPACA BIRD_TURKEY\n" - " autobutcher watch ALPACA BIRD_TURKEY\n" - " autobutcher start\n" - " This means you want to have max 7 kids (4 female, 3 male) and max 3 adults\n" - " (2 female, 1 male) of the races alpaca and turkey. Once the kids grow up the\n" - " oldest adults will get slaughtered. Excess kids will get slaughtered starting\n" - " the the youngest to allow that the older ones grow into adults.\n" - " autobutcher target 0 0 0 0 new\n" - " autobutcher autowatch\n" - " autobutcher start\n" - " This tells autobutcher to automatically put all new races onto the watchlist\n" - " and mark unnamed tame units for slaughter as soon as they arrive in your\n" - " fortress. Settings already made for some races will be left untouched.\n"; - -command_result init_autobutcher(color_ostream &out); -command_result cleanup_autobutcher(color_ostream &out); -command_result start_autobutcher(color_ostream &out); - - -/////////////// -// stuff for autobutcher -// should be moved to own plugin once the tool methods it shares with the zone plugin are moved to Unit.h / Building.h - -command_result autoNestbox( color_ostream &out, bool verbose ); -command_result autoButcher( color_ostream &out, bool verbose ); - -static bool enable_autobutcher = false; -static bool enable_autobutcher_autowatch = false; -static size_t sleep_autobutcher = 6000; - -static PersistentDataItem config_autobutcher; - -DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) -{ - switch (event) - { - case DFHack::SC_MAP_LOADED: - // initialize from the world just loaded - init_autobutcher(out); - break; - case DFHack::SC_MAP_UNLOADED: - enable_autobutcher = false; - // cleanup - cleanup_autobutcher(out); - break; - default: - break; - } - return CR_OK; -} - -DFhackCExport command_result plugin_onupdate ( color_ostream &out ) -{ - static size_t ticks_autobutcher = 0; - - if(enable_autobutcher) - { - if(++ticks_autobutcher >= sleep_autobutcher) - { - ticks_autobutcher= 0; - autoButcher(out, false); - } - } - - return CR_OK; -} - - /////////////// // Various small tool functions // probably many of these should be moved to Unit.h and Building.h sometime later... -df::general_ref_building_civzone_assignedst * createCivzoneRef(); -bool unassignUnitFromBuilding(df::unit* unit); -command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building* building, bool verbose); -void unitInfo(color_ostream & out, df::unit* creature, bool verbose); -void zoneInfo(color_ostream & out, df::building* building, bool verbose); -void cageInfo(color_ostream & out, df::building* building, bool verbose); -void chainInfo(color_ostream & out, df::building* building, bool verbose); -bool isBuiltCageAtPos(df::coord pos); -bool isInBuiltCageRoom(df::unit*); - -void doMarkForSlaughter(df::unit* unit) +// static df::general_ref_building_civzone_assignedst * createCivzoneRef(); +// static bool unassignUnitFromBuilding(df::unit* unit); +// static command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building* building, bool verbose); +// static void unitInfo(color_ostream & out, df::unit* creature, bool verbose); +// static void zoneInfo(color_ostream & out, df::building* building, bool verbose); +// static void cageInfo(color_ostream & out, df::building* building, bool verbose); +// static void chainInfo(color_ostream & out, df::building* building, bool verbose); +static bool isInBuiltCageRoom(df::unit*); + +static void doMarkForSlaughter(df::unit* unit) { unit->flags2.bits.slaughter = 1; } @@ -335,7 +173,7 @@ void doMarkForSlaughter(df::unit* unit) // found a unit with weird position values on one of my maps (negative and in the thousands) // it didn't appear in the animal stocks screen, but looked completely fine otherwise (alive, tame, own, etc) // maybe a rare bug, but better avoid assigning such units to zones or slaughter etc. -bool hasValidMapPos(df::unit* unit) +static bool hasValidMapPos(df::unit* unit) { if( unit->pos.x >=0 && unit->pos.y >= 0 && unit->pos.z >= 0 && unit->pos.x < world->map.x_count @@ -347,7 +185,7 @@ bool hasValidMapPos(df::unit* unit) } // dump some unit info -void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) +static void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) { out.print("Unit %d ", unit->id); //race %d, civ %d,", creature->race, creature->civ_id if(unit->name.has_name) @@ -364,11 +202,11 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) out << ", "; } - if(isAdult(unit)) + if(Units::isAdult(unit)) out << "adult"; - else if(isBaby(unit)) + else if(Units::isBaby(unit)) out << "baby"; - else if(isChild(unit)) + else if(Units::isChild(unit)) out << "child"; out << " "; // sometimes empty even in vanilla RAWS, sometimes contains full race name (e.g. baby alpaca) @@ -376,7 +214,7 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) //out << getRaceBabyName(unit); //out << getRaceChildName(unit); - out << getRaceName(unit) << " ("; + out << Units::getRaceName(unit) << " ("; switch(unit->sex) { case 0: @@ -391,26 +229,26 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) break; } out << ")"; - out << ", age: " << getAge(unit, true); + out << ", age: " << Units::getAge(unit, true); - if(isTame(unit)) + if(Units::isTame(unit)) out << ", tame"; - if(isOwnCiv(unit)) + if(Units::isOwnCiv(unit)) out << ", owned"; - if(isWar(unit)) + if(Units::isWar(unit)) out << ", war"; - if(isHunter(unit)) + if(Units::isHunter(unit)) out << ", hunter"; - if(isMerchant(unit)) + if(Units::isMerchant(unit)) out << ", merchant"; - if(isForest(unit)) + if(Units::isForest(unit)) out << ", forest"; - if(isEggLayer(unit)) + if(Units::isEggLayer(unit)) out << ", egglayer"; - if(isGrazer(unit)) + if(Units::isGrazer(unit)) out << ", grazer"; - if(isMilkable(unit)) + if(Units::isMilkable(unit)) out << ", milkable"; if(unit->flags2.bits.slaughter) out << ", slaughter"; @@ -418,7 +256,7 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) if(verbose) { out << ". Pos: ("<pos.x << "/"<< unit->pos.y << "/" << unit->pos.z << ") " << endl; - out << "index in units vector: " << findIndexById(unit->id) << endl; + out << "index in units vector: " << Units::findIndexById(unit->id) << endl; } out << endl; @@ -461,17 +299,17 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) } } -bool isCage(df::building * building) +static bool isCage(df::building * building) { - return building && (building->getType() == building_type::Cage); + return building && (building->getType() == df::building_type::Cage); } -bool isChain(df::building * building) +static bool isChain(df::building * building) { - return building && (building->getType() == building_type::Chain); + return building && (building->getType() == df::building_type::Chain); } -df::general_ref_building_civzone_assignedst * createCivzoneRef() +static df::general_ref_building_civzone_assignedst * createCivzoneRef() { static bool vt_initialized = false; df::general_ref_building_civzone_assignedst* newref = NULL; @@ -510,14 +348,14 @@ df::general_ref_building_civzone_assignedst * createCivzoneRef() return newref; } -bool isInBuiltCage(df::unit* unit); +static bool isInBuiltCage(df::unit* unit); // check if assigned to pen, pit, (built) cage or chain // note: BUILDING_CAGED is not set for animals (maybe it's used for dwarves who get caged as sentence) // animals in cages (no matter if built or on stockpile) get the ref CONTAINED_IN_ITEM instead // removing them from cages on stockpiles is no problem even without clearing the ref // and usually it will be desired behavior to do so. -bool isAssigned(df::unit* unit) +static bool isAssigned(df::unit* unit) { bool assigned = false; for (size_t r=0; r < unit->general_refs.size(); r++) @@ -537,7 +375,7 @@ bool isAssigned(df::unit* unit) return assigned; } -bool isAssignedToZone(df::unit* unit) +static bool isAssignedToZone(df::unit* unit) { bool assigned = false; for (size_t r=0; r < unit->general_refs.size(); r++) @@ -553,26 +391,8 @@ bool isAssignedToZone(df::unit* unit) return assigned; } -// check if assigned to a chain or built cage -// (need to check if the ref needs to be removed, until then touching them is forbidden) -bool isChained(df::unit* unit) -{ - bool contained = false; - for (size_t r=0; r < unit->general_refs.size(); r++) - { - df::general_ref * ref = unit->general_refs[r]; - auto rtype = ref->getType(); - if(rtype == df::general_ref_type::BUILDING_CHAIN) - { - contained = true; - break; - } - } - return contained; -} - // check if contained in item (e.g. animals in cages) -bool isContainedInItem(df::unit* unit) +static bool isContainedInItem(df::unit* unit) { bool contained = false; for (size_t r=0; r < unit->general_refs.size(); r++) @@ -588,13 +408,13 @@ bool isContainedInItem(df::unit* unit) return contained; } -bool isInBuiltCage(df::unit* unit) +static bool isInBuiltCage(df::unit* unit) { bool caged = false; for (size_t b=0; b < world->buildings.all.size(); b++) { df::building* building = world->buildings.all[b]; - if( building->getType() == building_type::Cage) + if( building->getType() == df::building_type::Cage) { df::building_cagest* cage = (df::building_cagest*) building; for(size_t c=0; cassigned_units.size(); c++) @@ -613,7 +433,7 @@ bool isInBuiltCage(df::unit* unit) } // built cage defined as room (supposed to detect zoo cages) -bool isInBuiltCageRoom(df::unit* unit) +static bool isInBuiltCageRoom(df::unit* unit) { bool caged_room = false; for (size_t b=0; b < world->buildings.all.size(); b++) @@ -626,7 +446,7 @@ bool isInBuiltCageRoom(df::unit* unit) if(!building->is_room) continue; - if(building->getType() == building_type::Cage) + if(building->getType() == df::building_type::Cage) { df::building_cagest* cage = (df::building_cagest*) building; for(size_t c=0; cassigned_units.size(); c++) @@ -644,35 +464,13 @@ bool isInBuiltCageRoom(df::unit* unit) return caged_room; } -// check a map position for a built cage -// animals in cages are CONTAINED_IN_ITEM, no matter if they are on a stockpile or inside a built cage -// if they are on animal stockpiles they should count as unassigned to allow pasturing them -// if they are inside built cages they should be ignored in case the cage is a zoo or linked to a lever or whatever -bool isBuiltCageAtPos(df::coord pos) -{ - bool cage = false; - for (size_t b=0; b < world->buildings.all.size(); b++) - { - df::building* building = world->buildings.all[b]; - if( building->getType() == building_type::Cage - && building->x1 == pos.x - && building->y1 == pos.y - && building->z == pos.z ) - { - cage = true; - break; - } - } - return cage; -} - -df::building * getBuiltCageAtPos(df::coord pos) +static df::building * getBuiltCageAtPos(df::coord pos) { df::building* cage = NULL; for (size_t b=0; b < world->buildings.all.size(); b++) { df::building* building = world->buildings.all[b]; - if( building->getType() == building_type::Cage + if( building->getType() == df::building_type::Cage && building->x1 == pos.x && building->y1 == pos.y && building->z == pos.z ) @@ -688,120 +486,12 @@ df::building * getBuiltCageAtPos(df::coord pos) return cage; } -bool isNestboxAtPos(int32_t x, int32_t y, int32_t z) -{ - bool found = false; - for (size_t b=0; b < world->buildings.all.size(); b++) - { - df::building* building = world->buildings.all[b]; - if( building->getType() == building_type::NestBox - && building->x1 == x - && building->y1 == y - && building->z == z ) - { - found = true; - break; - } - } - return found; -} - -bool isFreeNestboxAtPos(int32_t x, int32_t y, int32_t z) -{ - bool found = false; - for (size_t b=0; b < world->buildings.all.size(); b++) - { - df::building* building = world->buildings.all[b]; - if( building->getType() == building_type::NestBox - && building->x1 == x - && building->y1 == y - && building->z == z ) - { - df::building_nest_boxst* nestbox = (df::building_nest_boxst*) building; - if(nestbox->claimed_by == -1 && nestbox->contained_items.size() == 1) - { - found = true; - break; - } - } - } - return found; -} - -bool isEmptyPasture(df::building* building) -{ - if(!isPenPasture(building)) - return false; - df::building_civzonest * civ = (df::building_civzonest *) building; - if(civ->assigned_units.size() == 0) - return true; - else - return false; -} - -df::building* findFreeNestboxZone() -{ - df::building * free_building = NULL; - for (size_t b=0; b < world->buildings.all.size(); b++) - { - df::building* building = world->buildings.all[b]; - if( isEmptyPasture(building) && - isActive(building) && - isFreeNestboxAtPos(building->x1, building->y1, building->z)) - { - free_building = building; - break; - } - } - return free_building; -} - -bool isFreeEgglayer(df::unit * unit) -{ - return isActive(unit) && !isUndead(unit) - && isFemale(unit) - && isTame(unit) - && isOwnCiv(unit) - && isEggLayer(unit) - && !isAssigned(unit) - && !isGrazer(unit) // exclude grazing birds because they're messy - && !isMerchant(unit) // don't steal merchant mounts - && !isForest(unit); // don't steal birds from traders, they hate that -} - -df::unit * findFreeEgglayer() -{ - df::unit* free_unit = NULL; - for (size_t i=0; i < world->units.all.size(); i++) - { - df::unit* unit = world->units.all[i]; - if(isFreeEgglayer(unit)) - { - free_unit = unit; - break; - } - } - return free_unit; -} - -size_t countFreeEgglayers() -{ - size_t count = 0; - for (size_t i=0; i < world->units.all.size(); i++) - { - df::unit* unit = world->units.all[i]; - if(isFreeEgglayer(unit)) - count ++; - } - return count; -} - // check if unit is already assigned to a zone, remove that ref from unit and old zone // check if unit is already assigned to a cage, remove that ref from the cage // returns false if no cage or pasture information was found // helps as workaround for http://www.bay12games.com/dwarves/mantisbt/view.php?id=4475 by the way // (pastured animals assigned to chains will get hauled back and forth because the pasture ref is not deleted) -bool unassignUnitFromBuilding(df::unit* unit) +static bool unassignUnitFromBuilding(df::unit* unit) { bool success = false; for (std::size_t idx = 0; idx < unit->general_refs.size(); idx++) @@ -885,10 +575,10 @@ bool unassignUnitFromBuilding(df::unit* unit) } // assign to pen or pit -command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building* building, bool verbose = false) +static command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building* building, bool verbose = false) { // building must be a pen/pasture or pit - if(!isPenPasture(building) && !isPitPond(building)) + if(!Buildings::isPenPasture(building) && !Buildings::isPitPond(building)) { out << "Invalid building type. This is not a pen/pasture or pit/pond." << endl; return CR_WRONG_USAGE; @@ -926,18 +616,18 @@ command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building civz->assigned_units.push_back(unit->id); out << "Unit " << unit->id - << "(" << getRaceName(unit) << ")" + << "(" << Units::getRaceName(unit) << ")" << " assigned to zone " << building->id; - if(isPitPond(building)) + if(Buildings::isPitPond(building)) out << " (pit/pond)."; - if(isPenPasture(building)) + if(Buildings::isPenPasture(building)) out << " (pen/pasture)."; out << endl; return CR_OK; } -command_result assignUnitToCage(color_ostream& out, df::unit* unit, df::building* building, bool verbose) +static command_result assignUnitToCage(color_ostream& out, df::unit* unit, df::building* building, bool verbose) { // building must be a pen/pasture or pit if(!isCage(building)) @@ -967,24 +657,24 @@ command_result assignUnitToCage(color_ostream& out, df::unit* unit, df::building civz->assigned_units.push_back(unit->id); out << "Unit " << unit->id - << "(" << getRaceName(unit) << ")" + << "(" << Units::getRaceName(unit) << ")" << " assigned to cage " << building->id; out << endl; return CR_OK; } -command_result assignUnitToChain(color_ostream& out, df::unit* unit, df::building* building, bool verbose) +static command_result assignUnitToChain(color_ostream& out, df::unit* unit, df::building* building, bool verbose) { out << "sorry. assigning to chains is not possible yet." << endl; return CR_WRONG_USAGE; } -command_result assignUnitToBuilding(color_ostream& out, df::unit* unit, df::building* building, bool verbose) +static command_result assignUnitToBuilding(color_ostream& out, df::unit* unit, df::building* building, bool verbose) { command_result result = CR_WRONG_USAGE; - if(isActivityZone(building)) + if(Buildings::isActivityZone(building)) result = assignUnitToZone(out, unit, building, verbose); else if(isCage(building)) result = assignUnitToCage(out, unit, building, verbose); @@ -996,9 +686,9 @@ command_result assignUnitToBuilding(color_ostream& out, df::unit* unit, df::buil return result; } -command_result assignUnitsToCagezone(color_ostream& out, vector units, df::building* building, bool verbose) +static command_result assignUnitsToCagezone(color_ostream& out, vector units, df::building* building, bool verbose) { - if(!isPenPasture(building)) + if(!Buildings::isPenPasture(building)) { out << "A cage zone needs to be a pen/pasture containing at least one cage!" << endl; return CR_WRONG_USAGE; @@ -1055,10 +745,10 @@ command_result assignUnitsToCagezone(color_ostream& out, vector units return CR_OK; } -command_result nickUnitsInZone(color_ostream& out, df::building* building, string nick) +static command_result nickUnitsInZone(color_ostream& out, df::building* building, string nick) { // building must be a pen/pasture or pit - if(!isPenPasture(building) && !isPitPond(building)) + if(!Buildings::isPenPasture(building) && !Buildings::isPitPond(building)) { out << "Invalid building type. This is not a pen/pasture or pit/pond." << endl; return CR_WRONG_USAGE; @@ -1075,7 +765,7 @@ command_result nickUnitsInZone(color_ostream& out, df::building* building, strin return CR_OK; } -command_result nickUnitsInCage(color_ostream& out, df::building* building, string nick) +static command_result nickUnitsInCage(color_ostream& out, df::building* building, string nick) { // building must be a pen/pasture or pit if(!isCage(building)) @@ -1095,7 +785,7 @@ command_result nickUnitsInCage(color_ostream& out, df::building* building, strin return CR_OK; } -command_result nickUnitsInChain(color_ostream& out, df::building* building, string nick) +static command_result nickUnitsInChain(color_ostream& out, df::building* building, string nick) { out << "sorry. nicknaming chained units is not possible yet." << endl; return CR_WRONG_USAGE; @@ -1103,11 +793,11 @@ command_result nickUnitsInChain(color_ostream& out, df::building* building, stri // give all units inside a pasture or cage the same nickname // (usage example: protect them from being autobutchered) -command_result nickUnitsInBuilding(color_ostream& out, df::building* building, string nick) +static command_result nickUnitsInBuilding(color_ostream& out, df::building* building, string nick) { command_result result = CR_WRONG_USAGE; - if(isActivityZone(building)) + if(Buildings::isActivityZone(building)) result = nickUnitsInZone(out, building, nick); else if(isCage(building)) result = nickUnitsInCage(out, building, nick); @@ -1120,9 +810,9 @@ command_result nickUnitsInBuilding(color_ostream& out, df::building* building, s } // dump some zone info -void zoneInfo(color_ostream & out, df::building* building, bool verbose) +static void zoneInfo(color_ostream & out, df::building* building, bool verbose) { - if(!isActivityZone(building)) + if(!Buildings::isActivityZone(building)) return; string name; @@ -1138,7 +828,7 @@ void zoneInfo(color_ostream & out, df::building* building, bool verbose) out.print("\n"); df::building_civzonest * civ = (df::building_civzonest *) building; - if(isActive(civ)) + if(Buildings::isActive(civ)) out << "active"; else out << "not active"; @@ -1180,7 +870,7 @@ void zoneInfo(color_ostream & out, df::building* building, bool verbose) } // dump some cage info -void cageInfo(color_ostream & out, df::building* building, bool verbose) +static void cageInfo(color_ostream & out, df::building* building, bool verbose) { if(!isCage(building)) return; @@ -1220,7 +910,7 @@ void cageInfo(color_ostream & out, df::building* building, bool verbose) } // dump some chain/restraint info -void chainInfo(color_ostream & out, df::building* building, bool list_refs = false) +static void chainInfo(color_ostream & out, df::building* building, bool list_refs = false) { if(!isChain(building)) return; @@ -1247,7 +937,7 @@ void chainInfo(color_ostream & out, df::building* building, bool list_refs = fal } } -df::building* getAssignableBuildingAtCursor(color_ostream& out) +static df::building* getAssignableBuildingAtCursor(color_ostream& out) { // set building at cursor position to be new target building if (cursor->x == -30000) @@ -1267,7 +957,7 @@ df::building* getAssignableBuildingAtCursor(color_ostream& out) } else { - auto zone_at_tile = findPenPitAt(Gui::getCursorPos()); + auto zone_at_tile = Buildings::findPenPitAt(Gui::getCursorPos()); if(!zone_at_tile) { out << "No pen/pasture, pit, or cage under cursor!" << endl; @@ -1284,38 +974,38 @@ df::building* getAssignableBuildingAtCursor(color_ostream& out) // ZONE FILTERS (as in, filters used by 'zone') // Maps parameter names to filters. -unordered_map> zone_filters; +static unordered_map> zone_filters; static struct zone_filters_init { zone_filters_init() { zone_filters["caged"] = isContainedInItem; - zone_filters["egglayer"] = isEggLayer; - zone_filters["female"] = isFemale; - zone_filters["grazer"] = isGrazer; - zone_filters["hunting"] = isHunter; - zone_filters["male"] = isMale; - zone_filters["milkable"] = isMilkable; - zone_filters["naked"] = isNaked; - zone_filters["own"] = isOwnCiv; - zone_filters["tamable"] = isTamable; - zone_filters["tame"] = isTame; + zone_filters["egglayer"] = Units::isEggLayer; + zone_filters["female"] = Units::isFemale; + zone_filters["grazer"] = Units::isGrazer; + zone_filters["hunting"] = Units::isHunter; + zone_filters["male"] = Units::isMale; + zone_filters["milkable"] = Units::isMilkable; + zone_filters["naked"] = Units::isNaked; + zone_filters["own"] = Units::isOwnCiv; + zone_filters["tamable"] = Units::isTamable; + zone_filters["tame"] = Units::isTame; zone_filters["trainablewar"] = [](df::unit *unit) -> bool { - return !isWar(unit) && !isHunter(unit) && isTrainableWar(unit); + return !Units::isWar(unit) && !Units::isHunter(unit) && Units::isTrainableWar(unit); }; zone_filters["trainablehunt"] = [](df::unit *unit) -> bool { - return !isWar(unit) && !isHunter(unit) && isTrainableHunting(unit); + return !Units::isWar(unit) && !Units::isHunter(unit) && Units::isTrainableHunting(unit); }; - zone_filters["trained"] = isTrained; + zone_filters["trained"] = Units::isTrained; // backwards compatibility zone_filters["unassigned"] = [](df::unit *unit) -> bool { return !isAssigned(unit); }; - zone_filters["war"] = isWar; + zone_filters["war"] = Units::isWar; }} zone_filters_init_; // Extra annotations / descriptions for parameter names. -unordered_map zone_filter_notes; +static unordered_map zone_filter_notes; static struct zone_filter_notes_init { zone_filter_notes_init() { zone_filter_notes["caged"] = "caged (ignores built cages)"; zone_filter_notes["hunting"] = "trained hunting creature"; @@ -1326,7 +1016,7 @@ static struct zone_filter_notes_init { zone_filter_notes_init() { zone_filter_notes["war"] = "trained war creature"; }} zone_filter_notes_init_; -pair> createRaceFilter(vector &filter_args) +static pair> createRaceFilter(vector &filter_args) { // guaranteed to exist. string race = filter_args[0]; @@ -1334,12 +1024,12 @@ pair> createRaceFilter(vector &filter_ return make_pair( "race of " + race, [race](df::unit *unit) -> bool { - return getRaceName(unit) == race; + return Units::getRaceName(unit) == race; } ); } -pair> createAgeFilter(vector &filter_args) +static pair> createAgeFilter(vector &filter_args) { int target_age; stringstream ss(filter_args[0]); @@ -1360,12 +1050,12 @@ pair> createAgeFilter(vector &filter_a return make_pair( "age of exactly " + int_to_string(target_age), [target_age](df::unit *unit) -> bool { - return getAge(unit, true) == target_age; + return Units::getAge(unit, true) == target_age; } ); } -pair> createMinAgeFilter(vector &filter_args) +static pair> createMinAgeFilter(vector &filter_args) { double min_age; stringstream ss(filter_args[0]); @@ -1386,12 +1076,12 @@ pair> createMinAgeFilter(vector &filte return make_pair( "minimum age of " + int_to_string(min_age), [min_age](df::unit *unit) -> bool { - return getAge(unit, true) >= min_age; + return Units::getAge(unit, true) >= min_age; } ); } -pair> createMaxAgeFilter(vector &filter_args) +static pair> createMaxAgeFilter(vector &filter_args) { double max_age; stringstream ss(filter_args[0]); @@ -1412,7 +1102,7 @@ pair> createMaxAgeFilter(vector &filte return make_pair( "maximum age of " + int_to_string(max_age), [max_age](df::unit *unit) -> bool { - return getAge(unit, true) <= max_age; + return Units::getAge(unit, true) <= max_age; } ); } @@ -1428,7 +1118,7 @@ pair> createMaxAgeFilter(vector &filte // Constructor functions are permitted to throw strings, which will be caught and printed. // Result filter functions are not permitted to throw std::exceptions. // Result filter functions should not store references -unordered_map>(vector&)>>> zone_param_filters; static struct zone_param_filters_init { zone_param_filters_init() { zone_param_filters["race"] = make_pair(1, createRaceFilter); @@ -1437,7 +1127,7 @@ static struct zone_param_filters_init { zone_param_filters_init() { zone_param_filters["maxage"] = make_pair(1, createMaxAgeFilter); }} zone_param_filters_init_; -command_result df_zone (color_ostream &out, vector & parameters) +static command_result df_zone (color_ostream &out, vector & parameters) { CoreSuspender suspend; @@ -1526,7 +1216,7 @@ command_result df_zone (color_ostream &out, vector & parameters) else if(p0 == "unassign") { // if there's a unit selected... - df::unit *unit = getSelectedUnit(out, true); + df::unit *unit = Gui::getSelectedUnit(out, true); if (unit) { // remove assignment reference from unit and old zone if(unassignUnitFromBuilding(unit)) @@ -1865,7 +1555,7 @@ command_result df_zone (color_ostream &out, vector & parameters) active_filters.push_back([](df::unit *unit) { - return !isMerchant(unit) && !isForest(unit); + return !Units::isMerchant(unit) && !Units::isForest(unit); } ); } else { @@ -1875,7 +1565,7 @@ command_result df_zone (color_ostream &out, vector & parameters) active_filters.push_back([](df::unit *unit) { - return isMerchant(unit) || isForest(unit); + return Units::isMerchant(unit) || Units::isForest(unit); } ); } @@ -1926,7 +1616,7 @@ command_result df_zone (color_ostream &out, vector & parameters) { // filter for units in the building unordered_set assigned_unit_ids; - if(isActivityZone(target_building)) + if(Buildings::isActivityZone(target_building)) { df::building_civzonest *civz = (df::building_civzonest *) target_building; auto &assigned_units_vec = civz->assigned_units; @@ -1984,7 +1674,7 @@ command_result df_zone (color_ostream &out, vector & parameters) if(!race_filter_set && (building_assign || cagezone_assign || unit_slaughter)) { - string own_race_name = getRaceNameById(ui->race_id); + string own_race_name = Units::getRaceNameById(ui->race_id); out.color(COLOR_BROWN); out << "Default filter for " << parameters[0] << ": 'not (race " << own_race_name << " or own civilization)'; use 'race " @@ -1994,7 +1684,7 @@ command_result df_zone (color_ostream &out, vector & parameters) active_filters.push_back([](df::unit *unit) { - return !isOwnRace(unit) || !isOwnCiv(unit); + return !Units::isOwnRace(unit) || !Units::isOwnCiv(unit); } ); } @@ -2022,7 +1712,7 @@ command_result df_zone (color_ostream &out, vector & parameters) active_filters.push_back([](df::unit *unit) { - return !isMerchant(unit) && !isForest(unit); + return !Units::isMerchant(unit) && !Units::isForest(unit); } ); } @@ -2058,7 +1748,7 @@ command_result df_zone (color_ostream &out, vector & parameters) df::unit *unit = *unit_it; // ignore inactive and undead units - if (!isActive(unit) || isUndead(unit)) { + if (!Units::isActive(unit) || Units::isUndead(unit)) { continue; } @@ -2130,10 +1820,10 @@ command_result df_zone (color_ostream &out, vector & parameters) if (removed) { out << "Unit " << unit->id - << "(" << getRaceName(unit) << ")" + << "(" << Units::getRaceName(unit) << ")" << " unassigned from"; - if (isActivityZone(target_building)) + if (Buildings::isActivityZone(target_building)) { out << " zone "; } @@ -2179,7 +1869,7 @@ command_result df_zone (color_ostream &out, vector & parameters) else { // must have unit selected - df::unit *unit = getSelectedUnit(out, true); + df::unit *unit = Gui::getSelectedUnit(out, true); if (!unit) { out.color(COLOR_RED); out << "Error: no unit selected!" << endl; @@ -2207,1361 +1897,72 @@ command_result df_zone (color_ostream &out, vector & parameters) return CR_OK; } -//////////////////// -// autobutcher stuff - -// getUnitAge() returns 0 if born in current year, therefore the look at birth_time in that case -// (assuming that the value from there indicates in which tick of the current year the unit was born) -bool compareUnitAgesYounger(df::unit* i, df::unit* j) -{ - int32_t age_i = (int32_t) getAge(i, true); - int32_t age_j = (int32_t) getAge(j, true); - if(age_i == 0 && age_j == 0) - { - age_i = i->birth_time; - age_j = j->birth_time; - } - return (age_i < age_j); -} -bool compareUnitAgesOlder(df::unit* i, df::unit* j) -{ - int32_t age_i = (int32_t) getAge(i, true); - int32_t age_j = (int32_t) getAge(j, true); - if(age_i == 0 && age_j == 0) - { - age_i = i->birth_time; - age_j = j->birth_time; - } - return (age_i > age_j); -} - - - -//enum WatchedRaceSubtypes -//{ -// femaleKid=0, -// maleKid, -// femaleAdult, -// maleAdult -//}; - -enum unit_ptr_index -{ - fk_index = 0, - mk_index = 1, - fa_index = 2, - ma_index = 3 -}; +//START zone filters -struct WatchedRace +class zone_filter { public: - PersistentDataItem rconfig; - - bool isWatched; // if true, autobutcher will process this race - int raceId; - - // target amounts - unsigned fk; // max female kids - unsigned mk; // max male kids - unsigned fa; // max female adults - unsigned ma; // max male adults - - // amounts of protected (not butcherable) units - unsigned fk_prot; - unsigned fa_prot; - unsigned mk_prot; - unsigned ma_prot; - - // butcherable units - vector unit_ptr[4]; - - // priority butcherable units - vector prot_ptr[4]; - - WatchedRace(bool watch, int id, unsigned _fk, unsigned _mk, unsigned _fa, unsigned _ma) - { - isWatched = watch; - raceId = id; - fk = _fk; - mk = _mk; - fa = _fa; - ma = _ma; - fk_prot = fa_prot = mk_prot = ma_prot = 0; - } - - ~WatchedRace() + zone_filter() { - ClearUnits(); + initialized = false; } - void UpdateConfig(color_ostream & out) + void initialize(const df::ui_sidebar_mode &mode) { - if(!rconfig.isValid()) - { - string keyname = "autobutcher/watchlist/" + getRaceNameById(raceId); - rconfig = World::GetPersistentData(keyname, NULL); - } - if(rconfig.isValid()) - { - rconfig.ival(0) = raceId; - rconfig.ival(1) = isWatched; - rconfig.ival(2) = fk; - rconfig.ival(3) = mk; - rconfig.ival(4) = fa; - rconfig.ival(5) = ma; - } - else + if (!initialized) { - // this should never happen - string keyname = "autobutcher/watchlist/" + getRaceNameById(raceId); - out << "Something failed, could not find/create config key " << keyname << "!" << endl; - } - } + this->mode = mode; + saved_ui_building_assign_type.clear(); + saved_ui_building_assign_units.clear(); + saved_ui_building_assign_items.clear(); + saved_ui_building_assign_is_marked.clear(); + saved_indexes.clear(); - void RemoveConfig(color_ostream & out) - { - if(!rconfig.isValid()) - return; - World::DeletePersistentData(rconfig); - } + for (size_t i = 0; i < ui_building_assign_units->size(); i++) + { + saved_ui_building_assign_type.push_back(ui_building_assign_type->at(i)); + saved_ui_building_assign_units.push_back(ui_building_assign_units->at(i)); + saved_ui_building_assign_items.push_back(ui_building_assign_items->at(i)); + saved_ui_building_assign_is_marked.push_back(ui_building_assign_is_marked->at(i)); + } - void SortUnitsByAge() - { - sort(unit_ptr[fk_index].begin(), unit_ptr[fk_index].end(), compareUnitAgesOlder); - sort(unit_ptr[mk_index].begin(), unit_ptr[mk_index].end(), compareUnitAgesOlder); - sort(unit_ptr[fa_index].begin(), unit_ptr[fa_index].end(), compareUnitAgesYounger); - sort(unit_ptr[ma_index].begin(), unit_ptr[ma_index].end(), compareUnitAgesYounger); - sort(prot_ptr[fk_index].begin(), prot_ptr[fk_index].end(), compareUnitAgesOlder); - sort(prot_ptr[mk_index].begin(), prot_ptr[mk_index].end(), compareUnitAgesOlder); - sort(prot_ptr[fa_index].begin(), prot_ptr[fa_index].end(), compareUnitAgesYounger); - sort(prot_ptr[ma_index].begin(), prot_ptr[ma_index].end(), compareUnitAgesYounger); - } + search_string.clear(); + show_non_grazers = show_pastured = show_noncaged = show_male = show_female = show_other_zones = true; + entry_mode = false; - void PushUnit(df::unit * unit) - { - if(isFemale(unit)) - { - if(isBaby(unit) || isChild(unit)) - unit_ptr[fk_index].push_back(unit); - else - unit_ptr[fa_index].push_back(unit); - } - else //treat sex n/a like it was male - { - if(isBaby(unit) || isChild(unit)) - unit_ptr[mk_index].push_back(unit); - else - unit_ptr[ma_index].push_back(unit); + initialized = true; } } - void PushPriorityUnit(df::unit * unit) + void deinitialize() { - if(isFemale(unit)) - { - if(isBaby(unit) || isChild(unit)) - prot_ptr[fk_index].push_back(unit); - else - prot_ptr[fa_index].push_back(unit); - } - else - { - if(isBaby(unit) || isChild(unit)) - prot_ptr[mk_index].push_back(unit); - else - prot_ptr[ma_index].push_back(unit); - } + initialized = false; } - void PushProtectedUnit(df::unit * unit) + void apply_filters() { - if(isFemale(unit)) - { - if(isBaby(unit) || isChild(unit)) - fk_prot++; - else - fa_prot++; - } - else //treat sex n/a like it was male + if (saved_indexes.size() > 0) { - if(isBaby(unit) || isChild(unit)) - mk_prot++; - else - ma_prot++; - } - } + bool list_has_been_sorted = (ui_building_assign_units->size() == reference_list.size() + && *ui_building_assign_units != reference_list); - void ClearUnits() - { - fk_prot = fa_prot = mk_prot = ma_prot = 0; - for (size_t i = 0; i < 4; i++) - { - unit_ptr[i].clear(); - prot_ptr[i].clear(); - } - } + for (size_t i = 0; i < saved_indexes.size(); i++) + { + int adjusted_item_index = i; + if (list_has_been_sorted) + { + for (size_t j = 0; j < ui_building_assign_units->size(); j++) + { + if (ui_building_assign_units->at(j) == reference_list[i]) + { + adjusted_item_index = j; + break; + } + } + } - int ProcessUnits(vector& unit_ptr, vector& unit_pri_ptr, unsigned prot, unsigned goal) - { - int subcount = 0; - while(unit_pri_ptr.size() && (unit_ptr.size() + unit_pri_ptr.size() + prot > goal) ) - { - df::unit* unit = unit_pri_ptr.back(); - doMarkForSlaughter(unit); - unit_pri_ptr.pop_back(); - subcount++; - } - while(unit_ptr.size() && (unit_ptr.size() + prot > goal) ) - { - df::unit* unit = unit_ptr.back(); - doMarkForSlaughter(unit); - unit_ptr.pop_back(); - subcount++; - } - return subcount; - } - - int ProcessUnits() - { - SortUnitsByAge(); - int slaughter_count = 0; - slaughter_count += ProcessUnits(unit_ptr[fk_index], prot_ptr[fk_index], fk_prot, fk); - slaughter_count += ProcessUnits(unit_ptr[mk_index], prot_ptr[mk_index], mk_prot, mk); - slaughter_count += ProcessUnits(unit_ptr[fa_index], prot_ptr[fa_index], fa_prot, fa); - slaughter_count += ProcessUnits(unit_ptr[ma_index], prot_ptr[ma_index], ma_prot, ma); - ClearUnits(); - return slaughter_count; - } -}; -// vector of races handled by autobutcher -// the name is a bit misleading since entries can be set to 'unwatched' -// to ignore them for a while but still keep the target count settings -std::vector watched_races; - -// helper for sorting the watchlist alphabetically -bool compareRaceNames(WatchedRace* i, WatchedRace* j) -{ - string name_i = getRaceNamePluralById(i->raceId); - string name_j = getRaceNamePluralById(j->raceId); - - return (name_i < name_j); -} - -static void autobutcher_sortWatchList(color_ostream &out); - -// default target values for autobutcher -static unsigned default_fk = 5; -static unsigned default_mk = 1; -static unsigned default_fa = 5; -static unsigned default_ma = 1; - -command_result df_autobutcher(color_ostream &out, vector & parameters) -{ - CoreSuspender suspend; - - bool verbose = false; - bool watch_race = false; - bool unwatch_race = false; - bool forget_race = false; - bool list_watched = false; - bool list_export = false; - bool change_target = false; - vector target_racenames; - vector target_raceids; - - unsigned target_fk = default_fk; - unsigned target_mk = default_mk; - unsigned target_fa = default_fa; - unsigned target_ma = default_ma; - - if(!parameters.size()) - { - out << "You must specify a command!" << endl; - out << autobutcher_help << endl; - return CR_OK; - } - - // parse main command - string & p = parameters[0]; - if (p == "help" || p == "?") - { - out << autobutcher_help << endl; - return CR_OK; - } - if (p == "example") - { - out << autobutcher_help_example << endl; - return CR_OK; - } - else if (p == "start") - { - plugin_enable(out, true); - enable_autobutcher = true; - start_autobutcher(out); - return autoButcher(out, verbose); - } - else if (p == "stop") - { - enable_autobutcher = false; - if(config_autobutcher.isValid()) - config_autobutcher.ival(0) = enable_autobutcher; - out << "Autobutcher stopped." << endl; - return CR_OK; - } - else if(p == "sleep") - { - parameters.erase(parameters.begin()); - if(!parameters.size()) - { - out.printerr("No duration specified!\n"); - return CR_WRONG_USAGE; - } - else - { - size_t ticks = 0; - stringstream ss(parameters.back()); - ss >> ticks; - if(ticks <= 0) - { - out.printerr("Invalid duration specified (must be > 0)!\n"); - return CR_WRONG_USAGE; - } - sleep_autobutcher = ticks; - if(config_autobutcher.isValid()) - config_autobutcher.ival(1) = sleep_autobutcher; - out << "New sleep timer for autobutcher: " << ticks << " ticks." << endl; - return CR_OK; - } - } - else if(p == "watch") - { - parameters.erase(parameters.begin()); - watch_race = true; - out << "Start watching race(s): "; // << endl; - } - else if(p == "unwatch") - { - parameters.erase(parameters.begin()); - unwatch_race = true; - out << "Stop watching race(s): "; // << endl; - } - else if(p == "forget") - { - parameters.erase(parameters.begin()); - forget_race = true; - out << "Removing race(s) from watchlist: "; // << endl; - } - else if(p == "target") - { - // needs at least 5 more parameters: - // fk mk fa ma R (can have more than 1 R) - if(parameters.size() < 6) - { - out.printerr("Not enough parameters!\n"); - return CR_WRONG_USAGE; - } - else - { - stringstream fk(parameters[1]); - stringstream mk(parameters[2]); - stringstream fa(parameters[3]); - stringstream ma(parameters[4]); - fk >> target_fk; - mk >> target_mk; - fa >> target_fa; - ma >> target_ma; - parameters.erase(parameters.begin(), parameters.begin()+5); - change_target = true; - out << "Setting new target count for race(s): "; // << endl; - } - } - else if(p == "autowatch") - { - out << "Auto-adding to watchlist started." << endl; - enable_autobutcher_autowatch = true; - if(config_autobutcher.isValid()) - config_autobutcher.ival(2) = enable_autobutcher_autowatch; - return CR_OK; - } - else if(p == "noautowatch") - { - out << "Auto-adding to watchlist stopped." << endl; - enable_autobutcher_autowatch = false; - if(config_autobutcher.isValid()) - config_autobutcher.ival(2) = enable_autobutcher_autowatch; - return CR_OK; - } - else if(p == "list") - { - list_watched = true; - } - else if(p == "list_export") - { - list_export = true; - } - else - { - out << "Unknown command: " << p << endl; - return CR_WRONG_USAGE; - } - - if(list_watched) - { - out << "Autobutcher status: "; - - if(enable_autobutcher) - out << "enabled,"; - else - out << "not enabled,"; - - if (enable_autobutcher_autowatch) - out << " autowatch,"; - else - out << " noautowatch,"; - - out << " sleep: " << sleep_autobutcher << endl; - - out << "Default setting for new races:" - << " fk=" << default_fk - << " mk=" << default_mk - << " fa=" << default_fa - << " ma=" << default_ma - << endl; - - if(!watched_races.size()) - { - out << "The autobutcher race list is empty." << endl; - return CR_OK; - } - - out << "Races on autobutcher list: " << endl; - for(size_t i=0; iraws.creatures.all[w->raceId]; - string name = raw->creature_id; - if(w->isWatched) - out << "watched: "; - else - out << "not watched: "; - out << name - << " fk=" << w->fk - << " mk=" << w->mk - << " fa=" << w->fa - << " ma=" << w->ma - << endl; - } - return CR_OK; - } - - if(list_export) - { - // force creation of config - out << "autobutcher start" << endl; - - if(!enable_autobutcher) - out << "autobutcher stop" << endl; - - if (enable_autobutcher_autowatch) - out << "autobutcher autowatch" << endl; - - out << "autobutcher sleep " << sleep_autobutcher << endl; - out << "autobutcher target" - << " " << default_fk - << " " << default_mk - << " " << default_fa - << " " << default_ma - << " new" << endl; - - for(size_t i=0; iraws.creatures.all[w->raceId]; - string name = raw->creature_id; - - out << "autobutcher target" - << " " << w->fk - << " " << w->mk - << " " << w->fa - << " " << w->ma - << " " << name << endl; - - if(w->isWatched) - out << "autobutcher watch " << name << endl; - } - return CR_OK; - } - - // parse rest of parameters for commands followed by a list of races - if( watch_race - || unwatch_race - || forget_race - || change_target ) - { - if(!parameters.size()) - { - out.printerr("No race(s) specified!\n"); - return CR_WRONG_USAGE; - } - while(parameters.size()) - { - string tr = parameters.back(); - target_racenames.push_back(tr); - parameters.pop_back(); - out << tr << " "; - } - out << endl; - } - - if(change_target && target_racenames.size() && target_racenames[0] == "all") - { - out << "Setting target count for all races on watchlist." << endl; - for(size_t i=0; ifk = target_fk; - w->mk = target_mk; - w->fa = target_fa; - w->ma = target_ma; - w->UpdateConfig(out); - } - } - - if(target_racenames.size() && (target_racenames[0] == "all" || target_racenames[0] == "new")) - { - if(change_target) - { - out << "Setting target count for the future." << endl; - default_fk = target_fk; - default_mk = target_mk; - default_fa = target_fa; - default_ma = target_ma; - if(config_autobutcher.isValid()) - { - config_autobutcher.ival(3) = default_fk; - config_autobutcher.ival(4) = default_mk; - config_autobutcher.ival(5) = default_fa; - config_autobutcher.ival(6) = default_ma; - } - return CR_OK; - } - else if(target_racenames[0] == "new") - { - out << "The only valid usage of 'new' is in combination when setting a target count!" << endl; - - // hm, maybe instead of complaining start/stop autowatch instead? and get rid of the autowatch option? - if(unwatch_race) - out << "'unwatch new' makes no sense! Use 'noautowatch' instead." << endl; - else if(forget_race) - out << "'forget new' makes no sense, 'forget' is only for existing watchlist entries! Use 'noautowatch' instead." << endl; - else if(watch_race) - out << "'watch new' makes no sense! Use 'autowatch' instead." << endl; - return CR_WRONG_USAGE; - } - } - - if(target_racenames.size() && target_racenames[0] == "all") - { - // fill with race ids from watchlist - for(size_t i=0; iraceId); - } - } - else - { - // map race names from parameter list to ids - size_t num_races = world->raws.creatures.all.size(); - while(target_racenames.size()) - { - bool found_race = false; - for(size_t i=0; iraceId == target_raceids.back()) - { - if(unwatch_race) - { - w->isWatched=false; - w->UpdateConfig(out); - } - else if(forget_race) - { - w->RemoveConfig(out); - watched_races.erase(watched_races.begin()+i); - } - else if(watch_race) - { - w->isWatched = true; - w->UpdateConfig(out); - } - else if(change_target) - { - w->fk = target_fk; - w->mk = target_mk; - w->fa = target_fa; - w->ma = target_ma; - w->UpdateConfig(out); - } - entry_found = true; - break; - } - } - if(!entry_found && (watch_race||change_target)) - { - WatchedRace * w = new WatchedRace(watch_race, target_raceids.back(), target_fk, target_mk, target_fa, target_ma); - w->UpdateConfig(out); - watched_races.push_back(w); - autobutcher_sortWatchList(out); - } - target_raceids.pop_back(); - } - - return CR_OK; -} - -// check watched_races vector for a race id, return -1 if nothing found -// calling method needs to check itself if the race is currently being watched or ignored -int getWatchedIndex(int id) -{ - for(size_t i=0; iraceId == id) // && w->isWatched) - return i; - } - return -1; -} - -command_result autoButcher( color_ostream &out, bool verbose = false ) -{ - // don't run if not supposed to - if(!Maps::IsValid()) - return CR_OK; - - // check if there is anything to watch before walking through units vector - if(!enable_autobutcher_autowatch) - { - bool watching = false; - for(size_t i=0; iisWatched) - { - watching = true; - break; - } - } - if(!watching) - return CR_OK; - } - - for(size_t i=0; iunits.all.size(); i++) - { - df::unit * unit = world->units.all[i]; - - // this check is now divided into two steps, squeezed autowatch into the middle - // first one ignores completely inappropriate units (dead, undead, not belonging to the fort, ...) - // then let autowatch add units to the watchlist which will probably start breeding (owned pets, war animals, ...) - // then process units counting those which can't be butchered (war animals, named pets, ...) - // so that they are treated as "own stock" as well and count towards the target quota - if( !isActive(unit) - || isUndead(unit) - || isMarkedForSlaughter(unit) - || isMerchant(unit) // ignore merchants' draught animals - || isForest(unit) // ignore merchants' caged animals - || !isOwnCiv(unit) - || !isTame(unit) - ) - continue; - - // found a bugged unit which had invalid coordinates but was not in a cage. - // marking it for slaughter didn't seem to have negative effects, but you never know... - if(!isContainedInItem(unit) && !hasValidMapPos(unit)) - continue; - - WatchedRace * w = NULL; - int watched_index = getWatchedIndex(unit->race); - if(watched_index != -1) - { - w = watched_races[watched_index]; - } - else if(enable_autobutcher_autowatch) - { - w = new WatchedRace(true, unit->race, default_fk, default_mk, default_fa, default_ma); - w->UpdateConfig(out); - watched_races.push_back(w); - - string announce; - announce = "New race added to autobutcher watchlist: " + getRaceNamePluralById(w->raceId); - Gui::showAnnouncement(announce, 2, false); - autobutcher_sortWatchList(out); - } - - if(w && w->isWatched) - { - // don't butcher protected units, but count them as stock as well - // this way they count towards target quota, so if you order that you want 1 female adult cat - // and have 2 cats, one of them being a pet, the other gets butchered - if( isWar(unit) // ignore war dogs etc - || isHunter(unit) // ignore hunting dogs etc - // ignore creatures in built cages which are defined as rooms to leave zoos alone - // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) - || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() - || isAvailableForAdoption(unit) - || unit->name.has_name ) - w->PushProtectedUnit(unit); - else if ( isGay(unit) - || isGelded(unit)) - w->PushPriorityUnit(unit); - else - w->PushUnit(unit); - } - } - - int slaughter_count = 0; - for(size_t i=0; iProcessUnits(); - slaughter_count += slaughter_subcount; - if(slaughter_subcount) - { - stringstream ss; - ss << slaughter_subcount; - string announce; - announce = getRaceNamePluralById(w->raceId) + " marked for slaughter: " + ss.str(); - Gui::showAnnouncement(announce, 2, false); - } - } - - return CR_OK; -} - -//////////////////////////////////////////////////// -// autobutcher start/init/cleanup - -command_result start_autobutcher(color_ostream &out) -{ - plugin_enable(out, true); - enable_autobutcher = true; - - if (!config_autobutcher.isValid()) - { - config_autobutcher = World::AddPersistentData("autobutcher/config"); - - if (!config_autobutcher.isValid()) - { - out << "Cannot enable autobutcher without a world!" << endl; - return CR_OK; - } - - config_autobutcher.ival(1) = sleep_autobutcher; - config_autobutcher.ival(2) = enable_autobutcher_autowatch; - config_autobutcher.ival(3) = default_fk; - config_autobutcher.ival(4) = default_mk; - config_autobutcher.ival(5) = default_fa; - config_autobutcher.ival(6) = default_ma; - } - - config_autobutcher.ival(0) = enable_autobutcher; - - out << "Starting autobutcher." << endl; - init_autobutcher(out); - return CR_OK; -} - -command_result init_autobutcher(color_ostream &out) -{ - cleanup_autobutcher(out); - - config_autobutcher = World::GetPersistentData("autobutcher/config"); - if(config_autobutcher.isValid()) - { - if (config_autobutcher.ival(0) == -1) - { - config_autobutcher.ival(0) = enable_autobutcher; - config_autobutcher.ival(1) = sleep_autobutcher; - config_autobutcher.ival(2) = enable_autobutcher_autowatch; - config_autobutcher.ival(3) = default_fk; - config_autobutcher.ival(4) = default_mk; - config_autobutcher.ival(5) = default_fa; - config_autobutcher.ival(6) = default_ma; - out << "Autobutcher's persistent config object was invalid!" << endl; - } - else - { - enable_autobutcher = config_autobutcher.ival(0); - sleep_autobutcher = config_autobutcher.ival(1); - enable_autobutcher_autowatch = config_autobutcher.ival(2); - default_fk = config_autobutcher.ival(3); - default_mk = config_autobutcher.ival(4); - default_fa = config_autobutcher.ival(5); - default_ma = config_autobutcher.ival(6); - } - } - - if(!enable_autobutcher) - return CR_OK; - - plugin_enable(out, true); - // read watchlist from save - - std::vector items; - World::GetPersistentData(&items, "autobutcher/watchlist/", true); - for (auto p = items.begin(); p != items.end(); p++) - { - string key = p->key(); - out << "Reading from save: " << key << endl; - //out << " raceid: " << p->ival(0) << endl; - //out << " watched: " << p->ival(1) << endl; - //out << " fk: " << p->ival(2) << endl; - //out << " mk: " << p->ival(3) << endl; - //out << " fa: " << p->ival(4) << endl; - //out << " ma: " << p->ival(5) << endl; - WatchedRace * w = new WatchedRace(p->ival(1), p->ival(0), p->ival(2), p->ival(3),p->ival(4),p->ival(5)); - w->rconfig = *p; - watched_races.push_back(w); - } - autobutcher_sortWatchList(out); - return CR_OK; -} - -command_result cleanup_autobutcher(color_ostream &out) -{ - for(size_t i=0; iunits.all.size(); i++) - { - df::unit * unit = world->units.all[i]; - - if(unit->race != race) - continue; - - if( !isActive(unit) - || isUndead(unit) - || isMerchant(unit) // ignore merchants' draught animals - || isForest(unit) // ignore merchants' caged animals - || !isOwnCiv(unit) - ) - continue; - - // found a bugged unit which had invalid coordinates but was not in a cage. - // marking it for slaughter didn't seem to have negative effects, but you never know... - if(!isContainedInItem(unit) && !hasValidMapPos(unit)) - continue; - - w->PushUnit(unit); - } - return w; -} - -WatchedRace * checkRaceStocksProtected(int race) -{ - WatchedRace * w = new WatchedRace(true, race, default_fk, default_mk, default_fa, default_ma); - - for(size_t i=0; iunits.all.size(); i++) - { - df::unit * unit = world->units.all[i]; - - if(unit->race != race) - continue; - - if( !isActive(unit) - || isUndead(unit) - || isMerchant(unit) // ignore merchants' draught animals - || isForest(unit) // ignore merchants' caged animals - || !isOwnCiv(unit) - ) - continue; - - // found a bugged unit which had invalid coordinates but was not in a cage. - // marking it for slaughter didn't seem to have negative effects, but you never know... - if(!isContainedInItem(unit) && !hasValidMapPos(unit)) - continue; - - if( !isTame(unit) - || isWar(unit) // ignore war dogs etc - || isHunter(unit) // ignore hunting dogs etc - // ignore creatures in built cages which are defined as rooms to leave zoos alone - // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) - || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() - || isAvailableForAdoption(unit) - || unit->name.has_name ) - w->PushUnit(unit); - } - return w; -} - -WatchedRace * checkRaceStocksButcherable(int race) -{ - WatchedRace * w = new WatchedRace(true, race, default_fk, default_mk, default_fa, default_ma); - - for(size_t i=0; iunits.all.size(); i++) - { - df::unit * unit = world->units.all[i]; - - if(unit->race != race) - continue; - - if( !isActive(unit) - || isUndead(unit) - || isMerchant(unit) // ignore merchants' draught animals - || isForest(unit) // ignore merchants' caged animals - || !isOwnCiv(unit) - || !isTame(unit) - || isWar(unit) // ignore war dogs etc - || isHunter(unit) // ignore hunting dogs etc - // ignore creatures in built cages which are defined as rooms to leave zoos alone - // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) - || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() - || isAvailableForAdoption(unit) - || unit->name.has_name - ) - continue; - - // found a bugged unit which had invalid coordinates but was not in a cage. - // marking it for slaughter didn't seem to have negative effects, but you never know... - if(!isContainedInItem(unit) && !hasValidMapPos(unit)) - continue; - - w->PushUnit(unit); - } - return w; -} - -WatchedRace * checkRaceStocksButcherFlag(int race) -{ - WatchedRace * w = new WatchedRace(true, race, default_fk, default_mk, default_fa, default_ma); - - for(size_t i=0; iunits.all.size(); i++) - { - df::unit * unit = world->units.all[i]; - - if(unit->race != race) - continue; - - if( !isActive(unit) - || isUndead(unit) - || isMerchant(unit) // ignore merchants' draught animals - || isForest(unit) // ignore merchants' caged animals - || !isOwnCiv(unit) - ) - continue; - - // found a bugged unit which had invalid coordinates but was not in a cage. - // marking it for slaughter didn't seem to have negative effects, but you never know... - if(!isContainedInItem(unit) && !hasValidMapPos(unit)) - continue; - - if(isMarkedForSlaughter(unit)) - w->PushUnit(unit); - } - return w; -} - -void butcherRace(int race) -{ - for(size_t i=0; iunits.all.size(); i++) - { - df::unit * unit = world->units.all[i]; - - if(unit->race != race) - continue; - - if( !isActive(unit) - || isUndead(unit) - || isMerchant(unit) // ignore merchants' draught animals - || isForest(unit) // ignore merchants' caged animals - || !isOwnCiv(unit) - || !isTame(unit) - || isWar(unit) // ignore war dogs etc - || isHunter(unit) // ignore hunting dogs etc - // ignore creatures in built cages which are defined as rooms to leave zoos alone - // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) - || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() - || isAvailableForAdoption(unit) - || unit->name.has_name - ) - continue; - - // found a bugged unit which had invalid coordinates but was not in a cage. - // marking it for slaughter didn't seem to have negative effects, but you never know... - if(!isContainedInItem(unit) && !hasValidMapPos(unit)) - continue; - - doMarkForSlaughter(unit); - } -} - -// remove butcher flag for all units of a given race -void unbutcherRace(int race) -{ - for(size_t i=0; iunits.all.size(); i++) - { - df::unit * unit = world->units.all[i]; - - if(unit->race != race) - continue; - - if( !isActive(unit) - || isUndead(unit) - || !isMarkedForSlaughter(unit) - ) - continue; - - if(!isContainedInItem(unit) && !hasValidMapPos(unit)) - continue; - - unit->flags2.bits.slaughter = 0; - } -} - - -///////////////////////////////////// -// API functions to control autobutcher with a lua script - -static bool autobutcher_isEnabled() { return enable_autobutcher; } -static bool autowatch_isEnabled() { return enable_autobutcher_autowatch; } - -static unsigned autobutcher_getSleep(color_ostream &out) -{ - return sleep_autobutcher; -} - -static void autobutcher_setSleep(color_ostream &out, unsigned ticks) -{ - sleep_autobutcher = ticks; - if(config_autobutcher.isValid()) - config_autobutcher.ival(1) = sleep_autobutcher; -} - -static void autobutcher_setEnabled(color_ostream &out, bool enable) -{ - if(enable) - { - enable_autobutcher = true; - start_autobutcher(out); - autoButcher(out, false); - } - else - { - enable_autobutcher = false; - if(config_autobutcher.isValid()) - config_autobutcher.ival(0) = enable_autobutcher; - out << "Autobutcher stopped." << endl; - } - - if (enable) - plugin_enable(out, true); -} - -static void autowatch_setEnabled(color_ostream &out, bool enable) -{ - if(enable) - { - out << "Auto-adding to watchlist started." << endl; - enable_autobutcher_autowatch = true; - if(config_autobutcher.isValid()) - config_autobutcher.ival(2) = enable_autobutcher_autowatch; - } - else - { - out << "Auto-adding to watchlist stopped." << endl; - enable_autobutcher_autowatch = false; - if(config_autobutcher.isValid()) - config_autobutcher.ival(2) = enable_autobutcher_autowatch; - } -} - -// set all data for a watchlist race in one go -// if race is not already on watchlist it will be added -// params: (id, fk, mk, fa, ma, watched) -static void autobutcher_setWatchListRace(color_ostream &out, unsigned id, unsigned fk, unsigned mk, unsigned fa, unsigned ma, bool watched) -{ - int watched_index = getWatchedIndex(id); - if(watched_index != -1) - { - out << "updating watchlist entry" << endl; - WatchedRace * w = watched_races[watched_index]; - w->fk = fk; - w->mk = mk; - w->fa = fa; - w->ma = ma; - w->isWatched = watched; - w->UpdateConfig(out); - } - else - { - out << "creating new watchlist entry" << endl; - WatchedRace * w = new WatchedRace(watched, id, fk, mk, fa, ma); //default_fk, default_mk, default_fa, default_ma); - w->UpdateConfig(out); - watched_races.push_back(w); - - string announce; - announce = "New race added to autobutcher watchlist: " + getRaceNamePluralById(w->raceId); - Gui::showAnnouncement(announce, 2, false); - autobutcher_sortWatchList(out); - } -} - -// remove entry from watchlist -static void autobutcher_removeFromWatchList(color_ostream &out, unsigned id) -{ - int watched_index = getWatchedIndex(id); - if(watched_index != -1) - { - out << "updating watchlist entry" << endl; - WatchedRace * w = watched_races[watched_index]; - w->RemoveConfig(out); - watched_races.erase(watched_races.begin() + watched_index); - } -} - -// sort watchlist alphabetically -static void autobutcher_sortWatchList(color_ostream &out) -{ - sort(watched_races.begin(), watched_races.end(), compareRaceNames); -} - -// set default target values for new races -static void autobutcher_setDefaultTargetNew(color_ostream &out, unsigned fk, unsigned mk, unsigned fa, unsigned ma) -{ - default_fk = fk; - default_mk = mk; - default_fa = fa; - default_ma = ma; - if(config_autobutcher.isValid()) - { - config_autobutcher.ival(3) = default_fk; - config_autobutcher.ival(4) = default_mk; - config_autobutcher.ival(5) = default_fa; - config_autobutcher.ival(6) = default_ma; - } -} - -// set default target values for ALL races (update watchlist and set new default) -static void autobutcher_setDefaultTargetAll(color_ostream &out, unsigned fk, unsigned mk, unsigned fa, unsigned ma) -{ - for(unsigned i=0; ifk = fk; - w->mk = mk; - w->fa = fa; - w->ma = ma; - w->UpdateConfig(out); - } - autobutcher_setDefaultTargetNew(out, fk, mk, fa, ma); -} - -static void autobutcher_butcherRace(color_ostream &out, unsigned id) -{ - butcherRace(id); -} - -static void autobutcher_unbutcherRace(color_ostream &out, unsigned id) -{ - unbutcherRace(id); -} - -// push autobutcher settings on lua stack -static int autobutcher_getSettings(lua_State *L) -{ - lua_newtable(L); - int ctable = lua_gettop(L); - Lua::SetField(L, enable_autobutcher, ctable, "enable_autobutcher"); - Lua::SetField(L, enable_autobutcher_autowatch, ctable, "enable_autowatch"); - Lua::SetField(L, default_fk, ctable, "fk"); - Lua::SetField(L, default_mk, ctable, "mk"); - Lua::SetField(L, default_fa, ctable, "fa"); - Lua::SetField(L, default_ma, ctable, "ma"); - Lua::SetField(L, sleep_autobutcher, ctable, "sleep"); - return 1; -} - -// push the watchlist vector as nested table on the lua stack -static int autobutcher_getWatchList(lua_State *L) -{ - lua_newtable(L); - - for(size_t i=0; iraceId, ctable, "id"); - Lua::SetField(L, w->isWatched, ctable, "watched"); - Lua::SetField(L, getRaceNamePluralById(w->raceId), ctable, "name"); - Lua::SetField(L, w->fk, ctable, "fk"); - Lua::SetField(L, w->mk, ctable, "mk"); - Lua::SetField(L, w->fa, ctable, "fa"); - Lua::SetField(L, w->ma, ctable, "ma"); - - int id = w->raceId; - - w = checkRaceStocksTotal(id); - Lua::SetField(L, w->unit_ptr[fk_index].size(), ctable, "fk_total"); - Lua::SetField(L, w->unit_ptr[mk_index].size(), ctable, "mk_total"); - Lua::SetField(L, w->unit_ptr[fa_index].size(), ctable, "fa_total"); - Lua::SetField(L, w->unit_ptr[ma_index].size(), ctable, "ma_total"); - delete w; - - w = checkRaceStocksProtected(id); - Lua::SetField(L, w->unit_ptr[fk_index].size(), ctable, "fk_protected"); - Lua::SetField(L, w->unit_ptr[mk_index].size(), ctable, "mk_protected"); - Lua::SetField(L, w->unit_ptr[fa_index].size(), ctable, "fa_protected"); - Lua::SetField(L, w->unit_ptr[ma_index].size(), ctable, "ma_protected"); - delete w; - - w = checkRaceStocksButcherable(id); - Lua::SetField(L, w->unit_ptr[fk_index].size(), ctable, "fk_butcherable"); - Lua::SetField(L, w->unit_ptr[mk_index].size(), ctable, "mk_butcherable"); - Lua::SetField(L, w->unit_ptr[fa_index].size(), ctable, "fa_butcherable"); - Lua::SetField(L, w->unit_ptr[ma_index].size(), ctable, "ma_butcherable"); - delete w; - - w = checkRaceStocksButcherFlag(id); - Lua::SetField(L, w->unit_ptr[fk_index].size(), ctable, "fk_butcherflag"); - Lua::SetField(L, w->unit_ptr[mk_index].size(), ctable, "mk_butcherflag"); - Lua::SetField(L, w->unit_ptr[fa_index].size(), ctable, "fa_butcherflag"); - Lua::SetField(L, w->unit_ptr[ma_index].size(), ctable, "ma_butcherflag"); - delete w; - - lua_rawseti(L, -2, i+1); - } - - return 1; -} - -DFHACK_PLUGIN_LUA_FUNCTIONS { - DFHACK_LUA_FUNCTION(autobutcher_isEnabled), - DFHACK_LUA_FUNCTION(autowatch_isEnabled), - DFHACK_LUA_FUNCTION(autobutcher_setEnabled), - DFHACK_LUA_FUNCTION(autowatch_setEnabled), - DFHACK_LUA_FUNCTION(autobutcher_getSleep), - DFHACK_LUA_FUNCTION(autobutcher_setSleep), - DFHACK_LUA_FUNCTION(autobutcher_setWatchListRace), - DFHACK_LUA_FUNCTION(autobutcher_setDefaultTargetNew), - DFHACK_LUA_FUNCTION(autobutcher_setDefaultTargetAll), - DFHACK_LUA_FUNCTION(autobutcher_butcherRace), - DFHACK_LUA_FUNCTION(autobutcher_unbutcherRace), - DFHACK_LUA_FUNCTION(autobutcher_removeFromWatchList), - DFHACK_LUA_FUNCTION(autobutcher_sortWatchList), - DFHACK_LUA_END -}; - -DFHACK_PLUGIN_LUA_COMMANDS { - DFHACK_LUA_COMMAND(autobutcher_getSettings), - DFHACK_LUA_COMMAND(autobutcher_getWatchList), - DFHACK_LUA_END -}; - -// end lua API - - - -//START zone filters - -class zone_filter -{ -public: - zone_filter() - { - initialized = false; - } - - void initialize(const df::ui_sidebar_mode &mode) - { - if (!initialized) - { - this->mode = mode; - saved_ui_building_assign_type.clear(); - saved_ui_building_assign_units.clear(); - saved_ui_building_assign_items.clear(); - saved_ui_building_assign_is_marked.clear(); - saved_indexes.clear(); - - for (size_t i = 0; i < ui_building_assign_units->size(); i++) - { - saved_ui_building_assign_type.push_back(ui_building_assign_type->at(i)); - saved_ui_building_assign_units.push_back(ui_building_assign_units->at(i)); - saved_ui_building_assign_items.push_back(ui_building_assign_items->at(i)); - saved_ui_building_assign_is_marked.push_back(ui_building_assign_is_marked->at(i)); - } - - search_string.clear(); - show_non_grazers = show_pastured = show_noncaged = show_male = show_female = show_other_zones = true; - entry_mode = false; - - initialized = true; - } - } - - void deinitialize() - { - initialized = false; - } - - void apply_filters() - { - if (saved_indexes.size() > 0) - { - bool list_has_been_sorted = (ui_building_assign_units->size() == reference_list.size() - && *ui_building_assign_units != reference_list); - - for (size_t i = 0; i < saved_indexes.size(); i++) - { - int adjusted_item_index = i; - if (list_has_been_sorted) - { - for (size_t j = 0; j < ui_building_assign_units->size(); j++) - { - if (ui_building_assign_units->at(j) == reference_list[i]) - { - adjusted_item_index = j; - break; - } - } - } - - saved_ui_building_assign_is_marked[saved_indexes[i]] = ui_building_assign_is_marked->at(adjusted_item_index); - } + saved_ui_building_assign_is_marked[saved_indexes[i]] = ui_building_assign_is_marked->at(adjusted_item_index); + } } string search_string_l = toLower(search_string); @@ -3578,7 +1979,7 @@ public: if (!curr_unit) continue; - if (!show_non_grazers && !isGrazer(curr_unit)) + if (!show_non_grazers && !Units::isGrazer(curr_unit)) continue; if (!show_pastured && isAssignedToZone(curr_unit)) @@ -3594,10 +1995,10 @@ public: continue; } - if (!show_male && isMale(curr_unit)) + if (!show_male && Units::isMale(curr_unit)) continue; - if (!show_female && isFemale(curr_unit)) + if (!show_female && Units::isFemale(curr_unit)) continue; if (!search_string_l.empty()) @@ -3894,21 +2295,15 @@ DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "zone", "manage activity zones.", - df_zone, false, - zone_help.c_str() - )); - commands.push_back(PluginCommand( - "autobutcher", "auto-assign lifestock for butchering.", - df_autobutcher, false, - autobutcher_help.c_str() - )); - init_autobutcher(out); + "zone", + "manage activity zones.", + df_zone, + false, + zone_help.c_str())); return CR_OK; } DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { - cleanup_autobutcher(out); return CR_OK; } From fe2212db96244c056e00a374bba5269aad3e49fc Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 2 Aug 2022 01:07:36 -0700 Subject: [PATCH 050/111] output status when run without params --- plugins/autonestbox.cpp | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/plugins/autonestbox.cpp b/plugins/autonestbox.cpp index 6c878806c..b1932b269 100644 --- a/plugins/autonestbox.cpp +++ b/plugins/autonestbox.cpp @@ -41,12 +41,14 @@ static const string autonestbox_help = "Usage:\n" "\n" "enable autonestbox\n" - " Start checking for unpastured egg-layers and assigning them to nestbox zones.\n" + " Start checking for unpastured egg-layers and assigning them to nestbox zones.\n" + "autonestbox\n" + " Print current status." "autonestbox now\n" - " Run a scan and assignment cycle right now. Does not require that the plugin is enabled.\n" + " Run a scan and assignment cycle right now. Does not require that the plugin is enabled.\n" "autonestbox ticks \n" - " Change the number of ticks between scan and assignment cycles when the plugin is enabled.\n" - " The default is 6000 (about 8 days)\n"; + " Change the number of ticks between scan and assignment cycles when the plugin is enabled.\n" + " The default is 6000 (about 8 days)\n"; namespace DFHack { DBG_DECLARE(autonestbox, status); @@ -56,8 +58,8 @@ namespace DFHack { static const string CONFIG_KEY = "autonestbox/config"; static PersistentDataItem config; enum ConfigValues { - IS_ENABLED = 0, - CYCLE_TICKS = 1, + CONFIG_IS_ENABLED = 0, + CONFIG_CYCLE_TICKS = 1, }; static int get_config_val(int index) { if (!config.isValid()) @@ -103,15 +105,15 @@ static void init_autonestbox(color_ostream &out) { if (!config.isValid()) config = World::AddPersistentData(CONFIG_KEY); - if (get_config_val(IS_ENABLED) == -1) { - set_config_val(IS_ENABLED, 0); - set_config_val(CYCLE_TICKS, 6000); + if (get_config_val(CONFIG_IS_ENABLED) == -1) { + set_config_val(CONFIG_IS_ENABLED, 0); + set_config_val(CONFIG_CYCLE_TICKS, 6000); } if (is_enabled) - set_config_val(IS_ENABLED, 1); + set_config_val(CONFIG_IS_ENABLED, 1); else - is_enabled = (get_config_val(IS_ENABLED) == 1); + is_enabled = (get_config_val(CONFIG_IS_ENABLED) == 1); did_complain = false; } @@ -167,7 +169,8 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan } DFhackCExport command_result plugin_onupdate(color_ostream &out) { - if (is_enabled && ++cycle_counter >= (size_t)get_config_val(CYCLE_TICKS)) + if (is_enabled && ++cycle_counter >= + (size_t)get_config_val(CONFIG_CYCLE_TICKS)) autonestbox_cycle(out); return CR_OK; } @@ -209,12 +212,15 @@ static command_result df_autonestbox(color_ostream &out, vector ¶met return CR_WRONG_USAGE; if (opts.ticks > -1) { - set_config_val(CYCLE_TICKS, opts.ticks); + set_config_val(CONFIG_CYCLE_TICKS, opts.ticks); INFO(status,out).print("New cycle timer: %d ticks.\n", opts.ticks); } else if (opts.now) { autonestbox_cycle(out); } + else { + out << "autonestbox is " << (is_enabled ? "" : "not ") << "running\n"; + } return CR_OK; } From 3983b4d75b41c31510c42ae833825514b730232a Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 2 Aug 2022 01:54:21 -0700 Subject: [PATCH 051/111] update docs --- plugins/autobutcher.cpp | 121 +++++++++++++++++++++--------------- plugins/autonestbox.cpp | 1 - plugins/lua/autobutcher.lua | 9 --- 3 files changed, 72 insertions(+), 59 deletions(-) diff --git a/plugins/autobutcher.cpp b/plugins/autobutcher.cpp index 0d7a9c632..69799207a 100644 --- a/plugins/autobutcher.cpp +++ b/plugins/autobutcher.cpp @@ -34,63 +34,86 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled); REQUIRE_GLOBAL(world); const string autobutcher_help = - "Assigns your lifestock for slaughter once it reaches a specific count. Requires\n" + "Automatically butcher excess livestock. This plugin monitors how many pets\n" + "you have of each gender and age and assigns excess lifestock for slaughter\n" + "once they reach a specific count. Requires\n" "that you add the target race(s) to a watch list. Only tame units will be\n" "processed. Named units will be completely ignored (you can give animals\n" "nicknames with the tool 'rename unit' to protect them from getting slaughtered\n" - "automatically. Trained war or hunting pets will be ignored.\n" - "Once you have too much adults, the oldest will be butchered first.\n" - "Once you have too much kids, the youngest will be butchered first.\n" + "automatically). Trained war or hunting pets will be ignored.\n" + "Once you have too many adults, the oldest will be butchered first.\n" + "Once you have too many kids, the youngest will be butchered first.\n" "If you don't set a target count the following default will be used:\n" "1 male kid, 5 female kids, 1 male adult, 5 female adults.\n" - "Options:\n" - " start - run every X frames (df simulation ticks)\n" - " default: X=6000 (~60 seconds at 100fps)\n" - " stop - stop running automatically\n" - " sleep X - change timer to sleep X frames between runs.\n" - " watch R - start watching race(s)\n" - " R = valid race RAW id (ALPACA, BIRD_TURKEY, etc)\n" - " or a list of RAW ids seperated by spaces\n" - " or the keyword 'all' which affects your whole current watchlist.\n" - " unwatch R - stop watching race(s)\n" - " the current target settings will be remembered\n" - " forget R - unwatch race(s) and forget target settings for it/them\n" - " autowatch - automatically adds all new races (animals you buy\n" - " from merchants, tame yourself or get from migrants)\n" - " to the watch list using default target count\n" - " noautowatch - stop auto-adding new races to the watch list\n" - " list - print status and watchlist\n" - " list_export - print status and watchlist in batchfile format\n" - " can be used to copy settings into another savegame\n" - " usage: 'dfhack-run autobutcher list_export > xyz.bat' \n" - " target fk mk fa ma R\n" - " - set target count for specified race:\n" - " fk = number of female kids\n" - " mk = number of male kids\n" - " fa = number of female adults\n" - " ma = number of female adults\n" - " R = 'all' sets count for all races on the current watchlist\n" - " including the races which are currenly set to 'unwatched'\n" - " and sets the new default for future watch commands\n" - " R = 'new' sets the new default for future watch commands\n" - " without changing your current watchlist\n" - " example - print some usage examples\n"; - -const string autobutcher_help_example = + "\n" + "Usage:\n" + "\n" + "enable autobutcher\n" + " Start processing livestock according to the configuration. Note that\n" + " no races are watched by default. You have to add the ones you want to\n" + " monitor (or use autowatch)\n" + "autobutcher autowatch\n" + " Automatically add all new races (animals you buy\n" + " from merchants, tame yourself, or get from migrants)\n" + " to the watch list using the default target counts.\n" + "autobutcher noautowatch\n" + " Stop auto-adding new races to the watch list.\n" + "autobutcher target all|new| [ ...] \n" + " Set target counts for the specified races:\n" + " fk = number of female kids\n" + " mk = number of male kids\n" + " fa = number of female adults\n" + " ma = number of female adults\n" + " If you specify 'all', then this command will set the counts for all races\n" + " on your current watchlist (including the races which are currenly set to\n" + " 'unwatched') and sets the new default for future watch commands. If you\n" + " specify 'new', then this command just sets the new default counts for\n" + " future watch commands without changing your current watchlist. Otherwise,\n" + " all space separated races listed will be modified (or added to the watchlist\n" + " if they aren't there already).\n" + "autobutcher ticks \n" + " Change the number of ticks between scanning cycles when the plugin is\n" + " enabled. By default, a cycle happens every 6000 ticks (about 8 game days).\n" + "autobutcher watch all| [ ...]\n" + " Start watching the listed races. If they aren't already in your watchlist, then\n" + " they will be added with the default target counts. If you specify the keyword 'all',\n" + " then all races in your watchlist that are currently marked as unwatched will become\n" + " watched.\n" + "autobutcher unwatch all| [ ...]\n" + " Stop watching the specified race(s) (or all races on your watchlist if 'all' is\n" + " given). The current target settings will be remembered.\n" + "autobutcher forget all| [ ...]\n" + " Unwatch the specified race(s) (or all races on your watchlist if 'all' is given)\n" + " and forget target settings for it/them.\n" + "autobutcher [list]\n" + " Print status and current settings, including the watchlist.\n" + "autobutcher list_export\n" + " Print commands required to set the current settings in another fort.\n" + " Useful to run form dfhack-run like: 'dfhack-run autobutcher list_export > autobutcher.script'\n" + "\n" + "To see a list of all races, run this command:\n" + "\n" + " devel/query --table df.global.world.raws.creatures.all --search ^creature_id --maxdepth 1'\n" + "\n" + "Though not all the races listed there are tameable/butcherable\n" + "\n" "Examples:\n" - " autobutcher target 4 3 2 1 ALPACA BIRD_TURKEY\n" - " autobutcher watch ALPACA BIRD_TURKEY\n" - " autobutcher start\n" - " This means you want to have max 7 kids (4 female, 3 male) and max 3 adults\n" - " (2 female, 1 male) of the races alpaca and turkey. Once the kids grow up the\n" + "\n" + "autobutcher target 4 3 2 1 BIRD_TURKEY\n" + " This means you want to have at most 7 kids (4 female, 3 male) and at most 3 adults\n" + " (2 female, 1 male) for turkeys. Once the kids grow up, the\n" " oldest adults will get slaughtered. Excess kids will get slaughtered starting\n" " the the youngest to allow that the older ones grow into adults.\n" - " autobutcher target 0 0 0 0 new\n" - " autobutcher autowatch\n" - " autobutcher start\n" - " This tells autobutcher to automatically put all new races onto the watchlist\n" - " and mark unnamed tame units for slaughter as soon as they arrive in your\n" - " fortress. Settings already made for some races will be left untouched.\n"; + "autobutcher target 2 2 2 2 DOG\n" + "autobutcher target 1 1 2 2 CAT\n" + "autobutcher target 50 50 14 2 BIRD_GOOSE\n" + "autobutcher target 2 2 4 2 ALPACA SHEEP LLAMA\n" + "autobutcher target 5 5 6 2 PIG\n" + "autobutcher target 0 0 0 0 new\n" + "autobutcher autowatch\n" + " Configure useful limits for dogs, cats, geese (for eggs, leather, and bones), alpacas, sheep,\n" + " and llamas (for wool), and pigs (for milk and meat). All other unnamed tame units will be marked\n" + " for slaughter as soon as they arrive in your fortress.\n"; namespace DFHack { DBG_DECLARE(autobutcher, status); diff --git a/plugins/autonestbox.cpp b/plugins/autonestbox.cpp index b1932b269..f61f9d635 100644 --- a/plugins/autonestbox.cpp +++ b/plugins/autonestbox.cpp @@ -37,7 +37,6 @@ static const string autonestbox_help = "If the pen is bigger than 1x1 the nestbox must be in the top left corner.\n" "Only 1 unit will be assigned per pen, regardless of the size.\n" "The age of the units is currently not checked, most birds grow up quite fast.\n" - "When called without options autonestbox will instantly run once.\n" "Usage:\n" "\n" "enable autonestbox\n" diff --git a/plugins/lua/autobutcher.lua b/plugins/lua/autobutcher.lua index ab334896c..f357f8fb1 100644 --- a/plugins/lua/autobutcher.lua +++ b/plugins/lua/autobutcher.lua @@ -1,14 +1,5 @@ local _ENV = mkmodule('plugins.autobutcher') ---[[ - - Native functions: - - * autobutcher_isEnabled() - * autowatch_isEnabled() - ---]] - local argparse = require('argparse') local function is_int(val) From db81538f63c3a4a6c334d084dccd311077e50885 Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 2 Aug 2022 02:00:15 -0700 Subject: [PATCH 052/111] update changelog --- docs/changelog.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index ce61d31d3..efaab734d 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -34,6 +34,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: # Future ## New Plugins +- `autonestbox`: split off from `zone` into its own plugin. Note that to enable, the command has changed from ``autonestbox start`` to `enable autonestbox`. +- `autobutcher`: split off from `zone` into its own plugin. Note that to enable, the command has changed from ``autobutcher start`` to `enable autobutcher`. ## New Tweaks From 4acb59cb64666f0ce3c57c56b3f3de667a433a15 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 2 Aug 2022 09:01:43 +0000 Subject: [PATCH 053/111] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- plugins/autonestbox.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/autonestbox.cpp b/plugins/autonestbox.cpp index f61f9d635..10c0e3cd8 100644 --- a/plugins/autonestbox.cpp +++ b/plugins/autonestbox.cpp @@ -100,7 +100,7 @@ static void autonestbox_cycle(color_ostream &out); static void init_autonestbox(color_ostream &out) { config = World::GetPersistentData(CONFIG_KEY); - + if (!config.isValid()) config = World::AddPersistentData(CONFIG_KEY); From 9595e2152da4e9502b9926894b7af9540041d13e Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 2 Aug 2022 02:02:23 -0700 Subject: [PATCH 054/111] update changelog (fix typo) --- docs/changelog.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index efaab734d..2733cd539 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -34,8 +34,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: # Future ## New Plugins -- `autonestbox`: split off from `zone` into its own plugin. Note that to enable, the command has changed from ``autonestbox start`` to `enable autonestbox`. -- `autobutcher`: split off from `zone` into its own plugin. Note that to enable, the command has changed from ``autobutcher start`` to `enable autobutcher`. +- `autonestbox`: split off from `zone` into its own plugin. Note that to enable, the command has changed from ``autonestbox start`` to ``enable autonestbox``. +- `autobutcher`: split off from `zone` into its own plugin. Note that to enable, the command has changed from ``autobutcher start`` to ``enable autobutcher``. ## New Tweaks From f98015ae5544b526aab2799796894bd1535f42c4 Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 2 Aug 2022 10:42:54 -0700 Subject: [PATCH 055/111] ensure we run every N ticks, not frames add more debug messages fix watching/unwatching/forgetting races that aren't in the watchlist --- data/examples/init/onMapLoad_dreamfort.init | 10 ++- plugins/autobutcher.cpp | 67 +++++++++++++++------ plugins/autonestbox.cpp | 14 +++-- 3 files changed, 61 insertions(+), 30 deletions(-) diff --git a/data/examples/init/onMapLoad_dreamfort.init b/data/examples/init/onMapLoad_dreamfort.init index c75620846..05d32134f 100644 --- a/data/examples/init/onMapLoad_dreamfort.init +++ b/data/examples/init/onMapLoad_dreamfort.init @@ -37,17 +37,14 @@ enable automelt # creates manager orders to produce replacements for worn clothing enable tailor -tailor enable # auto-assigns nesting birds to nestbox zones and protects fertile eggs from # being cooked/eaten -enable zone nestboxes -autonestbox start +enable zone autonestbox nestboxes # manages seed stocks enable seedwatch seedwatch all 30 -seedwatch start # ensures important tasks get assigned to workers. # otherwise these job types can get ignored in busy forts. @@ -65,6 +62,7 @@ prioritize -a --reaction-name=TAN_A_HIDE CustomReaction # feel free to change this to "target 0 0 0 0" if you don't expect to want to raise # any animals not listed here -- you can always change it anytime during the game # later if you change your mind. +on-new-fortress enable autobutcher on-new-fortress autobutcher target 2 2 2 2 new # dogs and cats. You should raise the limits for dogs if you will be training them # for hunting or war. @@ -82,5 +80,5 @@ on-new-fortress autobutcher target 2 2 4 2 ALPACA SHEEP LLAMA on-new-fortress autobutcher target 5 5 6 2 PIG # butcher all unprofitable animals on-new-fortress autobutcher target 0 0 0 0 HORSE YAK DONKEY WATER_BUFFALO GOAT CAVY BIRD_DUCK BIRD_GUINEAFOWL -# start it up! -on-new-fortress autobutcher start; autobutcher watch all; autobutcher autowatch +# watch for new animals +on-new-fortress autobutcher autowatch diff --git a/plugins/autobutcher.cpp b/plugins/autobutcher.cpp index 69799207a..0b0d4ffdb 100644 --- a/plugins/autobutcher.cpp +++ b/plugins/autobutcher.cpp @@ -150,7 +150,7 @@ static void set_config_bool(int index, bool value) { } static unordered_map race_to_id; -static size_t cycle_counter = 0; // how many ticks since the last cycle +static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle static size_t DEFAULT_CYCLE_TICKS = 6000; @@ -252,7 +252,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan } DFhackCExport command_result plugin_onupdate(color_ostream &out) { - if (is_enabled && ++cycle_counter >= (size_t)get_config_val(CONFIG_CYCLE_TICKS)) + if (is_enabled && world->frame_counter - cycle_timestamp >= get_config_val(CONFIG_CYCLE_TICKS)) autobutcher_cycle(out); return CR_OK; } @@ -663,7 +663,8 @@ static void autobutcher_status(color_ostream &out) { static void autobutcher_target(color_ostream &out, const autobutcher_options &opts) { if (opts.races_new) { - DEBUG(status,out).print("setting targets for new races\n"); + DEBUG(status,out).print("setting targets for new races to fk=%u, mk=%u, fa=%u, ma=%u\n", + opts.fk, opts.mk, opts.fa, opts.ma); set_config_val(CONFIG_DEFAULT_FK, opts.fk); set_config_val(CONFIG_DEFAULT_MK, opts.mk); set_config_val(CONFIG_DEFAULT_FA, opts.fa); @@ -671,7 +672,8 @@ static void autobutcher_target(color_ostream &out, const autobutcher_options &op } if (opts.races_all) { - DEBUG(status,out).print("setting targets for all races on watchlist\n"); + DEBUG(status,out).print("setting targets for all races on watchlist to fk=%u, mk=%u, fa=%u, ma=%u\n", + opts.fk, opts.mk, opts.fa, opts.ma); for (auto w : watched_races) { w.second->fk = opts.fk; w.second->mk = opts.mk; @@ -689,7 +691,6 @@ static void autobutcher_target(color_ostream &out, const autobutcher_options &op int id = race_to_id[*race]; WatchedRace *w; if (!watched_races.count(id)) { - DEBUG(status,out).print("adding new targets for %s\n", race->c_str()); w = new WatchedRace(out, id, true, opts.fk, opts.mk, opts.fa, opts.ma); watched_races.emplace(id, w); } else { @@ -707,8 +708,6 @@ static void autobutcher_modify_watchlist(color_ostream &out, const autobutcher_o unordered_set ids; if (opts.races_all) { - DEBUG(status,out).print("modifying all races on watchlist: %s\n", - opts.command.c_str()); for (auto w : watched_races) ids.emplace(w.first); } @@ -722,13 +721,41 @@ static void autobutcher_modify_watchlist(color_ostream &out, const autobutcher_o } for (int id : ids) { - if (opts.command == "watch") - watched_races[id]->isWatched = true; - else if (opts.command == "unwatch") - watched_races[id]->isWatched = false; + if (opts.command == "watch") { + if (!watched_races.count(id)) { + watched_races.emplace(id, + new WatchedRace(out, id, true, + get_config_val(CONFIG_DEFAULT_FK), + get_config_val(CONFIG_DEFAULT_MK), + get_config_val(CONFIG_DEFAULT_FA), + get_config_val(CONFIG_DEFAULT_MA))); + } + else if (!watched_races[id]->isWatched) { + DEBUG(status,out).print("watching: %s\n", opts.command.c_str()); + watched_races[id]->isWatched = true; + } + } + else if (opts.command == "unwatch") { + if (!watched_races.count(id)) { + watched_races.emplace(id, + new WatchedRace(out, id, false, + get_config_val(CONFIG_DEFAULT_FK), + get_config_val(CONFIG_DEFAULT_MK), + get_config_val(CONFIG_DEFAULT_FA), + get_config_val(CONFIG_DEFAULT_MA))); + } + else if (watched_races[id]->isWatched) { + DEBUG(status,out).print("unwatching: %s\n", opts.command.c_str()); + watched_races[id]->isWatched = false; + } + } else if (opts.command == "forget") { - watched_races[id]->RemoveConfig(out); - watched_races.erase(id); + if (watched_races.count(id)) { + DEBUG(status,out).print("forgetting: %s\n", opts.command.c_str()); + watched_races[id]->RemoveConfig(out); + delete watched_races[id]; + watched_races.erase(id); + } continue; } watched_races[id]->UpdateConfig(out); @@ -776,7 +803,10 @@ static bool isInBuiltCageRoom(df::unit *unit) { } static void autobutcher_cycle(color_ostream &out) { - DEBUG(cycle,out).print("running autobutcher_cycle\n"); + // mark that we have recently run + cycle_timestamp = world->frame_counter; + + DEBUG(cycle,out).print("running autobutcher cycle\n"); // check if there is anything to watch before walking through units vector if (!get_config_bool(CONFIG_AUTOWATCH)) { @@ -850,14 +880,13 @@ static void autobutcher_cycle(color_ostream &out) { } } - int slaughter_count = 0; for (auto w : watched_races) { - int slaughter_subcount = w.second->ProcessUnits(); - slaughter_count += slaughter_subcount; - if (slaughter_subcount) { + int slaughter_count = w.second->ProcessUnits(); + if (slaughter_count) { stringstream ss; - ss << slaughter_subcount; + ss << slaughter_count; string announce = Units::getRaceNamePluralById(w.first) + " marked for slaughter: " + ss.str(); + DEBUG(cycle,out).print("%s\n", announce.c_str()); Gui::showAnnouncement(announce, 2, false); } } diff --git a/plugins/autonestbox.cpp b/plugins/autonestbox.cpp index 10c0e3cd8..668938c2c 100644 --- a/plugins/autonestbox.cpp +++ b/plugins/autonestbox.cpp @@ -73,7 +73,7 @@ static bool set_config_val(int index, int value) { } static bool did_complain = false; // avoids message spam -static size_t cycle_counter = 0; // how many ticks since the last cycle +static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle struct autonestbox_options { // whether to display help @@ -168,8 +168,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan } DFhackCExport command_result plugin_onupdate(color_ostream &out) { - if (is_enabled && ++cycle_counter >= - (size_t)get_config_val(CONFIG_CYCLE_TICKS)) + if (is_enabled && world->frame_counter - cycle_timestamp >= get_config_val(CONFIG_CYCLE_TICKS)) autonestbox_cycle(out); return CR_OK; } @@ -342,7 +341,7 @@ static bool assignUnitToZone(color_ostream &out, df::unit *unit, df::building *b df::general_ref_building_civzone_assignedst *ref = createCivzoneRef(); if (!ref) { ERR(cycle,out).print("Could not find a clonable activity zone reference!" - " You need to pen/pasture/pit at least one creature" + " You need to manually pen/pasture/pit at least one creature" " before autonestbox can function.\n"); return false; } @@ -381,6 +380,8 @@ static size_t assign_nestboxes(color_ostream &out) { DEBUG(cycle,out).print("Failed to assign unit to building.\n"); return processed; } + DEBUG(cycle,out).print("assigned unit %d to zone %d\n", + free_unit->id, free_building->id); ++processed; } } while (free_unit && free_building); @@ -406,13 +407,16 @@ static size_t assign_nestboxes(color_ostream &out) { static void autonestbox_cycle(color_ostream &out) { // mark that we have recently run - cycle_counter = 0; + cycle_timestamp = world->frame_counter; + + DEBUG(cycle,out).print("running autonestbox cycle\n"); size_t processed = assign_nestboxes(out); if (processed > 0) { stringstream ss; ss << processed << " nestboxes were assigned."; string announce = ss.str(); + DEBUG(cycle,out).print("%s\n", announce.c_str()); Gui::showAnnouncement(announce, 2, false); out << announce << endl; // can complain again From 1dec977476c606ed048289f1d7e8f851d6a16795 Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 2 Aug 2022 11:07:36 -0700 Subject: [PATCH 056/111] clean up and add logging to state persistence --- plugins/autonestbox.cpp | 43 +++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/plugins/autonestbox.cpp b/plugins/autonestbox.cpp index 668938c2c..ea8d7a50d 100644 --- a/plugins/autonestbox.cpp +++ b/plugins/autonestbox.cpp @@ -65,11 +65,15 @@ static int get_config_val(int index) { return -1; return config.ival(index); } -static bool set_config_val(int index, int value) { - if (!config.isValid()) - return false; - config.ival(index) = value; - return true; +static bool get_config_bool(int index) { + return get_config_val(index) == 1; +} +static void set_config_val(int index, int value) { + if (config.isValid()) + config.ival(index) = value; +} +static void set_config_bool(int index, bool value) { + set_config_val(index, value ? 1 : 0); } static bool did_complain = false; // avoids message spam @@ -101,23 +105,24 @@ static void autonestbox_cycle(color_ostream &out); static void init_autonestbox(color_ostream &out) { config = World::GetPersistentData(CONFIG_KEY); - if (!config.isValid()) + if (!config.isValid()) { + DEBUG(status,out).print("no config found in this save; initializing\n"); config = World::AddPersistentData(CONFIG_KEY); - - if (get_config_val(CONFIG_IS_ENABLED) == -1) { - set_config_val(CONFIG_IS_ENABLED, 0); + set_config_bool(CONFIG_IS_ENABLED, false); set_config_val(CONFIG_CYCLE_TICKS, 6000); } - if (is_enabled) - set_config_val(CONFIG_IS_ENABLED, 1); - else - is_enabled = (get_config_val(CONFIG_IS_ENABLED) == 1); + is_enabled = get_config_bool(CONFIG_IS_ENABLED); + DEBUG(status,out).print("loading persisted enabled state: %s\n", + is_enabled ? "true" : "false"); did_complain = false; } static void cleanup_autonestbox(color_ostream &out) { - is_enabled = false; + if (is_enabled) { + DEBUG(status,out).print("disabling (not persisting)\n"); + is_enabled = false; + } } DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { @@ -140,10 +145,10 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { if (enable != is_enabled) { is_enabled = enable; - if (is_enabled) - init_autonestbox(out); - else - cleanup_autonestbox(out); + DEBUG(status,out).print("%s from the API, persisting\n", + is_enabled ? "enabled" : "disabled"); + set_config_bool(CONFIG_IS_ENABLED, is_enabled); + init_autonestbox(out); } return CR_OK; } @@ -217,7 +222,7 @@ static command_result df_autonestbox(color_ostream &out, vector ¶met autonestbox_cycle(out); } else { - out << "autonestbox is " << (is_enabled ? "" : "not ") << "running\n"; + out << "autonestbox is " << (is_enabled ? "" : "not ") << "running" << endl; } return CR_OK; } From 1695919411635922a2aece78c8134bc91d7db6c3 Mon Sep 17 00:00:00 2001 From: myk002 Date: Wed, 3 Aug 2022 22:40:55 -0700 Subject: [PATCH 057/111] apply canonical class 3 plugin structure --- plugins/autobutcher.cpp | 301 +++++++++++++++++++++------------------- plugins/autonestbox.cpp | 134 +++++++++--------- 2 files changed, 221 insertions(+), 214 deletions(-) diff --git a/plugins/autobutcher.cpp b/plugins/autobutcher.cpp index 0b0d4ffdb..ee93d30e5 100644 --- a/plugins/autobutcher.cpp +++ b/plugins/autobutcher.cpp @@ -1,16 +1,19 @@ -// - full automation of marking live-stock for slaughtering -// races can be added to a watchlist and it can be set how many male/female kids/adults are left alive -// adding to the watchlist can be automated as well. -// config for autobutcher (state and sleep setting) is saved the first time autobutcher is started -// config for watchlist entries is saved when they are created or modified +// full automation of marking live-stock for slaughtering +// races can be added to a watchlist and it can be set how many male/female kids/adults are left alive +// adding to the watchlist can be automated as well. +// config for autobutcher (state and sleep setting) is saved the first time autobutcher is started +// config for watchlist entries is saved when they are created or modified +#include #include #include +#include #include "df/building_cagest.h" #include "df/creature_raw.h" #include "df/world.h" +#include "Core.h" #include "Debug.h" #include "LuaTools.h" #include "PluginManager.h" @@ -33,6 +36,56 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled); REQUIRE_GLOBAL(world); +// logging levels can be dynamically controlled with the `debugfilter` command. +namespace DFHack { + // for configuration-related logging + DBG_DECLARE(autobutcher, status, DebugCategory::LINFO); + // for logging during the periodic scan + DBG_DECLARE(autobutcher, cycle, DebugCategory::LINFO); +} + +static const string CONFIG_KEY = string(plugin_name) + "/config"; +static const string WATCHLIST_CONFIG_KEY_PREFIX = string(plugin_name) + "/watchlist/"; +static PersistentDataItem config; + +enum ConfigValues { + CONFIG_IS_ENABLED = 0, + CONFIG_CYCLE_TICKS = 1, + CONFIG_AUTOWATCH = 2, + CONFIG_DEFAULT_FK = 3, + CONFIG_DEFAULT_MK = 4, + CONFIG_DEFAULT_FA = 5, + CONFIG_DEFAULT_MA = 6, +}; +static int get_config_val(int index) { + if (!config.isValid()) + return -1; + return config.ival(index); +} +static bool get_config_bool(int index) { + return get_config_val(index) == 1; +} +static void set_config_val(int index, int value) { + if (config.isValid()) + config.ival(index) = value; +} +static void set_config_bool(int index, bool value) { + set_config_val(index, value ? 1 : 0); +} + +struct WatchedRace; +// vector of races handled by autobutcher +// the name is a bit misleading since entries can be set to 'unwatched' +// to ignore them for a while but still keep the target count settings +static unordered_map watched_races; +static unordered_map race_to_id; +static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle + +static void init_autobutcher(color_ostream &out); +static void cleanup_autobutcher(color_ostream &out); +static command_result df_autobutcher(color_ostream &out, vector ¶meters); +static void autobutcher_cycle(color_ostream &out); + const string autobutcher_help = "Automatically butcher excess livestock. This plugin monitors how many pets\n" "you have of each gender and age and assigns excess lifestock for slaughter\n" @@ -115,44 +168,90 @@ const string autobutcher_help = " and llamas (for wool), and pigs (for milk and meat). All other unnamed tame units will be marked\n" " for slaughter as soon as they arrive in your fortress.\n"; -namespace DFHack { - DBG_DECLARE(autobutcher, status); - DBG_DECLARE(autobutcher, cycle); +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + commands.push_back(PluginCommand( + plugin_name, + "Automatically butcher excess livestock.", + df_autobutcher, + false, + autobutcher_help.c_str())); + return CR_OK; } -static const string CONFIG_KEY = "autobutcher/config"; -static const string WATCHLIST_CONFIG_KEY_PREFIX = "autobutcher/watchlist/"; +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { + if (!Core::getInstance().isWorldLoaded()) { + out.printerr("Cannot enable %s without a loaded world.\n", plugin_name); + return CR_FAILURE; + } -static PersistentDataItem config; -enum ConfigValues { - CONFIG_IS_ENABLED = 0, - CONFIG_CYCLE_TICKS = 1, - CONFIG_AUTOWATCH = 2, - CONFIG_DEFAULT_FK = 3, - CONFIG_DEFAULT_MK = 4, - CONFIG_DEFAULT_FA = 5, - CONFIG_DEFAULT_MA = 6, -}; -static int get_config_val(int index) { - if (!config.isValid()) - return -1; - return config.ival(index); + if (enable != is_enabled) { + is_enabled = enable; + DEBUG(status,out).print("%s from the API; persisting\n", + is_enabled ? "enabled" : "disabled"); + set_config_bool(CONFIG_IS_ENABLED, is_enabled); + } else { + DEBUG(status,out).print("%s from the API, but already %s; no action\n", + is_enabled ? "enabled" : "disabled", + is_enabled ? "enabled" : "disabled"); + } + return CR_OK; } -static bool get_config_bool(int index) { - return get_config_val(index) == 1; + +DFhackCExport command_result plugin_shutdown (color_ostream &out) { + DEBUG(status,out).print("shutting down %s\n", plugin_name); + cleanup_autobutcher(out); + return CR_OK; } -static void set_config_val(int index, int value) { - if (config.isValid()) - config.ival(index) = value; + +DFhackCExport command_result plugin_load_data (color_ostream &out) { + config = World::GetPersistentData(CONFIG_KEY); + + if (!config.isValid()) { + DEBUG(status,out).print("no config found in this save; initializing\n"); + config = World::AddPersistentData(CONFIG_KEY); + set_config_bool(CONFIG_IS_ENABLED, is_enabled); + set_config_val(CONFIG_CYCLE_TICKS, 6000); + set_config_bool(CONFIG_AUTOWATCH, false); + set_config_val(CONFIG_DEFAULT_FK, 5); + set_config_val(CONFIG_DEFAULT_MK, 1); + set_config_val(CONFIG_DEFAULT_FA, 5); + set_config_val(CONFIG_DEFAULT_MA, 1); + } + + // we have to copy our enabled flag into the global plugin variable, but + // all the other state we can directly read/modify from the persistent + // data structure. + is_enabled = get_config_bool(CONFIG_IS_ENABLED); + DEBUG(status,out).print("loading persisted enabled state: %s\n", + is_enabled ? "true" : "false"); + + // load the persisted watchlist + init_autobutcher(out); + + return CR_OK; } -static void set_config_bool(int index, bool value) { - set_config_val(index, value ? 1 : 0); + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { + if (event == DFHack::SC_WORLD_UNLOADED) { + if (is_enabled) { + DEBUG(status,out).print("world unloaded; disabling %s\n", + plugin_name); + is_enabled = false; + } + cleanup_autobutcher(out); + } + return CR_OK; } -static unordered_map race_to_id; -static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle +DFhackCExport command_result plugin_onupdate(color_ostream &out) { + if (is_enabled && world->frame_counter - cycle_timestamp >= get_config_val(CONFIG_CYCLE_TICKS)) + autobutcher_cycle(out); + return CR_OK; +} -static size_t DEFAULT_CYCLE_TICKS = 6000; +///////////////////////////////////////////////////// +// autobutcher config logic +// struct autobutcher_options { // whether to display help @@ -199,68 +298,30 @@ static const struct_field_info autobutcher_options_fields[] = { }; struct_identity autobutcher_options::_identity(sizeof(autobutcher_options), &df::allocator_fn, NULL, "autobutcher_options", NULL, autobutcher_options_fields); -static void init_autobutcher(color_ostream &out); -static void cleanup_autobutcher(color_ostream &out); -static command_result df_autobutcher(color_ostream &out, vector ¶meters); -static void autobutcher_cycle(color_ostream &out); - -DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand( - "autobutcher", - "Automatically butcher excess livestock.", - df_autobutcher, - false, - autobutcher_help.c_str())); - - init_autobutcher(out); - return CR_OK; -} - -DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { - if (!Maps::IsValid()) { - out.printerr("Cannot run autobutcher without a loaded map.\n"); - return CR_FAILURE; - } +static bool get_options(color_ostream &out, + autobutcher_options &opts, + const vector ¶meters) +{ + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); - if (enable != is_enabled) { - is_enabled = enable; - if (is_enabled) - init_autobutcher(out); - else - cleanup_autobutcher(out); + if (!lua_checkstack(L, parameters.size() + 2) || + !Lua::PushModulePublic( + out, L, "plugins.autobutcher", "parse_commandline")) { + out.printerr("Failed to load autobutcher Lua code\n"); + return false; } - return CR_OK; -} -DFhackCExport command_result plugin_shutdown (color_ostream &out) { - cleanup_autobutcher(out); - return CR_OK; -} + Lua::Push(L, &opts); + for (const string ¶m : parameters) + Lua::Push(L, param); -DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { - switch (event) { - case DFHack::SC_MAP_LOADED: - init_autobutcher(out); - break; - case DFHack::SC_MAP_UNLOADED: - cleanup_autobutcher(out); - break; - default: - break; - } - return CR_OK; -} + if (!Lua::SafeCall(out, L, parameters.size() + 1, 0)) + return false; -DFhackCExport command_result plugin_onupdate(color_ostream &out) { - if (is_enabled && world->frame_counter - cycle_timestamp >= get_config_val(CONFIG_CYCLE_TICKS)) - autobutcher_cycle(out); - return CR_OK; + return true; } -///////////////////////////////////////////////////// -// autobutcher config logic -// - static void doMarkForSlaughter(df::unit *unit) { unit->flags2.bits.slaughter = 1; } @@ -460,35 +521,7 @@ public: } }; -// vector of races handled by autobutcher -// the name is a bit misleading since entries can be set to 'unwatched' -// to ignore them for a while but still keep the target count settings -static unordered_map watched_races; - static void init_autobutcher(color_ostream &out) { - config = World::GetPersistentData(CONFIG_KEY); - - if (!config.isValid()) - config = World::AddPersistentData(CONFIG_KEY); - - if (get_config_val(CONFIG_IS_ENABLED) == -1) { - set_config_bool(CONFIG_IS_ENABLED, false); - set_config_val(CONFIG_CYCLE_TICKS, DEFAULT_CYCLE_TICKS); - set_config_bool(CONFIG_AUTOWATCH, false); - set_config_val(CONFIG_DEFAULT_FK, 5); - set_config_val(CONFIG_DEFAULT_MK, 1); - set_config_val(CONFIG_DEFAULT_FA, 5); - set_config_val(CONFIG_DEFAULT_MA, 1); - } - - if (is_enabled) - set_config_bool(CONFIG_IS_ENABLED, true); - else - is_enabled = (get_config_val(CONFIG_IS_ENABLED) == 1); - - if (!config.isValid()) - return; - if (!race_to_id.size()) { const size_t num_races = world->raws.creatures.all.size(); for(size_t i = 0; i < num_races; ++i) @@ -505,37 +538,13 @@ static void init_autobutcher(color_ostream &out) { } static void cleanup_autobutcher(color_ostream &out) { - is_enabled = false; + DEBUG(status,out).print("cleaning %s state\n", plugin_name); race_to_id.clear(); for (auto w : watched_races) delete w.second; watched_races.clear(); } -static bool get_options(color_ostream &out, - autobutcher_options &opts, - const vector ¶meters) -{ - auto L = Lua::Core::State; - Lua::StackUnwinder top(L); - - if (!lua_checkstack(L, parameters.size() + 2) || - !Lua::PushModulePublic( - out, L, "plugins.autobutcher", "parse_commandline")) { - out.printerr("Failed to load autobutcher Lua code\n"); - return false; - } - - Lua::Push(L, &opts); - for (const string ¶m : parameters) - Lua::Push(L, param); - - if (!Lua::SafeCall(out, L, parameters.size() + 1, 0)) - return false; - - return true; -} - static void autobutcher_export(color_ostream &out); static void autobutcher_status(color_ostream &out); static void autobutcher_target(color_ostream &out, const autobutcher_options &opts); @@ -544,8 +553,8 @@ static void autobutcher_modify_watchlist(color_ostream &out, const autobutcher_o static command_result df_autobutcher(color_ostream &out, vector ¶meters) { CoreSuspender suspend; - if (!Maps::IsValid()) { - out.printerr("Cannot run autobutcher without a loaded map.\n"); + if (!Core::getInstance().isWorldLoaded()) { + out.printerr("Cannot run %s without a loaded world.\n", plugin_name); return CR_FAILURE; } @@ -763,7 +772,7 @@ static void autobutcher_modify_watchlist(color_ostream &out, const autobutcher_o } ///////////////////////////////////////////////////// -// autobutcher cycle logic +// cycle logic // // check if contained in item (e.g. animals in cages) @@ -806,7 +815,7 @@ static void autobutcher_cycle(color_ostream &out) { // mark that we have recently run cycle_timestamp = world->frame_counter; - DEBUG(cycle,out).print("running autobutcher cycle\n"); + DEBUG(cycle,out).print("running %s cycle\n", plugin_name); // check if there is anything to watch before walking through units vector if (!get_config_bool(CONFIG_AUTOWATCH)) { diff --git a/plugins/autonestbox.cpp b/plugins/autonestbox.cpp index ea8d7a50d..c47f4425f 100644 --- a/plugins/autonestbox.cpp +++ b/plugins/autonestbox.cpp @@ -4,6 +4,9 @@ // maybe check for minimum age? it's not that useful to fill nestboxes with freshly hatched birds // state and sleep setting is saved the first time autonestbox is started (to avoid writing stuff if the plugin is never used) +#include +#include + #include "df/building_cagest.h" #include "df/building_civzonest.h" #include "df/building_nest_boxst.h" @@ -16,7 +19,6 @@ #include "modules/Buildings.h" #include "modules/Gui.h" -#include "modules/Maps.h" #include "modules/Persistence.h" #include "modules/Units.h" #include "modules/World.h" @@ -50,11 +52,13 @@ static const string autonestbox_help = " The default is 6000 (about 8 days)\n"; namespace DFHack { - DBG_DECLARE(autonestbox, status); - DBG_DECLARE(autonestbox, cycle); + // for configuration-related logging + DBG_DECLARE(autonestbox, status, DebugCategory::LINFO); + // for logging during the periodic scan + DBG_DECLARE(autonestbox, cycle, DebugCategory::LINFO); } -static const string CONFIG_KEY = "autonestbox/config"; +static const string CONFIG_KEY = string(plugin_name) + "/config"; static PersistentDataItem config; enum ConfigValues { CONFIG_IS_ENABLED = 0, @@ -79,95 +83,65 @@ static void set_config_bool(int index, bool value) { static bool did_complain = false; // avoids message spam static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle -struct autonestbox_options { - // whether to display help - bool help = false; - - // whether to run a cycle right now - bool now = false; - - // how many ticks to wait between automatic cycles, -1 means unset - int32_t ticks = -1; - - static struct_identity _identity; -}; -static const struct_field_info autonestbox_options_fields[] = { - { struct_field_info::PRIMITIVE, "help", offsetof(autonestbox_options, help), &df::identity_traits::identity, 0, 0 }, - { struct_field_info::PRIMITIVE, "now", offsetof(autonestbox_options, now), &df::identity_traits::identity, 0, 0 }, - { struct_field_info::PRIMITIVE, "ticks", offsetof(autonestbox_options, ticks), &df::identity_traits::identity, 0, 0 }, - { struct_field_info::END } -}; -struct_identity autonestbox_options::_identity(sizeof(autonestbox_options), &df::allocator_fn, NULL, "autonestbox_options", NULL, autonestbox_options_fields); - static command_result df_autonestbox(color_ostream &out, vector ¶meters); static void autonestbox_cycle(color_ostream &out); -static void init_autonestbox(color_ostream &out) { - config = World::GetPersistentData(CONFIG_KEY); - - if (!config.isValid()) { - DEBUG(status,out).print("no config found in this save; initializing\n"); - config = World::AddPersistentData(CONFIG_KEY); - set_config_bool(CONFIG_IS_ENABLED, false); - set_config_val(CONFIG_CYCLE_TICKS, 6000); - } - - is_enabled = get_config_bool(CONFIG_IS_ENABLED); - DEBUG(status,out).print("loading persisted enabled state: %s\n", - is_enabled ? "true" : "false"); - did_complain = false; -} - -static void cleanup_autonestbox(color_ostream &out) { - if (is_enabled) { - DEBUG(status,out).print("disabling (not persisting)\n"); - is_enabled = false; - } -} - DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "autonestbox", + plugin_name, "Auto-assign egg-laying female pets to nestbox zones.", df_autonestbox, false, autonestbox_help.c_str())); - - init_autonestbox(out); return CR_OK; } DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { - if (!Maps::IsValid()) { - out.printerr("Cannot run autonestbox without a loaded map.\n"); + if (!Core::getInstance().isWorldLoaded()) { + out.printerr("Cannot enable %s without a loaded world.\n", plugin_name); return CR_FAILURE; } if (enable != is_enabled) { is_enabled = enable; - DEBUG(status,out).print("%s from the API, persisting\n", + DEBUG(status,out).print("%s from the API; persisting\n", is_enabled ? "enabled" : "disabled"); set_config_bool(CONFIG_IS_ENABLED, is_enabled); - init_autonestbox(out); + } else { + DEBUG(status,out).print("%s from the API, but already %s; no action\n", + is_enabled ? "enabled" : "disabled", + is_enabled ? "enabled" : "disabled"); } return CR_OK; } -DFhackCExport command_result plugin_shutdown (color_ostream &out) { - cleanup_autonestbox(out); +DFhackCExport command_result plugin_load_data (color_ostream &out) { + config = World::GetPersistentData(CONFIG_KEY); + + if (!config.isValid()) { + DEBUG(status,out).print("no config found in this save; initializing\n"); + config = World::AddPersistentData(CONFIG_KEY); + set_config_bool(CONFIG_IS_ENABLED, is_enabled); + set_config_val(CONFIG_CYCLE_TICKS, 6000); + } + + // we have to copy our enabled flag into the global plugin variable, but + // all the other state we can directly read/modify from the persistent + // data structure. + is_enabled = get_config_bool(CONFIG_IS_ENABLED); + DEBUG(status,out).print("loading persisted enabled state: %s\n", + is_enabled ? "true" : "false"); + did_complain = false; return CR_OK; } DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { - switch (event) { - case DFHack::SC_MAP_LOADED: - init_autonestbox(out); - break; - case DFHack::SC_MAP_UNLOADED: - cleanup_autonestbox(out); - break; - default: - break; + if (event == DFHack::SC_WORLD_UNLOADED) { + if (is_enabled) { + DEBUG(status,out).print("world unloaded; disabling %s\n", + plugin_name); + is_enabled = false; + } } return CR_OK; } @@ -178,6 +152,30 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) { return CR_OK; } +///////////////////////////////////////////////////// +// configuration interface +// + +struct autonestbox_options { + // whether to display help + bool help = false; + + // whether to run a cycle right now + bool now = false; + + // how many ticks to wait between automatic cycles, -1 means unset + int32_t ticks = -1; + + static struct_identity _identity; +}; +static const struct_field_info autonestbox_options_fields[] = { + { struct_field_info::PRIMITIVE, "help", offsetof(autonestbox_options, help), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "now", offsetof(autonestbox_options, now), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "ticks", offsetof(autonestbox_options, ticks), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::END } +}; +struct_identity autonestbox_options::_identity(sizeof(autonestbox_options), &df::allocator_fn, NULL, "autonestbox_options", NULL, autonestbox_options_fields); + static bool get_options(color_ostream &out, autonestbox_options &opts, const vector ¶meters) @@ -205,8 +203,8 @@ static bool get_options(color_ostream &out, static command_result df_autonestbox(color_ostream &out, vector ¶meters) { CoreSuspender suspend; - if (!Maps::IsValid()) { - out.printerr("Cannot run autonestbox without a loaded map.\n"); + if (!Core::getInstance().isWorldLoaded()) { + out.printerr("Cannot run %s without a loaded world.\n", plugin_name); return CR_FAILURE; } @@ -228,7 +226,7 @@ static command_result df_autonestbox(color_ostream &out, vector ¶met } ///////////////////////////////////////////////////// -// autonestbox cycle logic +// cycle logic // static bool isEmptyPasture(df::building *building) { From c73200bf66dc4e93d6de6e572229e2d9c0cbad5a Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sat, 6 Aug 2022 07:16:47 +0000 Subject: [PATCH 058/111] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 6a66be4fa..f37c0d022 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 6a66be4facd1d5a7b3ab4cb61c34c678d19e0795 +Subproject commit f37c0d022135ca10fb5375c4e56e11215bf208ae From ac175affbc2d2a5928a941239cba656bf886996c Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 6 Aug 2022 12:22:27 -0400 Subject: [PATCH 059/111] Make renderer-msg draw somewhat more reliably From g_src (enabler.cpp: renderer::display()), either update_all() or update_tile() is called at least once per frame --- plugins/devel/renderer-msg.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/plugins/devel/renderer-msg.cpp b/plugins/devel/renderer-msg.cpp index e6a33114f..4d5da09a6 100644 --- a/plugins/devel/renderer-msg.cpp +++ b/plugins/devel/renderer-msg.cpp @@ -23,13 +23,22 @@ struct scdata { }; struct renderer_msg : public Renderer::renderer_wrap { - virtual void update_tile (int32_t x, int32_t y) { + virtual void update_tile(int32_t x, int32_t y) override { + draw_message(); + renderer_wrap::update_tile(x, y); + } + + virtual void update_all() override { + draw_message(); + renderer_wrap::update_all(); + } + + void draw_message() { static std::string str = std::string("DFHack: ") + plugin_name + " active"; - Screen::paintString(Screen::Pen(' ', 9, 0), 0, gps->dimy - 1, str); + Screen::paintString(Screen::Pen(' ', COLOR_LIGHTCYAN, COLOR_GREEN), 0, gps->dimy - 1, str); for (int32_t i = 0; i < gps->dimx; ++i) ((scdata*)screen)[i * gps->dimy + gps->dimy - 1].bg = 2; - renderer_wrap::update_tile(x, y); - }; + } }; DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) From 69a84c23c2da0190ee25a10332f31e29183a1c89 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 6 Aug 2022 12:33:25 -0400 Subject: [PATCH 060/111] renderer-msg: draw less often suggested by Quietust --- plugins/devel/renderer-msg.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/plugins/devel/renderer-msg.cpp b/plugins/devel/renderer-msg.cpp index 4d5da09a6..1f26886c6 100644 --- a/plugins/devel/renderer-msg.cpp +++ b/plugins/devel/renderer-msg.cpp @@ -23,6 +23,8 @@ struct scdata { }; struct renderer_msg : public Renderer::renderer_wrap { + bool message_dirty = true; // force redraw when renderer is installed + virtual void update_tile(int32_t x, int32_t y) override { draw_message(); renderer_wrap::update_tile(x, y); @@ -33,11 +35,20 @@ struct renderer_msg : public Renderer::renderer_wrap { renderer_wrap::update_all(); } + virtual void render() override { + message_dirty = true; + renderer_wrap::render(); + } + void draw_message() { - static std::string str = std::string("DFHack: ") + plugin_name + " active"; - Screen::paintString(Screen::Pen(' ', COLOR_LIGHTCYAN, COLOR_GREEN), 0, gps->dimy - 1, str); - for (int32_t i = 0; i < gps->dimx; ++i) - ((scdata*)screen)[i * gps->dimy + gps->dimy - 1].bg = 2; + if (message_dirty) { + static std::string str = std::string("DFHack: ") + plugin_name + " active"; + Screen::paintString(Screen::Pen(' ', COLOR_LIGHTCYAN, COLOR_GREEN), 0, gps->dimy - 1, str); + for (int32_t i = 0; i < gps->dimx; ++i) + ((scdata*)screen)[i * gps->dimy + gps->dimy - 1].bg = 2; + + message_dirty = false; + } } }; From 8930f30b72c2c4d3486a70ab05ee350c55295ef9 Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 7 Aug 2022 22:36:47 -0700 Subject: [PATCH 061/111] remove unuseful command from automelt --- plugins/automelt.cpp | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/plugins/automelt.cpp b/plugins/automelt.cpp index 852b324d6..4bbb727d7 100644 --- a/plugins/automelt.cpp +++ b/plugins/automelt.cpp @@ -250,19 +250,6 @@ IMPLEMENT_VMETHOD_INTERPOSE(melt_hook, feed); IMPLEMENT_VMETHOD_INTERPOSE(melt_hook, render); -static command_result automelt_cmd(color_ostream &out, vector & parameters) -{ - if (!parameters.empty()) - { - if (parameters.size() == 1 && toLower(parameters[0])[0] == 'v') - { - out << "Automelt" << endl << "Version: " << PLUGIN_VERSION << endl; - } - } - - return CR_OK; -} - DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { switch (event) @@ -296,15 +283,12 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - commands.push_back( - PluginCommand( - "automelt", "Automatically melt metal items in marked stockpiles.", - automelt_cmd, false, "")); - return CR_OK; } DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { + // ensure we disengage our hooks + plugin_enable(out, false); return CR_OK; } From a01114a41bbe6cb19edb88bcd566c32dbecd2f62 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sat, 13 Aug 2022 07:17:18 +0000 Subject: [PATCH 062/111] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index f37c0d022..4c7eecb5c 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit f37c0d022135ca10fb5375c4e56e11215bf208ae +Subproject commit 4c7eecb5cbeeead3c4374fafc562ee774e8e229e From 09401ac3707ad6348b2287f8a9464ee06ea8986d Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sun, 14 Aug 2022 07:16:48 +0000 Subject: [PATCH 063/111] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 4c7eecb5c..3d65c9a75 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 4c7eecb5cbeeead3c4374fafc562ee774e8e229e +Subproject commit 3d65c9a75a8c7a3607e71529f6264bab3e637755 From 9f648d532ee29897dbf3acd5bfafb4d15b7932fd Mon Sep 17 00:00:00 2001 From: myk002 Date: Sun, 31 Jul 2022 14:22:21 -0700 Subject: [PATCH 064/111] modify seedwatch all to actually watch all seeds --- docs/changelog.txt | 1 + plugins/seedwatch.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 2733cd539..637dd03f0 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -51,6 +51,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - ``materials.ItemTraitsDialog``: added a default ``on_select``-handler which toggles the traits. - `orders`: added useful library of manager orders. see them with ``orders list`` and import them with, for example, ``orders import library/basic`` - `prospect`: add new ``--show`` option to give the player control over which report sections are shown. e.g. ``prospect all --show ores`` will just show information on ores. +- `seedwatch`: ``seedwatch all`` now adds all plants with seeds to the watchlist, not just the "basic" crops. ## Documentation diff --git a/plugins/seedwatch.cpp b/plugins/seedwatch.cpp index 478a0899a..65b89cfa1 100644 --- a/plugins/seedwatch.cpp +++ b/plugins/seedwatch.cpp @@ -132,7 +132,9 @@ command_result df_seedwatch(color_ostream &out, vector& parameters) map plantIDs; for(size_t i = 0; i < world->raws.plants.all.size(); ++i) { - plantIDs[world->raws.plants.all[i]->id] = i; + auto & plant = world->raws.plants.all[i]; + if (plant->material_defs.type[plant_material_def::seed] != -1) + plantIDs[plant->id] = i; } t_gamemodes gm; @@ -226,10 +228,8 @@ command_result df_seedwatch(color_ostream &out, vector& parameters) if(limit < 0) limit = 0; if(parameters[0] == "all") { - for(auto i = abbreviations.begin(); i != abbreviations.end(); ++i) - { - if(plantIDs.count(i->second) > 0) Kitchen::setLimit(plantIDs[i->second], limit); - } + for(auto & entry : plantIDs) + Kitchen::setLimit(entry.second, limit); } else { From 2f9021a3a0911d1cd39108535e02b91f4abb8db3 Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 2 Aug 2022 12:56:44 -0700 Subject: [PATCH 065/111] move examples to the examples folder --- docs/Dev-intro.rst | 4 +-- plugins/CMakeLists.txt | 6 ---- plugins/{skeleton => examples}/skeleton.cpp | 0 .../{skeleton => examples}/skeletonShort.cpp | 0 plugins/skeleton/CMakeLists.txt | 36 ------------------- plugins/skeleton/skeleton.h | 1 - 6 files changed, 2 insertions(+), 45 deletions(-) rename plugins/{skeleton => examples}/skeleton.cpp (100%) rename plugins/{skeleton => examples}/skeletonShort.cpp (100%) delete mode 100644 plugins/skeleton/CMakeLists.txt delete mode 100644 plugins/skeleton/skeleton.h diff --git a/docs/Dev-intro.rst b/docs/Dev-intro.rst index 758bf225f..d4167aae3 100644 --- a/docs/Dev-intro.rst +++ b/docs/Dev-intro.rst @@ -22,7 +22,7 @@ Plugins DFHack plugins are written in C++ and located in the ``plugins`` folder. Currently, documentation on how to write plugins is somewhat sparse. There are -templates that you can use to get started in the ``plugins/skeleton`` +templates that you can use to get started in the ``plugins/examples`` folder, and the source code of existing plugins can also be helpful. If you want to compile a plugin that you have just added, you will need to add a @@ -35,7 +35,7 @@ other commands). Plugins can also register handlers to run on every tick, and can interface with the built-in `enable` and `disable` commands. For the full plugin API, see the -skeleton plugins or ``PluginManager.cpp``. +example plugins or ``PluginManager.cpp``. Installed plugins live in the ``hack/plugins`` folder of a DFHack installation, and the `load` family of commands can be used to load a recompiled plugin diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 338db4ab9..6a34117b9 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -185,12 +185,6 @@ if(BUILD_SUPPORTED) # see instructions for adding "external" plugins at the end of this file. endif() -# this is the skeleton plugin. If you want to make your own, make a copy and then change it -option(BUILD_SKELETON "Build the skeleton plugin." OFF) -if(BUILD_SKELETON) - add_subdirectory(skeleton) -endif() - macro(subdirlist result subdir) file(GLOB children ABSOLUTE ${subdir}/ ${subdir}/*/) set(dirlist "") diff --git a/plugins/skeleton/skeleton.cpp b/plugins/examples/skeleton.cpp similarity index 100% rename from plugins/skeleton/skeleton.cpp rename to plugins/examples/skeleton.cpp diff --git a/plugins/skeleton/skeletonShort.cpp b/plugins/examples/skeletonShort.cpp similarity index 100% rename from plugins/skeleton/skeletonShort.cpp rename to plugins/examples/skeletonShort.cpp diff --git a/plugins/skeleton/CMakeLists.txt b/plugins/skeleton/CMakeLists.txt deleted file mode 100644 index cbe5f7ce6..000000000 --- a/plugins/skeleton/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -project(skeleton) -# A list of source files -set(PROJECT_SRCS - skeleton.cpp -) -# A list of headers -set(PROJECT_HDRS - skeleton.h -) -set_source_files_properties(${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) - -# mash them together (headers are marked as headers and nothing will try to compile them) -list(APPEND PROJECT_SRCS ${PROJECT_HDRS}) - -# option to use a thread for no particular reason -option(SKELETON_THREAD "Use threads in the skeleton plugin." ON) -if(UNIX) - if(APPLE) - set(PROJECT_LIBS - # add any extra mac libraries here - ${PROJECT_LIBS} - ) - else() - set(PROJECT_LIBS - # add any extra linux libraries here - ${PROJECT_LIBS} - ) - endif() -else() - set(PROJECT_LIBS - # add any extra windows libraries here - ${PROJECT_LIBS} - ) -endif() -# this makes sure all the stuff is put in proper places and linked to dfhack -dfhack_plugin(skeleton ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS}) diff --git a/plugins/skeleton/skeleton.h b/plugins/skeleton/skeleton.h deleted file mode 100644 index 6f70f09be..000000000 --- a/plugins/skeleton/skeleton.h +++ /dev/null @@ -1 +0,0 @@ -#pragma once From 0bbbacf161aa0f4bd4869d108f56d85f9c5ec8e6 Mon Sep 17 00:00:00 2001 From: myk002 Date: Tue, 2 Aug 2022 18:40:50 -0700 Subject: [PATCH 066/111] extend the docs and examples in skeleton.cpp --- plugins/examples/skeleton.cpp | 221 +++++++++++++++++++++------------- 1 file changed, 134 insertions(+), 87 deletions(-) diff --git a/plugins/examples/skeleton.cpp b/plugins/examples/skeleton.cpp index 7d5936f6d..e1f552d31 100644 --- a/plugins/examples/skeleton.cpp +++ b/plugins/examples/skeleton.cpp @@ -1,65 +1,71 @@ -// This is a generic plugin that does nothing useful apart from acting as an example... of a plugin that does nothing :D +// This is an example plugin that just documents and implements all the plugin +// callbacks and features. You can compile it, load it, run it, and see the +// debug messages get printed to the console. +// +// See the other example plugins in this directory for plugins that are +// configured for specific use cases (but don't come with as many comments as +// this one does). + +#include +#include + +#include "df/world.h" -// some headers required for a plugin. Nothing special, just the basics. #include "Core.h" -#include -#include -#include -#include -// If you need to save data per-world: -//#include "modules/Persistence.h" +#include "Debug.h" +#include "LuaTools.h" +#include "PluginManager.h" -// DF data structure definition headers -#include "DataDefs.h" -//#include "df/world.h" +#include "modules/Persistence.h" +#include "modules/World.h" -// our own, empty header. -#include "skeleton.h" +using std::string; +using std::vector; using namespace DFHack; -using namespace df::enums; -// Expose the plugin name to the DFHack core, as well as metadata like the DFHack version. -// The name string provided must correspond to the filename - +// Expose the plugin name to the DFHack core, as well as metadata like the +// DFHack version that this plugin was compiled with. This macro provides a +// variable for the plugin name as const char * plugin_name. +// The name provided must correspond to the filename -- // skeleton.plug.so, skeleton.plug.dylib, or skeleton.plug.dll in this case DFHACK_PLUGIN("skeleton"); -// The identifier declared with this macro (ie. enabled) can be specified by the user -// and subsequently used to manage the plugin's operations. -// This will also be tracked by `plug`; when true the plugin will be shown as enabled. -DFHACK_PLUGIN_IS_ENABLED(enabled); +// The identifier declared with this macro (i.e. is_enabled) is used to track +// whether the plugin is in an "enabled" state. If you don't need enablement +// for your plugin, you don't need this line. This variable will also be read +// by the `plug` builtin command; when true the plugin will be shown as enabled. +DFHACK_PLUGIN_IS_ENABLED(is_enabled); // Any globals a plugin requires (e.g. world) should be listed here. // For example, this line expands to "using df::global::world" and prevents the -// plugin from being loaded if df::global::world is null (i.e. missing from symbols.xml): -// +// plugin from being loaded if df::global::world is null (i.e. missing from +// symbols.xml). REQUIRE_GLOBAL(world); -// You may want some compile time debugging options -// one easy system just requires you to cache the color_ostream &out into a global debug variable -//#define P_DEBUG 1 -//uint16_t maxTickFreq = 1200; //maybe you want to use some events +// logging levels can be dynamically controlled with the `debugfilter` command. +namespace DFHack { + // for configuration-related logging + DBG_DECLARE(skeleton, status, DebugCategory::LINFO); + // for logging during the periodic scan + DBG_DECLARE(skeleton, cycle, DebugCategory::LINFO); +} -command_result command_callback1(color_ostream &out, std::vector ¶meters); +command_result command_callback1(color_ostream &out, vector ¶meters); +// run when the plugin is loaded DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("skeleton", - "~54 character description of plugin", //to use one line in the ``[DFHack]# ls`` output - command_callback1, - false, - "example usage" - " skeleton