diff --git a/docs/plugins/buildingplan.rst b/docs/plugins/buildingplan.rst index 1eb18b1d5..c51f79721 100644 --- a/docs/plugins/buildingplan.rst +++ b/docs/plugins/buildingplan.rst @@ -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 the items are available. -This is very useful when combined with manager work orders or `workflow` -- you -can set a constraint to 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. +This is very powerful when used with tools like `quickfort`, which allow you to +set a building plan according to a blueprint, and the buildings will simply be +built when you can build them. + +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 ----- @@ -23,37 +26,27 @@ Usage :: enable buildingplan - buildingplan set + buildingplan [status] buildingplan set true|false -Running ``buildingplan set`` without parameters displays the current settings. - .. _buildingplan-settings: Global settings --------------- -The buildingplan plugin has global settings that can be set from the UI -(:kbd:`G` from any building placement screen, for example: -:kbd:`b`:kbd:`a`:kbd:`G`). These settings can also be set via the -``buildingplan set`` command. The available settings are: +The buildingplan plugin has several global settings that affect what materials +can be chosen when attaching items to planned buildings: -``all_enabled`` (default: false) - Enable planning mode for all building types. ``blocks``, ``boulders``, ``logs``, ``bars`` (defaults: true, true, true, false) Allow blocks, boulders, logs, or bars to be matched for generic "building 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 -your fort, so you only have to set them once and they will be persisted in your -save. +These settings are saved with your fort, so you only have to set them once and +they will be persisted in your save. 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 -always configure buildingplan to just use blocks for buildings and +always configure `buildingplan` to just use blocks for buildings and constructions:: 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 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. diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 039c83b0f..81f026cbc 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -15,14 +15,14 @@ #include "df/job_item.h" #include "df/world.h" -#include +#include #include #include #include using std::map; using std::pair; -using std::queue; +using std::deque; using std::string; using std::unordered_map; using std::vector; @@ -34,11 +34,8 @@ 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(buildingplan, status, DebugCategory::LINFO); - // for logging during the periodic scan DBG_DECLARE(buildingplan, cycle, DebugCategory::LINFO); } @@ -108,7 +105,7 @@ static PersistentDataItem config; // building id -> PlannedBuilding unordered_map planned_buildings; // vector id -> filter bucket -> queue of (building id, job_item index) -map>>> tasks; +map>>> tasks; // 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. @@ -359,7 +356,7 @@ static void finalizeBuilding(color_ostream &out, df::building * bld) { Job::checkBuildingsNow(); } -static df::building * popInvalidTasks(color_ostream &out, queue> & task_queue) { +static df::building * popInvalidTasks(color_ostream &out, deque> & task_queue) { while (!task_queue.empty()) { auto & task = task_queue.front(); auto id = task.first; @@ -369,13 +366,13 @@ static df::building * popInvalidTasks(color_ostream &out, queue>> & buckets) { + map>> & buckets) { auto other_id = ENUM_ATTR(job_item_vector_id, other, vector_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", @@ -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 // be completed with the correct number of items. --job->job_items[filter_idx]->quantity; - task_queue.pop(); + task_queue.pop_front(); if (isJobReady(out, job)) { finalizeBuilding(out, bld); planned_buildings.at(id).remove(out); @@ -586,7 +583,7 @@ static bool registerPlannedBuilding(color_ostream &out, PlannedBuilding & pb) { // as invalid for (auto vector_id : vector_ids) { 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; " "%zu vector(s), %zu filter bucket(s), %zu task(s) in bucket", 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) { DEBUG(status,out).print("entering buildingplan_printStatus\n"); 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(" 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 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("\n"); + + map counts; + int32_t total = 0; + for (auto &buckets : tasks) { + for (auto &bucket_queue : buckets.second) { + deque> &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) { diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index 9b953dd7c..4420f8534 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -4,8 +4,6 @@ local _ENV = mkmodule('plugins.buildingplan') 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 isPlannedBuilding(df::building *bld) * void addPlannedBuilding(df::building *bld) @@ -36,6 +34,15 @@ function parse_commandline(...) return false 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 end