print out more status info for buildingplan

develop
Myk Taylor 2023-02-08 18:47:10 -08:00
parent 4cc01f98e2
commit b443f81ecd
No known key found for this signature in database
3 changed files with 65 additions and 49 deletions

@ -11,11 +11,14 @@ available, and they will be created in a suspended state. Buildingplan will
periodically scan for appropriate items, and the jobs will be unsuspended when periodically scan for appropriate items, and the jobs will be unsuspended when
the items are available. the items are available.
This is very useful when combined with manager work orders or `workflow` -- you This is very powerful when used with tools like `quickfort`, which allow you to
can set a constraint to always have one or two doors/beds/tables/chairs/etc. set a building plan according to a blueprint, and the buildings will simply be
available, and place as many as you like. Materials are used to build the built when you can build them.
planned buildings as they are produced, with minimal space dedicated to
stockpiles. You can use manager work orders or `workflow` to ensure you always have one or
two doors/beds/tables/chairs/etc. available, and place as many as you like.
Materials are used to build the planned buildings as they are produced, with
minimal space dedicated to stockpiles.
Usage Usage
----- -----
@ -23,37 +26,27 @@ Usage
:: ::
enable buildingplan enable buildingplan
buildingplan set buildingplan [status]
buildingplan set <setting> true|false buildingplan set <setting> true|false
Running ``buildingplan set`` without parameters displays the current settings.
.. _buildingplan-settings: .. _buildingplan-settings:
Global settings Global settings
--------------- ---------------
The buildingplan plugin has global settings that can be set from the UI The buildingplan plugin has several global settings that affect what materials
(:kbd:`G` from any building placement screen, for example: can be chosen when attaching items to planned buildings:
:kbd:`b`:kbd:`a`:kbd:`G`). These settings can also be set via the
``buildingplan set`` command. The available settings are:
``all_enabled`` (default: false)
Enable planning mode for all building types.
``blocks``, ``boulders``, ``logs``, ``bars`` (defaults: true, true, true, false) ``blocks``, ``boulders``, ``logs``, ``bars`` (defaults: true, true, true, false)
Allow blocks, boulders, logs, or bars to be matched for generic "building Allow blocks, boulders, logs, or bars to be matched for generic "building
material" items. material" items.
``quickfort_mode`` (default: false)
Enable compatibility mode for the legacy Python Quickfort (this setting is
not required for DFHack `quickfort`)
The settings for ``blocks``, ``boulders``, ``logs``, and ``bars`` are saved with These settings are saved with your fort, so you only have to set them once and
your fort, so you only have to set them once and they will be persisted in your they will be persisted in your save.
save.
If you normally embark with some blocks on hand for early workshops, you might If you normally embark with some blocks on hand for early workshops, you might
want to add this line to your ``dfhack-config/init/onMapLoad.init`` file to want to add this line to your ``dfhack-config/init/onMapLoad.init`` file to
always configure buildingplan to just use blocks for buildings and always configure `buildingplan` to just use blocks for buildings and
constructions:: constructions::
on-new-fortress buildingplan set boulders false; buildingplan set logs false on-new-fortress buildingplan set boulders false; buildingplan set logs false
@ -76,17 +69,3 @@ keep the filter values that were set when the building was placed.
For example, you can be sure that all your constructed walls are the same color For example, you can be sure that all your constructed walls are the same color
by setting a filter to accept only certain types of stone. by setting a filter to accept only certain types of stone.
Quickfort mode
--------------
If you use the external Python Quickfort to apply building blueprints instead of
the native DFHack `quickfort` script, you must enable Quickfort mode. This
temporarily enables buildingplan for all building types and adds an extra blank
screen after every building placement. This "dummy" screen is needed for Python
Quickfort to interact successfully with Dwarf Fortress.
Note that Quickfort mode is only for compatibility with the legacy Python
Quickfort. The DFHack `quickfort` script does not need this Quickfort mode to be
enabled. The `quickfort` script will successfully integrate with buildingplan as
long as the buildingplan plugin itself is enabled.

@ -15,14 +15,14 @@
#include "df/job_item.h" #include "df/job_item.h"
#include "df/world.h" #include "df/world.h"
#include <queue> #include <deque>
#include <string> #include <string>
#include <vector> #include <vector>
#include <unordered_map> #include <unordered_map>
using std::map; using std::map;
using std::pair; using std::pair;
using std::queue; using std::deque;
using std::string; using std::string;
using std::unordered_map; using std::unordered_map;
using std::vector; using std::vector;
@ -34,11 +34,8 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled);
REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(world);
// logging levels can be dynamically controlled with the `debugfilter` command.
namespace DFHack { namespace DFHack {
// for configuration-related logging
DBG_DECLARE(buildingplan, status, DebugCategory::LINFO); DBG_DECLARE(buildingplan, status, DebugCategory::LINFO);
// for logging during the periodic scan
DBG_DECLARE(buildingplan, cycle, DebugCategory::LINFO); DBG_DECLARE(buildingplan, cycle, DebugCategory::LINFO);
} }
@ -108,7 +105,7 @@ static PersistentDataItem config;
// building id -> PlannedBuilding // building id -> PlannedBuilding
unordered_map<int32_t, PlannedBuilding> planned_buildings; unordered_map<int32_t, PlannedBuilding> planned_buildings;
// vector id -> filter bucket -> queue of (building id, job_item index) // vector id -> filter bucket -> queue of (building id, job_item index)
map<df::job_item_vector_id, map<string, queue<pair<int32_t, int>>>> tasks; map<df::job_item_vector_id, map<string, deque<pair<int32_t, int>>>> tasks;
// note that this just removes the PlannedBuilding. the tasks will get dropped // note that this just removes the PlannedBuilding. the tasks will get dropped
// as we discover them in the tasks queues and they fail to be found in planned_buildings. // as we discover them in the tasks queues and they fail to be found in planned_buildings.
@ -359,7 +356,7 @@ static void finalizeBuilding(color_ostream &out, df::building * bld) {
Job::checkBuildingsNow(); Job::checkBuildingsNow();
} }
static df::building * popInvalidTasks(color_ostream &out, queue<pair<int32_t, int>> & task_queue) { static df::building * popInvalidTasks(color_ostream &out, deque<pair<int32_t, int>> & task_queue) {
while (!task_queue.empty()) { while (!task_queue.empty()) {
auto & task = task_queue.front(); auto & task = task_queue.front();
auto id = task.first; auto id = task.first;
@ -369,13 +366,13 @@ static df::building * popInvalidTasks(color_ostream &out, queue<pair<int32_t, in
return bld; return bld;
} }
DEBUG(cycle,out).print("discarding invalid task: bld=%d, job_item_idx=%d\n", id, task.second); DEBUG(cycle,out).print("discarding invalid task: bld=%d, job_item_idx=%d\n", id, task.second);
task_queue.pop(); task_queue.pop_front();
} }
return NULL; return NULL;
} }
static void doVector(color_ostream &out, df::job_item_vector_id vector_id, static void doVector(color_ostream &out, df::job_item_vector_id vector_id,
map<string, queue<pair<int32_t, int>>> & buckets) { map<string, deque<pair<int32_t, int>>> & buckets) {
auto other_id = ENUM_ATTR(job_item_vector_id, other, vector_id); auto other_id = ENUM_ATTR(job_item_vector_id, other, vector_id);
auto item_vector = df::global::world->items.other[other_id]; auto item_vector = df::global::world->items.other[other_id];
DEBUG(cycle,out).print("matching %zu item(s) in vector %s against %zu filter bucket(s)\n", DEBUG(cycle,out).print("matching %zu item(s) in vector %s against %zu filter bucket(s)\n",
@ -423,7 +420,7 @@ static void doVector(color_ostream &out, df::job_item_vector_id vector_id,
// items so if buildingplan is turned off, the building will // items so if buildingplan is turned off, the building will
// be completed with the correct number of items. // be completed with the correct number of items.
--job->job_items[filter_idx]->quantity; --job->job_items[filter_idx]->quantity;
task_queue.pop(); task_queue.pop_front();
if (isJobReady(out, job)) { if (isJobReady(out, job)) {
finalizeBuilding(out, bld); finalizeBuilding(out, bld);
planned_buildings.at(id).remove(out); planned_buildings.at(id).remove(out);
@ -586,7 +583,7 @@ static bool registerPlannedBuilding(color_ostream &out, PlannedBuilding & pb) {
// as invalid // as invalid
for (auto vector_id : vector_ids) { for (auto vector_id : vector_ids) {
for (int item_num = 0; item_num < job_item->quantity; ++item_num) { for (int item_num = 0; item_num < job_item->quantity; ++item_num) {
tasks[vector_id][bucket].push(std::make_pair(id, job_item_idx)); tasks[vector_id][bucket].push_back(std::make_pair(id, job_item_idx));
DEBUG(status,out).print("added task: %s/%s/%d,%d; " DEBUG(status,out).print("added task: %s/%s/%d,%d; "
"%zu vector(s), %zu filter bucket(s), %zu task(s) in bucket", "%zu vector(s), %zu filter bucket(s), %zu task(s) in bucket",
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(), ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
@ -609,13 +606,46 @@ static bool registerPlannedBuilding(color_ostream &out, PlannedBuilding & pb) {
static void printStatus(color_ostream &out) { static void printStatus(color_ostream &out) {
DEBUG(status,out).print("entering buildingplan_printStatus\n"); DEBUG(status,out).print("entering buildingplan_printStatus\n");
out.print("buildingplan is %s\n\n", is_enabled ? "enabled" : "disabled"); out.print("buildingplan is %s\n\n", is_enabled ? "enabled" : "disabled");
out.print(" finding materials for %zd buildings\n", planned_buildings.size());
out.print("Current settings:\n"); out.print("Current settings:\n");
out.print(" use blocks: %s\n", get_config_bool(config, CONFIG_BLOCKS) ? "yes" : "no"); out.print(" use blocks: %s\n", get_config_bool(config, CONFIG_BLOCKS) ? "yes" : "no");
out.print(" use boulders: %s\n", get_config_bool(config, CONFIG_BOULDERS) ? "yes" : "no"); out.print(" use boulders: %s\n", get_config_bool(config, CONFIG_BOULDERS) ? "yes" : "no");
out.print(" use logs: %s\n", get_config_bool(config, CONFIG_LOGS) ? "yes" : "no"); out.print(" use logs: %s\n", get_config_bool(config, CONFIG_LOGS) ? "yes" : "no");
out.print(" use bars: %s\n", get_config_bool(config, CONFIG_BARS) ? "yes" : "no"); out.print(" use bars: %s\n", get_config_bool(config, CONFIG_BARS) ? "yes" : "no");
out.print("\n"); out.print("\n");
map<string, int32_t> counts;
int32_t total = 0;
for (auto &buckets : tasks) {
for (auto &bucket_queue : buckets.second) {
deque<pair<int32_t, int>> &tqueue = bucket_queue.second;
for (auto it = tqueue.begin(); it != tqueue.end();) {
auto & task = *it;
auto id = task.first;
df::building *bld = NULL;
if (!planned_buildings.count(id) ||
!(bld = planned_buildings.at(id).getBuildingIfValidOrRemoveIfNot(out))) {
DEBUG(status,out).print("discarding invalid task: bld=%d, job_item_idx=%d\n",
id, task.second);
it = tqueue.erase(it);
continue;
}
auto *jitem = bld->jobs[0]->job_items[task.second];
int32_t quantity = jitem->quantity;
if (quantity) {
string desc = toLower(ENUM_KEY_STR(item_type, jitem->item_type));
counts[desc] += quantity;
total += quantity;
}
++it;
}
}
}
out.print("Waiting for %d item(s) to be produced or %zd building(s):\n",
total, planned_buildings.size());
for (auto &count : counts)
out.print(" %3d %s\n", count.second, count.first.c_str());
out.print("\n");
} }
static bool setSetting(color_ostream &out, string name, bool value) { static bool setSetting(color_ostream &out, string name, bool value) {

@ -4,8 +4,6 @@ local _ENV = mkmodule('plugins.buildingplan')
Native functions: Native functions:
* void setSetting(string name, boolean value)
* bool isPlanModeEnabled(df::building_type type, int16_t subtype, int32_t custom)
* bool isPlannableBuilding(df::building_type type, int16_t subtype, int32_t custom) * bool isPlannableBuilding(df::building_type type, int16_t subtype, int32_t custom)
* bool isPlannedBuilding(df::building *bld) * bool isPlannedBuilding(df::building *bld)
* void addPlannedBuilding(df::building *bld) * void addPlannedBuilding(df::building *bld)
@ -36,6 +34,15 @@ function parse_commandline(...)
return false return false
end end
local command = table.remove(positionals, 1)
if not command or command == 'status' then
printStatus()
elseif command == 'set' then
setSetting(positionals[1], positionals[2] == 'true')
else
return false
end
return true return true
end end